#include <stdint.h>
#include <stm32h747xx.h>
#include "generic.h"
#include <stdbool.h>
#ifndef USB_H
#define USB_H
// NOTE: fields here have to be kept in same order,
// and care must be taken the structs are not padded.
// They are from the usb standard.
#define USB_SUBLANG_ENGLISH_US 0x01
#define USB_LANG_ENGLISH 0x09
#pragma GCC diagnostic error "-Wpadded"
/**
* @struct usb_device_status_t
* USB Device status halfword
*/
typedef struct {
uint8_t self_powered : 1; /**< Is the device self powered? */
uint8_t remote_wakeup : 1; /**< Can the device be remotely waken up by usb? */
uint16_t reserved_zeros : 14; /**< Should be zeros, reserved. */
} usb_device_status_t;
_Static_assert(sizeof(usb_device_status_t) == 2, "Size check");
/**
* @struct usb_endpoint_status_t
* USB endpoint status halfword
*/
typedef struct {
uint8_t halt : 1; /**< Is the endpoint halted? */
uint16_t reserved_zeros : 15; /**< Should be zeros, reserved */
} usb_endpoint_status_t;
_Static_assert(sizeof(usb_endpoint_status_t) == 2, "Size check");
/**
* @struct usb_interface_status_t
* USB interface status halfword
*/
typedef struct {
uint16_t reserved; /**< Should be zeros, reserved. */
} usb_interface_status_t;
_Static_assert(sizeof(usb_interface_status_t) == 2, "Size check");
/**
* @enum usb_setup_command_direction_t
* USB setup command request direction
*/
typedef enum {
USB_SETUP_HOST_TO_DEVICE, /**< The host will send data to device. */
USB_SETUP_DEVICE_TO_HOST, /**< The device will send dat to host. */
} usb_setup_command_direction_t;
/**
* @enum usb_setup_command_type_t
* USB setup command type
*/
typedef enum {
USB_SETUP_STANDARD, /**< A standard setup command */
USB_SETUP_CLASS, /**< A class-specific setup commandj */
USB_SETUP_VENDOR, /**< A vendor-specific setup command */
USB_SETUP_RESERVED, /**< Reserved. */
} usb_setup_command_type_t;
/**
* @enum usb_setup_command_recipient_t
* USB setup command recipient
*/
typedef enum {
USB_SETUP_DEVICE, /**< The command's recipient is the device */
USB_SETUP_INTERFACE, /**< The command's recipient is an interface (ie. get descriptor of an interface) */
USB_SETUP_ENDPOINT, /**< The command's recipient is an endpoint (ie. get descriptor of an endpoint) */
USB_SETUP_OTHER, /**< Other. */
} usb_setup_command_recipient_t;
/**
* @struct usb_setup_command_request_type_t
* USB setup command request type
*/
typedef struct __attribute__((packed)) {
uint8_t recipient : 5; /**< The address of the recipient */
usb_setup_command_type_t type : 2; /**< The type of the command */
usb_setup_command_direction_t direction : 1; /**< The direction of the setup command */
} usb_setup_command_request_type_t;
/**
* @enum usb_setup_request_t
* Generic setup request commands
*/
typedef enum __attribute__((packed)) {
USB_SETUP_GET_STATUS = 0, /**< Get status byte from the device */
USB_SETUP_CLEAR_FEATURE = 1, /**< Clear a feature of an interface or an endpoint, like halt */
RESERVED1 = 2, /**< Reserved. */
USB_SETUP_SET_FEATURE = 3, /**< Set a feature of an interface or an endpoint, like halt. */
RESERVED2 = 4, /**< Reserved. */
USB_SETUP_SET_ADDRESS = 5, /**< Set address of the device to what is in data of the request. */
USB_SETUP_GET_DESCRIPTOR = 6, /**< Get a specific descriptor of the device. It should be sent in data stage. */
USB_SETUP_SET_DESCRIPTOR = 7, /**< Set an alternate setting for given descriptor. */
USB_SETUP_GET_CONFIGURATION = 8, /**< Get currently used configuration alternate setting. */
USB_SETUP_SET_CONFIGURATION = 9, /**< Set alternate configuration. */
USB_SETUP_GET_INTERFACE = 10, /**< Get currently used interface alternate setting. */
USB_SETUP_SET_INTERFACE = 11, /**< Set alternate interface. */
USB_SETUP_SYNCH_FRAME = 12, /**< I don't know. */
} usb_setup_request_t;
/**
* @enum usb_setup_feature_selector_t
* USB setup features for SET_FEATURE and CLEAR_FEATURE
*/
typedef enum {
USB_FEATURE_ENDPOINT_HALT = 0, /**< Endpoint halt feature */
USB_FEATURE_DEVICE_REMOTE_WAKEUP = 1, /**< Device remote wakeup feature */
USB_FEATURE_TEST_MODE = 2, /**< Device test mode */
} usb_setup_feature_selector_t;
typedef enum {
DESCRIPTOR_DEVICE = 1,
DESCRIPTOR_CONFIGURATION = 2,
DESCRIPTOR_STRING = 3,
DESCRIPTOR_INTERFACE = 4,
DESCRIPTOR_ENDPOINT = 5,
DESCRIPTOR_DEVICE_QUALIFIER = 6,
DESCRIPTOR_OTHER_SPEED_CONFIGURATION = 7,
DESCRIPTOR_INTERFACE_POWER = 8,
} usb_descriptor_type_t;
/**
* @struct usb_setup_command_t
* USB setup command structure
*/
typedef struct __attribute__((packed)) {
usb_setup_command_request_type_t bmRequestType; /**< Type of the request */
usb_setup_request_t bRequest; /**< The request data. */
uint16_t wValue; /**< Value of the request, dependant on the request type. For example device address to set. */
uint16_t wIndex; /**< Index, dependant on the request type. For example interface index. */
uint16_t wLength; /**< For output request, amount of data. For input request, maximum number of bytes to send. */
} usb_setup_command_t;
_Static_assert(sizeof(usb_setup_command_t) == 8, "Size check");
/**
* @struct usb_descriptor_t
* Header of every usb descriptor
*/
typedef struct __attribute__((packed)) {
uint8_t bLength; /**< Full length of this descriptor. */
uint8_t bDescriptorType; /**< Type of the descriptor - usb_descriptor_type_t */
} usb_descriptor_t;
typedef struct {
usb_descriptor_t header;
uint16_t bcdUSB;
uint8_t bDeviceClass;
uint8_t bDeviceSubClass;
uint8_t bDeviceProtocol;
uint8_t bMaxPacketSize0;
uint16_t idVendor;
uint16_t idProduct;
uint16_t bcdDevice;
uint8_t iManufacturer;
uint8_t iProduct;
uint8_t iSerialNumber;
uint8_t bNumConfigurations;
} usb_device_descriptor_t;
_Static_assert(sizeof(usb_device_descriptor_t) == 18, "Size check");
typedef struct {
usb_descriptor_t header;
uint16_t bcdUSB;
uint8_t bDeviceClass;
uint8_t bDeviceSubClass;
uint8_t bDeviceProtocol;
uint8_t bMaxPacketSize0;
uint8_t bNumConfigurations;
uint8_t bReserved;
} usb_device_qualifier_t;
_Static_assert(sizeof(usb_device_qualifier_t) == 10, "Size check");
typedef struct {
uint8_t reserved1 : 1;
uint8_t self_powered : 1;
uint8_t remote_wakeup : 1;
uint8_t reserved_zeros: 5;
} usb_configuration_descriptor_attributes;
typedef struct __attribute__((packed)) {
usb_descriptor_t header;
uint16_t wTotalLength;
uint8_t bNumInterfaces;
uint8_t bConfigurationValue;
uint8_t iConfiguration;
usb_configuration_descriptor_attributes bmAttributes;
uint8_t bMaxPower;
} usb_configuration_descriptor_t;
_Static_assert(sizeof(usb_configuration_descriptor_t) == 9, "Size check");
typedef usb_configuration_descriptor_t usb_other_speed_configuration_t;
typedef struct __attribute__((packed)) {
usb_descriptor_t header;
uint8_t bInterfaceNumber;
uint8_t bAlternateSetting;
uint8_t bNumEndpoints;
uint8_t bInterfaceClass;
uint8_t bInterfaceSubClass;
uint8_t bInterfaceProtocol;
uint8_t iInterface;
} usb_interface_descriptor_t;
_Static_assert(sizeof(usb_interface_descriptor_t) == 9, "Size check");
typedef enum {
USB_ENDPOINT_OUT = 0,
USB_ENDPOINT_IN = 1,
} usb_endpoint_direction_t;
typedef struct __attribute__((packed)) {
uint8_t endpoint_number : 4;
uint8_t reserved : 3;
usb_endpoint_direction_t direction : 1;
} usb_endpoint_address_t;
typedef enum {
USB_ENDPOINT_TYPE_CONTROL = 0,
USB_ENDPOINT_TYPE_ISOCHRONOUS = 1,
USB_ENDPOINT_TYPE_BULK = 2,
USB_ENDPOINT_TYPE_INTERRUPT = 3,
} usb_endpoint_transfer_type_t;
typedef enum {
USB_ENDPOINT_SYNC_NO_SYNC = 0,
USB_ENDPOINT_SYNC_ASYNCHRONOUS = 1,
USB_ENDPOINT_SYNC_ADAPTIVE = 2,
USB_ENDPOINT_SYNC_SYNCHRONOUS = 3,
} usb_endpoint_synchronization_type_t;
typedef enum {
USB_ENDPOINT_USAGE_DATA = 0,
USB_ENDPOINT_USAGE_FEEDBACK = 1,
USB_ENDPOINT_USAGE_IMPLICIT_FEEDBACK = 2,
USB_ENDPOINT_USAGE_RESERVED = 3,
} usb_endpoint_usage_type_t;
typedef struct __attribute__((packed)) {
usb_endpoint_transfer_type_t transfer_type : 2;
usb_endpoint_synchronization_type_t synchronization_type : 2;
usb_endpoint_usage_type_t usage_type : 2;
uint8_t reserved_zeros : 2;
} usb_endpoint_attributes_t;
typedef struct __attribute__((packed)) {
usb_descriptor_t header;
usb_endpoint_address_t bEndpointAddress;
usb_endpoint_attributes_t bmAttributes;
uint16_t wMaxPacketSize;
uint8_t bInterval;
} usb_endpoint_descriptor_t;
_Static_assert(sizeof(usb_endpoint_descriptor_t) == 7, "Size check");
#pragma GCC diagnostic ignored "-Wpadded"
typedef struct {
// uint8_t bLength; // lengto of wLANGID + 2
usb_descriptor_t header;
uint16_t *wLANGID; // should have length equal to bLength - 2
} usb_string_descriptor_zero_t;
typedef struct {
usb_descriptor_t header;
uint16_t *bString;
} usb_unicode_string_descriptor_t;
// NOTE: End of structs from usb specification here
/**
* @brief Put data into fifo
* @details
* This function will fill fifo of the given endpoint, without configuring the endpoint.
* The motivation for this function is to be able to pass multiple structures into the fifo.
* For example if you want to send usb configuration, you first send configuration descriptor,
* then endpoint descriptor and so on. These structures are not usually aligned to 4 bytes.
* So we need to always pass in only part of the structure that is divisible by 4 bytes and
* put the rest to the next one. To do that, we accept pointers to sub word data and bytes.
* It's expected to first send all structures, and at the end, if the sub_word_bytes is >0,
* to send one more call with size == 4, and data == sub_word_data.
* These hold the data that overflow this 4 byte threshold.
* @param[in,out] endpoint NOT used.
* @param[out] data The data to send, like a usb descriptor.
* @param[in] size Size of the data to send.
* @param[out] fifo_tx_target The fifo to put the data to. It is written to the same address even for multiple words.
* @param[in, out] sub_word_data Input and output data. For input this means data to prefix the transfer with. For output this means what to prefix next transfer with.
* @param[in, out] sub_word_bytes Input and output byte count of sub_word_data.
*/
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_fill_fifo(USB_OTG_INEndpointTypeDef *endpoint,
uint8_t *data,
uint16_t size,
volatile uint32_t *fifo_tx_target);
task_result_t usb_generic_setup_in_endpoint(USB_OTG_INEndpointTypeDef *endpoint,
uint16_t size,
uint16_t max_packet_size);
/**
* @brief Send data through usb
* @details Configures the endpoint with given data count, fills fifos with those, and enabled the endpoint.
* @param[in,out] endpoint The endpoint to send the data with.
* @param[out] data Description
* @param[in] size Number of bytes to send.
* @param[out] fifo_tx_target Address of the fifo. All data is written to this exact address, no offsets are made.
*/
task_result_t usb_generic_send(USB_OTG_INEndpointTypeDef *endpoint,
uint8_t *data,
uint16_t size,
volatile uint32_t *fifo_tx_target);
bool usb_is_inendpoint_ready(USB_OTG_INEndpointTypeDef *endpoint);
bool usb_check_fifo_space(USB_OTG_INEndpointTypeDef *endpoint, uint16_t size);
/**
* @brief Read data from the usb fifo.
* @details The data are read from the fifo pointer, without any offsets, and put into data array
* @param[in,out] data The array to put data to
* @param[in] size Number of bytes to store in the array.
* @param[out] fifo_rx_source Address of the fifo. All data are read from this exact address, no offsets are made.
*/
task_result_t usb_generic_read(uint8_t *data,
uint16_t size,
volatile uint32_t *fifo_rx_source);
/**
* @brief Sends descriptor
* @details Sends data matching the given descriptor. It is expected that the descriptor has length set properly.
* @param[in,out] endpoint The endpoint to send data with.
* @param[out] descriptor The descriptor to send. The wlength has to be set.
* @param[out] fifo_tx_target Address of the fifo. All data are written to this exact address, no offsets are made.
*/
task_result_t usb_send_descriptor(USB_OTG_INEndpointTypeDef *endpoint,
usb_descriptor_t *descriptor,
uint16_t max_size,
volatile uint32_t *fifo_tx_target);
/**
* @brief Sends device descriptor.
* @details Sets appropriate length of the descriptor, and then sends it using @see usb_send_descriptor
* @param[in,out] endpoint The endpoint to send data with.
* @param[out] descriptor The descriptor to send. The wLength in header can be wrong, it will get replaced.
* @param[out] fifo_tx_target Address of the fifo. All data are written to this exact address, no offsets are made.
*/
task_result_t usb_send_device_descriptor(USB_OTG_INEndpointTypeDef *endpoint,
usb_device_descriptor_t *descriptor,
uint16_t max_size,
volatile uint32_t *fifo_tx_target);
/**
* @brief Sends device qualifier descriptor.
* @details Sets appropriate length of the descriptor, and then sends it using @see usb_send_descriptor
* @param[in,out] endpoint The endpoint to send data with.
* @param[out] descriptor The descriptor to send. The wLength in header can be wrong, it will get replaced.
* @param[out] fifo_tx_target Address of the fifo. All data are written to this exact address, no offsets are made.
*/
task_result_t usb_send_device_qualifier_descriptor(USB_OTG_INEndpointTypeDef* endpoint,usb_device_qualifier_t *descriptor,
uint16_t max_size,
volatile uint32_t *fifo_tx_target);
/**
* @brief Sends interface qualifier descriptor.
* @details Sets appropriate length of the descriptor, and then sends it using @see usb_send_descriptor
* @param[in,out] endpoint The endpoint to send data with.
* @param[out] descriptor The descriptor to send. The wLength in header can be wrong, it will get replaced.
* @param[out] fifo_tx_target Address of the fifo. All data are written to this exact address, no offsets are made.
*/
task_result_t usb_send_interface_descriptor(USB_OTG_INEndpointTypeDef *endpoint,
usb_interface_descriptor_t *descriptor,
uint16_t max_size,
volatile uint32_t *fifo_tx_target);
/**
* @brief Sends endpoint qualifier descriptor.
* @details Sets appropriate length of the descriptor, and then sends it using @see usb_send_descriptor
* @param[in,out] endpoint The endpoint to send data with.
* @param[out] descriptor The descriptor to send. The wLength in header can be wrong, it will get replaced.
* @param[out] fifo_tx_target Address of the fifo. All data are written to this exact address, no offsets are made.
*/
task_result_t usb_send_endpoint_descriptor(USB_OTG_INEndpointTypeDef *endpoint,
usb_endpoint_descriptor_t *descriptor,
uint16_t max_size,
volatile uint32_t *fifo_tx_target);
/**
* @brief Sends string descriptor zero.
* @details The length of the array has to be known, it is determined by wLength of the header.
* @param[in,out] endpoint The endpoint to send data with.
* @param[out] string_descriptor The descriptor to send. The wLength NEEDS to be correct!
* @param[out] fifo_tx_target Address of the fifo. All data are written to this exact address, no offsets are made.
*/
task_result_t usb_send_string_descriptor_zero(
USB_OTG_INEndpointTypeDef *endpoint,
usb_string_descriptor_zero_t *string_descriptor,
uint16_t max_size,
volatile uint32_t *fifo_tx_target);
/**
* @brief Sends unicode string descriptor.
* @details The length of the array has to be known, it is determined by wLength of the header.
* @param[in,out] endpoint The endpoint to send data with.
* @param[out] string_descriptor The descriptor to send. The wLength NEEDS to be correct!
* @param[out] fifo_tx_target Address of the fifo. All data are written to this exact address, no offsets are made.
*/
task_result_t usb_send_unicode_string_descriptor(
USB_OTG_INEndpointTypeDef *endpoint,
usb_unicode_string_descriptor_t *string_descriptor,
uint16_t max_size,
volatile uint32_t *fifo_tx_target);
task_result_t usb_send_unicode_string_descriptor(
USB_OTG_INEndpointTypeDef *endpoint,
usb_unicode_string_descriptor_t *string_descriptor,
uint16_t max_size,
volatile uint32_t *fifo_tx_target);
void usb_flush_tx_fifo(USB_OTG_GlobalTypeDef* core, uint16_t fifo_num);
void usb_flush_rx_fifo(USB_OTG_GlobalTypeDef* core);
#endif // USB_H