#include "usb.h" #include "usb_device.h" #include "usb_device_cdc.h" #include #include #include 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 }