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