#ifndef _IPXE_RNDIS_H
#define _IPXE_RNDIS_H

/** @file
 *
 * Remote Network Driver Interface Specification
 *
 */

FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );

#include <stdint.h>
#include <ipxe/netdevice.h>
#include <ipxe/iobuf.h>

/** Maximum time to wait for a transaction to complete
 *
 * This is a policy decision.
 */
#define RNDIS_MAX_WAIT_MS 1000

/** RNDIS message header */
struct rndis_header {
	/** Message type */
	uint32_t type;
	/** Message length */
	uint32_t len;
} __attribute__ (( packed ));

/** RNDIS initialise message */
#define RNDIS_INITIALISE_MSG 0x00000002UL

/** RNDIS initialise message */
struct rndis_initialise_message {
	/** Request ID */
	uint32_t id;
	/** Major version */
	uint32_t major;
	/** Minor version */
	uint32_t minor;
	/** Maximum transfer size */
	uint32_t mtu;
} __attribute__ (( packed ));

/** Request ID used for initialisation
 *
 * This is a policy decision.
 */
#define RNDIS_INIT_ID 0xe110e110UL

/** RNDIS major version */
#define RNDIS_VERSION_MAJOR 1

/** RNDIS minor version */
#define RNDIS_VERSION_MINOR 0

/** RNDIS maximum transfer size
 *
 * This is a policy decision.
 */
#define RNDIS_MTU 2048

/** RNDIS initialise completion */
#define RNDIS_INITIALISE_CMPLT 0x80000002UL

/** RNDIS initialise completion */
struct rndis_initialise_completion {
	/** Request ID */
	uint32_t id;
	/** Status */
	uint32_t status;
	/** Major version */
	uint32_t major;
	/** Minor version */
	uint32_t minor;
	/** Device flags */
	uint32_t flags;
	/** Medium */
	uint32_t medium;
	/** Maximum packets per transfer */
	uint32_t max_pkts;
	/** Maximum transfer size */
	uint32_t mtu;
	/** Packet alignment factor */
	uint32_t align;
	/** Reserved */
	uint32_t reserved[2];
} __attribute__ (( packed ));

/** RNDIS halt message */
#define RNDIS_HALT_MSG 0x00000003UL

/** RNDIS halt message */
struct rndis_halt_message {
	/** Request ID */
	uint32_t id;
} __attribute__ (( packed ));

/** RNDIS query OID message */
#define RNDIS_QUERY_MSG 0x00000004UL

/** RNDIS set OID message */
#define RNDIS_SET_MSG 0x00000005UL

/** RNDIS query or set OID message */
struct rndis_oid_message {
	/** Request ID */
	uint32_t id;
	/** Object ID */
	uint32_t oid;
	/** Information buffer length */
	uint32_t len;
	/** Information buffer offset */
	uint32_t offset;
	/** Reserved */
	uint32_t reserved;
} __attribute__ (( packed ));

/** RNDIS query OID completion */
#define RNDIS_QUERY_CMPLT 0x80000004UL

/** RNDIS query OID completion */
struct rndis_query_completion {
	/** Request ID */
	uint32_t id;
	/** Status */
	uint32_t status;
	/** Information buffer length */
	uint32_t len;
	/** Information buffer offset */
	uint32_t offset;
} __attribute__ (( packed ));

/** RNDIS set OID completion */
#define RNDIS_SET_CMPLT 0x80000005UL

/** RNDIS set OID completion */
struct rndis_set_completion {
	/** Request ID */
	uint32_t id;
	/** Status */
	uint32_t status;
} __attribute__ (( packed ));

/** RNDIS reset message */
#define RNDIS_RESET_MSG 0x00000006UL

/** RNDIS reset message */
struct rndis_reset_message {
	/** Reserved */
	uint32_t reserved;
} __attribute__ (( packed ));

/** RNDIS reset completion */
#define RNDIS_RESET_CMPLT 0x80000006UL

/** RNDIS reset completion */
struct rndis_reset_completion {
	/** Status */
	uint32_t status;
	/** Addressing reset */
	uint32_t addr;
} __attribute__ (( packed ));

/** RNDIS indicate status message */
#define RNDIS_INDICATE_STATUS_MSG 0x00000007UL

/** RNDIS diagnostic information */
struct rndis_diagnostic_info {
	/** Status */
	uint32_t status;
	/** Error offset */
	uint32_t offset;
} __attribute__ (( packed ));

/** RNDIS indicate status message */
struct rndis_indicate_status_message {
	/** Status */
	uint32_t status;
	/** Status buffer length */
	uint32_t len;
	/** Status buffer offset */
	uint32_t offset;
	/** Diagnostic information (optional) */
	struct rndis_diagnostic_info diag[0];
} __attribute__ (( packed ));

/** RNDIS status codes */
enum rndis_status {
	/** Device is connected to a network medium */
	RNDIS_STATUS_MEDIA_CONNECT = 0x4001000bUL,
	/** Device is disconnected from the medium */
	RNDIS_STATUS_MEDIA_DISCONNECT = 0x4001000cUL,
	/** Unknown start-of-day status code */
	RNDIS_STATUS_WTF_WORLD = 0x40020006UL,
};

/** RNDIS keepalive message */
#define RNDIS_KEEPALIVE_MSG 0x00000008UL

/** RNDIS keepalive message */
struct rndis_keepalive_message {
	/** Request ID */
	uint32_t id;
} __attribute__ (( packed ));

/** RNDIS keepalive completion */
#define RNDIS_KEEPALIVE_CMPLT 0x80000008UL

/** RNDIS keepalive completion */
struct rndis_keepalive_completion {
	/** Request ID */
	uint32_t id;
	/** Status */
	uint32_t status;
} __attribute__ (( packed ));

/** RNDIS packet message */
#define RNDIS_PACKET_MSG 0x00000001UL

/** RNDIS packet field */
struct rndis_packet_field {
	/** Offset */
	uint32_t offset;
	/** Length */
	uint32_t len;
} __attribute__ (( packed ));

/** RNDIS packet message */
struct rndis_packet_message {
	/** Data */
	struct rndis_packet_field data;
	/** Out-of-band data records */
	struct rndis_packet_field oob;
	/** Number of out-of-band data records */
	uint32_t oob_count;
	/** Per-packet information record */
	struct rndis_packet_field ppi;
	/** Reserved */
	uint32_t reserved[2];
} __attribute__ (( packed ));

/** RNDIS packet record */
struct rndis_packet_record {
	/** Length */
	uint32_t len;
	/** Type */
	uint32_t type;
	/** Offset */
	uint32_t offset;
} __attribute__ (( packed ));

/** OID for packet filter */
#define RNDIS_OID_GEN_CURRENT_PACKET_FILTER 0x0001010eUL

/** Packet filter bits */
enum rndis_packet_filter {
	/** Unicast packets */
	RNDIS_FILTER_UNICAST = 0x00000001UL,
	/** Multicast packets */
	RNDIS_FILTER_MULTICAST = 0x00000002UL,
	/** All multicast packets */
	RNDIS_FILTER_ALL_MULTICAST = 0x00000004UL,
	/** Broadcast packets */
	RNDIS_FILTER_BROADCAST = 0x00000008UL,
	/** All packets */
	RNDIS_FILTER_PROMISCUOUS = 0x00000020UL
};

/** OID for media status */
#define RNDIS_OID_GEN_MEDIA_CONNECT_STATUS 0x00010114UL

/** OID for permanent MAC address */
#define RNDIS_OID_802_3_PERMANENT_ADDRESS 0x01010101UL

/** OID for current MAC address */
#define RNDIS_OID_802_3_CURRENT_ADDRESS	0x01010102UL

struct rndis_device;

/** RNDIS device operations */
struct rndis_operations {
	/**
	 * Open RNDIS device
	 *
	 * @v rndis		RNDIS device
	 * @ret rc		Return status code
	 */
	int ( * open ) ( struct rndis_device *rndis );
	/**
	 * Close RNDIS device
	 *
	 * @v rndis		RNDIS device
	 */
	void ( * close ) ( struct rndis_device *rndis );
	/**
	 * Transmit packet
	 *
	 * @v rndis		RNDIS device
	 * @v iobuf		I/O buffer
	 * @ret rc		Return status code
	 *
	 * If this method returns success then the RNDIS device must
	 * eventually report completion via rndis_tx_complete().
	 */
	int ( * transmit ) ( struct rndis_device *rndis,
			     struct io_buffer *iobuf );
	/**
	 * Poll for completed and received packets
	 *
	 * @v rndis		RNDIS device
	 */
	void ( * poll ) ( struct rndis_device *rndis );
};

/** An RNDIS device */
struct rndis_device {
	/** Network device */
	struct net_device *netdev;
	/** Device name */
	const char *name;
	/** RNDIS operations */
	struct rndis_operations *op;
	/** Driver private data */
	void *priv;

	/** Request ID for current blocking request */
	unsigned int wait_id;
	/** Return status code for current blocking request */
	int wait_rc;
};

/**
 * Initialise an RNDIS device
 *
 * @v rndis		RNDIS device
 * @v op		RNDIS device operations
 */
static inline void rndis_init ( struct rndis_device *rndis,
				struct rndis_operations *op ) {

	rndis->op = op;
}

extern void rndis_tx_complete_err ( struct rndis_device *rndis,
				    struct io_buffer *iobuf, int rc );
extern int rndis_tx_defer ( struct rndis_device *rndis,
			    struct io_buffer *iobuf );
extern void rndis_rx ( struct rndis_device *rndis, struct io_buffer *iobuf );
extern void rndis_rx_err ( struct rndis_device *rndis, struct io_buffer *iobuf,
			   int rc );

extern struct rndis_device * alloc_rndis ( size_t priv_len );
extern int register_rndis ( struct rndis_device *rndis );
extern void unregister_rndis ( struct rndis_device *rndis );
extern void free_rndis ( struct rndis_device *rndis );

/**
 * Complete message transmission
 *
 * @v rndis		RNDIS device
 * @v iobuf		I/O buffer
 */
static inline void rndis_tx_complete ( struct rndis_device *rndis,
				       struct io_buffer *iobuf ) {

	rndis_tx_complete_err ( rndis, iobuf, 0 );
}

#endif /* _IPXE_RNDIS_H */
