From b0473a7c4394492c2da6e87e36817d884fb6acb6 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sun, 20 Jul 2025 14:44:22 -0500 Subject: [PATCH 1/5] pbio/drv/usb/stm32: rework Pybricks USB characteristic handling The main change here is switching from using BOS descriptors to using interface/class-specific descriptors for the equivalent of reading BLE GATT characteristics over USB. BOS are really meant for operating system capabilities, not application-level protocols. Instead, we now have interface/class-specific responses to control requests that allow reading each characteristic individually. Since the wValue parameter is 16 bits, it is natural to use the BLE 16-bit UUIDs for this purpose. To avoid potential clashes, we also use bRequest to sort these by groups. For now, there is one for standard BLE 16-bit UUIDs and one for Pybricks-defined 16-bit UUIDs. Now, all remaining BOS descriptor info is known at compile time, so we no longer need to fill it in at runtime. There are also some fixes to make the BOS descriptors work correctly with Chromium-based browsers. Chromium seems to be a bugging and expects the WebUSB descriptor to be the first device capability and the Microsoft OS 2.0 descriptor to be the last device capability. Otherwise, it doesn't seem to parse them correctly. Also, we had the bcdUSB incorrectly set to 2.0.1 instead of 2.1.0. --- lib/pbio/drv/usb/stm32_usbd/usbd_desc.c | 206 +++++--------------- lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c | 23 ++- lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h | 10 +- lib/pbio/drv/usb/usb_stm32.c | 70 +++++++ 4 files changed, 138 insertions(+), 171 deletions(-) diff --git a/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c b/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c index 9d2d83794..c03afa3ad 100644 --- a/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c +++ b/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c @@ -47,11 +47,8 @@ #include -#include #include #include -#include -#include #include "usbd_core.h" #include "usbd_conf.h" @@ -63,37 +60,28 @@ #define USBD_CONFIGURATION_FS_STRING "Pybricks Config" #define USBD_INTERFACE_FS_STRING "Pybricks Interface" -static const char firmware_version[] = PBIO_VERSION_STR; -static const char software_version[] = PBIO_PROTOCOL_VERSION_STR; - +// STM32 MCU Device ID register addresses +// REVISIT: make pbdrv_xxx_get_serial_number() and use that instead #define DEVICE_ID1 (0x1FFF7A10) #define DEVICE_ID2 (0x1FFF7A14) #define DEVICE_ID3 (0x1FFF7A18) -#define USB_DEV_CAP_TYPE_PLATFORM (5) +// bDevCapabilityType for USB_DEVICE_CAPABITY_TYPE +#define USB_DEV_CAP_TYPE_PLATFORM (5) -#define USB_SIZ_STRING_SERIAL 0x1A -#define USB_SIZ_BOS_DESC_CONST (5 + 28 + 24) -#define USB_SIZ_UUID (128 / 8) -#define USB_SIZ_PLATFORM_HDR (4 + USB_SIZ_UUID) -#define USB_SIZ_HUB_NAME_MAX (16) -#define USB_SIZ_BOS_DESC (USB_SIZ_BOS_DESC_CONST + \ - USB_SIZ_PLATFORM_HDR + USB_SIZ_HUB_NAME_MAX + \ - USB_SIZ_PLATFORM_HDR + sizeof(firmware_version) - 1 + \ - USB_SIZ_PLATFORM_HDR + sizeof(software_version) - 1 + \ - USB_SIZ_PLATFORM_HDR + PBIO_PYBRICKS_HUB_CAPABILITIES_VALUE_SIZE) +// descriptor sizes +#define USB_SIZ_STRING_SERIAL 26 +#define USB_SIZ_BOS_DESC (5 + 28 + 24) /* USB Standard Device Descriptor */ __ALIGN_BEGIN static #if !defined(PBDRV_CONFIG_USB_STM32F4_HUB_VARIANT_ADDR) const #endif -uint8_t USBD_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = { +uint8_t USBD_DeviceDesc[] __ALIGN_END = { 0x12, /* bLength */ USB_DESC_TYPE_DEVICE, /* bDescriptorType */ - 0x01, /* bcdUSB */ /* changed to USB version 2.01 - in order to support BOS Desc */ - 0x02, + 0x10, 0x02, /* bcdUSB = 2.1.0 (for BOS support) */ PBIO_PYBRICKS_USB_DEVICE_CLASS, /* bDeviceClass */ PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, /* bDeviceSubClass */ PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, /* bDeviceProtocol */ @@ -102,16 +90,16 @@ uint8_t USBD_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = { HIBYTE(PBDRV_CONFIG_USB_VID), /* idVendor */ LOBYTE(PBDRV_CONFIG_USB_PID), /* idProduct */ HIBYTE(PBDRV_CONFIG_USB_PID), /* idProduct */ - 0x00, /* bcdDevice rel. 2.00 */ - 0x02, + 0x00, 0x02, /* bcdDevice rel. 2.0.0 */ USBD_IDX_MFC_STR, /* Index of manufacturer string */ USBD_IDX_PRODUCT_STR, /* Index of product string */ USBD_IDX_SERIAL_STR, /* Index of serial number string */ USBD_MAX_NUM_CONFIGURATION /* bNumConfigurations */ }; /* USB_DeviceDescriptor */ +_Static_assert(USB_LEN_DEV_DESC == sizeof(USBD_DeviceDesc)); /** BOS descriptor. */ -__ALIGN_BEGIN static uint8_t USBD_BOSDesc[USB_SIZ_BOS_DESC] __ALIGN_END = +__ALIGN_BEGIN static const uint8_t USBD_BOSDesc[] __ALIGN_END = { 5, /* bLength */ USB_DESC_TYPE_BOS, /* bDescriptorType = BOS */ @@ -119,55 +107,58 @@ __ALIGN_BEGIN static uint8_t USBD_BOSDesc[USB_SIZ_BOS_DESC] __ALIGN_END = HIBYTE(USB_SIZ_BOS_DESC), /* wTotalLength */ 2, /* bNumDeviceCaps */ - 28, /* bLength */ + // IMPORTANT: The WebUSB descriptor must be first to make Chromium happy. + + 24, /* bLength */ USB_DEVICE_CAPABITY_TYPE, /* bDescriptorType = Device Capability */ USB_DEV_CAP_TYPE_PLATFORM, /* bDevCapabilityType */ 0x00, /* bReserved */ /* * PlatformCapabilityUUID - * Microsoft OS 2.0 descriptor platform capability ID - * D8DD60DF-4589-4CC7-9CD2-659D9E648A9F + * WebUSB Platform Capability descriptor + * 3408B638-09A9-47A0-8BFD-A0768815B665 * RFC 4122 explains the correct byte ordering */ - 0xDF, 0x60, 0xDD, 0xD8, /* 32-bit value */ - 0x89, 0x45, /* 16-bit value */ - 0xC7, 0x4C, /* 16-bit value */ - 0x9C, 0xD2, - 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F, + 0x38, 0xB6, 0x08, 0x34, /* 32-bit value */ + 0xA9, 0x09, /* 16-bit value */ + 0xA0, 0x47, /* 16-bit value */ + 0x8B, 0xFD, + 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65, - 0x00, 0x00, 0x03, 0x06, /* dwWindowsVersion = 0x06030000 for Windows 8.1 Build */ - LOBYTE(USBD_SIZ_MS_OS_DSCRPTR_SET), /* wMSOSDescriptorSetTotalLength */ - HIBYTE(USBD_SIZ_MS_OS_DSCRPTR_SET), /* wMSOSDescriptorSetTotalLength */ - USBD_MS_VENDOR_CODE, /* bMS_VendorCode */ - 0x00, /* bAltEnumCode = Does not support alternate enumeration */ + LOBYTE(0x0100), /* bcdVersion */ + HIBYTE(0x0100), /* bcdVersion */ + USBD_VENDOR_CODE_WEBUSB, /* bVendorCode */ + USBD_WEBUSB_LANDING_PAGE_IDX, /* iLandingPage */ - 24, /* bLength */ + // IMPORTANT: The MS OS 2.0 descriptor must be last to make Chromium happy. + + 28, /* bLength */ USB_DEVICE_CAPABITY_TYPE, /* bDescriptorType = Device Capability */ USB_DEV_CAP_TYPE_PLATFORM, /* bDevCapabilityType */ 0x00, /* bReserved */ /* * PlatformCapabilityUUID - * WebUSB Platform Capability descriptor - * 3408B638-09A9-47A0-8BFD-A0768815B665 + * Microsoft OS 2.0 descriptor platform capability ID + * D8DD60DF-4589-4CC7-9CD2-659D9E648A9F * RFC 4122 explains the correct byte ordering */ - 0x38, 0xB6, 0x08, 0x34, /* 32-bit value */ - 0xA9, 0x09, /* 16-bit value */ - 0xA0, 0x47, /* 16-bit value */ - 0x8B, 0xFD, - 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65, + 0xDF, 0x60, 0xDD, 0xD8, /* 32-bit value */ + 0x89, 0x45, /* 16-bit value */ + 0xC7, 0x4C, /* 16-bit value */ + 0x9C, 0xD2, + 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F, - LOBYTE(0x0100), /* bcdVersion */ - HIBYTE(0x0100), /* bcdVersion */ - USBD_WEBUSB_VENDOR_CODE, /* bVendorCode */ - USBD_WEBUSB_LANDING_PAGE_IDX /* iLandingPage */ + 0x00, 0x00, 0x03, 0x06, /* dwWindowsVersion = 0x06030000 for Windows 8.1 Build */ + LOBYTE(USBD_SIZ_MS_OS_DSCRPTR_SET), /* wMSOSDescriptorSetTotalLength */ + HIBYTE(USBD_SIZ_MS_OS_DSCRPTR_SET), /* wMSOSDescriptorSetTotalLength */ + USBD_VENDOR_CODE_MS, /* bMS_VendorCode */ + 0x00, /* bAltEnumCode = Does not support alternate enumeration */ }; +_Static_assert(USB_SIZ_BOS_DESC == sizeof(USBD_BOSDesc)); -static uint16_t USBD_BOSDesc_Len; - -__ALIGN_BEGIN const uint8_t USBD_OSDescSet[USBD_SIZ_MS_OS_DSCRPTR_SET] __ALIGN_END = +__ALIGN_BEGIN const uint8_t USBD_OSDescSet[] __ALIGN_END = { 0x0A, 0x00, /* wLength = 10 */ 0x00, 0x00, /* wDescriptorType = MS_OS_20_SET_HEADER_DESCRIPTOR */ @@ -254,6 +245,7 @@ __ALIGN_BEGIN const uint8_t USBD_OSDescSet[USBD_SIZ_MS_OS_DSCRPTR_SET] __ALIGN_E '\0', '\0', '\0', '\0' }; +_Static_assert(USBD_SIZ_MS_OS_DSCRPTR_SET == sizeof(USBD_OSDescSet)); /* USB Standard Device Descriptor */ __ALIGN_BEGIN static const uint8_t USBD_LangIDDesc[USB_LEN_LANGID_STR_DESC] __ALIGN_END = { @@ -314,38 +306,6 @@ static void Get_SerialNum(void) { } } -/** - * @brief Add the short BLE UUID to the buffer in little-endian format. - * @param dst The destination buffer - * @param dst The short BLE UUID to add - * @retval None - */ -static void add_ble_short_uuid_le(uint8_t *dst, uint16_t short_uuid) { - /* 32-bit */ - dst[0] = LOBYTE(short_uuid); - dst[1] = HIBYTE(short_uuid); - dst[2] = 0x00; - dst[3] = 0x00; - - /* 16-bit */ - dst[4] = 0x00; - dst[5] = 0x00; - - /* 16-bit */ - dst[6] = 0x00; - dst[7] = 0x10; - - /* 8-byte buffer */ - dst[8] = 0x80; - dst[9] = 0x00; - dst[10] = 0x00; - dst[11] = 0x80; - dst[12] = 0x5F; - dst[13] = 0x9B; - dst[14] = 0x34; - dst[15] = 0xFB; -} - /** * @brief Returns the device descriptor. * @param speed: Current device speed @@ -443,7 +403,7 @@ static uint8_t *USBD_Pybricks_BOSDescriptor(USBD_SpeedTypeDef speed, uint16_t *l /* Prevent unused argument(s) compilation warning */ UNUSED(speed); - *length = USBD_BOSDesc_Len; + *length = USB_SIZ_BOS_DESC; return (uint8_t *)USBD_BOSDesc; } @@ -471,80 +431,4 @@ void USBD_Pybricks_Desc_Init(void) { USBD_DeviceDesc[11] = HIBYTE(PBDRV_CONFIG_USB_PID_1); } #endif - - const char *str; - size_t len; - - uint8_t *ptr = &USBD_BOSDesc[USB_SIZ_BOS_DESC_CONST]; - - /* Add device name */ - str = pbdrv_bluetooth_get_hub_name(); - len = MIN(strlen(str), USB_SIZ_HUB_NAME_MAX); - - *ptr++ = USB_SIZ_PLATFORM_HDR + len; // bLength - *ptr++ = USB_DEVICE_CAPABITY_TYPE; // bDescriptorType - *ptr++ = USB_DEV_CAP_TYPE_PLATFORM; // bDevCapabilityType - *ptr++ = 0x00; // bReserved - - // PlatformCapabilityUUID - add_ble_short_uuid_le(ptr, pbio_gatt_device_name_char_uuid); - ptr += USB_SIZ_UUID; - - // CapabilityData: Device Name - memcpy(ptr, str, len); - ptr += len; - - /* Add firmware version */ - *ptr++ = USB_SIZ_PLATFORM_HDR + sizeof(firmware_version) - 1; // bLength - *ptr++ = USB_DEVICE_CAPABITY_TYPE; // bDescriptorType - *ptr++ = USB_DEV_CAP_TYPE_PLATFORM; // bDevCapabilityType - *ptr++ = 0x00; // bReserved - - // PlatformCapabilityUUID - add_ble_short_uuid_le(ptr, pbio_gatt_firmware_version_char_uuid); - ptr += USB_SIZ_UUID; - - // CapabilityData: Firmware Version - memcpy(ptr, firmware_version, sizeof(firmware_version) - 1); - ptr += sizeof(firmware_version) - 1; - - /* Add software (protocol) version */ - *ptr++ = USB_SIZ_PLATFORM_HDR + sizeof(software_version) - 1; // bLength - *ptr++ = USB_DEVICE_CAPABITY_TYPE; // bDescriptorType - *ptr++ = USB_DEV_CAP_TYPE_PLATFORM; // bDevCapabilityType - *ptr++ = 0x00; // bReserved - - // PlatformCapabilityUUID - add_ble_short_uuid_le(ptr, pbio_gatt_software_version_char_uuid); - ptr += USB_SIZ_UUID; - - // CapabilityData: Software Version - memcpy(ptr, software_version, sizeof(software_version) - 1); - ptr += sizeof(software_version) - 1; - - /* Add hub capabilities */ - *ptr++ = USB_SIZ_PLATFORM_HDR + PBIO_PYBRICKS_HUB_CAPABILITIES_VALUE_SIZE; // bLength - *ptr++ = USB_DEVICE_CAPABITY_TYPE; // bDescriptorType - *ptr++ = USB_DEV_CAP_TYPE_PLATFORM; // bDevCapabilityType - *ptr++ = 0x00; // bReserved - - // PlatformCapabilityUUID - pbio_uuid128_le_copy(ptr, pbio_pybricks_hub_capabilities_char_uuid); - ptr += USB_SIZ_UUID; - - // CapabilityData: Hub Capabilities - pbio_pybricks_hub_capabilities(ptr, - USBD_PYBRICKS_MAX_PACKET_SIZE - 1, - PBSYS_CONFIG_APP_FEATURE_FLAGS, - pbsys_storage_get_maximum_program_size(), - PBSYS_CONFIG_HMI_NUM_SLOTS); - ptr += PBIO_PYBRICKS_HUB_CAPABILITIES_VALUE_SIZE; - - /* Update wTotalLength field in BOS Descriptor */ - USBD_BOSDesc_Len = ptr - USBD_BOSDesc; - USBD_BOSDesc[2] = LOBYTE(USBD_BOSDesc_Len); - USBD_BOSDesc[3] = HIBYTE(USBD_BOSDesc_Len); - - /* Update bNumDeviceCaps field in BOS Descriptor */ - USBD_BOSDesc[4] += 4; } diff --git a/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c index d35235dd9..df65c6bf5 100644 --- a/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c +++ b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c @@ -37,6 +37,7 @@ /* Includes ------------------------------------------------------------------*/ #include +#include #include "usbd_ctlreq.h" #include "usbd_pybricks.h" @@ -155,16 +156,25 @@ __ALIGN_BEGIN static uint8_t USBD_Pybricks_CfgDesc[USBD_PYBRICKS_CONFIG_DESC_SIZ 0x00 /* bInterval */ }; -__ALIGN_BEGIN static const uint8_t WebUSB_DescSet[20] __ALIGN_END = +__ALIGN_BEGIN static const uint8_t WebUSB_DescSet[] __ALIGN_END = { + #if PBIO_VERSION_LEVEL_HEX == 0xA + 21, /* bLength */ + #else 20, /* bLength */ + #endif 0x03, /* bDescriptorType = URL */ 0x01, /* bScheme = https:// */ /* URL */ - 'c', 'o', 'd', 'e', '.', - 'p', 'y', 'b', 'r', 'i', 'c', 'k', 's', '.', - 'c', 'o', 'm' + #if PBIO_VERSION_LEVEL_HEX == 0xA + 'a', 'l', 'p', 'h', 'a', + #elif PBIO_VERSION_LEVEL_HEX == 0xB + 'b', 'e', 't', 'a', + #else + 'c', 'o', 'd', 'e', + #endif + '.', 'p', 'y', 'b', 'r', 'i', 'c', 'k', 's', '.', 'c', 'o', 'm' }; /** @@ -245,18 +255,19 @@ 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: switch (req->bRequest) { - case USBD_MS_VENDOR_CODE: + case USBD_VENDOR_CODE_MS: (void)USBD_CtlSendData(pdev, (uint8_t *)USBD_OSDescSet, MIN(sizeof(USBD_OSDescSet), req->wLength)); break; - case USBD_WEBUSB_VENDOR_CODE: + case USBD_VENDOR_CODE_WEBUSB: if ((req->wValue == USBD_WEBUSB_LANDING_PAGE_IDX) && (req->wIndex == 0x02)) { (void)USBD_CtlSendData(pdev, (uint8_t *)WebUSB_DescSet, diff --git a/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h index 455446156..c9619cea2 100644 --- a/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h +++ b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h @@ -40,10 +40,10 @@ extern "C" { /** @defgroup USBD_Pybricks_Exported_Defines * @{ */ -#define USBD_MS_VENDOR_CODE 0x01 -#define USBD_WEBUSB_VENDOR_CODE 0x02 - -#define USBD_SIZ_MS_OS_DSCRPTR_SET (10 + 20 + 132) +enum { + USBD_VENDOR_CODE_WEBUSB, + USBD_VENDOR_CODE_MS +}; #define USBD_WEBUSB_LANDING_PAGE_IDX 1 @@ -94,6 +94,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_Pybricks_ItfTypeDef; @@ -121,6 +122,7 @@ typedef struct extern USBD_ClassTypeDef USBD_Pybricks_ClassDriver; +#define USBD_SIZ_MS_OS_DSCRPTR_SET (10 + 20 + 132) extern const uint8_t USBD_OSDescSet[USBD_SIZ_MS_OS_DSCRPTR_SET]; /** diff --git a/lib/pbio/drv/usb/usb_stm32.c b/lib/pbio/drv/usb/usb_stm32.c index 45573ce9f..72999dbe8 100644 --- a/lib/pbio/drv/usb/usb_stm32.c +++ b/lib/pbio/drv/usb/usb_stm32.c @@ -17,11 +17,15 @@ #include #include +#include #include #include #include +#include #include +#include #include +#include #include "../charger/charger.h" #include "./usb_stm32.h" @@ -284,11 +288,77 @@ static USBD_StatusTypeDef Pybricks_Itf_TransmitCplt(uint8_t *Buf, uint32_t Len, return ret; } +#define USBD_PYBRICKS_INTERFACE_READ_CHARACTERISTIC_GATT 0x01 +#define USBD_PYBRICKS_INTERFACE_READ_CHARACTERISTIC_PYBRICKS 0x02 + +static USBD_StatusTypeDef Pybricks_Itf_ReadCharacteristic(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req) { + USBD_StatusTypeDef ret = USBD_OK; + + switch (req->bRequest) { + case USBD_PYBRICKS_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 USBD_PYBRICKS_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; +} + USBD_Pybricks_ItfTypeDef USBD_Pybricks_fops = { .Init = Pybricks_Itf_Init, .DeInit = Pybricks_Itf_DeInit, .Receive = Pybricks_Itf_Receive, .TransmitCplt = Pybricks_Itf_TransmitCplt, + .ReadCharacteristic = Pybricks_Itf_ReadCharacteristic, }; // Common USB driver implementation. From 977f8ccc316c9fdbf266fb2224bbe69071731716 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sun, 20 Jul 2025 20:20:01 -0500 Subject: [PATCH 2/5] pbio/sys/host: drop use of MIN macro Replace use of MIN macro with explicit comparisons. We are removing the defines for these, so we can't depend on them anymore. --- lib/pbio/sys/host.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/pbio/sys/host.c b/lib/pbio/sys/host.c index 61f904269..d4714edb1 100644 --- a/lib/pbio/sys/host.c +++ b/lib/pbio/sys/host.c @@ -125,7 +125,7 @@ pbio_error_t pbsys_host_stdout_write(const uint8_t *data, uint32_t *size) { uint32_t bt_avail = pbsys_bluetooth_tx_available(); uint32_t usb_avail = pbdrv_usb_stdout_tx_available(); - uint32_t available = MIN(UINT32_MAX, MIN(bt_avail, usb_avail)); + uint32_t available = bt_avail < usb_avail ? bt_avail : usb_avail; // If all tx_available() calls returned UINT32_MAX, then there is one listening. if (available == UINT32_MAX) { @@ -138,7 +138,9 @@ pbio_error_t pbsys_host_stdout_write(const uint8_t *data, uint32_t *size) { // Limit size to smallest available space from all transports so that we // don't do partial writes to one transport and not the other. - *size = MIN(*size, available); + if (*size > available) { + *size = available; + } // Unless something became disconnected in an interrupt handler, these // functions should always succeed since we already checked tx_available(). From a2c15a14696eda63ab46ae178bc4aa9299f8d5ec Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sun, 20 Jul 2025 15:54:12 -0500 Subject: [PATCH 3/5] lib/contiki: drop some generic defines Drop the MIN/MAX macros from the contiki includes. These can conflict with other definitions, and are not used in the code. --- lib/contiki-core/sys/cc.h | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lib/contiki-core/sys/cc.h b/lib/contiki-core/sys/cc.h index bf3f1e7aa..c1aafef25 100644 --- a/lib/contiki-core/sys/cc.h +++ b/lib/contiki-core/sys/cc.h @@ -134,18 +134,6 @@ #define NULL 0 #endif /* NULL */ -#ifndef MAX -#define MAX(n, m) (((n) < (m)) ? (m) : (n)) -#endif - -#ifndef MIN -#define MIN(n, m) (((n) < (m)) ? (n) : (m)) -#endif - -#ifndef ABS -#define ABS(n) (((n) < 0) ? -(n) : (n)) -#endif - #define CC_CONCAT2(s1, s2) s1##s2 /** From 466b0da8b42ef22e5fd748aba36fa62350dd1eb0 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sun, 20 Jul 2025 16:22:41 -0500 Subject: [PATCH 4/5] nxos: usb: adapt NxOS USB configuration for Pybricks NxOS had it's own USB configuration. Replace it with the Pybricks USB configuration so that we can use the Pybricks USB protocol. This also fixes not setting bit 7 of bmAttributes. --- lib/pbio/platform/nxt/nxos/drivers/usb.c | 565 +++++++++++++++++------ lib/pbio/platform/nxt/pbsysconfig.h | 1 + 2 files changed, 414 insertions(+), 152 deletions(-) diff --git a/lib/pbio/platform/nxt/nxos/drivers/usb.c b/lib/pbio/platform/nxt/nxos/drivers/usb.c index 3598ef40d..35aa2ab21 100644 --- a/lib/pbio/platform/nxt/nxos/drivers/usb.c +++ b/lib/pbio/platform/nxt/nxos/drivers/usb.c @@ -1,15 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-only /* Copyright (C) 2007 the NxOS developers + * Copyright (C) 2025 the Pybricks Authors * * See AUTHORS for a full list of the developers. - * - * Redistribution of this file is permitted under - * the terms of the GNU Public License (GPL) version 2. */ #include #include #include +#include +#include +#include +#include +#include + #include #include "nxos/interrupts.h" @@ -37,16 +42,20 @@ * * TODO: clean up these. Most are unused. */ -#define USB_BMREQUEST_DIR 0x80 -#define USB_BMREQUEST_H_TO_D 0x0 -#define USB_BMREQUEST_D_TO_H 0x80 - -#define USB_BMREQUEST_RCPT 0x0F -#define USB_BMREQUEST_RCPT_DEV 0x0 /* device */ -#define USB_BMREQUEST_RCPT_INT 0x1 /* interface */ -#define USB_BMREQUEST_RCPT_EPT 0x2 /* endpoint */ -#define USB_BMREQUEST_RCPT_OTH 0x3 /* other */ - +#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 @@ -65,6 +74,11 @@ #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 @@ -84,14 +98,14 @@ */ static const uint8_t usb_device_descriptor[] = { 18, USB_DESC_TYPE_DEVICE, /* Packet size and type. */ - 0x00, 0x20, /* This packet is USB 2.0. */ - 2, /* Class code. */ - 0, /* Sub class code. */ - 0, /* Device protocol. */ + 0x10, 0x02, /* This packet is USB 2.1 (needed for BOS descriptors). */ + PBIO_PYBRICKS_USB_DEVICE_CLASS, /* Class code. */ + PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, /* Sub class code. */ + PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, /* Device protocol. */ MAX_EP0_SIZE, /* Maximum packet size for EP0 (control endpoint). */ 0x94, 0x06, /* Vendor ID : LEGO */ - 0x00, 0xFF, /* Product ID : NXOS */ - 0x00, 0x00, /* Product revision. */ + 0x02, 0x00, /* Product ID : NXT */ + 0x00, 0x02, /* Product revision: 2.0.0. */ 1, /* Index of the vendor string. */ 2, /* Index of the product string. */ 0, /* Index of the serial number (none for us). */ @@ -100,15 +114,72 @@ static const uint8_t usb_device_descriptor[] = { static const uint8_t usb_dev_qualifier_desc[] = { 10, USB_DESC_TYPE_DEVICE_QUALIFIER, /* Packet size and type. */ - 0x00, 0x20, /* This packet is USB 2.0. */ - 2, /* Class code */ - 0, /* Sub class code */ - 0, /* Device protocol */ + 0x10, 0x02, /* This packet is USB 2.1. */ + PBIO_PYBRICKS_USB_DEVICE_CLASS, /* Class code */ + PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, /* Sub class code */ + PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, /* Device protocol */ MAX_EP0_SIZE, /* Maximum packet size for EP0. */ 1, /* The number of possible configurations. */ 0 /* Reserved for future use, must be zero. */ }; +// These enumerations are specific to the configuration of this device. + +enum { + USBD_VENDOR_CODE_WEBUSB, + USBD_VENDOR_CODE_MS, +}; + +// NB: Chromium seems quite particular about the order of these descriptors. +// The WebUSB descriptor must come first and the MS OS 2.0 descriptor be last. +static const uint8_t usb_bos_desc[] = { + 5, USB_DESC_TYPE_BOS, /* Descriptor length and type. */ + 0x39, 0x00, /* Total length of the descriptor = 57. */ + 2, /* Number of device capabilities. */ + + 24, /* bLength */ + USB_DEVICE_CAPABILITY_TYPE, /* bDescriptorType = Device Capability */ + USB_DEV_CAP_TYPE_PLATFORM, /* bDevCapabilityType */ + 0x00, /* bReserved */ + + /* + * PlatformCapabilityUUID + * WebUSB Platform Capability descriptor + * 3408B638-09A9-47A0-8BFD-A0768815B665 + * RFC 4122 explains the correct byte ordering + */ + 0x38, 0xB6, 0x08, 0x34, /* 32-bit value */ + 0xA9, 0x09, /* 16-bit value */ + 0xA0, 0x47, /* 16-bit value */ + 0x8B, 0xFD, + 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65, + + 0x00, 0x01, /* bcdVersion = 1.00 */ + USBD_VENDOR_CODE_WEBUSB, /* bVendorCode */ + 1, /* iLandingPage */ + + 28, /* bLength */ + USB_DEVICE_CAPABILITY_TYPE, /* bDescriptorType = Device Capability */ + USB_DEV_CAP_TYPE_PLATFORM, /* bDevCapabilityType */ + 0x00, /* bReserved */ + + /* + * PlatformCapabilityUUID + * Microsoft OS 2.0 descriptor platform capability ID + * D8DD60DF-4589-4CC7-9CD2-659D9E648A9F + * RFC 4122 explains the correct byte ordering + */ + 0xDF, 0x60, 0xDD, 0xD8, /* 32-bit value */ + 0x89, 0x45, /* 16-bit value */ + 0xC7, 0x4C, /* 16-bit value */ + 0x9C, 0xD2, + 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F, + + 0x00, 0x00, 0x03, 0x06, /* dwWindowsVersion = 0x06030000 for Windows 8.1 Build */ + 0xA2, 0x00, /* wMSOSDescriptorSetTotalLength = 162 */ + USBD_VENDOR_CODE_MS, /* bMS_VendorCode */ + 0x00, /* bAltEnumCode = Does not support alternate enumeration */ +}; static const uint8_t usb_nxos_full_config[] = { 0x09, USB_DESC_TYPE_CONFIG, /* Descriptor size and type. */ @@ -123,7 +194,7 @@ static const uint8_t usb_nxos_full_config[] = { * 1 because the NXT is self-powered, bit 5 is 0 because the NXT * doesn't support remote wakeup, and bits 0-4 are 0 (reserved). */ - 0x40, + 0xC0, 0, /* Device power consumption, for non self-powered devices. */ @@ -137,9 +208,9 @@ static const uint8_t usb_nxos_full_config[] = { 0x02, /* The number of endpoints defined by this interface * (excluding EP0). */ - 0xFF, /* Interface class ("Vendor specific"). */ - 0xFF, /* Interface subclass (see above). */ - 0xFF, /* Interface protocol (see above). */ + PBIO_PYBRICKS_USB_DEVICE_CLASS, /* Interface class ("Vendor specific"). */ + PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, /* Interface subclass (see above). */ + PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, /* Interface protocol (see above). */ 0x00, /* Index of the string descriptor for this interface (none). */ @@ -178,11 +249,21 @@ static const uint8_t usb_lego_str[] = { }; static const uint8_t usb_nxt_str[] = { - 10, USB_DESC_TYPE_STR, + 30, USB_DESC_TYPE_STR, 'N', 0, - 'x', 0, - 'O', 0, - 'S', 0, + 'X', 0, + 'T', 0, + ' ', 0, + '+', 0, + ' ', 0, + 'P', 0, + 'y', 0, + 'b', 0, + 'r', 0, + 'i', 0, + 'c', 0, + 'k', 0, + 's', 0 }; @@ -374,6 +455,107 @@ static void usb_send_null(void) { usb_write_data(0, NULL, 0); } +static const uint8_t ms_os_desc_set[] = { + 0x0A, 0x00, /* wLength = 10 */ + 0x00, 0x00, /* wDescriptorType = MS_OS_20_SET_HEADER_DESCRIPTOR */ + 0x00, 0x00, 0x03, 0x06, /* dwWindowsVersion = 0x06030000 for Windows 8.1 Build */ + 0xA2, 0x00, /* wTotalLength = 162 */ + + 0x14, 0x00, /* wLength = 20 */ + 0x03, 0x00, /* wDescriptorType = MS_OS_20_FEATURE_COMPATBLE_ID */ + 'W', 'I', 'N', 'U', 'S', 'B', /* CompatibleID */ + 0x00, 0x00, /* CompatibleID (cont.) */ + 0x00, 0x00, 0x00, 0x00, /* SubCompatibleID */ + 0x00, 0x00, 0x00, 0x00, /* SubCompatibleID (cont.) */ + + 0x84, 0x00, /* wLength = 132 */ + 0x04, 0x00, /* wDescriptorType = MS_OS_20_FEATURE_REG_PROPERTY */ + 0x07, 0x00, /* wStringType = REG_MULTI_SZ */ + /* wPropertyNameLength = 42 */ + 0x2A, 0x00, + /* PropertyName = DeviceInterfaceGUIDs */ + 'D', '\0', + 'e', '\0', + 'v', '\0', + 'i', '\0', + 'c', '\0', + 'e', '\0', + 'I', '\0', + 'n', '\0', + 't', '\0', + 'e', '\0', + 'r', '\0', + 'f', '\0', + 'a', '\0', + 'c', '\0', + 'e', '\0', + 'G', '\0', + 'U', '\0', + 'I', '\0', + 'D', '\0', + 's', '\0', + '\0', '\0', + + /* wPropertyDataLength = 80 */ + 0x50, 0x00, + /* PropertyData = {A5C44A4C-53D4-4389-9821-AE95051908A1} */ + '{', '\0', + 'A', '\0', + '5', '\0', + 'C', '\0', + '4', '\0', + '4', '\0', + 'A', '\0', + '4', '\0', + 'C', '\0', + '-', '\0', + '5', '\0', + '3', '\0', + 'D', '\0', + '4', '\0', + '-', '\0', + '4', '\0', + '3', '\0', + '8', '\0', + '9', '\0', + '-', '\0', + '9', '\0', + '8', '\0', + '2', '\0', + '1', '\0', + '-', '\0', + 'A', '\0', + 'E', '\0', + '9', '\0', + '5', '\0', + '0', '\0', + '5', '\0', + '1', '\0', + '9', '\0', + '0', '\0', + '8', '\0', + 'A', '\0', + '1', '\0', + '}', '\0', + '\0', '\0', + '\0', '\0' +}; + +static const uint8_t WebUSB_DescSet[] = { + 20, /* bLength */ + 0x03, /* bDescriptorType = URL */ + 0x01, /* bScheme = https:// */ + + /* URL */ + #if PBIO_VERSION_LEVEL_HEX == 0xA + 'a', 'l', 'p', 'h', 'a', + #elif PBIO_VERSION_LEVEL_HEX == 0xB + 'b', 'e', 't', 'a', + #else + 'c', 'o', 'd', 'e', + #endif + '.', 'p', 'y', 'b', 'r', 'i', 'c', 'k', 's', '.', 'c', 'o', 'm' +}; /* Handle receiving and responding to setup packets on EP0. */ static uint32_t usb_manage_setup_packet(void) { @@ -386,7 +568,6 @@ static uint32_t usb_manage_setup_packet(void) { uint16_t length; /* The number of bytes transferred in the (optional) * second phase of the control transfer. */ } packet; - uint16_t response; uint32_t size; uint8_t index; @@ -404,143 +585,223 @@ static uint32_t usb_manage_setup_packet(void) { usb_csr_clear_flag(0, AT91C_UDP_RXSETUP); + switch (packet.request_attrs & USB_BMREQUEST_TYPE) { + case USB_BMREQUEST_TYPE_STD: + /* Respond to the control request. */ + switch (packet.request) { + case USB_BREQUEST_GET_STATUS: { + /* The host wants to know our status. + * + * If it wants the device status, just reply that the NXT is still + * self-powered (as first declared by the setup packets). If it + * wants endpoint status, reply that the endpoint has not + * halted. Any other status request types are reserved, which + * translates to replying zero. + */ + uint16_t response; + + if ((packet.request_attrs & USB_BMREQUEST_RCPT) == USB_BMREQUEST_RCPT_DEV) + response = 1; + else + response = 0; + + usb_write_data(0, (uint8_t*)&response, 2); + } + break; - response = 0; - - - /* Respond to the control request. */ - switch (packet.request) { - case USB_BREQUEST_GET_STATUS: - /* The host wants to know our status. - * - * If it wants the device status, just reply that the NXT is still - * self-powered (as first declared by the setup packets). If it - * wants endpoint status, reply that the endpoint has not - * halted. Any other status request types are reserved, which - * translates to replying zero. - */ - if ((packet.request_attrs & USB_BMREQUEST_RCPT) == USB_BMREQUEST_RCPT_DEV) - response = 1; - else - response = 0; - - usb_write_data(0, (uint8_t*)&response, 2); - break; - - case USB_BREQUEST_CLEAR_FEATURE: - case USB_BREQUEST_SET_INTERFACE: - case USB_BREQUEST_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. - */ - usb_send_null(); - break; - - case USB_BREQUEST_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. - */ - usb_state.new_device_address = packet.value; - usb_send_null(); - - /* If the address change is to 0, do it immediately. - * - * TODO: Why? And when does this happen? - */ - if (usb_state.new_device_address == 0) { - *AT91C_UDP_FADDR = AT91C_UDP_FEN; - *AT91C_UDP_GLBSTATE = 0; - } - break; - - case USB_BREQUEST_GET_DESCRIPTOR: - /* The host requested a descriptor. */ + case USB_BREQUEST_CLEAR_FEATURE: + case USB_BREQUEST_SET_INTERFACE: + case USB_BREQUEST_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. + */ + usb_send_null(); + break; - index = (packet.value & USB_WVALUE_INDEX); - switch ((packet.value & USB_WVALUE_TYPE) >> 8) { - case USB_DESC_TYPE_DEVICE: /* Device descriptor */ - size = usb_device_descriptor[0]; - usb_write_data(0, usb_device_descriptor, - MIN(size, packet.length)); + case USB_BREQUEST_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. + */ + usb_state.new_device_address = packet.value; + usb_send_null(); + + /* If the address change is to 0, do it immediately. + * + * TODO: Why? And when does this happen? + */ + if (usb_state.new_device_address == 0) { + *AT91C_UDP_FADDR = AT91C_UDP_FEN; + *AT91C_UDP_GLBSTATE = 0; + } break; - case USB_DESC_TYPE_CONFIG: /* Configuration descriptor */ - usb_write_data(0, usb_nxos_full_config, - MIN(usb_nxos_full_config[2], packet.length)); + case USB_BREQUEST_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 */ + size = usb_device_descriptor[0]; + usb_write_data(0, usb_device_descriptor, + MIN(size, packet.length)); + break; + + case USB_DESC_TYPE_CONFIG: /* Configuration descriptor */ + usb_write_data(0, usb_nxos_full_config, + MIN(usb_nxos_full_config[2], packet.length)); + + /* TODO: Why? This is not specified in the USB specs. */ + if (usb_nxos_full_config[2] < packet.length) + usb_send_null(); + break; + + case USB_DESC_TYPE_STR: /* String or language info. */ + if ((packet.value & USB_WVALUE_INDEX) == 0) { + usb_write_data(0, usb_string_desc, + MIN(usb_string_desc[0], packet.length)); + } else { + /* The host wants a specific string. */ + /* TODO: This should check if the requested string exists. */ + usb_write_data(0, usb_strings[index-1], + MIN(usb_strings[index-1][0], + packet.length)); + } + break; + + case USB_DESC_TYPE_DEVICE_QUALIFIER: /* Device qualifier descriptor. */ + size = usb_dev_qualifier_desc[0]; + usb_write_data(0, usb_dev_qualifier_desc, + MIN(size, packet.length)); + break; + + case USB_DESC_TYPE_BOS: /* BOS descriptor */ + size = usb_bos_desc[2]; + usb_write_data(0, usb_bos_desc, MIN(size, packet.length)); + break; + + default: /* Unknown descriptor, tell the host by stalling. */ + usb_send_stall(0); + } + break; - /* TODO: Why? This is not specified in the USB specs. */ - if (usb_nxos_full_config[2] < packet.length) - usb_send_null(); + case USB_BREQUEST_GET_CONFIG: + /* The host wants to know the ID of the current configuration. */ + usb_write_data(0, (uint8_t *)&(usb_state.current_config), 1); break; - case USB_DESC_TYPE_STR: /* String or language info. */ - if ((packet.value & USB_WVALUE_INDEX) == 0) { - usb_write_data(0, usb_string_desc, - MIN(usb_string_desc[0], packet.length)); - } else { - /* The host wants a specific string. */ - /* TODO: This should check if the requested string exists. */ - usb_write_data(0, usb_strings[index-1], - MIN(usb_strings[index-1][0], - packet.length)); + case USB_BREQUEST_SET_CONFIG: + /* The host selected a new configuration. */ + usb_state.current_config = packet.value; + + /* we ack */ + usb_send_null(); + + /* we set the register in configured mode */ + *AT91C_UDP_GLBSTATE = packet.value > 0 ? + (AT91C_UDP_CONFG | AT91C_UDP_FADDEN) + :AT91C_UDP_FADDEN; + + /* TODO: Make this a little nicer. Not quite sure how. */ + + /* we can only active the EP1 if we have a buffer to get the data */ + /* TODO: This was: + * + * if (usb_state.rx_len == 0 && usb_state.rx_size >= 0) { + * + * The second part always evaluates to true. + */ + if (usb_state.rx_len == 0) { + 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)); } - break; - case USB_DESC_TYPE_DEVICE_QUALIFIER: /* Device qualifier descriptor. */ - size = usb_dev_qualifier_desc[0]; - usb_write_data(0, usb_dev_qualifier_desc, - MIN(size, packet.length)); + 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[3] = 0; + while (AT91C_UDP_CSR[3] != 0); + + usb_state.status = USB_READY; break; - default: /* Unknown descriptor, tell the host by stalling. */ + case USB_BREQUEST_GET_INTERFACE: /* TODO: This should respond, not stall. */ + case USB_BREQUEST_SET_DESCRIPTOR: + default: usb_send_stall(0); + break; } break; - - case USB_BREQUEST_GET_CONFIG: - /* The host wants to know the ID of the current configuration. */ - usb_write_data(0, (uint8_t *)&(usb_state.current_config), 1); + case USB_BMREQUEST_TYPE_CLASS: + 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 0x01: // Standard GATT characteristic + switch (packet.value) { + case 0x2A00: { // device name + const char *name = pbdrv_bluetooth_get_hub_name(); + usb_write_data(0, (const uint8_t *)name, MIN(strlen(name), packet.length)); + break; + } + case 0x2A26: { // firmware revision + const char *fw = PBIO_VERSION_STR; + usb_write_data(0, (const uint8_t *)fw, MIN(strlen(fw), packet.length)); + break; + } + case 0x2A28: { // software revision + const char *sw = PBIO_PROTOCOL_VERSION_STR; + usb_write_data(0, (const uint8_t *)sw, MIN(strlen(sw), packet.length)); + break; + } + default: + usb_send_stall(0); + break; + } + break; + case 0x02: // 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); + usb_write_data(0, caps, MIN(sizeof(caps), packet.length)); + break; + } + default: + usb_send_stall(0); + break; + } + break; + default: + usb_send_stall(0); + break; + } + break; + default: + usb_send_stall(0); + break; + } break; - - case USB_BREQUEST_SET_CONFIG: - /* The host selected a new configuration. */ - usb_state.current_config = packet.value; - - /* we ack */ - usb_send_null(); - - /* we set the register in configured mode */ - *AT91C_UDP_GLBSTATE = packet.value > 0 ? - (AT91C_UDP_CONFG | AT91C_UDP_FADDEN) - :AT91C_UDP_FADDEN; - - /* TODO: Make this a little nicer. Not quite sure how. */ - - /* we can only active the EP1 if we have a buffer to get the data */ - /* TODO: This was: - * - * if (usb_state.rx_len == 0 && usb_state.rx_size >= 0) { - * - * The second part always evaluates to true. - */ - if (usb_state.rx_len == 0) { - 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)); + case USB_BMREQUEST_TYPE_VENDOR: + switch (packet.request) { + case USBD_VENDOR_CODE_WEBUSB: + // Since there is only one WebUSB descriptor, we ignore the index. + usb_write_data(0, WebUSB_DescSet, MIN(sizeof(WebUSB_DescSet), packet.length)); + break; + case USBD_VENDOR_CODE_MS: + // Since there is only one MS descriptor, we ignore the index. + usb_write_data(0, ms_os_desc_set, MIN(sizeof(ms_os_desc_set), packet.length)); + break; + default: + usb_send_stall(0); + break; } - - 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[3] = 0; - while (AT91C_UDP_CSR[3] != 0); - - usb_state.status = USB_READY; break; - - case USB_BREQUEST_GET_INTERFACE: /* TODO: This should respond, not stall. */ - case USB_BREQUEST_SET_DESCRIPTOR: default: usb_send_stall(0); break; diff --git a/lib/pbio/platform/nxt/pbsysconfig.h b/lib/pbio/platform/nxt/pbsysconfig.h index 7f5399e01..38bc90ed2 100644 --- a/lib/pbio/platform/nxt/pbsysconfig.h +++ b/lib/pbio/platform/nxt/pbsysconfig.h @@ -6,6 +6,7 @@ #define PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_IMU_CALIBRATION (0) #define PBSYS_CONFIG_FEATURE_PROGRAM_FORMAT_MULTI_MPY_V6 (1) #define PBSYS_CONFIG_FEATURE_PROGRAM_FORMAT_MULTI_MPY_V6_3_NATIVE (0) +#define PBSYS_CONFIG_HMI_NUM_SLOTS (1) #define PBSYS_CONFIG_HOST (1) #define PBSYS_CONFIG_HOST_STDIN_BUF_SIZE (64) #define PBSYS_CONFIG_MAIN (1) From 334d08d7233574ad7294ce2c864f3f7d7de44d05 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sun, 20 Jul 2025 18:04:43 -0500 Subject: [PATCH 5/5] pbio/drv/usb_nxt: Move driver from NxOS. We have adapted the NxOS USB driver for Pybricks, so now we can move it to the common pbio/drv/usb directory. While moving, the code style is automatically formatted and pbio naming conventions are applied. --- bricks/_common/arm_none_eabi.mk | 1 - bricks/_common/sources.mk | 1 + lib/pbio/drv/reset/reset_nxt.c | 5 +- lib/pbio/drv/usb/usb_nxt.c | 1073 +++++++++++++++++++++ lib/pbio/drv/usb/usb_nxt.h | 11 + lib/pbio/platform/nxt/nxos/drivers/_usb.h | 37 - lib/pbio/platform/nxt/nxos/drivers/usb.c | 1070 -------------------- lib/pbio/platform/nxt/nxos/drivers/usb.h | 89 -- lib/pbio/platform/nxt/pbdrvconfig.h | 3 + lib/pbio/platform/nxt/platform.c | 4 +- 10 files changed, 1093 insertions(+), 1201 deletions(-) create mode 100644 lib/pbio/drv/usb/usb_nxt.c create mode 100644 lib/pbio/drv/usb/usb_nxt.h delete mode 100644 lib/pbio/platform/nxt/nxos/drivers/_usb.h delete mode 100644 lib/pbio/platform/nxt/nxos/drivers/usb.c delete mode 100644 lib/pbio/platform/nxt/nxos/drivers/usb.h diff --git a/bricks/_common/arm_none_eabi.mk b/bricks/_common/arm_none_eabi.mk index c7ec8c647..85b94114a 100644 --- a/bricks/_common/arm_none_eabi.mk +++ b/bricks/_common/arm_none_eabi.mk @@ -515,7 +515,6 @@ NXOS_SRC_C = $(addprefix lib/pbio/platform/nxt/nxos/,\ drivers/radar.c \ drivers/rs485.c \ drivers/sensors.c \ - drivers/usb.c \ interrupts.c \ lock.c \ util.c \ diff --git a/bricks/_common/sources.mk b/bricks/_common/sources.mk index 9ae7489f4..4e61a939d 100644 --- a/bricks/_common/sources.mk +++ b/bricks/_common/sources.mk @@ -187,6 +187,7 @@ PBIO_SRC_C = $(addprefix lib/pbio/,\ drv/uart/uart_stm32f4_ll_irq.c \ drv/uart/uart_stm32l4_ll_dma.c \ drv/usb/usb_ev3.c \ + drv/usb/usb_nxt.c \ drv/usb/usb_stm32.c \ drv/watchdog/watchdog_ev3.c \ drv/watchdog/watchdog_stm32.c \ diff --git a/lib/pbio/drv/reset/reset_nxt.c b/lib/pbio/drv/reset/reset_nxt.c index 3315e67d2..4f1c451d1 100644 --- a/lib/pbio/drv/reset/reset_nxt.c +++ b/lib/pbio/drv/reset/reset_nxt.c @@ -10,11 +10,12 @@ #include #include #include -#include #include #include +#include "../usb/usb_nxt.h" + void pbdrv_reset_init(void) { } @@ -70,7 +71,7 @@ void pbdrv_reset_power_off(void) { } nx__lcd_shutdown(); - nx__usb_disable(); + pbdrv_usb_nxt_deinit(); nx__avr_power_down(); } diff --git a/lib/pbio/drv/usb/usb_nxt.c b/lib/pbio/drv/usb/usb_nxt.c new file mode 100644 index 000000000..c1610932c --- /dev/null +++ b/lib/pbio/drv/usb/usb_nxt.c @@ -0,0 +1,1073 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2007 the NxOS developers + * Copyright (C) 2025 the Pybricks Authors + * + * See AUTHORS for a full list of the developers. + */ + +#include + +#if PBDRV_CONFIG_USB_NXT + +#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" + +/* 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. + */ +#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 + +/* 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. + */ +static const uint8_t pbdrv_usb_nxt_device_descriptor[] = { + 18, USB_DESC_TYPE_DEVICE, /* Packet size and type. */ + 0x10, 0x02, /* This packet is USB 2.1 (needed for BOS descriptors). */ + PBIO_PYBRICKS_USB_DEVICE_CLASS, /* Class code. */ + PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, /* Sub class code. */ + PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, /* Device protocol. */ + MAX_EP0_SIZE, /* Maximum packet size for EP0 (control endpoint). */ + 0x94, 0x06, /* Vendor ID : LEGO */ + 0x02, 0x00, /* Product ID : NXT */ + 0x00, 0x02, /* Product revision: 2.0.0. */ + 1, /* Index of the vendor string. */ + 2, /* Index of the product string. */ + 0, /* Index of the serial number (none for us). */ + 1, /* The number of possible configurations. */ +}; + +static const uint8_t pbdrv_usb_nxt_dev_qualifier_desc[] = { + 10, USB_DESC_TYPE_DEVICE_QUALIFIER, /* Packet size and type. */ + 0x10, 0x02, /* This packet is USB 2.1. */ + PBIO_PYBRICKS_USB_DEVICE_CLASS, /* Class code */ + PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, /* Sub class code */ + PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, /* Device protocol */ + MAX_EP0_SIZE, /* Maximum packet size for EP0. */ + 1, /* The number of possible configurations. */ + 0, /* Reserved for future use, must be zero. */ +}; + +// These enumerations are specific to the configuration of this device. + +enum { + PBDRV_USB_NXT_VENDOR_CODE_WEBUSB, + PBDRV_USB_NXT_VENDOR_CODE_MS, +}; + +// NB: Chromium seems quite particular about the order of these descriptors. +// The WebUSB descriptor must come first and the MS OS 2.0 descriptor be last. +static const uint8_t pbdrv_usb_nxt_bos_desc[] = { + 5, USB_DESC_TYPE_BOS, /* Descriptor length and type. */ + 0x39, 0x00, /* Total length of the descriptor = 57. */ + 2, /* Number of device capabilities. */ + + 24, /* bLength */ + USB_DEVICE_CAPABILITY_TYPE, /* bDescriptorType = Device Capability */ + USB_DEV_CAP_TYPE_PLATFORM, /* bDevCapabilityType */ + 0x00, /* bReserved */ + + /* + * PlatformCapabilityUUID + * WebUSB Platform Capability descriptor + * 3408B638-09A9-47A0-8BFD-A0768815B665 + * RFC 4122 explains the correct byte ordering + */ + 0x38, 0xB6, 0x08, 0x34, /* 32-bit value */ + 0xA9, 0x09, /* 16-bit value */ + 0xA0, 0x47, /* 16-bit value */ + 0x8B, 0xFD, + 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65, + + 0x00, 0x01, /* bcdVersion = 1.00 */ + PBDRV_USB_NXT_VENDOR_CODE_WEBUSB, /* bVendorCode */ + 1, /* iLandingPage */ + + 28, /* bLength */ + USB_DEVICE_CAPABILITY_TYPE, /* bDescriptorType = Device Capability */ + USB_DEV_CAP_TYPE_PLATFORM, /* bDevCapabilityType */ + 0x00, /* bReserved */ + + /* + * PlatformCapabilityUUID + * Microsoft OS 2.0 descriptor platform capability ID + * D8DD60DF-4589-4CC7-9CD2-659D9E648A9F + * RFC 4122 explains the correct byte ordering + */ + 0xDF, 0x60, 0xDD, 0xD8, /* 32-bit value */ + 0x89, 0x45, /* 16-bit value */ + 0xC7, 0x4C, /* 16-bit value */ + 0x9C, 0xD2, + 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F, + + 0x00, 0x00, 0x03, 0x06, /* dwWindowsVersion = 0x06030000 for Windows 8.1 Build */ + 0xA2, 0x00, /* wMSOSDescriptorSetTotalLength = 162 */ + PBDRV_USB_NXT_VENDOR_CODE_MS, /* bMS_VendorCode */ + 0x00, /* bAltEnumCode = Does not support alternate enumeration */ +}; + +static const uint8_t pbdrv_usb_nxt_full_config[] = { + 0x09, USB_DESC_TYPE_CONFIG, /* Descriptor size and type. */ + 0x20, 0x00, /* Total length of the configuration, interface + * description included. + */ + 1, /* The number of interfaces declared by this configuration. */ + 1, /* The ID for this configuration. */ + 0, /* Index of the configuration description string (none). */ + + /* Configuration attributes bitmap. Bit 7 (MSB) must be 1, bit 6 is + * 1 because the NXT is self-powered, bit 5 is 0 because the NXT + * doesn't support remote wakeup, and bits 0-4 are 0 (reserved). + */ + 0xC0, + 0, /* Device power consumption, for non self-powered devices. */ + + /* + * This is the descriptor for the interface associated with the + * configuration. + */ + 0x09, USB_DESC_TYPE_INT, /* Descriptor size and type. */ + 0x00, /* Interface index. */ + 0x00, /* ID for this interface configuration. */ + 0x02, /* The number of endpoints defined by this interface + * (excluding EP0). + */ + PBIO_PYBRICKS_USB_DEVICE_CLASS, /* Interface class ("Vendor specific"). */ + PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, /* Interface subclass (see above). */ + PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, /* Interface protocol (see above). */ + 0x00, /* Index of the string descriptor for this interface (none). */ + + /* + * Descriptor for EP1. + */ + 7, USB_DESC_TYPE_ENDPT, /* Descriptor length and type. */ + 0x1, /* Endpoint number. MSB is zero, meaning this is an OUT EP. */ + 0x2, /* Endpoint type (bulk). */ + MAX_RCV_SIZE, 0x00, /* Maximum packet size (64). */ + 0, /* EP maximum NAK rate (device never NAKs). */ + + /* + * Descriptor for EP2. + */ + 7, USB_DESC_TYPE_ENDPT, /* Descriptor length and type. */ + 0x82, /* Endpoint number. MSB is one, meaning this is an IN EP. */ + 0x2, /* Endpoint type (bulk). */ + MAX_RCV_SIZE, 0x00, /* Maximum packet size (64). */ + 0, /* EP maximum NAK rate (device never NAKs). */ +}; + +static const uint8_t pbdrv_usb_nxt_string_desc[] = { + 4, USB_DESC_TYPE_STR, /* Descriptor length and type. */ + 0x09, 0x04, /* Supported language ID (US English). */ +}; + +static const uint8_t pbdrv_usb_lego_str[] = { + 10, USB_DESC_TYPE_STR, + 'L', 0, + 'E', 0, + 'G', 0, + 'O', 0, +}; + +static const uint8_t pbdrv_usb_nxt_str[] = { + 30, USB_DESC_TYPE_STR, + 'N', 0, + 'X', 0, + 'T', 0, + ' ', 0, + '+', 0, + ' ', 0, + 'P', 0, + 'y', 0, + 'b', 0, + 'r', 0, + 'i', 0, + 'c', 0, + 'k', 0, + 's', 0, +}; + +/* Internal lookup table mapping string descriptors to their indices + * in the USB string descriptor table. + */ +static const uint8_t *pbdrv_usb_nxt_strings[] = { + pbdrv_usb_lego_str, + pbdrv_usb_nxt_str, +}; + +typedef enum { + USB_UNINITIALIZED, + USB_READY, + USB_BUSY, + USB_SUSPENDED, +} pbdrv_usb_nxt_status_t; + +/* + * 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; + + /* Holds the status the bus was in before entering suspend. */ + pbdrv_usb_nxt_status_t pre_suspend_status; + + /* 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; + + /* The currently selected USB configuration. */ + uint8_t current_config; + + /* 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]; + + /* Used to write the data from the EP1 + */ + uint8_t *rx_data; + + /* size of the rx data buffer */ + uint32_t rx_size; + + /* length of the read packet (0 if none) */ + uint32_t rx_len; + + /* The USB controller has two hardware input buffers. This remembers + * the one currently in use. + */ + uint8_t current_rx_bank; +} pbdrv_usb_nxt_state; + +/* The flags in the UDP_CSR register are a little strange: writing to + * them does not instantly change their value. Their value will change + * to reflect the write when the USB controller has taken the change + * into account. The driver must wait until the controller + * acknowledges changes to CSR. + * + * These helpers set/clear CSR flags, and then loop waiting for the + * controller to synchronize + */ +static void pbdrv_usb_nxt_csr_clear_flag(uint8_t endpoint, uint32_t flags) { + AT91C_UDP_CSR[endpoint] &= ~(flags); + while (AT91C_UDP_CSR[endpoint] & (flags)) { + ; + } +} + +static void pbdrv_usb_nxt_csr_set_flag(uint8_t endpoint, uint32_t flags) { + AT91C_UDP_CSR[endpoint] |= (flags); + while ((AT91C_UDP_CSR[endpoint] & (flags)) != (flags)) { + ; + } +} + +/* Starts sending data to the host. If the data cannot fit into a + * single USB packet, the data is split and scheduled to be sent in + * several packets. + */ +static void pbdrv_usb_nxt_write_data(int endpoint, const uint8_t *ptr, uint32_t length) { + uint32_t packet_size; + int tx; + + if (endpoint != 0 && endpoint != 2) { + return; + } + + tx = endpoint / 2; + + /* The bus is now busy. */ + pbdrv_usb_nxt_state.status = USB_BUSY; + + if (endpoint == 0) { + packet_size = MIN(MAX_EP0_SIZE, length); + } else { + packet_size = MIN(MAX_SND_SIZE, length); + } + + /* If there is more data than can fit in a single packet, queue the + * rest up. + */ + 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; + } else { + pbdrv_usb_nxt_state.tx_data[tx] = NULL; + pbdrv_usb_nxt_state.tx_len[tx] = 0; + } + + /* Push a packet into the USB FIFO, and tell the controller to send. */ + while (packet_size) { + AT91C_UDP_FDR[endpoint] = *ptr; + ptr++; + packet_size--; + } + pbdrv_usb_nxt_csr_set_flag(endpoint, AT91C_UDP_TXPKTRDY); +} + +/* Read one data packet from the USB controller. + * Assume that pbdrv_usb_nxt_state.rx_data and pbdrv_usb_nxt_state.rx_len are set. + */ +static void pbdrv_usb_nxt_read_data(int endpoint) { + uint16_t i; + uint16_t total; + + /* Given our configuration, we should only be getting packets on + * endpoint 1. Ignore data on any other endpoint. + * (note: data from EP0 are managed by usb_manage_setup()) + */ + if (endpoint != 1) { + pbdrv_usb_nxt_csr_clear_flag(endpoint, AT91C_UDP_RX_DATA_BK0 | AT91C_UDP_RX_DATA_BK1); + return; + } + + /* must not happen ! */ + if (pbdrv_usb_nxt_state.rx_len > 0) { + return; + } + + total = (AT91C_UDP_CSR[endpoint] & AT91C_UDP_RXBYTECNT) >> 16; + + /* we start reading */ + /* all the bytes will be put in rx_data */ + for (i = 0; + i < total && i < pbdrv_usb_nxt_state.rx_size; + i++) { + pbdrv_usb_nxt_state.rx_data[i] = AT91C_UDP_FDR[1]; + } + + pbdrv_usb_nxt_state.rx_len = i; + + /* if we have read all the byte ... */ + if (i == total) { + /* Acknowledge reading the current RX bank, and switch to the other. */ + pbdrv_usb_nxt_csr_clear_flag(1, pbdrv_usb_nxt_state.current_rx_bank); + if (pbdrv_usb_nxt_state.current_rx_bank == AT91C_UDP_RX_DATA_BK0) { + pbdrv_usb_nxt_state.current_rx_bank = AT91C_UDP_RX_DATA_BK1; + } else { + pbdrv_usb_nxt_state.current_rx_bank = AT91C_UDP_RX_DATA_BK0; + } + } + /* else we let the interruption running : + * after this function, the interruption should be disabled until + * a new buffer to read is provided */ +} + +/* On the endpoint 0: A stall is USB's way of sending + * back an error (either "not understood" or "not handled + * by this device"). The connexion will be reinitialized + * by the host. + * 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); +} + +/* During setup, we need to send packets with null data. */ +static void pbdrv_usb_nxt_send_null(void) { + pbdrv_usb_nxt_write_data(0, NULL, 0); +} + +static const uint8_t pbdrv_usb_desc_set_ms_os[] = { + 0x0A, 0x00, /* wLength = 10 */ + 0x00, 0x00, /* wDescriptorType = MS_OS_20_SET_HEADER_DESCRIPTOR */ + 0x00, 0x00, 0x03, 0x06, /* dwWindowsVersion = 0x06030000 for Windows 8.1 Build */ + 0xA2, 0x00, /* wTotalLength = 162 */ + + 0x14, 0x00, /* wLength = 20 */ + 0x03, 0x00, /* wDescriptorType = MS_OS_20_FEATURE_COMPATBLE_ID */ + 'W', 'I', 'N', 'U', 'S', 'B', /* CompatibleID */ + 0x00, 0x00, /* CompatibleID (cont.) */ + 0x00, 0x00, 0x00, 0x00, /* SubCompatibleID */ + 0x00, 0x00, 0x00, 0x00, /* SubCompatibleID (cont.) */ + + 0x84, 0x00, /* wLength = 132 */ + 0x04, 0x00, /* wDescriptorType = MS_OS_20_FEATURE_REG_PROPERTY */ + 0x07, 0x00, /* wStringType = REG_MULTI_SZ */ + /* wPropertyNameLength = 42 */ + 0x2A, 0x00, + /* PropertyName = DeviceInterfaceGUIDs */ + 'D', '\0', + 'e', '\0', + 'v', '\0', + 'i', '\0', + 'c', '\0', + 'e', '\0', + 'I', '\0', + 'n', '\0', + 't', '\0', + 'e', '\0', + 'r', '\0', + 'f', '\0', + 'a', '\0', + 'c', '\0', + 'e', '\0', + 'G', '\0', + 'U', '\0', + 'I', '\0', + 'D', '\0', + 's', '\0', + '\0', '\0', + + /* wPropertyDataLength = 80 */ + 0x50, 0x00, + /* PropertyData = {A5C44A4C-53D4-4389-9821-AE95051908A1} */ + '{', '\0', + 'A', '\0', + '5', '\0', + 'C', '\0', + '4', '\0', + '4', '\0', + 'A', '\0', + '4', '\0', + 'C', '\0', + '-', '\0', + '5', '\0', + '3', '\0', + 'D', '\0', + '4', '\0', + '-', '\0', + '4', '\0', + '3', '\0', + '8', '\0', + '9', '\0', + '-', '\0', + '9', '\0', + '8', '\0', + '2', '\0', + '1', '\0', + '-', '\0', + 'A', '\0', + 'E', '\0', + '9', '\0', + '5', '\0', + '0', '\0', + '5', '\0', + '1', '\0', + '9', '\0', + '0', '\0', + '8', '\0', + 'A', '\0', + '1', '\0', + '}', '\0', + '\0', '\0', + '\0', '\0', +}; + +static const uint8_t pbdrv_usb_desc_set_webusb[] = { + 20, /* bLength */ + 0x03, /* bDescriptorType = URL */ + 0x01, /* bScheme = https:// */ + + /* URL */ + #if PBIO_VERSION_LEVEL_HEX == 0xA + 'a', 'l', 'p', 'h', 'a', + #elif PBIO_VERSION_LEVEL_HEX == 0xB + 'b', 'e', 't', 'a', + #else + 'c', 'o', 'd', 'e', + #endif + '.', 'p', 'y', 'b', 'r', 'i', 'c', 'k', 's', '.', 'c', 'o', 'm', +}; + +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) { + uint32_t size; + uint8_t index; + + switch (packet->request) { + case USB_BREQUEST_GET_STATUS: { + /* The host wants to know our status. + * + * If it wants the device status, just reply that the NXT is still + * self-powered (as first declared by the setup packets). If it + * wants endpoint status, reply that the endpoint has not + * halted. Any other status request types are reserved, which + * translates to replying zero. + */ + uint16_t response; + + if ((packet->request_attrs & USB_BMREQUEST_RCPT) == USB_BMREQUEST_RCPT_DEV) { + response = 1; + } else { + response = 0; + } + + pbdrv_usb_nxt_write_data(0, (uint8_t *)&response, 2); + } + break; + + case USB_BREQUEST_CLEAR_FEATURE: + case USB_BREQUEST_SET_INTERFACE: + case USB_BREQUEST_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. + */ + pbdrv_usb_nxt_send_null(); + break; + + case USB_BREQUEST_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_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) { + *AT91C_UDP_FADDR = AT91C_UDP_FEN; + *AT91C_UDP_GLBSTATE = 0; + } + break; + + case USB_BREQUEST_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 */ + size = pbdrv_usb_nxt_device_descriptor[0]; + pbdrv_usb_nxt_write_data(0, pbdrv_usb_nxt_device_descriptor, + MIN(size, packet->length)); + break; + + case USB_DESC_TYPE_CONFIG: /* Configuration descriptor */ + pbdrv_usb_nxt_write_data(0, pbdrv_usb_nxt_full_config, + MIN(pbdrv_usb_nxt_full_config[2], packet->length)); + + /* TODO: Why? This is not specified in the USB specs. */ + if (pbdrv_usb_nxt_full_config[2] < packet->length) { + pbdrv_usb_nxt_send_null(); + } + break; + + case USB_DESC_TYPE_STR: /* String or language info. */ + if ((packet->value & USB_WVALUE_INDEX) == 0) { + pbdrv_usb_nxt_write_data(0, pbdrv_usb_nxt_string_desc, + MIN(pbdrv_usb_nxt_string_desc[0], packet->length)); + } else { + /* The host wants a specific string. */ + /* TODO: This should check if the requested string exists. */ + pbdrv_usb_nxt_write_data(0, pbdrv_usb_nxt_strings[index - 1], + MIN(pbdrv_usb_nxt_strings[index - 1][0], + packet->length)); + } + break; + + case USB_DESC_TYPE_DEVICE_QUALIFIER: /* Device qualifier descriptor. */ + size = pbdrv_usb_nxt_dev_qualifier_desc[0]; + pbdrv_usb_nxt_write_data(0, pbdrv_usb_nxt_dev_qualifier_desc, + MIN(size, packet->length)); + break; + + case USB_DESC_TYPE_BOS: /* BOS descriptor */ + size = pbdrv_usb_nxt_bos_desc[2]; + pbdrv_usb_nxt_write_data(0, pbdrv_usb_nxt_bos_desc, MIN(size, packet->length)); + break; + + default: /* Unknown descriptor, tell the host by stalling. */ + pbdrv_usb_nxt_send_stall(0); + } + break; + + case USB_BREQUEST_GET_CONFIG: + /* 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); + break; + + case USB_BREQUEST_SET_CONFIG: + /* The host selected a new configuration. */ + pbdrv_usb_nxt_state.current_config = packet->value; + + /* we ack */ + pbdrv_usb_nxt_send_null(); + + /* we set the register in configured mode */ + *AT91C_UDP_GLBSTATE = packet->value > 0 ? + (AT91C_UDP_CONFG | AT91C_UDP_FADDEN) :AT91C_UDP_FADDEN; + + /* TODO: Make this a little nicer. Not quite sure how. */ + + /* we can only active the EP1 if we have a buffer to get the data */ + /* TODO: This was: + * + * if (pbdrv_usb_nxt_state.rx_len == 0 && pbdrv_usb_nxt_state.rx_size >= 0) { + * + * The second part always evaluates to true. + */ + if (pbdrv_usb_nxt_state.rx_len == 0) { + 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)) { + ; + } + } + + 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[3] = 0; + while (AT91C_UDP_CSR[3] != 0) { + ; + } + + pbdrv_usb_nxt_state.status = USB_READY; + break; + + case USB_BREQUEST_GET_INTERFACE: /* TODO: This should respond, not stall. */ + case USB_BREQUEST_SET_DESCRIPTOR: + default: + pbdrv_usb_nxt_send_stall(0); + 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 0x01: // Standard GATT characteristic + switch (packet->value) { + case 0x2A00: { // device name + const char *name = pbdrv_bluetooth_get_hub_name(); + pbdrv_usb_nxt_write_data(0, (const uint8_t *)name, + MIN(strlen(name), packet->length)); + break; + } + case 0x2A26: { // firmware revision + const char *fw = PBIO_VERSION_STR; + pbdrv_usb_nxt_write_data(0, (const uint8_t *)fw, + MIN(strlen(fw), packet->length)); + break; + } + case 0x2A28: { // software revision + const char *sw = PBIO_PROTOCOL_VERSION_STR; + pbdrv_usb_nxt_write_data(0, (const uint8_t *)sw, + MIN(strlen(sw), packet->length)); + break; + } + default: + pbdrv_usb_nxt_send_stall(0); + break; + } + break; + case 0x02: // 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; + } + break; + default: + pbdrv_usb_nxt_send_stall(0); + break; + } +} + +/* Handle receiving and responding to setup packets on EP0. */ +static uint32_t pbdrv_usb_nxt_manage_setup_packet(void) { + /* The structure of a USB setup packet. */ + pbdrv_usb_nxt_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 */ + } + + pbdrv_usb_nxt_csr_clear_flag(0, AT91C_UDP_RXSETUP); + + switch (packet.request_attrs & USB_BMREQUEST_TYPE) { + case USB_BMREQUEST_TYPE_STD: + pbdrv_usb_handle_std_request(&packet); + break; + case USB_BMREQUEST_TYPE_CLASS: + pbdrv_usb_nxt_handle_class_request(&packet); + break; + case USB_BMREQUEST_TYPE_VENDOR: + switch (packet.request) { + case PBDRV_USB_NXT_VENDOR_CODE_WEBUSB: + // Since there is only one WebUSB descriptor, we ignore the index. + pbdrv_usb_nxt_write_data(0, pbdrv_usb_desc_set_webusb, + MIN(sizeof(pbdrv_usb_desc_set_webusb), packet.length)); + break; + case PBDRV_USB_NXT_VENDOR_CODE_MS: + // Since there is only one MS descriptor, we ignore the index. + pbdrv_usb_nxt_write_data(0, pbdrv_usb_desc_set_ms_os, + MIN(sizeof(pbdrv_usb_desc_set_ms_os), packet.length)); + break; + default: + pbdrv_usb_nxt_send_stall(0); + break; + } + break; + default: + pbdrv_usb_nxt_send_stall(0); + break; + } + + return packet.request; +} + +/* The main USB interrupt handler. */ +static void pbdrv_usb_nxt_isr(void) { + uint8_t endpoint = 127; + uint32_t csr, isr; + + isr = *AT91C_UDP_ISR; + + /* We sent a stall, the host has acknowledged the stall. */ + if (AT91C_UDP_CSR[0] & AT91C_UDP_ISOERROR) { + pbdrv_usb_nxt_csr_clear_flag(0, AT91C_UDP_FORCESTALL | AT91C_UDP_ISOERROR); + } + + /* End of bus reset. Starting the device setup procedure. */ + if (isr & AT91C_UDP_ENDBUSRES) { + pbdrv_usb_nxt_state.status = USB_UNINITIALIZED; + + /* Disable and clear all interruptions, reverting to the base + * state. + */ + *AT91C_UDP_IDR = ~0; + *AT91C_UDP_ICR = ~0; + + /* Reset all endpoint FIFOs. */ + *AT91C_UDP_RSTEP = ~0; + *AT91C_UDP_RSTEP = 0; + + /* Reset internal state. */ + pbdrv_usb_nxt_state.current_rx_bank = AT91C_UDP_RX_DATA_BK0; + pbdrv_usb_nxt_state.current_config = 0; + + /* Reset EP0 to a basic control endpoint. */ + /* TODO: The while is ugly. Fix it. */ + AT91C_UDP_CSR[0] = AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_CTRL; + while (AT91C_UDP_CSR[0] != (AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_CTRL)) { + ; + } + + /* Enable interrupt handling for all three endpoints, as well as + * suspend/resume. + */ + *AT91C_UDP_IER = (AT91C_UDP_EPINT0 | AT91C_UDP_EPINT1 | + AT91C_UDP_EPINT2 | AT91C_UDP_EPINT3 | + AT91C_UDP_RXSUSP | AT91C_UDP_RXRSM); + + /* Enable the function endpoints, setting address 0, and return + * immediately. Given that we've just reset everything, there's no + * point in continuing. + */ + *AT91C_UDP_FADDR = AT91C_UDP_FEN; + + return; + } + + if (isr & AT91C_UDP_WAKEUP) { + *AT91C_UDP_ICR = AT91C_UDP_WAKEUP; + isr &= ~AT91C_UDP_WAKEUP; + } + + if (isr & AT91C_UDP_SOFINT) { + *AT91C_UDP_ICR = AT91C_UDP_SOFINT; + isr &= ~AT91C_UDP_SOFINT; + } + + 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++) { + if (isr & (1 << endpoint)) { + break; + } + } + + if (endpoint == 0) { + + if (AT91C_UDP_CSR[0] & AT91C_UDP_RXSETUP) { + csr = pbdrv_usb_nxt_manage_setup_packet(); + return; + } + } + + if (endpoint < PBDRV_USB_NXT_N_ENDPOINTS) { /* if an endpoint was specified */ + csr = AT91C_UDP_CSR[endpoint]; + + 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) { + ; + } + } + + pbdrv_usb_nxt_read_data(endpoint); + + return; + } + + if (csr & AT91C_UDP_TXCOMP) { + + /* 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) { + /* 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; + /* 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; + } + + /* and we will send the following data */ + if (pbdrv_usb_nxt_state.tx_len[endpoint] > 0 + && 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]); + } else { + /* then it means that we sent all the data and the host has acknowledged it */ + pbdrv_usb_nxt_state.status = USB_READY; + } + return; + } + + } + + /* We clear also the unused bits, + * just "to be sure" */ + if (isr) { + *AT91C_UDP_ICR = 0xFFFFC4F0; + } +} + +void pbdrv_usb_nxt_deinit(void) { + nx_aic_disable(AT91C_ID_UDP); + + *AT91C_PIOA_PER = (1 << 16); + *AT91C_PIOA_OER = (1 << 16); + *AT91C_PIOA_SODR = (1 << 16); + nx_systick_wait_ms(200); +} + +void pbdrv_usb_init(void) { + pbdrv_usb_nxt_deinit(); + memset((void *)&pbdrv_usb_nxt_state, 0, sizeof(pbdrv_usb_nxt_state)); + + uint32_t state = nx_interrupts_disable(); + + /* usb pll was already set in init.S */ + + /* enable peripheral clock */ + *AT91C_PMC_PCER = (1 << AT91C_ID_UDP); + + /* enable system clock */ + *AT91C_PMC_SCER = AT91C_PMC_UDP; + + /* disable all the interruptions */ + *AT91C_UDP_IDR = ~0; + + /* reset all the endpoints */ + *AT91C_UDP_RSTEP = 0xF; + *AT91C_UDP_RSTEP = 0; + + *AT91C_UDP_ICR = 0xFFFFFFFF; + + /* Install the interruption routine */ + + /* the first interruption we will get is an ENDBUSRES + * this interruption is always emit (can't be disable with UDP_IER) + */ + /* other interruptions will be enabled when needed */ + nx_aic_install_isr(AT91C_ID_UDP, AIC_PRIO_DRIVER, AIC_TRIG_LEVEL, pbdrv_usb_nxt_isr); + + nx_interrupts_enable(state); + + /* Enable the UDP pull up by outputting a zero on PA.16 */ + /* Enabling the pull up will tell to the host (the computer) that + * we are ready for a communication + */ + *AT91C_PIOA_PER = (1 << 16); + *AT91C_PIOA_OER = (1 << 16); + *AT91C_PIOA_CODR = (1 << 16); +} + +bool nx_usb_can_write(void) { + return pbdrv_usb_nxt_state.status == USB_READY; +} + +void nx_usb_write(uint8_t *data, uint32_t length) { + NX_ASSERT_MSG(pbdrv_usb_nxt_state.status != USB_UNINITIALIZED, + "USB not init"); + NX_ASSERT_MSG(pbdrv_usb_nxt_state.status != USB_SUSPENDED, + "USB asleep"); + NX_ASSERT(data != NULL); + NX_ASSERT(length > 0); + + /* TODO: Make call asynchronous */ + while (pbdrv_usb_nxt_state.status != USB_READY) { + ; + } + + /* start sending the data */ + pbdrv_usb_nxt_write_data(2, data, length); +} + +bool nx_usb_data_written(void) { + return pbdrv_usb_nxt_state.tx_len[1] == 0; +} + +bool nx_usb_is_connected(void) { + return pbdrv_usb_nxt_state.status != USB_UNINITIALIZED; +} + +void nx_usb_read(uint8_t *data, uint32_t length) { + pbdrv_usb_nxt_state.rx_data = data; + pbdrv_usb_nxt_state.rx_size = length; + pbdrv_usb_nxt_state.rx_len = 0; + + 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; + } +} + +uint32_t nx_usb_data_read(void) { + return pbdrv_usb_nxt_state.rx_len; +} + +#endif // PBDRV_CONFIG_USB_NXT diff --git a/lib/pbio/drv/usb/usb_nxt.h b/lib/pbio/drv/usb/usb_nxt.h new file mode 100644 index 000000000..d220f5b18 --- /dev/null +++ b/lib/pbio/drv/usb/usb_nxt.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 The Pybricks Authors + +#ifndef PBIO_DRV_USB_NXT_H +#define PBIO_DRV_USB_NXT_H + +#include + +void pbdrv_usb_nxt_deinit(void); + +#endif // PBIO_DRV_USB_NXT_H diff --git a/lib/pbio/platform/nxt/nxos/drivers/_usb.h b/lib/pbio/platform/nxt/nxos/drivers/_usb.h deleted file mode 100644 index 522706fab..000000000 --- a/lib/pbio/platform/nxt/nxos/drivers/_usb.h +++ /dev/null @@ -1,37 +0,0 @@ -/** @file _usb.h - * @brief USB communication internal interface. - */ - -/* Copyright (C) 2007 the NxOS developers - * - * See AUTHORS for a full list of the developers. - * - * Redistribution of this file is permitted under - * the terms of the GNU Public License (GPL) version 2. - */ - -#ifndef __NXOS_BASE_DRIVERS__USB_H__ -#define __NXOS_BASE_DRIVERS__USB_H__ - -#include "nxos/drivers/usb.h" - -/** @addtogroup driverinternal */ -/*@{*/ - -/** @defgroup usbinternal USB communication */ -/*@{*/ - -/** Initialize the USB driver. */ -void nx__usb_init(void); - -/** Perform an orderly shutdown of the USB driver. - * - * If the brick is connected to a USB bus, it will be properly - * unregistered. - */ -void nx__usb_disable(void); - -/*@}*/ -/*@}*/ - -#endif /* __NXOS_BASE_DRIVERS__USB_H__ */ diff --git a/lib/pbio/platform/nxt/nxos/drivers/usb.c b/lib/pbio/platform/nxt/nxos/drivers/usb.c deleted file mode 100644 index 35aa2ab21..000000000 --- a/lib/pbio/platform/nxt/nxos/drivers/usb.c +++ /dev/null @@ -1,1070 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* Copyright (C) 2007 the NxOS developers - * Copyright (C) 2025 the Pybricks Authors - * - * See AUTHORS for a full list of the developers. - */ - -#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" - -#include "nxos/drivers/_usb.h" - -/* The USB controller supports up to 4 endpoints. */ -#define N_ENDPOINTS 4 - -/* Maximum data packet sizes. Endpoint 0 is a special case (control - * endpoint). - * - * TODO: Discuss the need/use for separating recv/send. - */ -#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 - - -/* 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. - */ -static const uint8_t usb_device_descriptor[] = { - 18, USB_DESC_TYPE_DEVICE, /* Packet size and type. */ - 0x10, 0x02, /* This packet is USB 2.1 (needed for BOS descriptors). */ - PBIO_PYBRICKS_USB_DEVICE_CLASS, /* Class code. */ - PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, /* Sub class code. */ - PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, /* Device protocol. */ - MAX_EP0_SIZE, /* Maximum packet size for EP0 (control endpoint). */ - 0x94, 0x06, /* Vendor ID : LEGO */ - 0x02, 0x00, /* Product ID : NXT */ - 0x00, 0x02, /* Product revision: 2.0.0. */ - 1, /* Index of the vendor string. */ - 2, /* Index of the product string. */ - 0, /* Index of the serial number (none for us). */ - 1, /* The number of possible configurations. */ -}; - -static const uint8_t usb_dev_qualifier_desc[] = { - 10, USB_DESC_TYPE_DEVICE_QUALIFIER, /* Packet size and type. */ - 0x10, 0x02, /* This packet is USB 2.1. */ - PBIO_PYBRICKS_USB_DEVICE_CLASS, /* Class code */ - PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, /* Sub class code */ - PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, /* Device protocol */ - MAX_EP0_SIZE, /* Maximum packet size for EP0. */ - 1, /* The number of possible configurations. */ - 0 /* Reserved for future use, must be zero. */ -}; - -// These enumerations are specific to the configuration of this device. - -enum { - USBD_VENDOR_CODE_WEBUSB, - USBD_VENDOR_CODE_MS, -}; - -// NB: Chromium seems quite particular about the order of these descriptors. -// The WebUSB descriptor must come first and the MS OS 2.0 descriptor be last. -static const uint8_t usb_bos_desc[] = { - 5, USB_DESC_TYPE_BOS, /* Descriptor length and type. */ - 0x39, 0x00, /* Total length of the descriptor = 57. */ - 2, /* Number of device capabilities. */ - - 24, /* bLength */ - USB_DEVICE_CAPABILITY_TYPE, /* bDescriptorType = Device Capability */ - USB_DEV_CAP_TYPE_PLATFORM, /* bDevCapabilityType */ - 0x00, /* bReserved */ - - /* - * PlatformCapabilityUUID - * WebUSB Platform Capability descriptor - * 3408B638-09A9-47A0-8BFD-A0768815B665 - * RFC 4122 explains the correct byte ordering - */ - 0x38, 0xB6, 0x08, 0x34, /* 32-bit value */ - 0xA9, 0x09, /* 16-bit value */ - 0xA0, 0x47, /* 16-bit value */ - 0x8B, 0xFD, - 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65, - - 0x00, 0x01, /* bcdVersion = 1.00 */ - USBD_VENDOR_CODE_WEBUSB, /* bVendorCode */ - 1, /* iLandingPage */ - - 28, /* bLength */ - USB_DEVICE_CAPABILITY_TYPE, /* bDescriptorType = Device Capability */ - USB_DEV_CAP_TYPE_PLATFORM, /* bDevCapabilityType */ - 0x00, /* bReserved */ - - /* - * PlatformCapabilityUUID - * Microsoft OS 2.0 descriptor platform capability ID - * D8DD60DF-4589-4CC7-9CD2-659D9E648A9F - * RFC 4122 explains the correct byte ordering - */ - 0xDF, 0x60, 0xDD, 0xD8, /* 32-bit value */ - 0x89, 0x45, /* 16-bit value */ - 0xC7, 0x4C, /* 16-bit value */ - 0x9C, 0xD2, - 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F, - - 0x00, 0x00, 0x03, 0x06, /* dwWindowsVersion = 0x06030000 for Windows 8.1 Build */ - 0xA2, 0x00, /* wMSOSDescriptorSetTotalLength = 162 */ - USBD_VENDOR_CODE_MS, /* bMS_VendorCode */ - 0x00, /* bAltEnumCode = Does not support alternate enumeration */ -}; - -static const uint8_t usb_nxos_full_config[] = { - 0x09, USB_DESC_TYPE_CONFIG, /* Descriptor size and type. */ - 0x20, 0x00, /* Total length of the configuration, interface - * description included. - */ - 1, /* The number of interfaces declared by this configuration. */ - 1, /* The ID for this configuration. */ - 0, /* Index of the configuration description string (none). */ - - /* Configuration attributes bitmap. Bit 7 (MSB) must be 1, bit 6 is - * 1 because the NXT is self-powered, bit 5 is 0 because the NXT - * doesn't support remote wakeup, and bits 0-4 are 0 (reserved). - */ - 0xC0, - 0, /* Device power consumption, for non self-powered devices. */ - - - /* - * This is the descriptor for the interface associated with the - * configuration. - */ - 0x09, USB_DESC_TYPE_INT, /* Descriptor size and type. */ - 0x00, /* Interface index. */ - 0x00, /* ID for this interface configuration. */ - 0x02, /* The number of endpoints defined by this interface - * (excluding EP0). - */ - PBIO_PYBRICKS_USB_DEVICE_CLASS, /* Interface class ("Vendor specific"). */ - PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, /* Interface subclass (see above). */ - PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, /* Interface protocol (see above). */ - 0x00, /* Index of the string descriptor for this interface (none). */ - - - /* - * Descriptor for EP1. - */ - 7, USB_DESC_TYPE_ENDPT, /* Descriptor length and type. */ - 0x1, /* Endpoint number. MSB is zero, meaning this is an OUT EP. */ - 0x2, /* Endpoint type (bulk). */ - MAX_RCV_SIZE, 0x00, /* Maximum packet size (64). */ - 0, /* EP maximum NAK rate (device never NAKs). */ - - - /* - * Descriptor for EP2. - */ - 7, USB_DESC_TYPE_ENDPT, /* Descriptor length and type. */ - 0x82, /* Endpoint number. MSB is one, meaning this is an IN EP. */ - 0x2, /* Endpoint type (bulk). */ - MAX_RCV_SIZE, 0x00, /* Maximum packet size (64). */ - 0, /* EP maximum NAK rate (device never NAKs). */ -}; - - -static const uint8_t usb_string_desc[] = { - 4, USB_DESC_TYPE_STR, /* Descriptor length and type. */ - 0x09, 0x04, /* Supported language ID (US English). */ -}; - -static const uint8_t usb_lego_str[] = { - 10, USB_DESC_TYPE_STR, - 'L', 0, - 'E', 0, - 'G', 0, - 'O', 0 -}; - -static const uint8_t usb_nxt_str[] = { - 30, USB_DESC_TYPE_STR, - 'N', 0, - 'X', 0, - 'T', 0, - ' ', 0, - '+', 0, - ' ', 0, - 'P', 0, - 'y', 0, - 'b', 0, - 'r', 0, - 'i', 0, - 'c', 0, - 'k', 0, - 's', 0 -}; - - -/* Internal lookup table mapping string descriptors to their indices - * in the USB string descriptor table. - */ -static const uint8_t *usb_strings[] = { - usb_lego_str, - usb_nxt_str, -}; - - -/* - * 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. */ - enum usb_status { - USB_UNINITIALIZED = 0, - USB_READY, - USB_BUSY, - USB_SUSPENDED, - } status; - - /* Holds the status the bus was in before entering suspend. */ - enum usb_status pre_suspend_status; - - /* 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; - - /* The currently selected USB configuration. */ - uint8_t current_config; - - /* 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]; - - /* Used to write the data from the EP1 - */ - uint8_t *rx_data; - - /* size of the rx data buffer */ - uint32_t rx_size; - - /* length of the read packet (0 if none) */ - uint32_t rx_len; - - - /* The USB controller has two hardware input buffers. This remembers - * the one currently in use. - */ - uint8_t current_rx_bank; -} usb_state; - - -/* The flags in the UDP_CSR register are a little strange: writing to - * them does not instantly change their value. Their value will change - * to reflect the write when the USB controller has taken the change - * into account. The driver must wait until the controller - * acknowledges changes to CSR. - * - * These helpers set/clear CSR flags, and then loop waiting for the - * controller to synchronize - */ -static inline void usb_csr_clear_flag(uint8_t endpoint, uint32_t flags) { - AT91C_UDP_CSR[endpoint] &= ~(flags); - while (AT91C_UDP_CSR[endpoint] & (flags)); -} - -static inline void usb_csr_set_flag(uint8_t endpoint, uint32_t flags) { - AT91C_UDP_CSR[endpoint] |= (flags); - while ( (AT91C_UDP_CSR[endpoint] & (flags)) != (flags)); -} - - -/* Starts sending data to the host. If the data cannot fit into a - * single USB packet, the data is split and scheduled to be sent in - * several packets. - */ -static void usb_write_data(int endpoint, const uint8_t *ptr, uint32_t length) { - uint32_t packet_size; - int tx; - - if (endpoint != 0 && endpoint != 2) - return; - - tx = endpoint / 2; - - /* The bus is now busy. */ - usb_state.status = USB_BUSY; - - if (endpoint == 0) - packet_size = MIN(MAX_EP0_SIZE, length); - else - packet_size = MIN(MAX_SND_SIZE, length); - - /* If there is more data than can fit in a single packet, queue the - * rest up. - */ - if (length > packet_size) { - length -= packet_size; - usb_state.tx_data[tx] = (uint8_t*)(ptr + packet_size); - usb_state.tx_len[tx] = length; - } else { - usb_state.tx_data[tx] = NULL; - usb_state.tx_len[tx] = 0; - } - - /* Push a packet into the USB FIFO, and tell the controller to send. */ - while(packet_size) { - AT91C_UDP_FDR[endpoint] = *ptr; - ptr++; - packet_size--; - } - usb_csr_set_flag(endpoint, AT91C_UDP_TXPKTRDY); -} - - -/* Read one data packet from the USB controller. - * Assume that usb_state.rx_data and usb_state.rx_len are set. - */ -static void usb_read_data(int endpoint) { - uint16_t i; - uint16_t total; - - /* Given our configuration, we should only be getting packets on - * endpoint 1. Ignore data on any other endpoint. - * (note: data from EP0 are managed by usb_manage_setup()) - */ - if (endpoint != 1) { - usb_csr_clear_flag(endpoint, AT91C_UDP_RX_DATA_BK0 | AT91C_UDP_RX_DATA_BK1); - return; - } - - /* must not happen ! */ - if (usb_state.rx_len > 0) - return; - - total = (AT91C_UDP_CSR[endpoint] & AT91C_UDP_RXBYTECNT) >> 16; - - /* we start reading */ - /* all the bytes will be put in rx_data */ - for (i = 0 ; - i < total && i < usb_state.rx_size ; - i++) - usb_state.rx_data[i] = AT91C_UDP_FDR[1]; - - usb_state.rx_len = i; - - - /* if we have read all the byte ... */ - if (i == total) { - /* Acknowledge reading the current RX bank, and switch to the other. */ - usb_csr_clear_flag(1, usb_state.current_rx_bank); - if (usb_state.current_rx_bank == AT91C_UDP_RX_DATA_BK0) - usb_state.current_rx_bank = AT91C_UDP_RX_DATA_BK1; - else - usb_state.current_rx_bank = AT91C_UDP_RX_DATA_BK0; - } - /* else we let the interruption running : - * after this function, the interruption should be disabled until - * a new buffer to read is provided */ -} - - -/* On the endpoint 0: A stall is USB's way of sending - * back an error (either "not understood" or "not handled - * by this device"). The connexion will be reinitialized - * by the host. - * On the other endpoint : Indicates to the host that the endpoint is halted - */ -static void usb_send_stall(int endpoint) { - usb_state.status = USB_UNINITIALIZED; - usb_csr_set_flag(endpoint, AT91C_UDP_FORCESTALL); -} - - -/* During setup, we need to send packets with null data. */ -static void usb_send_null(void) { - usb_write_data(0, NULL, 0); -} - -static const uint8_t ms_os_desc_set[] = { - 0x0A, 0x00, /* wLength = 10 */ - 0x00, 0x00, /* wDescriptorType = MS_OS_20_SET_HEADER_DESCRIPTOR */ - 0x00, 0x00, 0x03, 0x06, /* dwWindowsVersion = 0x06030000 for Windows 8.1 Build */ - 0xA2, 0x00, /* wTotalLength = 162 */ - - 0x14, 0x00, /* wLength = 20 */ - 0x03, 0x00, /* wDescriptorType = MS_OS_20_FEATURE_COMPATBLE_ID */ - 'W', 'I', 'N', 'U', 'S', 'B', /* CompatibleID */ - 0x00, 0x00, /* CompatibleID (cont.) */ - 0x00, 0x00, 0x00, 0x00, /* SubCompatibleID */ - 0x00, 0x00, 0x00, 0x00, /* SubCompatibleID (cont.) */ - - 0x84, 0x00, /* wLength = 132 */ - 0x04, 0x00, /* wDescriptorType = MS_OS_20_FEATURE_REG_PROPERTY */ - 0x07, 0x00, /* wStringType = REG_MULTI_SZ */ - /* wPropertyNameLength = 42 */ - 0x2A, 0x00, - /* PropertyName = DeviceInterfaceGUIDs */ - 'D', '\0', - 'e', '\0', - 'v', '\0', - 'i', '\0', - 'c', '\0', - 'e', '\0', - 'I', '\0', - 'n', '\0', - 't', '\0', - 'e', '\0', - 'r', '\0', - 'f', '\0', - 'a', '\0', - 'c', '\0', - 'e', '\0', - 'G', '\0', - 'U', '\0', - 'I', '\0', - 'D', '\0', - 's', '\0', - '\0', '\0', - - /* wPropertyDataLength = 80 */ - 0x50, 0x00, - /* PropertyData = {A5C44A4C-53D4-4389-9821-AE95051908A1} */ - '{', '\0', - 'A', '\0', - '5', '\0', - 'C', '\0', - '4', '\0', - '4', '\0', - 'A', '\0', - '4', '\0', - 'C', '\0', - '-', '\0', - '5', '\0', - '3', '\0', - 'D', '\0', - '4', '\0', - '-', '\0', - '4', '\0', - '3', '\0', - '8', '\0', - '9', '\0', - '-', '\0', - '9', '\0', - '8', '\0', - '2', '\0', - '1', '\0', - '-', '\0', - 'A', '\0', - 'E', '\0', - '9', '\0', - '5', '\0', - '0', '\0', - '5', '\0', - '1', '\0', - '9', '\0', - '0', '\0', - '8', '\0', - 'A', '\0', - '1', '\0', - '}', '\0', - '\0', '\0', - '\0', '\0' -}; - -static const uint8_t WebUSB_DescSet[] = { - 20, /* bLength */ - 0x03, /* bDescriptorType = URL */ - 0x01, /* bScheme = https:// */ - - /* URL */ - #if PBIO_VERSION_LEVEL_HEX == 0xA - 'a', 'l', 'p', 'h', 'a', - #elif PBIO_VERSION_LEVEL_HEX == 0xB - 'b', 'e', 't', 'a', - #else - 'c', 'o', 'd', 'e', - #endif - '.', 'p', 'y', 'b', 'r', 'i', 'c', 'k', 's', '.', 'c', 'o', 'm' -}; - -/* Handle receiving and responding to setup packets on EP0. */ -static uint32_t usb_manage_setup_packet(void) { - /* The structure of a USB setup packet. */ - 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. */ - } packet; - uint32_t size; - uint8_t index; - - /* 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) { - usb_csr_set_flag(0, AT91C_UDP_DIR); /* TODO: contradicts atmel doc p475 */ - } - - usb_csr_clear_flag(0, AT91C_UDP_RXSETUP); - - switch (packet.request_attrs & USB_BMREQUEST_TYPE) { - case USB_BMREQUEST_TYPE_STD: - /* Respond to the control request. */ - switch (packet.request) { - case USB_BREQUEST_GET_STATUS: { - /* The host wants to know our status. - * - * If it wants the device status, just reply that the NXT is still - * self-powered (as first declared by the setup packets). If it - * wants endpoint status, reply that the endpoint has not - * halted. Any other status request types are reserved, which - * translates to replying zero. - */ - uint16_t response; - - if ((packet.request_attrs & USB_BMREQUEST_RCPT) == USB_BMREQUEST_RCPT_DEV) - response = 1; - else - response = 0; - - usb_write_data(0, (uint8_t*)&response, 2); - } - break; - - case USB_BREQUEST_CLEAR_FEATURE: - case USB_BREQUEST_SET_INTERFACE: - case USB_BREQUEST_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. - */ - usb_send_null(); - break; - - case USB_BREQUEST_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. - */ - usb_state.new_device_address = packet.value; - usb_send_null(); - - /* If the address change is to 0, do it immediately. - * - * TODO: Why? And when does this happen? - */ - if (usb_state.new_device_address == 0) { - *AT91C_UDP_FADDR = AT91C_UDP_FEN; - *AT91C_UDP_GLBSTATE = 0; - } - break; - - case USB_BREQUEST_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 */ - size = usb_device_descriptor[0]; - usb_write_data(0, usb_device_descriptor, - MIN(size, packet.length)); - break; - - case USB_DESC_TYPE_CONFIG: /* Configuration descriptor */ - usb_write_data(0, usb_nxos_full_config, - MIN(usb_nxos_full_config[2], packet.length)); - - /* TODO: Why? This is not specified in the USB specs. */ - if (usb_nxos_full_config[2] < packet.length) - usb_send_null(); - break; - - case USB_DESC_TYPE_STR: /* String or language info. */ - if ((packet.value & USB_WVALUE_INDEX) == 0) { - usb_write_data(0, usb_string_desc, - MIN(usb_string_desc[0], packet.length)); - } else { - /* The host wants a specific string. */ - /* TODO: This should check if the requested string exists. */ - usb_write_data(0, usb_strings[index-1], - MIN(usb_strings[index-1][0], - packet.length)); - } - break; - - case USB_DESC_TYPE_DEVICE_QUALIFIER: /* Device qualifier descriptor. */ - size = usb_dev_qualifier_desc[0]; - usb_write_data(0, usb_dev_qualifier_desc, - MIN(size, packet.length)); - break; - - case USB_DESC_TYPE_BOS: /* BOS descriptor */ - size = usb_bos_desc[2]; - usb_write_data(0, usb_bos_desc, MIN(size, packet.length)); - break; - - default: /* Unknown descriptor, tell the host by stalling. */ - usb_send_stall(0); - } - break; - - case USB_BREQUEST_GET_CONFIG: - /* The host wants to know the ID of the current configuration. */ - usb_write_data(0, (uint8_t *)&(usb_state.current_config), 1); - break; - - case USB_BREQUEST_SET_CONFIG: - /* The host selected a new configuration. */ - usb_state.current_config = packet.value; - - /* we ack */ - usb_send_null(); - - /* we set the register in configured mode */ - *AT91C_UDP_GLBSTATE = packet.value > 0 ? - (AT91C_UDP_CONFG | AT91C_UDP_FADDEN) - :AT91C_UDP_FADDEN; - - /* TODO: Make this a little nicer. Not quite sure how. */ - - /* we can only active the EP1 if we have a buffer to get the data */ - /* TODO: This was: - * - * if (usb_state.rx_len == 0 && usb_state.rx_size >= 0) { - * - * The second part always evaluates to true. - */ - if (usb_state.rx_len == 0) { - 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)); - } - - 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[3] = 0; - while (AT91C_UDP_CSR[3] != 0); - - usb_state.status = USB_READY; - break; - - case USB_BREQUEST_GET_INTERFACE: /* TODO: This should respond, not stall. */ - case USB_BREQUEST_SET_DESCRIPTOR: - default: - usb_send_stall(0); - break; - } - break; - case USB_BMREQUEST_TYPE_CLASS: - 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 0x01: // Standard GATT characteristic - switch (packet.value) { - case 0x2A00: { // device name - const char *name = pbdrv_bluetooth_get_hub_name(); - usb_write_data(0, (const uint8_t *)name, MIN(strlen(name), packet.length)); - break; - } - case 0x2A26: { // firmware revision - const char *fw = PBIO_VERSION_STR; - usb_write_data(0, (const uint8_t *)fw, MIN(strlen(fw), packet.length)); - break; - } - case 0x2A28: { // software revision - const char *sw = PBIO_PROTOCOL_VERSION_STR; - usb_write_data(0, (const uint8_t *)sw, MIN(strlen(sw), packet.length)); - break; - } - default: - usb_send_stall(0); - break; - } - break; - case 0x02: // 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); - usb_write_data(0, caps, MIN(sizeof(caps), packet.length)); - break; - } - default: - usb_send_stall(0); - break; - } - break; - default: - usb_send_stall(0); - break; - } - break; - default: - usb_send_stall(0); - break; - } - break; - case USB_BMREQUEST_TYPE_VENDOR: - switch (packet.request) { - case USBD_VENDOR_CODE_WEBUSB: - // Since there is only one WebUSB descriptor, we ignore the index. - usb_write_data(0, WebUSB_DescSet, MIN(sizeof(WebUSB_DescSet), packet.length)); - break; - case USBD_VENDOR_CODE_MS: - // Since there is only one MS descriptor, we ignore the index. - usb_write_data(0, ms_os_desc_set, MIN(sizeof(ms_os_desc_set), packet.length)); - break; - default: - usb_send_stall(0); - break; - } - break; - default: - usb_send_stall(0); - break; - } - - return packet.request; -} - - -/* The main USB interrupt handler. */ -static void usb_isr(void) { - uint8_t endpoint = 127; - uint32_t csr, isr; - - isr = *AT91C_UDP_ISR; - - /* We sent a stall, the host has acknowledged the stall. */ - if (AT91C_UDP_CSR[0] & AT91C_UDP_ISOERROR) - usb_csr_clear_flag(0, AT91C_UDP_FORCESTALL | AT91C_UDP_ISOERROR); - - /* End of bus reset. Starting the device setup procedure. */ - if (isr & AT91C_UDP_ENDBUSRES) { - usb_state.status = USB_UNINITIALIZED; - - /* Disable and clear all interruptions, reverting to the base - * state. - */ - *AT91C_UDP_IDR = ~0; - *AT91C_UDP_ICR = ~0; - - /* Reset all endpoint FIFOs. */ - *AT91C_UDP_RSTEP = ~0; - *AT91C_UDP_RSTEP = 0; - - /* Reset internal state. */ - usb_state.current_rx_bank = AT91C_UDP_RX_DATA_BK0; - usb_state.current_config = 0; - - /* Reset EP0 to a basic control endpoint. */ - /* TODO: The while is ugly. Fix it. */ - AT91C_UDP_CSR[0] = AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_CTRL; - while (AT91C_UDP_CSR[0] != (AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_CTRL)); - - /* Enable interrupt handling for all three endpoints, as well as - * suspend/resume. - */ - *AT91C_UDP_IER = (AT91C_UDP_EPINT0 | AT91C_UDP_EPINT1 | - AT91C_UDP_EPINT2 | AT91C_UDP_EPINT3 | - AT91C_UDP_RXSUSP | AT91C_UDP_RXRSM); - - /* Enable the function endpoints, setting address 0, and return - * immediately. Given that we've just reset everything, there's no - * point in continuing. - */ - *AT91C_UDP_FADDR = AT91C_UDP_FEN; - - return; - } - - if (isr & AT91C_UDP_WAKEUP) { - *AT91C_UDP_ICR = AT91C_UDP_WAKEUP; - isr &= ~AT91C_UDP_WAKEUP; - } - - - if (isr & AT91C_UDP_SOFINT) { - *AT91C_UDP_ICR = AT91C_UDP_SOFINT; - isr &= ~AT91C_UDP_SOFINT; - } - - - if (isr & AT91C_UDP_RXSUSP) { - *AT91C_UDP_ICR = AT91C_UDP_RXSUSP; - isr &= ~AT91C_UDP_RXSUSP; - usb_state.pre_suspend_status = usb_state.status; - usb_state.status = USB_SUSPENDED; - } - - if (isr & AT91C_UDP_RXRSM) { - *AT91C_UDP_ICR = AT91C_UDP_RXRSM; - isr &= ~AT91C_UDP_RXRSM; - usb_state.status = usb_state.pre_suspend_status; - } - - - - for (endpoint = 0; endpoint < N_ENDPOINTS ; endpoint++) { - if (isr & (1 << endpoint)) - break; - } - - - if (endpoint == 0) { - - if (AT91C_UDP_CSR[0] & AT91C_UDP_RXSETUP) { - csr = usb_manage_setup_packet(); - return; - } - } - - - if (endpoint < N_ENDPOINTS) { /* if an endpoint was specified */ - csr = AT91C_UDP_CSR[endpoint]; - - 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); - } - - usb_read_data(endpoint); - - return; - } - - if (csr & AT91C_UDP_TXCOMP) { - - /* so first we will reset this flag */ - usb_csr_clear_flag(endpoint, AT91C_UDP_TXCOMP); - - if (usb_state.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 | usb_state.new_device_address; - /* and we tell the controller that we are in addressed mode now */ - *AT91C_UDP_GLBSTATE = AT91C_UDP_FADDEN; - usb_state.new_device_address = 0; - } - - - /* and we will send the following data */ - if (usb_state.tx_len[endpoint] > 0 - && usb_state.tx_data[endpoint] != NULL) { - usb_write_data(endpoint, usb_state.tx_data[endpoint], - usb_state.tx_len[endpoint]); - } else { - /* then it means that we sent all the data and the host has acknowledged it */ - usb_state.status = USB_READY; - } - return; - } - - } - - - /* We clear also the unused bits, - * just "to be sure" */ - if (isr) { - *AT91C_UDP_ICR = 0xFFFFC4F0; - } -} - - -void nx__usb_disable(void) { - nx_aic_disable(AT91C_ID_UDP); - - *AT91C_PIOA_PER = (1 << 16); - *AT91C_PIOA_OER = (1 << 16); - *AT91C_PIOA_SODR = (1 << 16); - nx_systick_wait_ms(200); -} - - -static inline void usb_enable(void) { - /* Enable the UDP pull up by outputting a zero on PA.16 */ - /* Enabling the pull up will tell to the host (the computer) that - * we are ready for a communication - */ - *AT91C_PIOA_PER = (1 << 16); - *AT91C_PIOA_OER = (1 << 16); - *AT91C_PIOA_CODR = (1 << 16); - nx_systick_wait_ms(200); -} - - -void nx__usb_init(void) { - nx__usb_disable(); - memset((void*)&usb_state, 0, sizeof(usb_state)); - - uint32_t state = nx_interrupts_disable(); - - /* usb pll was already set in init.S */ - - /* enable peripheral clock */ - *AT91C_PMC_PCER = (1 << AT91C_ID_UDP); - - /* enable system clock */ - *AT91C_PMC_SCER = AT91C_PMC_UDP; - - /* disable all the interruptions */ - *AT91C_UDP_IDR = ~0; - - /* reset all the endpoints */ - *AT91C_UDP_RSTEP = 0xF; - *AT91C_UDP_RSTEP = 0; - - *AT91C_UDP_ICR = 0xFFFFFFFF; - - /* Install the interruption routine */ - - /* the first interruption we will get is an ENDBUSRES - * this interruption is always emit (can't be disable with UDP_IER) - */ - /* other interruptions will be enabled when needed */ - nx_aic_install_isr(AT91C_ID_UDP, AIC_PRIO_DRIVER, - AIC_TRIG_LEVEL, usb_isr); - - - nx_interrupts_enable(state); - - usb_enable(); -} - - -bool nx_usb_can_write(void) { - return (usb_state.status == USB_READY); -} - - -void nx_usb_write(uint8_t *data, uint32_t length) { - NX_ASSERT_MSG(usb_state.status != USB_UNINITIALIZED, - "USB not init"); - NX_ASSERT_MSG(usb_state.status != USB_SUSPENDED, - "USB asleep"); - NX_ASSERT(data != NULL); - NX_ASSERT(length > 0); - - /* TODO: Make call asynchronous */ - while (usb_state.status != USB_READY); - - /* start sending the data */ - usb_write_data(2, data, length); -} - -bool nx_usb_data_written(void) { - return (usb_state.tx_len[1] == 0); -} - - -bool nx_usb_is_connected(void) { - return (usb_state.status != USB_UNINITIALIZED); -} - - -void nx_usb_read(uint8_t *data, uint32_t length) -{ - usb_state.rx_data = data; - usb_state.rx_size = length; - usb_state.rx_len = 0; - - if (usb_state.status > USB_UNINITIALIZED - && usb_state.status != USB_SUSPENDED) { - AT91C_UDP_CSR[1] |= AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_BULK_OUT; - } -} - - -uint32_t nx_usb_data_read(void) -{ - return usb_state.rx_len; -} diff --git a/lib/pbio/platform/nxt/nxos/drivers/usb.h b/lib/pbio/platform/nxt/nxos/drivers/usb.h deleted file mode 100644 index 5b7ab10fb..000000000 --- a/lib/pbio/platform/nxt/nxos/drivers/usb.h +++ /dev/null @@ -1,89 +0,0 @@ -/** @file usb.h - * @brief USB communication interface. - */ - -/* Copyright (C) 2007 the NxOS developers - * - * See AUTHORS for a full list of the developers. - * - * Redistribution of this file is permitted under - * the terms of the GNU Public License (GPL) version 2. - */ - -#ifndef __NXOS_BASE_DRIVERS_USB_H__ -#define __NXOS_BASE_DRIVERS_USB_H__ - -#include -#include - -/** @addtogroup driver */ -/*@{*/ - -/** @defgroup usb USB communication - * - * This driver turns the NXT into a functional USB 2.0 peripheral. - * - * The device is configured with the following USB endpoints: - * @li Endpoint 0 is used by the driver to control the usb connection. - * @li Endpoint 1 is used for PC to NXT transfers. - * @li Endpoint 2 is used for NXT to PC transfers. - * - * @note Given the limitations of the controller hardware, the brick - * cannot function as a host, only as a slave peripheral. - */ -/*@{*/ - -/** - * The size of an USB packet. - * @note recommanded size to provide to nx_usb_read() - */ -#define NX_USB_PACKET_SIZE 64 - -/** Check if the NXT is connected and configured on a USB bus. - * - * @return true if the NXT is connected and configured, else false. - */ -bool nx_usb_is_connected(void); - -/** Check if a call to nx_usb_send() will block. - * - * @return true if data can be sent, false if the driver buffers are - * saturated. - */ -bool nx_usb_can_write(void); - -/** Send @a length bytes of @a data to the USB host. - * - * If there is already data buffered, this function may block. Use - * nx_usb_can_send() to check for buffered data. - * - * @param data The data to send. - * @param length The amount of data to send. - */ -void nx_usb_write(uint8_t *data, uint32_t length); - -/** - * Return true when all the data has been sent to - * the USB controller and that these data can be - * freed/erased from the memory. - */ -bool nx_usb_data_written(void); - -/** - * Specify where the next read data must be put - * @note if a packet has a size smaller than the provided one, then all the area won't be used - */ -void nx_usb_read(uint8_t *data, uint32_t length); - -/** - * Indicates when the data have been read. - * @note initial value = 0 ; reset to 0 after each call to nx_usb_read() - * @return the packet size read - */ -uint32_t nx_usb_data_read(void); - - -/*@}*/ -/*@}*/ - -#endif /* __NXOS_BASE_DRIVERS_USB_H__ */ diff --git a/lib/pbio/platform/nxt/pbdrvconfig.h b/lib/pbio/platform/nxt/pbdrvconfig.h index 3b8c32877..17f1a36f5 100644 --- a/lib/pbio/platform/nxt/pbdrvconfig.h +++ b/lib/pbio/platform/nxt/pbdrvconfig.h @@ -47,3 +47,6 @@ #define PBDRV_CONFIG_STACK (1) #define PBDRV_CONFIG_STACK_EMBEDDED (1) + +#define PBDRV_CONFIG_USB (1) +#define PBDRV_CONFIG_USB_NXT (1) diff --git a/lib/pbio/platform/nxt/platform.c b/lib/pbio/platform/nxt/platform.c index b0c0b71eb..1b2e9c06d 100644 --- a/lib/pbio/platform/nxt/platform.c +++ b/lib/pbio/platform/nxt/platform.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -113,7 +112,8 @@ void SystemInit(void) { nx__lcd_init(); nx__display_init(); nx__sensors_init(); - nx__usb_init(); + extern void pbdrv_usb_init(void); + pbdrv_usb_init(); nx_i2c_init(); /* Delay a little post-init, to let all the drivers settle down. */