From 13c0fa252f0b1e16a2f5e4d544da8b062bee49cb Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Mon, 8 Jun 2026 15:28:47 +0200 Subject: [PATCH 1/3] pbio/platform: Enable USB for beta release. This reverts commit 0bd1ca8eb2f9971f4263ce662895ad1268c7ea52. --- lib/pbio/platform/essential_hub/pbdrvconfig.h | 2 +- lib/pbio/platform/prime_hub/pbdrvconfig.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pbio/platform/essential_hub/pbdrvconfig.h b/lib/pbio/platform/essential_hub/pbdrvconfig.h index ccd41e6ff..5becae4eb 100644 --- a/lib/pbio/platform/essential_hub/pbdrvconfig.h +++ b/lib/pbio/platform/essential_hub/pbdrvconfig.h @@ -108,7 +108,7 @@ #define PBDRV_CONFIG_USB_MFG_STR LEGO_USB_MFG_STR #define PBDRV_CONFIG_USB_PROD_STR LEGO_USB_PROD_STR_TECHNIC_SMALL_HUB " + Pybricks" #define PBDRV_CONFIG_USB_STM32F4 (1) -#define PBDRV_CONFIG_USB_CHARGE_ONLY (1) +#define PBDRV_CONFIG_USB_CHARGE_ONLY (0) #define PBDRV_CONFIG_STACK (1) #define PBDRV_CONFIG_STACK_EMBEDDED (1) diff --git a/lib/pbio/platform/prime_hub/pbdrvconfig.h b/lib/pbio/platform/prime_hub/pbdrvconfig.h index f29a1532d..ea16ea21e 100644 --- a/lib/pbio/platform/prime_hub/pbdrvconfig.h +++ b/lib/pbio/platform/prime_hub/pbdrvconfig.h @@ -128,7 +128,7 @@ #define PBDRV_CONFIG_USB_PROD_STR LEGO_USB_PROD_STR_TECHNIC_LARGE_HUB " + Pybricks" #define PBDRV_CONFIG_USB_STM32F4 (1) #define PBDRV_CONFIG_USB_STM32F4_HUB_VARIANT_ADDR 0x08007d80 -#define PBDRV_CONFIG_USB_CHARGE_ONLY (1) +#define PBDRV_CONFIG_USB_CHARGE_ONLY (0) #define PBDRV_CONFIG_STACK (1) #define PBDRV_CONFIG_STACK_EMBEDDED (1) From 34f8acf064b769f239e4931613432e5c19800b0b Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Mon, 15 Jun 2026 10:33:25 +0200 Subject: [PATCH 2/3] poetry: Add pyserial. --- poetry.lock | 17 ++++++++++++++++- pyproject.toml | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 01654b021..7f6861679 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2385,6 +2385,21 @@ files = [ [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pyserial" +version = "3.5" +description = "Python Serial Port Extension" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0"}, + {file = "pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb"}, +] + +[package.extras] +cp2110 = ["hidapi"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -3489,4 +3504,4 @@ propcache = ">=0.2.1" [metadata] lock-version = "2.1" python-versions = ">=3.10,<4.0" -content-hash = "a7130f69ba0247a060870bc3cb21635de623bdd22866218997bcfee49dd5e8a8" +content-hash = "9ff1232a58f0a61a895e31a1505ed639028023b03e25b54e31e1c3a66ad11f13" diff --git a/pyproject.toml b/pyproject.toml index be9eff2bc..abbf9e963 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,7 @@ package-mode = false [tool.poetry.dependencies] python = ">=3.10,<4.0" +pyserial = "^3.5" [tool.poetry.group.dev.dependencies] eventlet = "^0.40.3" From 50bddc14dc0718b92ec67e761da822393d120eca Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 18 Jun 2026 09:22:58 +0200 Subject: [PATCH 3/3] pbio/drv/usb: Switch to serial. The previous WebUSB driver was a promising start, but it was not reliable. Messages could get stuck and the hub would not know if the app was disconnected. Bidirectional traffic was slow and prone to lockups. This commit replaces the USB drivers on all platforms with a standard serial USB device. We will be able to use this with Web Serial on most systems. Instead of manually subscribing and unsubscribing to keep track of the app connection state, we can use the DTR signal which is asserted on connect and deasserted on disconnect, even when the browser tab is abrubtly closed. Since serial is handled by the OS rather than our host application, in-flight messages don't get stuck if the host app is not reading them, which was part of the reason we had lockups before. This should also make it possible to use it with RFCOMM so we can add Bluetooth support for EV3 and NXT with relatively little changes. Finally, it may allow us to align the SPIKE Prime update procedure with the official firmware, for a more streamlined approach. Although the medium is a serial stream, we keep the same packetized event messages as before, much like we do for BLE. Frames are encoded with COBS with a 0x00 delimiter between messages, which makes it easy to get back in sync. The pull request for this change discusses further work. --- lib/pbio/drv/usb/stm32_usbd/usbd_desc.c | 18 +- lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c | 152 +++- lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h | 11 +- lib/pbio/drv/usb/usb.c | 346 ++++++++-- lib/pbio/drv/usb/usb.h | 84 ++- lib/pbio/drv/usb/usb_ch9.h | 186 ++--- lib/pbio/drv/usb/usb_common_desc.c | 86 --- lib/pbio/drv/usb/usb_common_desc.h | 46 -- lib/pbio/drv/usb/usb_ev3.c | 438 ++++++------ lib/pbio/drv/usb/usb_nxt.c | 650 ++++++++---------- lib/pbio/drv/usb/usb_simulation.c | 76 +- lib/pbio/drv/usb/usb_simulation_pico.c | 64 +- lib/pbio/drv/usb/usb_stm32.c | 100 +-- lib/pbio/include/pbio/protocol.h | 82 ++- lib/pbio/platform/build_hat/pbdrvconfig.h | 2 - lib/pbio/platform/essential_hub/pbdrvconfig.h | 2 - lib/pbio/platform/ev3/pbdrvconfig.h | 2 - lib/pbio/platform/nxt/pbdrvconfig.h | 2 - lib/pbio/platform/prime_hub/pbdrvconfig.h | 2 - lib/pbio/platform/virtual_hub/pbdrvconfig.h | 2 - 20 files changed, 1212 insertions(+), 1139 deletions(-) diff --git a/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c b/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c index e3355d9aa..969b3082a 100644 --- a/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c +++ b/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c @@ -82,10 +82,10 @@ pbdrv_usb_dev_desc_union_t USBD_DeviceDesc = { .s = { .bLength = sizeof(pbdrv_usb_dev_desc_t), .bDescriptorType = DESC_TYPE_DEVICE, - .bcdUSB = 0x0210, /* 2.1.0 (for BOS support) */ - .bDeviceClass = PBIO_PYBRICKS_USB_DEVICE_CLASS, - .bDeviceSubClass = PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, - .bDeviceProtocol = PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, + .bcdUSB = 0x0200, /* 2.0.0 */ + .bDeviceClass = USB_CLASS_MISC, + .bDeviceSubClass = USB_MISC_SUBCLASS_COMMON, + .bDeviceProtocol = USB_MISC_PROTOCOL_IAD, .bMaxPacketSize0 = USB_MAX_EP0_SIZE, .idVendor = PBDRV_CONFIG_USB_VID, .idProduct = PBDRV_CONFIG_USB_PID, @@ -221,21 +221,13 @@ static uint8_t *USBD_Pybricks_SerialStrDescriptor(USBD_SpeedTypeDef speed, uint1 return (uint8_t *)USBD_StringSerial; } -static uint8_t *USBD_Pybricks_BOSDescriptor(USBD_SpeedTypeDef speed, uint16_t *length) { - /* Prevent unused argument(s) compilation warning */ - UNUSED(speed); - - *length = sizeof(pbdrv_usb_bos_desc_set.s); - return (uint8_t *)&pbdrv_usb_bos_desc_set; -} - USBD_DescriptorsTypeDef USBD_Pybricks_Desc = { .GetDeviceDescriptor = USBD_Pybricks_DeviceDescriptor, .GetLangIDStrDescriptor = USBD_Pybricks_LangIDStrDescriptor, .GetManufacturerStrDescriptor = USBD_Pybricks_ManufacturerStrDescriptor, .GetProductStrDescriptor = USBD_Pybricks_ProductStrDescriptor, .GetSerialStrDescriptor = USBD_Pybricks_SerialStrDescriptor, - .GetBOSDescriptor = USBD_Pybricks_BOSDescriptor, + .GetBOSDescriptor = NULL, }; void USBD_Pybricks_Desc_Init(void) { diff --git a/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c index f550db0ec..c3c1e5d86 100644 --- a/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c +++ b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c @@ -36,14 +36,10 @@ */ /* Includes ------------------------------------------------------------------*/ -#include -#include - #include "usbd_ctlreq.h" #include "usbd_pybricks.h" #include "../usb_ch9.h" -#include "../usb_common_desc.h" /** @addtogroup STM32_USB_DEVICE_LIBRARY @@ -116,7 +112,14 @@ USBD_ClassTypeDef USBD_Pybricks_ClassDriver = /* USB Pybricks device Configuration Descriptor */ typedef struct PBDRV_PACKED { pbdrv_usb_conf_desc_t conf_desc; - pbdrv_usb_iface_desc_t iface_desc; + pbdrv_usb_iad_desc_t iad; + pbdrv_usb_iface_desc_t comm_iface; + pbdrv_usb_cdc_header_desc_t cdc_header; + pbdrv_usb_cdc_call_mgmt_desc_t cdc_call_mgmt; + pbdrv_usb_cdc_acm_desc_t cdc_acm; + pbdrv_usb_cdc_union_desc_t cdc_union; + pbdrv_usb_ep_desc_t cmd_ep; + pbdrv_usb_iface_desc_t data_iface; pbdrv_usb_ep_desc_t ep_out; pbdrv_usb_ep_desc_t ep_in; } pbdrv_usb_stm32_conf_t; @@ -128,21 +131,80 @@ static pbdrv_usb_stm32_conf_union_t USBD_Pybricks_CfgDesc = { .bLength = sizeof(pbdrv_usb_conf_desc_t), .bDescriptorType = DESC_TYPE_CONFIGURATION, .wTotalLength = sizeof(pbdrv_usb_stm32_conf_t), - .bNumInterfaces = 1, + .bNumInterfaces = 2, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = USB_CONF_DESC_BM_ATTR_MUST_BE_SET, .bMaxPower = 250, /* 500mA (number of 2mA units) */ }, - .iface_desc = { + /* Interface Association: groups the comm and data interfaces into one + * CDC ACM function. */ + .iad = { + .bLength = sizeof(pbdrv_usb_iad_desc_t), + .bDescriptorType = DESC_TYPE_INTERFACE_ASSOCIATION, + .bFirstInterface = 0, + .bInterfaceCount = 2, + .bFunctionClass = USB_CLASS_CDC, + .bFunctionSubClass = USB_CDC_SUBCLASS_ACM, + .bFunctionProtocol = USB_CDC_PROTOCOL_AT, + .iFunction = 0, + }, + /* Communication interface */ + .comm_iface = { .bLength = sizeof(pbdrv_usb_iface_desc_t), .bDescriptorType = DESC_TYPE_INTERFACE, .bInterfaceNumber = 0, .bAlternateSetting = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_CDC, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, + .bInterfaceProtocol = USB_CDC_PROTOCOL_AT, + .iInterface = 0, + }, + .cdc_header = { + .bFunctionLength = sizeof(pbdrv_usb_cdc_header_desc_t), + .bDescriptorType = USB_CDC_CS_INTERFACE, + .bDescriptorSubtype = USB_CDC_FUNC_SUBTYPE_HEADER, + .bcdCDC = 0x0110, + }, + .cdc_call_mgmt = { + .bFunctionLength = sizeof(pbdrv_usb_cdc_call_mgmt_desc_t), + .bDescriptorType = USB_CDC_CS_INTERFACE, + .bDescriptorSubtype = USB_CDC_FUNC_SUBTYPE_CALL_MGMT, + .bmCapabilities = 0x00, + .bDataInterface = 1, + }, + .cdc_acm = { + .bFunctionLength = sizeof(pbdrv_usb_cdc_acm_desc_t), + .bDescriptorType = USB_CDC_CS_INTERFACE, + .bDescriptorSubtype = USB_CDC_FUNC_SUBTYPE_ACM, + .bmCapabilities = 0x02, + }, + .cdc_union = { + .bFunctionLength = sizeof(pbdrv_usb_cdc_union_desc_t), + .bDescriptorType = USB_CDC_CS_INTERFACE, + .bDescriptorSubtype = USB_CDC_FUNC_SUBTYPE_UNION, + .bControlInterface = 0, + .bSubordinateInterface0 = 1, + }, + .cmd_ep = { + .bLength = sizeof(pbdrv_usb_ep_desc_t), + .bDescriptorType = DESC_TYPE_ENDPOINT, + .bEndpointAddress = USBD_PYBRICKS_CMD_EP, + .bmAttributes = PBDRV_USB_EP_TYPE_INTR, + .wMaxPacketSize = USBD_PYBRICKS_CMD_PACKET_SIZE, + .bInterval = 16, + }, + /* Data interface */ + .data_iface = { + .bLength = sizeof(pbdrv_usb_iface_desc_t), + .bDescriptorType = DESC_TYPE_INTERFACE, + .bInterfaceNumber = 1, + .bAlternateSetting = 0, .bNumEndpoints = 2, - .bInterfaceClass = PBIO_PYBRICKS_USB_DEVICE_CLASS, - .bInterfaceSubClass = PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, - .bInterfaceProtocol = PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, .iInterface = 0, }, .ep_out = { @@ -185,12 +247,28 @@ static USBD_StatusTypeDef USBD_Pybricks_Init(USBD_HandleTypeDef *pdev, uint8_t c pdev->pClassData = &hPybricks; + /* Default line coding reported to the host: 115200 baud, 1 stop bit, no + * parity, 8 data bits. This is inert: USB does not transfer data at this + * rate (it is not a real UART). CDC ACM just requires valid line coding to + * be stored and echoed back on GET_LINE_CODING. */ + hPybricks.LineCoding[0] = 0x00; + hPybricks.LineCoding[1] = 0xC2; + hPybricks.LineCoding[2] = 0x01; + hPybricks.LineCoding[3] = 0x00; + hPybricks.LineCoding[4] = 0x00; + hPybricks.LineCoding[5] = 0x00; + hPybricks.LineCoding[6] = 0x08; + hPybricks.CmdOpCode = 0xFFU; + (void)USBD_LL_OpenEP(pdev, USBD_PYBRICKS_IN_EP, USBD_EP_TYPE_BULK, USBD_PYBRICKS_MAX_PACKET_SIZE); pdev->ep_in[USBD_PYBRICKS_IN_EP & 0xFU].is_used = 1U; (void)USBD_LL_OpenEP(pdev, USBD_PYBRICKS_OUT_EP, USBD_EP_TYPE_BULK, USBD_PYBRICKS_MAX_PACKET_SIZE); pdev->ep_out[USBD_PYBRICKS_OUT_EP & 0xFU].is_used = 1U; + (void)USBD_LL_OpenEP(pdev, USBD_PYBRICKS_CMD_EP, USBD_EP_TYPE_INTR, USBD_PYBRICKS_CMD_PACKET_SIZE); + pdev->ep_in[USBD_PYBRICKS_CMD_EP & 0xFU].is_used = 1U; + /* Init physical Interface components */ ((USBD_Pybricks_ItfTypeDef *)pdev->pUserData[pdev->classId])->Init(); @@ -217,6 +295,10 @@ static USBD_StatusTypeDef USBD_Pybricks_DeInit(USBD_HandleTypeDef *pdev, uint8_t (void)USBD_LL_CloseEP(pdev, USBD_PYBRICKS_OUT_EP); pdev->ep_out[USBD_PYBRICKS_OUT_EP & 0xFU].is_used = 0U; + /* Close command EP */ + (void)USBD_LL_CloseEP(pdev, USBD_PYBRICKS_CMD_EP); + pdev->ep_in[USBD_PYBRICKS_CMD_EP & 0xFU].is_used = 0U; + /* DeInit physical Interface components */ if (pdev->pClassData != NULL) { ((USBD_Pybricks_ItfTypeDef *)pdev->pUserData[pdev->classId])->DeInit(); @@ -235,6 +317,7 @@ static USBD_StatusTypeDef USBD_Pybricks_DeInit(USBD_HandleTypeDef *pdev, uint8_t */ static USBD_StatusTypeDef USBD_Pybricks_Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req) { + USBD_Pybricks_HandleTypeDef *hPybricks = pdev->pClassData; uint8_t ifalt = 0U; uint16_t status_info = 0U; USBD_StatusTypeDef ret = USBD_OK; @@ -242,24 +325,37 @@ static USBD_StatusTypeDef USBD_Pybricks_Setup(USBD_HandleTypeDef *pdev, switch (req->bmRequest & USB_REQ_TYPE_MASK) { case USB_REQ_TYPE_CLASS: - ret = ((USBD_Pybricks_ItfTypeDef *)pdev->pUserData[pdev->classId])->ReadCharacteristic(pdev, req); - break; - - case USB_REQ_TYPE_VENDOR: + if (hPybricks == NULL) { + USBD_CtlError(pdev, req); + ret = USBD_FAIL; + break; + } switch (req->bRequest) { - case PBDRV_USB_VENDOR_REQ_MS_20: - (void)USBD_CtlSendData(pdev, - (uint8_t *)&pbdrv_usb_ms_20_desc_set, - MIN(sizeof(pbdrv_usb_ms_20_desc_set.s), req->wLength)); + case USB_CDC_REQ_SET_LINE_CODING: + /* Receive the line coding into the handle. The value is + * accepted but not acted on (the link is not a real UART). */ + if (req->wLength == USB_CDC_LINE_CODING_SIZE) { + hPybricks->CmdOpCode = (uint8_t)req->bRequest; + hPybricks->CmdLength = USB_CDC_LINE_CODING_SIZE; + (void)USBD_CtlPrepareRx(pdev, hPybricks->LineCoding, USB_CDC_LINE_CODING_SIZE); + } else { + USBD_CtlError(pdev, req); + ret = USBD_FAIL; + } break; - case PBDRV_USB_VENDOR_REQ_WEBUSB: - if ((req->wValue == PBDRV_USB_WEBUSB_LANDING_PAGE_URL_IDX) && (req->wIndex == WEBUSB_REQ_GET_URL)) { - (void)USBD_CtlSendData(pdev, - (uint8_t *)&pbdrv_usb_webusb_landing_page, - MIN(pbdrv_usb_webusb_landing_page.s.bLength, req->wLength)); - } + case USB_CDC_REQ_GET_LINE_CODING: + (void)USBD_CtlSendData(pdev, hPybricks->LineCoding, + MIN(USB_CDC_LINE_CODING_SIZE, req->wLength)); + break; + + case USB_CDC_REQ_SET_CONTROL_LINE_STATE: + /* The DTR bit indicates whether a host application has + * opened the port. This is the USB analog of a BLE host + * subscribing to notifications. */ + ((USBD_Pybricks_ItfTypeDef *)pdev->pUserData[pdev->classId])->SetControlLineState( + (req->wValue & USB_CDC_CONTROL_LINE_STATE_DTR) != 0U); break; default: @@ -388,6 +484,14 @@ static USBD_StatusTypeDef USBD_Pybricks_DataOut(USBD_HandleTypeDef *pdev, uint8_ * @retval status */ static USBD_StatusTypeDef USBD_Pybricks_EP0_RxReady(USBD_HandleTypeDef *pdev) { + USBD_Pybricks_HandleTypeDef *hPybricks = pdev->pClassData; + + if (hPybricks != NULL && hPybricks->CmdOpCode != 0xFFU) { + /* SET_LINE_CODING data has been received into hPybricks->LineCoding. + * Nothing to do; the value is stored for GET_LINE_CODING. */ + hPybricks->CmdOpCode = 0xFFU; + } + return USBD_OK; } diff --git a/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h index c040d9813..2939c9231 100644 --- a/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h +++ b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h @@ -25,8 +25,12 @@ extern "C" { #endif /* Includes ------------------------------------------------------------------*/ +#include + #include "usbd_ioreq.h" +#include "../usb_ch9.h" + /** @addtogroup STM32_USB_DEVICE_LIBRARY * @{ */ @@ -38,8 +42,10 @@ extern "C" { #define USBD_PYBRICKS_IN_EP 0x81U /* EP1 for data IN */ #define USBD_PYBRICKS_OUT_EP 0x01U /* EP1 for data OUT */ +#define USBD_PYBRICKS_CMD_EP 0x82U /* EP2 for CDC notifications */ #define USBD_PYBRICKS_MAX_PACKET_SIZE 64U +#define USBD_PYBRICKS_CMD_PACKET_SIZE 8U /** * @} @@ -59,7 +65,7 @@ typedef struct USBD_StatusTypeDef (*DeInit)(void); USBD_StatusTypeDef (*Receive)(uint8_t *Buf, uint32_t Len); USBD_StatusTypeDef (*TransmitCplt)(uint8_t *Buf, uint32_t Len, uint8_t epnum); - USBD_StatusTypeDef (*ReadCharacteristic)(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req); + USBD_StatusTypeDef (*SetControlLineState)(bool dtr); } USBD_Pybricks_ItfTypeDef; @@ -69,6 +75,9 @@ typedef struct uint8_t *TxBuffer; uint32_t RxLength; uint32_t TxLength; + uint8_t CmdOpCode; + uint8_t CmdLength; + uint8_t LineCoding[USB_CDC_LINE_CODING_SIZE]; } USBD_Pybricks_HandleTypeDef; diff --git a/lib/pbio/drv/usb/usb.c b/lib/pbio/drv/usb/usb.c index fdc0807fa..8bb70cdbe 100644 --- a/lib/pbio/drv/usb/usb.c +++ b/lib/pbio/drv/usb/usb.c @@ -23,25 +23,109 @@ #include +#include + +#include + +#include + +#include +#include + static pbio_util_void_callback_t pbdrv_usb_host_connection_changed_callback; void pbdrv_usb_set_host_connection_changed_callback(pbio_util_void_callback_t callback) { pbdrv_usb_host_connection_changed_callback = callback; } + /** - * Host is subscribed to our outgoing event messages. + * Whether the host has opened the serial port (DTR asserted). This is the USB + * analog of a BLE host subscribing to notifications. */ -static bool pbdrv_usb_events_subscribed; +static bool pbdrv_usb_dtr; -static void pbdrv_usb_events_subscribed_set(bool subscribed) { - pbdrv_usb_events_subscribed = subscribed; - if (pbdrv_usb_host_connection_changed_callback) { - pbdrv_usb_host_connection_changed_callback(); +bool pbdrv_usb_connection_is_active(void) { + return pbdrv_usb_is_ready() && pbdrv_usb_dtr; +} + +// +// COBS (Consistent Overhead Byte Stuffing) framing. +// +// The host to hub and hub to host directions are raw byte streams (CDC ACM / +// Web Serial), so messages are delimited with a zero byte and COBS-encoded to +// guarantee the payload itself never contains a zero. This is self +// synchronizing: a corrupt or oversized frame is discarded at the next +// delimiter without losing frame alignment. +// +// The delimiter, size macros and the encode/decode helpers live in usb.h so +// the mock simulation drivers can reuse them. +// + +/** + * COBS-encodes @p len bytes from @p src into @p dst and appends the frame + * delimiter. @p dst must have room for at least ::PBDRV_USB_MAX_ENCODED_MESSAGE_SIZE bytes + * when @p len is at most ::PBDRV_USB_MAX_DECODED_MESSAGE_SIZE. + * + * @return Number of bytes written to @p dst, including the trailing delimiter. + */ +uint32_t pbdrv_usb_cobs_encode(const uint8_t *src, uint32_t len, uint8_t *dst) { + uint32_t read_idx = 0; + uint32_t write_idx = 1; + uint32_t code_idx = 0; + uint8_t code = 1; + + while (read_idx < len) { + if (src[read_idx] == 0) { + dst[code_idx] = code; + code = 1; + code_idx = write_idx++; + read_idx++; + } else { + dst[write_idx++] = src[read_idx++]; + if (++code == 0xFF) { + dst[code_idx] = code; + code = 1; + code_idx = write_idx++; + } + } } + + dst[code_idx] = code; + dst[write_idx++] = PBDRV_USB_COBS_DELIMITER; + + return write_idx; } -bool pbdrv_usb_connection_is_active(void) { - return pbdrv_usb_events_subscribed && pbdrv_usb_is_ready(); +/** + * COBS-decodes @p len bytes from @p src (a single frame with the delimiter + * already stripped) into @p dst. + * + * @return Number of decoded bytes, or 0 if the frame was empty or malformed. + */ +uint32_t pbdrv_usb_cobs_decode(const uint8_t *src, uint32_t len, uint8_t *dst, uint32_t dst_max) { + uint32_t read_idx = 0; + uint32_t write_idx = 0; + + while (read_idx < len) { + uint8_t code = src[read_idx++]; + + for (uint8_t i = 1; i < code; i++) { + if (read_idx >= len || write_idx >= dst_max) { + // Malformed frame: ran out of input or output space. + return 0; + } + dst[write_idx++] = src[read_idx++]; + } + + if (code < 0xFF && read_idx < len) { + if (write_idx >= dst_max) { + return 0; + } + dst[write_idx++] = 0; + } + } + + return write_idx; } /** @@ -53,15 +137,66 @@ void pbdrv_usb_set_receive_handler(pbdrv_usb_receive_handler_t handler) { pbdrv_usb_receive_handler = handler; } +/** Maximum number of value bytes in a USB read reply message. */ +#define PBDRV_USB_READ_MAX_PAYLOAD (PBDRV_USB_MAX_DECODED_MESSAGE_SIZE - 4) + +static uint32_t pbdrv_usb_copy_str(uint8_t *buf, const char *str) { + uint32_t size = strlen(str); + if (size > PBDRV_USB_READ_MAX_PAYLOAD) { + size = PBDRV_USB_READ_MAX_PAYLOAD; + } + memcpy(buf, str, size); + return size; +} + +/** + * Provides characteristic values when a USB host requests a read. This is the + * USB analog of a BLE host reading the device info or Pybricks characteristics. + * + * REVISIT: this reaches up into pbsys for the value sources, much like each BLE + * driver still does today. We should invert this so pbsys registers a single read + * handler shared by USB and all BLE drivers. + */ +static uint32_t pbdrv_usb_read_characteristic(uint8_t service, uint16_t char_id, uint8_t *buf) { + switch (service) { + case PBIO_PYBRICKS_USB_INTERFACE_READ_CHARACTERISTIC_GATT: + switch (char_id) { + case 0x2A00: // Device Name + return pbdrv_usb_copy_str(buf, pbdrv_bluetooth_get_hub_name()); + case 0x2A26: // Firmware Revision + return pbdrv_usb_copy_str(buf, PBIO_VERSION_STR); + case 0x2A28: // Software Revision + return pbdrv_usb_copy_str(buf, PBIO_PROTOCOL_VERSION_STR); + default: + return 0; + } + case PBIO_PYBRICKS_USB_INTERFACE_READ_CHARACTERISTIC_PYBRICKS: + switch (char_id) { + case 0x0003: // Hub capabilities + pbio_pybricks_hub_capabilities(buf, + PBDRV_USB_MAX_DECODED_MESSAGE_SIZE - 1, + PBSYS_CONFIG_APP_FEATURE_FLAGS, + pbsys_storage_get_maximum_program_size(), + PBSYS_CONFIG_HMI_NUM_SLOTS); + return PBIO_PYBRICKS_HUB_CAPABILITIES_VALUE_SIZE; + default: + return 0; + } + default: + return 0; + } +} + // // Functions related to sending value notifications (stdout, status, app data). // /** - * Each event has a buffer of maximum packet size. They are prioritized by - * ascending event number, so status gets sent first, then stdout, etc. + * Each event has a buffer sized to the maximum host notification. They are + * prioritized by ascending event number, so status gets sent first, then + * stdout, etc. */ -static uint8_t pbdrv_usb_noti_buf[PBIO_PYBRICKS_EVENT_NUM_EVENTS][PBDRV_CONFIG_USB_MAX_PACKET_SIZE] __attribute__((aligned(4))); +static uint8_t pbdrv_usb_noti_buf[PBIO_PYBRICKS_EVENT_NUM_EVENTS][PBSYS_CONFIG_HOST_NOTIFICATION_SIZE] __attribute__((aligned(4))); static uint32_t pbdrv_usb_noti_size[PBIO_PYBRICKS_EVENT_NUM_EVENTS]; /** @@ -70,6 +205,41 @@ static uint32_t pbdrv_usb_noti_size[PBIO_PYBRICKS_EVENT_NUM_EVENTS]; static uint8_t pbdrv_usb_status_data[PBIO_PYBRICKS_EVENT_STATUS_REPORT_SIZE]; static bool pbdrv_usb_status_data_pending; +/** + * Incoming COBS frame assembly buffer (encoded bytes, delimiter excluded). + */ +static uint8_t pbdrv_usb_rx_frame[PBDRV_USB_MAX_ENCODED_MESSAGE_SIZE]; +static uint32_t pbdrv_usb_rx_frame_len; +static bool pbdrv_usb_rx_overflow; + +void pbdrv_usb_on_dtr_changed(bool dtr) { + if (dtr == pbdrv_usb_dtr) { + return; + } + + pbdrv_usb_dtr = dtr; + + // Drop any partially-assembled incoming frame. Otherwise stray bytes from a + // previous port opener (e.g. an OS modem probe like ModemManager, which + // writes AT strings with no frame delimiter) would prepend to and corrupt + // the first real frame of this new connection. + pbdrv_usb_rx_frame_len = 0; + pbdrv_usb_rx_overflow = false; + + if (dtr) { + // Host just opened the port. Send the current status right away, like + // the first notification after a BLE host subscribes. Device info is + // not pushed; the host reads it on demand via read requests. + pbdrv_usb_status_data_pending = true; + } + + if (pbdrv_usb_host_connection_changed_callback) { + pbdrv_usb_host_connection_changed_callback(); + } + + pbio_os_request_poll(); +} + void pbdrv_usb_schedule_status_update(const uint8_t *status_msg) { // Ignore if message identical to last. if (!memcmp(pbdrv_usb_status_data, status_msg, sizeof(pbdrv_usb_status_data))) { @@ -211,53 +381,105 @@ static bool update_and_get_event_buffer(uint8_t **buf, uint32_t **len) { return false; } +/** + * Pending command response to the most recently received command. + * + * The host keeps a single command outstanding at a time (like a BLE write with + * response), so a single command response slot is sufficient. + */ +static uint8_t pbdrv_usb_command_response_buf[sizeof(uint32_t) + 1] = { + [0] = PBIO_PYBRICKS_IN_EP_MSG_RESPONSE, +}; +static bool pbdrv_usb_command_response_pending; +/** + * Pending reply to the most recently received read request. + * + * Like responses, the host keeps a single read outstanding at a time, so a + * single reply slot is sufficient. The buffer holds the full message + * `[READ_REPLY, service, char_id_lo, char_id_hi, value...]`. + */ +static uint8_t pbdrv_usb_read_reply_buf[PBDRV_USB_MAX_DECODED_MESSAGE_SIZE]; +static uint32_t pbdrv_usb_read_reply_len; +static bool pbdrv_usb_read_reply_pending; -static bool pbdrv_usb_respond_soon; -static pbio_pybricks_error_t pbdrv_usb_respond_result; +/** Number of header bytes before the value in a read reply. */ +#define PBDRV_USB_READ_REPLY_HEADER_SIZE 4 /** - * Non-blocking poll handler to process pending incoming messages. + * Non-blocking poll handler to process incoming bytes. + * + * Bytes are assembled into COBS frames. Each completed frame is decoded into a + * host-to-hub message whose first byte selects the type: a command is handled + * synchronously and its result queued as a response, while a read request is + * answered synchronously and queued as a read reply. This never depends on the + * transmit state, so it cannot deadlock. */ static void pbdrv_usb_handle_data_in(void) { - // Ignore incoming data if we haven't sent our previous response yet. - if (pbdrv_usb_respond_soon) { - return; - } - - // Data is copied here so the driver can immediately clear it and queue - // the next receive. - uint8_t data_in[PBDRV_CONFIG_USB_MAX_PACKET_SIZE]; + // Bytes are copied here so the driver can immediately queue the next + // receive. Only the single USB process thread runs this, so a static + // scratch buffer is safe and keeps the worst-case packet off the stack. + static uint8_t data_in[PBDRV_USB_RX_PACKET_MAX_SIZE]; uint32_t size = pbdrv_usb_get_data_and_start_receive(data_in); - // Expecting at least EP_MSG and payload. - if (size < 2) { - return; - } + for (uint32_t i = 0; i < size; i++) { + uint8_t byte = data_in[i]; + + if (byte != PBDRV_USB_COBS_DELIMITER) { + if (pbdrv_usb_rx_frame_len < sizeof(pbdrv_usb_rx_frame)) { + pbdrv_usb_rx_frame[pbdrv_usb_rx_frame_len++] = byte; + } else { + // Frame too big. Discard until the next delimiter resyncs us. + pbdrv_usb_rx_overflow = true; + } + continue; + } - switch (data_in[0]) { - case PBIO_PYBRICKS_OUT_EP_MSG_SUBSCRIBE: - pbdrv_usb_events_subscribed_set(data_in[1]); - pbdrv_usb_respond_result = PBIO_PYBRICKS_ERROR_OK; - pbdrv_usb_respond_soon = true; - - // Schedule sending current status immediately after subscribing. - pbdrv_usb_status_data_pending = true; - break; - case PBIO_PYBRICKS_OUT_EP_MSG_COMMAND: - if (pbdrv_usb_receive_handler) { - pbdrv_usb_respond_result = pbdrv_usb_receive_handler(data_in + 1, size - 1); - pbdrv_usb_respond_soon = true; + // Delimiter reached: end of frame. + if (!pbdrv_usb_rx_overflow && pbdrv_usb_rx_frame_len > 0) { + uint8_t msg[PBDRV_USB_MAX_DECODED_MESSAGE_SIZE]; + uint32_t msg_size = pbdrv_usb_cobs_decode( + pbdrv_usb_rx_frame, pbdrv_usb_rx_frame_len, msg, sizeof(msg)); + + // The first byte is the host-to-hub message type and the rest is + // its payload. + if (msg_size >= 2 && msg[0] == PBIO_PYBRICKS_OUT_EP_MSG_COMMAND && pbdrv_usb_receive_handler) { + // Compute the response synchronously and queue it immediately. + pbio_set_uint32_le(&pbdrv_usb_command_response_buf[1], + pbdrv_usb_receive_handler(&msg[1], msg_size - 1)); + pbdrv_usb_command_response_pending = true; + pbio_os_request_poll(); + } else if (msg_size >= 4 && msg[0] == PBIO_PYBRICKS_OUT_EP_MSG_READ) { + // A read request is [type, service, char_id_lo, char_id_hi]. + // Read the value synchronously and queue the reply, echoing the + // selector so the host can correlate it. + uint8_t service = msg[1]; + uint16_t char_id = pbio_get_uint16_le(&msg[2]); + pbdrv_usb_read_reply_buf[0] = PBIO_PYBRICKS_IN_EP_MSG_READ_REPLY; + pbdrv_usb_read_reply_buf[1] = service; + pbdrv_usb_read_reply_buf[2] = msg[2]; + pbdrv_usb_read_reply_buf[3] = msg[3]; + uint32_t value_size = pbdrv_usb_read_characteristic( + service, char_id, &pbdrv_usb_read_reply_buf[PBDRV_USB_READ_REPLY_HEADER_SIZE]); + pbdrv_usb_read_reply_len = PBDRV_USB_READ_REPLY_HEADER_SIZE + value_size; + pbdrv_usb_read_reply_pending = true; + pbio_os_request_poll(); } - break; + } + + pbdrv_usb_rx_frame_len = 0; + pbdrv_usb_rx_overflow = false; } } static void pbdrv_usb_reset_state(void) { - pbdrv_usb_events_subscribed_set(false); - pbdrv_usb_respond_soon = false; + pbdrv_usb_on_dtr_changed(false); + pbdrv_usb_command_response_pending = false; + pbdrv_usb_read_reply_pending = false; pbdrv_usb_status_data_pending = false; + pbdrv_usb_rx_frame_len = 0; + pbdrv_usb_rx_overflow = false; lwrb_reset(&pbdrv_usb_stdout_ring_buf); memset(pbdrv_usb_noti_size, 0, sizeof(pbdrv_usb_noti_size)); } @@ -271,6 +493,10 @@ static pbio_error_t pbdrv_usb_process_thread(pbio_os_state_t *state, void *conte static uint32_t *noti_size; static uint8_t *noti_buf; + // COBS-encoded frame scratch buffer, populated just before each transmit. + static uint8_t tx_frame[PBDRV_USB_MAX_ENCODED_MESSAGE_SIZE]; + static uint32_t tx_frame_len; + pbio_error_t err; // Runs every time. If there is no connection, there just won't be data. @@ -289,23 +515,34 @@ static pbio_error_t pbdrv_usb_process_thread(pbio_os_state_t *state, void *conte while (pbdrv_usb_process.request != PBIO_OS_PROCESS_REQUEST_TYPE_CANCEL && pbdrv_usb_is_ready()) { - // Find out what we should send, if anything, priotizing response, then - // status, then stdout, then other events. - if (pbdrv_usb_respond_soon) { - // Double buffer response so we're ready for another. - static pbio_pybricks_error_t error_code; - error_code = pbdrv_usb_respond_result; - pbdrv_usb_respond_soon = false; + // Find out what we should send, if anything, prioritizing replies + // to host requests (command response, then read reply), then status, then + // stdout, then other events. Unlike events, replies do not require + // an active connection, since they answer a request that was just + // received. + if (pbdrv_usb_command_response_pending) { + tx_frame_len = pbdrv_usb_cobs_encode(pbdrv_usb_command_response_buf, sizeof(pbdrv_usb_command_response_buf), tx_frame); + + PBIO_OS_AWAIT(state, &sub, err = pbdrv_usb_tx_chunk(&sub, tx_frame, tx_frame_len)); + pbdrv_usb_command_response_pending = false; + if (err != PBIO_SUCCESS) { + pbdrv_usb_reset_state(); + PBIO_OS_AWAIT(state, &sub, pbdrv_usb_tx_reset(&sub)); + } + } else if (pbdrv_usb_read_reply_pending) { + // Reply to a characteristic read request. + tx_frame_len = pbdrv_usb_cobs_encode(pbdrv_usb_read_reply_buf, pbdrv_usb_read_reply_len, tx_frame); - // Send the response. - PBIO_OS_AWAIT(state, &sub, err = pbdrv_usb_tx_response(&sub, error_code)); + PBIO_OS_AWAIT(state, &sub, err = pbdrv_usb_tx_chunk(&sub, tx_frame, tx_frame_len)); + pbdrv_usb_read_reply_pending = false; if (err != PBIO_SUCCESS) { pbdrv_usb_reset_state(); PBIO_OS_AWAIT(state, &sub, pbdrv_usb_tx_reset(&sub)); } } else if (pbdrv_usb_connection_is_active() && update_and_get_event_buffer(¬i_buf, ¬i_size)) { - // Send out pending event if any. - PBIO_OS_AWAIT(state, &sub, err = pbdrv_usb_tx_event(&sub, noti_buf, *noti_size)); + tx_frame_len = pbdrv_usb_cobs_encode(noti_buf, *noti_size, tx_frame); + + PBIO_OS_AWAIT(state, &sub, err = pbdrv_usb_tx_chunk(&sub, tx_frame, tx_frame_len)); *noti_size = 0; if (err != PBIO_SUCCESS) { pbdrv_usb_reset_state(); @@ -336,7 +573,8 @@ void pbdrv_usb_init(void) { pbdrv_usb_noti_buf[i][1] = i; // event type } - static uint8_t stdout_buf[PBDRV_CONFIG_USB_MAX_PACKET_SIZE * PBDRV_CONFIG_USB_NUM_BUFFERED_PACKETS]; + // Hardware agnostic buffer before user print waits. + static uint8_t stdout_buf[512]; lwrb_init(&pbdrv_usb_stdout_ring_buf, stdout_buf, PBIO_ARRAY_SIZE(stdout_buf)); pbio_os_process_start(&pbdrv_usb_process, pbdrv_usb_process_thread, NULL); diff --git a/lib/pbio/drv/usb/usb.h b/lib/pbio/drv/usb/usb.h index 2864675eb..03bbd0a5d 100644 --- a/lib/pbio/drv/usb/usb.h +++ b/lib/pbio/drv/usb/usb.h @@ -18,6 +18,52 @@ #define PBDRV_USB_TRANSMIT_TIMEOUT (500) +/** Frame delimiter byte. */ +#define PBDRV_USB_COBS_DELIMITER 0x00 + +/** + * Maximum size of a host notification, and of any other host-facing message. + * + * This is deliberately decoupled from the USB hardware packet size: it bounds + * how large a single message can be, independent of how the transport splits + * the resulting byte stream into hardware packets. + * + * REVISIT: this is a system-level concept that should eventually live in pbsys + * config and be shared with the BLE transport. + */ +#define PBSYS_CONFIG_HOST_NOTIFICATION_SIZE (62) + +/** Maximum size of a decoded message (message type byte plus payload). */ +#define PBDRV_USB_MAX_DECODED_MESSAGE_SIZE PBSYS_CONFIG_HOST_NOTIFICATION_SIZE + +/** Maximum size of a COBS-encoded frame including the trailing delimiter. */ +#define PBDRV_USB_MAX_ENCODED_MESSAGE_SIZE (PBDRV_USB_MAX_DECODED_MESSAGE_SIZE + PBDRV_USB_MAX_DECODED_MESSAGE_SIZE / 254 + 2) + +/** + * Largest single USB packet any platform delivers on the data OUT endpoint + * (EV3 high-speed bulk). The common driver provides a receive scratch buffer of + * this size; the framing layer then reassembles messages from the raw byte + * stream, which may pack several small frames into one hardware packet. + */ +#define PBDRV_USB_RX_PACKET_MAX_SIZE (512) + +/** + * COBS-encodes @p len bytes from @p src into @p dst and appends the frame + * delimiter. @p dst must have room for at least ::PBDRV_USB_MAX_ENCODED_MESSAGE_SIZE bytes + * when @p len is at most ::PBDRV_USB_MAX_DECODED_MESSAGE_SIZE. + * + * @return Number of bytes written to @p dst, including the trailing delimiter. + */ +uint32_t pbdrv_usb_cobs_encode(const uint8_t *src, uint32_t len, uint8_t *dst); + +/** + * COBS-decodes @p len bytes from @p src (a single frame with the delimiter + * already stripped) into @p dst. + * + * @return Number of decoded bytes, or 0 if the frame was empty or malformed. + */ +uint32_t pbdrv_usb_cobs_decode(const uint8_t *src, uint32_t len, uint8_t *dst, uint32_t dst_max); + /** * Initializes the USB driver on boot. */ @@ -39,52 +85,48 @@ void pbdrv_usb_deinit(void); void pbdrv_usb_deinit_device(void); /** - * Gets most recent incoming message and copies it to provided buffer. + * Gets bytes most recently received on the data OUT endpoint and copies them + * to the provided buffer. * - * The message is then cleared and the driver prepares to read a new message. + * The driver's receive buffer is then cleared and prepared to receive again. * - * @param [in] data Buffer to copy the message to. + * The host to hub direction is a raw byte stream (the message framing is + * handled by the common driver), so the returned bytes are an arbitrary slice + * of that stream, not necessarily a whole message. + * + * @param [in] data Buffer to copy the bytes to. Must be at least + * ::PBDRV_USB_RX_PACKET_MAX_SIZE bytes. * @return Number of bytes copied. Zero means nothing was available. */ uint32_t pbdrv_usb_get_data_and_start_receive(uint8_t *data); /** - * Sends and awaits event message from hub to host via the Pybricks USB interface OUT endpoint. + * Sends and awaits a raw chunk of bytes on the data IN endpoint. * * Driver-specific implementation. Must return within ::PBDRV_USB_TRANSMIT_TIMEOUT. * * The USB process ensures that only one call is made at once. * - * Data must include the endpoint type and event code, so size is at least 2. - * * @param [in] state Protothread state. * @param [in] data Data to send. * @param [in] size Data size. * @return ::PBIO_SUCCESS on completion. - * ::PBIO_ERROR_INVALID_OP if there is no connection. * ::PBIO_ERROR_AGAIN while awaiting. * ::PBIO_ERROR_BUSY if this operation is already ongoing. - * ::PBIO_ERROR_INVALID_ARG if @p size is too large. * ::PBIO_ERROR_TIMEDOUT if the operation was started but could not complete. */ -pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uint32_t size); +pbio_error_t pbdrv_usb_tx_chunk(pbio_os_state_t *state, const uint8_t *data, uint32_t size); /** - * Sends and awaits response to an earlier incoming message. - * - * Driver-specific implementation. Must return within ::PBDRV_USB_TRANSMIT_TIMEOUT. + * Notifies the common driver that the host's serial control line state (DTR) + * changed. Called by the platform driver, typically from interrupt context. * - * The USB process ensures that only one call is made at once. + * DTR asserted means a host application has opened the serial port and is the + * USB analog of a BLE host subscribing to notifications. * - * @param [in] state Protothread state. - * @param [in] code Error code to send. - * @return ::PBIO_SUCCESS on completion. - * ::PBIO_ERROR_INVALID_OP if there is no connection. - * ::PBIO_ERROR_AGAIN while awaiting. - * ::PBIO_ERROR_BUSY if this operation is already ongoing. - * ::PBIO_ERROR_TIMEDOUT if the operation was started but could not complete. + * @param [in] dtr True if DTR is asserted (port open), otherwise false. */ -pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t code); +void pbdrv_usb_on_dtr_changed(bool dtr); /** * Resets the driver transmission state. diff --git a/lib/pbio/drv/usb/usb_ch9.h b/lib/pbio/drv/usb/usb_ch9.h index dcc50f8de..395790763 100644 --- a/lib/pbio/drv/usb/usb_ch9.h +++ b/lib/pbio/drv/usb/usb_ch9.h @@ -80,12 +80,6 @@ PBDRV_USB_TYPE_PUNNING_HELPER(pbdrv_usb_setup_packet) #define DESC_TYPE_DEVICE_QUALIFIER 6 #define DESC_TYPE_OTHER_SPEED_CONFIGURATION 7 #define DESC_TYPE_INTERFACE_POWER 8 -// Binary Object Store, originally from the Wireless USB / USB 3.0 spec -// These descriptors have been backported to USB 2 and are available to -// devices which indicate a bcdDevice of at least 2.1. -// They are used for WebUSB and Microsoft WinUSB driver installation. -#define DESC_TYPE_BOS 15 -#define DESC_TYPE_DEVICE_CAPABILITY 16 // Device descriptor typedef struct PBDRV_PACKED { @@ -170,125 +164,87 @@ typedef struct PBDRV_PACKED { } pbdrv_usb_dev_qualifier_desc_t; PBDRV_USB_TYPE_PUNNING_HELPER(pbdrv_usb_dev_qualifier_desc); -// Binary Object Store -typedef struct PBDRV_PACKED { - uint8_t bLength; - uint8_t bDescriptorType; - uint16_t wTotalLength; - uint8_t bNumDeviceCaps; -} pbdrv_usb_bos_desc_t; - -typedef struct PBDRV_PACKED { - uint32_t u0; - uint16_t u1; - uint16_t u2; - uint8_t u3[8]; -} pbdrv_usb_uuid_t; +// +// USB Communications Device Class (CDC) and Interface Association Descriptor +// (IAD) definitions, used for the CDC-ACM (virtual serial port) function. +// -// Platform-specific capabilities -#define USB_DEVICE_CAPABILITY_TYPE_PLATFORM 5 +// Interface Association Descriptor +#define DESC_TYPE_INTERFACE_ASSOCIATION 11 typedef struct PBDRV_PACKED { uint8_t bLength; uint8_t bDescriptorType; - uint8_t bDevCapabilityType; - uint8_t bReserved; - pbdrv_usb_uuid_t uuid; -} pbdrv_usb_platform_caps_common_t; - + uint8_t bFirstInterface; + uint8_t bInterfaceCount; + uint8_t bFunctionClass; + uint8_t bFunctionSubClass; + uint8_t bFunctionProtocol; + uint8_t iFunction; +} pbdrv_usb_iad_desc_t; + +// Device/function class codes +#define USB_CLASS_MISC 0xEF +#define USB_MISC_SUBCLASS_COMMON 0x02 +#define USB_MISC_PROTOCOL_IAD 0x01 + +#define USB_CLASS_CDC 0x02 +#define USB_CDC_SUBCLASS_ACM 0x02 +#define USB_CDC_PROTOCOL_AT 0x01 +#define USB_CLASS_CDC_DATA 0x0A + +// CDC functional descriptor types and subtypes +#define USB_CDC_CS_INTERFACE 0x24 +#define USB_CDC_CS_ENDPOINT 0x25 +#define USB_CDC_FUNC_SUBTYPE_HEADER 0x00 +#define USB_CDC_FUNC_SUBTYPE_CALL_MGMT 0x01 +#define USB_CDC_FUNC_SUBTYPE_ACM 0x02 +#define USB_CDC_FUNC_SUBTYPE_UNION 0x06 + +// CDC class-specific requests +#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 + +// SET_CONTROL_LINE_STATE wValue bits +#define USB_CDC_CONTROL_LINE_STATE_DTR 0x01 +#define USB_CDC_CONTROL_LINE_STATE_RTS 0x02 + +// Number of bytes in a CDC line coding structure +#define USB_CDC_LINE_CODING_SIZE 7 + +// CDC Header functional descriptor typedef struct PBDRV_PACKED { - pbdrv_usb_platform_caps_common_t hdr; - uint16_t bcdVersion; - uint8_t bVendorCode; - uint8_t iLandingPage; -} pbdrv_usb_webusb_capability_t; - -// {3408b638-09a9-47a0-8bfd-a0768815b665} -#define USB_PLATFORM_CAP_GUID_WEBUSB { \ - .u0 = 0x3408b638, \ - .u1 = 0x09a9, \ - .u2 = 0x47a0, \ - .u3 = {0x8b, 0xfd, 0xa0, 0x76, 0x88, 0x15, 0xb6, 0x65}, \ -} + uint8_t bFunctionLength; + uint8_t bDescriptorType; // CS_INTERFACE + uint8_t bDescriptorSubtype; // HEADER + uint16_t bcdCDC; +} pbdrv_usb_cdc_header_desc_t; +// CDC Call Management functional descriptor typedef struct PBDRV_PACKED { - pbdrv_usb_platform_caps_common_t hdr; - uint32_t dwWindowsVersion; - uint16_t wMSOSDescriptorSetTotalLength; - uint8_t bMS_VendorCode; - uint8_t bAltEnumCode; -} pbdrv_usb_microsoft_20_capability_t; - -// {D8DD60DF-4589-4CC7-9CD2-659D9E648A9F} -#define USB_PLATFORM_CAP_GUID_MS_20 { \ - .u0 = 0xD8DD60DF, \ - .u1 = 0x4589, \ - .u2 = 0x4CC7, \ - .u3 = {0x9C, 0xD2, 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F}, \ -} - -// These are not technically part of USB Chapter 9, but -// we need them and they logically fit with the rest of the structs. - -#define WEBUSB_REQ_GET_URL 2 -#define WEBUSB_DESC_TYPE_URL 3 -#define WEBUSB_URL_SCHEME_HTTP 0 -#define WEBUSB_URL_SCHEME_HTTPS 1 - + uint8_t bFunctionLength; + uint8_t bDescriptorType; // CS_INTERFACE + uint8_t bDescriptorSubtype; // CALL_MGMT + uint8_t bmCapabilities; + uint8_t bDataInterface; +} pbdrv_usb_cdc_call_mgmt_desc_t; + +// CDC Abstract Control Management functional descriptor typedef struct PBDRV_PACKED { - uint8_t bLength; - uint8_t bDescriptorType; - uint8_t bScheme; - uint8_t url[]; -} pbdrv_usb_webusb_url_desc_t; -PBDRV_USB_TYPE_PUNNING_HELPER(pbdrv_usb_webusb_url_desc); - -// Microsoft wIndex values -#define MS_OS_20_DESCRIPTOR_INDEX 7 -#define MS_OS_20_SET_ALT_ENUMERATION 8 - -// Microsoft descriptor types -#define MS_OS_20_SET_HEADER_DESCRIPTOR 0 -#define MS_OS_20_SUBSET_HEADER_CONFIGURATION 1 -#define MS_OS_20_SUBSET_HEADER_FUNCTION 2 -#define MS_OS_20_FEATURE_COMPATBLE_ID 3 -#define MS_OS_20_FEATURE_REG_PROPERTY 4 -#define MS_OS_20_FEATURE_MIN_RESUME_TIME 5 -#define MS_OS_20_FEATURE_MODEL_ID 6 -#define MS_OS_20_FEATURE_CCGP_DEVICE 7 -#define MS_OS_20_FEATURE_VENDOR_REVISION 8 - -// We only need (and thus define) the following descriptor types - -typedef struct PBDRV_PACKED { - uint16_t wLength; - uint16_t wDescriptorType; - uint32_t dwWindowsVersion; - uint16_t wTotalLength; -} pbdrv_usb_ms_20_desc_set_header_t; + uint8_t bFunctionLength; + uint8_t bDescriptorType; // CS_INTERFACE + uint8_t bDescriptorSubtype; // ACM + uint8_t bmCapabilities; +} pbdrv_usb_cdc_acm_desc_t; +// CDC Union functional descriptor typedef struct PBDRV_PACKED { - uint16_t wLength; - uint16_t wDescriptorType; - uint8_t CompatibleID[8]; - uint8_t SubCompatibleID[8]; -} pbdrv_usb_ms_20_compatible_t; - -typedef struct PBDRV_PACKED { - uint16_t wLength; - uint16_t wDescriptorType; - uint16_t wPropertyDataType; -} pbdrv_usb_ms_20_reg_prop_hdr_t; - -#define MS_OS_20_REG_PROP_TYPE_REG_SZ 1 -#define MS_OS_20_REG_PROP_TYPE_REG_EXPAND_SZ 2 -#define MS_OS_20_REG_PROP_TYPE_REG_BINARY 3 -#define MS_OS_20_REG_PROP_TYPE_REG_DWORD_LE 4 -#define MS_OS_20_REG_PROP_TYPE_REG_DWORD_BE 5 -#define MS_OS_20_REG_PROP_TYPE_REG_LINK 6 -#define MS_OS_20_REG_PROP_TYPE_REG_MULTI_SZ 7 - -// This is the minimum Windows version needed to support these descriptors -#define MS_WINDOWS_VERSION_81 0x06030000 + uint8_t bFunctionLength; + uint8_t bDescriptorType; // CS_INTERFACE + uint8_t bDescriptorSubtype; // UNION + uint8_t bControlInterface; + uint8_t bSubordinateInterface0; +} pbdrv_usb_cdc_union_desc_t; #endif // _INTERNAL_PBDRV_USB_CH9_H_ diff --git a/lib/pbio/drv/usb/usb_common_desc.c b/lib/pbio/drv/usb/usb_common_desc.c index edc3e2802..45300078c 100644 --- a/lib/pbio/drv/usb/usb_common_desc.c +++ b/lib/pbio/drv/usb/usb_common_desc.c @@ -2,99 +2,13 @@ // Copyright (c) 2025 The Pybricks Authors // Descriptors which are shared across devices -// (i.e. WebUSB, Microsoft OS descriptors) #include #if PBDRV_CONFIG_USB -#include - #include "usb_common_desc.h" -#if PBIO_VERSION_LEVEL_HEX == 0xA -#define PYBRICKS_CODE_URL "labs.pybricks.com" -#elif PBIO_VERSION_LEVEL_HEX == 0xB -#define PYBRICKS_CODE_URL "beta.pybricks.com" -#else -#define PYBRICKS_CODE_URL "code.pybricks.com" -#endif - -const pbdrv_usb_webusb_url_desc_union_t pbdrv_usb_webusb_landing_page = { - .s = { - .bLength = sizeof(pbdrv_usb_webusb_url_desc_t) + sizeof(PYBRICKS_CODE_URL) - 1, - .bDescriptorType = WEBUSB_DESC_TYPE_URL, - .bScheme = WEBUSB_URL_SCHEME_HTTPS, - .url = PYBRICKS_CODE_URL, - }, -}; - -#define MS_20_REGISTRY_DATA_EXTRA_SZ \ - 2 + sizeof(PBDRV_USB_MS_20_REG_PROPERTY_NAME) + \ - 2 + sizeof(PBDRV_USB_MS_20_REG_PROPERTY_VAL) - -const pbdrv_usb_ms_20_desc_set_union_t pbdrv_usb_ms_20_desc_set = { - .s = { - .desc_set_hdr = { - .wLength = sizeof(pbdrv_usb_ms_20_desc_set_header_t), - .wDescriptorType = MS_OS_20_SET_HEADER_DESCRIPTOR, - .dwWindowsVersion = MS_WINDOWS_VERSION_81, - .wTotalLength = sizeof(pbdrv_usb_ms_20_desc_set_t), - }, - .compatible = { - .wLength = sizeof(pbdrv_usb_ms_20_compatible_t), - .wDescriptorType = MS_OS_20_FEATURE_COMPATBLE_ID, - .CompatibleID = "WINUSB", - .SubCompatibleID = "", - }, - .reg_prop_hdr = { - .wLength = sizeof(pbdrv_usb_ms_20_reg_prop_hdr_t) + MS_20_REGISTRY_DATA_EXTRA_SZ, - .wDescriptorType = MS_OS_20_FEATURE_REG_PROPERTY, - .wPropertyDataType = MS_OS_20_REG_PROP_TYPE_REG_MULTI_SZ, - }, - .property_name_len = sizeof(PBDRV_USB_MS_20_REG_PROPERTY_NAME), - .device_interface_guids = PBDRV_USB_MS_20_REG_PROPERTY_NAME, - .property_val_len = sizeof(PBDRV_USB_MS_20_REG_PROPERTY_VAL), - .device_interface_guid_val = PBDRV_USB_MS_20_REG_PROPERTY_VAL, - } -}; - -const pbdrv_usb_bos_desc_set_union_t pbdrv_usb_bos_desc_set = { - .s = { - .bos = { - .bLength = sizeof(pbdrv_usb_bos_desc_t), - .bDescriptorType = DESC_TYPE_BOS, - .wTotalLength = sizeof(pbdrv_usb_bos_desc_set_t), - .bNumDeviceCaps = 2, - }, - .webusb = { - .hdr = { - .bLength = sizeof(pbdrv_usb_webusb_capability_t), - .bDescriptorType = DESC_TYPE_DEVICE_CAPABILITY, - .bDevCapabilityType = USB_DEVICE_CAPABILITY_TYPE_PLATFORM, - .bReserved = 0, - .uuid = USB_PLATFORM_CAP_GUID_WEBUSB, - }, - .bcdVersion = 0x0100, - .bVendorCode = PBDRV_USB_VENDOR_REQ_WEBUSB, - .iLandingPage = PBDRV_USB_WEBUSB_LANDING_PAGE_URL_IDX, - }, - .ms_20 = { - .hdr = { - .bLength = sizeof(pbdrv_usb_microsoft_20_capability_t), - .bDescriptorType = DESC_TYPE_DEVICE_CAPABILITY, - .bDevCapabilityType = USB_DEVICE_CAPABILITY_TYPE_PLATFORM, - .bReserved = 0, - .uuid = USB_PLATFORM_CAP_GUID_MS_20, - }, - .dwWindowsVersion = MS_WINDOWS_VERSION_81, - .wMSOSDescriptorSetTotalLength = sizeof(pbdrv_usb_ms_20_desc_set_t), - .bMS_VendorCode = PBDRV_USB_VENDOR_REQ_MS_20, - .bAltEnumCode = 0, - } - } -}; - const pbdrv_usb_langid_union_t pbdrv_usb_str_desc_langid = { .s = { .bLength = 4, diff --git a/lib/pbio/drv/usb/usb_common_desc.h b/lib/pbio/drv/usb/usb_common_desc.h index ff77c17e2..25eabd65f 100644 --- a/lib/pbio/drv/usb/usb_common_desc.h +++ b/lib/pbio/drv/usb/usb_common_desc.h @@ -2,7 +2,6 @@ // Copyright (c) 2025 The Pybricks Authors // Descriptors which are shared across devices -// (i.e. WebUSB, Microsoft OS descriptors) #ifndef _INTERNAL_PBDRV_USB_COMMON_DESC_H_ #define _INTERNAL_PBDRV_USB_COMMON_DESC_H_ @@ -14,51 +13,6 @@ #include "usb_ch9.h" -/** - * USB vendor requests which will be used to retrieve WebUSB and Microsoft descriptors - */ -enum { - PBDRV_USB_VENDOR_REQ_WEBUSB, - PBDRV_USB_VENDOR_REQ_MS_20, -}; - -// The descriptor index for the WebUSB landing page -#define PBDRV_USB_WEBUSB_LANDING_PAGE_URL_IDX 1 - -#define PBDRV_USB_MS_20_REG_PROPERTY_NAME u"DeviceInterfaceGUIDs" -// This UUID has been generated by Pybricks and identifies specifically -// our devices which use the Pybricks protocol. Windows APIs can match on this. -// An extra null terminator is required to terminate a REG_MULTI_SZ -#define PBDRV_USB_MS_20_REG_PROPERTY_VAL u"{A5C44A4C-53D4-4389-9821-AE95051908A1}\x00" - -// Complete set of Microsoft USB descriptors -typedef struct PBDRV_PACKED { - pbdrv_usb_ms_20_desc_set_header_t desc_set_hdr; - pbdrv_usb_ms_20_compatible_t compatible; - pbdrv_usb_ms_20_reg_prop_hdr_t reg_prop_hdr; - uint16_t property_name_len; - uint16_t device_interface_guids[PBIO_ARRAY_SIZE(PBDRV_USB_MS_20_REG_PROPERTY_NAME)]; - uint16_t property_val_len; - uint16_t device_interface_guid_val[PBIO_ARRAY_SIZE(PBDRV_USB_MS_20_REG_PROPERTY_VAL)]; -} pbdrv_usb_ms_20_desc_set_t; -PBDRV_USB_TYPE_PUNNING_HELPER(pbdrv_usb_ms_20_desc_set); - -extern const pbdrv_usb_ms_20_desc_set_union_t pbdrv_usb_ms_20_desc_set; - -// WebUSB descriptor -extern const pbdrv_usb_webusb_url_desc_union_t pbdrv_usb_webusb_landing_page; - -// Complete set of USB BOS descriptors -typedef struct PBDRV_PACKED { - pbdrv_usb_bos_desc_t bos; - // WebUSB must occur before Microsoft OS descriptors - pbdrv_usb_webusb_capability_t webusb; - pbdrv_usb_microsoft_20_capability_t ms_20; -} pbdrv_usb_bos_desc_set_t; -PBDRV_USB_TYPE_PUNNING_HELPER(pbdrv_usb_bos_desc_set); - -extern const pbdrv_usb_bos_desc_set_union_t pbdrv_usb_bos_desc_set; - // (Human-readable) string descriptors typedef struct PBDRV_PACKED { uint8_t bLength; diff --git a/lib/pbio/drv/usb/usb_ev3.c b/lib/pbio/drv/usb/usb_ev3.c index a0ace57aa..69cb79c65 100644 --- a/lib/pbio/drv/usb/usb_ev3.c +++ b/lib/pbio/drv/usb/usb_ev3.c @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2025 The Pybricks Authors -// EV3 / TI AM1808 / Mentor Graphics MUSBMHDRC driver -// implementing a bespoke USB stack for Pybricks USB protocol +// EV3 / TI AM1808 / Mentor Graphics MUSBMHDRC driver implementing a bespoke +// USB stack for a USB CDC-ACM (virtual serial port) device. #include @@ -12,23 +12,13 @@ #include #include -#include #include #include #include #include -#include -#include -#include -#include -#include -#include -#include #include "usb.h" -#include - #include #include #include @@ -58,13 +48,10 @@ #define EP0_BUF_SZ 64 #define PYBRICKS_EP_PKT_SZ_FS 64 #define PYBRICKS_EP_PKT_SZ_HS 512 +// Packet size of the CDC notification (interrupt IN) endpoint. We never send +// notifications, so this only needs to be large enough to be valid. +#define NOTIF_EP_PKT_SZ 8 -// All buffers must allow the highest possible packet size. When writing -// to them, pbdrv_usb_max_package_size() gets the actual limit based on -// active speed mode. -#if PBDRV_CONFIG_USB_MAX_PACKET_SIZE != PYBRICKS_EP_PKT_SZ_HS -#error Inconsistent USB packet size -#endif /** * Indices for string descriptors @@ -82,12 +69,12 @@ static const pbdrv_usb_dev_desc_union_t dev_desc = { .s = { .bLength = sizeof(pbdrv_usb_dev_desc_t), .bDescriptorType = DESC_TYPE_DEVICE, - // A BOS descriptor is needed for Windows driver auto-installation, - // so this must be at least 2.1 - .bcdUSB = 0x0210, - .bDeviceClass = PBIO_PYBRICKS_USB_DEVICE_CLASS, - .bDeviceSubClass = PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, - .bDeviceProtocol = PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, + .bcdUSB = 0x0200, + // Use the Interface Association Descriptor device class so that the + // CDC comm and data interfaces are grouped into one function. + .bDeviceClass = USB_CLASS_MISC, + .bDeviceSubClass = USB_MISC_SUBCLASS_COMMON, + .bDeviceProtocol = USB_MISC_PROTOCOL_IAD, .bMaxPacketSize0 = EP0_BUF_SZ, .idVendor = PBDRV_CONFIG_USB_VID, .idProduct = PBDRV_CONFIG_USB_PID, @@ -102,10 +89,10 @@ static const pbdrv_usb_dev_qualifier_desc_union_t dev_qualifier_desc = { .s = { .bLength = sizeof(pbdrv_usb_dev_qualifier_desc_t), .bDescriptorType = DESC_TYPE_DEVICE_QUALIFIER, - .bcdUSB = 0x0210, - .bDeviceClass = PBIO_PYBRICKS_USB_DEVICE_CLASS, - .bDeviceSubClass = PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, - .bDeviceProtocol = PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, + .bcdUSB = 0x0200, + .bDeviceClass = USB_CLASS_MISC, + .bDeviceSubClass = USB_MISC_SUBCLASS_COMMON, + .bDeviceProtocol = USB_MISC_PROTOCOL_IAD, .bMaxPacketSize0 = EP0_BUF_SZ, .bNumConfigurations = 1, .bReserved = 0, @@ -114,105 +101,151 @@ static const pbdrv_usb_dev_qualifier_desc_union_t dev_qualifier_desc = { typedef struct PBDRV_PACKED { pbdrv_usb_conf_desc_t conf_desc; - pbdrv_usb_iface_desc_t iface_desc; + pbdrv_usb_iad_desc_t iad; + pbdrv_usb_iface_desc_t comm_iface; + pbdrv_usb_cdc_header_desc_t cdc_header; + pbdrv_usb_cdc_call_mgmt_desc_t cdc_call_mgmt; + pbdrv_usb_cdc_acm_desc_t cdc_acm; + pbdrv_usb_cdc_union_desc_t cdc_union; + pbdrv_usb_ep_desc_t notif_ep; + pbdrv_usb_iface_desc_t data_iface; pbdrv_usb_ep_desc_t ep_1_out; pbdrv_usb_ep_desc_t ep_1_in; } pbdrv_usb_ev3_conf_1_t; PBDRV_USB_TYPE_PUNNING_HELPER(pbdrv_usb_ev3_conf_1); +// The high-speed and full-speed configuration descriptors are identical apart +// from the bulk endpoint max packet size, so a macro fills in the common parts. +#define PBDRV_USB_EV3_CONF_1_COMMON(bulk_pkt_size) \ + .conf_desc = { \ + .bLength = sizeof(pbdrv_usb_conf_desc_t), \ + .bDescriptorType = DESC_TYPE_CONFIGURATION, \ + .wTotalLength = sizeof(pbdrv_usb_ev3_conf_1_t), \ + .bNumInterfaces = 2, \ + .bConfigurationValue = 1, \ + .iConfiguration = 0, \ + .bmAttributes = USB_CONF_DESC_BM_ATTR_MUST_BE_SET | USB_CONF_DESC_BM_ATTR_SELF_POWERED, \ + .bMaxPower = 0, \ + }, \ + /* Interface Association: groups the comm and data interfaces into one \ + * CDC ACM function. */ \ + .iad = { \ + .bLength = sizeof(pbdrv_usb_iad_desc_t), \ + .bDescriptorType = DESC_TYPE_INTERFACE_ASSOCIATION, \ + .bFirstInterface = 0, \ + .bInterfaceCount = 2, \ + .bFunctionClass = USB_CLASS_CDC, \ + .bFunctionSubClass = USB_CDC_SUBCLASS_ACM, \ + .bFunctionProtocol = USB_CDC_PROTOCOL_AT, \ + .iFunction = 0, \ + }, \ + /* Communication interface */ \ + .comm_iface = { \ + .bLength = sizeof(pbdrv_usb_iface_desc_t), \ + .bDescriptorType = DESC_TYPE_INTERFACE, \ + .bInterfaceNumber = 0, \ + .bAlternateSetting = 0, \ + .bNumEndpoints = 1, \ + .bInterfaceClass = USB_CLASS_CDC, \ + .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, \ + .bInterfaceProtocol = USB_CDC_PROTOCOL_AT, \ + .iInterface = 0, \ + }, \ + .cdc_header = { \ + .bFunctionLength = sizeof(pbdrv_usb_cdc_header_desc_t), \ + .bDescriptorType = USB_CDC_CS_INTERFACE, \ + .bDescriptorSubtype = USB_CDC_FUNC_SUBTYPE_HEADER, \ + .bcdCDC = 0x0110, \ + }, \ + .cdc_call_mgmt = { \ + .bFunctionLength = sizeof(pbdrv_usb_cdc_call_mgmt_desc_t), \ + .bDescriptorType = USB_CDC_CS_INTERFACE, \ + .bDescriptorSubtype = USB_CDC_FUNC_SUBTYPE_CALL_MGMT, \ + .bmCapabilities = 0x00, \ + .bDataInterface = 1, \ + }, \ + .cdc_acm = { \ + .bFunctionLength = sizeof(pbdrv_usb_cdc_acm_desc_t), \ + .bDescriptorType = USB_CDC_CS_INTERFACE, \ + .bDescriptorSubtype = USB_CDC_FUNC_SUBTYPE_ACM, \ + .bmCapabilities = 0x02, \ + }, \ + .cdc_union = { \ + .bFunctionLength = sizeof(pbdrv_usb_cdc_union_desc_t), \ + .bDescriptorType = USB_CDC_CS_INTERFACE, \ + .bDescriptorSubtype = USB_CDC_FUNC_SUBTYPE_UNION, \ + .bControlInterface = 0, \ + .bSubordinateInterface0 = 1, \ + }, \ + .notif_ep = { \ + .bLength = sizeof(pbdrv_usb_ep_desc_t), \ + .bDescriptorType = DESC_TYPE_ENDPOINT, \ + .bEndpointAddress = 0x82, \ + .bmAttributes = PBDRV_USB_EP_TYPE_INTR, \ + .wMaxPacketSize = NOTIF_EP_PKT_SZ, \ + .bInterval = 16, \ + }, \ + /* Data interface */ \ + .data_iface = { \ + .bLength = sizeof(pbdrv_usb_iface_desc_t), \ + .bDescriptorType = DESC_TYPE_INTERFACE, \ + .bInterfaceNumber = 1, \ + .bAlternateSetting = 0, \ + .bNumEndpoints = 2, \ + .bInterfaceClass = USB_CLASS_CDC_DATA, \ + .bInterfaceSubClass = 0, \ + .bInterfaceProtocol = 0, \ + .iInterface = 0, \ + }, \ + .ep_1_out = { \ + .bLength = sizeof(pbdrv_usb_ep_desc_t), \ + .bDescriptorType = DESC_TYPE_ENDPOINT, \ + .bEndpointAddress = 0x01, \ + .bmAttributes = PBDRV_USB_EP_TYPE_BULK, \ + .wMaxPacketSize = (bulk_pkt_size), \ + .bInterval = 0, \ + }, \ + .ep_1_in = { \ + .bLength = sizeof(pbdrv_usb_ep_desc_t), \ + .bDescriptorType = DESC_TYPE_ENDPOINT, \ + .bEndpointAddress = 0x81, \ + .bmAttributes = PBDRV_USB_EP_TYPE_BULK, \ + .wMaxPacketSize = (bulk_pkt_size), \ + .bInterval = 0, \ + } + static const pbdrv_usb_ev3_conf_1_union_t configuration_1_desc_hs = { .s = { - .conf_desc = { - .bLength = sizeof(pbdrv_usb_conf_desc_t), - .bDescriptorType = DESC_TYPE_CONFIGURATION, - .wTotalLength = sizeof(pbdrv_usb_ev3_conf_1_t), - .bNumInterfaces = 1, - .bConfigurationValue = 1, - .iConfiguration = 0, - .bmAttributes = USB_CONF_DESC_BM_ATTR_MUST_BE_SET | USB_CONF_DESC_BM_ATTR_SELF_POWERED, - .bMaxPower = 0, - }, - .iface_desc = { - .bLength = sizeof(pbdrv_usb_iface_desc_t), - .bDescriptorType = DESC_TYPE_INTERFACE, - .bInterfaceNumber = 0, - .bAlternateSetting = 0, - .bNumEndpoints = 2, - .bInterfaceClass = PBIO_PYBRICKS_USB_DEVICE_CLASS, - .bInterfaceSubClass = PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, - .bInterfaceProtocol = PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, - .iInterface = 0, - }, - .ep_1_out = { - .bLength = sizeof(pbdrv_usb_ep_desc_t), - .bDescriptorType = DESC_TYPE_ENDPOINT, - .bEndpointAddress = 0x01, - .bmAttributes = PBDRV_USB_EP_TYPE_BULK, - .wMaxPacketSize = PYBRICKS_EP_PKT_SZ_HS, - .bInterval = 0, - }, - .ep_1_in = { - .bLength = sizeof(pbdrv_usb_ep_desc_t), - .bDescriptorType = DESC_TYPE_ENDPOINT, - .bEndpointAddress = 0x81, - .bmAttributes = PBDRV_USB_EP_TYPE_BULK, - .wMaxPacketSize = PYBRICKS_EP_PKT_SZ_HS, - .bInterval = 0, - }, + PBDRV_USB_EV3_CONF_1_COMMON(PYBRICKS_EP_PKT_SZ_HS), } }; static const pbdrv_usb_ev3_conf_1_union_t configuration_1_desc_fs = { .s = { - .conf_desc = { - .bLength = sizeof(pbdrv_usb_conf_desc_t), - .bDescriptorType = DESC_TYPE_CONFIGURATION, - .wTotalLength = sizeof(pbdrv_usb_ev3_conf_1_t), - .bNumInterfaces = 1, - .bConfigurationValue = 1, - .iConfiguration = 0, - .bmAttributes = USB_CONF_DESC_BM_ATTR_MUST_BE_SET | USB_CONF_DESC_BM_ATTR_SELF_POWERED, - .bMaxPower = 0, - }, - .iface_desc = { - .bLength = sizeof(pbdrv_usb_iface_desc_t), - .bDescriptorType = DESC_TYPE_INTERFACE, - .bInterfaceNumber = 0, - .bAlternateSetting = 0, - .bNumEndpoints = 2, - .bInterfaceClass = PBIO_PYBRICKS_USB_DEVICE_CLASS, - .bInterfaceSubClass = PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, - .bInterfaceProtocol = PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, - .iInterface = 0, - }, - .ep_1_out = { - .bLength = sizeof(pbdrv_usb_ep_desc_t), - .bDescriptorType = DESC_TYPE_ENDPOINT, - .bEndpointAddress = 0x01, - .bmAttributes = PBDRV_USB_EP_TYPE_BULK, - .wMaxPacketSize = PYBRICKS_EP_PKT_SZ_FS, - .bInterval = 0, - }, - .ep_1_in = { - .bLength = sizeof(pbdrv_usb_ep_desc_t), - .bDescriptorType = DESC_TYPE_ENDPOINT, - .bEndpointAddress = 0x81, - .bmAttributes = PBDRV_USB_EP_TYPE_BULK, - .wMaxPacketSize = PYBRICKS_EP_PKT_SZ_FS, - .bInterval = 0, - }, + PBDRV_USB_EV3_CONF_1_COMMON(PYBRICKS_EP_PKT_SZ_FS), } }; // This dynamic buffer is needed in order to have an aligned, // global-lifetime buffer for sending dynamic data in response -// to control transfers. This is used for the serial number string -// and for Pybricks protocol requests. +// to control transfers. This is used for the serial number string. static union { uint8_t b[EP0_BUF_SZ]; uint32_t u[EP0_BUF_SZ / sizeof(uint32_t)]; } pbdrv_usb_ev3_ep0_buffer; -_Static_assert(PBIO_PYBRICKS_HUB_CAPABILITIES_VALUE_SIZE <= EP0_BUF_SZ); + +// CDC line coding (baud rate, stop bits, parity, data bits). The host can set +// and get it, but we ignore the actual values since this is a virtual port. +// Defaults to 115200 8N1. +static union { + uint8_t b[USB_CDC_LINE_CODING_SIZE]; + uint32_t u[(USB_CDC_LINE_CODING_SIZE + sizeof(uint32_t) - 1) / sizeof(uint32_t)]; +} pbdrv_usb_ev3_line_coding = { + .b = { 0x00, 0xC2, 0x01, 0x00, 0x00, 0x00, 0x08 }, +}; + +// Set while EP0 is waiting for the host to send the SET_LINE_CODING data stage. +static bool pbdrv_usb_ep0_expect_line_coding; // Defined in pbio/platform/ev3/platform.c extern uint8_t pbdrv_ev3_bluetooth_mac_address[6]; @@ -269,8 +302,7 @@ _Static_assert(sizeof(usb_cppi_hpd_t) <= CPPI_DESCRIPTOR_ALIGN); // rather than dynamically allocating them as needed enum { CPPI_DESC_RX, - CPPI_DESC_TX_RESPONSE, - CPPI_DESC_TX_PYBRICKS_EVENT, + CPPI_DESC_TX, // the minimum number of descriptors we can allocate is 32, // even though we do not use nearly all of them CPPI_DESC_COUNT = 32, @@ -315,8 +347,8 @@ static void usb_setup_rx_dma_desc(void) { // Fill in the CPPI DMA descriptor to send a packet -static void usb_setup_tx_dma_desc(int tx_type, void *buf, uint32_t buf_len) { - PBDRV_UNCACHED(cppi_descriptors[tx_type]) = (usb_cppi_hpd_t) { +static void usb_setup_tx_dma_desc(void *buf, uint32_t buf_len) { + PBDRV_UNCACHED(cppi_descriptors[CPPI_DESC_TX]) = (usb_cppi_hpd_t) { .word0 = { .hostPktType = CPPI_HOST_PACKET_DESCRIPTOR_TYPE, .pktLength = buf_len, @@ -338,7 +370,7 @@ static void usb_setup_tx_dma_desc(int tx_type, void *buf, uint32_t buf_len) { pbdrv_compiler_memory_barrier(); HWREG(USB_0_OTGBASE + CPDMA_QUEUE_REGISTER_D + TX_SUBMITQ1 * 16) = - (uint32_t)(&cppi_descriptors[tx_type]) | CPPI_DESCRIPTOR_SIZE_BITS; + (uint32_t)(&cppi_descriptors[CPPI_DESC_TX]) | CPPI_DESCRIPTOR_SIZE_BITS; } // Helper function to set up CPPI DMA upon USB reset @@ -363,6 +395,20 @@ static void usb_reset_cppi_dma(void) { HWREGH(USB0_BASE + USB_O_TXFIFOADD) = EP0_BUF_SZ / 8; HWREGH(USB0_BASE + USB_O_RXFIFOADD) = (EP0_BUF_SZ + PYBRICKS_EP_PKT_SZ_HS) / 8; + // Set up the CDC notification endpoint (EP2 IN, interrupt). The host + // (e.g. Linux cdc_acm) requires this endpoint to exist to bind the driver, + // but we never send notifications, so it just NAKs forever. Its FIFO is + // placed after the EP1 TX and RX FIFOs. + HWREGB(USB0_BASE + USB_O_EPIDX) = 2; + HWREGB(USB0_BASE + USB_O_TXFIFOSZ) = USB_TXFIFOSZ_SIZE_8; + HWREGH(USB0_BASE + USB_O_TXFIFOADD) = + (EP0_BUF_SZ + PYBRICKS_EP_PKT_SZ_HS + PYBRICKS_EP_PKT_SZ_HS) / 8; + HWREGH(USB0_BASE + USB_O_TXMAXP2) = NOTIF_EP_PKT_SZ; + // Select TX direction for the shared endpoint FIFO. + HWREGB(USB0_BASE + USB_O_TXCSRH2) = USB_TXCSRH2_MODE; + // Reset data toggle and flush the FIFO. + HWREGB(USB0_BASE + USB_O_TXCSRL2) = USB_TXCSRL2_CLRDT | USB_TXCSRL2_FLUSH; + // Set up the TX fifo for DMA and a stall condition HWREGH(USB0_BASE + USB_O_TXCSRL1) = ((USB_TXCSRH1_AUTOSET | USB_TXCSRH1_MODE | USB_TXCSRH1_DMAEN | USB_TXCSRH1_DMAMOD) << 8) | USB_TXCSRL1_STALL; // Set up the RX fifo for DMA and a stall condition @@ -491,11 +537,6 @@ static bool usb_get_descriptor(uint16_t wValue) { return true; } break; - - case DESC_TYPE_BOS: - pbdrv_usb_setup_data_to_send = pbdrv_usb_bos_desc_set.u; - pbdrv_usb_setup_data_to_send_sz = sizeof(pbdrv_usb_bos_desc_set.s); - return true; } return false; @@ -545,6 +586,7 @@ static void usb_device_intr(void) { HWREGH(USB0_BASE + USB_O_CSRL0) = 0; pbdrv_usb_setup_data_to_send = 0; pbdrv_usb_addr_needs_setting = false; + pbdrv_usb_ep0_expect_line_coding = false; } if (peri_csr & USB_CSRL0_SETEND) { @@ -552,6 +594,7 @@ static void usb_device_intr(void) { HWREGH(USB0_BASE + USB_O_CSRL0) = USB_CSRL0_SETENDC; pbdrv_usb_setup_data_to_send = 0; pbdrv_usb_addr_needs_setting = false; + pbdrv_usb_ep0_expect_line_coding = false; } // If we got here (and didn't wipe out this flag), @@ -562,7 +605,21 @@ static void usb_device_intr(void) { pbdrv_usb_addr_needs_setting = false; } - if (peri_csr & USB_CSRL0_RXRDY) { + if ((peri_csr & USB_CSRL0_RXRDY) && pbdrv_usb_ep0_expect_line_coding) { + // OUT data stage of a CDC SET_LINE_CODING request. Read the line + // coding bytes out of the FIFO. The values are ignored since this + // is a virtual serial port, but we store them so GET_LINE_CODING + // returns something consistent. + pbdrv_usb_ep0_expect_line_coding = false; + uint32_t count = HWREGB(USB0_BASE + USB_O_COUNT0); + for (uint32_t i = 0; i < count; i++) { + uint8_t byte = HWREGB(USB0_BASE + USB_O_FIFO0); + if (i < sizeof(pbdrv_usb_ev3_line_coding.b)) { + pbdrv_usb_ev3_line_coding.b[i] = byte; + } + } + HWREGH(USB0_BASE + USB_O_CSRL0) = USB_CSRL0_RXRDYC | USB_CSRL0_DATAEND; + } else if (peri_csr & USB_CSRL0_RXRDY) { // Got a new setup packet pbdrv_usb_setup_packet_union_t setup_pkt; bool handled = false; @@ -592,6 +649,8 @@ static void usb_device_intr(void) { // Reset data toggle, clear stall, flush fifo HWREGB(USB0_BASE + USB_O_TXCSRL1) = USB_TXCSRL1_CLRDT | USB_TXCSRL1_FLUSH; HWREGB(USB0_BASE + USB_O_RXCSRL1) = USB_RXCSRL1_CLRDT | USB_RXCSRL1_FLUSH; + // Reset the notification endpoint too. + HWREGB(USB0_BASE + USB_O_TXCSRL2) = USB_TXCSRL2_CLRDT | USB_TXCSRL2_FLUSH; } else { // deconfiguring @@ -690,82 +749,32 @@ static void usb_device_intr(void) { } break; - case BM_REQ_TYPE_VENDOR: - switch (setup_pkt.s.bRequest) { - case PBDRV_USB_VENDOR_REQ_WEBUSB: - if (setup_pkt.s.wIndex == WEBUSB_REQ_GET_URL && setup_pkt.s.wValue == PBDRV_USB_WEBUSB_LANDING_PAGE_URL_IDX) { - pbdrv_usb_setup_data_to_send = pbdrv_usb_webusb_landing_page.u; - pbdrv_usb_setup_data_to_send_sz = pbdrv_usb_webusb_landing_page.s.bLength; - handled = true; - } - break; - - case PBDRV_USB_VENDOR_REQ_MS_20: - if (setup_pkt.s.wIndex == MS_OS_20_DESCRIPTOR_INDEX) { - pbdrv_usb_setup_data_to_send = pbdrv_usb_ms_20_desc_set.u; - pbdrv_usb_setup_data_to_send_sz = sizeof(pbdrv_usb_ms_20_desc_set.s); - handled = true; - } - break; - } - break; - case BM_REQ_TYPE_CLASS: if ((setup_pkt.s.bmRequestType & BM_REQ_RECIP_MASK) != BM_REQ_RECIP_IF) { - // Pybricks class requests must be directed at the interface + // CDC class requests are directed at the comm interface. break; } switch (setup_pkt.s.bRequest) { - const char *s; - - case PBIO_PYBRICKS_USB_INTERFACE_READ_CHARACTERISTIC_GATT: - // Standard GATT characteristic - switch (setup_pkt.s.wValue) { - case 0x2A00: - // GATT Device Name characteristic - s = pbdrv_bluetooth_get_hub_name(); - pbdrv_usb_setup_data_to_send_sz = strlen(s); - memcpy(pbdrv_usb_ev3_ep0_buffer.b, s, pbdrv_usb_setup_data_to_send_sz); - pbdrv_usb_setup_data_to_send = pbdrv_usb_ev3_ep0_buffer.u; - handled = true; - break; - - case 0x2A26: - // GATT Firmware Revision characteristic - s = PBIO_VERSION_STR; - pbdrv_usb_setup_data_to_send_sz = strlen(s); - memcpy(pbdrv_usb_ev3_ep0_buffer.b, s, pbdrv_usb_setup_data_to_send_sz); - pbdrv_usb_setup_data_to_send = pbdrv_usb_ev3_ep0_buffer.u; - handled = true; - break; + case USB_CDC_REQ_SET_LINE_CODING: + // The 7-byte line coding follows in an OUT data + // stage, which we read on the next RXRDY interrupt. + pbdrv_usb_ep0_expect_line_coding = true; + handled = true; + break; - case 0x2A28: - // GATT Software Revision characteristic - s = PBIO_PROTOCOL_VERSION_STR; - pbdrv_usb_setup_data_to_send_sz = strlen(s); - memcpy(pbdrv_usb_ev3_ep0_buffer.b, s, pbdrv_usb_setup_data_to_send_sz); - pbdrv_usb_setup_data_to_send = pbdrv_usb_ev3_ep0_buffer.u; - handled = true; - break; - } + case USB_CDC_REQ_GET_LINE_CODING: + pbdrv_usb_setup_data_to_send = pbdrv_usb_ev3_line_coding.u; + pbdrv_usb_setup_data_to_send_sz = sizeof(pbdrv_usb_ev3_line_coding.b); + handled = true; break; - case PBIO_PYBRICKS_USB_INTERFACE_READ_CHARACTERISTIC_PYBRICKS: - // Pybricks characteristic - switch (setup_pkt.s.wValue) { - case 0x0003: - pbio_pybricks_hub_capabilities( - pbdrv_usb_ev3_ep0_buffer.b, - (pbdrv_usb_is_usb_hs ? PYBRICKS_EP_PKT_SZ_HS : PYBRICKS_EP_PKT_SZ_FS) - 1, - PBSYS_CONFIG_APP_FEATURE_FLAGS, - pbsys_storage_get_maximum_program_size(), - PBSYS_CONFIG_HMI_NUM_SLOTS); - pbdrv_usb_setup_data_to_send = pbdrv_usb_ev3_ep0_buffer.u; - pbdrv_usb_setup_data_to_send_sz = PBIO_PYBRICKS_HUB_CAPABILITIES_VALUE_SIZE; - handled = true; - break; - } + case USB_CDC_REQ_SET_CONTROL_LINE_STATE: + // DTR asserted means a host app opened the serial + // port (analogous to a BLE host subscribing). + pbdrv_usb_on_dtr_changed( + (setup_pkt.s.wValue & USB_CDC_CONTROL_LINE_STATE_DTR) != 0); + handled = true; break; } break; @@ -779,8 +788,9 @@ static void usb_device_intr(void) { // We implement something similar but not identical. In general, we wait until // we have completely processed the request and decided what we're going to do // before we indicate that we are ready to progress to the next phase. - // We do not support any requests that require receiving data from the host, - // only zero-data requests or those that require sending data to the host. + // The only request that requires receiving data from the host is the CDC + // SET_LINE_CODING request, which is acknowledged here and whose data stage + // is read on the following EP0 RXRDY interrupt. // For errors or zero-data requests, we set USB_CSRL0_RXRDYC and USB_CSRL0_DATAEND // at the same time so that we don't get spurious SETUPEND errors // (which we treat as a command failure). For requests that require sending data, @@ -789,6 +799,10 @@ static void usb_device_intr(void) { if (!handled) { // Indicate we read the packet, but also send stall HWREGH(USB0_BASE + USB_O_CSRL0) = USB_CSRL0_RXRDYC | USB_CSRL0_STALL; + } else if (pbdrv_usb_ep0_expect_line_coding) { + // Acknowledge the SETUP only; the host to device data stage is + // received on the next EP0 RXRDY interrupt. + HWREGH(USB0_BASE + USB_O_CSRL0) = USB_CSRL0_RXRDYC; } else { if (pbdrv_usb_setup_data_to_send) { // Indicate we read the packet @@ -867,10 +881,7 @@ static void usb_device_intr(void) { // Pop the descriptor from the queue uint32_t qctrld = HWREG(USB_0_OTGBASE + CPDMA_QUEUE_REGISTER_D + TX_COMPQ1 * 16) & ~CPDMA_QUEUE_REGISTER_DESC_SIZE_MASK; - if (qctrld == (uint32_t)(&cppi_descriptors[CPPI_DESC_TX_RESPONSE])) { - transmitting = false; - pbio_os_request_poll(); - } else if (qctrld == (uint32_t)(&cppi_descriptors[CPPI_DESC_TX_PYBRICKS_EVENT])) { + if (qctrld == (uint32_t)(&cppi_descriptors[CPPI_DESC_TX])) { transmitting = false; pbio_os_request_poll(); } @@ -1032,33 +1043,33 @@ pbio_error_t pbdrv_usb_tx_reset(pbio_os_state_t *state) { PBIO_OS_ASYNC_END(PBIO_SUCCESS); } -pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uint32_t size) { +pbio_error_t pbdrv_usb_tx_chunk(pbio_os_state_t *state, const uint8_t *data, uint32_t size) { static pbio_os_timer_t timer; PBIO_OS_ASYNC_BEGIN(state); if (transmitting) { - DEBUG_PRINT("Cannot transmit USB event, busy.\n"); + DEBUG_PRINT("Cannot transmit USB chunk, busy.\n"); return PBIO_ERROR_BUSY; } transmitting = true; pbio_os_timer_set(&timer, PBDRV_USB_TRANSMIT_TIMEOUT); - // Transmit event. + // Transmit the raw bytes. Framing is handled by the common driver. pbdrv_cache_prepare_before_dma(data, size); - usb_setup_tx_dma_desc(CPPI_DESC_TX_PYBRICKS_EVENT, (uint8_t *)data, size); + usb_setup_tx_dma_desc((uint8_t *)data, size); PBIO_OS_AWAIT_UNTIL(state, !transmitting || pbio_os_timer_is_expired(&timer)); if (pbio_os_timer_is_expired(&timer)) { // Transmission has taken too long, so reset the state to allow // new transmissions. This can happen if the host stops reading - // data for some reason. This need some time to complete, so delegate + // data for some reason. This needs some time to complete, so delegate // the reset back to the process. - DEBUG_PRINT("USB event timed out\n"); - return PBIO_SUCCESS; + DEBUG_PRINT("USB chunk timed out\n"); + return PBIO_ERROR_TIMEDOUT; } #if DEBUG uint32_t elapsed_ms = pbdrv_clock_get_ms() - timer.start; @@ -1069,37 +1080,4 @@ pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uin PBIO_OS_ASYNC_END(PBIO_SUCCESS); } -pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t code) { - - static pbio_os_timer_t timer; - - static uint8_t ep1_tx_response_buf[1 + sizeof(uint32_t)] __aligned(4) = { PBIO_PYBRICKS_IN_EP_MSG_RESPONSE }; - - PBIO_OS_ASYNC_BEGIN(state); - - if (transmitting) { - DEBUG_PRINT("Cannot transmit USB response, busy.\n"); - return PBIO_ERROR_BUSY; - } - - transmitting = true; - pbio_os_timer_set(&timer, PBDRV_USB_TRANSMIT_TIMEOUT); - - // Response is just the error code. - pbio_set_uint32_le(&ep1_tx_response_buf[1], code); - - // Transmit response. - pbdrv_cache_prepare_before_dma(ep1_tx_response_buf, sizeof(ep1_tx_response_buf)); - usb_setup_tx_dma_desc(CPPI_DESC_TX_RESPONSE, ep1_tx_response_buf, sizeof(ep1_tx_response_buf)); - - // Wait until complete or trigger reset on timeout. - PBIO_OS_AWAIT_UNTIL(state, !transmitting || pbio_os_timer_is_expired(&timer)); - if (pbio_os_timer_is_expired(&timer)) { - DEBUG_PRINT("USB response timed out\n"); - return PBIO_ERROR_TIMEDOUT; - } - - PBIO_OS_ASYNC_END(PBIO_SUCCESS); -} - #endif // PBDRV_CONFIG_USB_EV3 diff --git a/lib/pbio/drv/usb/usb_nxt.c b/lib/pbio/drv/usb/usb_nxt.c index b5c45e905..ac2649a3f 100644 --- a/lib/pbio/drv/usb/usb_nxt.c +++ b/lib/pbio/drv/usb/usb_nxt.c @@ -5,6 +5,11 @@ * See lib/pbio/platform/nxt/nxos/AUTHORS for a full list of the developers. */ +// NXT / Atmel AT91SAM7S256 UDP driver implementing a USB CDC-ACM (virtual +// serial port) device. The byte stream is framed (COBS) by the common USB +// driver in usb.c, and the host's serial port open/close (DTR) is used to +// detect connection state, just like the STM32 and EV3 drivers. + #include #if PBDRV_CONFIG_USB_NXT @@ -13,18 +18,14 @@ #include #include -#include #include -#include -#include -#include -#include +#include +#include #include #include "nxos/interrupts.h" -#include "nxos/assert.h" #include "nxos/drivers/systick.h" #include "nxos/drivers/aic.h" #include "nxos/util.h" @@ -39,58 +40,19 @@ /* The USB controller supports up to 4 endpoints. */ #define PBDRV_USB_NXT_N_ENDPOINTS 4 -/* Maximum data packet sizes. Endpoint 0 is a special case (control - * endpoint). - * - * TODO: Discuss the need/use for separating recv/send. - */ +/* Physical endpoint numbers used by this driver. */ +#define EP_CONTROL 0 /* Control endpoint. */ +#define EP_BULK_OUT 1 /* CDC data, host to hub. */ +#define EP_BULK_IN 2 /* CDC data, hub to host. */ +#define EP_NOTIF 3 /* CDC notification (interrupt IN), never used. */ + +/* Maximum data packet sizes. Endpoint 0 is a special case (control endpoint). */ #define MAX_EP0_SIZE 8 #define MAX_RCV_SIZE 64 #define MAX_SND_SIZE 64 - -/* Various constants for the setup packets. - * - * TODO: clean up these. Most are unused. - */ -#define USB_BMREQUEST_DIR 0x80 -#define USB_BMREQUEST_H_TO_D 0x00 -#define USB_BMREQUEST_D_TO_H 0x80 -#define USB_BMREQUEST_TYPE 0x60 -#define USB_BMREQUEST_TYPE_STD 0x00 -#define USB_BMREQUEST_TYPE_CLASS 0x20 -#define USB_BMREQUEST_TYPE_VENDOR 0x40 -#define USB_BMREQUEST_RCPT 0x1F -#define USB_BMREQUEST_RCPT_DEV 0x00 /* device */ -#define USB_BMREQUEST_RCPT_INT 0x01 /* interface */ -#define USB_BMREQUEST_RCPT_EPT 0x02 /* endpoint */ -#define USB_BMREQUEST_RCPT_OTH 0x03 /* other */ - -// Standard requests -#define USB_BREQUEST_GET_STATUS 0x0 -#define USB_BREQUEST_CLEAR_FEATURE 0x1 -#define USB_BREQUEST_SET_FEATURE 0x3 -#define USB_BREQUEST_SET_ADDRESS 0x5 -#define USB_BREQUEST_GET_DESCRIPTOR 0x6 -#define USB_BREQUEST_SET_DESCRIPTOR 0x7 -#define USB_BREQUEST_GET_CONFIG 0x8 -#define USB_BREQUEST_SET_CONFIG 0x9 -#define USB_BREQUEST_GET_INTERFACE 0xA -#define USB_BREQUEST_SET_INTERFACE 0xB - -#define USB_WVALUE_TYPE (0xFF << 8) -#define USB_DESC_TYPE_DEVICE 1 -#define USB_DESC_TYPE_CONFIG 2 -#define USB_DESC_TYPE_STR 3 -#define USB_DESC_TYPE_INT 4 -#define USB_DESC_TYPE_ENDPT 5 -#define USB_DESC_TYPE_DEVICE_QUALIFIER 6 -#define USB_DESC_TYPE_BOS 15 - -// BOS descriptor related defines -#define USB_DEVICE_CAPABILITY_TYPE 0x10 -#define USB_DEV_CAP_TYPE_PLATFORM 5 - -#define USB_WVALUE_INDEX 0xFF +/* Packet size of the CDC notification (interrupt IN) endpoint. We never send + * notifications, so this only needs to be large enough to be valid. */ +#define NOTIF_EP_PKT_SZ 8 /** * Indices for string descriptors @@ -102,30 +64,20 @@ enum { STRING_DESC_SERIAL, }; -/* The following definitions are 'raw' USB setup packets. They are all - * standard responses to various setup requests by the USB host. These - * packets are all constant, and mostly boilerplate. Don't be too - * bothered if you skip over these to real code. - * - * If you want to understand the full meaning of every bit of these - * packets, you should refer to the USB 2.0 specifications. - * - * One point of interest: the USB device space is partitionned by - * vendor and product ID. As we are lacking money and real need, we - * don't have a vendor ID to use. Therefore, we are currently - * piggybacking on Lego's device space, using an unused product ID. - */ +// Device descriptor. The device class uses the Interface Association +// Descriptor so that the CDC comm and data interfaces are grouped into one +// function. static const pbdrv_usb_dev_desc_t pbdrv_usb_nxt_device_descriptor = { .bLength = sizeof(pbdrv_usb_dev_desc_t), .bDescriptorType = DESC_TYPE_DEVICE, - .bcdUSB = 0x0210, /* This packet is USB 2.1 (needed for BOS descriptors). */ - .bDeviceClass = PBIO_PYBRICKS_USB_DEVICE_CLASS, - .bDeviceSubClass = PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, - .bDeviceProtocol = PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, + .bcdUSB = 0x0200, + .bDeviceClass = USB_CLASS_MISC, + .bDeviceSubClass = USB_MISC_SUBCLASS_COMMON, + .bDeviceProtocol = USB_MISC_PROTOCOL_IAD, .bMaxPacketSize0 = MAX_EP0_SIZE, - .idVendor = 0x0694, /* Vendor ID : LEGO */ - .idProduct = 0x0002, /* Product ID : NXT */ - .bcdDevice = 0x0200, /* Product revision: 2.0.0. */ + .idVendor = PBDRV_CONFIG_USB_VID, + .idProduct = PBDRV_CONFIG_USB_PID, + .bcdDevice = 0x0200, .iManufacturer = STRING_DESC_MFG, .iProduct = STRING_DESC_PRODUCT, .iSerialNumber = STRING_DESC_SERIAL, @@ -134,7 +86,14 @@ static const pbdrv_usb_dev_desc_t pbdrv_usb_nxt_device_descriptor = { typedef struct PBDRV_PACKED { pbdrv_usb_conf_desc_t conf_desc; - pbdrv_usb_iface_desc_t iface_desc; + pbdrv_usb_iad_desc_t iad; + pbdrv_usb_iface_desc_t comm_iface; + pbdrv_usb_cdc_header_desc_t cdc_header; + pbdrv_usb_cdc_call_mgmt_desc_t cdc_call_mgmt; + pbdrv_usb_cdc_acm_desc_t cdc_acm; + pbdrv_usb_cdc_union_desc_t cdc_union; + pbdrv_usb_ep_desc_t notif_ep; + pbdrv_usb_iface_desc_t data_iface; pbdrv_usb_ep_desc_t ep_out; pbdrv_usb_ep_desc_t ep_in; } pbdrv_usb_nxt_conf_t; @@ -144,7 +103,7 @@ static const pbdrv_usb_nxt_conf_t pbdrv_usb_nxt_full_config = { .bLength = sizeof(pbdrv_usb_conf_desc_t), .bDescriptorType = DESC_TYPE_CONFIGURATION, .wTotalLength = sizeof(pbdrv_usb_nxt_conf_t), - .bNumInterfaces = 1, + .bNumInterfaces = 2, .bConfigurationValue = 1, .iConfiguration = 0, /* Configuration attributes bitmap. Bit 7 (MSB) must be 1, bit 6 is @@ -154,35 +113,93 @@ static const pbdrv_usb_nxt_conf_t pbdrv_usb_nxt_full_config = { .bmAttributes = USB_CONF_DESC_BM_ATTR_MUST_BE_SET | USB_CONF_DESC_BM_ATTR_SELF_POWERED, .bMaxPower = 0, }, - .iface_desc = { + /* Interface Association: groups the comm and data interfaces into one + * CDC ACM function. */ + .iad = { + .bLength = sizeof(pbdrv_usb_iad_desc_t), + .bDescriptorType = DESC_TYPE_INTERFACE_ASSOCIATION, + .bFirstInterface = 0, + .bInterfaceCount = 2, + .bFunctionClass = USB_CLASS_CDC, + .bFunctionSubClass = USB_CDC_SUBCLASS_ACM, + .bFunctionProtocol = USB_CDC_PROTOCOL_AT, + .iFunction = 0, + }, + /* Communication interface. */ + .comm_iface = { .bLength = sizeof(pbdrv_usb_iface_desc_t), .bDescriptorType = DESC_TYPE_INTERFACE, .bInterfaceNumber = 0, .bAlternateSetting = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_CDC, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, + .bInterfaceProtocol = USB_CDC_PROTOCOL_AT, + .iInterface = 0, + }, + .cdc_header = { + .bFunctionLength = sizeof(pbdrv_usb_cdc_header_desc_t), + .bDescriptorType = USB_CDC_CS_INTERFACE, + .bDescriptorSubtype = USB_CDC_FUNC_SUBTYPE_HEADER, + .bcdCDC = 0x0110, + }, + .cdc_call_mgmt = { + .bFunctionLength = sizeof(pbdrv_usb_cdc_call_mgmt_desc_t), + .bDescriptorType = USB_CDC_CS_INTERFACE, + .bDescriptorSubtype = USB_CDC_FUNC_SUBTYPE_CALL_MGMT, + .bmCapabilities = 0x00, + .bDataInterface = 1, + }, + .cdc_acm = { + .bFunctionLength = sizeof(pbdrv_usb_cdc_acm_desc_t), + .bDescriptorType = USB_CDC_CS_INTERFACE, + .bDescriptorSubtype = USB_CDC_FUNC_SUBTYPE_ACM, + .bmCapabilities = 0x02, + }, + .cdc_union = { + .bFunctionLength = sizeof(pbdrv_usb_cdc_union_desc_t), + .bDescriptorType = USB_CDC_CS_INTERFACE, + .bDescriptorSubtype = USB_CDC_FUNC_SUBTYPE_UNION, + .bControlInterface = 0, + .bSubordinateInterface0 = 1, + }, + /* Notification endpoint (EP3, interrupt IN). The host (e.g. Linux cdc_acm) + * requires this endpoint to exist to bind the driver, but we never send + * notifications, so it just NAKs forever. */ + .notif_ep = { + .bLength = sizeof(pbdrv_usb_ep_desc_t), + .bDescriptorType = DESC_TYPE_ENDPOINT, + .bEndpointAddress = 0x80 | EP_NOTIF, + .bmAttributes = PBDRV_USB_EP_TYPE_INTR, + .wMaxPacketSize = NOTIF_EP_PKT_SZ, + .bInterval = 16, + }, + /* Data interface. */ + .data_iface = { + .bLength = sizeof(pbdrv_usb_iface_desc_t), + .bDescriptorType = DESC_TYPE_INTERFACE, + .bInterfaceNumber = 1, + .bAlternateSetting = 0, .bNumEndpoints = 2, - .bInterfaceClass = PBIO_PYBRICKS_USB_DEVICE_CLASS, - .bInterfaceSubClass = PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, - .bInterfaceProtocol = PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, .iInterface = 0, }, - /* - * Descriptor for EP1. - */ + /* Bulk OUT endpoint (EP1, host to hub). */ .ep_out = { .bLength = sizeof(pbdrv_usb_ep_desc_t), .bDescriptorType = DESC_TYPE_ENDPOINT, - .bEndpointAddress = 0x01, /* Endpoint number. MSB is zero, meaning this is an OUT EP. */ + .bEndpointAddress = EP_BULK_OUT, .bmAttributes = PBDRV_USB_EP_TYPE_BULK, .wMaxPacketSize = MAX_RCV_SIZE, .bInterval = 0, }, - /* - * Descriptor for EP2. - */ + /* Bulk IN endpoint (EP2, hub to host). */ .ep_in = { .bLength = sizeof(pbdrv_usb_ep_desc_t), .bDescriptorType = DESC_TYPE_ENDPOINT, - .bEndpointAddress = 0x82, /* Endpoint number. MSB is one, meaning this is an IN EP. */ + .bEndpointAddress = 0x80 | EP_BULK_IN, .bmAttributes = PBDRV_USB_EP_TYPE_BULK, .wMaxPacketSize = MAX_SND_SIZE, .bInterval = 0, @@ -198,42 +215,40 @@ typedef struct PBDRV_PACKED { static pbdrv_usb_serial_number_desc_t pbdrv_usb_str_desc_serial; -typedef enum { - USB_UNINITIALIZED, - USB_READY, - USB_BUSY, - USB_SUSPENDED, -} pbdrv_usb_nxt_status_t; +// CDC line coding (baud rate, stop bits, parity, data bits). The host can set +// and get it, but we ignore the actual values since this is a virtual port. +// Defaults to 115200 8N1. +static uint8_t pbdrv_usb_nxt_line_coding[USB_CDC_LINE_CODING_SIZE] = { + 0x00, 0xC2, 0x01, 0x00, 0x00, 0x00, 0x08, +}; -/* - * The USB device state. Contains the current USB state (selected - * configuration, etc.) and transitory state for data transfers. - */ -static volatile struct { - /* The current state of the device. */ - pbdrv_usb_nxt_status_t status; +// Set while EP0 is waiting for the host to send the SET_LINE_CODING data stage. +static bool pbdrv_usb_nxt_expect_line_coding; - /* Holds the status the bus was in before entering suspend. */ - pbdrv_usb_nxt_status_t pre_suspend_status; +/* True once the host has selected a configuration (SET_CONFIGURATION). This is + * the USB analog of being enumerated and ready for data transfers. */ +static volatile bool pbdrv_usb_nxt_configured; - /* When the host gives us an address, we must send a null ACK packet - * back before actually changing addresses. This field stores the - * address that should be set once the ACK is sent. - */ - uint32_t new_device_address; +/* True while a transmission on the data IN endpoint (EP2) is in progress. */ +static volatile bool pbdrv_usb_nxt_transmitting; - /* The currently selected USB configuration. */ - uint8_t current_config; +/* When the host gives us an address, we must send a null ACK packet back + * before actually changing addresses. This field stores the address that + * should be set once the ACK is sent. */ +static uint32_t pbdrv_usb_nxt_new_device_address; - /* Holds the state of the data transmissions on both EP0 and - * EP2. This only gets used if the transmission needed to be split - * into several USB packets. - * 0 = EP0 - * 1 = EP2 - */ - uint8_t *tx_data[2]; - uint32_t tx_len[2]; -} pbdrv_usb_nxt_state; +/* The currently selected USB configuration. */ +static uint8_t pbdrv_usb_nxt_current_config; + +/* Holds the state of split (multi-packet) transmissions, indexed by the + * physical endpoint number (the same numbering used for the CSR/FDR + * registers). Only EP0 (control) and EP2 (bulk IN) ever transmit, so only + * those slots are used, but sizing the arrays to the full endpoint count keeps + * the indexing uniform with the hardware registers and avoids the previous + * endpoint/2 aliasing. + */ +static uint8_t *pbdrv_usb_nxt_tx_data[PBDRV_USB_NXT_N_ENDPOINTS]; +static uint32_t pbdrv_usb_nxt_tx_len[PBDRV_USB_NXT_N_ENDPOINTS]; /* The flags in the UDP_CSR register are a little strange: writing to * them does not instantly change their value. Their value will change @@ -265,18 +280,12 @@ static void pbdrv_usb_nxt_csr_set_flag(uint8_t endpoint, uint32_t flags) { static void pbdrv_usb_nxt_write_data(int endpoint, const void *ptr_, uint32_t length) { const uint8_t *ptr = ptr_; uint32_t packet_size; - int tx; - if (endpoint != 0 && endpoint != 2) { + if (endpoint != EP_CONTROL && endpoint != EP_BULK_IN) { return; } - tx = endpoint / 2; - - /* The bus is now busy. */ - pbdrv_usb_nxt_state.status = USB_BUSY; - - if (endpoint == 0) { + if (endpoint == EP_CONTROL) { packet_size = MIN(MAX_EP0_SIZE, length); } else { packet_size = MIN(MAX_SND_SIZE, length); @@ -287,18 +296,19 @@ static void pbdrv_usb_nxt_write_data(int endpoint, const void *ptr_, uint32_t le */ if (length > packet_size) { length -= packet_size; - pbdrv_usb_nxt_state.tx_data[tx] = (uint8_t *)(ptr + packet_size); - pbdrv_usb_nxt_state.tx_len[tx] = length; + pbdrv_usb_nxt_tx_data[endpoint] = (uint8_t *)(ptr + packet_size); + pbdrv_usb_nxt_tx_len[endpoint] = length; } else { - if (length == packet_size && endpoint == 0) { - // If we are sending data to the control pipe, we must terminate the data - // with a ZLP. In order to do so, we set the data pointer to non-NULL - // but the length to 0. We do not want to send ZLPs on the Pybricks bulk pipe. - pbdrv_usb_nxt_state.tx_data[tx] = (uint8_t *)(ptr); + if (length == packet_size && endpoint == EP_CONTROL) { + // If we are sending data to the control pipe, we must terminate the + // data with a ZLP. In order to do so, we set the data pointer to + // non-NULL but the length to 0. We do not want to send ZLPs on the + // CDC data pipe. + pbdrv_usb_nxt_tx_data[endpoint] = (uint8_t *)(ptr); } else { - pbdrv_usb_nxt_state.tx_data[tx] = NULL; + pbdrv_usb_nxt_tx_data[endpoint] = NULL; } - pbdrv_usb_nxt_state.tx_len[tx] = 0; + pbdrv_usb_nxt_tx_len[endpoint] = 0; } /* Push a packet into the USB FIFO, and tell the controller to send. */ @@ -319,9 +329,10 @@ static volatile uint32_t pbdrv_usb_rx_len; */ static void pbdrv_usb_rx_update(int endpoint) { - // Given our configuration, we should only get packets on endpoint 1. - // Ignore data on any other endpoint. Data from EP0 is handled separately. - if (endpoint != 1) { + // Given our configuration, we should only get packets on the bulk OUT + // endpoint. Ignore data on any other endpoint. Data from EP0 is handled + // separately. + if (endpoint != EP_BULK_OUT) { pbdrv_usb_nxt_csr_clear_flag(endpoint, AT91C_UDP_RX_DATA_BK0 | AT91C_UDP_RX_DATA_BK1); return; } @@ -330,7 +341,7 @@ static void pbdrv_usb_rx_update(int endpoint) { // Read all available bytes. for (uint16_t i = 0; i < pbdrv_usb_rx_len; i++) { - pbdrv_usb_rx_buf[i] = AT91C_UDP_FDR[1]; + pbdrv_usb_rx_buf[i] = AT91C_UDP_FDR[EP_BULK_OUT]; } // REVISIT: We could switch between RX banks to keep receiving data while @@ -345,7 +356,6 @@ static void pbdrv_usb_rx_update(int endpoint) { * On the other endpoint : Indicates to the host that the endpoint is halted */ static void pbdrv_usb_nxt_send_stall(int endpoint) { - pbdrv_usb_nxt_state.status = USB_UNINITIALIZED; pbdrv_usb_nxt_csr_set_flag(endpoint, AT91C_UDP_FORCESTALL); } @@ -354,21 +364,12 @@ static void pbdrv_usb_nxt_send_null(void) { pbdrv_usb_nxt_write_data(0, NULL, 0); } -typedef struct { - uint8_t request_attrs; /* Request characteristics. */ - uint8_t request; /* Request type. */ - uint16_t value; /* Request-specific value. */ - uint16_t index; /* Request-specific index. */ - uint16_t length; /* The number of bytes transferred in the (optional) - * second phase of the control transfer. */ -} pbdrv_usb_nxt_setup_packet_t; - -static void pbdrv_usb_handle_std_request(pbdrv_usb_nxt_setup_packet_t *packet) { +static void pbdrv_usb_handle_std_request(pbdrv_usb_setup_packet_t *packet) { uint32_t size; uint8_t index; - switch (packet->request) { - case USB_BREQUEST_GET_STATUS: { + switch (packet->bRequest) { + case GET_STATUS: { /* The host wants to know our status. * * If it wants the device status, just reply that the NXT is still @@ -379,19 +380,19 @@ static void pbdrv_usb_handle_std_request(pbdrv_usb_nxt_setup_packet_t *packet) { */ uint16_t response; - if ((packet->request_attrs & USB_BMREQUEST_RCPT) == USB_BMREQUEST_RCPT_DEV) { + if ((packet->bmRequestType & BM_REQ_RECIP_MASK) == BM_REQ_RECIP_DEV) { response = 1; } else { response = 0; } - pbdrv_usb_nxt_write_data(0, &response, 2); + pbdrv_usb_nxt_write_data(EP_CONTROL, &response, 2); } break; - case USB_BREQUEST_CLEAR_FEATURE: - case USB_BREQUEST_SET_INTERFACE: - case USB_BREQUEST_SET_FEATURE: + case CLEAR_FEATURE: + case SET_INTERFACE: + case SET_FEATURE: /* TODO: Refer back to the specs and send the right * replies. This is wrong, even though it happens to not break * on linux. @@ -399,43 +400,43 @@ static void pbdrv_usb_handle_std_request(pbdrv_usb_nxt_setup_packet_t *packet) { pbdrv_usb_nxt_send_null(); break; - case USB_BREQUEST_SET_ADDRESS: + case SET_ADDRESS: /* The host has given the NXT a new USB address. This address * must be set AFTER sending the ack packet. Therefore, we just * remember the new address, and the interrupt handler will set * it when the transmission completes. */ - pbdrv_usb_nxt_state.new_device_address = packet->value; + pbdrv_usb_nxt_new_device_address = packet->wValue; pbdrv_usb_nxt_send_null(); /* If the address change is to 0, do it immediately. * * TODO: Why? And when does this happen? */ - if (pbdrv_usb_nxt_state.new_device_address == 0) { + if (pbdrv_usb_nxt_new_device_address == 0) { *AT91C_UDP_FADDR = AT91C_UDP_FEN; *AT91C_UDP_GLBSTATE = 0; } break; - case USB_BREQUEST_GET_DESCRIPTOR: + case GET_DESCRIPTOR: /* The host requested a descriptor. */ - index = (packet->value & USB_WVALUE_INDEX); - switch ((packet->value & USB_WVALUE_TYPE) >> 8) { - case USB_DESC_TYPE_DEVICE: /* Device descriptor */ + index = (packet->wValue & 0xFF); + switch (packet->wValue >> 8) { + case DESC_TYPE_DEVICE: /* Device descriptor */ size = sizeof(pbdrv_usb_nxt_device_descriptor); - pbdrv_usb_nxt_write_data(0, &pbdrv_usb_nxt_device_descriptor, - MIN(size, packet->length)); + pbdrv_usb_nxt_write_data(EP_CONTROL, &pbdrv_usb_nxt_device_descriptor, + MIN(size, packet->wLength)); break; - case USB_DESC_TYPE_CONFIG: /* Configuration descriptor */ + case DESC_TYPE_CONFIGURATION: /* Configuration descriptor */ size = sizeof(pbdrv_usb_nxt_full_config); - pbdrv_usb_nxt_write_data(0, &pbdrv_usb_nxt_full_config, - MIN(size, packet->length)); + pbdrv_usb_nxt_write_data(EP_CONTROL, &pbdrv_usb_nxt_full_config, + MIN(size, packet->wLength)); break; - case USB_DESC_TYPE_STR: /* String or language info. */ + case DESC_TYPE_STRING: /* String or language info. */ { const void *desc = 0; switch (index) { @@ -458,174 +459,124 @@ static void pbdrv_usb_handle_std_request(pbdrv_usb_nxt_setup_packet_t *packet) { } if (desc) { - pbdrv_usb_nxt_write_data(0, desc, MIN(size, packet->length)); + pbdrv_usb_nxt_write_data(EP_CONTROL, desc, MIN(size, packet->wLength)); } else { - pbdrv_usb_nxt_send_stall(0); + pbdrv_usb_nxt_send_stall(EP_CONTROL); } } break; - case USB_DESC_TYPE_BOS: /* BOS descriptor */ - size = sizeof(pbdrv_usb_bos_desc_set.s); - pbdrv_usb_nxt_write_data(0, &pbdrv_usb_bos_desc_set, MIN(size, packet->length)); - break; - default: /* Unknown descriptor, tell the host by stalling. */ - pbdrv_usb_nxt_send_stall(0); + pbdrv_usb_nxt_send_stall(EP_CONTROL); } break; - case USB_BREQUEST_GET_CONFIG: + case GET_CONFIGURATION: /* The host wants to know the ID of the current configuration. */ - pbdrv_usb_nxt_write_data(0, (uint8_t *)&(pbdrv_usb_nxt_state.current_config), 1); + pbdrv_usb_nxt_write_data(EP_CONTROL, &pbdrv_usb_nxt_current_config, 1); break; - case USB_BREQUEST_SET_CONFIG: + case SET_CONFIGURATION: /* The host selected a new configuration. */ - pbdrv_usb_nxt_state.current_config = packet->value; + pbdrv_usb_nxt_current_config = packet->wValue; /* we ack */ pbdrv_usb_nxt_send_null(); /* we set the register in configured mode */ - *AT91C_UDP_GLBSTATE = packet->value > 0 ? + *AT91C_UDP_GLBSTATE = packet->wValue > 0 ? (AT91C_UDP_CONFG | AT91C_UDP_FADDEN) :AT91C_UDP_FADDEN; - /* TODO: Make this a little nicer. Not quite sure how. */ - - AT91C_UDP_CSR[1] = AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_BULK_OUT; - while (AT91C_UDP_CSR[1] != (AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_BULK_OUT)) { + /* Enable the CDC data and notification endpoints. */ + AT91C_UDP_CSR[EP_BULK_OUT] = AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_BULK_OUT; + while (AT91C_UDP_CSR[EP_BULK_OUT] != (AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_BULK_OUT)) { ; } - AT91C_UDP_CSR[2] = AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_BULK_IN; - while (AT91C_UDP_CSR[2] != (AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_BULK_IN)) { + AT91C_UDP_CSR[EP_BULK_IN] = AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_BULK_IN; + while (AT91C_UDP_CSR[EP_BULK_IN] != (AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_BULK_IN)) { ; } - AT91C_UDP_CSR[3] = 0; - while (AT91C_UDP_CSR[3] != 0) { + + AT91C_UDP_CSR[EP_NOTIF] = AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_INT_IN; + while (AT91C_UDP_CSR[EP_NOTIF] != (AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_INT_IN)) { ; } - pbdrv_usb_nxt_state.status = USB_READY; + pbdrv_usb_nxt_configured = packet->wValue > 0; break; - case USB_BREQUEST_GET_INTERFACE: /* TODO: This should respond, not stall. */ - case USB_BREQUEST_SET_DESCRIPTOR: + case GET_INTERFACE: /* TODO: This should respond, not stall. */ + case SET_DESCRIPTOR: default: - pbdrv_usb_nxt_send_stall(0); + pbdrv_usb_nxt_send_stall(EP_CONTROL); break; } } -static void pbdrv_usb_nxt_handle_class_request(pbdrv_usb_nxt_setup_packet_t *packet) { - switch (packet->request_attrs & USB_BMREQUEST_RCPT) { - case USB_BMREQUEST_RCPT_INT: - // Ignoring wIndex for now as we only have one interface. - switch (packet->request) { - case PBIO_PYBRICKS_USB_INTERFACE_READ_CHARACTERISTIC_GATT: - // Standard GATT characteristic - switch (packet->value) { - case 0x2A00: { // device name - const char *name = pbdrv_bluetooth_get_hub_name(); - pbdrv_usb_nxt_write_data(0, name, - MIN(strlen(name), packet->length)); - break; - } - case 0x2A26: { // firmware revision - const char *fw = PBIO_VERSION_STR; - pbdrv_usb_nxt_write_data(0, fw, - MIN(strlen(fw), packet->length)); - break; - } - case 0x2A28: { // software revision - const char *sw = PBIO_PROTOCOL_VERSION_STR; - pbdrv_usb_nxt_write_data(0, sw, - MIN(strlen(sw), packet->length)); - break; - } - default: - pbdrv_usb_nxt_send_stall(0); - break; - } - break; - case PBIO_PYBRICKS_USB_INTERFACE_READ_CHARACTERISTIC_PYBRICKS: - // Pybricks characteristic - switch (packet->value) { - case 0x0003: { // hub capabilities - uint8_t caps[PBIO_PYBRICKS_HUB_CAPABILITIES_VALUE_SIZE]; - pbio_pybricks_hub_capabilities(caps, - MAX_RCV_SIZE - 1, - PBSYS_CONFIG_APP_FEATURE_FLAGS, - pbsys_storage_get_maximum_program_size(), - PBSYS_CONFIG_HMI_NUM_SLOTS); - pbdrv_usb_nxt_write_data(0, caps, MIN(sizeof(caps), packet->length)); - break; - } - default: - pbdrv_usb_nxt_send_stall(0); - break; - } - break; - default: - pbdrv_usb_nxt_send_stall(0); - break; - } +static void pbdrv_usb_nxt_handle_class_request(pbdrv_usb_setup_packet_t *packet) { + // CDC class requests are directed at the comm interface. + if ((packet->bmRequestType & BM_REQ_RECIP_MASK) != BM_REQ_RECIP_IF) { + pbdrv_usb_nxt_send_stall(EP_CONTROL); + return; + } + + switch (packet->bRequest) { + case USB_CDC_REQ_SET_LINE_CODING: + // The 7-byte line coding follows in an OUT data stage, which we + // read on the next EP0 RX_DATA interrupt. Do not ack yet. + pbdrv_usb_nxt_expect_line_coding = true; break; + + case USB_CDC_REQ_GET_LINE_CODING: + pbdrv_usb_nxt_write_data(EP_CONTROL, pbdrv_usb_nxt_line_coding, + MIN(sizeof(pbdrv_usb_nxt_line_coding), packet->wLength)); + break; + + case USB_CDC_REQ_SET_CONTROL_LINE_STATE: + // DTR asserted means a host app opened the serial port. This is + // the USB analog of a BLE host subscribing to notifications, and + // is how we detect connect/disconnect. + pbdrv_usb_on_dtr_changed( + (packet->wValue & USB_CDC_CONTROL_LINE_STATE_DTR) != 0); + pbdrv_usb_nxt_send_null(); + break; + default: - pbdrv_usb_nxt_send_stall(0); + pbdrv_usb_nxt_send_stall(EP_CONTROL); break; } } /* Handle receiving and responding to setup packets on EP0. */ -static uint32_t pbdrv_usb_nxt_manage_setup_packet(void) { +static void pbdrv_usb_nxt_manage_setup_packet(void) { /* The structure of a USB setup packet. */ - pbdrv_usb_nxt_setup_packet_t packet; + pbdrv_usb_setup_packet_t packet; /* Read the packet from the FIFO into the above packet struct. */ - packet.request_attrs = AT91C_UDP_FDR[0]; - packet.request = AT91C_UDP_FDR[0]; - packet.value = (AT91C_UDP_FDR[0] & 0xFF) | (AT91C_UDP_FDR[0] << 8); - packet.index = (AT91C_UDP_FDR[0] & 0xFF) | (AT91C_UDP_FDR[0] << 8); - packet.length = (AT91C_UDP_FDR[0] & 0xFF) | (AT91C_UDP_FDR[0] << 8); - - if ((packet.request_attrs & USB_BMREQUEST_DIR) == USB_BMREQUEST_D_TO_H) { - pbdrv_usb_nxt_csr_set_flag(0, AT91C_UDP_DIR); /* TODO: contradicts atmel doc p475 */ + packet.bmRequestType = AT91C_UDP_FDR[EP_CONTROL]; + packet.bRequest = AT91C_UDP_FDR[EP_CONTROL]; + packet.wValue = (AT91C_UDP_FDR[EP_CONTROL] & 0xFF) | (AT91C_UDP_FDR[EP_CONTROL] << 8); + packet.wIndex = (AT91C_UDP_FDR[EP_CONTROL] & 0xFF) | (AT91C_UDP_FDR[EP_CONTROL] << 8); + packet.wLength = (AT91C_UDP_FDR[EP_CONTROL] & 0xFF) | (AT91C_UDP_FDR[EP_CONTROL] << 8); + + if ((packet.bmRequestType & BM_REQ_DIR_MASK) == BM_REQ_DIR_D2H) { + pbdrv_usb_nxt_csr_set_flag(EP_CONTROL, AT91C_UDP_DIR); /* TODO: contradicts atmel doc p475 */ } - pbdrv_usb_nxt_csr_clear_flag(0, AT91C_UDP_RXSETUP); + pbdrv_usb_nxt_csr_clear_flag(EP_CONTROL, AT91C_UDP_RXSETUP); - switch (packet.request_attrs & USB_BMREQUEST_TYPE) { - case USB_BMREQUEST_TYPE_STD: + switch (packet.bmRequestType & BM_REQ_TYPE_MASK) { + case BM_REQ_TYPE_STANDARD: pbdrv_usb_handle_std_request(&packet); break; - case USB_BMREQUEST_TYPE_CLASS: + case BM_REQ_TYPE_CLASS: pbdrv_usb_nxt_handle_class_request(&packet); break; - case USB_BMREQUEST_TYPE_VENDOR: - switch (packet.request) { - case PBDRV_USB_VENDOR_REQ_WEBUSB: - // Since there is only one WebUSB descriptor, we ignore the index. - pbdrv_usb_nxt_write_data(0, &pbdrv_usb_webusb_landing_page, - MIN(pbdrv_usb_webusb_landing_page.s.bLength, packet.length)); - break; - case PBDRV_USB_VENDOR_REQ_MS_20: - // Since there is only one MS descriptor, we ignore the index. - pbdrv_usb_nxt_write_data(0, &pbdrv_usb_ms_20_desc_set, - MIN(sizeof(pbdrv_usb_ms_20_desc_set.s), packet.length)); - break; - default: - pbdrv_usb_nxt_send_stall(0); - break; - } - break; default: - pbdrv_usb_nxt_send_stall(0); + pbdrv_usb_nxt_send_stall(EP_CONTROL); break; } - - return packet.request; } /* The main USB interrupt handler. */ @@ -642,7 +593,7 @@ static void pbdrv_usb_nxt_isr(void) { /* End of bus reset. Starting the device setup procedure. */ if (isr & AT91C_UDP_ENDBUSRES) { - pbdrv_usb_nxt_state.status = USB_UNINITIALIZED; + pbdrv_usb_nxt_configured = false; /* Disable and clear all interruptions, reverting to the base * state. @@ -655,7 +606,7 @@ static void pbdrv_usb_nxt_isr(void) { *AT91C_UDP_RSTEP = 0; /* Reset internal state. */ - pbdrv_usb_nxt_state.current_config = 0; + pbdrv_usb_nxt_current_config = 0; /* Reset EP0 to a basic control endpoint. */ /* TODO: The while is ugly. Fix it. */ @@ -693,14 +644,11 @@ static void pbdrv_usb_nxt_isr(void) { if (isr & AT91C_UDP_RXSUSP) { *AT91C_UDP_ICR = AT91C_UDP_RXSUSP; isr &= ~AT91C_UDP_RXSUSP; - pbdrv_usb_nxt_state.pre_suspend_status = pbdrv_usb_nxt_state.status; - pbdrv_usb_nxt_state.status = USB_SUSPENDED; } if (isr & AT91C_UDP_RXRSM) { *AT91C_UDP_ICR = AT91C_UDP_RXRSM; isr &= ~AT91C_UDP_RXRSM; - pbdrv_usb_nxt_state.status = pbdrv_usb_nxt_state.pre_suspend_status; } for (endpoint = 0; endpoint < PBDRV_USB_NXT_N_ENDPOINTS; endpoint++) { @@ -712,7 +660,33 @@ static void pbdrv_usb_nxt_isr(void) { if (endpoint == 0) { if (AT91C_UDP_CSR[0] & AT91C_UDP_RXSETUP) { - csr = pbdrv_usb_nxt_manage_setup_packet(); + pbdrv_usb_nxt_manage_setup_packet(); + return; + } + + if (AT91C_UDP_CSR[0] & (AT91C_UDP_RX_DATA_BK0 | AT91C_UDP_RX_DATA_BK1)) { + /* OUT data on the control endpoint. This is either the data stage + * of a host-to-device control write (only SET_LINE_CODING for us) + * or the zero-length OUT status stage that terminates a control + * read (e.g. GET_DESCRIPTOR). + */ + if (pbdrv_usb_nxt_expect_line_coding) { + uint32_t count = (AT91C_UDP_CSR[0] & AT91C_UDP_RXBYTECNT) >> 16; + for (uint32_t i = 0; i < count && i < sizeof(pbdrv_usb_nxt_line_coding); i++) { + pbdrv_usb_nxt_line_coding[i] = AT91C_UDP_FDR[0]; + } + pbdrv_usb_nxt_expect_line_coding = false; + pbdrv_usb_nxt_csr_clear_flag(0, AT91C_UDP_RX_DATA_BK0 | AT91C_UDP_RX_DATA_BK1); + /* Acknowledge the SET_LINE_CODING data stage with a ZLP. */ + pbdrv_usb_nxt_send_null(); + } else { + /* OUT status stage of a control read. The hardware ACKs it when + * we clear the RX flag; we must NOT send anything back here, or + * we would leave a stray IN packet armed on EP0 and corrupt the + * next control transfer. + */ + pbdrv_usb_nxt_csr_clear_flag(0, AT91C_UDP_RX_DATA_BK0 | AT91C_UDP_RX_DATA_BK1); + } return; } } @@ -723,9 +697,9 @@ static void pbdrv_usb_nxt_isr(void) { if (csr & AT91C_UDP_RX_DATA_BK0 || csr & AT91C_UDP_RX_DATA_BK1) { - if (endpoint == 1) { - AT91C_UDP_CSR[1] &= ~AT91C_UDP_EPEDS; - while (AT91C_UDP_CSR[1] & AT91C_UDP_EPEDS) { + if (endpoint == EP_BULK_OUT) { + AT91C_UDP_CSR[EP_BULK_OUT] &= ~AT91C_UDP_EPEDS; + while (AT91C_UDP_CSR[EP_BULK_OUT] & AT91C_UDP_EPEDS) { ; } } @@ -741,25 +715,27 @@ static void pbdrv_usb_nxt_isr(void) { /* so first we will reset this flag */ pbdrv_usb_nxt_csr_clear_flag(endpoint, AT91C_UDP_TXCOMP); - if (pbdrv_usb_nxt_state.new_device_address > 0) { + if (pbdrv_usb_nxt_new_device_address > 0) { /* the previous message received was SET_ADDR */ /* now that the computer ACK our send_null(), we can * set this address for real */ /* we set the specified usb address in the controller */ - *AT91C_UDP_FADDR = AT91C_UDP_FEN | pbdrv_usb_nxt_state.new_device_address; + *AT91C_UDP_FADDR = AT91C_UDP_FEN | pbdrv_usb_nxt_new_device_address; /* and we tell the controller that we are in addressed mode now */ *AT91C_UDP_GLBSTATE = AT91C_UDP_FADDEN; - pbdrv_usb_nxt_state.new_device_address = 0; + pbdrv_usb_nxt_new_device_address = 0; } /* and we will send the following data */ - if (pbdrv_usb_nxt_state.tx_data[endpoint] != NULL) { - pbdrv_usb_nxt_write_data(endpoint, pbdrv_usb_nxt_state.tx_data[endpoint], - pbdrv_usb_nxt_state.tx_len[endpoint]); + if (pbdrv_usb_nxt_tx_data[endpoint] != NULL) { + pbdrv_usb_nxt_write_data(endpoint, pbdrv_usb_nxt_tx_data[endpoint], + pbdrv_usb_nxt_tx_len[endpoint]); } else { /* then it means that we sent all the data and the host has acknowledged it */ - pbdrv_usb_nxt_state.status = USB_READY; + if (endpoint == EP_BULK_IN) { + pbdrv_usb_nxt_transmitting = false; + } pbio_os_request_poll(); } return; @@ -789,11 +765,21 @@ void pbdrv_usb_init_device(void) { for (uint8_t i = 0; i < PBIO_ARRAY_SIZE(pbdrv_usb_str_desc_serial.wString); i++) { pbdrv_usb_str_desc_serial.wString[i] = bluetooth_address_string[i]; } - pbdrv_usb_str_desc_serial.bLength = PBIO_ARRAY_SIZE(pbdrv_usb_str_desc_serial.wString) * 2; - pbdrv_usb_str_desc_serial.bDescriptorType = USB_DESC_TYPE_STR, + pbdrv_usb_str_desc_serial.bLength = sizeof(pbdrv_usb_str_desc_serial); + pbdrv_usb_str_desc_serial.bDescriptorType = DESC_TYPE_STRING; pbdrv_usb_nxt_deinit(); - memset((void *)&pbdrv_usb_nxt_state, 0, sizeof(pbdrv_usb_nxt_state)); + + pbdrv_usb_nxt_configured = false; + pbdrv_usb_nxt_transmitting = false; + pbdrv_usb_nxt_expect_line_coding = false; + pbdrv_usb_nxt_new_device_address = 0; + pbdrv_usb_nxt_current_config = 0; + for (int i = 0; i < PBDRV_USB_NXT_N_ENDPOINTS; i++) { + pbdrv_usb_nxt_tx_data[i] = NULL; + pbdrv_usb_nxt_tx_len[i] = 0; + } + pbdrv_usb_rx_len = 0; uint32_t state = nx_interrupts_disable(); @@ -838,61 +824,35 @@ void pbdrv_usb_deinit_device(void) { } pbio_error_t pbdrv_usb_wait_until_configured(pbio_os_state_t *state) { - PBIO_OS_ASYNC_BEGIN(state); - - PBIO_OS_AWAIT_UNTIL(state, pbdrv_usb_nxt_state.status == USB_READY); - - PBIO_OS_ASYNC_END(PBIO_SUCCESS); + return pbdrv_usb_nxt_configured ? PBIO_SUCCESS : PBIO_ERROR_AGAIN; } bool pbdrv_usb_is_ready(void) { - return pbdrv_usb_nxt_state.status != USB_UNINITIALIZED; + return pbdrv_usb_nxt_configured; } pbdrv_usb_bcd_t pbdrv_usb_get_bcd(void) { return PBDRV_USB_BCD_NONE; } -pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uint32_t size) { +pbio_error_t pbdrv_usb_tx_chunk(pbio_os_state_t *state, const uint8_t *data, uint32_t size) { static pbio_os_timer_t timer; PBIO_OS_ASYNC_BEGIN(state); - // REVISIT: Won't work if we include this check. - // if (pbdrv_usb_nxt_state.status != USB_READY) { - // return PBIO_ERROR_BUSY; - // } - - pbio_os_timer_set(&timer, PBDRV_USB_TRANSMIT_TIMEOUT); - pbdrv_usb_nxt_write_data(2, data, size); - - PBIO_OS_AWAIT_UNTIL(state, pbdrv_usb_nxt_state.status == USB_READY || pbio_os_timer_is_expired(&timer)); - if (pbio_os_timer_is_expired(&timer)) { - return PBIO_ERROR_TIMEDOUT; + if (pbdrv_usb_nxt_transmitting) { + return PBIO_ERROR_BUSY; } - PBIO_OS_ASYNC_END(PBIO_SUCCESS); -} - -pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t code) { - - static pbio_os_timer_t timer; - - static uint8_t usb_response_buf[PBIO_PYBRICKS_USB_MESSAGE_SIZE(sizeof(uint32_t))] __aligned(4) = { PBIO_PYBRICKS_IN_EP_MSG_RESPONSE }; - - PBIO_OS_ASYNC_BEGIN(state); + pbdrv_usb_nxt_transmitting = true; + pbio_os_timer_set(&timer, PBDRV_USB_TRANSMIT_TIMEOUT); - // REVISIT: Won't work if we include this check. - // if (pbdrv_usb_nxt_state.status != USB_READY) { - // return PBIO_ERROR_BUSY; - // } + // Transmit the raw bytes. Framing is handled by the common driver. + pbdrv_usb_nxt_write_data(EP_BULK_IN, data, size); - pbio_os_timer_set(&timer, PBDRV_USB_TRANSMIT_TIMEOUT); - pbio_set_uint32_le(&usb_response_buf[1], code); - pbdrv_usb_nxt_write_data(2, usb_response_buf, sizeof(usb_response_buf)); + PBIO_OS_AWAIT_UNTIL(state, !pbdrv_usb_nxt_transmitting || pbio_os_timer_is_expired(&timer)); - PBIO_OS_AWAIT_UNTIL(state, pbdrv_usb_nxt_state.status == USB_READY || pbio_os_timer_is_expired(&timer)); if (pbio_os_timer_is_expired(&timer)) { return PBIO_ERROR_TIMEDOUT; } @@ -901,8 +861,9 @@ pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t } pbio_error_t pbdrv_usb_tx_reset(pbio_os_state_t *state) { - // REVISIT: Make async. - pbdrv_usb_init_device(); + pbdrv_usb_nxt_tx_data[EP_BULK_IN] = NULL; + pbdrv_usb_nxt_tx_len[EP_BULK_IN] = 0; + pbdrv_usb_nxt_transmitting = false; return PBIO_SUCCESS; } @@ -918,9 +879,8 @@ uint32_t pbdrv_usb_get_data_and_start_receive(uint8_t *data) { pbdrv_usb_rx_len = 0; // Get ready to receive the next message. - if (pbdrv_usb_nxt_state.status > USB_UNINITIALIZED - && pbdrv_usb_nxt_state.status != USB_SUSPENDED) { - AT91C_UDP_CSR[1] |= AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_BULK_OUT; + if (pbdrv_usb_nxt_configured) { + AT91C_UDP_CSR[EP_BULK_OUT] |= AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_BULK_OUT; } return result; diff --git a/lib/pbio/drv/usb/usb_simulation.c b/lib/pbio/drv/usb/usb_simulation.c index b0dfd1603..0704791c8 100644 --- a/lib/pbio/drv/usb/usb_simulation.c +++ b/lib/pbio/drv/usb/usb_simulation.c @@ -10,7 +10,6 @@ #include #include -#include #include #include @@ -31,22 +30,30 @@ pbdrv_usb_bcd_t pbdrv_usb_get_bcd(void) { return PBDRV_USB_BCD_NONE; } -pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uint32_t size) { +pbio_error_t pbdrv_usb_tx_chunk(pbio_os_state_t *state, const uint8_t *data, uint32_t size) { static pbio_os_timer_t timer; PBIO_OS_ASYNC_BEGIN(state); - - // Stdout also goes to native stdout. - if (size > 2 && data[0] == PBIO_PYBRICKS_IN_EP_MSG_EVENT && data[1] == PBIO_PYBRICKS_EVENT_WRITE_STDOUT) { - int ret = write(STDOUT_FILENO, data + 2, size - 2); + // The common driver hands us a COBS-encoded frame with a trailing + // delimiter. This mock only forwards stdout to the native console, so + // decode the frame and write out the payload of stdout events only. + // REVISIT: This assumes that we do one chunk per stdout event. That is + // currently true for the logic in usb.c, but we should revise this to make + // it like the RX path if we turn it into an actual stream. + uint8_t msg[PBDRV_USB_MAX_DECODED_MESSAGE_SIZE]; + uint32_t msg_size = pbdrv_usb_cobs_decode(data, size - 1, msg, sizeof(msg)); + + if (msg_size >= 2 && msg[0] == PBIO_PYBRICKS_IN_EP_MSG_EVENT && + msg[1] == PBIO_PYBRICKS_EVENT_WRITE_STDOUT) { + int ret = write(STDOUT_FILENO, &msg[2], msg_size - 2); (void)ret; - } - #ifdef PBDRV_CONFIG_RPROC_VIRTUAL - pbdrv_rproc_virtual_socket_send(data + 1, size - 1); - #endif + #ifdef PBDRV_CONFIG_RPROC_VIRTUAL + pbdrv_rproc_virtual_socket_send(&msg[2], msg_size - 2); + #endif + } // Simulate some I/O time. PBIO_OS_AWAIT_MS(state, &timer, 1); @@ -54,37 +61,17 @@ pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uin PBIO_OS_ASYNC_END(PBIO_SUCCESS); } -pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t code) { - - static pbio_os_timer_t timer; - - static uint8_t response_buf[1 + sizeof(uint32_t)] __attribute__((aligned(4))) = - { PBIO_PYBRICKS_IN_EP_MSG_RESPONSE }; - - PBIO_OS_ASYNC_BEGIN(state); - - // Response is just the error code. - pbio_set_uint32_le(&response_buf[1], code); - - // Simulation never actually sends this. - - // Simulate some I/O time. - PBIO_OS_AWAIT_MS(state, &timer, 2); - - PBIO_OS_ASYNC_END(PBIO_SUCCESS); -} - pbio_error_t pbdrv_usb_tx_reset(pbio_os_state_t *state) { return PBIO_SUCCESS; } -static uint8_t usb_in_buf[PBDRV_CONFIG_USB_MAX_PACKET_SIZE]; +static uint8_t usb_in_buf[PBDRV_USB_MAX_ENCODED_MESSAGE_SIZE]; static uint32_t usb_in_size; uint32_t pbdrv_usb_get_data_and_start_receive(uint8_t *data) { // Invalid size. - if (usb_in_size > PBDRV_CONFIG_USB_MAX_PACKET_SIZE) { + if (usb_in_size > sizeof(usb_in_buf)) { usb_in_size = 0; } @@ -102,19 +89,14 @@ uint32_t pbdrv_usb_get_data_and_start_receive(uint8_t *data) { return size; } -// Simulates incoming USB data by reading from native host stdin. In -// MicroPython, it drives the REPL. +// Simulates incoming USB data by reading the raw byte stream from native host +// stdin. In MicroPython, it drives the REPL. static pbio_error_t pbdrv_usb_test_process_thread(pbio_os_state_t *state, void *context) { static pbio_os_timer_t timer; PBIO_OS_ASYNC_BEGIN(state); - // Simulate subscribe event. - usb_in_buf[0] = PBIO_PYBRICKS_OUT_EP_MSG_SUBSCRIBE; - usb_in_buf[1] = 1; - usb_in_size = 2; - #ifdef PBDRV_CONFIG_RUN_ON_CI // CI and MicroPython test suite have lots of problems with stdin. It is // only needed for the REPL and interactive input, so don't bother on CI. @@ -129,12 +111,15 @@ static pbio_error_t pbdrv_usb_test_process_thread(pbio_os_state_t *state, void * continue; } - // This has been made non-blocking in platform.c. - ssize_t num_read = read(STDIN_FILENO, &usb_in_buf[2], sizeof(usb_in_buf) - 2); + // Read raw bytes from native stdin and present them to the common + // driver as a COBS-framed write stdin command, the same way a real + // host would. This has been made non-blocking in platform.c. + static uint8_t cmd[PBDRV_USB_MAX_DECODED_MESSAGE_SIZE]; + cmd[0] = PBIO_PYBRICKS_OUT_EP_MSG_COMMAND; + cmd[1] = PBIO_PYBRICKS_COMMAND_WRITE_STDIN; + ssize_t num_read = read(STDIN_FILENO, &cmd[2], sizeof(cmd) - 2); if (num_read > 0) { - usb_in_buf[0] = PBIO_PYBRICKS_OUT_EP_MSG_COMMAND; - usb_in_buf[1] = PBIO_PYBRICKS_COMMAND_WRITE_STDIN; - usb_in_size = 2 + num_read; + usb_in_size = pbdrv_usb_cobs_encode(cmd, 2 + num_read, usb_in_buf); } } @@ -144,6 +129,9 @@ static pbio_error_t pbdrv_usb_test_process_thread(pbio_os_state_t *state, void * void pbdrv_usb_init_device(void) { static pbio_os_process_t pbdrv_usb_test_process; pbio_os_process_start(&pbdrv_usb_test_process, pbdrv_usb_test_process_thread, NULL); + + // No physical port to open, so report the connection as active right away. + pbdrv_usb_on_dtr_changed(true); } void pbdrv_usb_deinit_device(void) { diff --git a/lib/pbio/drv/usb/usb_simulation_pico.c b/lib/pbio/drv/usb/usb_simulation_pico.c index 6875a48bb..69b41e39c 100644 --- a/lib/pbio/drv/usb/usb_simulation_pico.c +++ b/lib/pbio/drv/usb/usb_simulation_pico.c @@ -26,6 +26,10 @@ static lwrb_t pbdrv_usb_simulation_pico_in_ringbuf; static volatile bool pbdrv_usb_simulation_tx_ready; +// Size of the UART receive ring buffer. This is a mock with no real USB +// hardware, so the value is arbitrary. +#define PBDRV_USB_SIMULATION_PICO_RX_RINGBUF_SIZE (128) + pbio_error_t pbdrv_usb_wait_until_configured(pbio_os_state_t *state) { return PBIO_ERROR_NOT_SUPPORTED; } @@ -38,18 +42,27 @@ pbdrv_usb_bcd_t pbdrv_usb_get_bcd(void) { return PBDRV_USB_BCD_NONE; } -pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uint32_t size) { +pbio_error_t pbdrv_usb_tx_chunk(pbio_os_state_t *state, const uint8_t *data, uint32_t size) { PBIO_OS_ASYNC_BEGIN(state); - // Only care about stdout for now. - if (size < 2 || data[0] != PBIO_PYBRICKS_IN_EP_MSG_EVENT || - data[1] != PBIO_PYBRICKS_EVENT_WRITE_STDOUT) { + // The common driver hands us a COBS-encoded frame with a trailing + // delimiter. This mock only forwards stdout to the UART, so decode the + // frame and ignore anything that is not a stdout event. + // REVISIT: This assumes that we do one chunk per stdout event. That is + // currently true for the logic in usb.c, but we should revise this to make + // it like the RX path if we turn it into an actual stream. + static uint8_t msg[PBDRV_USB_MAX_DECODED_MESSAGE_SIZE]; + static uint32_t msg_size; + msg_size = pbdrv_usb_cobs_decode(data, size - 1, msg, sizeof(msg)); + + if (msg_size < 2 || msg[0] != PBIO_PYBRICKS_IN_EP_MSG_EVENT || + msg[1] != PBIO_PYBRICKS_EVENT_WRITE_STDOUT) { return PBIO_SUCCESS; } static int i; - for (i = 2; i < size; i++) { + for (i = 2; i < msg_size; i++) { if (!uart_is_writable(uart_default)) { pbdrv_usb_simulation_tx_ready = false; // Enable TX interrupt to be notified when ready. @@ -57,33 +70,24 @@ pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uin PBIO_OS_AWAIT_UNTIL(state, pbdrv_usb_simulation_tx_ready); } - uart_get_hw(uart_default)->dr = data[i]; + uart_get_hw(uart_default)->dr = msg[i]; } PBIO_OS_ASYNC_END(PBIO_SUCCESS); } -pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t code) { - - PBIO_OS_ASYNC_BEGIN(state); - - // Not implemented. - - PBIO_OS_ASYNC_END(PBIO_SUCCESS); -} - pbio_error_t pbdrv_usb_tx_reset(pbio_os_state_t *state) { return PBIO_SUCCESS; } -static uint8_t pbdrv_usb_simulation_pico_in_buf[PBDRV_CONFIG_USB_MAX_PACKET_SIZE]; +static uint8_t pbdrv_usb_simulation_pico_in_buf[PBDRV_USB_MAX_ENCODED_MESSAGE_SIZE]; static uint32_t pbdrv_usb_simulation_pico_in_size; uint32_t pbdrv_usb_get_data_and_start_receive(uint8_t *data) { uint32_t size = pbdrv_usb_simulation_pico_in_size; // Invalid size. - if (size > PBDRV_CONFIG_USB_MAX_PACKET_SIZE) { + if (size > sizeof(pbdrv_usb_simulation_pico_in_buf)) { pbdrv_usb_simulation_pico_in_size = 0; return 0; } @@ -106,23 +110,21 @@ uint32_t pbdrv_usb_get_data_and_start_receive(uint8_t *data) { static pbio_error_t pbdrv_usb_test_process_thread(pbio_os_state_t *state, void *context) { PBIO_OS_ASYNC_BEGIN(state); - // Simulate subscribe event. - pbdrv_usb_simulation_pico_in_buf[0] = PBIO_PYBRICKS_OUT_EP_MSG_SUBSCRIBE; - pbdrv_usb_simulation_pico_in_buf[1] = 1; - pbdrv_usb_simulation_pico_in_size = 2; - for (;;) { static size_t available; PBIO_OS_AWAIT_UNTIL(state, pbdrv_usb_simulation_pico_in_size == 0 && (available = lwrb_get_full(&pbdrv_usb_simulation_pico_in_ringbuf)) > 0); - available = pbio_int_math_clamp(available, PBDRV_CONFIG_USB_MAX_PACKET_SIZE - 2); + available = pbio_int_math_clamp(available, PBDRV_USB_MAX_DECODED_MESSAGE_SIZE - 2); - pbdrv_usb_simulation_pico_in_buf[0] = PBIO_PYBRICKS_OUT_EP_MSG_COMMAND; - pbdrv_usb_simulation_pico_in_buf[1] = PBIO_PYBRICKS_COMMAND_WRITE_STDIN; - lwrb_read(&pbdrv_usb_simulation_pico_in_ringbuf, - &pbdrv_usb_simulation_pico_in_buf[2], available); - pbdrv_usb_simulation_pico_in_size = 2 + available; + // Wrap the raw UART bytes as a write stdin command and COBS-encode it, + // the same way a real host would, so the common driver can decode it. + static uint8_t cmd[PBDRV_USB_MAX_DECODED_MESSAGE_SIZE]; + cmd[0] = PBIO_PYBRICKS_OUT_EP_MSG_COMMAND; + cmd[1] = PBIO_PYBRICKS_COMMAND_WRITE_STDIN; + lwrb_read(&pbdrv_usb_simulation_pico_in_ringbuf, &cmd[2], available); + pbdrv_usb_simulation_pico_in_size = pbdrv_usb_cobs_encode( + cmd, 2 + available, pbdrv_usb_simulation_pico_in_buf); } PBIO_OS_ASYNC_END(PBIO_SUCCESS); @@ -147,7 +149,7 @@ static void pbdrv_usb_simulation_pico_uart_rx_irq(void) { } void pbdrv_usb_init_device(void) { - static uint8_t in_ringbuf_data[PBDRV_CONFIG_USB_MAX_PACKET_SIZE * 2]; + static uint8_t in_ringbuf_data[PBDRV_USB_SIMULATION_PICO_RX_RINGBUF_SIZE]; lwrb_init(&pbdrv_usb_simulation_pico_in_ringbuf, in_ringbuf_data, sizeof(in_ringbuf_data)); setup_default_uart(); @@ -157,6 +159,10 @@ void pbdrv_usb_init_device(void) { static pbio_os_process_t pbdrv_usb_test_process; pbio_os_process_start(&pbdrv_usb_test_process, pbdrv_usb_test_process_thread, NULL); + + // No physical port to detect, so report the host connection as active + // right away (USB analog of a BLE host subscribing). + pbdrv_usb_on_dtr_changed(true); } void pbdrv_usb_deinit_device(void) { diff --git a/lib/pbio/drv/usb/usb_stm32.c b/lib/pbio/drv/usb/usb_stm32.c index 65d3f46e9..2e571aafa 100644 --- a/lib/pbio/drv/usb/usb_stm32.c +++ b/lib/pbio/drv/usb/usb_stm32.c @@ -31,15 +31,9 @@ #include "./usb.h" #include "./usb_stm32.h" - -#if USBD_PYBRICKS_MAX_PACKET_SIZE != PBDRV_CONFIG_USB_MAX_PACKET_SIZE -#error Inconsistent USB packet size -#endif - // These buffers need to be 32-bit aligned because the USB driver moves data // to/from FIFOs in 32-bit chunks. static uint8_t usb_in_buf[USBD_PYBRICKS_MAX_PACKET_SIZE] __aligned(4); -static uint8_t usb_response_buf[PBIO_PYBRICKS_USB_MESSAGE_SIZE(sizeof(uint32_t))] __aligned(4) = { PBIO_PYBRICKS_IN_EP_MSG_RESPONSE }; static volatile uint32_t usb_in_sz; static volatile bool transmitting; @@ -217,66 +211,11 @@ static USBD_StatusTypeDef Pybricks_Itf_TransmitCplt(uint8_t *Buf, uint32_t Len, return USBD_OK; } -static USBD_StatusTypeDef Pybricks_Itf_ReadCharacteristic(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req) { - USBD_StatusTypeDef ret = USBD_OK; - - switch (req->bRequest) { - case PBIO_PYBRICKS_USB_INTERFACE_READ_CHARACTERISTIC_GATT: - switch (req->wValue) { - case 0x2A00: { - // GATT Device Name characteristic - const char *name = pbdrv_bluetooth_get_hub_name(); - (void)USBD_CtlSendData(pdev, (uint8_t *)name, MIN(strlen(name), req->wLength)); - } - break; - - case 0x2A26: { - // GATT Firmware Revision characteristic - const char *fw_version = PBIO_VERSION_STR; - (void)USBD_CtlSendData(pdev, (uint8_t *)fw_version, MIN(strlen(fw_version), req->wLength)); - } - break; - - case 0x2A28: { - // GATT Software Revision characteristic - const char *sw_version = PBIO_PROTOCOL_VERSION_STR; - (void)USBD_CtlSendData(pdev, (uint8_t *)sw_version, MIN(strlen(sw_version), req->wLength)); - } - break; - - default: - USBD_CtlError(pdev, req); - ret = USBD_FAIL; - break; - } - break; - case PBIO_PYBRICKS_USB_INTERFACE_READ_CHARACTERISTIC_PYBRICKS: - switch (req->wValue) { - case 0x0003: { - // Pybricks hub capabilities characteristic - uint8_t caps[PBIO_PYBRICKS_HUB_CAPABILITIES_VALUE_SIZE]; - pbio_pybricks_hub_capabilities(caps, - USBD_PYBRICKS_MAX_PACKET_SIZE - 1, - PBSYS_CONFIG_APP_FEATURE_FLAGS, - pbsys_storage_get_maximum_program_size(), - PBSYS_CONFIG_HMI_NUM_SLOTS); - (void)USBD_CtlSendData(pdev, caps, MIN(sizeof(caps), req->wLength)); - } - break; - - default: - USBD_CtlError(pdev, req); - ret = USBD_FAIL; - break; - } - break; - default: - USBD_CtlError(pdev, req); - ret = USBD_FAIL; - break; - } - - return ret; +static USBD_StatusTypeDef Pybricks_Itf_SetControlLineState(bool dtr) { + // The DTR signal indicates whether a host application has opened the serial + // port. This is the USB analog of a BLE host subscribing to notifications. + pbdrv_usb_on_dtr_changed(dtr); + return USBD_OK; } USBD_Pybricks_ItfTypeDef USBD_Pybricks_fops = { @@ -284,7 +223,7 @@ USBD_Pybricks_ItfTypeDef USBD_Pybricks_fops = { .DeInit = Pybricks_Itf_DeInit, .Receive = Pybricks_Itf_Receive, .TransmitCplt = Pybricks_Itf_TransmitCplt, - .ReadCharacteristic = Pybricks_Itf_ReadCharacteristic, + .SetControlLineState = Pybricks_Itf_SetControlLineState, }; pbio_error_t pbdrv_usb_tx_reset(pbio_os_state_t *state) { @@ -292,7 +231,7 @@ pbio_error_t pbdrv_usb_tx_reset(pbio_os_state_t *state) { return PBIO_SUCCESS; } -pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uint32_t size) { +pbio_error_t pbdrv_usb_tx_chunk(pbio_os_state_t *state, const uint8_t *data, uint32_t size) { static pbio_os_timer_t timer; @@ -314,31 +253,6 @@ pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uin PBIO_OS_ASYNC_END(PBIO_SUCCESS); } -pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t code) { - - static pbio_os_timer_t timer; - - PBIO_OS_ASYNC_BEGIN(state); - - if (transmitting) { - return PBIO_ERROR_BUSY; - } - - transmitting = true; - pbio_os_timer_set(&timer, PBDRV_USB_TRANSMIT_TIMEOUT); - - pbio_set_uint32_le(&usb_response_buf[1], code); - - USBD_Pybricks_TransmitPacket(&husbd, usb_response_buf, sizeof(usb_response_buf)); - - PBIO_OS_AWAIT_UNTIL(state, !transmitting || pbio_os_timer_is_expired(&timer)); - if (pbio_os_timer_is_expired(&timer)) { - return PBIO_ERROR_TIMEDOUT; - } - - PBIO_OS_ASYNC_END(PBIO_SUCCESS); -} - uint32_t pbdrv_usb_get_data_and_start_receive(uint8_t *data) { if (!usb_in_sz) { diff --git a/lib/pbio/include/pbio/protocol.h b/lib/pbio/include/pbio/protocol.h index 1f0bef29c..5f8ecb3a1 100644 --- a/lib/pbio/include/pbio/protocol.h +++ b/lib/pbio/include/pbio/protocol.h @@ -472,14 +472,13 @@ extern const uint8_t pbio_nus_service_uuid[]; extern const uint8_t pbio_nus_rx_char_uuid[]; extern const uint8_t pbio_nus_tx_char_uuid[]; -/** USB bDeviceClass for Pybricks hubs */ -#define PBIO_PYBRICKS_USB_DEVICE_CLASS 0xFF -/** USB bDeviceSubClass for Pybricks hubs */ -#define PBIO_PYBRICKS_USB_DEVICE_SUBCLASS 0xC5 -/** USB bDeviceProtocol for Pybricks hubs */ -#define PBIO_PYBRICKS_USB_DEVICE_PROTOCOL 0xF5 - -/** USB bRequest for Pybricks class-specific requests */ +/** + * Characteristic namespace for ::PBIO_PYBRICKS_OUT_EP_MSG_READ requests. + * + * Selects which group the 16-bit characteristic id in a read request belongs + * to, mirroring how a BLE host distinguishes standard GATT characteristics + * from Pybricks-specific ones. + */ enum { /** Retrieve GATT characteristics */ PBIO_PYBRICKS_USB_INTERFACE_READ_CHARACTERISTIC_GATT = 0x01, @@ -487,35 +486,66 @@ enum { PBIO_PYBRICKS_USB_INTERFACE_READ_CHARACTERISTIC_PYBRICKS = 0x02, }; -// NOTE: These enums values are sent over the wire, so cannot be changed. Also, -// 0 is skipped to avoid a zeroed buffer from being misinterpreted as a message. +// The Pybricks USB interface uses a CDC ACM data pipe (Web Serial on the host), +// which is a raw, bidirectional byte stream with no inherent message +// boundaries. Each direction therefore frames its messages with Consistent +// Overhead Byte Stuffing (COBS): every message is COBS-encoded and terminated +// with a single zero byte (0x00) delimiter. COBS guarantees the encoded payload +// never contains a zero, so the delimiter unambiguously marks the end of a +// frame. This framing is self synchronizing; a receiver that joins mid-stream +// or sees a corrupt frame simply resumes at the next delimiter. +// +// The byte stream is independent of the USB hardware packet size: a single +// frame may span several USB packets, and several small frames may share one +// packet. Only after a frame has been reassembled and decoded is its first +// byte interpreted as the message type below. -/** Hub to host messages via the Pybricks interface IN endpoint. */ +/** + * Hub to host message types. + * + * The hub to host direction is a single byte stream that multiplexes command + * responses, events and read replies, so the first byte of each message + * discriminates between them. + */ typedef enum { /** - * Analog of BLE status response. Emitted in response to every OUT message - * received. + * Reply to a ::PBIO_PYBRICKS_OUT_EP_MSG_COMMAND. The payload is the command + * error code, analogous to a BLE write response. */ PBIO_PYBRICKS_IN_EP_MSG_RESPONSE = 1, - /**Analog to BLE notification. Only emitted if subscribed. */ + /** Analog to BLE notification. Emitted while a host is connected. */ PBIO_PYBRICKS_IN_EP_MSG_EVENT = 2, + /** + * Reply to a ::PBIO_PYBRICKS_OUT_EP_MSG_READ. The payload is + * `[service, char_id_lo, char_id_hi, value...]`, echoing the selector from + * the request followed by the characteristic value. An empty value + * indicates an unknown characteristic. + */ + PBIO_PYBRICKS_IN_EP_MSG_READ_REPLY = 3, } pbio_pybricks_usb_in_ep_msg_t; -/** Host to hub messages via the Pybricks USB interface OUT endpoint. */ -typedef enum { - /** Analog of BLE Client Characteristic Configuration Descriptor (CCCD). */ - PBIO_PYBRICKS_OUT_EP_MSG_SUBSCRIBE = 1, - /** Analog of BLE Client Characteristic Write with response. */ - PBIO_PYBRICKS_OUT_EP_MSG_COMMAND = 2, -} pbio_pybricks_usb_out_ep_msg_t; - /** - * Size of USB messages for Pybricks USB interface. + * Host to hub message types. * - * USB has one extra byte header for a message type discriminator - * compared to BLE messages. + * The host to hub direction is a single byte stream, so the first byte of each + * message discriminates between a command and a characteristic read request. */ -#define PBIO_PYBRICKS_USB_MESSAGE_SIZE(n) (1 + n) +typedef enum { + /** + * A characteristic read request. The payload is + * `[service, char_id_lo, char_id_hi]`, where service is one of the + * ::PBIO_PYBRICKS_USB_INTERFACE_READ_CHARACTERISTIC_GATT values. The hub + * replies with a ::PBIO_PYBRICKS_IN_EP_MSG_READ_REPLY. This is the serial + * analog of a BLE host reading a characteristic by UUID. + */ + PBIO_PYBRICKS_OUT_EP_MSG_READ = 1, + /** + * A Pybricks command (see ::pbio_pybricks_command_t), carried in the + * remaining payload bytes. The hub replies with a + * ::PBIO_PYBRICKS_IN_EP_MSG_RESPONSE. + */ + PBIO_PYBRICKS_OUT_EP_MSG_COMMAND = 2, +} pbio_pybricks_usb_out_ep_msg_t; #endif // _PBIO_PROTOCOL_H_ diff --git a/lib/pbio/platform/build_hat/pbdrvconfig.h b/lib/pbio/platform/build_hat/pbdrvconfig.h index 0d0e5a462..a0308a8bc 100644 --- a/lib/pbio/platform/build_hat/pbdrvconfig.h +++ b/lib/pbio/platform/build_hat/pbdrvconfig.h @@ -50,8 +50,6 @@ // TODO: replace this with a serial port implementation - there is no USB hardware #define PBDRV_CONFIG_USB (1) -#define PBDRV_CONFIG_USB_MAX_PACKET_SIZE (64) -#define PBDRV_CONFIG_USB_NUM_BUFFERED_PACKETS (2) #define PBDRV_CONFIG_USB_SIMULATION_PICO (1) #define PBDRV_CONFIG_USB_MFG_STR u"Raspberry Pi" #define PBDRV_CONFIG_USB_PROD_STR u"Build HAT" diff --git a/lib/pbio/platform/essential_hub/pbdrvconfig.h b/lib/pbio/platform/essential_hub/pbdrvconfig.h index 5becae4eb..b11538332 100644 --- a/lib/pbio/platform/essential_hub/pbdrvconfig.h +++ b/lib/pbio/platform/essential_hub/pbdrvconfig.h @@ -101,8 +101,6 @@ #define PBDRV_CONFIG_UART_STM32F4_LL_IRQ_NUM_UART (2) #define PBDRV_CONFIG_USB (1) -#define PBDRV_CONFIG_USB_MAX_PACKET_SIZE (64) -#define PBDRV_CONFIG_USB_NUM_BUFFERED_PACKETS (2) #define PBDRV_CONFIG_USB_VID LEGO_USB_VID #define PBDRV_CONFIG_USB_PID LEGO_USB_PID_SPIKE_ESSENTIAL #define PBDRV_CONFIG_USB_MFG_STR LEGO_USB_MFG_STR diff --git a/lib/pbio/platform/ev3/pbdrvconfig.h b/lib/pbio/platform/ev3/pbdrvconfig.h index 9e994deae..79b8c5884 100644 --- a/lib/pbio/platform/ev3/pbdrvconfig.h +++ b/lib/pbio/platform/ev3/pbdrvconfig.h @@ -111,8 +111,6 @@ #define PBDRV_CONFIG_UART_EV3_NUM_UART (4) #define PBDRV_CONFIG_USB (1) -#define PBDRV_CONFIG_USB_MAX_PACKET_SIZE (512) -#define PBDRV_CONFIG_USB_NUM_BUFFERED_PACKETS (2) #define PBDRV_CONFIG_USB_EV3 (1) #define PBDRV_CONFIG_USB_VID LEGO_USB_VID #define PBDRV_CONFIG_USB_PID LEGO_USB_PID_EV3 diff --git a/lib/pbio/platform/nxt/pbdrvconfig.h b/lib/pbio/platform/nxt/pbdrvconfig.h index 5cbda7327..a0f9c6885 100644 --- a/lib/pbio/platform/nxt/pbdrvconfig.h +++ b/lib/pbio/platform/nxt/pbdrvconfig.h @@ -68,8 +68,6 @@ #define PBDRV_CONFIG_USB (1) #define PBDRV_CONFIG_USB_NXT (1) -#define PBDRV_CONFIG_USB_MAX_PACKET_SIZE (64) -#define PBDRV_CONFIG_USB_NUM_BUFFERED_PACKETS (2) #define PBDRV_CONFIG_USB_VID LEGO_USB_VID #define PBDRV_CONFIG_USB_PID LEGO_USB_PID_NXT #define PBDRV_CONFIG_USB_MFG_STR LEGO_USB_MFG_STR diff --git a/lib/pbio/platform/prime_hub/pbdrvconfig.h b/lib/pbio/platform/prime_hub/pbdrvconfig.h index ea16ea21e..01e7470c7 100644 --- a/lib/pbio/platform/prime_hub/pbdrvconfig.h +++ b/lib/pbio/platform/prime_hub/pbdrvconfig.h @@ -118,8 +118,6 @@ #define PBDRV_CONFIG_UART_STM32F4_LL_IRQ_NUM_UART (6) #define PBDRV_CONFIG_USB (1) -#define PBDRV_CONFIG_USB_MAX_PACKET_SIZE (64) -#define PBDRV_CONFIG_USB_NUM_BUFFERED_PACKETS (2) #define PBDRV_CONFIG_USB_VID LEGO_USB_VID #define PBDRV_CONFIG_USB_PID 0xFFFF #define PBDRV_CONFIG_USB_PID_0 LEGO_USB_PID_SPIKE_PRIME diff --git a/lib/pbio/platform/virtual_hub/pbdrvconfig.h b/lib/pbio/platform/virtual_hub/pbdrvconfig.h index d71ca5238..484499de4 100644 --- a/lib/pbio/platform/virtual_hub/pbdrvconfig.h +++ b/lib/pbio/platform/virtual_hub/pbdrvconfig.h @@ -59,7 +59,5 @@ #define PBDRV_CONFIG_USB (1) #define PBDRV_CONFIG_USB_SIMULATION (1) -#define PBDRV_CONFIG_USB_MAX_PACKET_SIZE (64) -#define PBDRV_CONFIG_USB_NUM_BUFFERED_PACKETS (2) #define PBDRV_CONFIG_USB_MFG_STR u"Pybricks" #define PBDRV_CONFIG_USB_PROD_STR u"Virtual Hub"