From 9548ce18b19dcd0e434e571c4e1d6dfbbc05cd5e Mon Sep 17 00:00:00 2001 From: Nate Karstens Date: Mon, 19 Feb 2024 00:51:19 -0600 Subject: [PATCH 1/8] pbdrv/usb: Add new Pybricks device class. Add a new Pybricks device class and provides the correct descriptors so that Windows will use the WinUSB driver. Co-authored-by: David Lechner Signed-off-by: Nate Karstens --- bricks/_common/arm_none_eabi.mk | 1 + lib/pbio/drv/usb/stm32_usbd/usbd_conf.h | 3 +- lib/pbio/drv/usb/stm32_usbd/usbd_desc.c | 137 +++++- lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c | 467 ++++++++++++++++++++ lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h | 126 ++++++ 5 files changed, 732 insertions(+), 2 deletions(-) create mode 100644 lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c create mode 100644 lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h diff --git a/bricks/_common/arm_none_eabi.mk b/bricks/_common/arm_none_eabi.mk index 6a897f2e0..53b6ec15b 100644 --- a/bricks/_common/arm_none_eabi.mk +++ b/bricks/_common/arm_none_eabi.mk @@ -394,6 +394,7 @@ endif SRC_STM32_USB_DEV += $(addprefix lib/pbio/drv/usb/stm32_usbd/,\ usbd_conf.c \ usbd_desc.c \ + usbd_pybricks.c \ ) NXOS_SRC_C = $(addprefix lib/pbio/platform/nxt/nxos/,\ diff --git a/lib/pbio/drv/usb/stm32_usbd/usbd_conf.h b/lib/pbio/drv/usb/stm32_usbd/usbd_conf.h index d74e585bd..063f10fd9 100644 --- a/lib/pbio/drv/usb/stm32_usbd/usbd_conf.h +++ b/lib/pbio/drv/usb/stm32_usbd/usbd_conf.h @@ -9,6 +9,7 @@ #define USBD_MAX_NUM_INTERFACES 1 #define USBD_MAX_NUM_CONFIGURATION 1 #define USBD_MAX_STR_DESC_SIZ 0x100 -#define USBD_SELF_POWERED 1 +#define USBD_SELF_POWERED 0 +#define USBD_CLASS_BOS_ENABLED 1 #endif /* _USBD_CONF_H_ */ diff --git a/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c b/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c index 2519506dd..f3d017e4b 100644 --- a/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c +++ b/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c @@ -51,6 +51,7 @@ #include "usbd_core.h" #include "usbd_conf.h" +#include "usbd_pybricks.h" /* Private typedef -----------------------------------------------------------*/ /* Private define ------------------------------------------------------------*/ @@ -62,7 +63,10 @@ #define DEVICE_ID2 (0x1FFF7A14) #define DEVICE_ID3 (0x1FFF7A18) +#define USB_DEV_CAP_TYPE_PLATFORM (5) + #define USB_SIZ_STRING_SERIAL 0x1A +#define USB_SIZ_BOS_DESC 33 /* USB Standard Device Descriptor */ __ALIGN_BEGIN static @@ -72,7 +76,8 @@ const uint8_t USBD_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = { 0x12, /* bLength */ USB_DESC_TYPE_DEVICE, /* bDescriptorType */ - 0x00, /* bcdUSB */ + 0x01, /* bcdUSB */ /* changed to USB version 2.01 + in order to support BOS Desc */ 0x02, PBIO_PYBRICKS_USB_DEVICE_CLASS, /* bDeviceClass */ PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, /* bDeviceSubClass */ @@ -90,6 +95,127 @@ uint8_t USBD_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = { USBD_MAX_NUM_CONFIGURATION /* bNumConfigurations */ }; /* USB_DeviceDescriptor */ +/** BOS descriptor. */ +__ALIGN_BEGIN static uint8_t USBD_BOSDesc[USB_SIZ_BOS_DESC] __ALIGN_END = +{ + 5, /* bLength */ + USB_DESC_TYPE_BOS, /* bDescriptorType = BOS */ + LOBYTE(USB_SIZ_BOS_DESC), /* wTotalLength */ + HIBYTE(USB_SIZ_BOS_DESC), /* wTotalLength */ + 1, /* bNumDeviceCaps */ + + 28, /* 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 + * 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 */ + 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 */ +}; + +__ALIGN_BEGIN const uint8_t USBD_OSDescSet[USBD_SIZ_MS_OS_DSCRPTR_SET] __ALIGN_END = +{ + 0x0A, 0x00, /* wLength = 10 */ + 0x00, 0x00, /* wDescriptorType = MS_OS_20_SET_HEADER_DESCRIPTOR */ + 0x00, 0x00, 0x03, 0x06, /* dwWindowsVersion = 0x06030000 for Windows 8.1 Build */ + LOBYTE(USBD_SIZ_MS_OS_DSCRPTR_SET), /* wTotalLength */ + HIBYTE(USBD_SIZ_MS_OS_DSCRPTR_SET), /* wTotalLength (cont.) */ + + 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' +}; + /* USB Standard Device Descriptor */ __ALIGN_BEGIN static const uint8_t USBD_LangIDDesc[USB_LEN_LANGID_STR_DESC] __ALIGN_END = { USB_LEN_LANGID_STR_DESC, @@ -242,6 +368,14 @@ static uint8_t *USBD_Pybricks_InterfaceStrDescriptor(USBD_SpeedTypeDef speed, ui return USBD_StrDesc; } +static uint8_t *USBD_Pybricks_BOSDescriptor(USBD_SpeedTypeDef speed, uint16_t *length) { + /* Prevent unused argument(s) compilation warning */ + UNUSED(speed); + + *length = sizeof(USBD_BOSDesc); + return (uint8_t *)USBD_BOSDesc; +} + USBD_DescriptorsTypeDef USBD_Pybricks_Desc = { .GetDeviceDescriptor = USBD_Pybricks_DeviceDescriptor, .GetLangIDStrDescriptor = USBD_Pybricks_LangIDStrDescriptor, @@ -250,6 +384,7 @@ USBD_DescriptorsTypeDef USBD_Pybricks_Desc = { .GetSerialStrDescriptor = USBD_Pybricks_SerialStrDescriptor, .GetConfigurationStrDescriptor = USBD_Pybricks_ConfigStrDescriptor, .GetInterfaceStrDescriptor = USBD_Pybricks_InterfaceStrDescriptor, + .GetBOSDescriptor = USBD_Pybricks_BOSDescriptor, }; void USBD_Pybricks_Desc_Init(void) { diff --git a/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c new file mode 100644 index 000000000..bb51856c8 --- /dev/null +++ b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c @@ -0,0 +1,467 @@ +/** + ****************************************************************************** + * @file usbd_pybricks.c + * @author MCD Application Team + * @brief This file provides the HID core functions. + * + ****************************************************************************** + * @attention + * + * Copyright (c) 2015 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + * @verbatim + * + * =================================================================== + * Pybricks Class Description + * =================================================================== + * + * + * + * + * + * + * @note In HS mode and when the DMA is used, all variables and data structures + * dealing with the DMA during the transaction process should be 32-bit aligned. + * + * + * @endverbatim + * + ****************************************************************************** + */ + +/* Includes ------------------------------------------------------------------*/ +#include + +#include "usbd_ctlreq.h" +#include "usbd_pybricks.h" + + +/** @addtogroup STM32_USB_DEVICE_LIBRARY + * @{ + */ + + +/** @defgroup USBD_Pybricks + * @brief usbd core module + * @{ + */ + +/** @defgroup USBD_Pybricks_Private_TypesDefinitions + * @{ + */ +/** + * @} + */ + + +/** @defgroup USBD_Pybricks_Private_Defines + * @{ + */ + +/** + * @} + */ + + +/** @defgroup USBD_Pybricks_Private_Macros + * @{ + */ + +/** + * @} + */ + + +/** @defgroup USBD_Pybricks_Private_FunctionPrototypes + * @{ + */ + +static USBD_StatusTypeDef USBD_Pybricks_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx); +static USBD_StatusTypeDef USBD_Pybricks_DeInit(USBD_HandleTypeDef *pdev, uint8_t cfgidx); +static USBD_StatusTypeDef USBD_Pybricks_Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req); +static USBD_StatusTypeDef USBD_Pybricks_DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum); +static USBD_StatusTypeDef USBD_Pybricks_DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum); +static USBD_StatusTypeDef USBD_Pybricks_EP0_RxReady(USBD_HandleTypeDef *pdev); +static uint8_t *USBD_Pybricks_GetCfgDesc(uint16_t *length); + +/** + * @} + */ + +/** @defgroup USBD_Pybricks_Private_Variables + * @{ + */ + +USBD_ClassTypeDef USBD_Pybricks_ClassDriver = +{ + .Init = USBD_Pybricks_Init, + .DeInit = USBD_Pybricks_DeInit, + .Setup = USBD_Pybricks_Setup, + .EP0_RxReady = USBD_Pybricks_EP0_RxReady, + .DataIn = USBD_Pybricks_DataIn, + .DataOut = USBD_Pybricks_DataOut, + .GetFSConfigDescriptor = USBD_Pybricks_GetCfgDesc, +}; + +/* USB Pybricks device Configuration Descriptor */ +__ALIGN_BEGIN static uint8_t USBD_Pybricks_CfgDesc[USBD_PYBRICKS_CONFIG_DESC_SIZ] __ALIGN_END = +{ + /* Configuration Descriptor */ + 0x09, /* bLength: Configuration Descriptor size */ + USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */ + LOBYTE(USBD_PYBRICKS_CONFIG_DESC_SIZ), /* wTotalLength:no of returned bytes */ + HIBYTE(USBD_PYBRICKS_CONFIG_DESC_SIZ), + 0x01, /* bNumInterfaces: 1 interface */ + 0x01, /* bConfigurationValue: Configuration value */ + 0x00, /* iConfiguration: Index of string descriptor describing the configuration */ + 0x80, /* bmAttributes */ + 250, /* MaxPower 500mA (number of 2mA units) */ + + /*---------------------------------------------------------------------------*/ + + /* Data class interface descriptor */ + 0x09, /* bLength: Endpoint Descriptor size */ + USB_DESC_TYPE_INTERFACE, /* bDescriptorType: */ + 0x00, /* bInterfaceNumber: Number of Interface */ + 0x00, /* bAlternateSetting: Alternate setting */ + 0x02, /* bNumEndpoints: Two endpoints used */ + PBIO_PYBRICKS_USB_DEVICE_CLASS, /* bInterfaceClass */ + PBIO_PYBRICKS_USB_DEVICE_SUBCLASS, /* bInterfaceSubClass */ + PBIO_PYBRICKS_USB_DEVICE_PROTOCOL, /* bInterfaceProtocol */ + 0x00, /* iInterface: */ + + /* Endpoint OUT Descriptor */ + 0x07, /* bLength: Endpoint Descriptor size */ + USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */ + USBD_PYBRICKS_OUT_EP, /* bEndpointAddress */ + USBD_EP_TYPE_BULK, /* bmAttributes */ + LOBYTE(USBD_PYBRICKS_MAX_PACKET_SIZE), /* wMaxPacketSize: */ + HIBYTE(USBD_PYBRICKS_MAX_PACKET_SIZE), + 0x00, /* bInterval: ignore for Bulk transfer */ + + /* Endpoint IN Descriptor */ + 0x07, /* bLength: Endpoint Descriptor size */ + USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */ + USBD_PYBRICKS_IN_EP, /* bEndpointAddress */ + USBD_EP_TYPE_BULK, /* bmAttributes */ + LOBYTE(USBD_PYBRICKS_MAX_PACKET_SIZE), /* wMaxPacketSize: */ + HIBYTE(USBD_PYBRICKS_MAX_PACKET_SIZE), + 0x00 /* bInterval */ +}; + +/** + * @} + */ + +/** @defgroup USBD_Pybricks_Private_Functions + * @{ + */ + +/** + * @brief USBD_Pybricks_Init + * Initialize the Pybricks interface + * @param pdev: device instance + * @param cfgidx: Configuration index + * @retval status + */ +static USBD_StatusTypeDef USBD_Pybricks_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx) { + UNUSED(cfgidx); + static USBD_Pybricks_HandleTypeDef hPybricks; + + pdev->pClassData = &hPybricks; + + (void)USBD_LL_OpenEP(pdev, USBD_PYBRICKS_IN_EP, USBD_EP_TYPE_BULK, USBD_PYBRICKS_MAX_PACKET_SIZE); + pdev->ep_in[USBD_PYBRICKS_IN_EP & 0xFU].is_used = 1U; + + (void)USBD_LL_OpenEP(pdev, USBD_PYBRICKS_OUT_EP, USBD_EP_TYPE_BULK, USBD_PYBRICKS_MAX_PACKET_SIZE); + pdev->ep_out[USBD_PYBRICKS_OUT_EP & 0xFU].is_used = 1U; + + /* Init physical Interface components */ + ((USBD_Pybricks_ItfTypeDef *)pdev->pUserData[pdev->classId])->Init(); + + (void)USBD_LL_PrepareReceive(pdev, USBD_PYBRICKS_OUT_EP, hPybricks.RxBuffer, USBD_PYBRICKS_MAX_PACKET_SIZE); + + return USBD_OK; +} + +/** + * @brief USBD_Pybricks_DeInit + * DeInitialize the Pybricks layer + * @param pdev: device instance + * @param cfgidx: Configuration index + * @retval status + */ +static USBD_StatusTypeDef USBD_Pybricks_DeInit(USBD_HandleTypeDef *pdev, uint8_t cfgidx) { + UNUSED(cfgidx); + + /* Close EP IN */ + (void)USBD_LL_CloseEP(pdev, USBD_PYBRICKS_IN_EP); + pdev->ep_in[USBD_PYBRICKS_IN_EP & 0xFU].is_used = 0U; + + /* Close EP OUT */ + (void)USBD_LL_CloseEP(pdev, USBD_PYBRICKS_OUT_EP); + pdev->ep_out[USBD_PYBRICKS_OUT_EP & 0xFU].is_used = 0U; + + /* DeInit physical Interface components */ + if (pdev->pClassData != NULL) { + ((USBD_Pybricks_ItfTypeDef *)pdev->pUserData[pdev->classId])->DeInit(); + pdev->pClassData = NULL; + } + + return USBD_OK; +} + +/** + * @brief USBD_Pybricks_Setup + * Handle the Pybricks specific requests + * @param pdev: instance + * @param req: usb requests + * @retval status + */ +static USBD_StatusTypeDef USBD_Pybricks_Setup(USBD_HandleTypeDef *pdev, + USBD_SetupReqTypedef *req) { + uint8_t ifalt = 0U; + uint16_t status_info = 0U; + USBD_StatusTypeDef ret = USBD_OK; + + switch (req->bmRequest & USB_REQ_TYPE_MASK) + { + case USB_REQ_TYPE_CLASS: + break; + + case USB_REQ_TYPE_VENDOR: + switch (req->bRequest) + { + case USBD_MS_VENDOR_CODE: + (void)USBD_CtlSendData(pdev, + (uint8_t *)USBD_OSDescSet, + MIN(sizeof(USBD_OSDescSet), req->wLength)); + break; + + default: + USBD_CtlError(pdev, req); + ret = USBD_FAIL; + break; + } + break; + + case USB_REQ_TYPE_STANDARD: + switch (req->bRequest) + { + case USB_REQ_GET_STATUS: + if (pdev->dev_state == USBD_STATE_CONFIGURED) { + (void)USBD_CtlSendData(pdev, (uint8_t *)&status_info, 2U); + } else { + USBD_CtlError(pdev, req); + ret = USBD_FAIL; + } + break; + + case USB_REQ_GET_INTERFACE: + if (pdev->dev_state == USBD_STATE_CONFIGURED) { + (void)USBD_CtlSendData(pdev, &ifalt, 1U); + } else { + USBD_CtlError(pdev, req); + ret = USBD_FAIL; + } + break; + + case USB_REQ_SET_INTERFACE: + if (pdev->dev_state != USBD_STATE_CONFIGURED) { + USBD_CtlError(pdev, req); + ret = USBD_FAIL; + } + break; + + case USB_REQ_CLEAR_FEATURE: + break; + + default: + USBD_CtlError(pdev, req); + ret = USBD_FAIL; + break; + } + break; + + default: + USBD_CtlError(pdev, req); + ret = USBD_FAIL; + break; + } + + return ret; +} + +/** + * @brief USBD_Pybricks_GetCfgDesc + * return configuration descriptor + * @param length : pointer data length + * @retval pointer to descriptor buffer + */ +static uint8_t *USBD_Pybricks_GetCfgDesc(uint16_t *length) { + *length = (uint16_t)sizeof(USBD_Pybricks_CfgDesc); + return USBD_Pybricks_CfgDesc; +} + +/** + * @brief USBD_Pybricks_DataIn + * Data sent on non-control IN endpoint + * @param pdev: device instance + * @param epnum: endpoint number + * @retval status + */ +static USBD_StatusTypeDef USBD_Pybricks_DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum) { + USBD_Pybricks_HandleTypeDef *hPybricks = pdev->pClassData; + PCD_HandleTypeDef *hpcd = pdev->pData; + + if (hPybricks == NULL) { + return USBD_FAIL; + } + + if ((pdev->ep_in[epnum].total_length > 0U) && + ((pdev->ep_in[epnum].total_length % hpcd->IN_ep[epnum].maxpacket) == 0U)) { + /* Update the packet total length */ + pdev->ep_in[epnum].total_length = 0U; + + /* Send ZLP */ + (void)USBD_LL_Transmit(pdev, epnum, NULL, 0U); + } else { + ((USBD_Pybricks_ItfTypeDef *)pdev->pUserData[pdev->classId])->TransmitCplt(hPybricks->TxBuffer, hPybricks->TxLength, epnum); + } + + return USBD_OK; +} + +/** + * @brief USBD_Pybricks_DataOut + * Data received on non-control Out endpoint + * @param pdev: device instance + * @param epnum: endpoint number + * @retval status + */ +static USBD_StatusTypeDef USBD_Pybricks_DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum) { + USBD_Pybricks_HandleTypeDef *hPybricks = pdev->pClassData; + + if (hPybricks == NULL) { + return USBD_FAIL; + } + + /* Get the received data length */ + hPybricks->RxLength = USBD_LL_GetRxDataSize(pdev, epnum); + + /* USB data will be immediately processed, this allow next USB traffic being + NAKed till the end of the application Xfer */ + + ((USBD_Pybricks_ItfTypeDef *)pdev->pUserData[pdev->classId])->Receive(hPybricks->RxBuffer, hPybricks->RxLength); + + return USBD_OK; +} + +/** + * @brief USBD_Pybricks_EP0_RxReady + * Handle EP0 Rx Ready event + * @param pdev: device instance + * @retval status + */ +static USBD_StatusTypeDef USBD_Pybricks_EP0_RxReady(USBD_HandleTypeDef *pdev) { + return USBD_OK; +} + +/** +* @brief USBD_Pybricks_RegisterInterface + * @param pdev: device instance + * @param fops: CD Interface callback + * @retval status + */ +USBD_StatusTypeDef USBD_Pybricks_RegisterInterface(USBD_HandleTypeDef *pdev, USBD_Pybricks_ItfTypeDef *fops) { + if (fops == NULL) { + return USBD_FAIL; + } + + pdev->pUserData[pdev->classId] = fops; + + return USBD_OK; +} + +/** + * @brief USBD_Pybricks_SetRxBuffer + * @param pdev: device instance + * @param pbuff: Rx Buffer + * @retval status + */ +USBD_StatusTypeDef USBD_Pybricks_SetRxBuffer(USBD_HandleTypeDef *pdev, uint8_t *pbuff) { + USBD_Pybricks_HandleTypeDef *hPybricks = pdev->pClassData; + + if (hPybricks == NULL) { + return USBD_FAIL; + } + + hPybricks->RxBuffer = pbuff; + + return USBD_OK; +} + +/** + * @brief USBD_Pybricks_TransmitPacket + * Transmit packet on IN endpoint + * @param pdev: device instance + * @retval status + */ +USBD_StatusTypeDef USBD_Pybricks_TransmitPacket(USBD_HandleTypeDef *pdev, uint8_t *pbuf, uint32_t length) { + USBD_Pybricks_HandleTypeDef *hPybricks = pdev->pClassData; + + if (hPybricks == NULL) { + return USBD_FAIL; + } + + hPybricks->TxBuffer = pbuf; + hPybricks->TxLength = length; + + /* Update the packet total length */ + pdev->ep_in[USBD_PYBRICKS_IN_EP & 0xFU].total_length = hPybricks->TxLength; + + /* Transmit next packet */ + (void)USBD_LL_Transmit(pdev, USBD_PYBRICKS_IN_EP, hPybricks->TxBuffer, hPybricks->TxLength); + + return USBD_OK; +} + + +/** + * @brief USBD_Pybricks_ReceivePacket + * prepare OUT Endpoint for reception + * @param pdev: device instance + * @retval status + */ +USBD_StatusTypeDef USBD_Pybricks_ReceivePacket(USBD_HandleTypeDef *pdev) { + USBD_Pybricks_HandleTypeDef *hPybricks = pdev->pClassData; + + if (hPybricks == NULL) { + return USBD_FAIL; + } + + /* Prepare Out endpoint to receive next packet */ + (void)USBD_LL_PrepareReceive(pdev, USBD_PYBRICKS_OUT_EP, hPybricks->RxBuffer, USBD_PYBRICKS_MAX_PACKET_SIZE); + + return USBD_OK; +} + +/** + * @} + */ + + +/** + * @} + */ + + +/** + * @} + */ diff --git a/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h new file mode 100644 index 000000000..4e691b68c --- /dev/null +++ b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h @@ -0,0 +1,126 @@ +/** + ****************************************************************************** + * @file usbd_pybricks.h + * @author MCD Application Team + * @brief Header file for the usbd_pybricks.c file. + ****************************************************************************** + * @attention + * + * Copyright (c) 2015 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __USB_PYBRICKS_H +#define __USB_PYBRICKS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Includes ------------------------------------------------------------------*/ +#include "usbd_ioreq.h" + +/** @addtogroup STM32_USB_DEVICE_LIBRARY + * @{ + */ + +/** @defgroup USBD_Pybricks + * @brief This file is the header file for usbd_pybricks.c + * @{ + */ + + +/** @defgroup USBD_Pybricks_Exported_Defines + * @{ + */ +#define USBD_MS_VENDOR_CODE 0x01 +#define USBD_SIZ_MS_OS_DSCRPTR_SET (10 + 20 + 132) + +#define USBD_PYBRICKS_CONFIG_DESC_SIZ (9 + 9 + 7 + 7) + +#define USBD_PYBRICKS_IN_EP 0x81U /* EP1 for data IN */ +#define USBD_PYBRICKS_OUT_EP 0x01U /* EP1 for data OUT */ + +#define USBD_PYBRICKS_MAX_PACKET_SIZE 64U + +/** + * @} + */ + + +/** @defgroup USBD_Pybricks_Exported_TypesDefinitions + * @{ + */ + +/** + * @} + */ +typedef struct +{ + USBD_StatusTypeDef (*Init)(void); + 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_Pybricks_ItfTypeDef; + + +typedef struct +{ + uint8_t *RxBuffer; + uint8_t *TxBuffer; + uint32_t RxLength; + uint32_t TxLength; +} USBD_Pybricks_HandleTypeDef; + + + +/** @defgroup USBD_Pybricks_Exported_Macros + * @{ + */ + +/** + * @} + */ + +/** @defgroup USBD_Pybricks_Exported_Variables + * @{ + */ + +extern USBD_ClassTypeDef USBD_Pybricks_ClassDriver; + +extern const uint8_t USBD_OSDescSet[USBD_SIZ_MS_OS_DSCRPTR_SET]; + +/** + * @} + */ + +/** @defgroup USB_Pybricks_Exported_Functions + * @{ + */ +USBD_StatusTypeDef USBD_Pybricks_RegisterInterface(USBD_HandleTypeDef *pdev, USBD_Pybricks_ItfTypeDef *fops); +USBD_StatusTypeDef USBD_Pybricks_SetRxBuffer(USBD_HandleTypeDef *pdev, uint8_t *pbuf); +USBD_StatusTypeDef USBD_Pybricks_ReceivePacket(USBD_HandleTypeDef *pdev); +USBD_StatusTypeDef USBD_Pybricks_TransmitPacket(USBD_HandleTypeDef *pdev, uint8_t *pbuf, uint32_t length); +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __USB_PYBRICKS_H */ +/** + * @} + */ + +/** + * @} + */ From 26256c840397991ed4789b54b751d118fdd47177 Mon Sep 17 00:00:00 2001 From: Nate Karstens Date: Mon, 19 Feb 2024 00:51:19 -0600 Subject: [PATCH 2/8] pbio/util: Add UUID little endian copy function. Add function to copy UUIDs in little endian format. USB is a little endian protocol, so this will be used to encode UUIDs properly on this medium. Signed-off-by: Nate Karstens --- lib/pbio/include/pbio/util.h | 1 + lib/pbio/src/util.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/lib/pbio/include/pbio/util.h b/lib/pbio/include/pbio/util.h index cf9227bfb..0ff8a38f7 100644 --- a/lib/pbio/include/pbio/util.h +++ b/lib/pbio/include/pbio/util.h @@ -118,6 +118,7 @@ void pbio_set_uint32_be(uint8_t *buf, uint32_t value) { buf[3] = value; } +void pbio_uuid128_le_copy(uint8_t *dst, const uint8_t *src); bool pbio_uuid128_reverse_compare(const uint8_t *uuid1, const uint8_t *uuid2); void pbio_uuid128_reverse_copy(uint8_t *dst, const uint8_t *src); diff --git a/lib/pbio/src/util.c b/lib/pbio/src/util.c index 1da0067af..efdd1d3ac 100644 --- a/lib/pbio/src/util.c +++ b/lib/pbio/src/util.c @@ -3,6 +3,35 @@ #include #include +#include + +/** + * Copies a 128-bit UUID from @p src to a buffer @p dst, + * which is a buffer used by a little endian medium. + * + * According to RFC 4122, the UUID is grouped into the following: + * 1) One 32-bit + * 2) Two 16-bit + * 3) Eight 8-bit + * + * @param [in] dst The destination that will receive the + * resulting little-endian-formatted UUID. + * @param [in] src The UUID in host byte order. + */ +void pbio_uuid128_le_copy(uint8_t *dst, const uint8_t *src) { + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; + + dst[4] = src[5]; + dst[5] = src[4]; + + dst[6] = src[7]; + dst[7] = src[6]; + + memcpy(&dst[8], &src[8], 8); +} /** * Compares two 128-bit UUIDs with opposite byte ordering for equality. From 651e4b1d92f660face05ada9437a57d2a0abcd46 Mon Sep 17 00:00:00 2001 From: Nate Karstens Date: Mon, 19 Feb 2024 00:51:19 -0600 Subject: [PATCH 3/8] pbdrv/usb: Add additional info to BOS descriptor. Add additional information to the BOS Descriptor by appending dynamically-generated platform descriptors that use the same UUIDs that are used with BLE and contain the following values: * Device Name * Firmware version * Software (protocol) version * Hub capabilities Signed-off-by: Nate Karstens --- lib/pbio/drv/usb/stm32_usbd/usbd_desc.c | 128 +++++++++++++++++++++++- lib/pbio/include/pbio/protocol.h | 1 + lib/pbio/src/protocol/pybricks.c | 3 + 3 files changed, 130 insertions(+), 2 deletions(-) diff --git a/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c b/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c index f3d017e4b..83118e99b 100644 --- a/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c +++ b/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c @@ -43,11 +43,15 @@ ****************************************************************************** */ /* Includes ------------------------------------------------------------------*/ +#include #include +#include #include #include +#include +#include #include "usbd_core.h" #include "usbd_conf.h" @@ -59,6 +63,9 @@ #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; + #define DEVICE_ID1 (0x1FFF7A10) #define DEVICE_ID2 (0x1FFF7A14) #define DEVICE_ID3 (0x1FFF7A18) @@ -66,7 +73,15 @@ #define USB_DEV_CAP_TYPE_PLATFORM (5) #define USB_SIZ_STRING_SERIAL 0x1A -#define USB_SIZ_BOS_DESC 33 +#define USB_SIZ_BOS_DESC_CONST (5 + 28) +#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) /* USB Standard Device Descriptor */ __ALIGN_BEGIN static @@ -128,6 +143,8 @@ __ALIGN_BEGIN static uint8_t USBD_BOSDesc[USB_SIZ_BOS_DESC] __ALIGN_END = 0x00 /* bAltEnumCode = Does not support alternate enumeration */ }; +static uint16_t USBD_BOSDesc_Len; + __ALIGN_BEGIN const uint8_t USBD_OSDescSet[USBD_SIZ_MS_OS_DSCRPTR_SET] __ALIGN_END = { 0x0A, 0x00, /* wLength = 10 */ @@ -275,6 +292,38 @@ 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 @@ -372,7 +421,7 @@ static uint8_t *USBD_Pybricks_BOSDescriptor(USBD_SpeedTypeDef speed, uint16_t *l /* Prevent unused argument(s) compilation warning */ UNUSED(speed); - *length = sizeof(USBD_BOSDesc); + *length = USBD_BOSDesc_Len; return (uint8_t *)USBD_BOSDesc; } @@ -400,4 +449,79 @@ 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()); + 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/include/pbio/protocol.h b/lib/pbio/include/pbio/protocol.h index 0e61c5e80..8610bdb54 100644 --- a/lib/pbio/include/pbio/protocol.h +++ b/lib/pbio/include/pbio/protocol.h @@ -403,6 +403,7 @@ extern const uint8_t pbio_pybricks_command_event_char_uuid[]; extern const uint8_t pbio_pybricks_hub_capabilities_char_uuid[]; extern const uint16_t pbio_gatt_device_info_service_uuid; +extern const uint16_t pbio_gatt_device_name_char_uuid; extern const uint16_t pbio_gatt_firmware_version_char_uuid; extern const uint16_t pbio_gatt_software_version_char_uuid; extern const uint16_t pbio_gatt_pnp_id_char_uuid; diff --git a/lib/pbio/src/protocol/pybricks.c b/lib/pbio/src/protocol/pybricks.c index 7acaf0f8a..ddd3aa392 100644 --- a/lib/pbio/src/protocol/pybricks.c +++ b/lib/pbio/src/protocol/pybricks.c @@ -89,6 +89,9 @@ const uint8_t pbio_pybricks_hub_capabilities_char_uuid[] = { /** Bluetooth Device Information Service UUID. */ const uint16_t pbio_gatt_device_info_service_uuid = 0x180A; +/** Bluetooth Device Name Characteristic UUID. */ +const uint16_t pbio_gatt_device_name_char_uuid = 0x2A00; + /** Bluetooth Firmware Version Characteristic UUID. */ const uint16_t pbio_gatt_firmware_version_char_uuid = 0x2A26; From c81481629172cf37ccd03c6379092a8cf75e2b90 Mon Sep 17 00:00:00 2001 From: Nate Karstens Date: Mon, 19 Feb 2024 00:51:19 -0600 Subject: [PATCH 4/8] pbdrv/usb: Configure USB device and process pybricks commands Add initial implementation of the Pybricks USB interface. This can process commands but doesn't send a response yet. Signed-off-by: Nate Karstens --- lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h | 22 ++++ lib/pbio/drv/usb/usb_stm32.c | 108 +++++++++++++++++++- lib/pbio/platform/essential_hub/platform.c | 7 +- lib/pbio/platform/prime_hub/platform.c | 7 +- 4 files changed, 141 insertions(+), 3 deletions(-) diff --git a/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h index 4e691b68c..65226dd6c 100644 --- a/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h +++ b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h @@ -54,6 +54,28 @@ extern "C" { * @} */ +// NOTE: These enums values are sent over the wire, so cannot be changed. Also, +// 0 is skipped to avoid a zeroed buffer from being misinterpreted as a message. + +/** Hub to host messages via the Pybricks interface IN endpoint. */ +enum { + /** + * Analog of BLE status response. Emitted in response to every OUT message + * received. + */ + USBD_PYBRICKS_IN_EP_MSG_RESPONSE = 1, + /**Analog to BLE notification. Only emitted if subscribed. */ + USBD_PYBRICKS_IN_EP_MSG_EVENT = 2, +}; + +/** Host to hub messages via the Pybricks USB interface OUT endpoint. */ +enum { + /** Analog of BLE Client Characteristic Configuration Descriptor (CCCD). */ + USBD_PYBRICKS_OUT_EP_MSG_SUBSCRIBE = 1, + /** Analog of BLE Client Characteristic Write with response. */ + USBD_PYBRICKS_OUT_EP_MSG_COMMAND = 2, +}; + /** @defgroup USBD_Pybricks_Exported_TypesDefinitions * @{ diff --git a/lib/pbio/drv/usb/usb_stm32.c b/lib/pbio/drv/usb/usb_stm32.c index e7ab1fd5b..9a782c785 100644 --- a/lib/pbio/drv/usb/usb_stm32.c +++ b/lib/pbio/drv/usb/usb_stm32.c @@ -13,17 +13,27 @@ #include #include #include +#include +#include #include #include +#include +#include #include "../charger/charger.h" #include "./usb_stm32.h" PROCESS(pbdrv_usb_process, "USB"); +// These buffers need to be 32-bit aligned because the USB driver moves data +// to/from FIFOs in 32-bit chunks. +static uint8_t usb_in_buf[USBD_PYBRICKS_MAX_PACKET_SIZE] __aligned(4); +static volatile uint32_t usb_in_sz; + static USBD_HandleTypeDef husbd; static PCD_HandleTypeDef hpcd; + static volatile bool vbus_active; static pbdrv_usb_bcd_t pbdrv_usb_bcd; @@ -121,6 +131,68 @@ void pbdrv_usb_stm32_handle_vbus_irq(bool active) { process_poll(&pbdrv_usb_process); } +/** + * @brief Pybricks_Itf_Init + * Initializes the Pybricks media low layer + * @param None + * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL + */ +static USBD_StatusTypeDef Pybricks_Itf_Init(void) { + USBD_Pybricks_SetRxBuffer(&husbd, usb_in_buf); + usb_in_sz = 0; + + return USBD_OK; +} + +/** + * @brief Pybricks_Itf_DeInit + * DeInitializes the Pybricks media low layer + * @param None + * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL + */ +static USBD_StatusTypeDef Pybricks_Itf_DeInit(void) { + return USBD_OK; +} + +/** + * @brief Pybricks_Itf_DataRx + * Data received over USB OUT endpoint are sent over Pybricks interface + * through this function. + * @param Buf: Buffer of data to be transmitted + * @param Len: Number of data received (in bytes) + * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL + */ +static USBD_StatusTypeDef Pybricks_Itf_Receive(uint8_t *Buf, uint32_t Len) { + + usb_in_sz = Len; + process_poll(&pbdrv_usb_process); + return USBD_OK; +} + +/** + * @brief Pybricks_Itf_TransmitCplt + * Data transmitted callback + * + * @note + * This function is IN transfer complete callback used to inform user that + * the submitted Data is successfully sent over USB. + * + * @param Buf: Buffer of data that was transmitted + * @param Len: Number of data transmitted (in bytes) + * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL + */ +static USBD_StatusTypeDef Pybricks_Itf_TransmitCplt(uint8_t *Buf, uint32_t Len, uint8_t epnum) { + process_poll(&pbdrv_usb_process); + return USBD_OK; +} + +static USBD_Pybricks_ItfTypeDef USBD_Pybricks_fops = { + .Init = Pybricks_Itf_Init, + .DeInit = Pybricks_Itf_DeInit, + .Receive = Pybricks_Itf_Receive, + .TransmitCplt = Pybricks_Itf_TransmitCplt, +}; + // Common USB driver implementation. void pbdrv_usb_init(void) { @@ -128,7 +200,12 @@ void pbdrv_usb_init(void) { husbd.pData = &hpcd; hpcd.pData = &husbd; - USBD_Init(&husbd, NULL, 0); + USBD_Pybricks_Desc_Init(); + USBD_Init(&husbd, &USBD_Pybricks_Desc, 0); + USBD_RegisterClass(&husbd, &USBD_Pybricks_ClassDriver); + USBD_Pybricks_RegisterInterface(&husbd, &USBD_Pybricks_fops); + USBD_Start(&husbd); + process_start(&pbdrv_usb_process); // VBUS may already be active @@ -145,6 +222,7 @@ PROCESS_THREAD(pbdrv_usb_process, ev, data) { static struct pt bcd_pt; static PBIO_ONESHOT(no_vbus_oneshot); static PBIO_ONESHOT(bcd_oneshot); + static PBIO_ONESHOT(pwrdn_oneshot); static bool bcd_busy; PROCESS_POLLHANDLER({ @@ -169,6 +247,34 @@ PROCESS_THREAD(pbdrv_usb_process, ev, data) { if (bcd_busy) { bcd_busy = PT_SCHEDULE(pbdrv_usb_stm32_bcd_detect(&bcd_pt)); + + if (bcd_busy) { + // All other USB functions are halted if BCD is busy + continue; + } + } + + if (pbsys_status_test(PBIO_PYBRICKS_STATUS_SHUTDOWN)) { + if (pbio_oneshot(true, &pwrdn_oneshot)) { + USBD_Stop(&husbd); + USBD_DeInit(&husbd); + } + + // USB communication is stopped after a shutdown, but + // the process is still needed to track the BCD state + continue; + } + + if (usb_in_sz) { + switch (usb_in_buf[0]) { + case USBD_PYBRICKS_OUT_EP_MSG_COMMAND: + pbsys_command(usb_in_buf + 1, usb_in_sz - 1); + break; + } + + // Prepare to receive the next packet + usb_in_sz = 0; + USBD_Pybricks_ReceivePacket(&husbd); } } diff --git a/lib/pbio/platform/essential_hub/platform.c b/lib/pbio/platform/essential_hub/platform.c index 699043356..e5e4372a8 100644 --- a/lib/pbio/platform/essential_hub/platform.c +++ b/lib/pbio/platform/essential_hub/platform.c @@ -670,8 +670,13 @@ void HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd) { } void HAL_PCD_MspDeInit(PCD_HandleTypeDef *hpcd) { - HAL_NVIC_DisableIRQ(EXTI9_5_IRQn); HAL_NVIC_DisableIRQ(OTG_FS_IRQn); + + // The VBUS IRQ remains enabled so that it can still + // be triggered if the device is shut down but left + // connected to charge. When the charging cable is + // disconnected, the IRQ will trigger and lead to the + // device fully powering down. } void OTG_FS_IRQHandler(void) { diff --git a/lib/pbio/platform/prime_hub/platform.c b/lib/pbio/platform/prime_hub/platform.c index 1acd3a0b0..982778772 100644 --- a/lib/pbio/platform/prime_hub/platform.c +++ b/lib/pbio/platform/prime_hub/platform.c @@ -970,8 +970,13 @@ void HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd) { } void HAL_PCD_MspDeInit(PCD_HandleTypeDef *hpcd) { - HAL_NVIC_DisableIRQ(EXTI9_5_IRQn); HAL_NVIC_DisableIRQ(OTG_FS_IRQn); + + // The VBUS IRQ remains enabled so that it can still + // be triggered if the device is shut down but left + // connected to charge. When the charging cable is + // disconnected, the IRQ will trigger and lead to the + // device fully powering down. } void OTG_FS_IRQHandler(void) { From 5e5ca1dc16cb57b971d9381f132239045626f181 Mon Sep 17 00:00:00 2001 From: Nate Karstens Date: Mon, 19 Feb 2024 00:51:19 -0600 Subject: [PATCH 5/8] pbdrv/usb: Transmit response message. Transmit the response to a command. This is used to communicate any errors in handling the command back to the user and provide backpressure to ensure the next command isn't sent until the previous has been handled. Signed-off-by: Nate Karstens --- lib/pbio/drv/usb/usb_stm32.c | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/lib/pbio/drv/usb/usb_stm32.c b/lib/pbio/drv/usb/usb_stm32.c index 9a782c785..23f604d64 100644 --- a/lib/pbio/drv/usb/usb_stm32.c +++ b/lib/pbio/drv/usb/usb_stm32.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2022 The Pybricks Authors +// Copyright (c) 2022-2025 The Pybricks Authors // Main file for STM32F4 USB driver. @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -29,7 +30,10 @@ PROCESS(pbdrv_usb_process, "USB"); // These buffers need to be 32-bit aligned because the USB driver moves data // to/from FIFOs in 32-bit chunks. static uint8_t usb_in_buf[USBD_PYBRICKS_MAX_PACKET_SIZE] __aligned(4); +static uint8_t usb_response_buf[1 + sizeof(uint32_t)] __aligned(4); static volatile uint32_t usb_in_sz; +static volatile uint32_t usb_response_sz; +static volatile bool transmitting; static USBD_HandleTypeDef husbd; static PCD_HandleTypeDef hpcd; @@ -140,6 +144,8 @@ void pbdrv_usb_stm32_handle_vbus_irq(bool active) { static USBD_StatusTypeDef Pybricks_Itf_Init(void) { USBD_Pybricks_SetRxBuffer(&husbd, usb_in_buf); usb_in_sz = 0; + usb_response_sz = 0; + transmitting = false; return USBD_OK; } @@ -182,8 +188,17 @@ static USBD_StatusTypeDef Pybricks_Itf_Receive(uint8_t *Buf, uint32_t Len) { * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL */ static USBD_StatusTypeDef Pybricks_Itf_TransmitCplt(uint8_t *Buf, uint32_t Len, uint8_t epnum) { + USBD_StatusTypeDef ret = USBD_OK; + + if (Buf == usb_response_buf) { + usb_response_sz = 0; + } else { + ret = USBD_FAIL; + } + + transmitting = false; process_poll(&pbdrv_usb_process); - return USBD_OK; + return ret; } static USBD_Pybricks_ItfTypeDef USBD_Pybricks_fops = { @@ -224,6 +239,7 @@ PROCESS_THREAD(pbdrv_usb_process, ev, data) { static PBIO_ONESHOT(bcd_oneshot); static PBIO_ONESHOT(pwrdn_oneshot); static bool bcd_busy; + static pbio_pybricks_error_t result; PROCESS_POLLHANDLER({ if (!bcd_busy && pbio_oneshot(!vbus_active, &no_vbus_oneshot)) { @@ -268,7 +284,12 @@ PROCESS_THREAD(pbdrv_usb_process, ev, data) { if (usb_in_sz) { switch (usb_in_buf[0]) { case USBD_PYBRICKS_OUT_EP_MSG_COMMAND: - pbsys_command(usb_in_buf + 1, usb_in_sz - 1); + if (usb_response_sz == 0) { + result = pbsys_command(usb_in_buf + 1, usb_in_sz - 1); + usb_response_buf[0] = USBD_PYBRICKS_IN_EP_MSG_RESPONSE; + pbio_set_uint32_le(&usb_response_buf[1], result); + usb_response_sz = sizeof(usb_response_buf); + } break; } @@ -276,6 +297,15 @@ PROCESS_THREAD(pbdrv_usb_process, ev, data) { usb_in_sz = 0; USBD_Pybricks_ReceivePacket(&husbd); } + + if (transmitting) { + continue; + } + + if (usb_response_sz) { + transmitting = true; + USBD_Pybricks_TransmitPacket(&husbd, usb_response_buf, usb_response_sz); + } } PROCESS_END(); From c70fa8145db007aee12353d3fdd82ff3e2babbdc Mon Sep 17 00:00:00 2001 From: Nate Karstens Date: Mon, 19 Feb 2024 00:51:19 -0600 Subject: [PATCH 6/8] pbdrv/usb: Periodically send status. Implement Pybricks status reporting over USB. TODO: this should only be enabled if the USB host subscribes. Current implementation is always on. Signed-off-by: Nate Karstens --- lib/pbio/drv/usb/usb_stm32.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/pbio/drv/usb/usb_stm32.c b/lib/pbio/drv/usb/usb_stm32.c index 23f604d64..1bf1bafb0 100644 --- a/lib/pbio/drv/usb/usb_stm32.c +++ b/lib/pbio/drv/usb/usb_stm32.c @@ -31,8 +31,10 @@ PROCESS(pbdrv_usb_process, "USB"); // to/from FIFOs in 32-bit chunks. static uint8_t usb_in_buf[USBD_PYBRICKS_MAX_PACKET_SIZE] __aligned(4); static uint8_t usb_response_buf[1 + sizeof(uint32_t)] __aligned(4); +static uint8_t usb_status_buf[1 + PBSYS_STATUS_REPORT_SIZE] __aligned(4); static volatile uint32_t usb_in_sz; static volatile uint32_t usb_response_sz; +static volatile uint32_t usb_status_sz; static volatile bool transmitting; static USBD_HandleTypeDef husbd; @@ -145,6 +147,7 @@ static USBD_StatusTypeDef Pybricks_Itf_Init(void) { USBD_Pybricks_SetRxBuffer(&husbd, usb_in_buf); usb_in_sz = 0; usb_response_sz = 0; + usb_status_sz = 0; transmitting = false; return USBD_OK; @@ -192,6 +195,8 @@ static USBD_StatusTypeDef Pybricks_Itf_TransmitCplt(uint8_t *Buf, uint32_t Len, if (Buf == usb_response_buf) { usb_response_sz = 0; + } else if (Buf == usb_status_buf) { + usb_status_sz = 0; } else { ret = USBD_FAIL; } @@ -240,6 +245,9 @@ PROCESS_THREAD(pbdrv_usb_process, ev, data) { static PBIO_ONESHOT(pwrdn_oneshot); static bool bcd_busy; static pbio_pybricks_error_t result; + static struct etimer timer; + static uint32_t prev_status_flags = ~0; + static uint32_t new_status_flags; PROCESS_POLLHANDLER({ if (!bcd_busy && pbio_oneshot(!vbus_active, &no_vbus_oneshot)) { @@ -258,6 +266,8 @@ PROCESS_THREAD(pbdrv_usb_process, ev, data) { PROCESS_BEGIN(); + etimer_set(&timer, 500); + for (;;) { PROCESS_WAIT_EVENT(); @@ -302,9 +312,23 @@ PROCESS_THREAD(pbdrv_usb_process, ev, data) { continue; } + new_status_flags = pbsys_status_get_flags(); + + // Transmit. Give priority to response, then status updates. if (usb_response_sz) { transmitting = true; USBD_Pybricks_TransmitPacket(&husbd, usb_response_buf, usb_response_sz); + } else if ((new_status_flags != prev_status_flags) || etimer_expired(&timer)) { + usb_status_buf[0] = USBD_PYBRICKS_IN_EP_MSG_EVENT; + _Static_assert(sizeof(usb_status_buf) + 1 >= PBIO_PYBRICKS_EVENT_STATUS_REPORT_SIZE, + "size of status report does not match size of event"); + usb_status_sz = 1 + pbsys_status_get_status_report(&usb_status_buf[1]); + + etimer_restart(&timer); + prev_status_flags = new_status_flags; + + transmitting = true; + USBD_Pybricks_TransmitPacket(&husbd, usb_status_buf, usb_status_sz); } } From 141952f3d9c4aae9d42acdba6f96105a6c1c0748 Mon Sep 17 00:00:00 2001 From: Nate Karstens Date: Mon, 19 Feb 2024 00:51:19 -0600 Subject: [PATCH 7/8] pbdrv/usb: Add support for transmitting stdout over USB. Add partial support for transmitting stdout messages over USB. TODO: Need to add implementation of subscribing to have this not enabled all of the time. Then this can be integrated with the mphal in MicroPython. Signed-off-by: Nate Karstens --- lib/pbio/drv/usb/usb_stm32.c | 57 +++++++++++++++++++++++++++++++++++- lib/pbio/include/pbdrv/usb.h | 23 +++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/lib/pbio/drv/usb/usb_stm32.c b/lib/pbio/drv/usb/usb_stm32.c index 1bf1bafb0..d67ffeba0 100644 --- a/lib/pbio/drv/usb/usb_stm32.c +++ b/lib/pbio/drv/usb/usb_stm32.c @@ -7,6 +7,7 @@ #if PBDRV_CONFIG_USB_STM32F4 +#include #include #include @@ -32,9 +33,11 @@ PROCESS(pbdrv_usb_process, "USB"); static uint8_t usb_in_buf[USBD_PYBRICKS_MAX_PACKET_SIZE] __aligned(4); static uint8_t usb_response_buf[1 + sizeof(uint32_t)] __aligned(4); static uint8_t usb_status_buf[1 + PBSYS_STATUS_REPORT_SIZE] __aligned(4); +static uint8_t usb_stdout_buf[USBD_PYBRICKS_MAX_PACKET_SIZE] __aligned(4); static volatile uint32_t usb_in_sz; static volatile uint32_t usb_response_sz; static volatile uint32_t usb_status_sz; +static volatile uint32_t usb_stdout_sz; static volatile bool transmitting; static USBD_HandleTypeDef husbd; @@ -137,6 +140,52 @@ void pbdrv_usb_stm32_handle_vbus_irq(bool active) { process_poll(&pbdrv_usb_process); } +/** + * Queues data to be transmitted via USB. + * @param data [in] The data to be sent. + * @param size [in, out] The size of @p data in bytes. After return, @p size + * contains the number of bytes actually written. + * @return ::PBIO_SUCCESS if @p data was queued, ::PBIO_ERROR_AGAIN + * if @p data could not be queued at this time (e.g. buffer + * is full), ::PBIO_ERROR_INVALID_OP if there is not an + * active USB connection or ::PBIO_ERROR_NOT_SUPPORTED + * if this platform does not support USB. + */ +pbio_error_t pbdrv_usb_stdout_tx(const uint8_t *data, uint32_t *size) { + uint8_t *ptr = usb_stdout_buf; + uint32_t ptr_len = sizeof(usb_stdout_buf); + + // TODO: return PBIO_ERROR_INVALID_OP if app flag is not set. Also need a + // timeout in case the app crashes and doesn't clear the flag on exit. + + if (usb_stdout_sz) { + return PBIO_ERROR_AGAIN; + } + + *ptr++ = USBD_PYBRICKS_IN_EP_MSG_EVENT; + ptr_len--; + + *ptr++ = PBIO_PYBRICKS_EVENT_WRITE_STDOUT; + ptr_len--; + + *size = MIN(*size, ptr_len); + memcpy(ptr, data, *size); + + usb_stdout_sz = 1 + 1 + *size; + + process_poll(&pbdrv_usb_process); + + return PBIO_SUCCESS; +} + +/** + * Indicates if there is stdout data waiting to be transmitted over USB. + * @retval false if stdout data is currently being transmitted. + */ +bool pbdrv_usb_stdout_tx_is_idle(void) { + return usb_stdout_sz == 0; +} + /** * @brief Pybricks_Itf_Init * Initializes the Pybricks media low layer @@ -148,6 +197,7 @@ static USBD_StatusTypeDef Pybricks_Itf_Init(void) { usb_in_sz = 0; usb_response_sz = 0; usb_status_sz = 0; + usb_stdout_sz = 0; transmitting = false; return USBD_OK; @@ -197,6 +247,8 @@ static USBD_StatusTypeDef Pybricks_Itf_TransmitCplt(uint8_t *Buf, uint32_t Len, usb_response_sz = 0; } else if (Buf == usb_status_buf) { usb_status_sz = 0; + } else if (Buf == usb_stdout_buf) { + usb_stdout_sz = 0; } else { ret = USBD_FAIL; } @@ -314,7 +366,7 @@ PROCESS_THREAD(pbdrv_usb_process, ev, data) { new_status_flags = pbsys_status_get_flags(); - // Transmit. Give priority to response, then status updates. + // Transmit. Give priority to response, then status updates, then stdout. if (usb_response_sz) { transmitting = true; USBD_Pybricks_TransmitPacket(&husbd, usb_response_buf, usb_response_sz); @@ -329,6 +381,9 @@ PROCESS_THREAD(pbdrv_usb_process, ev, data) { transmitting = true; USBD_Pybricks_TransmitPacket(&husbd, usb_status_buf, usb_status_sz); + } else if (usb_stdout_sz) { + transmitting = true; + USBD_Pybricks_TransmitPacket(&husbd, usb_stdout_buf, usb_stdout_sz); } } diff --git a/lib/pbio/include/pbdrv/usb.h b/lib/pbio/include/pbdrv/usb.h index 08c8a4f4e..3a306be7e 100644 --- a/lib/pbio/include/pbdrv/usb.h +++ b/lib/pbio/include/pbdrv/usb.h @@ -9,7 +9,10 @@ #ifndef _PBDRV_USB_H_ #define _PBDRV_USB_H_ +#include + #include +#include /** * Indicates battery charging capabilites that were detected on a USB port. @@ -37,12 +40,32 @@ typedef enum { */ pbdrv_usb_bcd_t pbdrv_usb_get_bcd(void); +/** + * Transmits the given buffer over the USB stdout stream. + * @return The result of the operation. + */ +pbio_error_t pbdrv_usb_stdout_tx(const uint8_t *data, uint32_t *size); + +/** + * Indicates if the USB stdout stream is idle. + * @return true if the USB stdout stream is idle. +*/ +bool pbdrv_usb_stdout_tx_is_idle(void); + #else // PBDRV_CONFIG_USB static inline pbdrv_usb_bcd_t pbdrv_usb_get_bcd(void) { return PBDRV_USB_BCD_NONE; } +static inline pbio_error_t pbdrv_usb_stdout_tx(const uint8_t *data, uint32_t *size) { + return PBIO_SUCCESS; +} + +static inline bool pbdrv_usb_stdout_tx_is_idle(void) { + return true; +} + #endif // PBDRV_CONFIG_USB #endif // _PBDRV_USB_H_ From fd7f6ae4c6b95d79c97970522da3ea5b185c4643 Mon Sep 17 00:00:00 2001 From: Nate Karstens Date: Mon, 19 Feb 2024 00:51:19 -0600 Subject: [PATCH 8/8] pbdrv/usb: Add WebUSB platform descriptor. Add a WebUSB platform descriptor to the BOS descriptor and a landing page pointing to https://code.pybricks.com. Signed-off-by: Nate Karstens --- lib/pbio/drv/usb/stm32_usbd/usbd_desc.c | 28 ++++++++++++++++++--- lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c | 20 +++++++++++++++ lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h | 4 +++ 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c b/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c index 83118e99b..8f31be426 100644 --- a/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c +++ b/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c @@ -73,7 +73,7 @@ static const char software_version[] = PBIO_PROTOCOL_VERSION_STR; #define USB_DEV_CAP_TYPE_PLATFORM (5) #define USB_SIZ_STRING_SERIAL 0x1A -#define USB_SIZ_BOS_DESC_CONST (5 + 28) +#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) @@ -117,7 +117,7 @@ __ALIGN_BEGIN static uint8_t USBD_BOSDesc[USB_SIZ_BOS_DESC] __ALIGN_END = USB_DESC_TYPE_BOS, /* bDescriptorType = BOS */ LOBYTE(USB_SIZ_BOS_DESC), /* wTotalLength */ HIBYTE(USB_SIZ_BOS_DESC), /* wTotalLength */ - 1, /* bNumDeviceCaps */ + 2, /* bNumDeviceCaps */ 28, /* bLength */ USB_DEVICE_CAPABITY_TYPE, /* bDescriptorType = Device Capability */ @@ -140,7 +140,29 @@ __ALIGN_BEGIN static uint8_t USBD_BOSDesc[USB_SIZ_BOS_DESC] __ALIGN_END = 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 */ + 0x00, /* bAltEnumCode = Does not support alternate enumeration */ + + 24, /* 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 + * 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, + + LOBYTE(0x0100), /* bcdVersion */ + HIBYTE(0x0100), /* bcdVersion */ + USBD_WEBUSB_VENDOR_CODE, /* bVendorCode */ + USBD_WEBUSB_LANDING_PAGE_IDX /* iLandingPage */ }; static uint16_t USBD_BOSDesc_Len; diff --git a/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c index bb51856c8..d35235dd9 100644 --- a/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c +++ b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c @@ -155,6 +155,18 @@ __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 = +{ + 20, /* bLength */ + 0x03, /* bDescriptorType = URL */ + 0x01, /* bScheme = https:// */ + + /* URL */ + 'c', 'o', 'd', 'e', '.', + 'p', 'y', 'b', 'r', 'i', 'c', 'k', 's', '.', + 'c', 'o', 'm' +}; + /** * @} */ @@ -244,6 +256,14 @@ static USBD_StatusTypeDef USBD_Pybricks_Setup(USBD_HandleTypeDef *pdev, MIN(sizeof(USBD_OSDescSet), req->wLength)); break; + case USBD_WEBUSB_VENDOR_CODE: + if ((req->wValue == USBD_WEBUSB_LANDING_PAGE_IDX) && (req->wIndex == 0x02)) { + (void)USBD_CtlSendData(pdev, + (uint8_t *)WebUSB_DescSet, + MIN(sizeof(WebUSB_DescSet), req->wLength)); + } + break; + default: USBD_CtlError(pdev, req); ret = USBD_FAIL; diff --git a/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h index 65226dd6c..455446156 100644 --- a/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h +++ b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h @@ -41,8 +41,12 @@ extern "C" { * @{ */ #define USBD_MS_VENDOR_CODE 0x01 +#define USBD_WEBUSB_VENDOR_CODE 0x02 + #define USBD_SIZ_MS_OS_DSCRPTR_SET (10 + 20 + 132) +#define USBD_WEBUSB_LANDING_PAGE_IDX 1 + #define USBD_PYBRICKS_CONFIG_DESC_SIZ (9 + 9 + 7 + 7) #define USBD_PYBRICKS_IN_EP 0x81U /* EP1 for data IN */