~ruther/stm32h747i-disco-usb-image-viewer

c8fe27c955b05f63fefa02f9c19dbbfb47de11ee — Rutherther 5 months ago 8fd37ea
feat: implement usb devicd cdc descriptors, init, setup
A include/clocks.h => include/clocks.h +39 -0
@@ 0,0 1,39 @@
#include <stdint.h>

#ifndef CLOCKS_H
#define CLOCKS_H

typedef enum {
  CLOCK_PLL1 = 0,
  CLOCK_PLL2 = 1,
  CLOCK_PLL3 = 2,
} clock_pll_t;

typedef enum {
  CLOCK_SOURCE_HSI = 0,
  CLOCK_SOURCE_HSE = 1,
  CLOCK_SOURCE_CSI = 2,
  CLOCK_SOURCE_PLL_1_P_CK = 3,
} sysclock_source_t;

typedef enum {
  PLL_SOURCE_HSI = 0,
  PLL_SOURCE_CSI = 1,
  PLL_SOURCE_HSE = 2,
  PLL_SOURCE_NONE = 3,
} pll_source_t;


void clocks_pll_configure(clock_pll_t pll, uint8_t divm, pll_source_t source,
                          uint16_t divn, uint8_t divp, uint8_t divq,
                          uint8_t divr);
void clocks_pll_enable(clock_pll_t pll);
void clocks_pll_disable(clock_pll_t pll);
void clocks_pll_wait_ready(clock_pll_t pll, uint16_t timeout_us);

void clocks_system_clock_source(sysclock_source_t source, uint8_t d1cpre,
                                uint8_t d1ppre, uint8_t hpre,
                                uint16_t timeout_us);
uint8_t clocks_system_clock_wait_ready(uint16_t timeout_us);

#endif // CLOCKS_H

A include/delay.h => include/delay.h +28 -0
@@ 0,0 1,28 @@
#ifndef DELAY_H
#define DELAY_H

// TODO: define system clock well
/* #define SYSTEM_CLOCK    ((uint32_t)360000000UL) */
#define SYSTEM_CLOCK    ((uint32_t)64000000UL)

#define SYSTICK_CALIB 0x3E8
#define SYSTICK_LOAD (SYSTEM_CLOCK/1000000UL)
#define SYSTICK_DELAY_CALIB (SYSTICK_LOAD >> 1)

#define DELAY_US(us) \
    do { \
         uint32_t start = SysTick->VAL; \
         uint32_t ticks = (us * SYSTICK_LOAD)-SYSTICK_DELAY_CALIB;  \
         while((start - SysTick->VAL) < ticks); \
    } while (0)

#define DELAY_MS(ms) \
    do { \
        for (uint32_t i = 0; i < ms; ++i) { \
            DELAY_US(1000); \
        } \
    } while (0)

void systick_configure();

#endif // DELAY_H

M include/usb.h => include/usb.h +6 -15
@@ 225,18 225,13 @@ typedef struct {

// NOTE: End of structs from usb specification here

typedef struct __attribute__((packed)) {
  usb_device_descriptor_t device_descriptor;
  usb_device_qualifier_t device_qualifier;
  // NOTE: keep these three fields in this order!
  usb_configuration_descriptor_t configuration_descriptor;
  usb_interface_descriptor_t interface_descriptor;
  usb_endpoint_descriptor_t endpoint_descriptors[8];
  //
  usb_string_descriptor_zero_t string_descriptor_zero;
  usb_unicode_string_descriptor_t *string_descriptors;
} usb_class_t;

void usb_generic_fill_fifo_words(USB_OTG_INEndpointTypeDef *endpoint,
                      uint8_t *data,
                      uint16_t size,
                      volatile uint32_t *fifo_tx_target,
                      uint32_t *sub_word_data,
                      uint8_t *sub_word_bytes);
void usb_generic_send(USB_OTG_INEndpointTypeDef *endpoint,
                      uint8_t *data,
                      uint16_t size,


@@ 252,10 247,6 @@ void usb_send_device_descriptor(USB_OTG_INEndpointTypeDef *endpoint,
                                volatile uint32_t *fifo_tx_target);
void usb_send_device_qualifier_descriptor(USB_OTG_INEndpointTypeDef* endpoint,usb_device_qualifier_t *descriptor,
                                          volatile uint32_t *fifo_tx_target);
/* void usb_send_ack(uint32_t* fifo_tx_target); */
void usb_send_configuration_descriptor(USB_OTG_INEndpointTypeDef* endpoint,
                                       usb_class_t *class,
                                       volatile uint32_t *fifo_tx_target);
void usb_send_interface_descriptor(USB_OTG_INEndpointTypeDef *endpoint,
                                   usb_interface_descriptor_t *descriptor,
                                   volatile uint32_t *fifo_tx_target);

M include/usb_device.h => include/usb_device.h +64 -9
@@ 23,7 23,56 @@ typedef struct {
  volatile uint32_t data[128];
} usb_fifo_t;

struct usb_class_header_t;
struct usb_device_t;
typedef struct usb_device_t usb_device_t;

typedef struct {
  struct usb_class_header_t *(*init)(usb_device_t *device,
                uint16_t id_vendor, uint16_t id_product,
                char *vendor_name, char *product_name,
                uint16_t serial_number, char* serial_name);
  void (*send_configuration)(usb_device_t* device, usb_setup_command_t* cmd);
  void (*setup_endpoints)(usb_device_t* device, uint8_t configuration);
  void (*reset_endpoints)(usb_device_t* device);

  void (*reset_callback)(usb_device_t* device);
  void (*setup_packet_callback)(usb_device_t* device, usb_setup_command_t* cmd);
  void (*enumeration_done_callback)(usb_device_t* device);
  void (*txfifo_empty_callback)(usb_device_t* device, uint8_t endpoint);
  void (*rxfifo_empty_callback)(usb_device_t* device, uint8_t endpoint);
  void (*transmit_done_callback)(usb_device_t* device, uint8_t endpoint);
  void (*nak_callback)(usb_device_t* device, uint8_t endpoint);
  void (*nyet_callback)(usb_device_t* device, uint8_t endpoint);
} usb_class_vtable_t;

typedef struct {
  usb_interface_descriptor_t interface_descriptor;
  uint8_t endpoint_descriptors_count;
  usb_endpoint_descriptor_t *endpoint_descriptors;
} usb_interface_t;

struct usb_class_header_t {
  usb_device_descriptor_t device_descriptor;
  usb_device_qualifier_t device_qualifier;
  usb_configuration_descriptor_t configuration_descriptor;
  uint8_t interfaces_count;
  usb_interface_t *interfaces;
  usb_string_descriptor_zero_t string_descriptor_zero;
  usb_unicode_string_descriptor_t *string_descriptors;
};
typedef struct usb_class_header_t usb_class_header_t;

typedef enum {
  SETUP_STAGE_NONE,
  SETUP_STAGE_RCVD_SETUP_PACKET,
  SETUP_STAGE_AWAITING_RESPONSE,
  SETUP_STAGE_SENDING_RESPONSE,
  SETUP_STAGE_SENDING_ACK,
  SETUP_STAGE_AWAITING_ACK,
} usb_setup_command_stage_t;

struct usb_device_t {
  USB_OTG_GlobalTypeDef *core;
  USB_OTG_DeviceTypeDef *device;



@@ 32,25 81,31 @@ typedef struct {

  usb_fifo_t *fifos;

  uint8_t endpoint_count;

  usb_class_t class;
  usb_class_vtable_t vt;
  usb_class_header_t* class;

  usb_device_state_t state;

  // TODO: is this count field required?
  uint8_t received_setup_commands_count;
  uint8_t received_setup_commands_index;
  usb_setup_command_stage_t setup_stage;
  uint8_t detected_setup_errors;
  usb_setup_command_t received_setup_commands[3];
} usb_device_t;
};

// has configuration etc

#define USB_DEVICE_SIZE sizeof(usb_device_t)

extern usb_device_t* usb1_device;

void* usb_device_init(void* peripheral_address, usb_class_t* class, void* buffer);
typedef enum {
  USB_OTG_HS1 = 0,
  USB_OTG_FS2 = 1,
} usb_device_slot_t;
extern usb_device_t usb_devices[2];

void *usb_device_init(usb_device_slot_t slot, usb_class_vtable_t *class,
                      uint16_t id_vendor, uint16_t id_product,
                      char *vendor_name, char *product_name,
                      uint16_t serial_number, char* serial_name);

void usb_device_setup(void* device);
void usb_device_wait_for_handshake(void* device);

A include/usb_device_cdc.h => include/usb_device_cdc.h +116 -0
@@ 0,0 1,116 @@
#include "usb_device.h"

#ifndef USB_DEVICE_CDC_H
#define USB_DEVICE_CDC_H

#define USB_CLASS_CDC_CODE 0x02
#define USB_CLASS_DATA_CODE 0x0A
#define USB_SUBCLASS_CDC_ACM_CODE 0x02

extern usb_class_vtable_t USB_CLASS_CDC_ACM;

typedef enum {
  SEND_ENCAPSULATED_COMMAND = 0,
  GET_ENCAPSULATED_RESPONSE = 1,
  /* SET_COMM_FEATURE = 2, */
  /* GET_COMM_FEATURE = 3, */
  /* CLEAR_COMM_FEATURE = 4, */
  /* SET_AUX_LINE_STATE = 0x10, */
  /* SET_HOOK_STATE = 0x11, */
  /* PULSE_SETUP = 0x12, */
  /* SEND_PULSE = 0x13, */
  /* SET_PULSE_TIME = 0x14, */
  /* RING_AUX_JACK = 0x15, */
  /* SET_LINE_CODING = 0x20, */
  /* GET_LINE_CODING = 0x21, */
} usb_cdc_request_code_type_t;

typedef enum {
  CS_INTERFACE = 0x24,
  CS_ENDPOINT = 0x25
} usb_cdc_functional_descriptor_type_t;

typedef enum {
  HEADER_FUNCTIONAL_DESCRIPTOR_FUNCTIONAL_DESCRIPTOR = 0x00,
  CALL_MANAGEMENT_FUNCTIONAL_FUNCTIONAL_DESCRIPTOR = 0x01,
  ABSTRACT_CONTROL_MANAGEMENT_FUNCTIONAL_DESCRIPTOR = 0x02,
  DIRECT_LINE_MANAGEMENT_FUNCTIONAL_DESCRIPTOR = 0x03,
  TELEPHONE_RINGER_FUNCTIONAL_DESCRIPTOR = 0x04,
  TELEPHONE_CALL_AND_LINE_STATE_REPORTING_CAPABILITIES_FUNCTIONAL_DESCRIPTOR = 0x05,
  UNION_FUNCTIONAL_DESCRIPTOR = 0x06,
  CONTRY_SELECTION_FUNCTIONAL_DESCRIPTOR = 0x07,
  TELEPHONE_OPERATIONAL_MODES_FUNCTIONAL_DESCRIPTOR = 0x08,
  USB_TERMINAL_FUNCTIONAL_DESCRIPTOR = 0x09,
  NETWORK_CHANNEL_FUNCTIONAL_DESCRIPTOR = 0x0A,
  PROTOCOL_UNIT_FUNCTIONAL_DESCRIPTOR = 0x0B,
  EXTENSION_UNIT_FUNCTIONAL_DESCRIPTOR = 0x0C,
  MULTI_CHANNEL_MANAGEMENT_FUNCTIONAL_DESCRIPTOR = 0x0D,
  CAPI_CONTROL_MANAGEMENT_FUNCTIONAL_DESCRIPTOR = 0x0E,
  ETHERNET_NETWORKING_FUNCTIONAL_DESCRIPTOR = 0x0F,
  ATM_NETWORKING_FUNCTIONAL_DESCRIPTOR = 0x10,
  WIRELESS_HANDSET_CONTROL_MODEL_FUNCTIONAL_DESCRIPTOR = 0x11,
} usb_cdc_functional_descriptor_subtype_t;

#pragma GCC diagnostic error "-Wpadded"
typedef struct __attribute__((packed)) {
  uint8_t bFunctionLength;
  usb_cdc_functional_descriptor_type_t bDescriptorType;
  usb_cdc_functional_descriptor_subtype_t bDescriptorSubType;
} usb_cdc_functional_descriptor_header_t;
_Static_assert(sizeof(usb_cdc_functional_descriptor_header_t) == 3, "Size check");

typedef struct __attribute__((packed)) {
  usb_cdc_functional_descriptor_header_t header;
  uint16_t bcdCDC;
} usb_cdc_header_functional_decriptor_t;
_Static_assert(sizeof(usb_cdc_header_functional_decriptor_t) == 5, "Size check");

typedef struct {
  usb_cdc_functional_descriptor_header_t header;
  uint8_t bmCapabilities;
} usb_cdc_acm_functional_decriptor_t;
_Static_assert(sizeof(usb_cdc_acm_functional_decriptor_t) == 4, "Size check");

typedef struct {
  usb_cdc_functional_descriptor_header_t header;
  uint8_t bControlInterface;
  uint8_t bSubordinateInterface0;
} usb_cdc_union_functional_decriptor_t;
_Static_assert(sizeof(usb_cdc_union_functional_decriptor_t) == 5, "Size check");

typedef struct {
  usb_cdc_functional_descriptor_header_t header;
  uint8_t bmCapabilities;
  uint8_t bDataInterface;
} usb_cdc_call_management_functional_decriptor_t;
_Static_assert(sizeof(usb_cdc_call_management_functional_decriptor_t) == 5, "Size check");

#pragma GCC diagnostic ignored "-Wpadded"
typedef struct {
  usb_class_header_t header;
  uint8_t functional_descriptors_count;
  usb_cdc_functional_descriptor_header_t** functional_descriptors;
} usb_device_cdc_t;

usb_class_header_t* usb_device_cdc_init(usb_device_t *device,
                uint16_t id_vendor, uint16_t id_product,
                char *vendor_name, char *product_name,
                uint16_t serial_number, char* serial_name);

void usb_device_cdc_send_configuration(usb_device_t* device, usb_setup_command_t* cmd);

void usb_device_cdc_setup_packet_callback(usb_device_t* device, usb_setup_command_t* cmd);
void usb_device_cdc_enumeration_done_callback(usb_device_t* device);
void usb_device_cdc_txfifo_empty_callback(usb_device_t* device, uint8_t endpoint);
void usb_device_cdc_rxfifo_empty_callback(usb_device_t* device, uint8_t endpoint);
void usb_device_cdc_transmit_done_callback(usb_device_t* device, uint8_t endpoint);
void usb_device_cdc_nak_callback(usb_device_t* device, uint8_t endpoint);
void usb_device_cdc_nyet_callback(usb_device_t* device, uint8_t endpoint);

void usb_device_cdc_acm_configure(usb_device_t *device // , TODO
                              );




#endif // USB_DEVICE_CDC_H

A src/clocks.c => src/clocks.c +38 -0
@@ 0,0 1,38 @@
#include "clocks.h"
#include "registers.h"
#include <stm32h747xx.h>

void clocks_pll_configure(clock_pll_t pll, uint8_t divm, pll_source_t source,
                          uint16_t divn, uint8_t divp, uint8_t divq,
                          uint8_t divr) {
  clocks_pll_disable(pll);

  reg_write_bits_pos(&RCC->PLLCKSELR, divm, RCC_PLLCKSELR_DIVM1_Pos + ((pll << 1) << 3), 0x3F);
  reg_write_bits_pos(&RCC->PLLCKSELR, source, RCC_PLLCKSELR_PLLSRC_Pos, 0x3);

  reg_write_bits(&RCC->PLL1DIVR + (pll << 1),
                 (((divr - 1) & 0x7F) << RCC_PLL1DIVR_R1_Pos) |
                 (((divq - 1) & 0x7F) << RCC_PLL1DIVR_Q1_Pos) |
                 (((divp - 1) & 0x7F) << RCC_PLL1DIVR_P1_Pos) |
                 (((divn - 1) & 0x1FF) << RCC_PLL1DIVR_N1_Pos),
                 0x7F7FFFFF);
}
void clocks_pll_enable(clock_pll_t pll) {
  reg_set_bits(&RCC->CR, RCC_CR_PLL1ON << (uint8_t)(pll << 1));
}
void clocks_pll_disable(clock_pll_t pll) {
  reg_clear_bits(&RCC->CR, RCC_CR_PLL1ON << ((uint8_t)(pll << 1)));
}
void clocks_pll_wait_ready(clock_pll_t pll, uint16_t timeout_us) {
  while (reg_read_bits_pos(&RCC->CR, RCC_CR_PLL1ON_Pos + (uint8_t)(pll << 1) + 1, 1) == 0);
}

void clocks_system_clock_source(sysclock_source_t source, uint8_t d1cpre, uint8_t d1ppre, uint8_t hpre, uint16_t timeout_us) {
  reg_write_bits(&RCC->D1CFGR,
                 ((d1cpre & 0xF) << RCC_D1CFGR_D1CPRE_Pos) |
                 ((d1ppre & 0x7) << RCC_D1CFGR_D1PPRE_Pos) |
                 ((hpre & 0xF) << RCC_D1CFGR_HPRE_Pos),
                 RCC_D1CFGR_D1CPRE | RCC_D1CFGR_D1PPRE | RCC_D1CFGR_HPRE);
  reg_write_bits(&RCC->CFGR, source, RCC_CFGR_SW);
  while (reg_read_bits_pos(&RCC->CFGR, RCC_CFGR_SWS_Pos, 0x7) != source);
}

A src/delay.c => src/delay.c +7 -0
@@ 0,0 1,7 @@
#include "delay.h"
#include <stm32h747xx.h>

void systick_configure() {
  SysTick->LOAD = SYSTICK_LOAD;
  SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk;
}

M src/main.c => src/main.c +34 -8
@@ 2,7 2,10 @@
#include <stm32h747xx.h>
#include <core_cm7.h>
#include <stdlib.h>
#include "delay.h"
#include "usb_device.h"
#include "usb_device_cdc.h"
#include "clocks.h"
#include "exti.h"
#include "registers.h"
#include "pin.h"


@@ 55,24 58,45 @@ void exti15_10_handler(void)

void main()
{
  // The hsi is by default on 64 MHz.
  // TODO: plls? configure dividers (note the need of refclk being between 1 and
  // 16 MHz, the hsi can stay at 64 MHz, and prescaler DIVM can be given to account for that)
  // enable the pll1, wait for it to be ready, and
  // switch the sys clock to pll1.
  // Is this necessary for something though?
  systick_configure();

  // Clocks section
  // Enable hsi48 for usb
  RCC->CR |= RCC_CR_HSI48ON;
  while ((RCC->CR & RCC_CR_HSI48RDY) == 0);

  // TODO: pll, system clock switch... too complicated it seems.
  //  Getting hard faults, apparently because of too low voltage
  // Has to be enabled before vco can be changed
  /* PWR->CR3 |= PWR_CR3_LDOEN; */
  /* PWR->CR3 &= ~PWR_CR3_LDOEN; */
  /* reg_write_bits_pos(&PWR->D3CR, 2, PWR_D3CR_VOS_Pos, 3); */

  /* /\* SYSCFG->PWRCR |= SYSCFG_PWRCR_ODEN; *\/ */

  /* while((PWR->CSR1 & PWR_CSR1_ACTVOSRDY) == 0); */
  /* /\* while((PWR->D3CR & PWR_D3CR_VOSRDY) == 0); *\/ */

  /* reg_write_bits(&FLASH->ACR, FLASH_ACR_LATENCY_3WS, FLASH_ACR_LATENCY_Msk); */

  /* // HSI is 64 MHz, not divided */
  /* // Diving by 32 to put to PLL -> 2 MHz */
  /* // DIVN = 360 -> F_VCO = 720 MHz */
  /* // DIVP = 2 -> pll1_p is 360 MHz */
  /* // DIVQ = 8 -> pll1_q is 90 MHz */
  /* // DIVR = 8 -> pll1_r is 90 MHz */
  /* clocks_pll_configure(CLOCK_PLL1, 32, PLL_SOURCE_HSI, */
  /*                      360, 2, 8, 8); */
  /* clocks_pll_enable(CLOCK_PLL1); */
  /* clocks_pll_wait_ready(CLOCK_PLL1, 300); */
  /* clocks_system_clock_source(CLOCK_SOURCE_PLL_1_P_CK, */
  /*                            1, 1, 2, 300); */

  // Clock gating
  RCC->APB4ENR |= RCC_APB4ENR_SYSCFGEN;
  volatile uint32_t dummy;
  dummy = RCC->APB4ENR;
  dummy = RCC->APB4ENR;

  RCC->AHB4ENR |= RCC_AHB4ENR_GPIOAEN | RCC_AHB4ENR_GPIOBEN | RCC_AHB4ENR_GPIOCEN | RCC_AHB4ENR_GPIOHEN | RCC_AHB4ENR_GPIOIEN | RCC_AHB4ENR_GPIOJEN;
  dummy = RCC->AHB4ENR;
  dummy = RCC->AHB4ENR;


@@ 131,7 155,9 @@ void main()
  // TODO: ?
  pin_into_input_highspeed(otg_hs_overcurrent);

  void* usb_otg = usb_device_init(USB1_OTG_HS, NULL, NULL);
  void *usb_otg = usb_device_init(USB_OTG_HS1, &USB_CLASS_CDC_ACM,
                                  0x1234, 0x1111, "Frantisek Bohacek",
                                  "Display", 1, NULL);
  usb_device_setup(usb_otg);

  usb_device_wait_for_handshake(usb_otg);

M src/usb.c => src/usb.c +88 -23
@@ 8,6 8,70 @@ typedef union {
  uint8_t bytes[4];
} usb_data_t;

uint32_t get_mask(uint8_t byte_count) {
  switch (byte_count) {
  case 0:
    return 0;
  case 1:
    return 0xFF;
  case 2:
    return 0xFFFF;
  case 3:
    return 0xFFFFFF;
  default:
    return ~0;
  }
}

void usb_generic_fill_fifo_words(USB_OTG_INEndpointTypeDef *endpoint,
                      uint8_t *data,
                      uint16_t size,
                      volatile uint32_t *fifo_tx_target,
                      uint32_t *sub_word_data,
                      uint8_t *sub_word_bytes) {
  usb_data_t tx_data;
  uint8_t total_bytes = size + *sub_word_bytes;
  if (total_bytes < 4) {
    uint8_t bytes = *sub_word_bytes;
    *sub_word_bytes = total_bytes;

    uint32_t mask = get_mask(bytes);
    tx_data.word = *sub_word_data & mask;

    for (int i = 0; i < size; i++) {
      tx_data.bytes[i + bytes] = *(data++);
    }

    *sub_word_bytes = tx_data.word;
    return;
  }

  if (*sub_word_bytes > 0) {
    // first send these bytes,
    uint8_t skip_bytes = *sub_word_bytes;
    tx_data.word = *sub_word_data;
    for (int i = 0; i < 4 - *sub_word_bytes; i++) {
      tx_data.bytes[skip_bytes + i] = *(data++);
      size--;
    }

    *fifo_tx_target = tx_data.word;
  }

  uint8_t subWordBytes = size % 4;
  uint8_t wordCount = size / 4;

  for (uint8_t i = 0; i < wordCount; i++) {
    tx_data.word = *((uint32_t*)data);
    *fifo_tx_target = tx_data.word;
    data += 4;
  }

  // NOTE: hm. This is not generally safe, but it's tempting...
  *sub_word_data = *((uint32_t*)data);
  *sub_word_bytes = subWordBytes;
}

void usb_generic_send(USB_OTG_INEndpointTypeDef *endpoint,
                      uint8_t *data,
                      uint16_t size,


@@ 18,7 82,7 @@ void usb_generic_send(USB_OTG_INEndpointTypeDef *endpoint,
  }

  // TODO: generic max packet size
  uint16_t packet_count = size + 63 / 64;
  uint16_t packet_count = (size + 63) / 64;

  endpoint->DIEPTSIZ = (packet_count << USB_OTG_DIEPTSIZ_PKTCNT_Pos) | (size << USB_OTG_DIEPTSIZ_XFRSIZ_Pos);



@@ 70,29 134,30 @@ void usb_send_descriptor(USB_OTG_INEndpointTypeDef *endpoint,
  usb_generic_send(endpoint, (uint8_t*)descriptor, descriptor->bLength, fifo_tx_target);
}

void usb_send_configuration_descriptor(USB_OTG_INEndpointTypeDef* endpoint,
                                       usb_class_t *class,
                                       volatile uint32_t *fifo_tx_target) {
  class->configuration_descriptor.header.bDescriptorType = DESCRIPTOR_CONFIGURATION;
  class->configuration_descriptor.header.bLength = sizeof(usb_configuration_descriptor_t);
  class->interface_descriptor.header.bDescriptorType = DESCRIPTOR_INTERFACE;
  class->interface_descriptor.header.bLength = sizeof(usb_interface_descriptor_t);
  uint16_t total_length =
    sizeof(usb_configuration_descriptor_t) +
    sizeof(usb_interface_descriptor_t) +
    class->interface_descriptor.bNumEndpoints * sizeof(usb_endpoint_descriptor_t);

  for (int i = 0; i < class->interface_descriptor.bNumEndpoints; i++) {
    class->endpoint_descriptors[i].header.bDescriptorType = DESCRIPTOR_ENDPOINT;
    class->endpoint_descriptors[i].header.bLength = sizeof(usb_endpoint_descriptor_t);
  }
/* void usb_send_configuration_descriptor(USB_OTG_INEndpointTypeDef* endpoint, */
/*                                        usb_class_t *class, */
/*                                        volatile uint32_t *fifo_tx_target) { */
/*   class->configuration_descriptor.header.bDescriptorType = DESCRIPTOR_CONFIGURATION; */
/*   class->configuration_descriptor.header.bLength = sizeof(usb_configuration_descriptor_t); */
/*   class->interface_descriptor.header.bDescriptorType = DESCRIPTOR_INTERFACE; */
/*   class->interface_descriptor.header.bLength = sizeof(usb_interface_descriptor_t); */
/*   uint16_t total_length = */
/*     sizeof(usb_configuration_descriptor_t) + */
/*     sizeof(usb_interface_descriptor_t) + */
/*     class->interface_descriptor.bNumEndpoints * sizeof(usb_endpoint_descriptor_t); */

/*   for (int i = 0; i < class->interface_descriptor.bNumEndpoints; i++) { */
/*     class->endpoint_descriptors[i].header.bDescriptorType = DESCRIPTOR_ENDPOINT; */
/*     class->endpoint_descriptors[i].header.bLength = sizeof(usb_endpoint_descriptor_t); */
/*   } */

/*   // NOTE: since the memory layout is: configuration, interface, endpoints, */
/*   // we can send directly like this. */
/*   usb_generic_send(endpoint, */
/*                    (uint8_t*)&class->configuration_descriptor, */
/*                    total_length, fifo_tx_target); */
/* } */

  // NOTE: since the memory layout is: configuration, interface, endpoints,
  // we can send directly like this.
  usb_generic_send(endpoint,
                   (uint8_t*)&class->configuration_descriptor,
                   total_length, fifo_tx_target);
}
void usb_send_device_descriptor(USB_OTG_INEndpointTypeDef *endpoint,
                                usb_device_descriptor_t *descriptor,
                                volatile uint32_t *fifo_tx_target) {

M src/usb_device.c => src/usb_device.c +91 -39
@@ 8,28 8,31 @@

/* USB_OTG_GlobalTypeDef */

usb_device_t* usb1_device;
usb_device_t usb_devices[2];
void* usb_periph_addresses[] =
{ (void*)USB1_OTG_HS, (void*)USB2_OTG_FS };

void* usb_device_init(void* peripheral_address, usb_class_t* class, void* buffer) {
  if (buffer == NULL) {
    buffer = (void*)malloc(sizeof(usb_device_t));
  }

  usb_device_t* device = (usb_device_t*)buffer;
void* usb_device_init(usb_device_slot_t slot, usb_class_vtable_t *class,
                      uint16_t id_vendor, uint16_t id_product,
                      char *vendor_name, char *product_name,
                      uint16_t serial_number, char* serial_name) {
  usb_device_t* device = &usb_devices[slot];
  void* peripheral_address = usb_periph_addresses[slot];

  device->state = INIT;
  device->detected_setup_errors = 0;
  device->core = peripheral_address + USB_OTG_GLOBAL_BASE;
  device->device = peripheral_address + USB_OTG_DEVICE_BASE;
  device->out = peripheral_address + USB_OTG_OUT_ENDPOINT_BASE;
  device->in = peripheral_address + USB_OTG_IN_ENDPOINT_BASE;
  device->fifos = (usb_fifo_t*)(((uint8_t*)device->core) + USB_OTG_FIFO_BASE);
  device->endpoint_count = 8;
  device->received_setup_commands_count = 0;
  device->received_setup_commands_index = 0;
  device->class = *class;
  device->vt = *class;

  // TODO: clarify how this should work...
  usb1_device = device;
  device->class = device->
    vt.init(device, id_vendor, id_product,
            vendor_name, product_name, serial_number, serial_name);

  return device;
}


@@ 143,6 146,10 @@ typedef struct {
} packet_info_t;

void usb_handle_setup(usb_device_t *device, usb_setup_command_t* cmd) {
  if (device->setup_stage != SETUP_STAGE_RCVD_SETUP_PACKET) {
    device->detected_setup_errors++;
  }

  switch (cmd->bRequest) {
  case USB_SETUP_GET_STATUS: {
    uint8_t size;


@@ 177,6 184,7 @@ void usb_handle_setup(usb_device_t *device, usb_setup_command_t* cmd) {

    usb_generic_send(device->in, packet, size,
                     &device->fifos[0].data[0]);
    device->setup_stage = SETUP_STAGE_SENDING_RESPONSE;
  }
    break;
  case USB_SETUP_GET_DESCRIPTOR: {


@@ 185,52 193,59 @@ void usb_handle_setup(usb_device_t *device, usb_setup_command_t* cmd) {

    switch (descriptor_type) {
    case DESCRIPTOR_DEVICE:
      usb_send_device_descriptor(device->in, &device->class.device_descriptor, &device->fifos[0].data[0]);
      usb_send_device_descriptor(device->in, &device->class->device_descriptor, &device->fifos[0].data[0]);
      break;
    case DESCRIPTOR_CONFIGURATION:
      usb_send_configuration_descriptor(device->in, &device->class.configuration_descriptor, &device->fifos[0].data[0]);
      device->vt.send_configuration(device, cmd);
      break;
    case DESCRIPTOR_STRING: {
      if (descriptor_index == 0) {
        usb_send_string_descriptor_zero(device->in, &device->class.string_descriptor_zero, &device->fifos[0].data[0]);
        usb_send_string_descriptor_zero(device->in, &device->class->string_descriptor_zero, &device->fifos[0].data[0]);
      } else {
        uint8_t index = descriptor_index - 1;
        // NOTE: the user could potentially read different memory part!!
        // This has to be fixed, the length has to be stored somewhere.
        usb_send_unicode_string_descriptor(device->in, &device->class.string_descriptors[index], &device->fifos[0].data[0]);
        usb_send_unicode_string_descriptor(device->in, &device->class->string_descriptors[index], &device->fifos[0].data[0]);
      }
    }
      break;
    case DESCRIPTOR_INTERFACE:
      usb_send_interface_descriptor(device->in, &device->class.interface_descriptor, &device->fifos[0].data[0]);
      usb_send_interface_descriptor(device->in, &device->class->interfaces[descriptor_index].interface_descriptor, &device->fifos[0].data[0]);
      device->setup_stage = SETUP_STAGE_SENDING_RESPONSE;
      break;
    case DESCRIPTOR_ENDPOINT:
      usb_send_endpoint_descriptor(device->in, &device->class.endpoint_descriptors[descriptor_index], &device->fifos[0].data[0]);
      // TODO: how to match the interface to the descriptor index?
      usb_send_endpoint_descriptor(device->in, &device->class->interfaces[0].endpoint_descriptors[descriptor_index], &device->fifos[0].data[0]);
      device->setup_stage = SETUP_STAGE_SENDING_RESPONSE;
      break;
    case DESCRIPTOR_DEVICE_QUALIFIER:
      usb_send_device_qualifier_descriptor(device->in, &device->class.device_qualifier, &device->fifos[0].data[0]);
      usb_send_device_qualifier_descriptor(device->in, &device->class->device_qualifier, &device->fifos[0].data[0]);
      device->setup_stage = SETUP_STAGE_SENDING_RESPONSE;
      break;
    case DESCRIPTOR_OTHER_SPEED_CONFIGURATION:
    case DESCRIPTOR_INTERFACE_POWER:
      reg_set_bits(&device->out[0].DOEPCTL, USB_OTG_DOEPCTL_STALL);
      device->setup_stage = SETUP_STAGE_NONE;
      break;
    }
    // TODO
  }
    break;
  case USB_SETUP_GET_CONFIGURATION: {
    uint8_t value = device->class.configuration_descriptor.bConfigurationValue;
    uint8_t value = device->class->configuration_descriptor.bConfigurationValue;
    if (device->state != ENUMERATED) {
      value = 0;
    }
    usb_generic_send(device->in, &value, sizeof(value), &device->fifos[0].data[0]);
    device->setup_stage = SETUP_STAGE_SENDING_RESPONSE;
  }
    break;
  case USB_SETUP_GET_INTERFACE: {
    usb_generic_send(device->in,
                     &device->class.interface_descriptor.bAlternateSetting,
                     &device->class->interfaces[cmd->wIndex].interface_descriptor.bAlternateSetting,
                     sizeof(uint8_t),
                     &device->fifos[0].data[0]);
    device->setup_stage = SETUP_STAGE_SENDING_RESPONSE;
  }
    break;
  case USB_SETUP_SET_ADDRESS:


@@ 239,6 254,7 @@ void usb_handle_setup(usb_device_t *device, usb_setup_command_t* cmd) {
                       USB_OTG_DCFG_DAD_Pos,
                       0x7F);
    usb_generic_send(device->in, NULL, 0, &device->fifos[0].data[0]);
    device->setup_stage = SETUP_STAGE_SENDING_ACK;
    device->state = SET_ADDRESS_RCVD;
    break;
  case USB_SETUP_SET_CONFIGURATION: {


@@ 247,9 263,10 @@ void usb_handle_setup(usb_device_t *device, usb_setup_command_t* cmd) {
      usb_generic_send(device->in, NULL, 0, &device->fifos[0].data[0]);

      // TODO disable endpoints
    } else if (cmd->wValue == device->class.configuration_descriptor.bConfigurationValue) {
    } else if (cmd->wValue == device->class->configuration_descriptor.bConfigurationValue) {
      device->state = ENUMERATED;
      usb_generic_send(device->in, NULL, 0, &device->fifos[0].data[0]);
      device->setup_stage = SETUP_STAGE_SENDING_ACK;

      // TODO setup endpoints
    } else {


@@ 272,6 289,7 @@ void usb_handle_setup(usb_device_t *device, usb_setup_command_t* cmd) {
    }

    usb_generic_send(device->in, NULL, 0, &device->fifos[0].data[0]);
    device->setup_stage = SETUP_STAGE_SENDING_ACK;
    break;
  case USB_SETUP_SET_FEATURE:
    switch (cmd->wValue) {


@@ 288,6 306,7 @@ void usb_handle_setup(usb_device_t *device, usb_setup_command_t* cmd) {
    }

    usb_generic_send(device->in, NULL, 0, &device->fifos[0].data[0]);
    device->setup_stage = SETUP_STAGE_SENDING_ACK;
    break;
  case USB_SETUP_SET_DESCRIPTOR:
  case USB_SETUP_SET_INTERFACE:


@@ 297,7 316,8 @@ void usb_handle_setup(usb_device_t *device, usb_setup_command_t* cmd) {
    break;
  case RESERVED1:
  case RESERVED2:
    device->state = UNKNOWN_CONTROL_COMMAND;
  default:
    device->vt.setup_packet_callback(device, cmd);
    break;
  }
}


@@ 315,10 335,11 @@ void usb_handle_rxflvl_control_int(usb_device_t *device,
    uint8_t setup_packets_count = 3 - reg_read_bits_pos(&device->out[0].DOEPTSIZ, USB_OTG_DOEPTSIZ_STUPCNT_Pos, 3);
    usb_generic_read(
        (uint8_t *)&device
            ->received_setup_commands[device->received_setup_commands_index++],
            ->received_setup_commands[device->received_setup_commands_count++],
        8,
        fifo);
    device->received_setup_commands_index %= 3;
    device->received_setup_commands_count %= 3;
    device->setup_stage = SETUP_STAGE_RCVD_SETUP_PACKET;

    if (setup_packets_count == 0) {
      reg_write_bits_pos(&device->out[0].DOEPTSIZ, 3, USB_OTG_DOEPTSIZ_STUPCNT_Pos, 3);


@@ 327,6 348,13 @@ void usb_handle_rxflvl_control_int(usb_device_t *device,
    dummy = *fifo; // the last that will trigger another interrupt
  } else if (packet_info->byte_count != 0) {
    usb_generic_read(data, packet_info->byte_count, fifo);

    if (device->setup_stage == SETUP_STAGE_AWAITING_ACK) {
      // This is an error, since there is data in status phase...
      // TODO: How to handle?
      device->detected_setup_errors++;
      device->setup_stage = SETUP_STAGE_NONE;
    }
  }
}



@@ 396,7 424,9 @@ void usb_handle_endpoint_in_int(usb_device_t* device) {
    // NOTE this should not be reached as thresholding is not used
    reg_clear_bits(&device->in[ep_id].DIEPINT, USB_OTG_DIEPINT_TXFIFOUDRN);
  } else if (interrupt_reg & USB_OTG_DIEPINT_TXFE) {
    // Now we can send more data. TODO notify application about this
    if (device->state == ENUMERATED) {
      device->vt.txfifo_empty_callback(device, ep_id);
    }
    reg_clear_bits(&device->in[ep_id].DIEPINT, USB_OTG_DIEPINT_TXFE);
  } else if (interrupt_reg & USB_OTG_DIEPINT_INEPNE) {
    // NAK effective. Okay, ack, go on.


@@ 418,7 448,18 @@ void usb_handle_endpoint_in_int(usb_device_t* device) {
    // Endpoint is disabled, per application's request. Okay.
    reg_clear_bits(&device->in[ep_id].DIEPINT, USB_OTG_DIEPINT_EPDISD);
  } else if (interrupt_reg & USB_OTG_DIEPINT_XFRC) {
    // Transfer is completed. TODO notify application?
    // Transfer is completed.
    if (device->state == ENUMERATED) {
      device->vt.transmit_done_callback(device, ep_id);
    }

    if (ep_id == 0) {
      if (device->setup_stage == SETUP_STAGE_SENDING_RESPONSE) {
        device->setup_stage = SETUP_STAGE_NONE;
      } else if (device->setup_stage == SETUP_STAGE_SENDING_ACK) {
        device->setup_stage = SETUP_STAGE_AWAITING_ACK;
      }
    }
    reg_clear_bits(&device->in[ep_id].DIEPINT, USB_OTG_DIEPINT_XFRC);
  }
}


@@ 445,11 486,11 @@ void usb_handle_endpoint_out_int(usb_device_t* device) {
    reg_clear_bits(&device->out[ep_id].DOEPINT, USB_OTG_DOEPINT_STPKTRX);
  } else if (interrupt_reg & USB_OTG_DOEPINT_NYET) {
    // We don't really care about this one for now
    // TODO: for future - trigger a callback to application
    device->vt.nyet_callback(device, ep_id);
    reg_clear_bits(&device->out[ep_id].DOEPINT, USB_OTG_DOEPINT_NYET);
  } else if (interrupt_reg & USB_OTG_DOEPINT_NAK) {
    // We don't really care about this one for now
    // TODO: for future - trigger a callback to application
    device->vt.nak_callback(device, ep_id);
    reg_clear_bits(&device->out[ep_id].DOEPINT, USB_OTG_DOEPINT_NAK);
  } else if (interrupt_reg & USB_OTG_DOEPINT_BERR) {
    // Uh? Babble much?


@@ 466,22 507,30 @@ void usb_handle_endpoint_out_int(usb_device_t* device) {
    device->state = CONTROL_ERROR;
    reg_clear_bits(&device->out[ep_id].DOEPINT, USB_OTG_DOEPINT_B2BSTUP);
  } else if (interrupt_reg & USB_OTG_DOEPINT_OTEPSPR) {
    // TODO: ack or stall the status phase? How?
    // This is valid only for DATA phases where the host sends us data.
    // Those are not supported yet! After they are, here is the place to
    // set STALL or send zero length packet.
    // TODO: ack or stall the status phase?
    reg_clear_bits(&device->out[ep_id].DOEPINT, USB_OTG_DOEPINT_OTEPSPR);
  } else if (interrupt_reg & USB_OTG_DOEPINT_OTEPDIS) {
    // NOTE: Can we handle this? a callback to application?
    reg_clear_bits(&device->out[ep_id].DOEPINT, USB_OTG_DOEPINT_OTEPDIS);
  } else if (interrupt_reg & USB_OTG_DOEPINT_STUP) {
    // handle all setup commands
    uint8_t setup_packets_count = 3 - reg_read_bits_pos(&device->out[ep_id].DOEPTSIZ, USB_OTG_DOEPTSIZ_STUPCNT_Pos, 3);
    usb_handle_setup(device, &device->received_setup_commands[0]);
    for (int i = 0; i < device->received_setup_commands_count; i++) {
    if (device->setup_stage != SETUP_STAGE_NONE) {
      // something went wrong. Let's continue, but this isn't looking good.
      device->detected_setup_errors++;
    }

    device->received_setup_commands_count = 0;
    // TODO: null the data? shouldn't be needed...
    uint8_t setup_packets_count = 3 - reg_read_bits_pos(&device->out[ep_id].DOEPTSIZ, USB_OTG_DOEPTSIZ_STUPCNT_Pos, 3);
    usb_handle_setup(device, &device->received_setup_commands[device->received_setup_commands_index]);

    device->received_setup_commands_index++;
    device->received_setup_commands_index %= 3;

    if (setup_packets_count == 0) {
      reg_write_bits_pos(&device->out[0].DOEPTSIZ, 3, USB_OTG_DOEPTSIZ_STUPCNT_Pos, 3);
    }

    // 3 packets to receive as setup
    reg_clear_bits(&device->out[ep_id].DOEPINT, USB_OTG_DOEPINT_STUP);
  } else if (interrupt_reg & USB_OTG_DOEPINT_AHBERR) {
    // NOTE This shoudln't be reached since DMA is not used


@@ 491,6 540,9 @@ void usb_handle_endpoint_out_int(usb_device_t* device) {
    // need handling?
    reg_clear_bits(&device->out[ep_id].DOEPINT, USB_OTG_DOEPINT_EPDISD);
  } else if (interrupt_reg & USB_OTG_DOEPINT_XFRC) {
    if (ep_id == 0 && device->setup_stage == SETUP_STAGE_AWAITING_ACK) {
      device->setup_stage = SETUP_STAGE_NONE;
    }
    // Transfer has been completed
    // TODO: handle data? - callback to device data handle?
    reg_clear_bits(&device->out[ep_id].DOEPINT, USB_OTG_DOEPINT_XFRC);


@@ 500,7 552,7 @@ void usb_handle_endpoint_out_int(usb_device_t* device) {
// NOTE: this is irq handler
void otg_hs_handler(void) {

  usb_device_t* device = usb1_device;
  usb_device_t* device = &usb_devices[USB_OTG_HS1];

  // Reset detected
  if (device->core->GINTSTS & USB_OTG_GINTSTS_USBRST) {


@@ 508,7 560,7 @@ void otg_hs_handler(void) {
    reg_set_bits(&device->device->DOEPMSK, USB_OTG_GINTMSK_IEPINT | USB_OTG_GINTMSK_OEPINT);

    // TODO: should this be done for ep 0 as well?
    for (int ep = 0; ep < device->endpoint_count; ep++) {
    for (int ep = 0; ep < 8; ep++) {
      USB_OTG_OUTEndpointTypeDef *out = device->out + ep;
      out->DOEPCTL |= USB_OTG_DOEPCTL_SNAK;
    }


@@ 521,7 573,7 @@ void otg_hs_handler(void) {
    // 512 bytes
    device->core->GRXFSIZ = 256 / 4;
    // 64 bytes, beginning of ram
    device->core->DIEPTXF[0] = (0) << USB_OTG_DIEPTXF_INEPTXSA_Pos | (64 / 4) << USB_OTG_DIEPTXF_INEPTXFD_Pos;
    device->core->DIEPTXF[0] = (0) << USB_OTG_TX0FSA_Pos | (64 / 4) << USB_OTG_TX0FD_Pos;

    // 3 packets to receive as setup
    reg_write_bits_pos(&device->out->DOEPTSIZ, 3, USB_OTG_DOEPTSIZ_STUPCNT_Pos, 3);

A src/usb_device_cdc.c => src/usb_device_cdc.c +338 -0
@@ 0,0 1,338 @@
#include "usb.h"
#include "usb_device.h"
#include "usb_device_cdc.h"
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

uint16_t usb_cdc_lang_descriptors[] = {
  USB_LANG_ENGLISH | (USB_SUBLANG_ENGLISH_US << 10)
};

usb_class_vtable_t USB_CLASS_CDC_ACM = {
  .init = usb_device_cdc_init,
  .send_configuration = usb_device_cdc_send_configuration,
  .setup_packet_callback = usb_device_cdc_setup_packet_callback,
  .enumeration_done_callback = usb_device_cdc_enumeration_done_callback,
  .txfifo_empty_callback = usb_device_cdc_txfifo_empty_callback,
  .rxfifo_empty_callback = usb_device_cdc_rxfifo_empty_callback,
  .transmit_done_callback = usb_device_cdc_transmit_done_callback,
  .nak_callback = usb_device_cdc_nak_callback,
  .nyet_callback = usb_device_cdc_nyet_callback,
};

usb_class_header_t *usb_device_cdc_init(usb_device_t *device, uint16_t id_vendor,
                          uint16_t id_product, char *vendor_name,
                          char *product_name, uint16_t serial_number,
                          char *serial_name) {
  usb_class_header_t* class = (usb_class_header_t*)calloc(1, sizeof(usb_device_cdc_t));

  class->string_descriptor_zero.wLANGID = usb_cdc_lang_descriptors;

  uint8_t string_count = 0;

  if (vendor_name != NULL) {
    string_count++;
    class->device_descriptor.iManufacturer = string_count;
  }
  if (product_name != NULL) {
    string_count++;
    class->device_descriptor.iProduct = string_count;
  }
  if (serial_name != NULL) {
    string_count++;
    class->device_descriptor.iSerialNumber = string_count;
  }

  usb_unicode_string_descriptor_t *descriptor =
    (usb_unicode_string_descriptor_t*)malloc(sizeof(usb_unicode_string_descriptor_t) * string_count);
  class->string_descriptors = descriptor;

  if (vendor_name != NULL) {
    descriptor->header.bDescriptorType = DESCRIPTOR_STRING;
    descriptor->header.bLength = 4 + strlen(vendor_name);
    descriptor->bString = (uint8_t*)vendor_name;

    descriptor++;
  }
  if (product_name != NULL) {
    descriptor->header.bDescriptorType = DESCRIPTOR_STRING;
    descriptor->header.bLength = 4 + strlen(product_name);
    descriptor->bString = (uint8_t*)product_name;
    descriptor++;
  }
  if (serial_name != NULL) {
    descriptor->header.bDescriptorType = DESCRIPTOR_STRING;
    descriptor->header.bLength = 4 + strlen(serial_name);
    descriptor->bString = (uint8_t*)serial_name;
    descriptor++;
  }

  return class;
}

void usb_device_cdc_acm_configure(usb_device_t* device) {
  usb_device_cdc_t* cdc = (usb_device_cdc_t*)device->class;
  usb_class_header_t* header = &cdc->header;

  usb_device_descriptor_t device_descriptor =
    {
      .header = { .bDescriptorType = DESCRIPTOR_DEVICE, .bLength = sizeof(usb_device_descriptor_t) },
      .bcdUSB = 0x20,
      .bDeviceClass = USB_CLASS_CDC_CODE,
      .bDeviceSubClass = USB_SUBCLASS_CDC_ACM_CODE,
      .bDeviceProtocol = 0x00,
      .bMaxPacketSize0 = 64,
      .idVendor = header->device_descriptor.idVendor,
      .idProduct = header->device_descriptor.idProduct,
      .bcdDevice = 0x0000,
      .iManufacturer = header->device_descriptor.iManufacturer,
      .iProduct = header->device_descriptor.iManufacturer,
      .iSerialNumber = header->device_descriptor.iSerialNumber,
      .bNumConfigurations = 1,
    };
  header->device_descriptor = device_descriptor;

  usb_device_qualifier_t qualifier =
    {
      .header = { .bDescriptorType = DESCRIPTOR_DEVICE_QUALIFIER, .bLength = sizeof(usb_device_qualifier_t) },
      .bcdUSB = 0x20,
      .bDeviceClass = USB_CLASS_CDC_CODE,
      .bDeviceSubClass = 0x00,
      .bDeviceProtocol = 0x00,
      .bMaxPacketSize0 = 64,
      .bNumConfigurations = 0,
      .bReserved = 0
    };
  header->device_qualifier = qualifier;

  usb_configuration_descriptor_t configuration_descriptor =
    {
      .header = { .bDescriptorType = DESCRIPTOR_CONFIGURATION, .bLength = sizeof(usb_configuration_descriptor_t) },
      .bNumInterfaces = 2,
      .bConfigurationValue = 1,
      .iConfiguration = 0,
      .bmAttributes =
      {
        .self_powered = 0,
        .remote_wakeup = 0,
        .reserved1 = 1,
        .reserved_zeros = 0,
      },
      .bMaxPower = 50,
    };
  header->configuration_descriptor = configuration_descriptor;

  header->interfaces_count = 2;
  header->interfaces = malloc(2 * sizeof(usb_interface_t));

  static usb_endpoint_descriptor_t notification_endpoint =
  {.header = {.bDescriptorType = DESCRIPTOR_ENDPOINT,
              .bLength = sizeof(usb_endpoint_descriptor_t)},
   .bEndpointAddress = { .endpoint_number = 1, .direction = USB_ENDPOINT_IN, .reserved = 0 },
   .bInterval = 16,
   .bmAttributes = {
       .reserved_zeros = 0,
       .synchronization_type = USB_ENDPOINT_SYNC_NO_SYNC,
       .usage_type = USB_ENDPOINT_USAGE_DATA,
       .transfer_type = USB_ENDPOINT_TYPE_INTERRUPT,
   },
    .wMaxPacketSize = 64,
  };
  usb_interface_t communications_interface =
  {.interface_descriptor = {
       .header = {.bDescriptorType = DESCRIPTOR_INTERFACE,
                  .bLength = sizeof(usb_interface_descriptor_t)},
       .bInterfaceNumber = 0,
       .bAlternateSetting = 1,
       .bNumEndpoints = 1,
       .bInterfaceClass = USB_CLASS_CDC_CODE,
       .bInterfaceSubClass = USB_SUBCLASS_CDC_ACM_CODE,
       .bInterfaceProtocol = 0,
       .iInterface = 0
      },
    .endpoint_descriptors_count = 1,
    // NOTE: mind here, the endpoint is same for all devices,
    // so it's defined here as static variable, meaning it will
    // not be deallocated when this function exits.
    .endpoint_descriptors = &notification_endpoint,
  };
  static usb_endpoint_descriptor_t data_endpoints[2] = {
    {.header = {.bDescriptorType = DESCRIPTOR_ENDPOINT,
                .bLength = sizeof(usb_endpoint_descriptor_t)},
     .bEndpointAddress = { .endpoint_number = 2, .direction = USB_ENDPOINT_IN, .reserved = 0 },
     .bInterval = 1,
     .bmAttributes = {
       .reserved_zeros = 0,
       .synchronization_type = USB_ENDPOINT_SYNC_NO_SYNC,
       .usage_type = USB_ENDPOINT_USAGE_DATA,
       .transfer_type = USB_ENDPOINT_TYPE_BULK,
     },
     .wMaxPacketSize = 64,
    },
    {.header = {.bDescriptorType = DESCRIPTOR_ENDPOINT,
                .bLength = sizeof(usb_endpoint_descriptor_t)},
     .bEndpointAddress = { .endpoint_number = 1, .direction = USB_ENDPOINT_OUT, .reserved = 0 },
     .bInterval = 16,
     .bmAttributes = {
       .reserved_zeros = 0,
       .synchronization_type = USB_ENDPOINT_SYNC_NO_SYNC,
       .usage_type = USB_ENDPOINT_USAGE_DATA,
       .transfer_type = USB_ENDPOINT_TYPE_BULK,
     },
     .wMaxPacketSize = 64,
    },
  };
  usb_interface_t data_interface = {
    .interface_descriptor = {
      .header = {.bDescriptorType = DESCRIPTOR_INTERFACE,
                 .bLength = sizeof(usb_interface_descriptor_t)},
      .bInterfaceNumber = 1,
      .bAlternateSetting = 2,
      .bNumEndpoints = 1,
      .bInterfaceClass = USB_CLASS_DATA_CODE,
      .bInterfaceSubClass = 0x00,
      .bInterfaceProtocol = 0,
      .iInterface = 0
    },
    .endpoint_descriptors_count = 2,
    // NOTE: mind here, the endpoint is same for all devices,
    // so it's defined here as static variable, meaning it will
    // not be deallocated when this function exits.
    .endpoint_descriptors = data_endpoints,
  };

  cdc->functional_descriptors_count = 4;

  static usb_cdc_header_functional_decriptor_t header_function = {
    .header = { .bFunctionLength = sizeof(usb_cdc_header_functional_decriptor_t), .bDescriptorType = CS_INTERFACE, .bDescriptorSubType = HEADER_FUNCTIONAL_DESCRIPTOR_FUNCTIONAL_DESCRIPTOR },
    .bcdCDC = 0x0110,
  };
  static usb_cdc_acm_functional_decriptor_t acm_function = {
    .header = { .bFunctionLength = sizeof(usb_cdc_acm_functional_decriptor_t), .bDescriptorType = CS_INTERFACE, .bDescriptorSubType = ABSTRACT_CONTROL_MANAGEMENT_FUNCTIONAL_DESCRIPTOR },
    .bmCapabilities = 0x00,
  };
  static usb_cdc_union_functional_decriptor_t union_function = {
    .header = { .bFunctionLength = sizeof(usb_cdc_union_functional_decriptor_t), .bDescriptorType = CS_INTERFACE, .bDescriptorSubType = UNION_FUNCTIONAL_DESCRIPTOR },
    .bControlInterface = 0,
    .bSubordinateInterface0 = 1,
  };
  static usb_cdc_call_management_functional_decriptor_t call_function = {
    .header = { .bFunctionLength = sizeof(usb_cdc_call_management_functional_decriptor_t), .bDescriptorType = CS_INTERFACE, .bDescriptorSubType = CALL_MANAGEMENT_FUNCTIONAL_FUNCTIONAL_DESCRIPTOR },
    .bmCapabilities = 0x00
  };
  static usb_cdc_functional_descriptor_header_t *headers[4] =
    { &header_function.header, &acm_function.header, &union_function.header, &call_function.header };
  cdc->functional_descriptors = headers;

  header->interfaces[0] = communications_interface;
  header->interfaces[1] = data_interface;
}

void usb_device_cdc_send_configuration(usb_device_t *device,
                                       usb_setup_command_t *cmd) {
  usb_device_cdc_t* dev = (usb_device_cdc_t*)device->class;
  USB_OTG_INEndpointTypeDef* enp0 = &device->in[0];
  volatile uint32_t* enp0fifo = &device->fifos[0].data[0];
  uint32_t sub_word_data;
  uint8_t  sub_word_count = 0;

  if (enp0->DIEPCTL & USB_OTG_DIEPCTL_EPENA) {
    // this is bad! Can't send the packet, this shouldn't get here, ever.
    while (enp0->DIEPCTL & USB_OTG_DIEPCTL_EPENA);
  }

  // first configure the size
  uint32_t size =
      sizeof(usb_configuration_descriptor_t) +
    dev->header.interfaces_count * sizeof(usb_interface_descriptor_t);

  for (uint8_t i = 0; i < dev->header.interfaces_count; i++) {
    usb_interface_t* interface = &dev->header.interfaces[i];
    size += interface->endpoint_descriptors_count * sizeof(usb_endpoint_descriptor_t);
  }

  for (uint8_t i = 0; i < dev->functional_descriptors_count; i++) {
    usb_cdc_functional_descriptor_header_t* descriptor = dev->functional_descriptors[i];
    size += descriptor->bFunctionLength;
  }

  dev->header.configuration_descriptor.wTotalLength = size;

  // TODO: what if there is not enough space for this?
  // I mean there should be... but that case should probably be handled to,
  // it depends a lot on how many functions and interfaces we do have...
  uint16_t packet_count = (size + 63) / 64;
  enp0->DIEPTSIZ = (packet_count << USB_OTG_DIEPTSIZ_PKTCNT_Pos) | (size << USB_OTG_DIEPTSIZ_XFRSIZ_Pos);

  // fill fifo with all configuration
  usb_generic_fill_fifo_words(enp0,
                              (uint8_t*)&dev->header.configuration_descriptor,
                              sizeof(usb_configuration_descriptor_t),
                              enp0fifo, &sub_word_data, &sub_word_count);

  // NOTE: there is always one control interface and one data one.
  for (uint8_t i = 0; i < dev->header.interfaces_count; i++) {
    usb_interface_t* interface = &dev->header.interfaces[i];
    usb_generic_fill_fifo_words(enp0,
                                (uint8_t*)&interface->interface_descriptor,
                                sizeof(usb_interface_descriptor_t),
                                enp0fifo, &sub_word_data, &sub_word_count);

    // Control interface has functional descriptors
    if (i == 0) {
      for (uint8_t j = 0; j < dev->functional_descriptors_count; j++) {
        usb_cdc_functional_descriptor_header_t* descriptor = dev->functional_descriptors[j];
        usb_generic_fill_fifo_words(enp0, (uint8_t *)&descriptor,
                                    descriptor->bFunctionLength,
                                    enp0fifo, &sub_word_data, &sub_word_count);
      }
    }

    for (uint8_t j = 0; j < interface->endpoint_descriptors_count; j++) {
      usb_generic_fill_fifo_words(enp0,
                                  (uint8_t*)&interface->endpoint_descriptors[j],
                                  sizeof(usb_endpoint_descriptor_t),
                                  enp0fifo, &sub_word_data, &sub_word_count);
    }

    // The fifo takes always 4 elements. We do not care what's written on the
    // last bytes, since the peripheral will know only about the first bytes
    // as per the size written to DIEPTSIZ
    if (sub_word_count > 0) {
      sub_word_count = 0;
      usb_generic_fill_fifo_words(enp0, (uint8_t *)&sub_word_data,
                                  4, enp0fifo, &sub_word_data, &sub_word_count);
    }

  }

  // enable endpoint
  enp0->DIEPCTL |= USB_OTG_DIEPCTL_CNAK | USB_OTG_DIEPCTL_EPENA;
}

void usb_device_cdc_setup_packet_callback(usb_device_t *device,
                                          usb_setup_command_t *cmd) {
  // TODO - is there something to do? maybe just the multiplexed commands?
}
void usb_device_cdc_enumeration_done_callback(usb_device_t *device) {
  // TODO start the application somehow
}
void usb_device_cdc_txfifo_empty_callback(usb_device_t *device,
                                          uint8_t endpoint) {
// TODO the application
}
void usb_device_cdc_rxfifo_empty_callback(usb_device_t *device,
                                          uint8_t endpoint) {
// TODO the application
}
void usb_device_cdc_transmit_done_callback(usb_device_t *device,
                                           uint8_t endpoint) {
// TODO the application
}
void usb_device_cdc_nak_callback(usb_device_t *device, uint8_t endpoint) {
  // Nothing to do for now
}
void usb_device_cdc_nyet_callback(usb_device_t *device, uint8_t endpoint) {
  // Nothing to do for now
}

Do not follow this link