#ifndef _IPXE_VMBUS_H
#define _IPXE_VMBUS_H

/** @file
 *
 * Hyper-V virtual machine bus
 *
 */

FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );

#include <byteswap.h>
#include <ipxe/uuid.h>
#include <ipxe/device.h>
#include <ipxe/tables.h>
#include <ipxe/uaccess.h>
#include <ipxe/iobuf.h>
#include <ipxe/hyperv.h>

/** VMBus message connection ID */
#define VMBUS_MESSAGE_ID 1

/** VMBus event connection ID */
#define VMBUS_EVENT_ID 2

/** VMBus message type */
#define VMBUS_MESSAGE_TYPE 1

/** VMBus message synthetic interrupt */
#define VMBUS_MESSAGE_SINT 2

/** VMBus version number */
union vmbus_version {
	/** Raw version */
	uint32_t raw;
	/** Major/minor version */
	struct {
		/** Minor version */
		uint16_t minor;
		/** Major version */
		uint16_t major;
	};
} __attribute__ (( packed ));

/** Known VMBus protocol versions */
enum vmbus_raw_version {
	/** Windows Server 2008 */
	VMBUS_VERSION_WS2008 = ( ( 0 << 16 ) | ( 13 << 0 ) ),
	/** Windows 7 */
	VMBUS_VERSION_WIN7 = ( ( 1 << 16 ) | ( 1 << 0 ) ),
	/** Windows 8 */
	VMBUS_VERSION_WIN8 = ( ( 2 << 16 ) | ( 4 << 0 ) ),
	/** Windows 8.1 */
	VMBUS_VERSION_WIN8_1 = ( ( 3 << 16 ) | ( 0 << 0 ) ),
};

/** Guest physical address range descriptor */
struct vmbus_gpa_range {
	/** Byte count */
	uint32_t len;
	/** Starting byte offset */
	uint32_t offset;
	/** Page frame numbers
	 *
	 * The length of this array is implied by the byte count and
	 * starting offset.
	 */
	uint64_t pfn[0];
} __attribute__ (( packed ));

/** VMBus message header */
struct vmbus_message_header {
	/** Message type */
	uint32_t type;
	/** Reserved */
	uint32_t reserved;
} __attribute__ (( packed ));

/** VMBus message types */
enum vmbus_message_type {
	VMBUS_OFFER_CHANNEL = 1,
	VMBUS_REQUEST_OFFERS = 3,
	VMBUS_ALL_OFFERS_DELIVERED = 4,
	VMBUS_OPEN_CHANNEL = 5,
	VMBUS_OPEN_CHANNEL_RESULT = 6,
	VMBUS_CLOSE_CHANNEL = 7,
	VMBUS_GPADL_HEADER = 8,
	VMBUS_GPADL_CREATED = 10,
	VMBUS_GPADL_TEARDOWN = 11,
	VMBUS_GPADL_TORNDOWN = 12,
	VMBUS_INITIATE_CONTACT = 14,
	VMBUS_VERSION_RESPONSE = 15,
	VMBUS_UNLOAD = 16,
	VMBUS_UNLOAD_RESPONSE = 17,
};

/** VMBus "offer channel" message */
struct vmbus_offer_channel {
	/** Message header */
	struct vmbus_message_header header;
	/** Channel type */
	union uuid type;
	/** Channel instance */
	union uuid instance;
	/** Reserved */
	uint8_t reserved_a[16];
	/** Flags */
	uint16_t flags;
	/** Reserved */
	uint8_t reserved_b[2];
	/** User data */
	uint8_t data[120];
	/** Reserved */
	uint8_t reserved_c[4];
	/** Channel ID */
	uint32_t channel;
	/** Monitor ID */
	uint8_t monitor;
	/** Monitor exists */
	uint8_t monitored;
	/** Reserved */
	uint8_t reserved[2];
	/** Connection ID */
	uint32_t connection;
} __attribute__ (( packed ));

/** VMBus "open channel" message */
struct vmbus_open_channel {
	/** Message header */
	struct vmbus_message_header header;
	/** Channel ID */
	uint32_t channel;
	/** Open ID */
	uint32_t id;
	/** Ring buffer GPADL ID */
	uint32_t gpadl;
	/** Reserved */
	uint32_t reserved;
	/** Outbound ring buffer size (in pages) */
	uint32_t out_pages;
	/** User-specific data */
	uint8_t data[120];
} __attribute__ (( packed ));

/** VMBus "open channel result" message */
struct vmbus_open_channel_result {
	/** Message header */
	struct vmbus_message_header header;
	/** Channel ID */
	uint32_t channel;
	/** Open ID */
	uint32_t id;
	/** Status */
	uint32_t status;
} __attribute__ (( packed ));

/** VMBus "close channel" message */
struct vmbus_close_channel {
	/** Message header */
	struct vmbus_message_header header;
	/** Channel ID */
	uint32_t channel;
} __attribute__ (( packed ));

/** VMBus "GPADL header" message */
struct vmbus_gpadl_header {
	/** Message header */
	struct vmbus_message_header header;
	/** Channel ID */
	uint32_t channel;
	/** GPADL ID */
	uint32_t gpadl;
	/** Length of range descriptors */
	uint16_t range_len;
	/** Number of range descriptors */
	uint16_t range_count;
	/** Range descriptors */
	struct vmbus_gpa_range range[0];
} __attribute__ (( packed ));

/** VMBus "GPADL created" message */
struct vmbus_gpadl_created {
	/** Message header */
	struct vmbus_message_header header;
	/** Channel ID */
	uint32_t channel;
	/** GPADL ID */
	uint32_t gpadl;
	/** Creation status */
	uint32_t status;
} __attribute__ (( packed ));

/** VMBus "GPADL teardown" message */
struct vmbus_gpadl_teardown {
	/** Message header */
	struct vmbus_message_header header;
	/** Channel ID */
	uint32_t channel;
	/** GPADL ID */
	uint32_t gpadl;
} __attribute__ (( packed ));

/** VMBus "GPADL torndown" message */
struct vmbus_gpadl_torndown {
	/** Message header */
	struct vmbus_message_header header;
	/** GPADL ID */
	uint32_t gpadl;
} __attribute__ (( packed ));

/** VMBus "initiate contact" message */
struct vmbus_initiate_contact {
	/** Message header */
	struct vmbus_message_header header;
	/** Requested version */
	union vmbus_version version;
	/** Target virtual CPU */
	uint32_t vcpu;
	/** Interrupt page base address */
	uint64_t intr;
	/** Parent to child monitor page base address */
	uint64_t monitor_in;
	/** Child to parent monitor page base address */
	uint64_t monitor_out;
} __attribute__ (( packed ));

/** VMBus "version response" message */
struct vmbus_version_response {
	/** Message header */
	struct vmbus_message_header header;
	/** Version is supported */
	uint8_t supported;
	/** Reserved */
	uint8_t reserved[3];
	/** Version */
	union vmbus_version version;
} __attribute__ (( packed ));

/** VMBus message */
union vmbus_message {
	/** Common message header */
	struct vmbus_message_header header;
	/** "Offer channel" message */
	struct vmbus_offer_channel offer;
	/** "Open channel" message */
	struct vmbus_open_channel open;
	/** "Open channel result" message */
	struct vmbus_open_channel_result opened;
	/** "Close channel" message */
	struct vmbus_close_channel close;
	/** "GPADL header" message */
	struct vmbus_gpadl_header gpadlhdr;
	/** "GPADL created" message */
	struct vmbus_gpadl_created created;
	/** "GPADL teardown" message */
	struct vmbus_gpadl_teardown teardown;
	/** "GPADL torndown" message */
	struct vmbus_gpadl_torndown torndown;
	/** "Initiate contact" message */
	struct vmbus_initiate_contact initiate;
	/** "Version response" message */
	struct vmbus_version_response version;
};

/** VMBus packet header */
struct vmbus_packet_header {
	/** Type */
	uint16_t type;
	/** Length of packet header (in quadwords) */
	uint16_t hdr_qlen;
	/** Length of packet (in quadwords) */
	uint16_t qlen;
	/** Flags */
	uint16_t flags;
	/** Transaction ID
	 *
	 * This is an opaque token: we therefore treat it as
	 * native-endian and don't worry about byte-swapping.
	 */
	uint64_t xid;
} __attribute__ (( packed ));

/** VMBus packet types */
enum vmbus_packet_type {
	VMBUS_DATA_INBAND = 6,
	VMBUS_DATA_XFER_PAGES = 7,
	VMBUS_DATA_GPA_DIRECT = 9,
	VMBUS_CANCELLATION = 10,
	VMBUS_COMPLETION = 11,
};

/** VMBus packet flags */
enum vmbus_packet_flags {
	VMBUS_COMPLETION_REQUESTED = 0x0001,
};

/** VMBus GPA direct header */
struct vmbus_gpa_direct_header {
	/** Packet header */
	struct vmbus_packet_header header;
	/** Reserved */
	uint32_t reserved;
	/** Number of range descriptors */
	uint32_t range_count;
	/** Range descriptors */
	struct vmbus_gpa_range range[0];
} __attribute__ (( packed ));

/** VMBus transfer page range */
struct vmbus_xfer_page_range {
	/** Length */
	uint32_t len;
	/** Offset */
	uint32_t offset;
} __attribute__ (( packed ));

/** VMBus transfer page header */
struct vmbus_xfer_page_header {
	/** Packet header */
	struct vmbus_packet_header header;
	/** Page set ID */
	uint16_t pageset;
	/** Sender owns page set */
	uint8_t owner;
	/** Reserved */
	uint8_t reserved;
	/** Number of range descriptors */
	uint32_t range_count;
	/** Range descriptors */
	struct vmbus_xfer_page_range range[0];
} __attribute__ (( packed ));

/** Maximum expected size of VMBus packet header */
#define VMBUS_PACKET_MAX_HEADER_LEN 64

/** VMBus maximum-sized packet header */
union vmbus_packet_header_max {
	/** Common header */
	struct vmbus_packet_header header;
	/** GPA direct header */
	struct vmbus_gpa_direct_header gpa;
	/** Transfer page header */
	struct vmbus_xfer_page_header xfer;
	/** Padding to maximum supported size */
	uint8_t padding[VMBUS_PACKET_MAX_HEADER_LEN];
} __attribute__ (( packed ));

/** VMBus packet footer */
struct vmbus_packet_footer {
	/** Reserved */
	uint32_t reserved;
	/** Producer index of the first byte of the packet */
	uint32_t prod;
} __attribute__ (( packed ));

/** VMBus ring buffer
 *
 * This is the structure of the each of the ring buffers created when
 * a VMBus channel is opened.
 */
struct vmbus_ring {
	/** Producer index (modulo ring length) */
	uint32_t prod;
	/** Consumer index (modulo ring length) */
	uint32_t cons;
	/** Interrupt mask */
	uint32_t intr_mask;
	/** Reserved */
	uint8_t reserved[4084];
	/** Ring buffer contents */
	uint8_t data[0];
} __attribute__ (( packed ));

/** VMBus interrupt page */
struct vmbus_interrupt {
	/** Inbound interrupts */
	uint8_t in[ PAGE_SIZE / 2 ];
	/** Outbound interrupts */
	uint8_t out[ PAGE_SIZE / 2 ];
} __attribute__ (( packed ));

/** A virtual machine bus */
struct vmbus {
	/** Interrupt page */
	struct vmbus_interrupt *intr;
	/** Inbound notifications */
	struct hv_monitor *monitor_in;
	/** Outbound notifications */
	struct hv_monitor *monitor_out;
	/** Received message buffer */
	const union vmbus_message *message;
};

struct vmbus_device;

/** VMBus channel operations */
struct vmbus_channel_operations {
	/**
	 * Handle received control packet
	 *
	 * @v vmdev		VMBus device
	 * @v xid		Transaction ID
	 * @v data		Data
	 * @v len		Length of data
	 * @ret rc		Return status code
	 */
	int ( * recv_control ) ( struct vmbus_device *vmdev, uint64_t xid,
				 const void *data, size_t len );
	/**
	 * Handle received data packet
	 *
	 * @v vmdev		VMBus device
	 * @v xid		Transaction ID
	 * @v data		Data
	 * @v len		Length of data
	 * @v list		List of I/O buffers
	 * @ret rc		Return status code
	 *
	 * This function takes ownership of the I/O buffer.  It should
	 * eventually call vmbus_send_completion() to indicate to the
	 * host that the buffer can be reused.
	 */
	int ( * recv_data ) ( struct vmbus_device *vmdev, uint64_t xid,
			      const void *data, size_t len,
			      struct list_head *list );
	/**
	 * Handle received completion packet
	 *
	 * @v vmdev		VMBus device
	 * @v xid		Transaction ID
	 * @v data		Data
	 * @v len		Length of data
	 * @ret rc		Return status code
	 */
	int ( * recv_completion ) ( struct vmbus_device *vmdev, uint64_t xid,
				    const void *data, size_t len );
	/**
	 * Handle received cancellation packet
	 *
	 * @v vmdev		VMBus device
	 * @v xid		Transaction ID
	 * @ret rc		Return status code
	 */
	int ( * recv_cancellation ) ( struct vmbus_device *vmdev,
				      uint64_t xid );
};

struct vmbus_xfer_pages;

/** VMBus transfer page set operations */
struct vmbus_xfer_pages_operations {
	/**
	 * Copy data from transfer page
	 *
	 * @v pages		Transfer page set
	 * @v data		Data buffer
	 * @v offset		Offset within page set
	 * @v len		Length within page set
	 * @ret rc		Return status code
	 */
	int ( * copy ) ( struct vmbus_xfer_pages *pages, void *data,
			 size_t offset, size_t len );
};

/** VMBus transfer page set */
struct vmbus_xfer_pages {
	/** List of all transfer page sets */
	struct list_head list;
	/** Page set ID (in protocol byte order) */
	uint16_t pageset;
	/** Page set operations */
	struct vmbus_xfer_pages_operations *op;
};

/** A VMBus device */
struct vmbus_device {
	/** Generic iPXE device */
	struct device dev;
	/** Hyper-V hypervisor */
	struct hv_hypervisor *hv;

	/** Channel instance */
	union uuid instance;
	/** Channel ID */
	unsigned int channel;
	/** Monitor ID */
	unsigned int monitor;
	/** Signal channel
	 *
	 * @v vmdev		VMBus device
	 */
	void ( * signal ) ( struct vmbus_device *vmdev );

	/** Outbound ring buffer length */
	uint32_t out_len;
	/** Inbound ring buffer length */
	uint32_t in_len;
	/** Outbound ring buffer */
	struct vmbus_ring *out;
	/** Inbound ring buffer */
	struct vmbus_ring *in;
	/** Ring buffer GPADL ID */
	unsigned int gpadl;

	/** Channel operations */
	struct vmbus_channel_operations *op;
	/** Maximum expected data packet length */
	size_t mtu;
	/** Packet buffer */
	void *packet;
	/** List of transfer page sets */
	struct list_head pages;

	/** Driver */
	struct vmbus_driver *driver;
	/** Driver-private data */
	void *priv;
};

/** A VMBus device driver */
struct vmbus_driver {
	/** Name */
	const char *name;
	/** Device type */
	union uuid type;
	/** Probe device
	 *
	 * @v vmdev		VMBus device
	 * @ret rc		Return status code
	 */
	int ( * probe ) ( struct vmbus_device *vmdev );
	/** Reset device
	 *
	 * @v vmdev		VMBus device
	 * @ret rc		Return status code
	 */
	int ( * reset ) ( struct vmbus_device *vmdev );
	/** Remove device
	 *
	 * @v vmdev		VMBus device
	 */
	void ( * remove ) ( struct vmbus_device *vmdev );
};

/** VMBus device driver table */
#define VMBUS_DRIVERS __table ( struct vmbus_driver, "vmbus_drivers" )

/** Declare a VMBus device driver */
#define __vmbus_driver __table_entry ( VMBUS_DRIVERS, 01 )

/**
 * Set VMBus device driver-private data
 *
 * @v vmdev		VMBus device
 * @v priv		Private data
 */
static inline void vmbus_set_drvdata ( struct vmbus_device *vmdev, void *priv ){
	vmdev->priv = priv;
}

/**
 * Get VMBus device driver-private data
 *
 * @v vmdev		VMBus device
 * @ret priv		Private data
 */
static inline void * vmbus_get_drvdata ( struct vmbus_device *vmdev ) {
	return vmdev->priv;
}

/** Construct VMBus type */
#define VMBUS_TYPE( a, b, c, d, e0, e1, e2, e3, e4, e5 ) {		\
	.canonical = {							\
		cpu_to_le32 ( a ), cpu_to_le16 ( b ),			\
		cpu_to_le16 ( c ), cpu_to_be16 ( d ),			\
		{ e0, e1, e2, e3, e4, e5 }				\
	 } }

/**
 * Check if data is present in ring buffer
 *
 * @v vmdev		VMBus device
 * @v has_data		Data is present
 */
static inline __attribute__ (( always_inline )) int
vmbus_has_data ( struct vmbus_device *vmdev ) {

	return ( vmdev->in->prod != vmdev->in->cons );
}

/**
 * Register transfer page set
 *
 * @v vmdev		VMBus device
 * @v pages		Transfer page set
 * @ret rc		Return status code
 */
static inline __attribute__ (( always_inline )) int
vmbus_register_pages ( struct vmbus_device *vmdev,
		       struct vmbus_xfer_pages *pages ) {

	list_add ( &pages->list, &vmdev->pages );
	return 0;
}

/**
 * Unregister transfer page set
 *
 * @v vmdev		VMBus device
 * @v pages		Transfer page set
 */
static inline __attribute__ (( always_inline )) void
vmbus_unregister_pages ( struct vmbus_device *vmdev,
			 struct vmbus_xfer_pages *pages ) {

	list_check_contains_entry ( pages, &vmdev->pages, list );
	list_del ( &pages->list );
}

extern unsigned int vmbus_obsolete_gpadl;

/**
 * Check if GPADL is obsolete
 *
 * @v gpadl		GPADL ID
 * @v is_obsolete	GPADL ID is obsolete
 *
 * Check if GPADL is obsolete (i.e. was created before the most recent
 * Hyper-V reset).
 */
static inline __attribute__ (( always_inline )) int
vmbus_gpadl_is_obsolete ( unsigned int gpadl ) {

	return ( gpadl <= vmbus_obsolete_gpadl );
}

extern int vmbus_establish_gpadl ( struct vmbus_device *vmdev, userptr_t data,
				   size_t len );
extern int vmbus_gpadl_teardown ( struct vmbus_device *vmdev,
				  unsigned int gpadl );
extern int vmbus_open ( struct vmbus_device *vmdev,
			struct vmbus_channel_operations *op,
			size_t out_len, size_t in_len, size_t mtu );
extern void vmbus_close ( struct vmbus_device *vmdev );
extern int vmbus_send_control ( struct vmbus_device *vmdev, uint64_t xid,
				const void *data, size_t len );
extern int vmbus_send_data ( struct vmbus_device *vmdev, uint64_t xid,
			     const void *data, size_t len,
			     struct io_buffer *iobuf );
extern int vmbus_send_completion ( struct vmbus_device *vmdev, uint64_t xid,
				   const void *data, size_t len );
extern int vmbus_send_cancellation ( struct vmbus_device *vmdev, uint64_t xid );
extern int vmbus_poll ( struct vmbus_device *vmdev );
extern void vmbus_dump_channel ( struct vmbus_device *vmdev );

extern int vmbus_probe ( struct hv_hypervisor *hv, struct device *parent );
extern int vmbus_reset ( struct hv_hypervisor *hv, struct device *parent );
extern void vmbus_remove ( struct hv_hypervisor *hv, struct device *parent );

#endif /* _IPXE_VMBUS_H */
