Aravis on Ultrascale+ using USB3

Hello,
First of all, thank you for building this project.
I’m currently studying the feasibility of using Aravis on a mixed environment with a proprietary camera connected to a Xilinx Ultrascale+ (with ARM APU and FPGA), whose processed video stream would be transferred through an USB 3.0 connection to a Linux PC running as host. The main idea would be to use the Aravis software on both Ultrascale ARM baremetal (no OS) environment (as a device) and on a PC Linux environment (as a host) for collecting the video stream.

It is possible to port the required Avaris software to an ARM environment to create an USB device and “simulate” a camera? Or this software is only prepared to be used as an USB host running Linux? I see many Linux dependencies such as glib, gio, etc. are used, so this port would require some “tweaks” which could make it very difficult.
Any help and ideas would be much appreciated.
Thank you.

Hi João,

Welcome here !

The USB code in aravis only implements the controller and stream receiving part of the USB protocol. It should be relatively easy to write the device USB code though, not very different than the GigEVision one I wrote some years ago for a work project running bare metal on a luminary microcontroller. Its is just a matter of receiving read/write command packets, and send back the request informations or an acknowledge.

Here is the code:

  • cocagne.c:
#include <inc/hw_ints.h>
#include <inc/hw_memmap.h>
#include <inc/hw_nvic.h>
#include <inc/hw_types.h>
#include <inc/hw_epi.h>
#include <inc/hw_flash.h>
#include <inc/lm3s9b92.h>
#include <driverlib/ethernet.h>
#include <driverlib/flash.h>
#include <driverlib/gpio.h>
#include <driverlib/interrupt.h>
#include <driverlib/rom.h>
#include <driverlib/rom_map.h>
#include <driverlib/sysctl.h>
#include <driverlib/systick.h>
#include <utils/uartstdio.h>
#include <utils/ustdlib.h>
#include <utils/lwiplib.h>
#include <ccggvcp.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>

#include <cocagne.zip.c>

#define CCG_FPGA_BOARD_ID_OFFSET		0
#define CCG_FPGA_BOARD_ID_MASK			0x7fff
#define CCG_FPGA_FIRMWARE_VERSION_OFFSET	2
#define CCG_FPGA_FIRMWARE_VERSION_MASK		0x00ff

/* System clock setting */

#define SYSTICKHZ               100
#define SYSTICKMS               (1000 / SYSTICKHZ)

#ifdef DEBUG
void
__error__(char *pcFilename, unsigned long ulLine)
{
}
#endif

static int firmware_update_request = 0;

/* Callback called periodically in the lwIP context. */

void
lwIPHostTimerHandler(void)
{
	static char g_pcTwirl[4] = { '\\', '|', '/', '-' };
	static unsigned long g_ulTwirlPos = 0;
	static unsigned long g_ulLastIPAddr = 0;
	unsigned long ulIPAddress;

	ulIPAddress = lwIPLocalIPAddrGet();

	if(ulIPAddress == 0) {
		UARTprintf("\b%c", g_pcTwirl[g_ulTwirlPos]);
		g_ulTwirlPos = (g_ulTwirlPos + 1) & 3;
	} else if (ulIPAddress != g_ulLastIPAddr) {
		UARTprintf("\rIP: %d.%d.%d.%d       \n", ulIPAddress & 0xff,
			   (ulIPAddress >> 8) & 0xff, (ulIPAddress >> 16) & 0xff,
			   (ulIPAddress >> 24) & 0xff);

		g_ulLastIPAddr = ulIPAddress;

		ulIPAddress = lwIPLocalNetMaskGet();
		UARTprintf("Netmask: %d.%d.%d.%d\n", ulIPAddress & 0xff,
			   (ulIPAddress >> 8) & 0xff, (ulIPAddress >> 16) & 0xff,
			   (ulIPAddress >> 24) & 0xff);

		ulIPAddress = lwIPLocalGWAddrGet();
		UARTprintf("Gateway: %d.%d.%d.%d\n\n", ulIPAddress & 0xff,
			   (ulIPAddress >> 8) & 0xff, (ulIPAddress >> 16) & 0xff,
			   (ulIPAddress >> 24) & 0xff);
	}
}

void
SysTickIntHandler(void)
{
    lwIPTimer (SYSTICKMS);
}

#define CCG_MEMORY_SIZE 		0x400
#define CCG_FPGA_REGISTERS_OFFSET	0x10000
#define CCG_FPGA_REGISTERS_SIZE		0x2000

typedef struct {
	void *pcb;
	volatile uint16_t *epi_memory;
	uint32_t control_channel_privilege;
	uint32_t heartbeat_value;
	char memory[CCG_MEMORY_SIZE];
} Cocagne;

static void
ccg_receive_gvcp_packet (void *arg, struct udp_pcb *pcb, struct pbuf *p,
			 struct ip_addr *addr, uint16_t port)
{
	Cocagne *cocagne = arg;
	CcgGvcpPacket *packet;
	struct pbuf *ack = NULL;
	uint32_t packet_count;
	uint32_t address;
	uint32_t value;
	uint32_t size;
	uint32_t IP_address;
	int dump_memory = 0;

	if (arg == NULL) {
		pbuf_free (p);
		return;
	}

	IP_address = lwIPLocalIPAddrGet();
	memcpy (cocagne->memory + CCG_GVBS_CURRENT_IP_ADDRESS_OFFSET, &IP_address, sizeof (IP_address));

	packet = p->payload;
	packet_count = ccg_gvcp_packet_get_packet_count (packet);

	/* A firmware update request is triggered by a write at the
	 * address CCG_FPGA_REGISTERS_OFFSET + CCG_FPGA_REGISTERS_SIZE */

	switch (ntohs (packet->header.command)) {
		case CCG_GVCP_COMMAND_DISCOVERY_CMD:
			UARTprintf ("Discovery\n");
			ack = ccg_gvcp_packet_new_discovery_ack ();
			if (ack != NULL)
				packet = ack->payload;
				memcpy (&packet->data, cocagne->memory, CCG_GVBS_DISCOVERY_DATA_SIZE);
			break;

		case CCG_GVCP_COMMAND_READ_MEMORY_CMD:
			ccg_gvcp_packet_get_read_memory_cmd_infos (packet, &address, &size);
			UARTprintf ("Read %d bytes at 0x%08x\n", size, address);
			ack = ccg_gvcp_packet_new_read_memory_ack (address, size, packet_count);
			if (ack == NULL)
				break;

			packet = ack->payload;
			if (size + address <= CCG_MEMORY_SIZE) {
				memcpy (ccg_gvcp_packet_get_read_memory_ack_data (packet),
					cocagne->memory + address, size);
				dump_memory = 1;
			} else if (address == CCG_FPGA_REGISTERS_OFFSET + CCG_FPGA_REGISTERS_SIZE &&
				   size == sizeof (uint32_t)) {
				value = 0;

				memcpy (ccg_gvcp_packet_get_read_memory_ack_data (packet),
					&value,
					sizeof (uint32_t));
			} else if (address >= CCG_FPGA_REGISTERS_OFFSET &&
				   address + size <= CCG_FPGA_REGISTERS_OFFSET + CCG_FPGA_REGISTERS_SIZE &&
				   size == sizeof (uint32_t)) {
				uint16_t low, high;
				unsigned epi_address;

				epi_address = (address - CCG_FPGA_REGISTERS_OFFSET) / 2;

				low = cocagne->epi_memory[epi_address];
				high =  cocagne->epi_memory[epi_address + 1];

				value = low + (high << 16);

				memcpy (ccg_gvcp_packet_get_read_memory_ack_data (packet),
					&value,
					sizeof (uint32_t));

			} else if (address >= CCG_GENICAM_DATA_OFFSET &&
				   address + size <= CCG_GENICAM_DATA_OFFSET + CCG_GENICAM_DATA_SIZE + 4 ) {
				memcpy (ccg_gvcp_packet_get_read_memory_ack_data (packet),
					ccg_genicam_xml + address - CCG_GENICAM_DATA_OFFSET,
					size);
			} else {
				memset (ccg_gvcp_packet_get_read_memory_ack_data (packet), 0, size);
				UARTprintf ("Invalid read area\n");
			}
			break;

		case CCG_GVCP_COMMAND_WRITE_MEMORY_CMD:
			ccg_gvcp_packet_get_write_memory_cmd_infos (packet, &address, &size);
			UARTprintf ("Write %d bytes at 0x%08x\n", size, address);

			if (address == CCG_FPGA_REGISTERS_OFFSET + CCG_FPGA_REGISTERS_SIZE &&
			    size == sizeof (uint32_t)) {
				UARTprintf ("Update request\n");
				firmware_update_request = 1;
			} else if (address >= CCG_FPGA_REGISTERS_OFFSET &&
			    address + size <= CCG_FPGA_REGISTERS_OFFSET + CCG_FPGA_REGISTERS_SIZE &&
			    size == sizeof (uint32_t)) {
				uint16_t low, high;
				unsigned epi_address;

				epi_address = (address - CCG_FPGA_REGISTERS_OFFSET) / 2;

				memcpy (&value,
					ccg_gvcp_packet_get_write_memory_cmd_data (packet),
					sizeof (uint32_t));

				low = value & 0xffff;
				high = (value >> 16) & 0xffff;

				cocagne->epi_memory[epi_address] = low;
				cocagne->epi_memory[epi_address + 1] = high;
			} else {
				UARTprintf ("Invalid write area\n");
			}
			ack = ccg_gvcp_packet_new_write_memory_ack (address, packet_count);
			break;

		case CCG_GVCP_COMMAND_READ_REGISTER_CMD:
			ccg_gvcp_packet_get_read_register_cmd_infos (packet, &address);
			UARTprintf ("Read register at 0x%08x\n", address);

			if (address + sizeof (uint32_t) <= CCG_MEMORY_SIZE) {
				value = *((uint32_t *) (char *) (cocagne->memory + address));
			} else if (address == CCG_FPGA_REGISTERS_OFFSET + CCG_FPGA_REGISTERS_SIZE) {
				value = 0;
			} else if (address >= CCG_FPGA_REGISTERS_OFFSET &&
				   address + sizeof (uint32_t) <= CCG_FPGA_REGISTERS_OFFSET + CCG_FPGA_REGISTERS_SIZE) {
				uint16_t low, high;
				unsigned epi_address;

				epi_address = (address - CCG_FPGA_REGISTERS_OFFSET) / 2;

				low = cocagne->epi_memory[epi_address];
				high =  cocagne->epi_memory[epi_address + 1];

				value = low + (high << 16);
			} else
				switch (address) {
					case CCG_GVBS_CONTROL_CHANNEL_PRIVILEGE_OFFSET:
						value = cocagne->control_channel_privilege;
						break;
					case CCG_GVBS_GVCP_CAPABILITY_OFFSET:
						value = 0;
						break;
					case CCG_GVBS_HEARTBEAT_TIMEOUT_OFFSET:
						value = cocagne->heartbeat_value;
						break;
					default:
						UARTprintf ("Invalid read address\n");
						value = 0;
				}
			ack = ccg_gvcp_packet_new_read_register_ack (value, packet_count);
			break;

		case CCG_GVCP_COMMAND_WRITE_REGISTER_CMD:
			ccg_gvcp_packet_get_write_register_cmd_infos (packet, &address, &value);
			UARTprintf ("Write 0x%08x at register 0x%08x\n", value, address);

			if (address + sizeof (u32_t) <= CCG_MEMORY_SIZE) {
				*((u32_t *) (char *) (cocagne->memory + address)) = value;
			} else if (address == CCG_FPGA_REGISTERS_OFFSET + CCG_FPGA_REGISTERS_SIZE) {
				UARTprintf ("Update request\n");
				firmware_update_request = 1;
			} else if (address >= CCG_FPGA_REGISTERS_OFFSET &&
				   address + sizeof (uint32_t) - CCG_FPGA_REGISTERS_OFFSET <= CCG_FPGA_REGISTERS_SIZE) {
				uint16_t low, high;
				unsigned epi_address;

				epi_address = (address - CCG_FPGA_REGISTERS_OFFSET) / 2;

				low = value & 0xffff;
				high = (value >> 16) & 0xffff;

				cocagne->epi_memory[epi_address] = low;
				cocagne->epi_memory[epi_address + 1] = high;
			} else {
				switch (address) {
					case CCG_GVBS_CONTROL_CHANNEL_PRIVILEGE_OFFSET:
						cocagne->control_channel_privilege = value;
						break;
					case CCG_GVBS_HEARTBEAT_TIMEOUT_OFFSET:
						cocagne->heartbeat_value = value;
						break;
					default:
						UARTprintf ("Invalid write address\n");
				}
			}
			ack = ccg_gvcp_packet_new_write_register_ack (address, packet_count);
			break;

		default:
			UARTprintf ("Unknown command\n");
	}

	pbuf_free(p);

	if (ack != NULL) {
		udp_sendto (cocagne->pcb, ack, addr, port);
		pbuf_free (ack);
	}

#if 0
	if (dump_memory) {
		int i;
		for (i = 0; i < block_size; i++) {
			UARTprintf ("0x%02x ", *(cocagne->memory + block_address + i));
			if ((i % 8) == 7)
				UARTprintf ("\n");
			else
				UARTprintf (" ");
		}
		if (i != 0)
			UARTprintf ("\n");
	}
#endif
}

static int
ccg_get_board_id (Cocagne *cocagne)
{
	if (cocagne == NULL)
		return 0;

	return cocagne->epi_memory[CCG_FPGA_BOARD_ID_OFFSET] & CCG_FPGA_BOARD_ID_MASK;
}

static int
ccg_get_firmware_version (Cocagne *cocagne)
{
	if (cocagne == NULL)
		return 0;

	return cocagne->epi_memory[CCG_FPGA_FIRMWARE_VERSION_OFFSET] & CCG_FPGA_FIRMWARE_VERSION_MASK;
}

static Cocagne *
ccg_new (uint16_t *epi_memory)
{
	Cocagne *cocagne;
	unsigned char pucMACArray[8];
	char *memory;
	int result;
	uint32_t value;

	cocagne = mem_malloc (sizeof (Cocagne));
	if (cocagne == NULL)
		return NULL;

	memset (cocagne, 0, sizeof (Cocagne));
	cocagne->epi_memory = epi_memory;
	memory = cocagne->memory;

	strcpy (memory + CCG_GVBS_MANUFACTURER_NAME_OFFSET, "LAPP");
	strcpy (memory + CCG_GVBS_MODEL_NAME_OFFSET, "CameraBox");
	strcpy (memory + CCG_GVBS_DEVICE_VERSION_OFFSET, "1.0");
	strcpy (memory + CCG_GVBS_MANUFACTURER_INFORMATIONS_OFFSET, "Annecy-le-Vieux - France");

	usnprintf (memory + CCG_GVBS_SERIAL_NUMBER_OFFSET, CCG_GVBS_SERIAL_NUMBER_SIZE,
		   "CCG%04x", ccg_get_board_id (cocagne));

	strcpy (memory + CCG_GVBS_XML_URL_0_OFFSET, ccg_genicam_filename);

	lwIPLocalMACGet (pucMACArray);

	memcpy (memory + CCG_GVBS_DEVICE_MAC_ADDRESS_HIGH_OFFSET + 2, pucMACArray, sizeof (pucMACArray));

	value = 0;
	memcpy (memory + CCG_GVBS_DEVICE_MODE_OFFSET, &value, sizeof (value));

	cocagne->pcb = udp_new ();
	udp_recv (cocagne->pcb, ccg_receive_gvcp_packet, cocagne);
	result = udp_bind (cocagne->pcb, IP_ADDR_ANY, CCG_GVCP_PORT);
	if (result != ERR_OK)
		UARTprintf("Bind failed\n");

	return cocagne;
}

static uint16_t *
epi_init_gpio_16_12 (uint32_t map_address_code, unsigned int clock_divider)
{
	uint32_t map_address;
	uint16_t *p_uint16;

	/* Configuration of EPI in GPIO mode, 16 data bits, 12 address bits. */

	/* 1. Enable the EPI module using the RCGC1 register. */

	ROM_SysCtlPeripheralEnable (SYSCTL_PERIPH_EPI0);

	/* 2. Enable the clock to the appropriate GPIO module via the RCGC2 
	 * register. See page 286. To find out which GPIO port to enable, refer
	 * to Table 9-1 on page 455 or Table 9-2 on page 456. */

	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);
	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);
	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE);
	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOG);
	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOH);
	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOJ);

	/* 3. Set the GPIO AFSEL bits for the appropriate pins. See page 421.
	 * To determine which GPIOs to configure, see Table 24-4 on page 1238. */

	GPIOPinConfigure(GPIO_PH3_EPI0S0);
	GPIOPinConfigure(GPIO_PH2_EPI0S1);
	GPIOPinConfigure(GPIO_PC4_EPI0S2);
	GPIOPinConfigure(GPIO_PC5_EPI0S3);
	GPIOPinConfigure(GPIO_PC6_EPI0S4);
	GPIOPinConfigure(GPIO_PC7_EPI0S5);
	GPIOPinConfigure(GPIO_PH0_EPI0S6);
	GPIOPinConfigure(GPIO_PH1_EPI0S7);
	GPIOPinConfigure(GPIO_PE0_EPI0S8);
	GPIOPinConfigure(GPIO_PE1_EPI0S9);
	GPIOPinConfigure(GPIO_PH4_EPI0S10);
	GPIOPinConfigure(GPIO_PH5_EPI0S11);
	GPIOPinConfigure(GPIO_PF4_EPI0S12);
	GPIOPinConfigure(GPIO_PG0_EPI0S13);
	GPIOPinConfigure(GPIO_PG1_EPI0S14);
	GPIOPinConfigure(GPIO_PF5_EPI0S15);
	GPIOPinConfigure(GPIO_PJ0_EPI0S16);
	GPIOPinConfigure(GPIO_PJ1_EPI0S17);
	GPIOPinConfigure(GPIO_PJ2_EPI0S18);
	GPIOPinConfigure(GPIO_PJ3_EPI0S19);
	GPIOPinConfigure(GPIO_PD2_EPI0S20);
	GPIOPinConfigure(GPIO_PD3_EPI0S21);
	GPIOPinConfigure(GPIO_PB5_EPI0S22);
	GPIOPinConfigure(GPIO_PB4_EPI0S23);
	GPIOPinConfigure(GPIO_PE2_EPI0S24);
	GPIOPinConfigure(GPIO_PE3_EPI0S25);
	GPIOPinConfigure(GPIO_PH6_EPI0S26);
	GPIOPinConfigure(GPIO_PH7_EPI0S27);
	GPIOPinConfigure(GPIO_PJ4_EPI0S28);
	GPIOPinConfigure(GPIO_PJ5_EPI0S29);
	GPIOPinConfigure(GPIO_PJ6_EPI0S30);
	GPIOPinConfigure(GPIO_PG7_EPI0S31);

	/* 4. Configure the GPIO current level and/or slew rate as specified for
	 *  the mode selected. See page 423 and page 431. */

	GPIOPinTypeEPI(GPIO_PORTB_BASE, GPIO_PIN_5 | GPIO_PIN_4);
	GPIOPinTypeEPI(GPIO_PORTC_BASE, GPIO_PIN_7 | GPIO_PIN_6 | GPIO_PIN_5 | GPIO_PIN_4);
	GPIOPinTypeEPI(GPIO_PORTD_BASE, GPIO_PIN_3 | GPIO_PIN_2);
	GPIOPinTypeEPI(GPIO_PORTE_BASE, GPIO_PIN_3 | GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_0);
	GPIOPinTypeEPI(GPIO_PORTF_BASE, GPIO_PIN_5 | GPIO_PIN_4);
	GPIOPinTypeEPI(GPIO_PORTG_BASE, GPIO_PIN_7 | GPIO_PIN_1 | GPIO_PIN_0);
	GPIOPinTypeEPI(GPIO_PORTH_BASE, GPIO_PIN_7 | GPIO_PIN_6 | GPIO_PIN_5 | GPIO_PIN_4 |
		       GPIO_PIN_3 | GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_0);
	GPIOPinTypeEPI(GPIO_PORTJ_BASE, GPIO_PIN_6 | GPIO_PIN_5 | GPIO_PIN_4 | GPIO_PIN_3 |
		       GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_0);

	/* 6.1. select the mode (GPIO) using the MODE field in EPICFG register - see page 368.
	 * EPI_CFG_BLKEN is mandatory for correct further register programming
	 * EPI_CFG_MODE_NONE sets "General Purpose" mode. */

	EPI0_CFG_R = EPI_CFG_BLKEN | EPI_CFG_MODE_NONE;

	 /* 6.2. set mode-specific details in registers EPIxxxCFG and EPIxxxCFG2
	  * -> GPIO mode - see pages 379, 388
	  * D16/A12, 1-cycle writes, 2-cycle reads, free running clock. */

	EPI0_GPCFG_R = EPI_GPCFG_CLKPIN |
		EPI_GPCFG_DSIZE_16BIT | EPI_GPCFG_ASIZE_12BIT |
		EPI_GPCFG_RD2CYC | EPI_GPCFG_RW;

	EPI0_BAUD_R = (EPI0_BAUD_R & ~EPI_BAUD_COUNT_M) | ((clock_divider - 1) & 0xffff);

	switch (map_address_code) {
		case EPI_ADDRMAP_EPADR_A000:
			map_address = 0xA0000000;
			EPI0_ADDRMAP_R = EPI_ADDRMAP_EPSZ_64KB | map_address_code;
			break;
		case EPI_ADDRMAP_EPADR_C000:
			map_address = 0xC0000000;
			EPI0_ADDRMAP_R = EPI_ADDRMAP_EPSZ_64KB | map_address_code;
			break;
		case EPI_ADDRMAP_ERADR_6000:
			map_address = 0x60000000;
			EPI0_ADDRMAP_R = EPI_ADDRMAP_ERSZ_64KB | map_address_code;
			break;
		case EPI_ADDRMAP_ERADR_8000:
			map_address = 0x80000000;
			EPI0_ADDRMAP_R = EPI_ADDRMAP_ERSZ_64KB | map_address_code;
			break;
		default:
			return NULL;
	}


	/* Set default addresses and increments - see pages 391 - 392. */

	EPI0_RADDR0_R = 0;
	EPI0_RSIZE0_R = EPI_RSIZE0_SIZE_16BIT;
	EPI0_RADDR1_R = 0;
	EPI0_RSIZE1_R = EPI_RSIZE1_SIZE_16BIT;

	p_uint16 = (void *) map_address;

	return (void *) map_address;
}

int
main(void)
{
	Cocagne *cocagne;
	unsigned long ulUser0, ulUser1;
	unsigned char pucMACArray[8];
	uint16_t *epi_memory;

	/* Set the clocking to run directly from the crystal */
	ROM_SysCtlClockSet(SYSCTL_SYSDIV_1 | SYSCTL_USE_OSC | SYSCTL_OSC_MAIN |
			   SYSCTL_XTAL_16MHZ);

	/* Initialize the UART */
	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
	GPIOPinConfigure(GPIO_PA0_U0RX);
	GPIOPinConfigure(GPIO_PA1_U0TX);
	ROM_GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
	UARTStdioInit(0);
	UARTprintf("\033[2JCOCAGNE - COntrole de CAmeras Gigabit etherNEt\n\n");
	UARTprintf("Laboratoire d'Annecy-le-Vieux de physique des particules - 2010-2013\n\n");

	/* Enable and Reset the Ethernet Controller */
	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_ETH);
	ROM_SysCtlPeripheralReset(SYSCTL_PERIPH_ETH);

	/* Enable Port F for Ethernet LEDs */
	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
	GPIOPinConfigure(GPIO_PF2_LED1);
	GPIOPinConfigure(GPIO_PF3_LED0);
	GPIOPinTypeEthernetLED(GPIO_PORTF_BASE, GPIO_PIN_2 | GPIO_PIN_3);

	/* Configure SysTick for a periodic interrupt */
	ROM_SysTickPeriodSet(ROM_SysCtlClockGet() / SYSTICKHZ);
	ROM_SysTickEnable();
	ROM_SysTickIntEnable();

	epi_memory = epi_init_gpio_16_12 (EPI_ADDRMAP_EPADR_C000, 4);
	if (epi_memory == NULL) {
		UARTprintf("EPI initialization failed!\n");
		while(1) { }
	}

	/* Enable processor interrupts */
	ROM_IntMasterEnable();

	/* Read the MAC address from the user registers */
	ROM_FlashUserGet(&ulUser0, &ulUser1);
	if((ulUser0 == 0xffffffff) || (ulUser1 == 0xffffffff))
	{
		uint32_t board_id = epi_memory[CCG_FPGA_BOARD_ID_OFFSET] & CCG_FPGA_BOARD_ID_MASK;

		pucMACArray[0] = 0x02;
		pucMACArray[1] = 0x63;
		pucMACArray[2] = 0x63;
		pucMACArray[3] = 0x67;
		pucMACArray[4] = (board_id & 0xff00) >> 8;
		pucMACArray[5] = board_id & 0xff;

		ulUser0 = pucMACArray[0] | (pucMACArray[1] << 8) | (pucMACArray[2] << 16);
		ulUser1 = pucMACArray[3] | (pucMACArray[4] << 8) | (pucMACArray[5] << 16);;

		ROM_FlashUserSet(ulUser0, ulUser1);

		UARTprintf("MAC address initialized!\n\n");
	} else {
		/* Convert the 24/24 split MAC address from NV ram into a 32/16 split MAC
		   address needed to program the hardware registers, then program the MAC
		   address into the Ethernet Controller registers. */

		pucMACArray[0] = ((ulUser0 >>  0) & 0xff);
		pucMACArray[1] = ((ulUser0 >>  8) & 0xff);
		pucMACArray[2] = ((ulUser0 >> 16) & 0xff);
		pucMACArray[3] = ((ulUser1 >>  0) & 0xff);
		pucMACArray[4] = ((ulUser1 >>  8) & 0xff);
		pucMACArray[5] = ((ulUser1 >> 16) & 0xff);
	}

	/* Initialze the lwIP library, using DHCP. */
	lwIPInit(pucMACArray, 0, 0, 0, IPADDR_USE_DHCP);

	cocagne = ccg_new (epi_memory);

	UARTprintf ("Board ID             : 0x%04x\n", ccg_get_board_id (cocagne));
	UARTprintf ("FPGA Firmware version: 0x%02x\n", ccg_get_firmware_version (cocagne));
	UARTprintf ("MAC address          : %02x:%02x:%02x:%02x:%02x:%02x\n\n",
		    pucMACArray[0], pucMACArray[1], pucMACArray[2],
		    pucMACArray[3], pucMACArray[4], pucMACArray[5]);

	if (HWREG (FLASH_BOOTCFG) & FLASH_BOOTCFG_NW) {
		unsigned long boot_cfg;

		UARTprintf ("Commit boot loader configuration\n");

		/* Boot loader enabled with Port E / pin 4 - lsm3s9b92.pdf - page 328 */
		boot_cfg = 0x00000102;
		HWREG (FLASH_FMD) = boot_cfg;

		/* Commit */
		HWREG(FLASH_FMA) = 0x75100000;
		HWREG(FLASH_FMC) = FLASH_FMC_WRKEY | FLASH_FMC_COMT;

		/* Wait for completion */
		while(HWREG(FLASH_FMC) & FLASH_FMC_COMT);

		UARTprintf ("Boot loader configuration commited\n");
	} else {
		unsigned long boot_cfg;

		boot_cfg = HWREG (FLASH_BOOTCFG);
		UARTprintf ("Boot loader configuration: %08x\n\n", boot_cfg);
	}

	/* Indicate that DHCP has started */
	UARTprintf("Waiting for IP... ");

	/* Loop until a firmware update request.  All the work is done in interrupt handlers. */
	while(!firmware_update_request) {
	}

	/* Wait for 3 seconds. (SysCtlDelay takes 3 cycles) */
	MAP_SysCtlDelay(ROM_SysCtlClockGet());

	UARTprintf("Load firmware...");

	/* Disable all processor interrupts.  Instead of disabling them
	   one at a time (and possibly missing an interrupt if new sources
	   are added), a direct write to NVIC is done to disable all
	   peripheral interrupts. */
	HWREG(NVIC_DIS0) = 0xffffffff;
	HWREG(NVIC_DIS1) = 0xffffffff;

	/* Also disable the SysTick interrupt. */
	SysTickIntDisable();

	/* Return control to the boot loader.  This is a call to the SVC
	   handler in the boot loader, or to the ROM if available. */
#ifdef ROM_UpdateEthernet
	ROM_UpdateEthernet(ROM_SysCtlClockGet());
#else
	(*((void (*)(void))(*(unsigned long *)0x2c)))();
#endif
  • ccggvcp.h:
#ifndef CCG_GVCP_H
#define CCG_GVCP_H

#include <stdlib.h>
#include <arch/cc.h>
#include <lwip/inet.h>

#define CCG_GVCP_PORT	3956

#define CCG_GVBS_VERSION_OFFSET				0x00000000
#define CCG_GVBS_VERSION_MINOR_MASK			0x0000ffff
#define CCG_GVBS_VERSION_MINOR_POS			0
#define CCG_GVBS_VERSION_MAJOR_MASK			0xffff0000
#define CCG_GVBS_VERSION_MAJOR_POS			16

#define CCG_GVBS_DEVICE_MODE_OFFSET			0x00000004
#define CCG_GVBS_DEVICE_MODE_BIG_ENDIAN			1 << 31
#define CCG_GVBS_DEVICE_MODE_CHARACTER_SET_MASK		0x0000ffff
#define CCG_GVBS_DEVICE_MODE_CHARACTER_SET_POS		0

#define CCG_GVBS_DEVICE_MAC_ADDRESS_HIGH_OFFSET		0x00000008
#define CCG_GVBS_DEVICE_MAC_ADDRESS_LOW_OFFSET		0x0000000c

#define CCG_GVBS_SUPPORTED_IP_CONFIGURATION_OFFSET	0x00000010
#define CCG_GVBS_CURRENT_IP_CONFIGURATION_OFFSET	0x00000014
#define CCG_GVBS_IP_CONFIGURATION_PERSISTENT		1 << 0
#define CCG_GVBS_IP_CONFIGURATION_DHCP			1 << 1
#define CCG_GVBS_IP_CONFIGURATION_LLA			1 << 2

#define CCG_GVBS_CURRENT_IP_ADDRESS_OFFSET		0x00000024
#define CCG_GVBS_CURRENT_SUBNET_MASK_OFFSET		0x00000034
#define CCG_GVBS_CURRENT_GATEWAY_OFFSET			0x00000044

#define CCG_GVBS_MANUFACTURER_NAME_OFFSET		0x00000048
#define CCG_GVBS_MANUFACTURER_NAME_SIZE			32

#define CCG_GVBS_MODEL_NAME_OFFSET			0x00000068
#define CCG_GVBS_MODEL_NAME_SIZE			32

#define CCG_GVBS_DEVICE_VERSION_OFFSET			0x00000088
#define CCG_GVBS_DEVICE_VERSION_SIZE			32

#define CCG_GVBS_MANUFACTURER_INFORMATIONS_OFFSET	0x000000a8
#define CCG_GVBS_MANUFACTURER_INFORMATIONS_SIZE		48

#define CCG_GVBS_SERIAL_NUMBER_OFFSET			0x000000d8
#define CCG_GVBS_SERIAL_NUMBER_SIZE			16

#define CCG_GVBS_USER_DEFINED_NAME_OFFSET		0x000000e8
#define CCG_GVBS_USER_DEFINED_NAME_SIZE			16

#define CCG_GVBS_DISCOVERY_DATA_SIZE			0xf8

#define CCG_GVBS_XML_URL_0_OFFSET			0x00000200
#define CCG_GVBS_XML_URL_1_OFFSET			0x00000400
#define CCG_GVBS_XML_URL_SIZE				512

#define CCG_GVBS_N_NETWORK_INTERFACES_OFFSET		0x00000600

#define	CCG_GVBS_PERSISTENT_IP_ADDRESS_0_OFFSET		0x0000064c
#define	CCG_GVBS_PERSISTENT_SUBNET_MASK_0_OFFSET	0x0000065c
#define	CCG_GVBS_PERSISTENT_GATEWAY_0_OFFSET		0x0000066c

#define CCG_GVBS_N_MESSAGE_CHANNELS_OFFSET		0x00000900
#define CCG_GVBS_N_STREAM_CHANNELS_OFFSET		0x00000904

#define CCG_GVBS_GVCP_CAPABILITY_OFFSET			0x00000934
#define CCG_GVBS_GVCP_CAPABILITY_CONCATENATION		1 << 0
#define CCG_GVBS_GVCP_CAPABILITY_WRITE_MEMORY		1 << 1
#define CCG_GVBS_GVCP_CAPABILITY_PACKET_RESEND		1 << 2
#define CCG_GVBS_GVCP_CAPABILITY_EVENT			1 << 3
#define CCG_GVBS_GVCP_CAPABILITY_EVENT_DATA		1 << 4
#define CCG_GVBS_GVCP_CAPABILITY_SERIAL_NUMBER		1 << 30
#define CCG_GVBS_GVCP_CAPABILITY_NAME_REGISTER		1 << 31

#define CCG_GVBS_HEARTBEAT_TIMEOUT_OFFSET		0x00000938
#define CCG_GVBS_TIMESTAMP_TICK_FREQUENCY_HIGH_OFFSET	0x0000093c
#define CCG_GVBS_TIMESTAMP_TICK_FREQUENCY_LOW_OFFSET	0x00000940
#define CCG_GVBS_TIMESTAMP_CONTROL_OFFSET		0x00000944
#define CCG_GVBS_TIMESTAMP_LATCHED_VALUE_HIGH_OFFSET	0x00000948
#define CCG_GVBS_TIMESTAMP_LATCHED_VALUE_LOW_OFFSET	0x0000094c

#define CCG_GVBS_CONTROL_CHANNEL_PRIVILEGE_OFFSET	0x00000a00
#define CCG_GVBS_CONTROL_CHANNEL_PRIVILEGE_CONTROL	1 << 1
#define CCG_GVBS_CONTROL_CHANNEL_PRIVILEGE_EXCLUSIVE	1 << 0

#define CCG_GVBS_STREAM_CHANNEL_0_PORT_OFFSET		0x00000d00

#define CCG_GVBS_STREAM_CHANNEL_0_PACKET_SIZE_OFFSET		0x00000d04
#define CCG_GVBS_STREAM_CHANNEL_0_PACKET_SIZE_MASK		0x0000ffff
#define CCG_GVBS_STREAM_CHANNEL_0_PACKET_SIZE_POS		0
#define CCG_GVBS_STREAM_CHANNEL_0_PACKET_BIG_ENDIAN		1 << 29
#define CCG_GVBS_STREAM_CHANNEL_0_PACKET_DO_NOT_FRAGMENT	1 << 30
#define CCG_GVBS_STREAM_CHANNEL_0_PACKET_SIZE_FIRE_TEST		1 << 31

#define CCG_GVBS_STREAM_CHANNEL_0_PACKET_DELAY_OFFSET		0x00000d08

#define CCG_GVBS_STREAM_CHANNEL_0_IP_ADDRESS_OFFSET		0x00000d18

#define CCG_GVBS_DEVICE_LINK_SPEED_0_OFFSET			0x0000b000

#define CCG_GVBS_STREAM_CHANNEL_0_PACKET_SIZE_MIN_OFFSET		0x0000c000
#define CCG_GVBS_STREAM_CHANNEL_0_PACKET_SIZE_MAX_OFFSET		0x0000c004
#define CCG_GVBS_STREAM_CHANNEL_0_PACKET_DELAY_MIN_OFFSET		0x0000c008
#define CCG_GVBS_STREAM_CHANNEL_0_PACKET_DELAY_MAX_OFFSET		0x0000c00c
#define CCG_GVBS_STREAM_CHANNEL_0_FRAME_TRANSMISSION_DELAY_OFFSET	0x0000c010
#define CCG_GVBS_STREAM_CHANNEL_0_FRAME_TRANSMISSION_DELAY_MIN_OFFSET	0x0000c014
#define CCG_GVBS_STREAM_CHANNEL_0_FRAME_TRANSMISSION_DELAY_MAX_OFFSET	0x0000c018
#define CCG_GVBS_STREAM_CHANNEL_0_BANDWITDH_RESERVE_OFFSET		0x0000c01c
#define CCG_GVBS_STREAM_CHANNEL_0_BANDWITDH_RESERVE_MIN_OFFSET		0x0000c020
#define CCG_GVBS_STREAM_CHANNEL_0_BANDWITDH_RESERVE_MAX_OFFSET		0x0000c024
#define CCG_GVBS_STREAM_CHANNEL_0_BANDWITDH_RESERVE_ACCUMULATION_OFFSET	0x0000c028
#define CCG_GVBS_STREAM_CHANNEL_0_BANDWITDH_RESERVE_ACCUMULATION_MIN_OFFSET	0x0000c02c
#define CCG_GVBS_STREAM_CHANNEL_0_BANDWITDH_RESERVE_ACCUMULATION_MAX_OFFSET	0x0000c030
#define CCG_GVBS_STREAM_CHANNEL_0_THROUGHPUT_MAX_OFFSET			0x0000c100
#define CCG_GVBS_STREAM_CHANNEL_0_CURRENT_THOURGHPUT_OFFSET		0x0000c104
#define CCG_GVBS_STREAM_CHANNEL_0_ASSIGNED_BANDWIDTH_OFFSET		0x0000c108
#define CCG_GVBS_STREAM_CHANNEL_0_FRAME_JITTER_MAX_OFFSET		0x0000c10c

#define CCG_GVCP_DATA_SIZE_MAX				512

typedef enum {
	CCG_GVCP_PACKET_TYPE_ACK =		0x0000,
	CCG_GVCP_PACKET_TYPE_RESEND =		0x4200,
	CCG_GVCP_PACKET_TYPE_CMD = 		0x4201,
	CCG_GVCP_PACKET_TYPE_ERROR =		0x8006
} CcgGvcpPacketType;

typedef enum {
	CCG_GVCP_COMMAND_DISCOVERY_CMD =	0x0002,
	CCG_GVCP_COMMAND_DISCOVERY_ACK =	0x0003,
	CCG_GVCP_COMMAND_BYE_CMD = 		0x0004,
	CCG_GVCP_COMMAND_BYE_ACK = 		0x0005,
	CCG_GVCP_COMMAND_PACKET_RESEND_CMD =	0x0040,
	CCG_GVCP_COMMAND_PACKET_RESEND_ACK =	0x0041,
	CCG_GVCP_COMMAND_READ_REGISTER_CMD =	0x0080,
	CCG_GVCP_COMMAND_READ_REGISTER_ACK =	0x0081,
	CCG_GVCP_COMMAND_WRITE_REGISTER_CMD =	0x0082,
	CCG_GVCP_COMMAND_WRITE_REGISTER_ACK =	0x0083,
	CCG_GVCP_COMMAND_READ_MEMORY_CMD =	0x0084,
	CCG_GVCP_COMMAND_READ_MEMORY_ACK =	0x0085,
	CCG_GVCP_COMMAND_WRITE_MEMORY_CMD =	0x0086,
	CCG_GVCP_COMMAND_WRITE_MEMORY_ACK =	0x0087
} CcgGvcpCommand;

typedef struct {
	u16_t packet_type;
	u16_t command;
	u16_t size;
	u16_t count;
}  __attribute__((__packed__)) CcgGvcpHeader;

typedef struct {
	CcgGvcpHeader header;
	unsigned char data[];
} CcgGvcpPacket;

struct pbuf * 		ccg_gvcp_packet_new_read_memory_ack 	(u32_t address, u32_t size, u32_t packet_count);
struct pbuf * 		ccg_gvcp_packet_new_write_memory_ack	(u32_t address, u32_t packet_count);
struct pbuf * 		ccg_gvcp_packet_new_read_register_ack 	(u32_t value, u32_t packet_count);
struct pbuf * 		ccg_gvcp_packet_new_write_register_ack 	(u32_t address, u32_t packet_count);
struct pbuf * 		ccg_gvcp_packet_new_discovery_cmd 	(void);
struct pbuf * 		ccg_gvcp_packet_new_discovery_ack 	(void);

static inline void
ccg_gvcp_packet_set_packet_count (CcgGvcpPacket *packet, u16_t count)
{
	if (packet != NULL)
		packet->header.count = htons (count);
}

static inline u16_t
ccg_gvcp_packet_get_packet_count (CcgGvcpPacket *packet)
{
	if (packet == NULL)
		return 0;

	return ntohs (packet->header.count);
}

static inline void
ccg_gvcp_packet_get_read_memory_cmd_infos (const CcgGvcpPacket *packet, u32_t *address, u32_t *size)
{
	if (packet == NULL)
		return;
	if (address != NULL)
		*address = ntohl (*((u32_t *) ((char *) packet + sizeof (CcgGvcpPacket))));
	if (size != NULL)
		*size = (ntohl (*((u32_t *) ((char *) packet + sizeof (CcgGvcpPacket) + sizeof (u32_t))))) &
			0xffff;
}

static inline size_t
ccg_gvcp_packet_get_read_memory_ack_size (u32_t data_size)
{
	return sizeof (CcgGvcpHeader) + sizeof (u32_t) + data_size;
}

static inline void *
ccg_gvcp_packet_get_read_memory_ack_data (const CcgGvcpPacket *packet)
{
	return (char *) packet + sizeof (CcgGvcpHeader) + sizeof (u32_t);
}

static inline void
ccg_gvcp_packet_get_write_memory_cmd_infos (const CcgGvcpPacket *packet, u32_t *address, u32_t *size)
{
	if (packet == NULL)
		return;

	if (address != NULL)
		*address = ntohl (*((u32_t *) ((char *) packet + sizeof (CcgGvcpPacket))));
	if (size != NULL)
		*size = ntohs (packet->header.size) - sizeof (u32_t);
}

static inline void *
ccg_gvcp_packet_get_write_memory_cmd_data (const CcgGvcpPacket *packet)
{
	return (char *) packet + sizeof (CcgGvcpPacket) + sizeof (u32_t);
}

static inline size_t
ccg_gvcp_packet_get_write_memory_ack_size (void)
{
	return sizeof (CcgGvcpPacket) + sizeof (u32_t);
}

static inline void
ccg_gvcp_packet_get_read_register_cmd_infos (const CcgGvcpPacket *packet, u32_t *address)
{
	if (packet == NULL)
		return;
	if (address != NULL)
		*address = ntohl (*((u32_t *) ((char *) packet + sizeof (CcgGvcpPacket))));
}

static inline u32_t
ccg_gvcp_packet_get_read_register_ack_value (const CcgGvcpPacket *packet)
{
	if (packet == NULL)
		return 0;
	return ntohl (*((u32_t *) ((char *) packet + sizeof (CcgGvcpPacket))));
}

static inline void
ccg_gvcp_packet_get_write_register_cmd_infos (const CcgGvcpPacket *packet, u32_t *address, u32_t *value)
{
	if (packet == NULL)
		return;
	if (address != NULL)
		*address = ntohl (*((u32_t *) ((char *) packet + sizeof (CcgGvcpPacket))));
	if (value != NULL)
		*value = ntohl (*((u32_t *) ((char *) packet + sizeof (CcgGvcpPacket) + sizeof (u32_t))));
}

#endif
  • ccgvcp.c:
#include <ccggvcp.h>
#include <string.h>
#include <utils/lwiplib.h>

struct pbuf *
ccg_gvcp_packet_new_read_memory_ack (u32_t address, u32_t size, u32_t packet_count)
{
	CcgGvcpPacket *packet;
	struct pbuf *buffer;
	u32_t n_address = htonl (address);

	buffer = pbuf_alloc(PBUF_TRANSPORT, sizeof (CcgGvcpHeader) + sizeof (u32_t) + size, PBUF_RAM);
	packet = buffer->payload;

	packet->header.packet_type = htons (CCG_GVCP_PACKET_TYPE_ACK);
	packet->header.command = htons (CCG_GVCP_COMMAND_READ_MEMORY_ACK);
	packet->header.size = htons (sizeof (u32_t) + size);
	packet->header.count = htons (packet_count);

	memcpy (&packet->data, &n_address, sizeof (u32_t));

	return buffer;
}

struct pbuf *
ccg_gvcp_packet_new_write_memory_ack (u32_t address, u32_t packet_count)
{
	CcgGvcpPacket *packet;
	struct pbuf *buffer;
	u32_t n_address = htonl (address);

	buffer = pbuf_alloc(PBUF_TRANSPORT, sizeof (CcgGvcpHeader) + sizeof (u32_t), PBUF_RAM);
	packet = buffer->payload;

	packet->header.packet_type = htons (CCG_GVCP_PACKET_TYPE_ACK);
	packet->header.command = htons (CCG_GVCP_COMMAND_WRITE_MEMORY_ACK);
	packet->header.size = htons (sizeof (u32_t));
	packet->header.count = htons (packet_count);

	memcpy (&packet->data, &n_address, sizeof (u32_t));

	return buffer;
}

struct pbuf *
ccg_gvcp_packet_new_read_register_ack (u32_t value, u32_t packet_count)
{
	CcgGvcpPacket *packet;
	struct pbuf *buffer;
	u32_t n_value = htonl (value);

	buffer = pbuf_alloc(PBUF_TRANSPORT, sizeof (CcgGvcpHeader) + sizeof (u32_t), PBUF_RAM);
	packet = buffer->payload;

	packet->header.packet_type = htons (CCG_GVCP_PACKET_TYPE_ACK);
	packet->header.command = htons (CCG_GVCP_COMMAND_READ_REGISTER_ACK);
	packet->header.size = htons (sizeof (u32_t));
	packet->header.count = htons (packet_count);

	memcpy (&packet->data, &n_value, sizeof (u32_t));

	return buffer;
}

struct pbuf *
ccg_gvcp_packet_new_write_register_ack 	(u32_t address, u32_t packet_count)
{
	CcgGvcpPacket *packet;
	u32_t n_address = htonl (address);
	struct pbuf *buffer;

	buffer = pbuf_alloc(PBUF_TRANSPORT, sizeof (CcgGvcpHeader) + sizeof (u32_t), PBUF_RAM);
	packet = buffer->payload;

	packet->header.packet_type = htons (CCG_GVCP_PACKET_TYPE_ACK);
	packet->header.command = htons (CCG_GVCP_COMMAND_WRITE_REGISTER_ACK);
	packet->header.size = htons (sizeof (u32_t));
	packet->header.count = htons (packet_count);

	memcpy (&packet->data, &n_address, sizeof (u32_t));

	return buffer;
}

struct pbuf *
ccg_gvcp_packet_new_discovery_cmd (void)
{
	CcgGvcpPacket *packet;
	struct pbuf *buffer;

	buffer = pbuf_alloc(PBUF_TRANSPORT, sizeof (CcgGvcpHeader), PBUF_RAM);
	packet = buffer->payload;

	packet->header.packet_type = htons (CCG_GVCP_PACKET_TYPE_CMD);
	packet->header.command = htons (CCG_GVCP_COMMAND_DISCOVERY_CMD);
	packet->header.size = htons (0x0000);
	packet->header.count = htons (0xffff);

	return buffer;
}

struct pbuf *
ccg_gvcp_packet_new_discovery_ack (void)
{
	CcgGvcpPacket *packet;
	struct pbuf *buffer;

	buffer = pbuf_alloc(PBUF_TRANSPORT, sizeof (CcgGvcpHeader) + CCG_GVBS_DISCOVERY_DATA_SIZE, PBUF_RAM);
	packet = buffer->payload;

	packet->header.packet_type = htons (CCG_GVCP_PACKET_TYPE_ACK);
	packet->header.command = htons (CCG_GVCP_COMMAND_DISCOVERY_ACK);
	packet->header.size = htons (CCG_GVBS_DISCOVERY_DATA_SIZE);
	packet->header.count = htons (0xffff);

	return buffer;
}

The USB packets have a different format than for GigEVision. In aravis, you will find the necessary informations in arvuvcp.c, arvuvcpprvate.h, arvuvsp.c and arvuvspprivate.h.

Cheers,

Emmanuel.

1 Like

Hello again Emmanuel,

Just writing to tell you some good news since I have successfully ported the USB3 Vision protocol into the Xilinx UltraScale+ and connected with Aravis without any modification from the recent unstable version, bringing a fair video streaming around 330MB/s, which is pretty good :+1:

Thank you for your support!
João

Nice. Is there any chance you can share your code ?

Emmanuel.

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