diff --git a/usb.c b/usb.c index bbe5387..7b36780 100644 --- a/usb.c +++ b/usb.c @@ -8,37 +8,10 @@ #include "usb.h" -// Number of serial devices we want to provide. +/* Number of serial devices we want to provide. */ #define NUM_SERIAL 2 -static const struct usb_device_descriptor dev = { - .bLength = USB_DT_DEVICE_SIZE, - .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = 0x0200, - - // Devices with class 0xEF, subclass 2, and protocol 1 are interface associations - .bDeviceClass = 0xEF, - .bDeviceSubClass = 2, - .bDeviceProtocol = 1, - - .bMaxPacketSize0 = 64, - - .idVendor = 0x0483, - .idProduct = 0x5740, - - // Revision of the device: 2.00 - .bcdDevice = 0x0200, - - // Indices into the string table (starts at 1 - 0 is none) - .iManufacturer = 1, - .iProduct = 2, - .iSerialNumber = 3, - - // We only have one configuration - .bNumConfigurations = 1, -}; - -// CDC-ACM devices need a functional descriptor that looks like this + /* CDC-ACM devices need a functional descriptor that looks like this */ struct usb_cdcacm_functional_descriptor { struct usb_cdc_header_descriptor header; struct usb_cdc_call_management_descriptor call_mgmt; @@ -46,40 +19,14 @@ struct usb_cdcacm_functional_descriptor { struct usb_cdc_union_descriptor cdc_union; } __attribute__((packed)); -// Each CDC-ACM device has... -// two data endpoints and one control endpoint -static struct usb_endpoint_descriptor endpoints[3 * NUM_SERIAL]; -// one functional descriptor -static struct usb_cdcacm_functional_descriptor - cdcacm_functional_descriptors[NUM_SERIAL]; -// a data interface combining the two data endpoints, and a control interface -static struct usb_interface_descriptor interface_descriptors[2 * NUM_SERIAL]; - -// instantiate the usb_interface struct needed by libopencm3 -static struct usb_interface interfaces[2 * NUM_SERIAL]; - -// Create the one configuration we have -static const struct usb_config_descriptor config = { - .bLength = USB_DT_CONFIGURATION_SIZE, - .bDescriptorType = USB_DT_CONFIGURATION, - .bNumInterfaces = - sizeof(interface_descriptors) / sizeof(interface_descriptors[0]), - // this is filled in later by libopencm3 - .wTotalLength = 0, - // first and only config - .bConfigurationValue = 1, - // name of the config (none here) - .iConfiguration = 0, - // 0x80 is required to be set - .bmAttributes = 0x80, - // multiply by 2 mA to get maximum power we request - .bMaxPower = 100, - // pointer to the interfaces declared above - .interface = interfaces, -}; - +/* Allocate some space for our serial */ static char serial[24]; -static char * usb_strings[] = { + +/* + These strings will be used as identifiers in some descriptors. + * In descriptors, indices are 1-based, and 0 is an empty string. + */ +static char const *usb_strings[] = { "Imaginaerraum.de", "DoorControl", serial, @@ -87,13 +34,75 @@ static char * usb_strings[] = { "Other" }; +usbd_device *g_usbd_dev; + +/* The descriptor for our device. We need only one of these. */ +static const struct usb_device_descriptor dev = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x0200, + /* + * Devices with class 0xEF, subclass 2, and protocol 1 are interface + * associations that can contain multiple unrelated interfaces, like a + * keyboard and a video device, or multiple serial devices + */ + .bDeviceClass = 0xEF, + .bDeviceSubClass = 2, + .bDeviceProtocol = 1, + /* Maximum packet size for endpoint zero */ + .bMaxPacketSize0 = 64, + /* Vendor and product ID are for ST's virtual modem */ + .idVendor = 0x0483, + .idProduct = 0x5740, + /* Revision of the device: 2.00 */ + .bcdDevice = 0x0200, + /* Indices into the string table (starts at 1 - 0 is none) */ + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 3, + /* We only have one configuration */ + .bNumConfigurations = 1, +}; + +/* Each CDC-ACM device has... */ +/* two data endpoints and one control endpoint */ +static struct usb_endpoint_descriptor endpoints[3 * NUM_SERIAL]; +/* one functional descriptor */ +static struct usb_cdcacm_functional_descriptor + cdcacm_functional_descriptors[NUM_SERIAL]; +/* a data interface combining the two data endpoints, and a control interface */ +static struct usb_interface_descriptor interface_descriptors[2 * NUM_SERIAL]; + +/* instantiate the usb_interface struct needed by libopencm3 */ +static struct usb_interface interfaces[sizeof(interface_descriptors) / sizeof(interface_descriptors[0])]; + +/* Create the one configuration we have */ +static const struct usb_config_descriptor config = { + .bLength = USB_DT_CONFIGURATION_SIZE, + .bDescriptorType = USB_DT_CONFIGURATION, + .bNumInterfaces = + sizeof(interface_descriptors) / sizeof(interface_descriptors[0]), + /* this is filled in later by libopencm3, and also why we can't just create all of this in ROM */ + .wTotalLength = 0, + /* first and only config */ + .bConfigurationValue = 1, + /* name of the config (none here) */ + .iConfiguration = 0, + /* 0x80 is required to be set */ + .bmAttributes = 0x80, + /* multiply by 2 mA to get maximum power we request */ + .bMaxPower = 100, + /* pointer to the interfaces declared above */ + .interface = interfaces, +}; + /* Buffer to be used for control requests. */ uint8_t usbd_control_buffer[512]; -static enum usbd_request_return_codes cdcacm_control_request(usbd_device *usbd_dev, - struct usb_setup_data *req, uint8_t **buf, uint16_t *len, - void (**complete)(usbd_device *usbd_dev, struct usb_setup_data *req)) -{ +static enum usbd_request_return_codes cdcacm_control_request( + usbd_device *usbd_dev, struct usb_setup_data *req, uint8_t **buf, + uint16_t *len, + void (**complete)(usbd_device *usbd_dev, struct usb_setup_data *req)) { (void)complete; (void)buf; (void)usbd_dev; @@ -123,11 +132,11 @@ static void cdcacm_data_rx_cb(usbd_device *usbd_dev, uint8_t ep) char buf[64]; - // Read some data from the endpoint + /* Read some data from the endpoint */ int len = usbd_ep_read_packet(usbd_dev, ep, buf, 64); if (len) { - // Write it to the endpoint of the other interface + /* Write it back */ while (usbd_ep_write_packet(usbd_dev, ep | 0x80, buf, len) == 0); } } @@ -137,7 +146,7 @@ static void cdcacm_set_config(usbd_device *usbd_dev, uint16_t wValue) (void)wValue; for (int i = 0; i < NUM_SERIAL; i++) { - // set up all the endpoints + /* set up all the endpoints for each serial device */ usbd_ep_setup(usbd_dev, 0x01 + 2*i, USB_ENDPOINT_ATTR_BULK, 64, cdcacm_data_rx_cb); usbd_ep_setup(usbd_dev, 0x81 + 2*i, USB_ENDPOINT_ATTR_BULK, 64, NULL); @@ -151,8 +160,6 @@ static void cdcacm_set_config(usbd_device *usbd_dev, uint16_t wValue) cdcacm_control_request); } -usbd_device *usbd_dev; - void usb_setup() { rcc_periph_clock_enable(RCC_GPIOA); rcc_periph_clock_enable(RCC_OTGFS); @@ -162,21 +169,26 @@ void usb_setup() { gpio_set_af(GPIOA, GPIO_AF10, GPIO11 | GPIO12); for (uint8_t i = 0; i < NUM_SERIAL; i++) { - // set up the endpoints - // data endpoints are at addresses 1, 3, 5 (or 0x81, 0x83, 0x85) and so on - // control endpoints are at addresses 0x82, 0x84, 0x86... - // index of the first endpoint in our array + /* + * set up the endpoints Data endpoints are at addresses 1, 3, 5 (or 0x81, + * 0x83, 0x85) and so on. + * + * Control endpoints are at addresses 0x82, 0x84, 0x86... + */ + /* index of the first data endpoint in our array struct */ struct usb_endpoint_descriptor *data_ep = &endpoints[3 * i]; + /* Index of the control endpoint */ struct usb_endpoint_descriptor *ctrl_ep = data_ep + 2; - // address of the first endpoint - + /* Numbers of the data and control endpoints */ const uint8_t data_ep_num = 0x01 + 2*i; const uint8_t ctrl_ep_num = data_ep_num + 1; + /* Interface numbers */ const uint8_t data_iface = 2*i; const uint8_t control_iface = data_iface + 1; - // OUT endpoint for data (host to device) + /* OUT endpoint for data (host to device) */ data_ep[0] = (struct usb_endpoint_descriptor) { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -184,13 +196,15 @@ void usb_setup() { .bmAttributes = USB_ENDPOINT_ATTR_BULK, .wMaxPacketSize = 64 }; - // IN endpoint (device to host) is the same except for the direction + /* IN endpoint (device to host) is the same except for the direction */ data_ep[1] = data_ep[0]; data_ep[1].bEndpointAddress |= 0x80; - // The control endpoint is for interrupt transfers. Its address is one - // higher than the data endpoints, has smaller packets, and we don't really - // care about its interval (because we're not using it) + /* + * The control endpoint is for interrupt transfers. Its address is one + * higher than the data endpoints, has smaller packets, and we don't really + * care about its interval (because we're not using it) + */ *ctrl_ep = (struct usb_endpoint_descriptor) { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -205,66 +219,80 @@ void usb_setup() { .bFunctionLength = sizeof (struct usb_cdc_header_descriptor), .bDescriptorType = CS_INTERFACE, .bDescriptorSubtype = USB_CDC_TYPE_HEADER, - // Version of the standard + /* Version of the standard */ .bcdCDC = 0x0110 }, .call_mgmt = (struct usb_cdc_call_management_descriptor) { .bFunctionLength = sizeof (struct usb_cdc_call_management_descriptor), .bDescriptorType = CS_INTERFACE, .bDescriptorSubtype = USB_CDC_TYPE_CALL_MANAGEMENT, - // We do no call management + /* We do no call management */ .bmCapabilities = 0, - // Index of our data interface + /* Index of our data interface */ .bDataInterface = data_iface, }, .acm = (struct usb_cdc_acm_descriptor){ .bFunctionLength = sizeof (struct usb_cdc_acm_descriptor), .bDescriptorType = CS_INTERFACE, .bDescriptorSubtype = USB_CDC_TYPE_ACM, - // Nothing here either + /* Nothing here either */ .bmCapabilities = 0, }, .cdc_union = (struct usb_cdc_union_descriptor) { .bFunctionLength = sizeof (struct usb_cdc_union_descriptor), .bDescriptorType = CS_INTERFACE, .bDescriptorSubtype = USB_CDC_TYPE_UNION, - // Our interfaces + /* Our interfaces */ .bControlInterface = control_iface, .bSubordinateInterface0 = data_iface, } }; - // A plain data interface + /* A plain data interface */ interface_descriptors[data_iface] = (struct usb_interface_descriptor) { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = data_iface, + /* This is the first alternate setting. There are no others. */ .bAlternateSetting = 0, - // OUT and IN + /* OUT and IN */ .bNumEndpoints = 2, .bInterfaceClass = USB_CLASS_DATA, + /* No subclass or protocol */ .bInterfaceSubClass = 0, .bInterfaceProtocol = 0, .iInterface = 0, + /* + * A pointer to the first of its endpoints - the others must be + * contiguous. + */ .endpoint = data_ep }; + + /* Required by libopencm3 for some reason. */ interfaces[data_iface] = (struct usb_interface) { .num_altsetting = 1, .altsetting = &interface_descriptors[data_iface] }; - + /* + * The control interface gets the actual CDC ACM classes, and the string + * description. It also needs the CDC ACM functional descriptors. + */ interface_descriptors[control_iface] = (struct usb_interface_descriptor) { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = control_iface, .bAlternateSetting = 0, - // Just one direction here + /* Just one direction here */ .bNumEndpoints = 1, .bInterfaceClass = USB_CLASS_CDC, .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, .bInterfaceProtocol = USB_CDC_PROTOCOL_AT, + /* The first three names are for manufacturer, product, and serial. */ .iInterface = (uint8_t)(4 + i), + /* Pointer to the single endpoint */ .endpoint = ctrl_ep, + /* Pointer and length for the CDC ACM functional descriptors */ .extra = &cdcacm_functional_descriptors[i], .extralen = sizeof cdcacm_functional_descriptors[0] }; @@ -272,12 +300,16 @@ void usb_setup() { .num_altsetting = 1, .altsetting = &interface_descriptors[control_iface]}; } - usbd_dev = usbd_init(&otgfs_usb_driver, &dev, &config, - (char const * const *)usb_strings, (sizeof usb_strings) / sizeof(usb_strings[0]), - usbd_control_buffer, sizeof(usbd_control_buffer)); + /* Initialize the device... */ + g_usbd_dev = usbd_init(&otgfs_usb_driver, &dev, &config, + (char const * const *)usb_strings, + (sizeof usb_strings) / sizeof(usb_strings[0]), + usbd_control_buffer, sizeof(usbd_control_buffer)); - usbd_register_set_config_callback(usbd_dev, cdcacm_set_config); + usbd_register_set_config_callback(g_usbd_dev, cdcacm_set_config); + + /* And use interrupts instead of polling. */ nvic_enable_irq(NVIC_OTG_FS_IRQ); } -void otg_fs_isr() { usbd_poll(usbd_dev); } +void otg_fs_isr() { usbd_poll(g_usbd_dev); }