#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->device_descriptor.idProduct = id_product;
class->device_descriptor.idVendor = id_vendor;
class->device_descriptor.bcdDevice = serial_number;
class->string_descriptor_zero.header.bDescriptorType = DESCRIPTOR_STRING;
class->string_descriptor_zero.header.bLength = sizeof(usb_string_descriptor_zero_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 = 0x00,
.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.iProduct,
.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 = 0,
.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 = ¬ification_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 = 2,
.bAlternateSetting = 0,
.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;
}
uint16_t get_size(uint16_t size_to_send, uint16_t* remaining_size) {
uint16_t size = size_to_send;
if (*remaining_size < size_to_send) {
size = *remaining_size;
*remaining_size = 0;
} else {
*remaining_size -= size_to_send;
}
return size;
}
task_result_t 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;
// first configure the size
uint16_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;
if (size > cmd->wLength) {
size = cmd->wLength;
}
// 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...
task_result_t result = usb_generic_setup_in_endpoint(enp0, size, 64);
if (result != RES_OK) {
return result;
}
// fill fifo with all configuration
usb_generic_fill_fifo_words(enp0,
(uint8_t*)&dev->header.configuration_descriptor,
get_size(sizeof(usb_configuration_descriptor_t), &size),
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,
get_size(sizeof(usb_interface_descriptor_t), &size),
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,
get_size(descriptor->bFunctionLength, &size),
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],
get_size(sizeof(usb_endpoint_descriptor_t), &size),
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);
}
// After the fifo is filled...
enp0->DIEPCTL = USB_OTG_DIEPCTL_CNAK;
return result;
}
task_result_t 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?
return RES_OK;
}
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
}