/*
 * Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 *
 * You can also choose to distribute this program under the terms of
 * the Unmodified Binary Distribution Licence (as given in the file
 * COPYING.UBDL), provided that you have satisfied its requirements.
 */

FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
FILE_SECBOOT ( PERMITTED );

#include <stdint.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ipxe/io.h>
#include <ipxe/nap.h>
#include <ipxe/malloc.h>
#include <ipxe/xen.h>
#include <ipxe/xenevent.h>
#include <ipxe/xenstore.h>

/*
 * xs_wire.h attempts to define a static error table xsd_errors, which
 * interacts badly with the dynamically generated error numbers used
 * by iPXE.  Prevent this table from being constructed by including
 * errno.h only after including xs_wire.h.
 *
 */
#include <xen/io/xs_wire.h>
#include <errno.h>

/** @file
 *
 * XenStore interface
 *
 */

/** Request identifier */
static uint32_t xenstore_req_id;

/**
 * Send XenStore request raw data
 *
 * @v xen		Xen hypervisor
 * @v data		Data buffer
 * @v len		Length of data
 */
static void xenstore_send ( struct xen_hypervisor *xen, const void *data,
			    size_t len ) {
	struct xenstore_domain_interface *intf = xen->store.intf;
	XENSTORE_RING_IDX prod = readl ( &intf->req_prod );
	XENSTORE_RING_IDX cons;
	XENSTORE_RING_IDX idx;
	const char *bytes = data;
	size_t offset;
	size_t fill;

	DBGCP ( intf, "XENSTORE raw request:\n" );
	DBGCP_HDA ( intf, MASK_XENSTORE_IDX ( prod ), data, len );

	/* Write one byte at a time */
	for ( offset = 0 ; offset < len ; offset++ ) {

		/* Wait for space to become available */
		while ( 1 ) {
			cons = readl ( &intf->req_cons );
			fill = ( prod - cons );
			if ( fill < XENSTORE_RING_SIZE )
				break;
			DBGC2 ( xen, "." );
			cpu_nap();
			rmb();
		}

		/* Write byte */
		idx = MASK_XENSTORE_IDX ( prod++ );
		writeb ( bytes[offset], &intf->req[idx] );
	}

	/* Update producer counter */
	wmb();
	writel ( prod, &intf->req_prod );
	wmb();
}

/**
 * Send XenStore request string (excluding terminating NUL)
 *
 * @v xen		Xen hypervisor
 * @v string		String
 */
static void xenstore_send_string ( struct xen_hypervisor *xen,
				   const char *string ) {

	xenstore_send ( xen, string, strlen ( string ) );
}

/**
 * Receive XenStore response raw data
 *
 * @v xen		Xen hypervisor
 * @v data		Data buffer, or NULL to discard data
 * @v len		Length of data
 */
static void xenstore_recv ( struct xen_hypervisor *xen, void *data,
			    size_t len ) {
	struct xenstore_domain_interface *intf = xen->store.intf;
	XENSTORE_RING_IDX cons = readl ( &intf->rsp_cons );
	XENSTORE_RING_IDX prod;
	XENSTORE_RING_IDX idx;
	char *bytes = data;
	size_t offset;
	size_t fill;

	DBGCP ( intf, "XENSTORE raw response:\n" );

	/* Read one byte at a time */
	for ( offset = 0 ; offset < len ; offset++ ) {

		/* Wait for data to be ready */
		while ( 1 ) {
			prod = readl ( &intf->rsp_prod );
			fill = ( prod - cons );
			if ( fill > 0 )
				break;
			DBGC2 ( xen, "." );
			cpu_nap();
			rmb();
		}

		/* Read byte */
		idx = MASK_XENSTORE_IDX ( cons++ );
		if ( data )
			bytes[offset] = readb ( &intf->rsp[idx] );
	}
	if ( data )
		DBGCP_HDA ( intf, MASK_XENSTORE_IDX ( cons - len ), data, len );

	/* Update consumer counter */
	writel ( cons, &intf->rsp_cons );
	wmb();
}

/**
 * Send XenStore request
 *
 * @v xen		Xen hypervisor
 * @v type		Message type
 * @v req_id		Request ID
 * @v value		Value, or NULL to omit
 * @v key		Key path components
 * @ret rc		Return status code
 */
static int xenstore_request ( struct xen_hypervisor *xen,
			      enum xsd_sockmsg_type type, uint32_t req_id,
			      const char *value, va_list key ) {
	struct xsd_sockmsg msg;
	struct evtchn_send event;
	const char *string;
	va_list tmp;
	int xenrc;
	int rc;

	/* Construct message header */
	msg.type = type;
	msg.req_id = req_id;
	msg.tx_id = 0;
	msg.len = 0;
	DBGC2 ( xen, "XENSTORE request ID %d type %d ", req_id, type );

	/* Calculate total length */
	va_copy ( tmp, key );
	while ( ( string = va_arg ( tmp, const char * ) ) != NULL ) {
		DBGC2 ( xen, "%s%s", ( msg.len ? "/" : "" ), string );
		msg.len += ( strlen ( string ) + 1 /* '/' or NUL */ );
	}
	va_end ( tmp );
	if ( value ) {
		DBGC2 ( xen, " = \"%s\"", value );
		msg.len += strlen ( value );
	}
	DBGC2 ( xen, "\n" );

	/* Send message */
	xenstore_send ( xen, &msg, sizeof ( msg ) );
	string = va_arg ( key, const char * );
	assert ( string != NULL );
	xenstore_send_string ( xen, string );
	while ( ( string = va_arg ( key, const char * ) ) != NULL ) {
		xenstore_send_string ( xen, "/" );
		xenstore_send_string ( xen, string );
	}
	xenstore_send ( xen, "", 1 ); /* Separating NUL */
	if ( value )
		xenstore_send_string ( xen, value );

	/* Notify the back end */
	event.port = xen->store.port;
	if ( ( xenrc = xenevent_send ( xen, &event ) ) != 0 ) {
		rc = -EXEN ( xenrc );
		DBGC ( xen, "XENSTORE could not notify back end: %s\n",
		       strerror ( rc ) );
		return rc;
	}

	return 0;
}

/**
 * Receive XenStore response
 *
 * @v xen		Xen hypervisor
 * @v req_id		Request ID
 * @v value		Value to fill in
 * @v len		Length to fill in
 * @ret rc		Return status code
 *
 * The caller is responsible for eventually calling free() on the
 * returned value.  Note that the value may comprise multiple
 * NUL-terminated strings concatenated together.  A terminating NUL
 * will always be appended to the returned value.
 */
static int xenstore_response ( struct xen_hypervisor *xen, uint32_t req_id,
			       char **value, size_t *len ) {
	struct xsd_sockmsg msg;
	char *string;
	int rc;

	/* Wait for response to become available */
	while ( ! xenevent_pending ( xen, xen->store.port ) )
		cpu_nap();

	/* Receive message header */
	xenstore_recv ( xen, &msg, sizeof ( msg ) );
	*len = msg.len;

	/* Allocate space for response */
	*value = zalloc ( msg.len + 1 /* terminating NUL */ );

	/* Receive data.  Do this even if allocation failed, or if the
	 * request ID was incorrect, to avoid leaving data in the
	 * ring.
	 */
	xenstore_recv ( xen, *value, msg.len );

	/* Validate request ID */
	if ( msg.req_id != req_id ) {
		DBGC ( xen, "XENSTORE response ID mismatch (got %d, expected "
		       "%d)\n", msg.req_id, req_id );
		rc = -EPROTO;
		goto err_req_id;
	}

	/* Check for allocation failure */
	if ( ! *value ) {
		DBGC ( xen, "XENSTORE could not allocate %d bytes for "
		       "response\n", msg.len );
		rc = -ENOMEM;
		goto err_alloc;
	}

	/* Check for explicit errors */
	if ( msg.type == XS_ERROR ) {
		DBGC ( xen, "XENSTORE response error \"%s\"\n", *value );
		rc = -EIO;
		goto err_explicit;
	}

	DBGC2 ( xen, "XENSTORE response ID %d\n", req_id );
	if ( DBG_EXTRA ) {
		for ( string = *value ; string < ( *value + msg.len ) ;
		      string += ( strlen ( string ) + 1 /* NUL */ ) ) {
			DBGC2 ( xen, " - \"%s\"\n", string );
		}
	}
	return 0;

 err_explicit:
 err_alloc:
 err_req_id:
	free ( *value );
	*value = NULL;
	return rc;
}

/**
 * Issue a XenStore message
 *
 * @v xen		Xen hypervisor
 * @v type		Message type
 * @v response		Response value to fill in, or NULL to discard
 * @v len		Response length to fill in, or NULL to ignore
 * @v request		Request value, or NULL to omit
 * @v key		Key path components
 * @ret rc		Return status code
 */
static int xenstore_message ( struct xen_hypervisor *xen,
			      enum xsd_sockmsg_type type, char **response,
			      size_t *len, const char *request, va_list key ) {
	char *response_value;
	size_t response_len;
	int rc;

	/* Send request */
	if ( ( rc = xenstore_request ( xen, type, ++xenstore_req_id,
				       request, key ) ) != 0 )
		return rc;

	/* Receive response */
	if ( ( rc = xenstore_response ( xen, xenstore_req_id, &response_value,
					&response_len ) ) != 0 )
		return rc;

	/* Return response, if applicable */
	if ( response ) {
		*response = response_value;
	} else {
		free ( response_value );
	}
	if ( len )
		*len = response_len;

	return 0;
}

/**
 * Read XenStore value
 *
 * @v xen		Xen hypervisor
 * @v value		Value to fill in
 * @v key		Key path components
 * @ret rc		Return status code
 *
 * On a successful return, the caller is responsible for calling
 * free() on the returned value.
 */
static int xenstore_vread ( struct xen_hypervisor *xen, char **value,
			    va_list key ) {

	return xenstore_message ( xen, XS_READ, value, NULL, NULL, key );
}

/**
 * Read XenStore value
 *
 * @v xen		Xen hypervisor
 * @v value		Value to fill in
 * @v ...		Key path components
 * @ret rc		Return status code
 *
 * On a successful return, the caller is responsible for calling
 * free() on the returned value.
 */
__attribute__ (( sentinel )) int
xenstore_read ( struct xen_hypervisor *xen, char **value, ... ) {
	va_list key;
	int rc;

	va_start ( key, value );
	rc = xenstore_vread ( xen, value, key );
	va_end ( key );
	return rc;
}

/**
 * Read XenStore numeric value
 *
 * @v xen		Xen hypervisor
 * @v num		Numeric value to fill in
 * @v ...		Key path components
 * @ret rc		Return status code
 */
__attribute__ (( sentinel )) int
xenstore_read_num ( struct xen_hypervisor *xen, unsigned long *num, ... ) {
	va_list key;
	char *value;
	char *endp;
	int rc;

	/* Try to read text value */
	va_start ( key, num );
	rc = xenstore_vread ( xen, &value, key );
	va_end ( key );
	if ( rc != 0 )
		goto err_read;

	/* Try to parse as numeric value */
	*num = strtoul ( value, &endp, 10 );
	if ( ( *value == '\0' ) || ( *endp != '\0' ) ) {
		DBGC ( xen, "XENSTORE found invalid numeric value \"%s\"\n",
		       value );
		rc = -EINVAL;
		goto err_strtoul;
	}

 err_strtoul:
	free ( value );
 err_read:
	return rc;
}

/**
 * Write XenStore value
 *
 * @v xen		Xen hypervisor
 * @v value		Value
 * @v key		Key path components
 * @ret rc		Return status code
 */
static int xenstore_vwrite ( struct xen_hypervisor *xen, const char *value,
			     va_list key ) {

	return xenstore_message ( xen, XS_WRITE, NULL, NULL, value, key );
}

/**
 * Write XenStore value
 *
 * @v xen		Xen hypervisor
 * @v value		Value
 * @v ...		Key path components
 * @ret rc		Return status code
 */
__attribute__ (( sentinel )) int
xenstore_write ( struct xen_hypervisor *xen, const char *value, ... ) {
	va_list key;
	int rc;

	va_start ( key, value );
	rc = xenstore_vwrite ( xen, value, key );
	va_end ( key );
	return rc;
}

/**
 * Write XenStore numeric value
 *
 * @v xen		Xen hypervisor
 * @v num		Numeric value
 * @v ...		Key path components
 * @ret rc		Return status code
 */
__attribute__ (( sentinel )) int
xenstore_write_num ( struct xen_hypervisor *xen, unsigned long num, ... ) {
	char value[ 21 /* "18446744073709551615" + NUL */ ];
	va_list key;
	int rc;

	/* Construct value */
	snprintf ( value, sizeof ( value ), "%ld", num );

	/* Write value */
	va_start ( key, num );
	rc = xenstore_vwrite ( xen, value, key );
	va_end ( key );
	return rc;
}

/**
 * Delete XenStore value
 *
 * @v xen		Xen hypervisor
 * @v ...		Key path components
 * @ret rc		Return status code
 */
__attribute__ (( sentinel )) int
xenstore_rm ( struct xen_hypervisor *xen, ... ) {
	va_list key;
	int rc;

	va_start ( key, xen );
	rc = xenstore_message ( xen, XS_RM, NULL, NULL, NULL, key );
	va_end ( key );
	return rc;
}

/**
 * Read XenStore directory
 *
 * @v xen		Xen hypervisor
 * @v children		Child key names to fill in
 * @v len		Length of child key names to fill in
 * @v ...		Key path components
 * @ret rc		Return status code
 */
__attribute__ (( sentinel )) int
xenstore_directory ( struct xen_hypervisor *xen, char **children, size_t *len,
		     ... ) {
	va_list key;
	int rc;

	va_start ( key, len );
	rc = xenstore_message ( xen, XS_DIRECTORY, children, len, NULL, key );
	va_end ( key );
	return rc;
}

/**
 * Dump XenStore directory contents (for debugging)
 *
 * @v xen		Xen hypervisor
 * @v key		Key
 */
void xenstore_dump ( struct xen_hypervisor *xen, const char *key ) {
	char *value;
	char *children;
	char *child;
	char *child_key;
	size_t len;
	int rc;

	/* Try to dump current key as a value */
	if ( ( rc = xenstore_read ( xen, &value, key, NULL ) ) == 0 ) {
		DBGC ( xen, "%s = \"%s\"\n", key, value );
		free ( value );
	}

	/* Try to recurse into each child in turn */
	if ( ( rc = xenstore_directory ( xen, &children, &len, key,
					 NULL ) ) == 0 ) {
		for ( child = children ; child < ( children + len ) ;
		      child += ( strlen ( child ) + 1 /* NUL */ ) ) {

			/* Construct child key */
			if ( asprintf ( &child_key, "%s/%s", key, child ) < 0 ){
				DBGC ( xen, "XENSTORE could not allocate child "
				       "key \"%s/%s\"\n", key, child );
				rc = -ENOMEM;
				break;
			}

			/* Recurse into child key, continuing on error */
			xenstore_dump ( xen, child_key );
			free ( child_key );
		}
		free ( children );
	}
}
