diff options
Diffstat (limited to 'src/generic')
-rw-r--r-- | src/generic/usb_cdc.c | 453 | ||||
-rw-r--r-- | src/generic/usb_cdc.h | 35 | ||||
-rw-r--r-- | src/generic/usbstd.h | 121 | ||||
-rw-r--r-- | src/generic/usbstd_cdc.h | 49 |
4 files changed, 658 insertions, 0 deletions
diff --git a/src/generic/usb_cdc.c b/src/generic/usb_cdc.c new file mode 100644 index 00000000..fea567ec --- /dev/null +++ b/src/generic/usb_cdc.c @@ -0,0 +1,453 @@ +// Support for standard serial port over USB emulation +// +// Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net> +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include <string.h> // memmove +#include "board/pgm.h" // PROGMEM +#include "board/usb_cdc.h" // usb_notify_setup +#include "byteorder.h" // cpu_to_le16 +#include "command.h" // output +#include "generic/usbstd.h" // struct usb_device_descriptor +#include "generic/usbstd_cdc.h" // struct usb_cdc_header_descriptor +#include "sched.h" // sched_wake_task + +// XXX - move to Kconfig +#define CONFIG_USB_VENDOR_ID 0x2341 +#define CONFIG_USB_PRODUCT_ID 0xabcd + + +/**************************************************************** + * Message block sending + ****************************************************************/ + +static struct task_wake usb_bulk_in_wake; +static char transmit_buf[96]; +static uint8_t transmit_pos; + +void +usb_notify_bulk_in(void) +{ + sched_wake_task(&usb_bulk_in_wake); +} + +void +usb_bulk_in_task(void) +{ + if (!sched_check_wake(&usb_bulk_in_wake)) + return; + uint_fast8_t tpos = transmit_pos; + if (!tpos) + return; + uint_fast8_t max_tpos = (tpos > USB_CDC_EP_BULK_IN_SIZE + ? USB_CDC_EP_BULK_IN_SIZE : tpos); + int_fast8_t ret = usb_send_bulk_in(transmit_buf, max_tpos); + if (ret <= 0) + return; + uint_fast8_t needcopy = tpos - ret; + if (needcopy) { + memmove(transmit_buf, &transmit_buf[ret], needcopy); + usb_notify_bulk_in(); + } + transmit_pos = needcopy; +} +DECL_TASK(usb_bulk_in_task); + +// Encode and transmit a "response" message +void +console_sendf(const struct command_encoder *ce, va_list args) +{ + // Verify space for message + uint_fast8_t tpos = transmit_pos, max_size = READP(ce->max_size); + if (tpos + max_size > sizeof(transmit_buf)) + // Not enough space for message + return; + + // Generate message + char *buf = &transmit_buf[tpos]; + uint8_t msglen = command_encodef(buf, ce, args); + command_add_frame(buf, msglen); + + // Start message transmit + transmit_pos = tpos + msglen; + usb_notify_bulk_in(); +} + + +/**************************************************************** + * Message block reading + ****************************************************************/ + +static struct task_wake usb_bulk_out_wake; +static char receive_buf[128]; +static uint8_t receive_pos; + +void +usb_notify_bulk_out(void) +{ + sched_wake_task(&usb_bulk_out_wake); +} + +void +usb_bulk_out_task(void) +{ + if (!sched_check_wake(&usb_bulk_out_wake)) + return; + // Process any existing message blocks + uint_fast8_t rpos = receive_pos; + uint8_t pop_count; + int_fast8_t ret = command_find_block(receive_buf, rpos, &pop_count); + if (ret > 0) + command_dispatch(receive_buf, pop_count); + if (ret) { + // Move buffer + uint_fast8_t needcopy = rpos - pop_count; + if (needcopy) { + memmove(receive_buf, &receive_buf[pop_count], needcopy); + usb_notify_bulk_out(); + } + rpos = needcopy; + } + // Read more data + if (rpos + USB_CDC_EP_BULK_OUT_SIZE <= sizeof(receive_buf)) { + ret = usb_read_bulk_out(&receive_buf[rpos], USB_CDC_EP_BULK_OUT_SIZE); + if (ret > 0) { + rpos += ret; + usb_notify_bulk_out(); + } + } + receive_pos = rpos; +} +DECL_TASK(usb_bulk_out_task); + + +/**************************************************************** + * USB descriptors + ****************************************************************/ + +// XXX - move to Kconfig +#define USB_STR_MANUFACTURER u"Klipper" +#define USB_STR_PRODUCT u"Klipper firmware" +#define USB_STR_SERIAL u"12345" + +// String descriptors +enum { + USB_STR_ID_MANUFACTURER = 1, USB_STR_ID_PRODUCT, USB_STR_ID_SERIAL, +}; + +#define SIZE_cdc_string_langids (sizeof(cdc_string_langids) + 2) + +static const struct usb_string_descriptor cdc_string_langids PROGMEM = { + .bLength = SIZE_cdc_string_langids, + .bDescriptorType = USB_DT_STRING, + .data = { cpu_to_le16(USB_LANGID_ENGLISH_US) }, +}; + +#define SIZE_cdc_string_manufacturer \ + (sizeof(cdc_string_manufacturer) + sizeof(USB_STR_MANUFACTURER) - 2) + +static const struct usb_string_descriptor cdc_string_manufacturer PROGMEM = { + .bLength = SIZE_cdc_string_manufacturer, + .bDescriptorType = USB_DT_STRING, + .data = USB_STR_MANUFACTURER, +}; + +#define SIZE_cdc_string_product \ + (sizeof(cdc_string_product) + sizeof(USB_STR_PRODUCT) - 2) + +static const struct usb_string_descriptor cdc_string_product PROGMEM = { + .bLength = SIZE_cdc_string_product, + .bDescriptorType = USB_DT_STRING, + .data = USB_STR_PRODUCT, +}; + +#define SIZE_cdc_string_serial \ + (sizeof(cdc_string_serial) + sizeof(USB_STR_SERIAL) - 2) + +static const struct usb_string_descriptor cdc_string_serial PROGMEM = { + .bLength = SIZE_cdc_string_serial, + .bDescriptorType = USB_DT_STRING, + .data = USB_STR_SERIAL, +}; + +// Device descriptor +static const struct usb_device_descriptor cdc_device_descriptor PROGMEM = { + .bLength = sizeof(cdc_device_descriptor), + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_COMM, + .bMaxPacketSize0 = USB_CDC_EP0_SIZE, + .idVendor = cpu_to_le16(CONFIG_USB_VENDOR_ID), + .idProduct = cpu_to_le16(CONFIG_USB_PRODUCT_ID), + .bcdDevice = cpu_to_le16(0x0100), + .iManufacturer = USB_STR_ID_MANUFACTURER, + .iProduct = USB_STR_ID_PRODUCT, + .iSerialNumber = USB_STR_ID_SERIAL, + .bNumConfigurations = 1, +}; + +// Config descriptor +static const struct config_s { + struct usb_config_descriptor config; + struct usb_interface_descriptor iface0; + struct usb_cdc_header_descriptor cdc_hdr; + struct usb_cdc_acm_descriptor cdc_acm; + struct usb_cdc_union_descriptor cdc_union; + struct usb_endpoint_descriptor ep1; + struct usb_interface_descriptor iface1; + struct usb_endpoint_descriptor ep2; + struct usb_endpoint_descriptor ep3; +} PACKED cdc_config_descriptor PROGMEM = { + .config = { + .bLength = sizeof(cdc_config_descriptor.config), + .bDescriptorType = USB_DT_CONFIG, + .wTotalLength = cpu_to_le16(sizeof(cdc_config_descriptor)), + .bNumInterfaces = 2, + .bConfigurationValue = 1, + .bmAttributes = 0xC0, + .bMaxPower = 50, + }, + .iface0 = { + .bLength = sizeof(cdc_config_descriptor.iface0), + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, + .bInterfaceProtocol = USB_CDC_ACM_PROTO_AT_V25TER, + }, + .cdc_hdr = { + .bLength = sizeof(cdc_config_descriptor.cdc_hdr), + .bDescriptorType = USB_CDC_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + .bcdCDC = 0x0110, + }, + .cdc_acm = { + .bLength = sizeof(cdc_config_descriptor.cdc_acm), + .bDescriptorType = USB_CDC_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ACM_TYPE, + .bmCapabilities = 0x06, + }, + .cdc_union = { + .bLength = sizeof(cdc_config_descriptor.cdc_union), + .bDescriptorType = USB_CDC_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + .bMasterInterface0 = 0, + .bSlaveInterface0 = 1, + }, + .ep1 = { + .bLength = sizeof(cdc_config_descriptor.ep1), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_CDC_EP_ACM | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(USB_CDC_EP_ACM_SIZE), + .bInterval = 255, + }, + .iface1 = { + .bLength = sizeof(cdc_config_descriptor.iface1), + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 1, + .bNumEndpoints = 2, + .bInterfaceClass = 0x0A, + }, + .ep2 = { + .bLength = sizeof(cdc_config_descriptor.ep2), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_CDC_EP_BULK_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(USB_CDC_EP_BULK_OUT_SIZE), + }, + .ep3 = { + .bLength = sizeof(cdc_config_descriptor.ep3), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_CDC_EP_BULK_IN | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(USB_CDC_EP_BULK_IN_SIZE), + }, +}; + +// List of available descriptors +static const struct descriptor_s { + uint_fast16_t wValue; + uint_fast16_t wIndex; + const void *desc; + uint_fast8_t size; +} cdc_descriptors[] PROGMEM = { + { USB_DT_DEVICE<<8, 0x0000, + &cdc_device_descriptor, sizeof(cdc_device_descriptor) }, + { USB_DT_CONFIG<<8, 0x0000, + &cdc_config_descriptor, sizeof(cdc_config_descriptor) }, + { USB_DT_STRING<<8, 0x0000, + &cdc_string_langids, SIZE_cdc_string_langids }, + { (USB_DT_STRING<<8) | USB_STR_ID_MANUFACTURER, USB_LANGID_ENGLISH_US, + &cdc_string_manufacturer, SIZE_cdc_string_manufacturer }, + { (USB_DT_STRING<<8) | USB_STR_ID_PRODUCT, USB_LANGID_ENGLISH_US, + &cdc_string_product, SIZE_cdc_string_product }, + { (USB_DT_STRING<<8) | USB_STR_ID_SERIAL, USB_LANGID_ENGLISH_US, + &cdc_string_serial, SIZE_cdc_string_serial }, +}; + + +/**************************************************************** + * USB endpoint 0 control message handling + ****************************************************************/ + +// State tracking +enum { + US_READY, US_SEND, US_READ +}; + +static uint_fast8_t usb_state; +static void *usb_xfer; +static uint_fast8_t usb_xfer_size; + +static void +usb_do_stall(void) +{ + usb_set_stall(); + usb_state = US_READY; +} + +// Sending data from device to host +static void +usb_state_xfer(void) +{ + for (;;) { + uint_fast8_t xs = usb_xfer_size; + if (xs > USB_CDC_EP0_SIZE) + xs = USB_CDC_EP0_SIZE; + int_fast8_t ret; + if (usb_state == US_SEND) + ret = usb_send_setup(usb_xfer, xs); + else + ret = usb_read_setup(usb_xfer, xs); + if (ret == xs) { + // Success + usb_xfer += xs; + usb_xfer_size -= xs; + if (!usb_xfer_size && xs < USB_CDC_EP0_SIZE) { + // Transfer completed successfully + if (usb_state == US_READ) + usb_send_setup(NULL, 0); + usb_state = US_READY; + return; + } + continue; + } + if (ret == -1) + // Interface busy - retry later + return; + // Error + usb_do_stall(); + return; + } +} + +static void +usb_req_get_descriptor(struct usb_ctrlrequest *req) +{ + // XXX - validate req + uint_fast8_t i; + for (i=0; i<ARRAY_SIZE(cdc_descriptors); i++) { + const struct descriptor_s *d = &cdc_descriptors[i]; + if (READP(d->wValue) == req->wValue + && READP(d->wIndex) == req->wIndex) { + usb_state = US_SEND; + usb_xfer = (void*)READP(d->desc); + usb_xfer_size = READP(d->size); + if (usb_xfer_size > req->wLength) + usb_xfer_size = req->wLength; + usb_state_xfer(); + return; + } + } + usb_do_stall(); +} + +static void +usb_req_set_address(struct usb_ctrlrequest *req) +{ + usb_set_address(req->wValue); +} + +static void +usb_req_set_configuration(struct usb_ctrlrequest *req) +{ + usb_set_configure(); + usb_send_setup(NULL, 0); + usb_notify_bulk_in(); +} + +static struct usb_cdc_line_coding line_coding; + +static void +usb_req_set_line_coding(struct usb_ctrlrequest *req) +{ + usb_state = US_READ; + usb_xfer = &line_coding; + usb_xfer_size = sizeof(line_coding); +} + +static void +usb_req_get_line_coding(struct usb_ctrlrequest *req) +{ + usb_state = US_SEND; + usb_xfer = &line_coding; + usb_xfer_size = sizeof(line_coding); +} + +static void +usb_req_line_state(struct usb_ctrlrequest *req) +{ + usb_send_setup(NULL, 0); +} + +static void +usb_state_ready(void) +{ + struct usb_ctrlrequest req; + int_fast8_t ret = usb_read_setup(&req, sizeof(req)); + if (ret != sizeof(req)) + // XXX - should verify that packet was sent with a setup token + return; + switch (req.bRequest) { + case USB_REQ_GET_DESCRIPTOR: usb_req_get_descriptor(&req); break; + case USB_REQ_SET_ADDRESS: usb_req_set_address(&req); break; + case USB_REQ_SET_CONFIGURATION: usb_req_set_configuration(&req); break; + case USB_CDC_REQ_SET_LINE_CODING: usb_req_set_line_coding(&req); break; + case USB_CDC_REQ_GET_LINE_CODING: usb_req_get_line_coding(&req); break; + case USB_CDC_REQ_SET_CONTROL_LINE_STATE: usb_req_line_state(&req); break; + default: usb_do_stall(); break; + } +} + +// State tracking dispatch +static struct task_wake usb_setup_wake; + +void +usb_notify_setup(void) +{ + sched_wake_task(&usb_setup_wake); +} + +void +usb_setup_task(void) +{ + if (!sched_check_wake(&usb_setup_wake)) + return; + switch (usb_state) { + case US_READY: usb_state_ready(); break; + case US_SEND: usb_state_xfer(); break; + case US_READ: usb_state_xfer(); break; + } +} +DECL_TASK(usb_setup_task); + +void +usb_shutdown(void) +{ + usb_notify_bulk_in(); + usb_notify_setup(); +} +DECL_SHUTDOWN(usb_shutdown); diff --git a/src/generic/usb_cdc.h b/src/generic/usb_cdc.h new file mode 100644 index 00000000..c5450bfa --- /dev/null +++ b/src/generic/usb_cdc.h @@ -0,0 +1,35 @@ +#ifndef __GENERIC_USB_CDC_H +#define __GENERIC_USB_CDC_H + +#include <stdint.h> // uint_fast8_t + +enum { + USB_CDC_EP0_SIZE = 16, + + // XXX - endpoint ids may need to changed per-board + USB_CDC_EP_ACM = 1, + USB_CDC_EP_ACM_SIZE = 8, + + USB_CDC_EP_BULK_OUT = 2, + USB_CDC_EP_BULK_OUT_SIZE = 64, + + USB_CDC_EP_BULK_IN = 5, + USB_CDC_EP_BULK_IN_SIZE = 64, +}; + +// callbacks provided by board specific code +int_fast8_t usb_read_bulk_out(void *data, uint_fast8_t max_len); +int_fast8_t usb_send_bulk_in(void *data, uint_fast8_t len); +int_fast8_t usb_read_setup(void *data, uint_fast8_t max_len); +int_fast8_t usb_send_setup(const void *data, uint_fast8_t len); +void usb_send_pgm_setup(void *data, uint_fast8_t len); +void usb_set_stall(void); +void usb_set_address(uint_fast8_t addr); +void usb_set_configure(void); + +// usb_cdc.c +void usb_notify_bulk_in(void); +void usb_notify_bulk_out(void); +void usb_notify_setup(void); + +#endif // usb_cdc.h diff --git a/src/generic/usbstd.h b/src/generic/usbstd.h new file mode 100644 index 00000000..86199992 --- /dev/null +++ b/src/generic/usbstd.h @@ -0,0 +1,121 @@ +// Standard definitions for USB commands and data structures +#ifndef __GENERIC_USBSTD_H +#define __GENERIC_USBSTD_H + +#include <stdint.h> // uint8_t +#include "compiler.h" // PACKED + +#define USB_DIR_OUT 0 /* to device */ +#define USB_DIR_IN 0x80 /* to host */ + +#define USB_REQ_GET_STATUS 0x00 +#define USB_REQ_CLEAR_FEATURE 0x01 +#define USB_REQ_SET_FEATURE 0x03 +#define USB_REQ_SET_ADDRESS 0x05 +#define USB_REQ_GET_DESCRIPTOR 0x06 +#define USB_REQ_SET_DESCRIPTOR 0x07 +#define USB_REQ_GET_CONFIGURATION 0x08 +#define USB_REQ_SET_CONFIGURATION 0x09 +#define USB_REQ_GET_INTERFACE 0x0A +#define USB_REQ_SET_INTERFACE 0x0B +#define USB_REQ_SYNCH_FRAME 0x0C + +struct usb_ctrlrequest { + uint8_t bRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; +} PACKED; + +#define USB_DT_DEVICE 0x01 +#define USB_DT_CONFIG 0x02 +#define USB_DT_STRING 0x03 +#define USB_DT_INTERFACE 0x04 +#define USB_DT_ENDPOINT 0x05 +#define USB_DT_DEVICE_QUALIFIER 0x06 +#define USB_DT_OTHER_SPEED_CONFIG 0x07 +#define USB_DT_ENDPOINT_COMPANION 0x30 + +struct usb_device_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + + 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; +} PACKED; + +#define USB_CLASS_PER_INTERFACE 0 /* for DeviceClass */ +#define USB_CLASS_AUDIO 1 +#define USB_CLASS_COMM 2 +#define USB_CLASS_HID 3 +#define USB_CLASS_PHYSICAL 5 +#define USB_CLASS_STILL_IMAGE 6 +#define USB_CLASS_PRINTER 7 +#define USB_CLASS_MASS_STORAGE 8 +#define USB_CLASS_HUB 9 + +struct usb_config_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + + uint16_t wTotalLength; + uint8_t bNumInterfaces; + uint8_t bConfigurationValue; + uint8_t iConfiguration; + uint8_t bmAttributes; + uint8_t bMaxPower; +} PACKED; + +struct usb_interface_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; +} PACKED; + +struct usb_endpoint_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + + uint8_t bEndpointAddress; + uint8_t bmAttributes; + uint16_t wMaxPacketSize; + uint8_t bInterval; +} PACKED; + +#define USB_ENDPOINT_NUMBER_MASK 0x0f /* in bEndpointAddress */ +#define USB_ENDPOINT_DIR_MASK 0x80 + +#define USB_ENDPOINT_XFERTYPE_MASK 0x03 /* in bmAttributes */ +#define USB_ENDPOINT_XFER_CONTROL 0 +#define USB_ENDPOINT_XFER_ISOC 1 +#define USB_ENDPOINT_XFER_BULK 2 +#define USB_ENDPOINT_XFER_INT 3 +#define USB_ENDPOINT_MAX_ADJUSTABLE 0x80 + +struct usb_string_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t data[]; +} PACKED; + +#define USB_LANGID_ENGLISH_US 0x0409 + +#endif // usbstd.h diff --git a/src/generic/usbstd_cdc.h b/src/generic/usbstd_cdc.h new file mode 100644 index 00000000..e6873a31 --- /dev/null +++ b/src/generic/usbstd_cdc.h @@ -0,0 +1,49 @@ +// Standard definitions for USB CDC devices +#ifndef __GENERIC_USBSTD_CDC_H +#define __GENERIC_USBSTD_CDC_H + +#define USB_CDC_SUBCLASS_ACM 0x02 + +#define USB_CDC_ACM_PROTO_AT_V25TER 1 + +struct usb_cdc_header_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint16_t bcdCDC; +} PACKED; + +#define USB_CDC_HEADER_TYPE 0x00 +#define USB_CDC_ACM_TYPE 0x02 +#define USB_CDC_UNION_TYPE 0x06 + +#define USB_CDC_CS_INTERFACE 0x24 +#define USB_CDC_CS_ENDPOINT 0x25 + +struct usb_cdc_acm_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bmCapabilities; +} PACKED; + +struct usb_cdc_union_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bMasterInterface0; + uint8_t bSlaveInterface0; +} PACKED; + +#define USB_CDC_REQ_SET_LINE_CODING 0x20 +#define USB_CDC_REQ_GET_LINE_CODING 0x21 +#define USB_CDC_REQ_SET_CONTROL_LINE_STATE 0x22 + +struct usb_cdc_line_coding { + uint32_t dwDTERate; + uint8_t bCharFormat; + uint8_t bParityType; + uint8_t bDataBits; +} PACKED; + +#endif // usbstd_cdc.h |