Aravis on Ultrascale+ using USB3

Hi Emmanuel,

I can only share both USB3 Vision control and stream classes I’ve implemented, which I believe that can be easily ported to any ARM environment by replacing all the related USB drivers with the specific processor drivers.

Cheers,
João

uvsp.c

/***************************** Include Files *********************************/
/* Xilinx includes */
#include <string.h>
#include "xil_printf.h"

/* USB3 includes */
#include "xusb/ch9_usb3.h"
#include "xusb/class_usb3.h"
#include "xusb/usb_wrapper.h"

/* U3V includes */
#include "vision/uvsp.h"
#include "vision/regmap.h"

/************************** Constant Definitions *****************************/
#define UVSP_LEADER_MAGIC 0x4C563355	   ///< UVSP leader prefix magic number
#define UVSP_TRAILER_MAGIC 0x54563355	   ///< UVSP trailer prefix magic number
#define UVSP_PAYLOAD_TYPE_IMAGE 0x0001	   ///< UVSP streaming image payload type
#define UVSP_PIXEL_FORMAT_RGB8 0x02180014u ///< RGB8 pixel format

/**************************** Type Definitions *******************************/
/**
 * @brief TODO
*/
typedef struct
{
	u8 isStreamActive;
	u8 busySending;
	u8 frameSendPending;
	u64 frame_id;
	u8 *frameBaseAddr;
	size_t frame_count;
	uvspState state;
} uvsp_ctx_t;

/************************** Variable Definitions *****************************/
extern struct Usb_DevData UsbInstance; ///< Main instance for USB communication
extern regmap_t U3V_regmap;			   ///< USB3 Vision register map
static uvsp_ctx_t _ctx = {};		   ///< Private UVSP struct declaration

/************************** Interrupt Routine ******************************/
/**
 * @brief Stream in endpoint interrupt routine.
 * 
 * Interrupt routine called after an USB transmission to host on the stream
 * endpoint. Releases @p busySending for a new streaming data to be sent 
 * afterwards. Also updates the streaming state for the next transmission.
 * 
 * @param[in] CallBackRef
 * @param[in] RequestedBytes bytes requested to send
 * @param[in] BytesTxed bytes transmitted
 * 
 * @note Reporting when @p RequestedBytes and @p BytesTxed have different
 * values. This may occur when the host limits the transmitted size, which 
 * shall not happen.
 */
void StreamInHandler(void *CallBackRef, u32 RequestedBytes, u32 BytesTxed)
{
	if (RequestedBytes != BytesTxed)
		xil_printf("StreamInHandler: RequestedBytes %d != BytesTxed %d\n",
				   RequestedBytes, BytesTxed);

	switch (_ctx.state)
	{
	case UVSP_SEND_LEADER:
		_ctx.state = UVSP_SEND_EQ_DATA;
		_ctx.frame_count = 0;
		break;

	case UVSP_SEND_EQ_DATA:
		if (++_ctx.frame_count == U3V_regmap.sirm.payload_count)
		{
			/* Check if Final Transfer 1 is needed */
			if (U3V_regmap.sirm.transfer1_size)
				_ctx.state = UVSP_SEND_TRF1_DATA;
			else
				_ctx.state = UVSP_SEND_TRAILER;
		}
		break;

	case UVSP_SEND_TRF1_DATA:
		/* Check if Final Transfer 2 is needed */
		if (U3V_regmap.sirm.transfer2_size)
			_ctx.state = UVSP_SEND_TRF2_DATA;
		else
			_ctx.state = UVSP_SEND_TRAILER;

		break;

	case UVSP_SEND_TRF2_DATA:
		_ctx.state = UVSP_SEND_TRAILER;
		break;

	case UVSP_SEND_TRAILER:
		_ctx.state = UVSP_SEND_LEADER;
		_ctx.frame_id++;
		_ctx.frameSendPending = FALSE;
		break;

	default:
		xil_printf("StreamInHandler: Unkwown state\n");
		return;
	}

	_ctx.busySending = FALSE;
}

/************************** Private Routines ******************************/
/**
 * @brief Creates new UVSP leader packet.
 * 
 * @param[in] frame_id frame ID of current transferred frame
 * @param[out] packet_id created UVSP leader packet
 * 
 * @return 	@e UVSP_SUCCESS for new UVSP leader packet @n
 * 			@e UVSP_ERR_NULL if any pointed arguments is NULL
 */
static uvspStatus _uvsp_packet_new_stream_leader(u64 frame_id, uvspLeader *packet)
{
	if (packet == NULL)
	{
		xil_printf("UVSP_ERR_NULL\n");
		return UVSP_ERR_NULL;
	}

	packet->header.magic = UVSP_LEADER_MAGIC;
	packet->header.reserved0 = 0;
	packet->header.size = sizeof(uvspLeader);
	packet->header.frame_id = frame_id;
	packet->infos.reserved0 = packet->infos.reserved1 = 0;
	packet->infos.payload_type = UVSP_PAYLOAD_TYPE_IMAGE;
	packet->infos.timestamp = -1; // @TODO Update with device timestamp
	packet->infos.pixel_format = UVSP_PIXEL_FORMAT_RGB8;
	packet->infos.width = STREAM_DEF_WIDTH;
	packet->infos.height = STREAM_DEF_HEIGHT;
	packet->infos.x_offset = 0;
	packet->infos.y_offset = 0;
	packet->infos.x_padding = 0;

	return UVSP_SUCCESS;
}

/**
 * @brief Creates new UVSP trailer packet.
 * 
 * @param[in] frame_id frame ID of current transferred frame
 * @param[out] packet_id created UVSP trailer packet
 * 
 * @return 	@e UVSP_SUCCESS for new UVSP trailer packet @n
 * 			@e UVSP_ERR_NULL if any pointed arguments is NULL
 */
static uvspStatus _uvsp_packet_new_stream_trailer(u64 frame_id, uvspTrailer *packet)
{
	if (packet == NULL)
	{
		xil_printf("UVSP_ERR_NULL\n");
		return UVSP_ERR_NULL;
	}

	packet->header.magic = UVSP_TRAILER_MAGIC;
	packet->header.reserved0 = 0;
	packet->header.size = sizeof(uvspTrailer);
	packet->header.frame_id = frame_id;
	packet->infos.status = 0; // @TODO Check if aravis deals with different status
	packet->infos.reserved0 = 0;
	packet->infos.payload_size = U3V_regmap.sirm.req_payload_size;
	packet->infos.size_y = STREAM_DEF_HEIGHT;

	return UVSP_SUCCESS;
}

/**
 * @brief Resets UVSP streaming state to starting state.
 */
static void _uvsp_reset_stream(void)
{
	_ctx.frame_id = _ctx.frame_count = 0;
	_ctx.frame_id = UVSP_SEND_LEADER;
	_ctx.frameSendPending = FALSE;
	_ctx.busySending = FALSE;
}

/**
 * @brief Sends streaming data to host.
 * 
 * @param[in] data streaming data to be sent
 * @param[in] size streaming data size
 * 
 * @return 	@e UVSP_SUCCESS for new UVSP trailer packet @n
 * 			@e UVSP_ERR_SENDING if streaming data could not be sent to host
 */
static uvspStatus _uvsp_send_data(u8 *data, size_t size)
{
	u32 status;

	_ctx.busySending = TRUE;
	status = EpBufferSend(UsbInstance.PrivateData, USB_EPOINT_STREAM, data, size);
	if (status != XST_SUCCESS)
	{
		xil_printf("UVSP_ERR_SENDING\n");
		return UVSP_ERR_SENDING;
	}

	return UVSP_SUCCESS;
}

/**
 * @brief Verifies if streaming is active.
 * 
 * Verifies if streaming is active from the control register on SIRM.
 * 
 * @return	@e TRUE for active stream @n
 * 			@e FALSE for inactive stream
 */
u8 uvsp_isStreamActive(void)
{
	if (U3V_regmap.sirm.control & 0b1)
		return TRUE;
	else
		return FALSE;
}

/**
 * @brief Checks if a frame send is pending.
 * 
 * @return	@e TRUE for pending frame @n
 * 			@e FALSE for no frame to be sent
 */
inline u32 usvp_isFrame_pending(void)
{
	return _ctx.frameSendPending;
}

/**
 * @brief Sets if a new frame send is pending or not.
 * 
 * @param[in] state pending frame state
 */
inline void uvsp_set_pending_frame(u8 state)
{
	_ctx.frameSendPending = state;
}

/**
 * @brief Sets a new memory base address for a frame.
 * 
 * @param[in] addr frame base address
 * 
 * @return 	@e UVSP_SUCCESS with new address set @n
 * 			@e UVSP_ERR_NULL if address is NULL
 */
uvspStatus uvsp_set_frame_base_addr(u8 *addr)
{
	if (addr == NULL)
	{
		xil_printf("UVSP_ERR_NULL\n");
		return UVSP_ERR_NULL;
	}

	_ctx.frameBaseAddr = addr;

	return UVSP_SUCCESS;
}

/**
 * @brief Main UVSP handler sending streaming data.
 * 
 * Being the streaming active, confirms if a new frame send is pending and if
 * any data send from a current frame is pending. Afterwards, streaming data is
 * generated depending on the streaming state of the current frame. This data is
 * then sent to the host.
 * 
 * @return	@e UVSP_SUCCESS with queued event sent to host @n
 * 			@e UVSP_NO_STREAM if streaming is not active @n
 * 			@e UVSP_NO_FRAME if no frame send is pending @n
 * 			@e UVSP_BUSY if stream endpoint is busy sending last frame data @n
 *          @e UVSP_ERR_NULL if frame address is NULL @n
 * 			@e UVSP_ERR_PACKET_CREATE if UVSP leader or trailer is not created @n
 * 			@e UVSP_ERR_SENDING if streaming data could not be sent to host
 */
uvspStatus uvsp_state_handler(void)
{
	/* Verify if streaming has been stopped by host */
	_ctx.isStreamActive = uvsp_isStreamActive();
	if (!_ctx.isStreamActive)
	{
		_uvsp_reset_stream();
		return UVSP_NO_STREAM;
	}

	/* Check if there is a new frame to be sent */
	if (!_ctx.frameSendPending)
		return UVSP_NO_FRAME;

	/* Last Buffer is still being sending */
	if (_ctx.busySending)
		return UVSP_BUSY;

	if (_ctx.frameBaseAddr == NULL)
	{
		xil_printf("UVSP_ERR_NULL\n");
		return UVSP_ERR_NULL;
	}

	uvspLeader pkt_leader;
	uvspTrailer pkt_trailer;
	u8 *data_start;

	/* Stream is active and endpoint is free, send something */
	switch (_ctx.state)
	{
	case UVSP_SEND_LEADER:
		if (_uvsp_packet_new_stream_leader(_ctx.frame_id, &pkt_leader) != UVSP_SUCCESS)
		{
			xil_printf("UVSP_ERR_PACKET_CREATE\n");
			return UVSP_ERR_PACKET_CREATE;
		}

		if (_uvsp_send_data((u8 *)&pkt_leader, pkt_leader.header.size) != UVSP_SUCCESS)
		{
			xil_printf("UVSP_ERR_SENDING\n");
			return UVSP_ERR_SENDING;
		}

		break;

	case UVSP_SEND_EQ_DATA:
		data_start = _ctx.frameBaseAddr + _ctx.frame_count * U3V_regmap.sirm.payload_size;

		if (_uvsp_send_data(data_start, U3V_regmap.sirm.payload_size) != UVSP_SUCCESS)
		{
			xil_printf("UVSP_ERR_SENDING\n");
			return UVSP_ERR_SENDING;
		}

		break;

	case UVSP_SEND_TRF1_DATA:
		data_start = _ctx.frameBaseAddr + U3V_regmap.sirm.payload_count * U3V_regmap.sirm.payload_size;

		if (_uvsp_send_data(data_start, U3V_regmap.sirm.transfer1_size) != UVSP_SUCCESS)
		{
			xil_printf("UVSP_ERR_SENDING\n");
			return UVSP_ERR_SENDING;
		}

		break;

	case UVSP_SEND_TRF2_DATA:
		data_start = _ctx.frameBaseAddr + (U3V_regmap.sirm.payload_count * U3V_regmap.sirm.payload_size) + U3V_regmap.sirm.transfer1_size;

		if (_uvsp_send_data(data_start, U3V_regmap.sirm.transfer2_size) != UVSP_SUCCESS)
		{
			xil_printf("UVSP_ERR_SENDING\n");
			return UVSP_ERR_SENDING;
		}

		break;

	case UVSP_SEND_TRAILER:
		if (_uvsp_packet_new_stream_trailer(_ctx.frame_id, &pkt_trailer) != UVSP_SUCCESS)
		{
			xil_printf("UVSP_ERR_PACKET_CREATE\n");
			return UVSP_ERR_PACKET_CREATE;
		}

		if (_uvsp_send_data((u8 *)&pkt_trailer, pkt_trailer.header.size) != UVSP_SUCCESS)
		{
			xil_printf("UVSP_ERR_SENDING\n");
			return UVSP_ERR_SENDING;
		}

		break;

	default:
		xil_printf("UVSP_ERR_UNKNOWN\n");
		return UVSP_ERR_UNKNOWN;
		break;
	}

	return UVSP_SUCCESS;
}

uvsp.h

#ifndef UVSP_H
#define UVSP_H

/************************** Include Files ****************************/
#include "xil_types.h"

/**************************** Type Definitions *******************************/
/**
 * @brief UVSP streaming data states.
 */
typedef enum
{
	UVSP_SEND_LEADER,
	UVSP_SEND_EQ_DATA,
	UVSP_SEND_TRF1_DATA,
	UVSP_SEND_TRF2_DATA,
	UVSP_SEND_TRAILER,
} uvspState;

/**
 * @brief UVSP status codes.
 */
typedef enum
{
	UVSP_SUCCESS,
	UVSP_NO_STREAM,
	UVSP_NO_FRAME,
	UVSP_BUSY,
	UVSP_ERR_UNKNOWN,
	UVSP_ERR_NULL,
	UVSP_ERR_SENDING,
	UVSP_ERR_PACKET_CREATE,
} uvspStatus;

/**
 * @brief UVSP packet header.
 *
 * UVSP header format as specified by U3V specification.
 */
typedef struct __attribute__((__packed__))
{
	u32 magic;
	u16 reserved0;
	u16 size;
	u64 frame_id;
} uvspHeader;

/**
 * @brief UVSP leader format.
 *
 * UVSP leader data format for an image payload type as specified by U3V
 * specification.
 */
typedef struct __attribute__((__packed__))
{
	u16 reserved0;
	u16 payload_type;
	u64 timestamp;
	u32 pixel_format;
	u32 width;
	u32 height;
	u32 x_offset;
	u32 y_offset;
	u16 x_padding;
	u16 reserved1;
} uvspLeaderInfos;

/**
 * @brief UVSP leader packet.
 *
 * UVSP leader packet as specified by U3V specification.
 */
typedef struct __attribute__((__packed__))
{
	uvspHeader header;
	uvspLeaderInfos infos;
} uvspLeader;

/**
 * @brief UVSP trailer format.
 *
 * UVSP trailer data format for an image payload type as specified by U3V
 * specification.
 */
typedef struct __attribute__((__packed__))
{
	u16 status;
	u16 reserved0;
	u64 payload_size;
	u32 size_y;
} uvspTrailerInfos;

/**
 * @brief UVSP trailer packet.
 *
 * UVSP trailer packet as specified by U3V specification.
 */
typedef struct __attribute__((__packed__))
{
	uvspHeader header;
	uvspTrailerInfos infos;
} uvspTrailer;

/**************************** Function Prototypes *******************************/
u8 uvsp_isStreamActive(void);
u32 usvp_isFrame_pending(void);
void uvsp_set_pending_frame(u8 state);
uvspStatus uvsp_set_frame_base_addr(u8 *addr);
uvspStatus uvsp_state_handler(void);

#endif // UVSP_H

uvcp.c

/************************** Include Files ****************************/
/* Xilinx includes */
#include <string.h>
#include "xil_printf.h"

/* USB3 includes */
#include "xusb/ch9_usb3.h"
#include "xusb/class_usb3.h"
#include "xusb/usb_wrapper.h"

/* U3V includes */
#include "vision/uvcp.h"
#include "vision/regmap.h"

/************************** Constant Definitions ****************************/
#define UVCP_OUT_BUF_SIZE 128		  ///< Control Out Endpoint buffer size
#define UVCP_CONTROL_MAGIC 0x43563355 ///< UVCP prefix magic number

/**************************** Type Definitions *******************************/
/**
 * @brief TODO
*/
typedef struct
{
	u8 newCommandRxed;
	u8 busySending;
	u8 EpControl_OutData[UVCP_OUT_BUF_SIZE];
} uvcp_ctx_t;

/************************** Variable Definitions *****************************/
extern struct Usb_DevData UsbInstance; ///< Main instance for USB communication
extern regmap_t U3V_regmap;			   ///< USB3 Vision register map
static size_t _reg_map_size;		   ///< Size of register map and manifest XML
static uvcp_ctx_t _ctx = {};		   ///< Private UVCP struct declaration

/************************** Interrupt Routines ******************************/
/**
 * @brief Control out endpoint interrupt routine.
 * 
 * Interrupt routine called after an USB reception from host on the control
 * endpoint. Simply set @p newCommandRxed since a new command candidate as been
 * received.
 * 
 * @param[in] CallBackRef
 * @param[in] RequestedBytes maximum bytes set to be received
 * @param[in] BytesTxed bytes received
 */
void ControlOutHandler(void *CallBackRef, u32 RequestedBytes, u32 BytesTxed)
{
	_ctx.newCommandRxed = TRUE;
}

/**
 * @brief Control in endpoint interrupt routine.
 * 
 * Interrupt routine called after an USB transmission to host on the control
 * endpoint. Releases @p newCommandRxed and @p busySending for a new command
 * to be sent afterwards. Also resets reception buffer for a new data to be 
 * received on the Control Out endpoint.
 * 
 * @param[in] CallBackRef
 * @param[in] RequestedBytes bytes requested to send
 * @param[in] BytesTxed bytes transmitted
 * 
 * @note Reporting when @p RequestedBytes and @p BytesTxed have different
 * values. This may occur when the host limits the transmitted size, which 
 * shall not happen.
 */
void ControlInHandler(void *CallBackRef, u32 RequestedBytes, u32 BytesTxed)
{
	if (RequestedBytes != BytesTxed)
		xil_printf("ControlInHandler: RequestedBytes %d != BytesTxed %d\n",
				   RequestedBytes, BytesTxed);

	_ctx.newCommandRxed = FALSE;
	_ctx.busySending = FALSE;

	/* Reset Rx buffer */
	uvcp_set_rx_buf();
}

/************************** Private Routines ******************************/
/**
 * @brief Initializes U3V register map base addresses.
 * 
 * The ABRM and SBRM keep the base addresses of other register maps as defined
 * in the USB3 Vision specification, as well the manifest table that keeps the
 * start address of the manifest XML file. These must be initialised at startup.
 */
static void _ucvp_setU3V_regmap(void)
{
	U3V_regmap.abrm.sbrm_addr = ((u32)&U3V_regmap.sbrm & 0x00000FFF);
	U3V_regmap.abrm.manifest_table_addr = ((u32)&U3V_regmap.manifest_table & 0x00000FFF);
	U3V_regmap.sbrm.sirm_addr = ((u32)&U3V_regmap.sirm & 0x00000FFF);
	U3V_regmap.sbrm.eirm_addr = ((u32)&U3V_regmap.eirm & 0x00000FFF);
	U3V_regmap.manifest_table.address = ((u32)&U3V_regmap.manifest_xml & 0x00000FFF);
	U3V_regmap.manifest_table.size = strlen(U3V_regmap.manifest_xml);

	_reg_map_size = sizeof(regmap_t) + U3V_regmap.manifest_table.size + 3;
}

/**
 * @brief Reads data from specified address of the U3V register map.
 * 
 * @param[in] addr register map base address to be read
 * @param[out] data buffer with read data
 * @param[in] size bytes to be read
 *
 * @return	@e UVCP_SUCCESS for write success @n
 * 			@e UVCP_ERR_INV_ADDR if given address and size result in data
 * 			outside the U3V register map.
 */
static uvcpStatus _uvcp_read_address(u64 addr, u8 *data, size_t size)
{
	if (addr < 0 || (addr + size) > _reg_map_size)
	{
		xil_printf("UVCP_ERR_INV_ADDR\n");
		return UVCP_ERR_INV_ADDR;
	}

	addr |= UV_REGMAP_ADDR;

	u8 *src = (u8 *)(u32)addr;
	memcpy(data, src, size);

	return UVCP_SUCCESS;
}

/**
 * @brief Writes data to specified address of the U3V register map.
 * 
 * @param[in] addr register map base address to be written
 * @param[in] data buffer with written data
 * @param[in] size bytes to be written
 * 
 * @return	@e UVCP_SUCCESS for write success @n
 * 			@e UVCP_ERR_INV_ADDR if given address and size result in data
 * 			outside the register maps.
 */
static uvcpStatus _uvcp_write_address(u64 addr, u8 *data, size_t size)
{
	if (addr < 0 || (addr + size) > _reg_map_size)
	{
		xil_printf("UVCP_ERR_INV_ADDR\n");
		return UVCP_ERR_INV_ADDR;
	}

	addr |= UV_REGMAP_ADDR;

	u8 *dest = (u8 *)(u32)addr;
	memcpy(dest, data, size);

	return UVCP_SUCCESS;
}

/**
 * @brief Returns the packet type field of an UVCP packet.
 * 
 * @param[in] packet UVCP packet
 * 
 * @return	packet type @n
 * 			@e UVCP_PACKET_TYPE_ERROR if @p packet is NULL.
 */
static uvcpPacketType _uvcp_packet_get_type(const uvcpPacket *packet)
{
	if (packet == NULL)
	{
		xil_printf("UVCP_PACKET_TYPE_ERROR\n");
		return UVCP_PACKET_TYPE_ERROR;
	}

	return (uvcpPacketType)(packet->header.packet_type);
}

/**
 * @brief Returns the command type field of an UVCP packet.
 * 
 * @param[in] packet UVCP packet
 * 
 * @return	packet command @n
 * 			0 if @p packet is NULL.
 */
static uvcpCommand _uvcp_packet_get_command(const uvcpPacket *packet)
{
	if (packet == NULL)
		return 0;

	return (uvcpCommand)(packet->header.command);
}

/**
 * @brief Returns the ID field of an UVCP packet.
 * 
 * @param[in] packet UVCP packet
 * 
 * @return	packet ID @n
 * 			0 if @p packet is NULL.
 */
static u16 _uvcp_packet_get_id(const uvcpPacket *packet)
{
	if (packet == NULL)
		return 0;

	return (packet->header.id);
}

/**
 * @brief Returns the size field of an UVCP packet.
 * 
 * @param[in] packet UVCP packet
 * 
 * @return	packet size @n
 * 			0 if @p packet is NULL.
 */
static u16 _uvcp_packet_get_size(const uvcpPacket *packet)
{
	if (packet == NULL)
		return 0;

	return (packet->header.size);
}

/**
 * @brief Returns the start of an UVCP packet command data.
 * 
 * @param[in] packet UVCP packet
 * 
 * @return pointer to packet data.
 */
static void *_uvcp_packet_get_cmd_data(const uvcpPacket *packet)
{
	return (u8 *)packet + sizeof(uvcpHeader);
}

/**
 * @brief Creates a UVCP packet for a memory read acknowledge.
 * 
 * @param[in] data read data buffer
 * @param[in] read_size size of read bytes 
 * @param[in] packet_id UVCP packet ID
 * @param[out] packet new UVCP packet
 * @param[out] packet_size size of new UVCP packet
 * 
 * @return 	@e UVCP_SUCCESS for new UVCP packet @n
 * 			@e UVCP_ERR_NULL if any pointed arguments is NULL
 */
static uvcpStatus _uvcp_packet_new_read_memory_ack(u8 *data, u16 read_size, u16 packet_id, uvcpPacket *packet, size_t *packet_size)
{
	if (packet == NULL || packet_size == NULL || data == NULL)
	{
		xil_printf("UVCP_ERR_NULL\n");
		return UVCP_ERR_NULL;
	}

	packet->header.magic = UVCP_CONTROL_MAGIC;
	packet->header.packet_type = UVCP_PACKET_TYPE_ACK;
	packet->header.command = UVCP_READMEM_ACK;
	packet->header.size = read_size;
	packet->header.id = packet_id;

	memcpy(packet->data, data, read_size);

	*packet_size = sizeof(uvcpHeader) + read_size;

	return UVCP_SUCCESS;
}

/**
 * @brief Creates a UVCP packet for a memory write acknowledge.
 * 
 * @param[in] length_written bytes written to memory
 * @param[in] packet_id UVCP packet ID
 * @param[out] packet new UVCP packet
 * @param[out] packet_size size of new UVCP packet
 * 
 * @return 	@e UVCP_SUCCESS for new UVCP packet @n
 * 			@e UVCP_ERR_NULL if any pointed arguments is NULL
 */
static uvcpStatus _uvcp_packet_new_write_memory_ack(u16 length_written, u16 packet_id, uvcpPacket *packet, size_t *packet_size)
{
	uvcpWriteMemoryAckInfos ack_data;

	if (packet == NULL || packet_size == NULL)
	{
		xil_printf("UVCP_ERR_NULL\n");
		return UVCP_ERR_NULL;
	}

	packet->header.magic = UVCP_CONTROL_MAGIC;
	packet->header.packet_type = UVCP_PACKET_TYPE_ACK;
	packet->header.command = UVCP_WRITEMEM_ACK;
	packet->header.size = sizeof(uvcpWriteMemoryAckInfos);
	packet->header.id = packet_id;

	ack_data.reserved = 0;
	ack_data.bytes_written = length_written;
	memcpy(packet->data, &ack_data, sizeof(uvcpWriteMemoryAckInfos));

	*packet_size = sizeof(uvcpWriteMemoryAck);

	return UVCP_SUCCESS;
}

/**
 * @brief Creates a UVCP packet for a pending acknowledge.
 * 
 * @param[in] temp_timeout new temporary timeout for host
 * @param[in] packet_id UVCP packet ID
 * @param[out] packet new UVCP packet
 * @param[out] packet_size size of new UVCP packet
 * 
 * @return 	@e UVCP_SUCCESS for new UVCP packet @n
 * 			@e UVCP_ERR_NULL if any pointed arguments is NULL
 * 
 * @todo Implement pending acknowledge scheme on UVCP if necessary.
 */
static uvcpStatus _uvcp_packet_new_pending_ack(u16 temp_timeout, u16 packet_id, uvcpPacket *packet, size_t *packet_size)
{
	uvcpPendingAckInfos ack_data;

	if (packet == NULL || packet_size == NULL)
	{
		xil_printf("UVCP_ERR_NULL\n");
		return UVCP_ERR_NULL;
	}

	packet->header.magic = UVCP_CONTROL_MAGIC;
	packet->header.packet_type = UVCP_PACKET_TYPE_ACK;
	packet->header.command = UVCP_PENDING_ACK;
	packet->header.size = sizeof(uvcpPendingAckInfos);
	packet->header.id = packet_id;

	ack_data.reserved = 0;
	ack_data.timeout = temp_timeout;
	memcpy(packet->data, &ack_data, sizeof(uvcpPendingAckInfos));

	*packet_size = sizeof(uvcpPendingAck);

	return UVCP_SUCCESS;
}

/**
 * @brief Decodes a valid UVCP packet and generates an acknowledge.
 * 
 * @param[in] buf buffer with data to be decoded
 * @param[out] ack_pkt new UVCP acknowledge packet
 * @param[out] ack_pkt_size size of new UVCP acknowledge packet
 * 
 * @return 	@e UVCP_SUCCESS if command was decoded and new acknowledge packet
 * 			generated @n
 * 			@e UVCP_ERR_NULL if any pointed arguments is NULL  @n
 * 			@e UVCP_ERR_INVALID_PACKET for invalid packet prefix @n
 * 			@e UVCP_ERR_MEMORY for invalid UVCP register map access @n
 * 			@e UVCP_ERR_PACKET_CREATE if acknowledge is not created @n
 * 			@e UVCP_ERR_INVALID_CMD for invalid packet command
 */
static uvcpStatus _ucvp_decode_control_packet(u8 *buf, uvcpPacket *ack_pkt, size_t *ack_pkt_size)
{

	u32 pkt_magic;
	u16 pkt_type, pkt_cmd, pkt_id;

	if (buf == NULL || ack_pkt == NULL || ack_pkt_size == NULL)
	{
		xil_printf("UVCP_ERR_NULL\n");
		return UVCP_ERR_NULL;
	}

	memcpy(&pkt_magic, buf, sizeof(u32));
	if (pkt_magic != UVCP_CONTROL_MAGIC)
	{
		xil_printf("UVCP_ERR_INVALID_PACKET\n");
		return UVCP_ERR_INVALID_PACKET;
	}

	pkt_type = _uvcp_packet_get_type((uvcpPacket *)buf);

	/* Only command type packets are received from the host as per U3V spec */
	if (pkt_type != UVCP_PACKET_TYPE_CMD)
	{
		xil_printf("UVCP_ERR_INVALID_PACKET\n");
		return UVCP_ERR_INVALID_PACKET;
	}

	pkt_cmd = _uvcp_packet_get_command((uvcpPacket *)buf);
	pkt_id = _uvcp_packet_get_id((uvcpPacket *)buf);

	uvcpReadMemoryCmdInfos *read_cmd;
	uvcpWriteMemoryCmdInfos *write_cmd;
	u8 read_buf[64];
	size_t write_size;

	switch (pkt_cmd)
	{
	case UVCP_READMEM_CMD:
		read_cmd = _uvcp_packet_get_cmd_data((uvcpPacket *)buf);

		if (_uvcp_read_address(read_cmd->address, read_buf, read_cmd->size) != UVCP_SUCCESS)
		{
			xil_printf("UVCP_ERR_MEMORY\n");
			return UVCP_ERR_MEMORY;
		}

		if (_uvcp_packet_new_read_memory_ack(read_buf, read_cmd->size, pkt_id, ack_pkt, ack_pkt_size) != UVCP_SUCCESS)
		{
			xil_printf("UVCP_ERR_PACKET_CREATE\n");
			return UVCP_ERR_PACKET_CREATE;
		}

		break;

	case UVCP_WRITEMEM_CMD:
		write_size = _uvcp_packet_get_size((uvcpPacket *)buf) - sizeof(write_cmd->address);
		write_cmd = _uvcp_packet_get_cmd_data((uvcpPacket *)buf);

		if (_uvcp_write_address(write_cmd->address, write_cmd->data, write_size) != UVCP_SUCCESS)
		{
			xil_printf("UVCP_ERR_MEMORY\n");
			return UVCP_ERR_MEMORY;
		}

		_uvcp_read_address(write_cmd->address, read_buf, write_size);
		if (memcmp(read_buf, write_cmd->data, write_size))
		{
			xil_printf("UVCP_ERR_VERIFY\n");
			return UVCP_ERR_VERIFY;
		}

		if (_uvcp_packet_new_write_memory_ack(write_size, pkt_id, ack_pkt, ack_pkt_size) != UVCP_SUCCESS)
		{
			xil_printf("UVCP_ERR_PACKET_CREATE\n");
			return UVCP_ERR_PACKET_CREATE;
		}

		break;
	default:
		xil_printf("UVCP_ERR_INVALID_CMD\n");
		return UVCP_ERR_INVALID_CMD;
		break;
	}

	return UVCP_SUCCESS;
}

/************************** Public Routines ******************************/
/**
 * @brief Initializes UVCP class.
 */
void uvcp_init(void)
{
	_ucvp_setU3V_regmap();
}

/**
 * @brief Setup reception buffer for Control Out Endpoint.
 */
void uvcp_set_rx_buf(void)
{
	EpBufferRecv(UsbInstance.PrivateData, USB_EPOINT_CONTROL, _ctx.EpControl_OutData, UVCP_OUT_BUF_SIZE);
}

/**
 * @brief Main UVCP handler decoding commands and sending acknowledges.
 * 
 * Confirms if a new command has been received and if no acknowledge send is
 * pending. Afterwards, tries to decode a valid control packet from a received
 * data from the host. On a valid command, an acknowledge is created and sent
 * to the host.
 * 
 * @return	@e UVCP_SUCCESS for correct command decoding and acknowledge sending @n
 * 			@e UVCP_NO_CMD if no command data has been received @n
 * 			@e UVCP_BUSY if control endpoint is busy sending an acknowledge @n
 * 			status code from @p _ucvp_decode_control_packet() call on its failure @n
 * 			@e UVCP_ERR_SENDING if acknowledge could not be sent to host
 */
uvcpStatus uvcp_handler(void)
{
	uvcpPacket ack_pkt;
	size_t ack_pkt_size;
	u32 status;

	/* No command to process */
	if (!_ctx.newCommandRxed)
		return UVCP_NO_CMD;

	/* Last Acknowledge is still being sent */
	if (_ctx.busySending)
		return UVCP_BUSY;

	status = _ucvp_decode_control_packet(_ctx.EpControl_OutData, &ack_pkt, &ack_pkt_size);
	if (status != UVCP_SUCCESS)
		return status;

	_ctx.busySending = TRUE;

	status = EpBufferSend(UsbInstance.PrivateData, USB_EPOINT_CONTROL, (u8 *)&ack_pkt, ack_pkt_size);
	if (status != XST_SUCCESS)
	{
		xil_printf("UVCP_ERR_SENDING\n");
		return UVCP_ERR_SENDING;
	}

	return UVCP_SUCCESS;
}

uvcp.h

#ifndef UVCP_H
#define UVCP_H

/************************** Include Files ****************************/
#include "xil_types.h"

/**************************** Type Definitions *******************************/
/**
 * @brief UVCP packet types.
 */
typedef enum
{
	UVCP_PACKET_TYPE_ACK = 0x0000,
	UVCP_PACKET_TYPE_CMD = 0x4000,
	UVCP_PACKET_TYPE_ERROR = 0xffff
} uvcpPacketType;

/**
 * @brief UVCP command types.
 */
typedef enum
{
	UVCP_READMEM_CMD = 0x0800,
	UVCP_READMEM_ACK = 0x0801,
	UVCP_WRITEMEM_CMD = 0x0802,
	UVCP_WRITEMEM_ACK = 0x0803,
	UVCP_PENDING_ACK = 0x0805,
} uvcpCommand;

/**
 * @brief UVCP status codes.
 */
typedef enum
{
	UVCP_SUCCESS,
	UVCP_NO_CMD,
	UVCP_BUSY,
	UVCP_ERR_UNKNOWN,
	UVCP_ERR_NULL,
	UVCP_ERR_SENDING,
	UVCP_ERR_INVALID_PACKET,
	UVCP_ERR_INVALID_CMD,
	UVCP_ERR_NO_CMD,
	UVCP_ERR_MEMORY,
	UVCP_ERR_VERIFY,
	UVCP_ERR_PACKET_CREATE,
	UVCP_ERR_INV_ADDR,
} uvcpStatus;

/**
 * @brief UVCP packet header.
 *
 * UVCP header format as specified by U3V specification.
 */
typedef struct __attribute__((__packed__))
{
	u32 magic;
	u16 packet_type;
	u16 command;
	u16 size;
	u16 id;
} uvcpHeader;

/**
 * @brief UVCP read command format.
 *
 * UVCP read command data format as specified by U3V specification.
 */
typedef struct __attribute__((__packed__))
{
	u64 address;
	u16 reserved; /* Listed as reserved, always 0 */
	u16 size;
} uvcpReadMemoryCmdInfos;

/**
 * @brief UVCP read command packet.
 *
 * UVCP read command packet as specified by U3V specification.
 */
typedef struct __attribute__((__packed__))
{
	uvcpHeader header;
	uvcpReadMemoryCmdInfos infos;
} uvcpReadMemoryCmd;

/**
 * @brief UVCP write command format.
 *
 * UVCP write command data format as specified by U3V specification.
 */
typedef struct __attribute__((__packed__))
{
	u64 address;
	u8 data[64];
} uvcpWriteMemoryCmdInfos;

/**
 * @brief UVCP write command packet.
 *
 * UVCP write command packet as specified by U3V specification.
 */
typedef struct __attribute__((__packed__))
{
	uvcpHeader header;
	uvcpWriteMemoryCmdInfos infos;
} uvcpWriteMemoryCmd;

/**
 * @brief UVCP write acknowledge format.
 *
 * UVCP write acknowledge data format as specified by U3V specification.
 */
typedef struct __attribute__((__packed__))
{
	u16 reserved; /* Listed as reserved, always 0 */
	u16 bytes_written;
} uvcpWriteMemoryAckInfos;

/**
 * @brief UVCP write acknowledge packet.
 *
 * UVCP write acknowledge packet as specified by U3V specification.
 */
typedef struct __attribute__((__packed__))
{
	uvcpHeader header;
	uvcpWriteMemoryAckInfos infos;
} uvcpWriteMemoryAck;

/**
 * @brief UVCP pending acknowledge format.
 *
 * UVCP pending acknowledge data format as specified by U3V specification.
 */
typedef struct __attribute__((__packed__))
{
	u16 reserved; /* Listed as reserved, always 0 */
	u16 timeout;
} uvcpPendingAckInfos;

/**
 * @brief UVCP pending acknowledge packet.
 *
 * UVCP pending acknowledge packet as specified by U3V specification.
 */
typedef struct __attribute__((__packed__))
{
	uvcpHeader header;
	uvcpPendingAckInfos infos;
} uvcpPendingAck;

/**
 * @brief UVCP packet.
 *
 * UVCP packet as specified by U3V specification.
 */
typedef struct __attribute__((__packed__))
{
	uvcpHeader header;
	u8 data[128];
} uvcpPacket;

/**************************** Function Prototypes *******************************/
void uvcp_init(void);
void uvcp_set_rx_buf(void);
uvcpStatus uvcp_handler(void);

#endif // UVCP_H