#ifndef _IPXE_XENGRANT_H
#define _IPXE_XENGRANT_H

/** @file
 *
 * Xen grant tables
 *
 */

FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );

#include <stdint.h>
#include <stdlib.h>
#include <ipxe/io.h>
#include <ipxe/xen.h>
#include <xen/grant_table.h>

/** Induced failure rate (for testing) */
#define XENGRANT_FAIL_RATE 0

/**
 * Query grant table size
 *
 * @v xen		Xen hypervisor
 * @v size		Table size
 * @ret xenrc		Xen status code
 */
static inline __attribute__ (( always_inline )) int
xengrant_query_size ( struct xen_hypervisor *xen,
		      struct gnttab_query_size *size ) {

	return xen_hypercall_3 ( xen, __HYPERVISOR_grant_table_op,
				 GNTTABOP_query_size,
				 virt_to_phys ( size ), 1 );
}

/**
 * Set grant table version
 *
 * @v xen		Xen hypervisor
 * @v version		Version
 * @ret xenrc		Xen status code
 */
static inline __attribute__ (( always_inline )) int
xengrant_set_version ( struct xen_hypervisor *xen,
		       struct gnttab_set_version *version ) {

	return xen_hypercall_3 ( xen, __HYPERVISOR_grant_table_op,
				 GNTTABOP_set_version,
				 virt_to_phys ( version ), 1 );
}

/**
 * Get grant table version
 *
 * @v xen		Xen hypervisor
 * @v version		Version
 * @ret xenrc		Xen status code
 */
static inline __attribute__ (( always_inline )) int
xengrant_get_version ( struct xen_hypervisor *xen,
		       struct gnttab_get_version *version ) {

	return xen_hypercall_3 ( xen, __HYPERVISOR_grant_table_op,
				 GNTTABOP_get_version,
				 virt_to_phys ( version ), 1 );
}

/**
 * Get number of grant table entries
 *
 * @v xen		Xen hypervisor
 * @ret entries		Number of grant table entries
 */
static inline __attribute__ (( always_inline )) unsigned int
xengrant_entries ( struct xen_hypervisor *xen ) {

	return ( ( xen->grant.len / sizeof ( xen->grant.table[0] ) )
		 >> xen->grant.shift );
}

/**
 * Get grant table entry header
 *
 * @v xen		Xen hypervisor
 * @v ref		Grant reference
 * @ret hdr		Grant table entry header
 */
static inline __attribute__ (( always_inline )) struct grant_entry_header *
xengrant_header ( struct xen_hypervisor *xen, grant_ref_t ref ) {
	struct grant_entry_v1 *v1;

	v1 = &xen->grant.table[ ref << xen->grant.shift ];
	return ( container_of ( &v1->flags, struct grant_entry_header, flags ));
}

/**
 * Get version 1 grant table entry
 *
 * @v hdr		Grant table entry header
 * @ret v1		Version 1 grant table entry
 */
static inline __attribute__ (( always_inline )) struct grant_entry_v1 *
xengrant_v1 ( struct grant_entry_header *hdr ) {

	return ( container_of ( &hdr->flags, struct grant_entry_v1, flags ) );
}

/**
 * Get version 2 grant table entry
 *
 * @v hdr		Grant table entry header
 * @ret v2		Version 2 grant table entry
 */
static inline __attribute__ (( always_inline )) union grant_entry_v2 *
xengrant_v2 ( struct grant_entry_header *hdr ) {

	return ( container_of ( &hdr->flags, union grant_entry_v2, hdr.flags ));
}

/**
 * Zero grant table entry
 *
 * @v xen		Xen hypervisor
 * @v hdr		Grant table entry header
 */
static inline void xengrant_zero ( struct xen_hypervisor *xen,
				   struct grant_entry_header *hdr ) {
	uint32_t *dword = ( ( uint32_t * ) hdr );
	unsigned int i = ( ( sizeof ( xen->grant.table[0] ) / sizeof ( *dword ))
			   << xen->grant.shift );

	while ( i-- )
		writel ( 0, dword++ );
}

/**
 * Invalidate access to a page
 *
 * @v xen		Xen hypervisor
 * @v ref		Grant reference
 */
static inline __attribute__ (( always_inline )) void
xengrant_invalidate ( struct xen_hypervisor *xen, grant_ref_t ref ) {
	struct grant_entry_header *hdr = xengrant_header ( xen, ref );

	/* Sanity check */
	assert ( ( readw ( &hdr->flags ) &
		   ( GTF_reading | GTF_writing ) ) == 0 );

	/* This should apparently be done using a cmpxchg instruction.
	 * We omit this: partly in the interests of simplicity, but
	 * mainly since our control flow generally does not permit
	 * failure paths to themselves fail.
	 */
	writew ( 0, &hdr->flags );

	/* Leave reference marked as in-use (see xengrant_alloc()) */
	writew ( DOMID_SELF, &hdr->domid );
}

/**
 * Permit access to a page
 *
 * @v xen		Xen hypervisor
 * @v ref		Grant reference
 * @v domid		Domain ID
 * @v subflags		Additional flags
 * @v addr		Physical address within page
 * @ret rc		Return status code
 */
static inline __attribute__ (( always_inline )) int
xengrant_permit_access ( struct xen_hypervisor *xen, grant_ref_t ref,
			 domid_t domid, unsigned int subflags,
			 physaddr_t addr ) {
	struct grant_entry_header *hdr = xengrant_header ( xen, ref );
	struct grant_entry_v1 *v1 = xengrant_v1 ( hdr );
	union grant_entry_v2 *v2 = xengrant_v2 ( hdr );
	unsigned long frame = ( addr / PAGE_SIZE );

	/* Fail (for test purposes) if applicable */
	if ( ( XENGRANT_FAIL_RATE > 0 ) &&
	     ( random() % XENGRANT_FAIL_RATE ) == 0 ) {
		return -EAGAIN;
	}

	/* Record frame number.  This may fail on a 64-bit system if
	 * we are using v1 grant tables.  On a 32-bit system, there is
	 * no way for this code path to fail (with either v1 or v2
	 * grant tables); we allow the compiler to optimise the
	 * failure paths away to save space.
	 */
	if ( sizeof ( physaddr_t ) == sizeof ( uint64_t ) ) {

		/* 64-bit system */
		if ( xen->grant.shift ) {
			/* Version 2 table: no possible failure */
			writeq ( frame, &v2->full_page.frame );
		} else {
			/* Version 1 table: may fail if address above 16TB */
			if ( frame > 0xffffffffUL )
				return -ERANGE;
			writel ( frame, &v1->frame );
		}

	} else {

		/* 32-bit system */
		if ( xen->grant.shift ) {
			/* Version 2 table: no possible failure */
			writel ( frame, &v2->full_page.frame );
		} else {
			/* Version 1 table: no possible failure */
			writel ( frame, &v1->frame );
		}
	}

	/* Record domain ID and flags */
	writew ( domid, &hdr->domid );
	wmb();
	writew ( ( GTF_permit_access | subflags ), &hdr->flags );
	wmb();

	return 0;
}

extern int xengrant_init ( struct xen_hypervisor *xen );
extern int xengrant_alloc ( struct xen_hypervisor *xen, grant_ref_t *refs,
			    unsigned int count );
extern void xengrant_free ( struct xen_hypervisor *xen, grant_ref_t *refs,
			    unsigned int count );

#endif /* _IPXE_XENGRANT_H */
