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..8f31be426 100644 --- a/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c +++ b/lib/pbio/drv/usb/stm32_usbd/usbd_desc.c @@ -43,14 +43,19 @@ ****************************************************************************** */ /* Includes ------------------------------------------------------------------*/ +#include #include +#include #include #include +#include +#include #include "usbd_core.h" #include "usbd_conf.h" +#include "usbd_pybricks.h" /* Private typedef -----------------------------------------------------------*/ /* Private define ------------------------------------------------------------*/ @@ -58,11 +63,25 @@ #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) +#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) /* USB Standard Device Descriptor */ __ALIGN_BEGIN static @@ -72,7 +91,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 +110,151 @@ 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 */ + 2, /* 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 */ + + 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; + +__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, @@ -149,6 +314,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 @@ -242,6 +439,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 = USBD_BOSDesc_Len; + return (uint8_t *)USBD_BOSDesc; +} + USBD_DescriptorsTypeDef USBD_Pybricks_Desc = { .GetDeviceDescriptor = USBD_Pybricks_DeviceDescriptor, .GetLangIDStrDescriptor = USBD_Pybricks_LangIDStrDescriptor, @@ -250,6 +455,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) { @@ -265,4 +471,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/drv/usb/stm32_usbd/usbd_pybricks.c b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c new file mode 100644 index 000000000..d35235dd9 --- /dev/null +++ b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c @@ -0,0 +1,487 @@ +/** + ****************************************************************************** + * @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 */ +}; + +__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' +}; + +/** + * @} + */ + +/** @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; + + 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; + 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..455446156 --- /dev/null +++ b/lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h @@ -0,0 +1,152 @@ +/** + ****************************************************************************** + * @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_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 */ +#define USBD_PYBRICKS_OUT_EP 0x01U /* EP1 for data OUT */ + +#define USBD_PYBRICKS_MAX_PACKET_SIZE 64U + +/** + * @} + */ + +// 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 + * @{ + */ + +/** + * @} + */ +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 */ +/** + * @} + */ + +/** + * @} + */ diff --git a/lib/pbio/drv/usb/usb_stm32.c b/lib/pbio/drv/usb/usb_stm32.c index e7ab1fd5b..d67ffeba0 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. @@ -7,23 +7,42 @@ #if PBDRV_CONFIG_USB_STM32F4 +#include #include #include #include #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 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; static PCD_HandleTypeDef hpcd; + static volatile bool vbus_active; static pbdrv_usb_bcd_t pbdrv_usb_bcd; @@ -121,6 +140,131 @@ 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 + * @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; + usb_response_sz = 0; + usb_status_sz = 0; + usb_stdout_sz = 0; + transmitting = false; + + 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) { + USBD_StatusTypeDef ret = USBD_OK; + + if (Buf == usb_response_buf) { + 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; + } + + transmitting = false; + process_poll(&pbdrv_usb_process); + return ret; +} + +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 +272,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,7 +294,12 @@ 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; + 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)) { @@ -164,11 +318,72 @@ PROCESS_THREAD(pbdrv_usb_process, ev, data) { PROCESS_BEGIN(); + etimer_set(&timer, 500); + for (;;) { PROCESS_WAIT_EVENT(); 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: + 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; + } + + // Prepare to receive the next packet + usb_in_sz = 0; + USBD_Pybricks_ReceivePacket(&husbd); + } + + if (transmitting) { + continue; + } + + new_status_flags = pbsys_status_get_flags(); + + // 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); + } 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); + } 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_ 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/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/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) { 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; 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.