From 7e59f1bf8a7590e13da7fe5e8c602b41c95d2341 Mon Sep 17 00:00:00 2001 From: TenGui Date: Thu, 23 Jul 2026 15:26:18 -0700 Subject: [PATCH 01/10] fix narrowing, add cast --- src/device/usbd.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/device/usbd.h b/src/device/usbd.h index 9be12ed0a..296ec417d 100644 --- a/src/device/usbd.h +++ b/src/device/usbd.h @@ -229,7 +229,7 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ // Config number, interface count, string index, total length, attribute, power in mA #define TUD_CONFIG_DESCRIPTOR(config_num, _itfcount, _stridx, _total_len, _attribute, _power_ma) \ - 9, TUSB_DESC_CONFIGURATION, U16_TO_U8S_LE(_total_len), _itfcount, config_num, _stridx, TU_BIT(7) | _attribute, (_power_ma)/2 + 9, TUSB_DESC_CONFIGURATION, U16_TO_U8S_LE(_total_len), _itfcount, config_num, _stridx, TU_BIT(7) | _attribute, (uint8_t)((_power_ma)/2) //--------------------------------------------------------------------+ // CDC Descriptor Templates From ca198f3dac0b61f3a13bc3319223ed37511e568c Mon Sep 17 00:00:00 2001 From: TenGui Date: Thu, 23 Jul 2026 15:58:28 -0700 Subject: [PATCH 02/10] also fix tusb_types --- src/common/tusb_types.h | 408 +++++++++++++++++++++------------------- 1 file changed, 219 insertions(+), 189 deletions(-) diff --git a/src/common/tusb_types.h b/src/common/tusb_types.h index d49d277ed..5a91d6182 100644 --- a/src/common/tusb_types.h +++ b/src/common/tusb_types.h @@ -13,43 +13,49 @@ #include "tusb_compiler.h" #ifdef __cplusplus - extern "C" { +extern "C" { #endif //------------- Device DCache declaration -------------// -#define TUD_EPBUF_DCACHE_SIZE(_size) (CFG_TUD_MEM_DCACHE_ENABLE ? \ - (TU_DIV_CEIL(_size, CFG_TUD_MEM_DCACHE_LINE_SIZE) * CFG_TUD_MEM_DCACHE_LINE_SIZE) : (_size)) +#define TUD_EPBUF_DCACHE_SIZE(_size) \ + (CFG_TUD_MEM_DCACHE_ENABLE ? (TU_DIV_CEIL(_size, CFG_TUD_MEM_DCACHE_LINE_SIZE) * CFG_TUD_MEM_DCACHE_LINE_SIZE) \ + : (_size)) // Declare an endpoint buffer with uint8_t[size] -#define TUD_EPBUF_DEF(_name, _size) \ - union { \ - CFG_TUD_MEM_ALIGN uint8_t _name[_size]; \ - TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(_size)]; \ +#define TUD_EPBUF_DEF(_name, _size) \ + union { \ + CFG_TUD_MEM_ALIGN uint8_t _name[_size]; \ + TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) \ + uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(_size)]; \ } // Declare an endpoint buffer with a type -#define TUD_EPBUF_TYPE_DEF(_type, _name) \ - union { \ - CFG_TUD_MEM_ALIGN _type _name; \ - TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ +#define TUD_EPBUF_TYPE_DEF(_type, _name) \ + union { \ + CFG_TUD_MEM_ALIGN _type _name; \ + TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) \ + uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ } //------------- Host DCache declaration -------------// -#define TUH_EPBUF_DCACHE_SIZE(_size) (CFG_TUH_MEM_DCACHE_ENABLE ? \ - (TU_DIV_CEIL(_size, CFG_TUH_MEM_DCACHE_LINE_SIZE) * CFG_TUH_MEM_DCACHE_LINE_SIZE) : (_size)) +#define TUH_EPBUF_DCACHE_SIZE(_size) \ + (CFG_TUH_MEM_DCACHE_ENABLE ? (TU_DIV_CEIL(_size, CFG_TUH_MEM_DCACHE_LINE_SIZE) * CFG_TUH_MEM_DCACHE_LINE_SIZE) \ + : (_size)) // Declare an endpoint buffer with uint8_t[size] -#define TUH_EPBUF_DEF(_name, _size) \ - union { \ - CFG_TUH_MEM_ALIGN uint8_t _name[_size]; \ - TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(_size)]; \ +#define TUH_EPBUF_DEF(_name, _size) \ + union { \ + CFG_TUH_MEM_ALIGN uint8_t _name[_size]; \ + TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) \ + uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(_size)]; \ } // Declare an endpoint buffer with a type -#define TUH_EPBUF_TYPE_DEF(_type, _name) \ - union { \ - CFG_TUH_MEM_ALIGN _type _name; \ - TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ +#define TUH_EPBUF_TYPE_DEF(_type, _name) \ + union { \ + CFG_TUH_MEM_ALIGN _type _name; \ + TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) \ + uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ } @@ -65,10 +71,10 @@ typedef enum { /// defined base on EHCI specs value for Endpoint Speed typedef enum { - TUSB_SPEED_FULL = 0, - TUSB_SPEED_LOW = 1, - TUSB_SPEED_HIGH = 2, - TUSB_SPEED_AUTO = 0xaa, + TUSB_SPEED_FULL = 0, + TUSB_SPEED_LOW = 1, + TUSB_SPEED_HIGH = 2, + TUSB_SPEED_AUTO = 0xaa, TUSB_SPEED_INVALID = 0xff, } tusb_speed_t; @@ -99,18 +105,18 @@ enum { }; // Endpoint Bulk size depending on host/device max speed -#define TUD_EPSIZE_BULK_MAX (TUD_OPT_HIGH_SPEED ? 512 : 64) -#define TUH_EPSIZE_BULK_MAX (TUH_OPT_HIGH_SPEED ? 512 : 64) +#define TUD_EPSIZE_BULK_MAX (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define TUH_EPSIZE_BULK_MAX (TUH_OPT_HIGH_SPEED ? 512 : 64) /// Isochronous Endpoint Attributes typedef enum { - TUSB_ISO_EP_ATT_NO_SYNC = 0x00, - TUSB_ISO_EP_ATT_ASYNCHRONOUS = 0x04, - TUSB_ISO_EP_ATT_ADAPTIVE = 0x08, - TUSB_ISO_EP_ATT_SYNCHRONOUS = 0x0C, - TUSB_ISO_EP_ATT_DATA = 0x00, ///< Data End Point - TUSB_ISO_EP_ATT_EXPLICIT_FB = 0x10, ///< Feedback End Point - TUSB_ISO_EP_ATT_IMPLICIT_FB = 0x20, ///< Data endpoint that also serves as an implicit feedback + TUSB_ISO_EP_ATT_NO_SYNC = 0x00, + TUSB_ISO_EP_ATT_ASYNCHRONOUS = 0x04, + TUSB_ISO_EP_ATT_ADAPTIVE = 0x08, + TUSB_ISO_EP_ATT_SYNCHRONOUS = 0x0C, + TUSB_ISO_EP_ATT_DATA = 0x00, ///< Data End Point + TUSB_ISO_EP_ATT_EXPLICIT_FB = 0x10, ///< Feedback End Point + TUSB_ISO_EP_ATT_IMPLICIT_FB = 0x20, ///< Data endpoint that also serves as an implicit feedback } tusb_iso_ep_attribute_t; /// USB Descriptor Types @@ -127,35 +133,35 @@ typedef enum { TUSB_DESC_DEBUG = 0x0A, TUSB_DESC_INTERFACE_ASSOCIATION = 0x0B, - TUSB_DESC_BOS = 0x0F, - TUSB_DESC_DEVICE_CAPABILITY = 0x10, + TUSB_DESC_BOS = 0x0F, + TUSB_DESC_DEVICE_CAPABILITY = 0x10, - TUSB_DESC_FUNCTIONAL = 0x21, + TUSB_DESC_FUNCTIONAL = 0x21, // Class Specific Descriptor - TUSB_DESC_CS_DEVICE = 0x21, - TUSB_DESC_CS_CONFIGURATION = 0x22, - TUSB_DESC_CS_STRING = 0x23, - TUSB_DESC_CS_INTERFACE = 0x24, - TUSB_DESC_CS_ENDPOINT = 0x25, + TUSB_DESC_CS_DEVICE = 0x21, + TUSB_DESC_CS_CONFIGURATION = 0x22, + TUSB_DESC_CS_STRING = 0x23, + TUSB_DESC_CS_INTERFACE = 0x24, + TUSB_DESC_CS_ENDPOINT = 0x25, TUSB_DESC_SUPERSPEED_ENDPOINT_COMPANION = 0x30, TUSB_DESC_SUPERSPEED_ISO_ENDPOINT_COMPANION = 0x31 } tusb_desc_type_t; typedef enum { - TUSB_REQ_GET_STATUS = 0 , - TUSB_REQ_CLEAR_FEATURE = 1 , - TUSB_REQ_RESERVED = 2 , - TUSB_REQ_SET_FEATURE = 3 , - TUSB_REQ_RESERVED2 = 4 , - TUSB_REQ_SET_ADDRESS = 5 , - TUSB_REQ_GET_DESCRIPTOR = 6 , - TUSB_REQ_SET_DESCRIPTOR = 7 , - TUSB_REQ_GET_CONFIGURATION = 8 , - TUSB_REQ_SET_CONFIGURATION = 9 , - TUSB_REQ_GET_INTERFACE = 10 , - TUSB_REQ_SET_INTERFACE = 11 , + TUSB_REQ_GET_STATUS = 0, + TUSB_REQ_CLEAR_FEATURE = 1, + TUSB_REQ_RESERVED = 2, + TUSB_REQ_SET_FEATURE = 3, + TUSB_REQ_RESERVED2 = 4, + TUSB_REQ_SET_ADDRESS = 5, + TUSB_REQ_GET_DESCRIPTOR = 6, + TUSB_REQ_SET_DESCRIPTOR = 7, + TUSB_REQ_GET_CONFIGURATION = 8, + TUSB_REQ_SET_CONFIGURATION = 9, + TUSB_REQ_GET_INTERFACE = 10, + TUSB_REQ_SET_INTERFACE = 11, TUSB_REQ_SYNCH_FRAME = 12 } tusb_request_code_t; @@ -173,7 +179,7 @@ typedef enum { } tusb_request_type_t; typedef enum { - TUSB_REQ_RCPT_DEVICE =0, + TUSB_REQ_RCPT_DEVICE = 0, TUSB_REQ_RCPT_INTERFACE, TUSB_REQ_RCPT_ENDPOINT, TUSB_REQ_RCPT_OTHER @@ -181,42 +187,41 @@ typedef enum { // https://www.usb.org/defined-class-codes typedef enum { - TUSB_CLASS_UNSPECIFIED = 0 , - TUSB_CLASS_AUDIO = 1 , - TUSB_CLASS_CDC = 2 , - TUSB_CLASS_HID = 3 , - TUSB_CLASS_RESERVED_4 = 4 , - TUSB_CLASS_PHYSICAL = 5 , - TUSB_CLASS_IMAGE = 6 , - TUSB_CLASS_PRINTER = 7 , - TUSB_CLASS_MSC = 8 , - TUSB_CLASS_HUB = 9 , - TUSB_CLASS_CDC_DATA = 10 , - TUSB_CLASS_SMART_CARD = 11 , - TUSB_CLASS_RESERVED_12 = 12 , - TUSB_CLASS_CONTENT_SECURITY = 13 , - TUSB_CLASS_VIDEO = 14 , - TUSB_CLASS_PERSONAL_HEALTHCARE = 15 , - TUSB_CLASS_AUDIO_VIDEO = 16 , + TUSB_CLASS_UNSPECIFIED = 0, + TUSB_CLASS_AUDIO = 1, + TUSB_CLASS_CDC = 2, + TUSB_CLASS_HID = 3, + TUSB_CLASS_RESERVED_4 = 4, + TUSB_CLASS_PHYSICAL = 5, + TUSB_CLASS_IMAGE = 6, + TUSB_CLASS_PRINTER = 7, + TUSB_CLASS_MSC = 8, + TUSB_CLASS_HUB = 9, + TUSB_CLASS_CDC_DATA = 10, + TUSB_CLASS_SMART_CARD = 11, + TUSB_CLASS_RESERVED_12 = 12, + TUSB_CLASS_CONTENT_SECURITY = 13, + TUSB_CLASS_VIDEO = 14, + TUSB_CLASS_PERSONAL_HEALTHCARE = 15, + TUSB_CLASS_AUDIO_VIDEO = 16, - TUSB_CLASS_DIAGNOSTIC = 0xDC , - TUSB_CLASS_WIRELESS_CONTROLLER = 0xE0 , - TUSB_CLASS_MISC = 0xEF , - TUSB_CLASS_APPLICATION_SPECIFIC = 0xFE , + TUSB_CLASS_DIAGNOSTIC = 0xDC, + TUSB_CLASS_WIRELESS_CONTROLLER = 0xE0, + TUSB_CLASS_MISC = 0xEF, + TUSB_CLASS_APPLICATION_SPECIFIC = 0xFE, TUSB_CLASS_VENDOR_SPECIFIC = 0xFF } tusb_class_code_t; -typedef enum -{ +typedef enum { MISC_SUBCLASS_COMMON = 2 -}misc_subclass_type_t; +} misc_subclass_type_t; typedef enum { MISC_PROTOCOL_IAD = 1 } misc_protocol_type_t; typedef enum { - APP_SUBCLASS_USBTMC = 0x03, + APP_SUBCLASS_USBTMC = 0x03, APP_SUBCLASS_DFU_RUNTIME = 0x01 } app_subclass_type_t; @@ -244,14 +249,14 @@ enum { TUSB_DESC_CONFIG_ATT_SELF_POWERED = 1 << 6, }; -#define TUSB_DESC_CONFIG_POWER_MA(x) ((x)/2) +#define TUSB_DESC_CONFIG_POWER_MA(x) (uint8_t)((x) / 2) // USB 2.0 Spec Table 9-7: Test Mode Selectors typedef enum { - TUSB_FEATURE_TEST_J = 1, - TUSB_FEATURE_TEST_K = 2, - TUSB_FEATURE_TEST_SE0_NAK = 3, - TUSB_FEATURE_TEST_PACKET = 4, + TUSB_FEATURE_TEST_J = 1, + TUSB_FEATURE_TEST_K = 2, + TUSB_FEATURE_TEST_SE0_NAK = 3, + TUSB_FEATURE_TEST_PACKET = 4, TUSB_FEATURE_TEST_FORCE_ENABLE = 5, } tusb_feature_test_mode_t; @@ -271,8 +276,8 @@ typedef enum { // TODO remove enum { - DESC_OFFSET_LEN = 0, - DESC_OFFSET_TYPE = 1, + DESC_OFFSET_LEN = 0, + DESC_OFFSET_TYPE = 1, DESC_OFFSET_SUBTYPE = 2 }; @@ -304,8 +309,8 @@ enum { }; enum { - TU_EP0_OUT = 0x00, - TU_EP0_IN = 0x80 + TU_EP0_OUT = 0x00, + TU_EP0_IN = 0x80 }; @@ -313,7 +318,7 @@ enum { // //--------------------------------------------------------------------+ typedef struct { - tusb_role_t role; + tusb_role_t role; tusb_speed_t speed; } tusb_rhport_init_t; @@ -332,77 +337,101 @@ TU_ATTR_BIT_FIELD_ORDER_BEGIN /// USB Device Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength ; ///< Size of this descriptor in bytes. - uint8_t bDescriptorType ; ///< DEVICE Descriptor Type. - uint16_t bcdUSB ; ///< BUSB Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H). - uint8_t bDeviceClass ; ///< Class code (assigned by the USB-IF). - uint8_t bDeviceSubClass ; ///< Subclass code (assigned by the USB-IF). - uint8_t bDeviceProtocol ; ///< Protocol code (assigned by the USB-IF). - uint8_t bMaxPacketSize0 ; ///< Maximum packet size for endpoint zero (only 8, 16, 32, or 64 are valid). For HS devices is fixed to 64. - uint16_t idVendor ; ///< Vendor ID (assigned by the USB-IF). - uint16_t idProduct ; ///< Product ID (assigned by the manufacturer). - uint16_t bcdDevice ; ///< Device release number in binary-coded decimal. - uint8_t iManufacturer ; ///< Index of string descriptor describing manufacturer. - uint8_t iProduct ; ///< Index of string descriptor describing product. - uint8_t iSerialNumber ; ///< Index of string descriptor describing the device's serial number. - uint8_t bNumConfigurations ; ///< Number of possible configurations. + uint8_t bLength; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType; ///< DEVICE Descriptor Type. + uint16_t bcdUSB; ///< BUSB Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H). + uint8_t bDeviceClass; ///< Class code (assigned by the USB-IF). + uint8_t bDeviceSubClass; ///< Subclass code (assigned by the USB-IF). + uint8_t bDeviceProtocol; ///< Protocol code (assigned by the USB-IF). + uint8_t bMaxPacketSize0; ///< Maximum packet size for endpoint zero (only 8, 16, 32, or 64 are valid). For HS devices + ///< is fixed to 64. + uint16_t idVendor; ///< Vendor ID (assigned by the USB-IF). + uint16_t idProduct; ///< Product ID (assigned by the manufacturer). + uint16_t bcdDevice; ///< Device release number in binary-coded decimal. + uint8_t iManufacturer; ///< Index of string descriptor describing manufacturer. + uint8_t iProduct; ///< Index of string descriptor describing product. + uint8_t iSerialNumber; ///< Index of string descriptor describing the device's serial number. + uint8_t bNumConfigurations; ///< Number of possible configurations. } tusb_desc_device_t; -TU_VERIFY_STATIC( sizeof(tusb_desc_device_t) == 18u, "size is not correct"); +TU_VERIFY_STATIC(sizeof(tusb_desc_device_t) == 18u, "size is not correct"); // USB Binary Device Object Store (BOS) Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength ; ///< Size of this descriptor in bytes - uint8_t bDescriptorType ; ///< CONFIGURATION Descriptor Type - uint16_t wTotalLength ; ///< Total length of data returned for this descriptor - uint8_t bNumDeviceCaps ; ///< Number of device capability descriptors in the BOS + uint8_t bLength; ///< Size of this descriptor in bytes + uint8_t bDescriptorType; ///< CONFIGURATION Descriptor Type + uint16_t wTotalLength; ///< Total length of data returned for this descriptor + uint8_t bNumDeviceCaps; ///< Number of device capability descriptors in the BOS } tusb_desc_bos_t; -TU_VERIFY_STATIC( sizeof(tusb_desc_bos_t) == 5u, "size is not correct"); +TU_VERIFY_STATIC(sizeof(tusb_desc_bos_t) == 5u, "size is not correct"); /// USB Configuration Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength ; ///< Size of this descriptor in bytes - uint8_t bDescriptorType ; ///< CONFIGURATION Descriptor Type - uint16_t wTotalLength ; ///< Total length of data returned for this configuration. Includes the combined length of all descriptors (configuration, interface, endpoint, and class- or vendor-specific) returned for this configuration. + uint8_t bLength; ///< Size of this descriptor in bytes + uint8_t bDescriptorType; ///< CONFIGURATION Descriptor Type + uint16_t wTotalLength; ///< Total length of data returned for this configuration. Includes the combined length of all + ///< descriptors (configuration, interface, endpoint, and class- or vendor-specific) returned + ///< for this configuration. - uint8_t bNumInterfaces ; ///< Number of interfaces supported by this configuration - uint8_t bConfigurationValue ; ///< Value to use as an argument to the SetConfiguration() request to select this configuration. - uint8_t iConfiguration ; ///< Index of string descriptor describing this configuration - uint8_t bmAttributes ; ///< Configuration characteristics \n D7: Reserved (set to one)\n D6: Self-powered \n D5: Remote Wakeup \n D4...0: Reserved (reset to zero) \n D7 is reserved and must be set to one for historical reasons. \n A device configuration that uses power from the bus and a local source reports a non-zero value in bMaxPower to indicate the amount of bus power required and sets D6. The actual power source at runtime may be determined using the GetStatus(DEVICE) request (see USB 2.0 spec Section 9.4.5). \n If a device configuration supports remote wakeup, D5 is set to one. - uint8_t bMaxPower ; ///< Maximum power consumption of the USB device from the bus in this specific configuration when the device is fully operational. Expressed in 2 mA units (i.e., 50 = 100 mA). + uint8_t bNumInterfaces; ///< Number of interfaces supported by this configuration + uint8_t bConfigurationValue; ///< Value to use as an argument to the SetConfiguration() request to select this + ///< configuration. + uint8_t iConfiguration; ///< Index of string descriptor describing this configuration + uint8_t bmAttributes; ///< Configuration characteristics \n D7: Reserved (set to one)\n D6: Self-powered \n D5: Remote + ///< Wakeup \n D4...0: Reserved (reset to zero) \n D7 is reserved and must be set to one for + ///< historical reasons. \n A device configuration that uses power from the bus and a local + ///< source reports a non-zero value in bMaxPower to indicate the amount of bus power required + ///< and sets D6. The actual power source at runtime may be determined using the + ///< GetStatus(DEVICE) request (see USB 2.0 spec Section 9.4.5). \n If a device configuration + ///< supports remote wakeup, D5 is set to one. + uint8_t bMaxPower; ///< Maximum power consumption of the USB device from the bus in this specific configuration when + ///< the device is fully operational. Expressed in 2 mA units (i.e., 50 = 100 mA). } tusb_desc_configuration_t; -TU_VERIFY_STATIC( sizeof(tusb_desc_configuration_t) == 9u, "size is not correct"); +TU_VERIFY_STATIC(sizeof(tusb_desc_configuration_t) == 9u, "size is not correct"); /// USB Interface Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength ; ///< Size of this descriptor in bytes - uint8_t bDescriptorType ; ///< INTERFACE Descriptor Type + uint8_t bLength; ///< Size of this descriptor in bytes + uint8_t bDescriptorType; ///< INTERFACE Descriptor Type - uint8_t bInterfaceNumber ; ///< Number of this interface. Zero-based value identifying the index in the array of concurrent interfaces supported by this configuration. - uint8_t bAlternateSetting ; ///< Value used to select this alternate setting for the interface identified in the prior field - uint8_t bNumEndpoints ; ///< Number of endpoints used by this interface (excluding endpoint zero). If this value is zero, this interface only uses the Default Control Pipe. - uint8_t bInterfaceClass ; ///< Class code (assigned by the USB-IF). \li A value of zero is reserved for future standardization. \li If this field is set to FFH, the interface class is vendor-specific. \li All other values are reserved for assignment by the USB-IF. - uint8_t bInterfaceSubClass ; ///< Subclass code (assigned by the USB-IF). \n These codes are qualified by the value of the bInterfaceClass field. \li If the bInterfaceClass field is reset to zero, this field must also be reset to zero. \li If the bInterfaceClass field is not set to FFH, all values are reserved for assignment by the USB-IF. - uint8_t bInterfaceProtocol ; ///< Protocol code (assigned by the USB). \n These codes are qualified by the value of the bInterfaceClass and the bInterfaceSubClass fields. If an interface supports class-specific requests, this code identifies the protocols that the device uses as defined by the specification of the device class. \li If this field is reset to zero, the device does not use a class-specific protocol on this interface. \li If this field is set to FFH, the device uses a vendor-specific protocol for this interface. - uint8_t iInterface ; ///< Index of string descriptor describing this interface + uint8_t bInterfaceNumber; ///< Number of this interface. Zero-based value identifying the index in the array of + ///< concurrent interfaces supported by this configuration. + uint8_t + bAlternateSetting; ///< Value used to select this alternate setting for the interface identified in the prior field + uint8_t bNumEndpoints; ///< Number of endpoints used by this interface (excluding endpoint zero). If this value is + ///< zero, this interface only uses the Default Control Pipe. + uint8_t bInterfaceClass; ///< Class code (assigned by the USB-IF). \li A value of zero is reserved for future + ///< standardization. \li If this field is set to FFH, the interface class is + ///< vendor-specific. \li All other values are reserved for assignment by the USB-IF. + uint8_t bInterfaceSubClass; ///< Subclass code (assigned by the USB-IF). \n These codes are qualified by the value of + ///< the bInterfaceClass field. \li If the bInterfaceClass field is reset to zero, this + ///< field must also be reset to zero. \li If the bInterfaceClass field is not set to FFH, + ///< all values are reserved for assignment by the USB-IF. + uint8_t bInterfaceProtocol; ///< Protocol code (assigned by the USB). \n These codes are qualified by the value of the + ///< bInterfaceClass and the bInterfaceSubClass fields. If an interface supports + ///< class-specific requests, this code identifies the protocols that the device uses as + ///< defined by the specification of the device class. \li If this field is reset to zero, + ///< the device does not use a class-specific protocol on this interface. \li If this + ///< field is set to FFH, the device uses a vendor-specific protocol for this interface. + uint8_t iInterface; ///< Index of string descriptor describing this interface } tusb_desc_interface_t; -TU_VERIFY_STATIC( sizeof(tusb_desc_interface_t) == 9u, "size is not correct"); +TU_VERIFY_STATIC(sizeof(tusb_desc_interface_t) == 9u, "size is not correct"); /// USB Endpoint Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength ; // Size of this descriptor in bytes - uint8_t bDescriptorType ; // ENDPOINT Descriptor Type + uint8_t bLength; // Size of this descriptor in bytes + uint8_t bDescriptorType; // ENDPOINT Descriptor Type - uint8_t bEndpointAddress ; // The address of the endpoint + uint8_t bEndpointAddress; // The address of the endpoint struct TU_ATTR_PACKED { #if (TU_BITFIELD_ORDER == TU_BITFIELD_LE) - uint8_t xfer : 2; // Control, ISO, Bulk, Interrupt - uint8_t sync : 2; // None, Asynchronous, Adaptive, Synchronous - uint8_t usage : 2; // Data, Feedback, Implicit feedback + uint8_t xfer : 2; // Control, ISO, Bulk, Interrupt + uint8_t sync : 2; // None, Asynchronous, Adaptive, Synchronous + uint8_t usage : 2; // Data, Feedback, Implicit feedback uint8_t : 2; #elif (TU_BITFIELD_ORDER == TU_BITFIELD_BE) uint8_t : 2; @@ -414,70 +443,70 @@ typedef struct TU_ATTR_PACKED { #endif } bmAttributes; - uint16_t wMaxPacketSize ; // Bit 10..0 : max packet size, bit 12..11 additional transaction per highspeed micro-frame - uint8_t bInterval ; // Polling interval, in frames or microframes depending on the operating speed + uint16_t wMaxPacketSize; // Bit 10..0 : max packet size, bit 12..11 additional transaction per highspeed micro-frame + uint8_t bInterval; // Polling interval, in frames or microframes depending on the operating speed } tusb_desc_endpoint_t; -TU_VERIFY_STATIC( sizeof(tusb_desc_endpoint_t) == 7u, "size is not correct"); +TU_VERIFY_STATIC(sizeof(tusb_desc_endpoint_t) == 7u, "size is not correct"); /// USB Other Speed Configuration Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength ; ///< Size of descriptor - uint8_t bDescriptorType ; ///< Other_speed_Configuration Type - uint16_t wTotalLength ; ///< Total length of data returned + uint8_t bLength; ///< Size of descriptor + uint8_t bDescriptorType; ///< Other_speed_Configuration Type + uint16_t wTotalLength; ///< Total length of data returned - uint8_t bNumInterfaces ; ///< Number of interfaces supported by this speed configuration - uint8_t bConfigurationValue ; ///< Value to use to select configuration - uint8_t iConfiguration ; ///< Index of string descriptor - uint8_t bmAttributes ; ///< Same as Configuration descriptor - uint8_t bMaxPower ; ///< Same as Configuration descriptor + uint8_t bNumInterfaces; ///< Number of interfaces supported by this speed configuration + uint8_t bConfigurationValue; ///< Value to use to select configuration + uint8_t iConfiguration; ///< Index of string descriptor + uint8_t bmAttributes; ///< Same as Configuration descriptor + uint8_t bMaxPower; ///< Same as Configuration descriptor } tusb_desc_other_speed_t; /// USB Device Qualifier Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength ; ///< Size of descriptor - uint8_t bDescriptorType ; ///< Device Qualifier Type - uint16_t bcdUSB ; ///< USB specification version number (e.g., 0200H for V2.00) + uint8_t bLength; ///< Size of descriptor + uint8_t bDescriptorType; ///< Device Qualifier Type + uint16_t bcdUSB; ///< USB specification version number (e.g., 0200H for V2.00) - uint8_t bDeviceClass ; ///< Class Code - uint8_t bDeviceSubClass ; ///< SubClass Code - uint8_t bDeviceProtocol ; ///< Protocol Code + uint8_t bDeviceClass; ///< Class Code + uint8_t bDeviceSubClass; ///< SubClass Code + uint8_t bDeviceProtocol; ///< Protocol Code - uint8_t bMaxPacketSize0 ; ///< Maximum packet size for other speed - uint8_t bNumConfigurations ; ///< Number of Other-speed Configurations - uint8_t bReserved ; ///< Reserved for future use, must be zero + uint8_t bMaxPacketSize0; ///< Maximum packet size for other speed + uint8_t bNumConfigurations; ///< Number of Other-speed Configurations + uint8_t bReserved; ///< Reserved for future use, must be zero } tusb_desc_device_qualifier_t; -TU_VERIFY_STATIC( sizeof(tusb_desc_device_qualifier_t) == 10u, "size is not correct"); +TU_VERIFY_STATIC(sizeof(tusb_desc_device_qualifier_t) == 10u, "size is not correct"); /// USB Interface Association Descriptor (IAD ECN) typedef struct TU_ATTR_PACKED { - uint8_t bLength ; ///< Size of descriptor - uint8_t bDescriptorType ; ///< Other_speed_Configuration Type + uint8_t bLength; ///< Size of descriptor + uint8_t bDescriptorType; ///< Other_speed_Configuration Type - uint8_t bFirstInterface ; ///< Index of the first associated interface. - uint8_t bInterfaceCount ; ///< Total number of associated interfaces. + uint8_t bFirstInterface; ///< Index of the first associated interface. + uint8_t bInterfaceCount; ///< Total number of associated interfaces. - uint8_t bFunctionClass ; ///< Interface class ID. - uint8_t bFunctionSubClass ; ///< Interface subclass ID. - uint8_t bFunctionProtocol ; ///< Interface protocol ID. + uint8_t bFunctionClass; ///< Interface class ID. + uint8_t bFunctionSubClass; ///< Interface subclass ID. + uint8_t bFunctionProtocol; ///< Interface protocol ID. - uint8_t iFunction ; ///< Index of the string descriptor describing the interface association. + uint8_t iFunction; ///< Index of the string descriptor describing the interface association. } tusb_desc_interface_assoc_t; -TU_VERIFY_STATIC( sizeof(tusb_desc_interface_assoc_t) == 8u, "size is not correct"); +TU_VERIFY_STATIC(sizeof(tusb_desc_interface_assoc_t) == 8u, "size is not correct"); // USB String Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength ; ///< Size of this descriptor in bytes - uint8_t bDescriptorType ; ///< Descriptor Type + uint8_t bLength; ///< Size of this descriptor in bytes + uint8_t bDescriptorType; ///< Descriptor Type uint16_t utf16le[]; } tusb_desc_string_t; // USB Binary Device Object Store (BOS) typedef struct TU_ATTR_PACKED { uint8_t bLength; - uint8_t bDescriptorType ; + uint8_t bDescriptorType; uint8_t bDevCapabilityType; uint8_t bReserved; uint8_t PlatformCapabilityUUID[16]; @@ -494,8 +523,8 @@ typedef struct TU_ATTR_PACKED { // DFU Functional Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; - uint8_t bDescriptorType; + uint8_t bLength; + uint8_t bDescriptorType; union { struct TU_ATTR_PACKED { @@ -522,13 +551,13 @@ typedef struct TU_ATTR_PACKED { union { struct TU_ATTR_PACKED { #if (TU_BITFIELD_ORDER == TU_BITFIELD_LE) - uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. - uint8_t type : 2; ///< Request type tusb_request_type_t. - uint8_t direction : 1; ///< Direction type. tusb_dir_t + uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. + uint8_t type : 2; ///< Request type tusb_request_type_t. + uint8_t direction : 1; ///< Direction type. tusb_dir_t #elif (TU_BITFIELD_ORDER == TU_BITFIELD_BE) - uint8_t direction : 1; ///< Direction type. tusb_dir_t - uint8_t type : 2; ///< Request type tusb_request_type_t. - uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. + uint8_t direction : 1; ///< Direction type. tusb_dir_t + uint8_t type : 2; ///< Request type tusb_request_type_t. + uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. #else #error "Please define TU_BITFIELD_ORDER as TU_BITFIELD_LE or TU_BITFIELD_BE" #endif @@ -543,42 +572,43 @@ typedef struct TU_ATTR_PACKED { uint16_t wLength; } tusb_control_request_t; -TU_VERIFY_STATIC( sizeof(tusb_control_request_t) == 8u, "size is not correct"); +TU_VERIFY_STATIC(sizeof(tusb_control_request_t) == 8u, "size is not correct"); -TU_ATTR_PACKED_END // End of all packed definitions -TU_ATTR_BIT_FIELD_ORDER_END +TU_ATTR_PACKED_END // End of all packed definitions + TU_ATTR_BIT_FIELD_ORDER_END -//--------------------------------------------------------------------+ -// Endpoint helper -//--------------------------------------------------------------------+ + //--------------------------------------------------------------------+ + // Endpoint helper + //--------------------------------------------------------------------+ -// Get direction from Endpoint address -TU_ATTR_ALWAYS_INLINE static inline tusb_dir_t tu_edpt_dir(uint8_t addr) { + // Get direction from Endpoint address + TU_ATTR_ALWAYS_INLINE static inline tusb_dir_t + tu_edpt_dir(uint8_t addr) { return (addr & TUSB_DIR_IN_MASK) ? TUSB_DIR_IN : TUSB_DIR_OUT; } // Get Endpoint number from address TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_edpt_number(uint8_t addr) { - return (uint8_t) (addr & TUSB_EPNUM_MASK); + return (uint8_t)(addr & TUSB_EPNUM_MASK); } TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_edpt_addr(uint8_t num, uint8_t dir) { - return (uint8_t) (num | (dir == (uint8_t)TUSB_DIR_IN ? (uint8_t)TUSB_DIR_IN_MASK : 0u)); + return (uint8_t)(num | (dir == (uint8_t)TUSB_DIR_IN ? (uint8_t)TUSB_DIR_IN_MASK : 0u)); } -TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_edpt_packet_size(tusb_desc_endpoint_t const* desc_ep) { +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_edpt_packet_size(const tusb_desc_endpoint_t *desc_ep) { return tu_le16toh(desc_ep->wMaxPacketSize) & 0x7FF; } #if CFG_TUSB_DEBUG TU_ATTR_ALWAYS_INLINE static inline const char *tu_edpt_type_str(tusb_xfer_type_t t) { - tu_static const char *str[] = {"control", "isochronous", "bulk", "interrupt"}; + const tu_static char *str[] = {"control", "isochronous", "bulk", "interrupt"}; return str[t]; } #endif #ifdef __cplusplus - } +} #endif #endif // TUSB_TYPES_H_ From ff0683d5f827a0db546f026a16abfa48b8dc67e1 Mon Sep 17 00:00:00 2001 From: TenGui Date: Thu, 23 Jul 2026 15:59:12 -0700 Subject: [PATCH 03/10] save without formatting --- src/common/tusb_types.h | 408 +++++++++++++++++++--------------------- 1 file changed, 189 insertions(+), 219 deletions(-) diff --git a/src/common/tusb_types.h b/src/common/tusb_types.h index 5a91d6182..d0796ccc8 100644 --- a/src/common/tusb_types.h +++ b/src/common/tusb_types.h @@ -13,49 +13,43 @@ #include "tusb_compiler.h" #ifdef __cplusplus -extern "C" { + extern "C" { #endif //------------- Device DCache declaration -------------// -#define TUD_EPBUF_DCACHE_SIZE(_size) \ - (CFG_TUD_MEM_DCACHE_ENABLE ? (TU_DIV_CEIL(_size, CFG_TUD_MEM_DCACHE_LINE_SIZE) * CFG_TUD_MEM_DCACHE_LINE_SIZE) \ - : (_size)) +#define TUD_EPBUF_DCACHE_SIZE(_size) (CFG_TUD_MEM_DCACHE_ENABLE ? \ + (TU_DIV_CEIL(_size, CFG_TUD_MEM_DCACHE_LINE_SIZE) * CFG_TUD_MEM_DCACHE_LINE_SIZE) : (_size)) // Declare an endpoint buffer with uint8_t[size] -#define TUD_EPBUF_DEF(_name, _size) \ - union { \ - CFG_TUD_MEM_ALIGN uint8_t _name[_size]; \ - TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) \ - uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(_size)]; \ +#define TUD_EPBUF_DEF(_name, _size) \ + union { \ + CFG_TUD_MEM_ALIGN uint8_t _name[_size]; \ + TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(_size)]; \ } // Declare an endpoint buffer with a type -#define TUD_EPBUF_TYPE_DEF(_type, _name) \ - union { \ - CFG_TUD_MEM_ALIGN _type _name; \ - TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) \ - uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ +#define TUD_EPBUF_TYPE_DEF(_type, _name) \ + union { \ + CFG_TUD_MEM_ALIGN _type _name; \ + TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ } //------------- Host DCache declaration -------------// -#define TUH_EPBUF_DCACHE_SIZE(_size) \ - (CFG_TUH_MEM_DCACHE_ENABLE ? (TU_DIV_CEIL(_size, CFG_TUH_MEM_DCACHE_LINE_SIZE) * CFG_TUH_MEM_DCACHE_LINE_SIZE) \ - : (_size)) +#define TUH_EPBUF_DCACHE_SIZE(_size) (CFG_TUH_MEM_DCACHE_ENABLE ? \ + (TU_DIV_CEIL(_size, CFG_TUH_MEM_DCACHE_LINE_SIZE) * CFG_TUH_MEM_DCACHE_LINE_SIZE) : (_size)) // Declare an endpoint buffer with uint8_t[size] -#define TUH_EPBUF_DEF(_name, _size) \ - union { \ - CFG_TUH_MEM_ALIGN uint8_t _name[_size]; \ - TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) \ - uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(_size)]; \ +#define TUH_EPBUF_DEF(_name, _size) \ + union { \ + CFG_TUH_MEM_ALIGN uint8_t _name[_size]; \ + TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(_size)]; \ } // Declare an endpoint buffer with a type -#define TUH_EPBUF_TYPE_DEF(_type, _name) \ - union { \ - CFG_TUH_MEM_ALIGN _type _name; \ - TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) \ - uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ +#define TUH_EPBUF_TYPE_DEF(_type, _name) \ + union { \ + CFG_TUH_MEM_ALIGN _type _name; \ + TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ } @@ -71,10 +65,10 @@ typedef enum { /// defined base on EHCI specs value for Endpoint Speed typedef enum { - TUSB_SPEED_FULL = 0, - TUSB_SPEED_LOW = 1, - TUSB_SPEED_HIGH = 2, - TUSB_SPEED_AUTO = 0xaa, + TUSB_SPEED_FULL = 0, + TUSB_SPEED_LOW = 1, + TUSB_SPEED_HIGH = 2, + TUSB_SPEED_AUTO = 0xaa, TUSB_SPEED_INVALID = 0xff, } tusb_speed_t; @@ -105,18 +99,18 @@ enum { }; // Endpoint Bulk size depending on host/device max speed -#define TUD_EPSIZE_BULK_MAX (TUD_OPT_HIGH_SPEED ? 512 : 64) -#define TUH_EPSIZE_BULK_MAX (TUH_OPT_HIGH_SPEED ? 512 : 64) +#define TUD_EPSIZE_BULK_MAX (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define TUH_EPSIZE_BULK_MAX (TUH_OPT_HIGH_SPEED ? 512 : 64) /// Isochronous Endpoint Attributes typedef enum { - TUSB_ISO_EP_ATT_NO_SYNC = 0x00, - TUSB_ISO_EP_ATT_ASYNCHRONOUS = 0x04, - TUSB_ISO_EP_ATT_ADAPTIVE = 0x08, - TUSB_ISO_EP_ATT_SYNCHRONOUS = 0x0C, - TUSB_ISO_EP_ATT_DATA = 0x00, ///< Data End Point - TUSB_ISO_EP_ATT_EXPLICIT_FB = 0x10, ///< Feedback End Point - TUSB_ISO_EP_ATT_IMPLICIT_FB = 0x20, ///< Data endpoint that also serves as an implicit feedback + TUSB_ISO_EP_ATT_NO_SYNC = 0x00, + TUSB_ISO_EP_ATT_ASYNCHRONOUS = 0x04, + TUSB_ISO_EP_ATT_ADAPTIVE = 0x08, + TUSB_ISO_EP_ATT_SYNCHRONOUS = 0x0C, + TUSB_ISO_EP_ATT_DATA = 0x00, ///< Data End Point + TUSB_ISO_EP_ATT_EXPLICIT_FB = 0x10, ///< Feedback End Point + TUSB_ISO_EP_ATT_IMPLICIT_FB = 0x20, ///< Data endpoint that also serves as an implicit feedback } tusb_iso_ep_attribute_t; /// USB Descriptor Types @@ -133,35 +127,35 @@ typedef enum { TUSB_DESC_DEBUG = 0x0A, TUSB_DESC_INTERFACE_ASSOCIATION = 0x0B, - TUSB_DESC_BOS = 0x0F, - TUSB_DESC_DEVICE_CAPABILITY = 0x10, + TUSB_DESC_BOS = 0x0F, + TUSB_DESC_DEVICE_CAPABILITY = 0x10, - TUSB_DESC_FUNCTIONAL = 0x21, + TUSB_DESC_FUNCTIONAL = 0x21, // Class Specific Descriptor - TUSB_DESC_CS_DEVICE = 0x21, - TUSB_DESC_CS_CONFIGURATION = 0x22, - TUSB_DESC_CS_STRING = 0x23, - TUSB_DESC_CS_INTERFACE = 0x24, - TUSB_DESC_CS_ENDPOINT = 0x25, + TUSB_DESC_CS_DEVICE = 0x21, + TUSB_DESC_CS_CONFIGURATION = 0x22, + TUSB_DESC_CS_STRING = 0x23, + TUSB_DESC_CS_INTERFACE = 0x24, + TUSB_DESC_CS_ENDPOINT = 0x25, TUSB_DESC_SUPERSPEED_ENDPOINT_COMPANION = 0x30, TUSB_DESC_SUPERSPEED_ISO_ENDPOINT_COMPANION = 0x31 } tusb_desc_type_t; typedef enum { - TUSB_REQ_GET_STATUS = 0, - TUSB_REQ_CLEAR_FEATURE = 1, - TUSB_REQ_RESERVED = 2, - TUSB_REQ_SET_FEATURE = 3, - TUSB_REQ_RESERVED2 = 4, - TUSB_REQ_SET_ADDRESS = 5, - TUSB_REQ_GET_DESCRIPTOR = 6, - TUSB_REQ_SET_DESCRIPTOR = 7, - TUSB_REQ_GET_CONFIGURATION = 8, - TUSB_REQ_SET_CONFIGURATION = 9, - TUSB_REQ_GET_INTERFACE = 10, - TUSB_REQ_SET_INTERFACE = 11, + TUSB_REQ_GET_STATUS = 0 , + TUSB_REQ_CLEAR_FEATURE = 1 , + TUSB_REQ_RESERVED = 2 , + TUSB_REQ_SET_FEATURE = 3 , + TUSB_REQ_RESERVED2 = 4 , + TUSB_REQ_SET_ADDRESS = 5 , + TUSB_REQ_GET_DESCRIPTOR = 6 , + TUSB_REQ_SET_DESCRIPTOR = 7 , + TUSB_REQ_GET_CONFIGURATION = 8 , + TUSB_REQ_SET_CONFIGURATION = 9 , + TUSB_REQ_GET_INTERFACE = 10 , + TUSB_REQ_SET_INTERFACE = 11 , TUSB_REQ_SYNCH_FRAME = 12 } tusb_request_code_t; @@ -179,7 +173,7 @@ typedef enum { } tusb_request_type_t; typedef enum { - TUSB_REQ_RCPT_DEVICE = 0, + TUSB_REQ_RCPT_DEVICE =0, TUSB_REQ_RCPT_INTERFACE, TUSB_REQ_RCPT_ENDPOINT, TUSB_REQ_RCPT_OTHER @@ -187,41 +181,42 @@ typedef enum { // https://www.usb.org/defined-class-codes typedef enum { - TUSB_CLASS_UNSPECIFIED = 0, - TUSB_CLASS_AUDIO = 1, - TUSB_CLASS_CDC = 2, - TUSB_CLASS_HID = 3, - TUSB_CLASS_RESERVED_4 = 4, - TUSB_CLASS_PHYSICAL = 5, - TUSB_CLASS_IMAGE = 6, - TUSB_CLASS_PRINTER = 7, - TUSB_CLASS_MSC = 8, - TUSB_CLASS_HUB = 9, - TUSB_CLASS_CDC_DATA = 10, - TUSB_CLASS_SMART_CARD = 11, - TUSB_CLASS_RESERVED_12 = 12, - TUSB_CLASS_CONTENT_SECURITY = 13, - TUSB_CLASS_VIDEO = 14, - TUSB_CLASS_PERSONAL_HEALTHCARE = 15, - TUSB_CLASS_AUDIO_VIDEO = 16, + TUSB_CLASS_UNSPECIFIED = 0 , + TUSB_CLASS_AUDIO = 1 , + TUSB_CLASS_CDC = 2 , + TUSB_CLASS_HID = 3 , + TUSB_CLASS_RESERVED_4 = 4 , + TUSB_CLASS_PHYSICAL = 5 , + TUSB_CLASS_IMAGE = 6 , + TUSB_CLASS_PRINTER = 7 , + TUSB_CLASS_MSC = 8 , + TUSB_CLASS_HUB = 9 , + TUSB_CLASS_CDC_DATA = 10 , + TUSB_CLASS_SMART_CARD = 11 , + TUSB_CLASS_RESERVED_12 = 12 , + TUSB_CLASS_CONTENT_SECURITY = 13 , + TUSB_CLASS_VIDEO = 14 , + TUSB_CLASS_PERSONAL_HEALTHCARE = 15 , + TUSB_CLASS_AUDIO_VIDEO = 16 , - TUSB_CLASS_DIAGNOSTIC = 0xDC, - TUSB_CLASS_WIRELESS_CONTROLLER = 0xE0, - TUSB_CLASS_MISC = 0xEF, - TUSB_CLASS_APPLICATION_SPECIFIC = 0xFE, + TUSB_CLASS_DIAGNOSTIC = 0xDC , + TUSB_CLASS_WIRELESS_CONTROLLER = 0xE0 , + TUSB_CLASS_MISC = 0xEF , + TUSB_CLASS_APPLICATION_SPECIFIC = 0xFE , TUSB_CLASS_VENDOR_SPECIFIC = 0xFF } tusb_class_code_t; -typedef enum { +typedef enum +{ MISC_SUBCLASS_COMMON = 2 -} misc_subclass_type_t; +}misc_subclass_type_t; typedef enum { MISC_PROTOCOL_IAD = 1 } misc_protocol_type_t; typedef enum { - APP_SUBCLASS_USBTMC = 0x03, + APP_SUBCLASS_USBTMC = 0x03, APP_SUBCLASS_DFU_RUNTIME = 0x01 } app_subclass_type_t; @@ -249,14 +244,14 @@ enum { TUSB_DESC_CONFIG_ATT_SELF_POWERED = 1 << 6, }; -#define TUSB_DESC_CONFIG_POWER_MA(x) (uint8_t)((x) / 2) +#define TUSB_DESC_CONFIG_POWER_MA(x) (uint8_t)((x)/2) // USB 2.0 Spec Table 9-7: Test Mode Selectors typedef enum { - TUSB_FEATURE_TEST_J = 1, - TUSB_FEATURE_TEST_K = 2, - TUSB_FEATURE_TEST_SE0_NAK = 3, - TUSB_FEATURE_TEST_PACKET = 4, + TUSB_FEATURE_TEST_J = 1, + TUSB_FEATURE_TEST_K = 2, + TUSB_FEATURE_TEST_SE0_NAK = 3, + TUSB_FEATURE_TEST_PACKET = 4, TUSB_FEATURE_TEST_FORCE_ENABLE = 5, } tusb_feature_test_mode_t; @@ -276,8 +271,8 @@ typedef enum { // TODO remove enum { - DESC_OFFSET_LEN = 0, - DESC_OFFSET_TYPE = 1, + DESC_OFFSET_LEN = 0, + DESC_OFFSET_TYPE = 1, DESC_OFFSET_SUBTYPE = 2 }; @@ -309,8 +304,8 @@ enum { }; enum { - TU_EP0_OUT = 0x00, - TU_EP0_IN = 0x80 + TU_EP0_OUT = 0x00, + TU_EP0_IN = 0x80 }; @@ -318,7 +313,7 @@ enum { // //--------------------------------------------------------------------+ typedef struct { - tusb_role_t role; + tusb_role_t role; tusb_speed_t speed; } tusb_rhport_init_t; @@ -337,101 +332,77 @@ TU_ATTR_BIT_FIELD_ORDER_BEGIN /// USB Device Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; ///< Size of this descriptor in bytes. - uint8_t bDescriptorType; ///< DEVICE Descriptor Type. - uint16_t bcdUSB; ///< BUSB Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H). - uint8_t bDeviceClass; ///< Class code (assigned by the USB-IF). - uint8_t bDeviceSubClass; ///< Subclass code (assigned by the USB-IF). - uint8_t bDeviceProtocol; ///< Protocol code (assigned by the USB-IF). - uint8_t bMaxPacketSize0; ///< Maximum packet size for endpoint zero (only 8, 16, 32, or 64 are valid). For HS devices - ///< is fixed to 64. - uint16_t idVendor; ///< Vendor ID (assigned by the USB-IF). - uint16_t idProduct; ///< Product ID (assigned by the manufacturer). - uint16_t bcdDevice; ///< Device release number in binary-coded decimal. - uint8_t iManufacturer; ///< Index of string descriptor describing manufacturer. - uint8_t iProduct; ///< Index of string descriptor describing product. - uint8_t iSerialNumber; ///< Index of string descriptor describing the device's serial number. - uint8_t bNumConfigurations; ///< Number of possible configurations. + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< DEVICE Descriptor Type. + uint16_t bcdUSB ; ///< BUSB Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H). + uint8_t bDeviceClass ; ///< Class code (assigned by the USB-IF). + uint8_t bDeviceSubClass ; ///< Subclass code (assigned by the USB-IF). + uint8_t bDeviceProtocol ; ///< Protocol code (assigned by the USB-IF). + uint8_t bMaxPacketSize0 ; ///< Maximum packet size for endpoint zero (only 8, 16, 32, or 64 are valid). For HS devices is fixed to 64. + uint16_t idVendor ; ///< Vendor ID (assigned by the USB-IF). + uint16_t idProduct ; ///< Product ID (assigned by the manufacturer). + uint16_t bcdDevice ; ///< Device release number in binary-coded decimal. + uint8_t iManufacturer ; ///< Index of string descriptor describing manufacturer. + uint8_t iProduct ; ///< Index of string descriptor describing product. + uint8_t iSerialNumber ; ///< Index of string descriptor describing the device's serial number. + uint8_t bNumConfigurations ; ///< Number of possible configurations. } tusb_desc_device_t; -TU_VERIFY_STATIC(sizeof(tusb_desc_device_t) == 18u, "size is not correct"); +TU_VERIFY_STATIC( sizeof(tusb_desc_device_t) == 18u, "size is not correct"); // USB Binary Device Object Store (BOS) Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; ///< Size of this descriptor in bytes - uint8_t bDescriptorType; ///< CONFIGURATION Descriptor Type - uint16_t wTotalLength; ///< Total length of data returned for this descriptor - uint8_t bNumDeviceCaps; ///< Number of device capability descriptors in the BOS + uint8_t bLength ; ///< Size of this descriptor in bytes + uint8_t bDescriptorType ; ///< CONFIGURATION Descriptor Type + uint16_t wTotalLength ; ///< Total length of data returned for this descriptor + uint8_t bNumDeviceCaps ; ///< Number of device capability descriptors in the BOS } tusb_desc_bos_t; -TU_VERIFY_STATIC(sizeof(tusb_desc_bos_t) == 5u, "size is not correct"); +TU_VERIFY_STATIC( sizeof(tusb_desc_bos_t) == 5u, "size is not correct"); /// USB Configuration Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; ///< Size of this descriptor in bytes - uint8_t bDescriptorType; ///< CONFIGURATION Descriptor Type - uint16_t wTotalLength; ///< Total length of data returned for this configuration. Includes the combined length of all - ///< descriptors (configuration, interface, endpoint, and class- or vendor-specific) returned - ///< for this configuration. + uint8_t bLength ; ///< Size of this descriptor in bytes + uint8_t bDescriptorType ; ///< CONFIGURATION Descriptor Type + uint16_t wTotalLength ; ///< Total length of data returned for this configuration. Includes the combined length of all descriptors (configuration, interface, endpoint, and class- or vendor-specific) returned for this configuration. - uint8_t bNumInterfaces; ///< Number of interfaces supported by this configuration - uint8_t bConfigurationValue; ///< Value to use as an argument to the SetConfiguration() request to select this - ///< configuration. - uint8_t iConfiguration; ///< Index of string descriptor describing this configuration - uint8_t bmAttributes; ///< Configuration characteristics \n D7: Reserved (set to one)\n D6: Self-powered \n D5: Remote - ///< Wakeup \n D4...0: Reserved (reset to zero) \n D7 is reserved and must be set to one for - ///< historical reasons. \n A device configuration that uses power from the bus and a local - ///< source reports a non-zero value in bMaxPower to indicate the amount of bus power required - ///< and sets D6. The actual power source at runtime may be determined using the - ///< GetStatus(DEVICE) request (see USB 2.0 spec Section 9.4.5). \n If a device configuration - ///< supports remote wakeup, D5 is set to one. - uint8_t bMaxPower; ///< Maximum power consumption of the USB device from the bus in this specific configuration when - ///< the device is fully operational. Expressed in 2 mA units (i.e., 50 = 100 mA). + uint8_t bNumInterfaces ; ///< Number of interfaces supported by this configuration + uint8_t bConfigurationValue ; ///< Value to use as an argument to the SetConfiguration() request to select this configuration. + uint8_t iConfiguration ; ///< Index of string descriptor describing this configuration + uint8_t bmAttributes ; ///< Configuration characteristics \n D7: Reserved (set to one)\n D6: Self-powered \n D5: Remote Wakeup \n D4...0: Reserved (reset to zero) \n D7 is reserved and must be set to one for historical reasons. \n A device configuration that uses power from the bus and a local source reports a non-zero value in bMaxPower to indicate the amount of bus power required and sets D6. The actual power source at runtime may be determined using the GetStatus(DEVICE) request (see USB 2.0 spec Section 9.4.5). \n If a device configuration supports remote wakeup, D5 is set to one. + uint8_t bMaxPower ; ///< Maximum power consumption of the USB device from the bus in this specific configuration when the device is fully operational. Expressed in 2 mA units (i.e., 50 = 100 mA). } tusb_desc_configuration_t; -TU_VERIFY_STATIC(sizeof(tusb_desc_configuration_t) == 9u, "size is not correct"); +TU_VERIFY_STATIC( sizeof(tusb_desc_configuration_t) == 9u, "size is not correct"); /// USB Interface Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; ///< Size of this descriptor in bytes - uint8_t bDescriptorType; ///< INTERFACE Descriptor Type + uint8_t bLength ; ///< Size of this descriptor in bytes + uint8_t bDescriptorType ; ///< INTERFACE Descriptor Type - uint8_t bInterfaceNumber; ///< Number of this interface. Zero-based value identifying the index in the array of - ///< concurrent interfaces supported by this configuration. - uint8_t - bAlternateSetting; ///< Value used to select this alternate setting for the interface identified in the prior field - uint8_t bNumEndpoints; ///< Number of endpoints used by this interface (excluding endpoint zero). If this value is - ///< zero, this interface only uses the Default Control Pipe. - uint8_t bInterfaceClass; ///< Class code (assigned by the USB-IF). \li A value of zero is reserved for future - ///< standardization. \li If this field is set to FFH, the interface class is - ///< vendor-specific. \li All other values are reserved for assignment by the USB-IF. - uint8_t bInterfaceSubClass; ///< Subclass code (assigned by the USB-IF). \n These codes are qualified by the value of - ///< the bInterfaceClass field. \li If the bInterfaceClass field is reset to zero, this - ///< field must also be reset to zero. \li If the bInterfaceClass field is not set to FFH, - ///< all values are reserved for assignment by the USB-IF. - uint8_t bInterfaceProtocol; ///< Protocol code (assigned by the USB). \n These codes are qualified by the value of the - ///< bInterfaceClass and the bInterfaceSubClass fields. If an interface supports - ///< class-specific requests, this code identifies the protocols that the device uses as - ///< defined by the specification of the device class. \li If this field is reset to zero, - ///< the device does not use a class-specific protocol on this interface. \li If this - ///< field is set to FFH, the device uses a vendor-specific protocol for this interface. - uint8_t iInterface; ///< Index of string descriptor describing this interface + uint8_t bInterfaceNumber ; ///< Number of this interface. Zero-based value identifying the index in the array of concurrent interfaces supported by this configuration. + uint8_t bAlternateSetting ; ///< Value used to select this alternate setting for the interface identified in the prior field + uint8_t bNumEndpoints ; ///< Number of endpoints used by this interface (excluding endpoint zero). If this value is zero, this interface only uses the Default Control Pipe. + uint8_t bInterfaceClass ; ///< Class code (assigned by the USB-IF). \li A value of zero is reserved for future standardization. \li If this field is set to FFH, the interface class is vendor-specific. \li All other values are reserved for assignment by the USB-IF. + uint8_t bInterfaceSubClass ; ///< Subclass code (assigned by the USB-IF). \n These codes are qualified by the value of the bInterfaceClass field. \li If the bInterfaceClass field is reset to zero, this field must also be reset to zero. \li If the bInterfaceClass field is not set to FFH, all values are reserved for assignment by the USB-IF. + uint8_t bInterfaceProtocol ; ///< Protocol code (assigned by the USB). \n These codes are qualified by the value of the bInterfaceClass and the bInterfaceSubClass fields. If an interface supports class-specific requests, this code identifies the protocols that the device uses as defined by the specification of the device class. \li If this field is reset to zero, the device does not use a class-specific protocol on this interface. \li If this field is set to FFH, the device uses a vendor-specific protocol for this interface. + uint8_t iInterface ; ///< Index of string descriptor describing this interface } tusb_desc_interface_t; -TU_VERIFY_STATIC(sizeof(tusb_desc_interface_t) == 9u, "size is not correct"); +TU_VERIFY_STATIC( sizeof(tusb_desc_interface_t) == 9u, "size is not correct"); /// USB Endpoint Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; // Size of this descriptor in bytes - uint8_t bDescriptorType; // ENDPOINT Descriptor Type + uint8_t bLength ; // Size of this descriptor in bytes + uint8_t bDescriptorType ; // ENDPOINT Descriptor Type - uint8_t bEndpointAddress; // The address of the endpoint + uint8_t bEndpointAddress ; // The address of the endpoint struct TU_ATTR_PACKED { #if (TU_BITFIELD_ORDER == TU_BITFIELD_LE) - uint8_t xfer : 2; // Control, ISO, Bulk, Interrupt - uint8_t sync : 2; // None, Asynchronous, Adaptive, Synchronous - uint8_t usage : 2; // Data, Feedback, Implicit feedback + uint8_t xfer : 2; // Control, ISO, Bulk, Interrupt + uint8_t sync : 2; // None, Asynchronous, Adaptive, Synchronous + uint8_t usage : 2; // Data, Feedback, Implicit feedback uint8_t : 2; #elif (TU_BITFIELD_ORDER == TU_BITFIELD_BE) uint8_t : 2; @@ -443,70 +414,70 @@ typedef struct TU_ATTR_PACKED { #endif } bmAttributes; - uint16_t wMaxPacketSize; // Bit 10..0 : max packet size, bit 12..11 additional transaction per highspeed micro-frame - uint8_t bInterval; // Polling interval, in frames or microframes depending on the operating speed + uint16_t wMaxPacketSize ; // Bit 10..0 : max packet size, bit 12..11 additional transaction per highspeed micro-frame + uint8_t bInterval ; // Polling interval, in frames or microframes depending on the operating speed } tusb_desc_endpoint_t; -TU_VERIFY_STATIC(sizeof(tusb_desc_endpoint_t) == 7u, "size is not correct"); +TU_VERIFY_STATIC( sizeof(tusb_desc_endpoint_t) == 7u, "size is not correct"); /// USB Other Speed Configuration Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; ///< Size of descriptor - uint8_t bDescriptorType; ///< Other_speed_Configuration Type - uint16_t wTotalLength; ///< Total length of data returned + uint8_t bLength ; ///< Size of descriptor + uint8_t bDescriptorType ; ///< Other_speed_Configuration Type + uint16_t wTotalLength ; ///< Total length of data returned - uint8_t bNumInterfaces; ///< Number of interfaces supported by this speed configuration - uint8_t bConfigurationValue; ///< Value to use to select configuration - uint8_t iConfiguration; ///< Index of string descriptor - uint8_t bmAttributes; ///< Same as Configuration descriptor - uint8_t bMaxPower; ///< Same as Configuration descriptor + uint8_t bNumInterfaces ; ///< Number of interfaces supported by this speed configuration + uint8_t bConfigurationValue ; ///< Value to use to select configuration + uint8_t iConfiguration ; ///< Index of string descriptor + uint8_t bmAttributes ; ///< Same as Configuration descriptor + uint8_t bMaxPower ; ///< Same as Configuration descriptor } tusb_desc_other_speed_t; /// USB Device Qualifier Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; ///< Size of descriptor - uint8_t bDescriptorType; ///< Device Qualifier Type - uint16_t bcdUSB; ///< USB specification version number (e.g., 0200H for V2.00) + uint8_t bLength ; ///< Size of descriptor + uint8_t bDescriptorType ; ///< Device Qualifier Type + uint16_t bcdUSB ; ///< USB specification version number (e.g., 0200H for V2.00) - uint8_t bDeviceClass; ///< Class Code - uint8_t bDeviceSubClass; ///< SubClass Code - uint8_t bDeviceProtocol; ///< Protocol Code + uint8_t bDeviceClass ; ///< Class Code + uint8_t bDeviceSubClass ; ///< SubClass Code + uint8_t bDeviceProtocol ; ///< Protocol Code - uint8_t bMaxPacketSize0; ///< Maximum packet size for other speed - uint8_t bNumConfigurations; ///< Number of Other-speed Configurations - uint8_t bReserved; ///< Reserved for future use, must be zero + uint8_t bMaxPacketSize0 ; ///< Maximum packet size for other speed + uint8_t bNumConfigurations ; ///< Number of Other-speed Configurations + uint8_t bReserved ; ///< Reserved for future use, must be zero } tusb_desc_device_qualifier_t; -TU_VERIFY_STATIC(sizeof(tusb_desc_device_qualifier_t) == 10u, "size is not correct"); +TU_VERIFY_STATIC( sizeof(tusb_desc_device_qualifier_t) == 10u, "size is not correct"); /// USB Interface Association Descriptor (IAD ECN) typedef struct TU_ATTR_PACKED { - uint8_t bLength; ///< Size of descriptor - uint8_t bDescriptorType; ///< Other_speed_Configuration Type + uint8_t bLength ; ///< Size of descriptor + uint8_t bDescriptorType ; ///< Other_speed_Configuration Type - uint8_t bFirstInterface; ///< Index of the first associated interface. - uint8_t bInterfaceCount; ///< Total number of associated interfaces. + uint8_t bFirstInterface ; ///< Index of the first associated interface. + uint8_t bInterfaceCount ; ///< Total number of associated interfaces. - uint8_t bFunctionClass; ///< Interface class ID. - uint8_t bFunctionSubClass; ///< Interface subclass ID. - uint8_t bFunctionProtocol; ///< Interface protocol ID. + uint8_t bFunctionClass ; ///< Interface class ID. + uint8_t bFunctionSubClass ; ///< Interface subclass ID. + uint8_t bFunctionProtocol ; ///< Interface protocol ID. - uint8_t iFunction; ///< Index of the string descriptor describing the interface association. + uint8_t iFunction ; ///< Index of the string descriptor describing the interface association. } tusb_desc_interface_assoc_t; -TU_VERIFY_STATIC(sizeof(tusb_desc_interface_assoc_t) == 8u, "size is not correct"); +TU_VERIFY_STATIC( sizeof(tusb_desc_interface_assoc_t) == 8u, "size is not correct"); // USB String Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; ///< Size of this descriptor in bytes - uint8_t bDescriptorType; ///< Descriptor Type + uint8_t bLength ; ///< Size of this descriptor in bytes + uint8_t bDescriptorType ; ///< Descriptor Type uint16_t utf16le[]; } tusb_desc_string_t; // USB Binary Device Object Store (BOS) typedef struct TU_ATTR_PACKED { uint8_t bLength; - uint8_t bDescriptorType; + uint8_t bDescriptorType ; uint8_t bDevCapabilityType; uint8_t bReserved; uint8_t PlatformCapabilityUUID[16]; @@ -523,8 +494,8 @@ typedef struct TU_ATTR_PACKED { // DFU Functional Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; - uint8_t bDescriptorType; + uint8_t bLength; + uint8_t bDescriptorType; union { struct TU_ATTR_PACKED { @@ -551,13 +522,13 @@ typedef struct TU_ATTR_PACKED { union { struct TU_ATTR_PACKED { #if (TU_BITFIELD_ORDER == TU_BITFIELD_LE) - uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. - uint8_t type : 2; ///< Request type tusb_request_type_t. - uint8_t direction : 1; ///< Direction type. tusb_dir_t + uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. + uint8_t type : 2; ///< Request type tusb_request_type_t. + uint8_t direction : 1; ///< Direction type. tusb_dir_t #elif (TU_BITFIELD_ORDER == TU_BITFIELD_BE) - uint8_t direction : 1; ///< Direction type. tusb_dir_t - uint8_t type : 2; ///< Request type tusb_request_type_t. - uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. + uint8_t direction : 1; ///< Direction type. tusb_dir_t + uint8_t type : 2; ///< Request type tusb_request_type_t. + uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. #else #error "Please define TU_BITFIELD_ORDER as TU_BITFIELD_LE or TU_BITFIELD_BE" #endif @@ -572,43 +543,42 @@ typedef struct TU_ATTR_PACKED { uint16_t wLength; } tusb_control_request_t; -TU_VERIFY_STATIC(sizeof(tusb_control_request_t) == 8u, "size is not correct"); +TU_VERIFY_STATIC( sizeof(tusb_control_request_t) == 8u, "size is not correct"); -TU_ATTR_PACKED_END // End of all packed definitions - TU_ATTR_BIT_FIELD_ORDER_END +TU_ATTR_PACKED_END // End of all packed definitions +TU_ATTR_BIT_FIELD_ORDER_END - //--------------------------------------------------------------------+ - // Endpoint helper - //--------------------------------------------------------------------+ +//--------------------------------------------------------------------+ +// Endpoint helper +//--------------------------------------------------------------------+ - // Get direction from Endpoint address - TU_ATTR_ALWAYS_INLINE static inline tusb_dir_t - tu_edpt_dir(uint8_t addr) { +// Get direction from Endpoint address +TU_ATTR_ALWAYS_INLINE static inline tusb_dir_t tu_edpt_dir(uint8_t addr) { return (addr & TUSB_DIR_IN_MASK) ? TUSB_DIR_IN : TUSB_DIR_OUT; } // Get Endpoint number from address TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_edpt_number(uint8_t addr) { - return (uint8_t)(addr & TUSB_EPNUM_MASK); + return (uint8_t) (addr & TUSB_EPNUM_MASK); } TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_edpt_addr(uint8_t num, uint8_t dir) { - return (uint8_t)(num | (dir == (uint8_t)TUSB_DIR_IN ? (uint8_t)TUSB_DIR_IN_MASK : 0u)); + return (uint8_t) (num | (dir == (uint8_t)TUSB_DIR_IN ? (uint8_t)TUSB_DIR_IN_MASK : 0u)); } -TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_edpt_packet_size(const tusb_desc_endpoint_t *desc_ep) { +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_edpt_packet_size(tusb_desc_endpoint_t const* desc_ep) { return tu_le16toh(desc_ep->wMaxPacketSize) & 0x7FF; } #if CFG_TUSB_DEBUG TU_ATTR_ALWAYS_INLINE static inline const char *tu_edpt_type_str(tusb_xfer_type_t t) { - const tu_static char *str[] = {"control", "isochronous", "bulk", "interrupt"}; + tu_static const char *str[] = {"control", "isochronous", "bulk", "interrupt"}; return str[t]; } #endif #ifdef __cplusplus -} + } #endif #endif // TUSB_TYPES_H_ From 7b9761f1919014152d3e9ee1aaf203d7fb0e762b Mon Sep 17 00:00:00 2001 From: Jie Feng Date: Sat, 25 Jul 2026 11:25:05 +0800 Subject: [PATCH 04/10] Enable APM32F0 dependency fetching and CI --- .github/workflows/ci_set_matrix.py | 1 + .gitignore | 1 + examples/device/net_lwip_webserver/skip.txt | 1 + .../apm32f0xx/FreeRTOSConfig/FreeRTOSConfig.h | 108 ++++++++++++++++++ .../boards/apm32f072_dev_board/board.cmake | 7 +- .../boards/apm32f072_dev_board/board.mk | 5 +- hw/bsp/apm32f0xx/family.c | 9 ++ hw/bsp/apm32f0xx/family.cmake | 2 +- hw/bsp/apm32f0xx/family.mk | 2 +- tools/get_deps.py | 3 + 10 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 hw/bsp/apm32f0xx/FreeRTOSConfig/FreeRTOSConfig.h diff --git a/.github/workflows/ci_set_matrix.py b/.github/workflows/ci_set_matrix.py index dc0d3871f..50ada5964 100755 --- a/.github/workflows/ci_set_matrix.py +++ b/.github/workflows/ci_set_matrix.py @@ -16,6 +16,7 @@ toolchain_list = [ # family: [supported toolchain] family_list = { + "apm32f0xx": ["arm-gcc"], "at32f402_405": ["arm-gcc"], "at32f403a_407": ["arm-gcc"], "at32f413": ["arm-gcc"], diff --git a/.gitignore b/.gitignore index ca745ee19..8773322e4 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,7 @@ hw/mcu/artery/ hw/mcu/broadcom/ hw/mcu/bridgetek/ft9xx/ft90x-sdk/ hw/mcu/gd/ +hw/mcu/geehy/ hw/mcu/hpmicro/ hw/mcu/infineon/ hw/mcu/microchip/ diff --git a/examples/device/net_lwip_webserver/skip.txt b/examples/device/net_lwip_webserver/skip.txt index c3df1ee4b..53836b581 100644 --- a/examples/device/net_lwip_webserver/skip.txt +++ b/examples/device/net_lwip_webserver/skip.txt @@ -1,3 +1,4 @@ +mcu:APM32F0XX mcu:CH32V103 mcu:CH32V20X mcu:LPC11UXX diff --git a/hw/bsp/apm32f0xx/FreeRTOSConfig/FreeRTOSConfig.h b/hw/bsp/apm32f0xx/FreeRTOSConfig/FreeRTOSConfig.h new file mode 100644 index 000000000..b8d555ebe --- /dev/null +++ b/hw/bsp/apm32f0xx/FreeRTOSConfig/FreeRTOSConfig.h @@ -0,0 +1,108 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026, Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef FREERTOS_CONFIG_H_ +#define FREERTOS_CONFIG_H_ + +#ifndef __IASMARM__ + #include "apm32f0xx.h" +#endif + +#define configENABLE_MPU 0 +#define configENABLE_FPU 0 +#define configENABLE_TRUSTZONE 0 +#define configMINIMAL_SECURE_STACK_SIZE 1024 + +#define configUSE_PREEMPTION 1 +#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0 +#define configCPU_CLOCK_HZ SystemCoreClock +#define configTICK_RATE_HZ 1000 +#define configMAX_PRIORITIES 5 +#define configMINIMAL_STACK_SIZE 128 +#define configTOTAL_HEAP_SIZE ( configSUPPORT_DYNAMIC_ALLOCATION * 4 * 1024 ) +#define configMAX_TASK_NAME_LEN 16 +#define configUSE_16_BIT_TICKS 0 +#define configIDLE_SHOULD_YIELD 1 +#define configUSE_MUTEXES 1 +#define configUSE_RECURSIVE_MUTEXES 1 +#define configUSE_COUNTING_SEMAPHORES 1 +#define configQUEUE_REGISTRY_SIZE 4 +#define configUSE_QUEUE_SETS 0 +#define configUSE_TIME_SLICING 0 +#define configUSE_NEWLIB_REENTRANT 0 +#define configENABLE_BACKWARD_COMPATIBILITY 1 +#define configSTACK_ALLOCATION_FROM_SEPARATE_HEAP 0 + +#define configSUPPORT_STATIC_ALLOCATION 1 +#define configSUPPORT_DYNAMIC_ALLOCATION 0 + +#define configUSE_IDLE_HOOK 0 +#define configUSE_TICK_HOOK 0 +#define configUSE_MALLOC_FAILED_HOOK 0 +#define configCHECK_FOR_STACK_OVERFLOW 2 +#define configCHECK_HANDLER_INSTALLATION 0 + +#define configGENERATE_RUN_TIME_STATS 0 +#define configRECORD_STACK_HIGH_ADDRESS 1 +#define configUSE_TRACE_FACILITY 1 +#define configUSE_STATS_FORMATTING_FUNCTIONS 0 + +#define configUSE_CO_ROUTINES 0 +#define configMAX_CO_ROUTINE_PRIORITIES 2 + +#define configUSE_TIMERS 1 +#define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 2 ) +#define configTIMER_QUEUE_LENGTH 32 +#define configTIMER_TASK_STACK_DEPTH configMINIMAL_STACK_SIZE + +#define INCLUDE_vTaskPrioritySet 0 +#define INCLUDE_uxTaskPriorityGet 0 +#define INCLUDE_vTaskDelete 0 +#define INCLUDE_vTaskSuspend 1 +#define INCLUDE_xResumeFromISR 0 +#define INCLUDE_vTaskDelayUntil 1 +#define INCLUDE_vTaskDelay 1 +#define INCLUDE_xTaskGetSchedulerState 0 +#define INCLUDE_xTaskGetCurrentTaskHandle 1 +#define INCLUDE_uxTaskGetStackHighWaterMark 0 +#define INCLUDE_xTaskGetIdleTaskHandle 0 +#define INCLUDE_xTimerGetTimerDaemonTaskHandle 0 +#define INCLUDE_pcTaskGetTaskName 0 +#define INCLUDE_eTaskGetState 0 +#define INCLUDE_xEventGroupSetBitFromISR 0 +#define INCLUDE_xTimerPendFunctionCall 0 + +#define xPortPendSVHandler PendSV_Handler +#define xPortSysTickHandler SysTick_Handler +#define vPortSVCHandler SVC_Handler + +#define configPRIO_BITS __NVIC_PRIO_BITS +#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY ( ( 1 << configPRIO_BITS ) - 1 ) +#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 2 +#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << ( 8 - configPRIO_BITS ) ) +#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << ( 8 - configPRIO_BITS ) ) + +#endif diff --git a/hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.cmake b/hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.cmake index 33148dbd4..d25f675be 100644 --- a/hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.cmake +++ b/hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.cmake @@ -4,5 +4,10 @@ set(MCU_LINKER_NAME APM32F07xxB) set(JLINK_DEVICE APM32F072RB) function(update_board TARGET) - target_compile_definitions(${TARGET} PUBLIC ${MCU_VARIANT}) + target_compile_definitions(${TARGET} PUBLIC + ${MCU_VARIANT} + CFG_EXAMPLE_MSC_READONLY + CFG_EXAMPLE_MSC_DUAL_READONLY + CFG_EXAMPLE_VIDEO_READONLY + ) endfunction() diff --git a/hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.mk b/hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.mk index 2e5df9947..f78d2091f 100644 --- a/hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.mk +++ b/hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.mk @@ -4,4 +4,7 @@ MCU_LINKER_NAME = APM32F07xxB JLINK_DEVICE = APM32F072RB CFLAGS += \ - -D${MCU_VARIANT} + -D${MCU_VARIANT} \ + -DCFG_EXAMPLE_MSC_READONLY \ + -DCFG_EXAMPLE_MSC_DUAL_READONLY \ + -DCFG_EXAMPLE_VIDEO_READONLY diff --git a/hw/bsp/apm32f0xx/family.c b/hw/bsp/apm32f0xx/family.c index cbc427f8c..9caa0207c 100644 --- a/hw/bsp/apm32f0xx/family.c +++ b/hw/bsp/apm32f0xx/family.c @@ -36,6 +36,15 @@ #include "bsp/board_api.h" #include "board.h" +void USBD_IRQHandler(void); +#if CFG_TUSB_OS == OPT_OS_NONE +void SysTick_Handler(void); +void SVC_Handler(void); +void PendSV_Handler(void); +#endif +void HardFault_Handler(void); +void _init(void); + //--------------------------------------------------------------------+ // Forward USB interrupt events to TinyUSB IRQ Handler //--------------------------------------------------------------------+ diff --git a/hw/bsp/apm32f0xx/family.cmake b/hw/bsp/apm32f0xx/family.cmake index 99a94a7a8..0cf199f26 100644 --- a/hw/bsp/apm32f0xx/family.cmake +++ b/hw/bsp/apm32f0xx/family.cmake @@ -1,7 +1,7 @@ include_guard() set(APM32_FAMILY apm32f0xx) -set(APM32_SDK ${TOP}/hw/mcu/geehy/APM32F0xx_SDK_V1.8.6/Libraries) +set(APM32_SDK ${TOP}/hw/mcu/geehy/APM32F0xx_SDK/Libraries) # include board specific include(${CMAKE_CURRENT_LIST_DIR}/boards/${BOARD}/board.cmake) diff --git a/hw/bsp/apm32f0xx/family.mk b/hw/bsp/apm32f0xx/family.mk index 73a762d69..735c83f90 100644 --- a/hw/bsp/apm32f0xx/family.mk +++ b/hw/bsp/apm32f0xx/family.mk @@ -1,5 +1,5 @@ APM32_FAMILY = apm32f0xx -APM32_SDK = hw/mcu/geehy/APM32F0xx_SDK_V1.8.6/Libraries +APM32_SDK = hw/mcu/geehy/APM32F0xx_SDK/Libraries include $(TOP)/$(BOARD_PATH)/board.mk diff --git a/tools/get_deps.py b/tools/get_deps.py index e26810cac..baaf3761f 100755 --- a/tools/get_deps.py +++ b/tools/get_deps.py @@ -46,6 +46,9 @@ deps_optional = { 'hw/mcu/gd/nuclei-sdk': ['https://github.com/Nuclei-Software/nuclei-sdk.git', '7eb7bfa9ea4fbeacfafe1d5f77d5a0e6ed3922e7', 'gd32vf103'], + 'hw/mcu/geehy/APM32F0xx_SDK': ['https://github.com/GeehySemi/APM32F0xx_SDK.git', + 'cfc1fe826e1869133de86d4c6b298fc153e6bc32', + 'apm32f0xx'], 'hw/mcu/infineon/mtb-xmclib-cat3': ['https://github.com/Infineon/mtb-xmclib-cat3.git', 'daf5500d03cba23e68c2f241c30af79cd9d63880', 'xmc4000'], From bf7a62055ec829f00bd9c3a129e327b6a67ac55b Mon Sep 17 00:00:00 2001 From: Jie Feng Date: Sat, 25 Jul 2026 11:37:33 +0800 Subject: [PATCH 05/10] Run APM32F0 CPU and USB from 48 MHz PLL --- hw/bsp/apm32f0xx/family.c | 16 ++++------------ hw/bsp/apm32f0xx/family.cmake | 1 - hw/bsp/apm32f0xx/family.mk | 1 - 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/hw/bsp/apm32f0xx/family.c b/hw/bsp/apm32f0xx/family.c index 9caa0207c..643490797 100644 --- a/hw/bsp/apm32f0xx/family.c +++ b/hw/bsp/apm32f0xx/family.c @@ -32,7 +32,6 @@ #include "apm32f0xx_rcm.h" #include "apm32f0xx_gpio.h" #include "apm32f0xx_misc.h" -#include "apm32f0xx_crs.h" #include "bsp/board_api.h" #include "board.h" @@ -56,18 +55,11 @@ void USBD_IRQHandler(void) { // Board Init //--------------------------------------------------------------------+ void board_init(void) { - // Enable HSI48 for USB clock - RCM_EnableHSI48(); - while (RCM_ReadStatusFlag(RCM_FLAG_HSI48RDY) == RESET) {} + // Configure HSE and PLL for a 48 MHz system clock + SystemClockConfig(); - // Select HSI48 as USB clock source - RCM_ConfigUSBCLK(RCM_USBCLK_HSI48); - - // Enable CRS for automatic HSI48 calibration from USB SOF - RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_CRS); - CRS_ConfigSynchronizationSource(CRS_SYNC_SOURCE_USB); - CRS_EnableAutomaticCalibration(); - CRS_EnableFrequencyErrorCounter(); + // Route the 48 MHz PLL clock to USB + RCM_ConfigUSBCLK(RCM_USBCLK_PLLCLK); // Enable USB peripheral clock RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_USB); diff --git a/hw/bsp/apm32f0xx/family.cmake b/hw/bsp/apm32f0xx/family.cmake index 0cf199f26..199ba7cb5 100644 --- a/hw/bsp/apm32f0xx/family.cmake +++ b/hw/bsp/apm32f0xx/family.cmake @@ -31,7 +31,6 @@ function(family_add_board BOARD_TARGET) ${APM32_SDK}/APM32F0xx_StdPeriphDriver/src/apm32f0xx_gpio.c ${APM32_SDK}/APM32F0xx_StdPeriphDriver/src/apm32f0xx_misc.c ${APM32_SDK}/APM32F0xx_StdPeriphDriver/src/apm32f0xx_rcm.c - ${APM32_SDK}/APM32F0xx_StdPeriphDriver/src/apm32f0xx_crs.c ) target_include_directories(${BOARD_TARGET} PUBLIC ${CMAKE_CURRENT_FUNCTION_LIST_DIR} diff --git a/hw/bsp/apm32f0xx/family.mk b/hw/bsp/apm32f0xx/family.mk index 735c83f90..7e26918c2 100644 --- a/hw/bsp/apm32f0xx/family.mk +++ b/hw/bsp/apm32f0xx/family.mk @@ -20,7 +20,6 @@ SRC_C += \ $(APM32_SDK)/APM32F0xx_StdPeriphDriver/src/apm32f0xx_gpio.c \ $(APM32_SDK)/APM32F0xx_StdPeriphDriver/src/apm32f0xx_misc.c \ $(APM32_SDK)/APM32F0xx_StdPeriphDriver/src/apm32f0xx_rcm.c \ - $(APM32_SDK)/APM32F0xx_StdPeriphDriver/src/apm32f0xx_crs.c \ $(APM32_SDK)/Device/Geehy/APM32F0xx/Source/system_apm32f0xx.c INC += \ From ba9940385c23db07c35ad546a1bcf153997a80a5 Mon Sep 17 00:00:00 2001 From: Zixun LI Date: Sat, 25 Jul 2026 21:32:28 +0200 Subject: [PATCH 06/10] refresh presets Signed-off-by: Zixun LI --- hw/bsp/BoardPresets.json | 44 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/hw/bsp/BoardPresets.json b/hw/bsp/BoardPresets.json index 93a8f2c32..a480efc3e 100644 --- a/hw/bsp/BoardPresets.json +++ b/hw/bsp/BoardPresets.json @@ -42,6 +42,10 @@ "name": "apard32690", "inherits": "default" }, + { + "name": "apm32f072_dev_board", + "inherits": "default" + }, { "name": "arduino_nano33_ble", "inherits": "default" @@ -510,6 +514,10 @@ "name": "portenta_c33", "inherits": "default" }, + { + "name": "py32f071_dev_board", + "inherits": "default" + }, { "name": "pybadge", "inherits": "default" @@ -1007,6 +1015,11 @@ "description": "Build preset for the apard32690 board", "configurePreset": "apard32690" }, + { + "name": "apm32f072_dev_board", + "description": "Build preset for the apm32f072_dev_board board", + "configurePreset": "apm32f072_dev_board" + }, { "name": "arduino_nano33_ble", "description": "Build preset for the arduino_nano33_ble board", @@ -1637,6 +1650,11 @@ "description": "Build preset for the portenta_c33 board", "configurePreset": "portenta_c33" }, + { + "name": "py32f071_dev_board", + "description": "Build preset for the py32f071_dev_board board", + "configurePreset": "py32f071_dev_board" + }, { "name": "pybadge", "description": "Build preset for the pybadge board", @@ -2257,6 +2275,19 @@ } ] }, + { + "name": "apm32f072_dev_board", + "steps": [ + { + "type": "configure", + "name": "apm32f072_dev_board" + }, + { + "type": "build", + "name": "apm32f072_dev_board" + } + ] + }, { "name": "arduino_nano33_ble", "steps": [ @@ -3895,6 +3926,19 @@ } ] }, + { + "name": "py32f071_dev_board", + "steps": [ + { + "type": "configure", + "name": "py32f071_dev_board" + }, + { + "type": "build", + "name": "py32f071_dev_board" + } + ] + }, { "name": "pybadge", "steps": [ From 05f19ab438641823d047c811e7426177788d0490 Mon Sep 17 00:00:00 2001 From: TenGui Date: Sun, 26 Jul 2026 12:16:23 -0700 Subject: [PATCH 07/10] TU_MIN suggestion --- src/common/tusb_types.h | 2 +- src/device/usbd.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/tusb_types.h b/src/common/tusb_types.h index d0796ccc8..fa4d67df1 100644 --- a/src/common/tusb_types.h +++ b/src/common/tusb_types.h @@ -244,7 +244,7 @@ enum { TUSB_DESC_CONFIG_ATT_SELF_POWERED = 1 << 6, }; -#define TUSB_DESC_CONFIG_POWER_MA(x) (uint8_t)((x)/2) +#define TUSB_DESC_CONFIG_POWER_MA(x) ((uint8_t)TU_MIN((x)/2, UINT8_MAX)) // USB 2.0 Spec Table 9-7: Test Mode Selectors typedef enum { diff --git a/src/device/usbd.h b/src/device/usbd.h index 296ec417d..2015d1869 100644 --- a/src/device/usbd.h +++ b/src/device/usbd.h @@ -229,7 +229,7 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ // Config number, interface count, string index, total length, attribute, power in mA #define TUD_CONFIG_DESCRIPTOR(config_num, _itfcount, _stridx, _total_len, _attribute, _power_ma) \ - 9, TUSB_DESC_CONFIGURATION, U16_TO_U8S_LE(_total_len), _itfcount, config_num, _stridx, TU_BIT(7) | _attribute, (uint8_t)((_power_ma)/2) + 9, TUSB_DESC_CONFIGURATION, U16_TO_U8S_LE(_total_len), _itfcount, config_num, _stridx, TU_BIT(7) | _attribute, (uint8_t)TU_MIN((_power_ma)/2, UINT8_MAX) //--------------------------------------------------------------------+ // CDC Descriptor Templates From 72f95d7d61a5a445be267eef2d677c33baa2fec2 Mon Sep 17 00:00:00 2001 From: Ha Thach Date: Tue, 28 Jul 2026 00:17:14 +0700 Subject: [PATCH 08/10] test/hil: replace PCI reset with root-port VBUS cycle for D-state recovery (#3789) test/hil: replace PCI reset with root-port VBUS cycle for D-state recovery pci-reset was documented as an FLR, but no controller on either rig has FLR, so it issued a PCIe secondary bus reset on a live, driver-bound xHCI -- halting the card until the PVE host was power-cycled, and returning success so the caller could not tell. It destroyed the ci controller twice. Replace it with root-cycle, which cuts VBUS at the xHCI root port and touches only the root hub, so it never takes the per-device lock the wedged ioctl holds. uhubctl needs -S, or its sysfs backend disconnects the child before cutting power and blocks on that same lock. Success is proven by the device's sysfs directory inode changing: node existence proves nothing, and devnum is reused once the per-bus map wraps. usbtest.py's hang path invokes it, then confirms via /proc that nothing still holds the device node. Skill scripts now run from the repo; the drifted /usr/local/sbin copies are deleted. --- .claude/skills/usb-kernel-debug/SKILL.md | 6 +- .claude/skills/usb-kernel-recover/SKILL.md | 73 +++++++---- .../usb-kernel-recover/scripts/usb_recover.sh | 89 +++++++++++-- test/hil/usbtest.py | 124 +++++++++++++++--- 4 files changed, 236 insertions(+), 56 deletions(-) diff --git a/.claude/skills/usb-kernel-debug/SKILL.md b/.claude/skills/usb-kernel-debug/SKILL.md index e4169b049..fd291c0ac 100644 --- a/.claude/skills/usb-kernel-debug/SKILL.md +++ b/.claude/skills/usb-kernel-debug/SKILL.md @@ -12,11 +12,11 @@ sits in the link — the rig PC when it is the host, or a Linux gadget peer (dwc2/UDC + gadget modules) when TinyUSB is the host. It cannot see inside the TinyUSB MCU — that is the `target-debug` skill. -Run this skill's `scripts/usb_dyndbg.sh` with `sudo` (abbreviated to -`usb_dyndbg.sh` in the examples below). It flips the dynamic-debug print flag -for an allowlisted set of USB modules only: +Run this skill's `scripts/usb_dyndbg.sh` with `sudo`. It flips the dynamic-debug +print flag for an allowlisted set of USB modules only: ```bash +# all examples below abbreviate: sudo .claude/skills/usb-kernel-debug/scripts/usb_dyndbg.sh sudo usb_dyndbg.sh on usbcore xhci_hcd # enable +p; pick modules from `lsusb -t` Driver= sudo usb_dyndbg.sh status [module] # list enabled print sites sudo usb_dyndbg.sh off usbcore xhci_hcd # ALWAYS turn off when done — very noisy diff --git a/.claude/skills/usb-kernel-recover/SKILL.md b/.claude/skills/usb-kernel-recover/SKILL.md index 3f03722fe..9e456417f 100644 --- a/.claude/skills/usb-kernel-recover/SKILL.md +++ b/.claude/skills/usb-kernel-recover/SKILL.md @@ -5,18 +5,20 @@ description: Use when a USB device or fixture attached to the ci HIL rig's Linux # USB Recovery on the HIL Rig (Linux kernel side) -Run this skill's `scripts/usb_recover.sh` with `sudo` (abbreviated to -`usb_recover.sh` in the examples below). It wraps the sysfs reset actions, a -uhubctl power-cycle escalator, and a resolver: +Run this skill's `scripts/usb_recover.sh` with `sudo`. It wraps the sysfs reset +actions, a uhubctl power-cycle escalator, and a resolver: ```bash +# all examples below abbreviate: sudo .claude/skills/usb-kernel-recover/scripts/usb_recover.sh sudo usb_recover.sh resolve /dev/ttyACM3 # /dev node -> busport (e.g. 3-4.7); also ttyUSB*, sg* sudo usb_recover.sh authorized # deauthorize+reauthorize: re-enumerate, no VBUS cut sudo usb_recover.sh rebind # usb driver unbind+bind: re-probe sudo usb_recover.sh hub-cycle # uhubctl VBUS cycle of the feeding port, walking parent hub # -> root port until the device re-enumerates +sudo usb_recover.sh root-cycle [serial] # uhubctl VBUS cut straight at the ROOT port (real ppps), no + # leaf walk, no device-lock touch: the D-state cure. + # [serial] is checked and a mismatch refused. sudo usb_recover.sh pci-rebind # whole HCD controller unbind+bind, e.g. 0000:02:00.0 -sudo usb_recover.sh pci-reset # PCI function-level reset: kills URBs at HW level, no device lock sudo usb_recover.sh pci-bind [drv] # re-bind a DRIVERLESS controller (auto-tries xHCI drivers) ``` @@ -34,21 +36,45 @@ ps -eo pid,stat,wchan:30,cmd | awk '$2 ~ /D/' ``` **If yes** (uninterruptible sleep, typically a usbfs ioctl — e.g. testusb inside -`usb_sg_wait`): run `pci-reset` and NOTHING ELSE first: +`usb_sg_wait`): cut VBUS at the root port, and nothing else. ```bash -sudo usb_recover.sh pci-reset +sudo usb_recover.sh root-cycle # e.g. 11-3.7 -> cycles bus 11 root port 3 ``` -FLR kills the URBs at the hardware level without taking the per-device lock; -the ioctl then returns and the convoy unwinds on its own. +This drops power to the wedged device, so its in-flight URB fails and the ioctl +returns. It targets the *root hub* — a different USB device from the wedged one — +and never *writes* the wedged device's sysfs. It reads a few attributes from it — +`idVendor`/`idProduct`/`serial`/`product` to report and check the target, and the +directory inode plus `devnum` afterwards — none of which take the device lock, so +it does not join the convoy the way `authorized`/`rebind`/`pci-rebind` do. +Recovery is proven by that inode changing — a real disconnect destroys the +kobject and reconnecting creates a new one, whereas a disconnect blocked on the +device lock leaves it untouched. It exits non-zero if the device does not come +back; a **zero exit only means it re-enumerated**, so still confirm the D-state +process actually let go. Pass the expected serial as a third argument and it +refuses a busport that now names a different device. -**Not every controller supports FLR.** The Renesas uPD720201 (`0000:01:00.0`) -has no reset method — `pci-reset` fails with `Inappropriate ioctl for device` -(ENOTTY). On those, there is no clean software D-state cure — a VM reboot is NOT +It bounces **every fixture under that root port** — on ci that is up to 25 +devices. Hold the affected boards' locks first if you can, but note +`board_lock.py` uses `LOCK_EX | LOCK_NB` and so fails immediately when CI already +holds them; there is no wait-for-lock. When CI is mid-run you are choosing +between bouncing its fixtures and leaving the bus wedged for everything. The +automated path in `usbtest.py` takes no locks at all and accepts that collateral +deliberately: by the time a D-state wedge exists the convoy will take the bus +down anyway. + +(The VBUS mechanism is verified on the ci rig — the leaf hubs report +`bmAttributes=e0`, "self-powered", but are physically bus-powered with no adapter, +so a root-port cut really does kill downstream power. Do not re-derive this from +the descriptor; it lies. Not yet confirmed against a live D-state wedge. If +`uhubctl` itself hangs, the convoy has already spread — escalate.) + +If `root-cycle` does not free the D-state process, there is no software cure +left: ask the operator for a full PVE **host** power cycle. A VM reboot is NOT reliable (downstream hubs can latch up across the PCIe reset and need a physical -replug); ask the operator for a full PVE host power cycle instead. Do NOT -fall through to `pci-rebind` (see next). +replug), and a graceful reboot stalls on the D-state process anyway. Do NOT fall +through to `pci-rebind` (see next). **`pci-rebind` can strand the controller driverless.** Its unbind succeeds but, with a D-state process still holding a URB, the *re-bind* hangs — leaving the @@ -62,10 +88,9 @@ power cycle (operator action) recovers. The Renesas binds via `xhci-pci-renesas` **Ordering is critical.** `authorized`/`rebind`/`pci-rebind` all take the per-device lock the stuck ioctl holds — they block and join the convoy, and soon every libusb tool (uhubctl, JLinkExe) hangs too. Worse, a blocked -`pci-rebind` grabs the PCI device lock on its way in, which `pci-reset` also -needs: once a rebind has been attempted and is stuck, even FLR deadlocks and -**only a full PVE host power cycle recovers**. pci-reset first (if supported), and never -`pci-rebind` a D-state wedge. +`pci-rebind` grabs the PCI device lock on its way in and can wedge the whole +function, after which **only a full PVE host power cycle recovers**. `root-cycle` +first, and never `pci-rebind` a D-state wedge. **If no** (device merely dead or silent), escalate gently: @@ -93,15 +118,19 @@ hubs themselves claim "ganged" switching but do not actually cut power. ## Common mistakes - `resolve` takes a **/dev node**, not a busport or serial ("no such device node"). -- `authorized`/`rebind` take a **busport** (`3-4.7`); `pci-rebind`/`pci-reset` - take a **PCI addr**. +- `authorized`/`rebind`/`hub-cycle`/`root-cycle` take a **busport** (`3-4.7`); + `pci-rebind`/`pci-bind` take a **PCI addr**. - Command produces no output and doesn't return → it is blocked on the device lock: a D-state holder exists; see above. - Trying `pci-rebind` on a D-state hang — its re-bind hangs and strands the controller **driverless**; recover with `pci-bind `, or a PVE host power - cycle if the D-state URB is unkillable. Use `pci-reset` (if supported) for D-state, never + cycle if the D-state URB is unkillable. Use `root-cycle` for D-state, never `pci-rebind`. -- Running `pci-reset` on a controller without FLR support (Renesas) → ENOTTY; - no software recovery — needs a PVE host power cycle. +- Writing `/sys/bus/pci/devices//reset` because the attribute is there. No + rig controller has FLR, so it becomes a PCIe bus reset that resets the xHCI + behind its live driver — the write succeeds, the card is halted for good, and + only a PVE host power cycle brings it back. Use `root-cycle`. +- `root-cycle` bounces **every** fixture under that root port, not just the target + — hold the sibling boards' locks first. - A J-Link reset (`r; go`) does not disconnect a wedged DUT from the host: the DWC2 soft-connect pullup stays up through a core halt, so stuck URBs stay stuck. diff --git a/.claude/skills/usb-kernel-recover/scripts/usb_recover.sh b/.claude/skills/usb-kernel-recover/scripts/usb_recover.sh index 7652253fa..2230602b9 100755 --- a/.claude/skills/usb-kernel-recover/scripts/usb_recover.sh +++ b/.claude/skills/usb-kernel-recover/scripts/usb_recover.sh @@ -6,9 +6,6 @@ # sudo usb_recover.sh authorized # e.g. 3-2 -> deauthorize+reauthorize (re-enumerate, NO VBUS cut) # sudo usb_recover.sh rebind # e.g. 3-2 -> usb driver unbind+bind (re-probe) # sudo usb_recover.sh pci-rebind # e.g. 0000:01:00.0 -> HCD unbind+bind (WHOLE controller) -# sudo usb_recover.sh pci-reset # e.g. 0000:01:00.0 -> PCI function-level reset: kills URBs at -# # HW level WITHOUT the device lock; the only cure when a process -# # is stuck in D state (usbfs ioctl) and unbind paths would convoy # sudo usb_recover.sh pci-bind [driver] # bind a DRIVERLESS controller (e.g. after a pci-rebind # # whose re-bind hung and left it unbound). Auto-tries the xHCI # # drivers (xhci-pci-renesas, xhci_hcd) unless one is named. @@ -17,6 +14,11 @@ # # re-enumerates. Ganged/fake-switching hubs may bounce ALL # # siblings; self-powered hubs only reset their uplink, which # # is why the walk ends at the root port (real xHCI ppps). +# sudo usb_recover.sh root-cycle [serial] # e.g. 13-1.6 -> uhubctl VBUS cut at the ROOT port feeding +# # it; [serial] is verified against the device and refused on mismatch, +# # skipping the leaf hubs (which fake ganged switching and do not +# # actually cut power). Bounces every sibling under that root port. +# # The D-state escape: no device lock, so it cannot convoy. # sudo usb_recover.sh resolve # e.g. /dev/ttyACM3 -> print its (no privilege needed) set -euo pipefail @@ -25,6 +27,29 @@ PCI_RE='^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9]$' DRIVER_RE='^[A-Za-z0-9_-]+$' die() { echo "usb_recover: $*" >&2; exit 1; } + +# Generation marker for "did this device actually re-enumerate". A real disconnect destroys the +# usb_device and its sysfs kobject; reconnecting creates a new one, and kernfs hands out inode +# numbers monotonically, so the directory inode changes. Verified on the rig: ports re-enumerated +# minutes ago carry inodes in the millions while ports untouched since boot are still in the tens +# of thousands, ranking identically to their mtimes. +# +# This beats comparing devnum, which Linux reuses once the per-bus map wraps (observed live: a +# single cycle moved one device 123 -> 113). It also beats watching for the node to vanish, since +# `uhubctl -a cycle` holds the whole power-off window inside itself and a poll afterwards can +# never witness the gap. The inode survives the gap, so no observation window is needed. +# +# Crucially, if the disconnect is blocked on the wedged device's lock the kobject is never +# recreated -- same inode -- which is exactly the case that must be reported as a failure. Verified +# against kernfs: __kernfs_new_node() allocates via idr_alloc_cyclic() but kernfs_id_ino() exposes +# the full 64-bit (id_highbits<<32 | lowbits) as st_ino on 64-bit ino_t, so a repeat needs ~2^64 +# node creations. authorized-toggle, set_configuration and suspend/resume all leave the parent +# device kobject alone, so none of them can move the marker and fake a success. +# +# The trailing slash is load-bearing: /sys/bus/usb/devices/ is a SYMLINK with its own +# separate inode, so without it stat reports the link rather than the device it points at, and the +# value would never change. Do not "tidy" it away. +sysfs_gen() { stat -c %i "/sys/bus/usb/devices/$1/" 2>/dev/null || echo none; } usage() { grep -E '^# sudo usb_recover' "$0" >&2; exit 2; } # Refuse to touch a PCI function that is not a USB controller (class 0x0c03xx), so a stray or @@ -107,6 +132,11 @@ case "$action" in [[ "$target" =~ $USBPATH_RE ]] || die "bad usb path: $target" UHUBCTL=$(command -v uhubctl || echo /sbin/uhubctl) [ -x "$UHUBCTL" ] || die "uhubctl not installed" + # sysfs generation, not node existence: a disconnect blocked on the device lock leaves the + # old node (and its idVendor) in place, so an existence check reports success without anything + # having happened -- and the walk to the root port, which is the part that actually cuts power + # on these fake-ganged leaf hubs, would never run. + gen=$(sysfs_gen "$target") dev="$target" while :; do if [[ "$dev" =~ ^([0-9]+)-([0-9]+)$ ]]; then # parent is the root hub @@ -118,8 +148,9 @@ case "$action" in "$UHUBCTL" -l "$loc" -p "$port" -a cycle -d 5 -f || echo " (uhubctl failed at $loc; walking up)" for _ in $(seq 1 10); do sleep 1 - if [ -e "/sys/bus/usb/devices/$target/idVendor" ]; then - echo "recovered: $target re-enumerated"; exit 0 + now=$(sysfs_gen "$target") + if [ "$now" != none ] && [ "$now" != "$gen" ]; then + echo "recovered: $target re-enumerated (gen $gen -> $now)"; exit 0 fi done [ -n "$up" ] || break @@ -127,12 +158,48 @@ case "$action" in done die "hub-cycle: $target still not enumerated after cycling up to the root port" ;; - pci-reset) - [[ "$target" =~ $PCI_RE ]] || die "bad pci addr: $target" - require_usb_controller "$target" - [ -e "/sys/bus/pci/devices/$target/reset" ] || die "no reset support on $target" - echo 1 > "/sys/bus/pci/devices/$target/reset" - echo "flr-reset pci $target" + root-cycle) + # VBUS cut at the ROOT port, where xHCI ppps is real. Unlike hub-cycle this does not walk up + # from the leaf (the 1a40:0201 hubs claim ganged switching but never cut power) and never + # writes the wedged device's sysfs or takes its lock, so it cannot join a D-state convoy. + # uhubctl exits 0 even when it does nothing ("No compatible devices detected" still returns + # 0), so its status proves nothing -- the sysfs_gen check below is the only real verdict. + [[ "$target" =~ $USBPATH_RE ]] || die "bad usb path: $target" + UHUBCTL=$(command -v uhubctl || echo /sbin/uhubctl) + [ -x "$UHUBCTL" ] || die "uhubctl not installed" + # Existence alone only proves *something* occupies that path -- bus numbers renumber every + # boot, so a stale busport can name a different device entirely and we would cut power to its + # whole subtree (up to 25 fixtures on this rig). Callers that know what they expect pass the + # serial as a third argument and we refuse on mismatch; otherwise print the identity so a + # wrong target is at least visible. + [ -e "/sys/bus/usb/devices/$target" ] || die "no such usb device: $target" + idf="/sys/bus/usb/devices/$target" + serial=$(cat "$idf/serial" 2>/dev/null || echo -) + expect=${3:-} + [ -z "$expect" ] || [ "$expect" = "$serial" ] || \ + die "root-cycle: $target has serial '$serial', expected '$expect' — stale busport, refusing" + echo "root-cycle: target $target is $(cat "$idf/idVendor" 2>/dev/null || echo -):$(cat "$idf/idProduct" 2>/dev/null || echo -)" \ + "serial=$serial product=$(cat "$idf/product" 2>/dev/null || echo -)" + bus=${target%%-*}; rest=${target#*-}; rootport=${rest%%.*} + gen=$(sysfs_gen "$target") + echo "root-cycle: cutting VBUS on bus $bus root port $rootport (feeds $target, bounces its siblings)" + # -S is load-bearing. By default uhubctl writes /sys/.../usb-port/disable (verified: + # two O_WRONLY opens per cycle), and the kernel's disable_store() takes the ROOT HUB's lock and + # synchronously usb_disconnect()s the child BEFORE cutting power -- against a wedged device that + # blocks on the lock we are trying to free, so power would never drop and uhubctl would D-state + # holding the root hub's lock, poisoning the whole bus. -S forces the libusb path, which sends + # the power-off control transfer straight to the root hub with no child-disconnect in front. + "$UHUBCTL" -S -l "$bus" -p "$rootport" -a cycle -d 5 \ + || die "uhubctl failed to cycle bus $bus port $rootport" + for _ in $(seq 1 10); do + sleep 1 + now=$(sysfs_gen "$target") + if [ "$now" != none ] && [ "$now" != "$gen" ]; then + echo "root-cycled $bus port $rootport: $target re-enumerated"\ + "(devnum $(cat "/sys/bus/usb/devices/$target/devnum" 2>/dev/null || echo ?), gen $gen -> $now)"; exit 0 + fi + done + die "root-cycle: $target did not re-enumerate after cycling bus $bus port $rootport (sysfs generation still $gen: no disconnect happened)" ;; *) usage diff --git a/test/hil/usbtest.py b/test/hil/usbtest.py index e17705a48..83ea3e24c 100755 --- a/test/hil/usbtest.py +++ b/test/hil/usbtest.py @@ -254,11 +254,46 @@ def dmesg_tail(): return '\n'.join(lines[-8:]) -def pci_addr_of_bus(busnum): - """Return the PCI B:D.F backing a USB bus, or None for a non-PCI (SoC/platform) controller.""" - m = re.search(r'([0-9a-f]{4}:[0-9a-f]{2}:[0-9a-f]{2}\.[0-9])/usb\d+$', - os.path.realpath(f'/sys/bus/usb/devices/usb{int(busnum)}')) - return m.group(1) if m else None +def wedged_pids(devnode): + """Return (pids, complete): PIDs in uninterruptible sleep whose cmdline names devnode, i.e. + still holding its usbfs device lock, and whether every /proc entry could actually be read. + + Matched by device node rather than by our child's pid because run_case() may wrap testusb in + sudo, in which case the Popen pid is the wrapper and the blocked process is its child -- + killing the wrapper would make a pid-based check look clean while the real holder is stuck. + + complete is False when a PermissionError hid an entry (a hidepid/ProtectProc mount, or the + root-owned child of that same sudo). An entry we could not read might be the holder, so the + caller must treat that as unrecovered rather than as an all-clear.""" + stuck, complete = [], True + # hidepid=2 and systemd's ProtectProc=invisible omit other users' processes from iterdir() + # entirely -- no entry at all, so no PermissionError to catch -- and testusb runs under sudo + # whenever the device node is not writable. The scan would then look clean while hiding the + # very holder it exists to find. pid 1 is always root-owned, so being unable to read it means + # enumeration is restricted and no result from this scan can be trusted as complete. + if os.geteuid() != 0 and not os.access('/proc/1/cmdline', os.R_OK): + complete = False + for entry in Path('/proc').iterdir(): + if not entry.name.isdigit(): + continue + try: + cmdline = (entry / 'cmdline').read_bytes() + except PermissionError: + complete = False # cannot rule this pid out + continue + except OSError: + continue # raced with process exit: genuinely gone, not hidden + if devnode.encode() not in cmdline: + continue + try: + stat = (entry / 'stat').read_text() + if stat[stat.rindex(')') + 2] == 'D': # comm may contain ')', so scan from the right + stuck.append(int(entry.name)) + except PermissionError: + complete = False + except (OSError, ValueError, IndexError): + continue + return stuck, complete def run_case(num, dev, testusb, quick, timeout): @@ -387,20 +422,68 @@ def main(): extra += f" {r['mbps']} MB/s" if 'mbps' in r else '' print(f"test {num:2d} {r['name']:22s} {r['status']:6s}{extra}") if r['status'] == 'HUNG': - pci = pci_addr_of_bus(dev['node'].split('/')[-2]) - if pci: - print(f'aborting battery: kernel-side hang, device wedged mid-transfer.\n' - f'auto-recovering: sudo {USB_RECOVER} pci-reset {pci} ' - f'(see .claude/skills/usb-kernel-recover)', file=sys.stderr) - # FLR frees the D-state ioctl without the device lock; must run BEFORE - # any unbind/remove_id, which would deadlock the bus otherwise - if sudo([str(USB_RECOVER), 'pci-reset', pci]).returncode != 0: - unrecovered_hang = True - time.sleep(5) # let the bus re-enumerate before cleanup touches sysfs - else: - unrecovered_hang = True - print('aborting battery: kernel-side hang, and the controller has no PCI address ' - 'for FLR recovery — manual intervention (reboot) required', file=sys.stderr) + print(f'aborting battery: kernel-side hang, device wedged mid-transfer.\n' + f'auto-recovering: {USB_RECOVER.name} root-cycle {dev["sysname"]} ' + f'(see .claude/skills/usb-kernel-recover)', file=sys.stderr) + # Cutting VBUS at the root port fails the in-flight URB so the usbfs ioctl returns. + # Must run BEFORE any unbind/remove_id, which would take the device lock the stuck + # ioctl holds and deadlock the bus. + # + # Assume unrecovered until proven otherwise, so that any early exit from this block + # -- an OSError spawning the helper, a KeyboardInterrupt, a sudo prompt killing the + # run -- still reaches the finally cleanup with the flag set, instead of running + # the remove_id/unbind the comments there forbid while a device lock is held. + unrecovered_hang = True + # Pass the serial so the helper refuses a stale busport rather than cutting power + # to whatever else now occupies that path. Popen rather than sudo()/subprocess.run: + # run() would kill() then wait() unbounded on timeout, which never returns if + # uhubctl is itself in D state -- the case the timeout exists for. Merge stderr + # into stdout so the helper's target-identity and action lines are not lost. + # Only pass the serial when we actually have one: an empty third argument reads as + # "no expectation" and would silently disable the helper's stale-busport guard. + cmd = [str(USB_RECOVER), 'root-cycle', dev['sysname']] + if dev['serial']: + cmd.append(dev['serial']) + if os.geteuid() != 0: + cmd = ['sudo', '-n'] + cmd + try: + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + text=True) + except OSError as e: + # helper missing or not executable, or sudo unavailable. unrecovered_hang is + # already True so the finally block still skips the unsafe cleanup -- this only + # replaces a traceback with a message that says what to fix. + print(f'cannot run {USB_RECOVER}: {e}', file=sys.stderr) + break + rc = None + try: + out, _ = p.communicate(timeout=60) # normal run is ~8s + rc = p.returncode + except subprocess.TimeoutExpired: + p.kill() + try: + out, _ = p.communicate(timeout=5) + rc = p.returncode + except subprocess.TimeoutExpired: + out = ('root-cycle abandoned after 60s: uhubctl did not die to SIGKILL, so ' + 'it is wedged too and the convoy has spread beyond this device') + if out: + print(out.strip(), file=sys.stderr) + if rc is not None: + time.sleep(5) # let the bus settle and the freed ioctl unwind + # Authoritative either way. A non-zero exit only means the device did not come + # back within the poll (a slow bootloader will do that) -- if nothing still + # holds the lock, the bus is usable and cleanup is safe. Conversely a zero exit + # only proves re-enumeration, not that the D-state holder let go. + stuck, complete = wedged_pids(dev['node']) + if stuck: + print(f'{dev["sysname"]}: pid(s) {stuck} still in D state on ' + f'{dev["node"]} — the device lock was never released', file=sys.stderr) + elif not complete: + print('cannot confirm recovery: /proc is only partly readable, so a ' + 'hidden D-state holder cannot be ruled out', file=sys.stderr) + else: + unrecovered_hang = False break # re-resolve: after a mid-battery re-enumeration the devnum (and thus the node # path) changes; keep testing the live node instead of the stale one. Match on the @@ -419,7 +502,8 @@ def main(): if unrecovered_hang: # testusb is still stuck in a usbfs ioctl holding the device lock; remove_id/unbind # would join the convoy and deadlock the bus (see usb-kernel-recover skill) — leave it be - print('skipping cleanup after unrecovered hang: reboot required to release the bus', + print('skipping cleanup after unrecovered hang: ask the operator for a full PVE host ' + 'power cycle (a VM reboot is not reliable — hubs latch up across the PCIe reset)', file=sys.stderr) elif not args.keep_binding: sysfs_write(DRIVER / 'remove_id', f'{VID} {PID}', check=False) From 1d915b6b59cb88f14344521db1f9345d8c7dc9a7 Mon Sep 17 00:00:00 2001 From: Ha Thach Date: Tue, 28 Jul 2026 12:50:28 +0700 Subject: [PATCH 09/10] bsp, hil: flash WCH boards with the unified OpenOCD fork (#3791) bsp, hil: flash with the unified OpenOCD fork https://github.com/hathach/openocd (branch tinyusb) is mainline plus every config these boards need: RPi RP2350, ADI max32/max78, the MounRiver WCH configs, and the wlinke adapter on mainline's riscv target. It is a superset of the vendor forks, so one 'openocd' covers all boards; -DOPENOCD=/OPENOCD= still select another, msdk's when MAXIM_PATH is set. Drops family_flash_openocd_wch and the OPENOCD_WCH pair, dedups family_flash_openocd_adi, aligns ch583's work area, and points hil at the flasher's own config instead of generating one per probe. Verified: HIL green on all four WCH boards and max32666fthr. --- .idea/debugServers/wch_riscv.xml | 2 +- hw/bsp/ch32v10x/family.cmake | 2 +- hw/bsp/ch32v10x/family.mk | 2 +- hw/bsp/ch32v20x/family.cmake | 2 +- hw/bsp/ch32v20x/family.mk | 2 +- hw/bsp/ch32v30x/family.cmake | 2 +- hw/bsp/ch32v30x/family.mk | 2 +- hw/bsp/ch583/family.cmake | 2 +- hw/bsp/ch583/family.mk | 2 +- hw/bsp/ch583/wch-riscv.cfg | 2 +- hw/bsp/family_rules.mk | 16 +++++------- hw/bsp/family_support.cmake | 44 ++++++++++--------------------- hw/bsp/rp2040/family.cmake | 2 ++ test/hil/hil_test.py | 45 +++++++------------------------- test/hil/tinyusb.json | 10 +++---- 15 files changed, 47 insertions(+), 90 deletions(-) diff --git a/.idea/debugServers/wch_riscv.xml b/.idea/debugServers/wch_riscv.xml index 2e147f1b6..0b2b83b2e 100644 --- a/.idea/debugServers/wch_riscv.xml +++ b/.idea/debugServers/wch_riscv.xml @@ -4,7 +4,7 @@ - + diff --git a/hw/bsp/ch32v10x/family.cmake b/hw/bsp/ch32v10x/family.cmake index fb9ccb3a3..287b8c2ff 100644 --- a/hw/bsp/ch32v10x/family.cmake +++ b/hw/bsp/ch32v10x/family.cmake @@ -93,6 +93,6 @@ function(family_configure_example TARGET RTOS) # Flashing family_add_bin_hex(${TARGET}) - family_flash_openocd_wch(${TARGET}) + family_flash_openocd(${TARGET}) #family_flash_uf2(${TARGET} ${UF2_FAMILY_ID}) endfunction() diff --git a/hw/bsp/ch32v10x/family.mk b/hw/bsp/ch32v10x/family.mk index fb699b0bb..443509699 100644 --- a/hw/bsp/ch32v10x/family.mk +++ b/hw/bsp/ch32v10x/family.mk @@ -49,5 +49,5 @@ INC += \ FREERTOS_PORTABLE_SRC = $(FREERTOS_PORTABLE_PATH)/RISC-V -OPENOCD_WCH_OPTION=-f $(TOP)/$(FAMILY_PATH)/wch-riscv.cfg +OPENOCD_OPTION=-f $(TOP)/$(FAMILY_PATH)/wch-riscv.cfg flash: flash-openocd-wch diff --git a/hw/bsp/ch32v20x/family.cmake b/hw/bsp/ch32v20x/family.cmake index 785f5ee35..a27ff021e 100644 --- a/hw/bsp/ch32v20x/family.cmake +++ b/hw/bsp/ch32v20x/family.cmake @@ -125,7 +125,7 @@ function(family_configure_example TARGET RTOS) # Flashing family_add_bin_hex(${TARGET}) - family_flash_openocd_wch(${TARGET}) + family_flash_openocd(${TARGET}) family_flash_wlink_rs(${TARGET}) #family_flash_uf2(${TARGET} ${UF2_FAMILY_ID}) endfunction() diff --git a/hw/bsp/ch32v20x/family.mk b/hw/bsp/ch32v20x/family.mk index 1d059bcba..1889c4e26 100644 --- a/hw/bsp/ch32v20x/family.mk +++ b/hw/bsp/ch32v20x/family.mk @@ -63,6 +63,6 @@ INC += \ FREERTOS_PORTABLE_SRC = $(FREERTOS_PORTABLE_PATH)/RISC-V -OPENOCD_WCH_OPTION=-f $(TOP)/$(FAMILY_PATH)/wch-riscv.cfg +OPENOCD_OPTION=-f $(TOP)/$(FAMILY_PATH)/wch-riscv.cfg flash: flash-wlink-rs #flash: flash-openocd-wch diff --git a/hw/bsp/ch32v30x/family.cmake b/hw/bsp/ch32v30x/family.cmake index b974bd5e7..e33e4b85d 100644 --- a/hw/bsp/ch32v30x/family.cmake +++ b/hw/bsp/ch32v30x/family.cmake @@ -115,6 +115,6 @@ function(family_configure_example TARGET RTOS) # Flashing family_add_bin_hex(${TARGET}) - family_flash_openocd_wch(${TARGET}) + family_flash_openocd(${TARGET}) family_flash_wlink_rs(${TARGET}) endfunction() diff --git a/hw/bsp/ch32v30x/family.mk b/hw/bsp/ch32v30x/family.mk index 5ccdea8ae..59778ec54 100644 --- a/hw/bsp/ch32v30x/family.mk +++ b/hw/bsp/ch32v30x/family.mk @@ -62,5 +62,5 @@ LD_FILE ?= $(FAMILY_PATH)/linker/ch32v30x.ld # For freeRTOS port source FREERTOS_PORTABLE_SRC = $(FREERTOS_PORTABLE_PATH)/RISC-V -OPENOCD_WCH_OPTION=-f $(TOP)/$(FAMILY_PATH)/wch-riscv.cfg +OPENOCD_OPTION=-f $(TOP)/$(FAMILY_PATH)/wch-riscv.cfg flash: flash-openocd-wch diff --git a/hw/bsp/ch583/family.cmake b/hw/bsp/ch583/family.cmake index a379298e5..f4b874e4e 100644 --- a/hw/bsp/ch583/family.cmake +++ b/hw/bsp/ch583/family.cmake @@ -104,5 +104,5 @@ function(family_configure_example TARGET RTOS) # Flashing family_add_bin_hex(${TARGET}) - family_flash_openocd_wch(${TARGET}) + family_flash_openocd(${TARGET}) endfunction() diff --git a/hw/bsp/ch583/family.mk b/hw/bsp/ch583/family.mk index 98d0f9337..3444c4811 100644 --- a/hw/bsp/ch583/family.mk +++ b/hw/bsp/ch583/family.mk @@ -52,7 +52,7 @@ INC += \ LD_FILE ?= $(FAMILY_PATH)/linker/ch582.ld -OPENOCD_WCH_OPTION=-f $(TOP)/$(FAMILY_PATH)/wch-riscv.cfg +OPENOCD_OPTION=-f $(TOP)/$(FAMILY_PATH)/wch-riscv.cfg flash: flash-openocd-wch # For freeRTOS port source diff --git a/hw/bsp/ch583/wch-riscv.cfg b/hw/bsp/ch583/wch-riscv.cfg index 64d595d8e..aa35aa9c5 100644 --- a/hw/bsp/ch583/wch-riscv.cfg +++ b/hw/bsp/ch583/wch-riscv.cfg @@ -9,7 +9,7 @@ sdi newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x00001 set _TARGETNAME $_CHIPNAME.cpu target create $_TARGETNAME.0 wch_riscv -chain-position $_TARGETNAME -$_TARGETNAME.0 configure -work-area-phys 0x20000000 -work-area-size 0x8000 -work-area-backup 1 +$_TARGETNAME.0 configure -work-area-phys 0x20000000 -work-area-size 10000 -work-area-backup 1 set _FLASHNAME $_CHIPNAME.flash flash bank $_FLASHNAME wch_riscv 0x00000000 0 0 0 $_TARGETNAME.0 diff --git a/hw/bsp/family_rules.mk b/hw/bsp/family_rules.mk index ccf49dd0e..011572888 100644 --- a/hw/bsp/family_rules.mk +++ b/hw/bsp/family_rules.mk @@ -130,20 +130,18 @@ flash-pyocd: $(BUILD)/$(PROJECT).hex #pyocd reset -t $(PYOCD_TARGET) # --------------- openocd ----------------- +# OPENOCD can name another build, e.g. one of the vendor forks, though +# https://github.com/hathach/openocd branch tinyusb covers every board here +OPENOCD ?= openocd OPENOCD_OPTION ?= flash-openocd: $(BUILD)/$(PROJECT).elf - openocd $(OPENOCD_OPTION) -c "program $< verify reset exit" + $(OPENOCD) $(OPENOCD_OPTION) -c "program $< verify reset exit" # --------------- openocd-wch ----------------- -# wch-linke is not supported yet in official openOCD yet. We need to either use -# 1. download openocd as part of mounriver studio http://www.mounriver.com/download or -# 2. compiled from https://github.com/hathach/riscv-openocd-wch or -# https://github.com/dragonlock2/miscboards/blob/main/wch/SDK/riscv-openocd.tar.xz -# with ./configure --disable-werror --enable-wlinke --enable-ch347=no -OPENOCD_WCH ?= /home/${USER}/app/riscv-openocd-wch/src/openocd -OPENOCD_WCH_OPTION ?= +# WCH parts need an openocd built with the wlinke adapter. The image is written +# without verify: WCH code flash is not readable back over the debug bus. flash-openocd-wch: $(BUILD)/$(PROJECT).elf - $(OPENOCD_WCH) $(OPENOCD_WCH_OPTION) -c init -c halt -c "flash write_image $<" -c reset -c exit + $(OPENOCD) $(OPENOCD_OPTION) -c init -c halt -c "flash write_image $<" -c reset -c exit # --------------- wlink-rs ----------------- # flash with https://github.com/ch32-rs/wlink diff --git a/hw/bsp/family_support.cmake b/hw/bsp/family_support.cmake index 1f3952205..33ceb49c2 100644 --- a/hw/bsp/family_support.cmake +++ b/hw/bsp/family_support.cmake @@ -688,7 +688,9 @@ function(family_flash_stflash TARGET) endfunction() -# Add flash openocd target +# Add flash openocd target. +# The default 'openocd' should be https://github.com/hathach/openocd (branch tinyusb): which is mainline plus +# every config the rig needs (RP2350, MAX32/MAX78, WCH) and a drop-in superset of the vendor (downstream) forks function(family_flash_openocd TARGET) if (NOT DEFINED OPENOCD) set(OPENOCD openocd) @@ -715,38 +717,20 @@ function(family_flash_openocd TARGET) #set_property(TARGET ${TARGET}-openocd PROPERTY FOLDER ${TARGET}-group) endfunction() - -# Add flash openocd-wch target -# compiled from https://github.com/hathach/riscv-openocd-wch or https://github.com/dragonlock2/miscboards/blob/main/wch/SDK/riscv-openocd.tar.xz -function(family_flash_openocd_wch TARGET) - if (NOT DEFINED OPENOCD) - set(OPENOCD $ENV{HOME}/app/riscv-openocd-wch/src/openocd) +# Add flash openocd adi (Analog Devices) target using the openocd included +# with msdk (MAXIM_PATH), otherwise the default openocd +function(family_flash_openocd_adi TARGET) + # use openocd from msdk if MAXIM_PATH is set, as cmake variable or in the + # environment. Normalize the latter since msdk can be Windows (MinGW) or Linux + if (NOT DEFINED MAXIM_PATH AND DEFINED ENV{MAXIM_PATH}) + file(TO_CMAKE_PATH "$ENV{MAXIM_PATH}" MAXIM_PATH) endif () - family_flash_openocd(${TARGET}) -endfunction() - - -# Add flash openocd adi (Analog Devices) target -# included with msdk or compiled from release branch of https://github.com/analogdevicesinc/openocd -function(family_flash_openocd_adi TARGET) - if (DEFINED MAXIM_PATH) - # use openocd from msdk with MAXIM_PATH cmake variable first if the user specified it - set(OPENOCD ${MAXIM_PATH}/Tools/OpenOCD/openocd) - set(OPENOCD_OPTION2 "-s ${MAXIM_PATH}/Tools/OpenOCD/scripts") - elseif (DEFINED ENV{MAXIM_PATH}) - # use openocd from msdk with MAXIM_PATH environment variable. Normalize - # since msdk can be Windows (MinGW) or Linux - file(TO_CMAKE_PATH "$ENV{MAXIM_PATH}" MAXIM_PATH_NORM) - set(OPENOCD ${MAXIM_PATH_NORM}/Tools/OpenOCD/openocd) - set(OPENOCD_OPTION2 "-s ${MAXIM_PATH_NORM}/Tools/OpenOCD/scripts") - else() - # compiled from source - if (NOT DEFINED OPENOCD_ADI_PATH) - set(OPENOCD_ADI_PATH $ENV{HOME}/app/openocd_adi) + if (MAXIM_PATH) + if (NOT DEFINED OPENOCD) + set(OPENOCD ${MAXIM_PATH}/Tools/OpenOCD/openocd) endif () - set(OPENOCD ${OPENOCD_ADI_PATH}/src/openocd) - set(OPENOCD_OPTION2 "-s ${OPENOCD_ADI_PATH}/tcl") + set(OPENOCD_OPTION2 "-s ${MAXIM_PATH}/Tools/OpenOCD/scripts") endif () family_flash_openocd(${TARGET}) diff --git a/hw/bsp/rp2040/family.cmake b/hw/bsp/rp2040/family.cmake index aab9a4fae..43b1dc234 100644 --- a/hw/bsp/rp2040/family.cmake +++ b/hw/bsp/rp2040/family.cmake @@ -28,6 +28,8 @@ elseif (PICO_PLATFORM STREQUAL "rp2350-arm-s" OR PICO_PLATFORM STREQUAL "rp2350" set(OPENOCD_TARGET rp2350) elseif (PICO_PLATFORM STREQUAL "rp2350-riscv") set(JLINK_DEVICE rp2350_riscv_0) + # rp2350-riscv.cfg needs the raspberrypi/openocd fork: mainline's riscv + # target does not take the -dap/-ap-num the config uses set(OPENOCD_TARGET rp2350-riscv) endif() diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py index 0efc6826f..80d1e1823 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -24,10 +24,11 @@ # Host setup (required: a missing tool fails its test rather than skipping it): # - System packages: sudo apt install mtools libmtp9 alsa-utils iperf -# mtools - read_disk_file (device/cdc_msc, device/msc_dual_lun) -# libmtp9 - pymtp ctypes load (device/mtp); Debian 13 uses libmtp9t64 -# alsa-utils - arecord (device/audio_test_freertos) -# iperf - throughput tests (device/net_lwip_*) +# mtools read_disk_file (device/cdc_msc, device/msc_dual_lun) +# libmtp9 pymtp ctypes load (device/mtp); Debian 13 uses libmtp9t64 +# alsa-utils arecord (device/audio_test_freertos) +# iperf throughput tests (device/net_lwip_*) +# openocd unified openocd from https://github.com/hathach/openocd (branch tinyusb) for wch, rp2040/rp2350, analog max32 # - device/usbtest: usbtest kernel module + testusb binary (kernel tools/usb/testusb.c) on PATH, # plus sudo for modprobe / sysfs writes # - Python packages: pip install -r requirements.txt @@ -377,25 +378,6 @@ def cmd_stdout_text(out: Any) -> str: return out.decode('utf-8', errors='ignore') return str(out) -WCH_RISCV_CONTENT = """ -adapter driver wlinke -adapter speed 6000 -transport select sdi - -wlink_set_address 0x00000000 -set _CHIPNAME wch_riscv -sdi newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x00001 - -set _TARGETNAME $_CHIPNAME.cpu - -target create $_TARGETNAME.0 wch_riscv -chain-position $_TARGETNAME -$_TARGETNAME.0 configure -work-area-phys 0x20000000 -work-area-size 10000 -work-area-backup 1 -set _FLASHNAME $_CHIPNAME.flash - -flash bank $_FLASHNAME wch_riscv 0x00000000 0 0 0 $_TARGETNAME.0 - -echo "Ready for Remote Connections" -""" MSC_README_TXT = \ b"This is tinyusb's MassStorage Class demo.\r\n\r\n\ @@ -662,24 +644,15 @@ def reset_openocd(board): def flash_openocd_wch(board, firmware): flasher = board['flasher'] - f_wch = f"wch-riscv_{board['uid']}.cfg" - if not os.path.exists(f_wch): - with open(f_wch, 'w') as file: - file.write(WCH_RISCV_CONTENT) - - ret = run_cmd(f'openocd_wch -c "adapter serial {flasher["uid"]}" -f {f_wch} ' - f'-c "program {firmware}.elf reset exit"') + ret = run_cmd(f'openocd -c "tcl_port disabled" -c "gdb_port disabled" -c "telnet_port disabled" ' + f'-c "adapter serial {flasher["uid"]}" {flasher.get("args", "")} -c "program {firmware}.elf reset exit"') return ret def reset_openocd_wch(board): flasher = board['flasher'] - f_wch = f"wch-riscv_{board['uid']}.cfg" - if not os.path.exists(f_wch): - with open(f_wch, 'w') as file: - file.write(WCH_RISCV_CONTENT) - - ret = run_cmd(f'openocd_wch -c "adapter serial {flasher["uid"]}" -f {f_wch} -c "program reset exit"') + ret = run_cmd(f'openocd -c "tcl_port disabled" -c "gdb_port disabled" -c "telnet_port disabled" ' + f'-c "adapter serial {flasher["uid"]}" {flasher.get("args", "")} -c "init; reset run; exit"') return ret diff --git a/test/hil/tinyusb.json b/test/hil/tinyusb.json index 812eb7571..8316dbc33 100644 --- a/test/hil/tinyusb.json +++ b/test/hil/tinyusb.json @@ -147,7 +147,7 @@ "dual": false }, "flasher": { - "name": "openocd_adi", + "name": "openocd", "uid": "E6614C311B597D32", "args": "-f interface/cmsis-dap.cfg -f target/max32665.cfg" } @@ -465,7 +465,7 @@ "flasher": { "name": "openocd_wch", "uid": "EBCA8F0670AF", - "args": "" + "args": "-f target/wch-riscv.cfg" } }, { @@ -480,7 +480,7 @@ "flasher": { "name": "openocd_wch", "uid": "BC4954081051", - "args": "" + "args": "-f target/wch-riscv.cfg" } }, { @@ -499,7 +499,7 @@ "flasher": { "name": "openocd_wch", "uid": "BC5DA47360D0", - "args": "" + "args": "-f target/wch-riscv.cfg" } }, { @@ -514,7 +514,7 @@ "flasher": { "name": "openocd_wch", "uid": "7FD88F0604B5", - "args": "" + "args": "-f target/wch-riscv.cfg" } }, { From 3fdd294b95143f909a6c991943a2e55e638b177a Mon Sep 17 00:00:00 2001 From: hathach Date: Tue, 28 Jul 2026 14:43:32 +0700 Subject: [PATCH 10/10] docs: add the unified OpenOCD plan, note worktree dep symlinks The plan doc records why the fork exists and how each vendor source was ported; the interim handoff it superseded is dropped. CLAUDE.md: a new worktree should symlink the dependency dirs to the primary checkout rather than re-fetching them, replacing a single symlink only when the branch needs a different dep revision. Also allow 'linke' in codespell - WCH-LinkE is a product name. --- CLAUDE.md | 2 +- .../plans/2026-07-27-openocd-unified-fork.md | 602 ++++++++++++++++++ tools/codespell/ignore-words.txt | 1 + 3 files changed, 604 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/plans/2026-07-27-openocd-unified-fork.md diff --git a/CLAUDE.md b/CLAUDE.md index 77dab4565..94b8192b7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,7 +12,7 @@ Bias toward caution over speed. For trivial tasks, use judgment. - **Simplicity** — no features, abstractions, flexibility, or error handling beyond what was asked. If 200 lines could be 50, rewrite. - **Surgical changes** — touch only what the task requires; match existing style; don't refactor working code; mention unrelated dead code rather than deleting it. Remove only orphans *your* changes created. - **Goal-driven** — turn tasks into verifiable goals ("write failing test, make it pass"). For multi-step work, state a brief `step → verify` plan. -- **Worktrees** — default to a git worktree for any branch or multi-step work; never switch the shared primary checkout's branch. Sessions run concurrently: switching the primary checkout mid-flight disrupts other sessions and can silently point a review, build, or commit at the wrong diff. Only trivial one-shot fixes may skip this. Standard location: `.worktrees/` at the repo root (gitignored), e.g. `git worktree add .worktrees/my-branch -b my-branch`. +- **Worktrees** — default to a git worktree for any branch or multi-step work; never switch the shared primary checkout's branch. Sessions run concurrently: switching the primary checkout mid-flight disrupts other sessions and can silently point a review, build, or commit at the wrong diff. Only trivial one-shot fixes may skip this. Standard location: `.worktrees/` at the repo root (gitignored), e.g. `git worktree add .worktrees/my-branch -b my-branch`. In a new worktree, symlink the dependency dirs (`lib/*`, `hw/mcu/*`, `tools/linkermap` — the keys of `deps_all` in `tools/get_deps.py`) to the primary checkout instead of re-cloning them; only if the branch needs a different dep revision, replace that one symlink with a real dir and run `get_deps.py` for it. ## Ground Rules diff --git a/docs/superpowers/plans/2026-07-27-openocd-unified-fork.md b/docs/superpowers/plans/2026-07-27-openocd-unified-fork.md new file mode 100644 index 000000000..e0f1f9678 --- /dev/null +++ b/docs/superpowers/plans/2026-07-27-openocd-unified-fork.md @@ -0,0 +1,602 @@ +# Unified OpenOCD Fork (`hathach/openocd`) Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** One OpenOCD fork at `hathach/openocd` (default branch `tinyusb`) that flashes, debugs and RTT-captures every TinyUSB rig target — RP2040, RP2350 (arm + riscv), all WCH CH32/CH5xx, Analog Devices MAX32, and Espressif — replacing the four separate OpenOCD trees on ci. + +**Architecture:** Fork `openocd-org/openocd` master (mainline is 1610 commits ahead of the RPi fork base and now the sole home of RISC-V support). Layer on top: 4 RP2350 TCL configs from the RPi fork, 1 ported max32665 TCL config from the ADI fork, the `wlinke` adapter + `sdi` transport + WCH flash drivers from `hathach/riscv-openocd-wch` (driving CH32 with **mainline's** riscv target if the DTM hypothesis holds), and ESP32-P4 TCL configs adapted from `espressif/openocd-esp32` onto mainline's generic-riscv ESP pattern. ESP32/S2/S3/C3/C6/H2 debug is already in mainline; ESP flash stays with esptool. + +**Tech Stack:** OpenOCD (autotools, C), TCL configs, GitHub CLI, TinyUSB HIL rig (`hil_test.py`, `board_lock.py`). + +## Global Constraints + +- Everything runs **on ci** (this machine *is* the rig — hostname `ci`); no SSH hop needed. +- Repo: `hathach/openocd`, default branch **`tinyusb`**, source clone at `~/app/openocd`, install prefix `$HOME/app/openocd_tinyusb`. +- **One commit per downstream fork** on the `tinyusb` branch: one for raspberrypi/openocd, one for analogdevicesinc/openocd, one for riscv-openocd-wch, one for espressif/openocd-esp32 (plus the initial README commit). Iterate with `git commit --amend` / squash before declaring a task done. +- No `Co-Authored-By: Claude` / `Claude-Session:` trailers in any commit. +- **`~/.local/bin/openocd_wch` (symlink) and `~/app/openocd_wch_new` stay untouched until Task 8's 4/4 WCH boards pass** — it is the rig's only CH32 flasher. Backup exists at `~/.local/bin/openocd_wch.bak-20260727`. +- Hold a board lock for every hardware step: `python3 test/hil/board_lock.py hold --reason "openocd-unified verify"`; release after. **Never stop the actions-runner.** +- WCH RTT: always `rtt polling_interval 1`; **never `reset run` inside an SDI session** (target does not come back). +- `pkill -x openocd` — never `pkill -f` (pattern matches your own shell). +- `libjim-dev` is required to configure mainline; all build deps are already installed on ci (mainline was built here 2026-07-27). +- Back up before replacing `/usr/local/bin/openocd`; the current binary is the RPi-fork build (byte-identical to `~/app/openocd_rpi/src/openocd`). +- Do not modify the TinyUSB checkout at `~/code/tinyusb` except where a task explicitly says so (hil_test.py WCH cfg template, on a `claude/`-prefixed branch). Never `git stash -u` in a TinyUSB worktree. +- OpenOCD resolves its scripts dir relative to the **realpath** of the binary — repoint via symlink into an installed prefix, never a bare copy of the binary. + +## Reference: current state (measured 2026-07-27, in `OPENOCD_UNIFIED_FORK_HANDOFF.md`) + +| Tree on ci | Repo @ commit | Role | +| --- | --- | --- | +| `~/app/openocd_rpi` | raspberrypi/openocd @ `ebec9504d` (sdk-2.0.0) | rig default (`/usr/local/bin/openocd`) | +| `~/app/openocd_adi` | analogdevicesinc/openocd @ `5fc33af` | max32666fthr (`~/app/openocd_adi/src/openocd`) | +| `~/app/riscv-openocd-wch` | hathach/riscv-openocd-wch @ `ccb04d7` | CH32 flash+RTT (`~/.local/bin/openocd_wch`) | +| `~/app/openocd-mainline` | openocd-org/openocd @ `43441cd83` | candidate build, verified on pico/pico2/max32666fthr | + +Rig flasher entries (`test/hil/tinyusb.json`): `openocd` (pico ×3, fruit_jam, stm32h743nucleo, stm32g0b1nucleo), `openocd_adi` (max32666fthr), `openocd_wch` (nanoch32v203, ch32v103r_r1_1v0, ch32v307v_r1_1v0, ch582m_evt), `esptool` (espressif_s3_devkitm, espressif_p4_function_ev). + +--- + +### Task 1: Create `hathach/openocd`, `tinyusb` branch, README + +**Files:** +- Create: `~/app/openocd/` (clone), `~/app/openocd/README.md` + +**Interfaces:** +- Produces: GitHub repo `hathach/openocd` with default branch `tinyusb`; local clone `~/app/openocd` with remotes `origin` (hathach) and `upstream` (openocd-org). All later tasks commit to this clone's `tinyusb` branch. + +- [ ] **Step 1: Fork and clone** + +```bash +gh repo fork openocd-org/openocd --clone=false +git clone --recursive https://github.com/hathach/openocd.git ~/app/openocd +cd ~/app/openocd +git remote add upstream https://github.com/openocd-org/openocd.git +git checkout -b tinyusb origin/master +``` + +- [ ] **Step 2: Verify the clone is at mainline HEAD** + +Run: `cd ~/app/openocd && git log --oneline -1` +Expected: `43441cd83 server: add 'services' command to list service information` or newer. + +- [ ] **Step 3: Write `README.md`** (new file — GitHub renders it instead of mainline's plain-text `README`, and leaving `README` untouched keeps future rebases conflict-free) + +```markdown +# OpenOCD for the TinyUSB test rig + +One OpenOCD build that flashes, debugs and RTT-captures every board family on +the [TinyUSB](https://github.com/hathach/tinyusb) hardware-in-the-loop rig, so +the rig does not need four different OpenOCD trees. + +This is the `tinyusb` branch, tracking +[openocd-org/openocd](https://github.com/openocd-org/openocd) `master`. +Everything not listed below is unmodified mainline. + +## Cherry-picked / ported from + +| Source repo | What we took | +| --- | --- | +| [raspberrypi/openocd](https://github.com/raspberrypi/openocd) (`sdk-2.0.0`) | `tcl/target/rp2350-riscv.cfg`, `rp2350-rescue.cfg`, `rp2350-dbgkey-secure.cfg`, `rp2350-dbgkey-nonsecure.cfg`. The RP2040/RP2350 C flash driver is already better in mainline (`rp2xxx.c`). | +| [analogdevicesinc/openocd](https://github.com/analogdevicesinc/openocd) (`release`) | `tcl/target/max32665.cfg` (MAX32665/MAX32666), re-ported onto mainline's `max32xxx_common.cfg`. The fork's QSPI block is dropped — it is guarded by `QSPI_ENABLE`, which this part sets to 0. | +| [hathach/riscv-openocd-wch](https://github.com/hathach/riscv-openocd-wch) (originally [dragonlock2/miscboards](https://github.com/dragonlock2/miscboards) WCH SDK) | `wlinke` adapter driver, `sdi` single-wire transport, and the WCH flash drivers (`wch_riscv`, `wch_arm`) for CH32V/CH32F/CH5xx over WCH-Link/LinkE. | +| [espressif/openocd-esp32](https://github.com/espressif/openocd-esp32) | `tcl/target/esp32p4.cfg` + `tcl/board/esp32p4-builtin.cfg`, adapted to mainline's generic RISC-V ESP pattern. ESP32/S2/S3/C3/C6/H2 debug is already in mainline; ESP flash programming stays with `esptool`. | + +## Build + + ./bootstrap + ./configure --enable-jlink --enable-cmsis-dap --enable-stlink \ + --enable-wlinke --disable-werror + make -j$(nproc) + +`libjim-dev` is required — mainline no longer builds the bundled jimtcl by +default and configure hard-fails without it. +``` + +- [ ] **Step 4: Commit, push, set default branch** + +```bash +cd ~/app/openocd +git add README.md +git commit -m "README: purpose of the tinyusb branch and its downstream sources" +git push -u origin tinyusb +gh repo edit hathach/openocd --default-branch tinyusb \ + --description "OpenOCD for the TinyUSB test rig - one build for RP2040/RP2350, WCH CH32, MAX32 and ESP32 targets" +``` + +- [ ] **Step 5: Verify default branch** + +Run: `gh repo view hathach/openocd --json defaultBranchRef -q .defaultBranchRef.name` +Expected: `tinyusb` + +--- + +### Task 2: Build the fork on ci + +**Files:** +- Create: `~/app/openocd_tinyusb/` (install prefix) + +**Interfaces:** +- Consumes: `~/app/openocd` clone from Task 1. +- Produces: `~/app/openocd_tinyusb/bin/openocd` (installed binary + scripts at `~/app/openocd_tinyusb/share/openocd/scripts/`). Every later flash/verify step uses this path. + +- [ ] **Step 1: Configure and build** (same recipe that already worked for mainline on this box) + +```bash +cd ~/app/openocd +./bootstrap +./configure --prefix=$HOME/app/openocd_tinyusb \ + --enable-jlink --enable-cmsis-dap --enable-stlink --disable-werror +make -j$(nproc) && make install +``` + +- [ ] **Step 2: Verify version and adapters** + +Run: `~/app/openocd_tinyusb/bin/openocd --version 2>&1 | head -1` +Expected: `Open On-Chip Debugger 0.12.0+dev-...` with a `-g` matching `git -C ~/app/openocd rev-parse --short HEAD`. + +Run: `~/app/openocd_tinyusb/bin/openocd -c 'adapter list; shutdown' 2>&1 | grep -E 'cmsis-dap|jlink|stlink'` +Expected: all three listed. + +*(No commit — build products only.)* + +--- + +### Task 3: Import the 5 TCL configs — one commit per downstream fork + +**Files:** +- Create: `~/app/openocd/tcl/target/rp2350-riscv.cfg`, `rp2350-rescue.cfg`, `rp2350-dbgkey-secure.cfg`, `rp2350-dbgkey-nonsecure.cfg`, `max32665.cfg` +- Source of truth: `~/code/tinyusb/openocd-unified-configs/` (the copies already hardware-verified this week; the max32665 port is already written there) + +**Interfaces:** +- Consumes: `~/app/openocd` + install prefix from Task 2. +- Produces: `target/rp2350-riscv.cfg` and `target/max32665.cfg` resolvable via `find` in the installed scripts dir — Task 4 flashes with them. + +- [ ] **Step 1: Copy the RPi configs and commit (downstream commit #1)** + +```bash +cd ~/app/openocd +cp ~/code/tinyusb/openocd-unified-configs/rp2350-riscv.cfg \ + ~/code/tinyusb/openocd-unified-configs/rp2350-rescue.cfg \ + ~/code/tinyusb/openocd-unified-configs/rp2350-dbgkey-secure.cfg \ + ~/code/tinyusb/openocd-unified-configs/rp2350-dbgkey-nonsecure.cfg \ + tcl/target/ +git add tcl/target/rp2350-*.cfg +git commit -m "tcl/target: add RP2350 riscv/rescue/dbgkey configs from raspberrypi/openocd + +Taken from raspberrypi/openocd branch sdk-2.0.0 @ ebec9504d. These four +configs are the only things that fork has which mainline lacks - the +rp2040/rp2350 C driver was consolidated upstream as rp2xxx.c. All four +use only mainline-present commands (swj_newdap, dap create -adiv6, +target create riscv -ap-num, riscv set_enable_virt2phys). + +rp2350-riscv.cfg is what hw/bsp/rp2040/family.cmake requests when +PICO_PLATFORM=rp2350-riscv." +``` + +- [ ] **Step 2: Copy the ADI config and commit (downstream commit #2)** + +```bash +cd ~/app/openocd +cp ~/code/tinyusb/openocd-unified-configs/max32665.cfg tcl/target/ +git add tcl/target/max32665.cfg +git commit -m "tcl/target: add max32665 config ported from analogdevicesinc/openocd + +Ported from analogdevicesinc/openocd @ 5fc33af onto mainline's +max32xxx_common.cfg (the ADI fork calls the same file max32xxx.cfg). +The fork's QSPI block is dropped: it is guarded by QSPI_ENABLE, which +this part sets to 0, and it needs the ADI-only max32xxx_qspi driver. +Covers MAX32665/MAX32666 (both flash banks). Hardware-verified on +max32666fthr 2026-07-27." +``` + +- [ ] **Step 3: Install and verify the configs resolve** + +```bash +cd ~/app/openocd && make install +~/app/openocd_tinyusb/bin/openocd -c 'puts [find target/max32665.cfg]; puts [find target/rp2350-riscv.cfg]; shutdown' +``` +Expected: both paths under `~/app/openocd_tinyusb/share/openocd/scripts/target/` printed; exit without "Can't find". + +- [ ] **Step 4: Push** + +```bash +cd ~/app/openocd && git push +``` + +--- + +### Task 4: Hardware-verify every current-openocd board with the fork binary + +**Files:** +- No source changes. Uses `~/code/tinyusb` builds + `test/hil/hil_test.py`. + +**Interfaces:** +- Consumes: `~/app/openocd_tinyusb/bin/openocd` with Task 3 configs installed. +- Produces: evidence that the fork can replace `/usr/local/bin/openocd` (Task 5's gate). PATH shim dir `~/app/openocd_tinyusb/shim/` reused by later tasks. + +Boards (every `openocd`/`openocd_adi` flasher entry in `tinyusb.json`): +`raspberry_pi_pico`, `raspberry_pi_pico_w`, `raspberry_pi_pico2`, `adafruit_fruit_jam`, `stm32h743nucleo`, `stm32g0b1nucleo`, `max32666fthr`. +Already verified on plain mainline 2026-07-27: pico, pico2, max32666fthr (re-run anyway — the binary changed). + +- [ ] **Step 1: Build any missing firmware sets** (repeat per board without `examples/cmake-build-`; `cmake-build-raspberry_pi_pico`, `-stm32g0b1nucleo`, `-max32666fthr` already exist) + +```bash +cd ~/code/tinyusb/examples +cmake -B cmake-build-raspberry_pi_pico2 -DBOARD=raspberry_pi_pico2 -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel . \ + && cmake --build cmake-build-raspberry_pi_pico2 +``` +(Same pattern for `raspberry_pi_pico_w`, `adafruit_fruit_jam`, `stm32h743nucleo`. If a board fails `get_deps`, run `python3 tools/get_deps.py -b ` first.) + +- [ ] **Step 2: Create the PATH shim** (lets `hil_test.py`'s hardcoded `openocd` resolve to the fork; symlink keeps scripts-dir resolution working because OpenOCD follows the realpath) + +```bash +mkdir -p ~/app/openocd_tinyusb/shim +ln -sf ~/app/openocd_tinyusb/bin/openocd ~/app/openocd_tinyusb/shim/openocd +``` + +- [ ] **Step 3: Smoke-flash one board directly** (fast signal before the full suite) + +```bash +python3 ~/code/tinyusb/test/hil/board_lock.py hold raspberry_pi_pico --reason "openocd-unified verify" +~/app/openocd_tinyusb/bin/openocd -c "adapter serial E6614103E72C1D2F" \ + -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000" \ + -c "program /home/hathach/code/tinyusb/examples/cmake-build-raspberry_pi_pico/device/cdc_msc/cdc_msc.elf verify reset exit" +``` +Expected: `** Verified OK **` then `** Resetting Target **`. Release the lock after (`board_lock.py release raspberry_pi_pico`). + +- [ ] **Step 4: Run the HIL suite for all 7 boards through the shim** + +```bash +cd ~/code/tinyusb +PATH=~/app/openocd_tinyusb/shim:$PATH \ +python3 test/hil/hil_test.py test/hil/tinyusb.json \ + -b raspberry_pi_pico -b raspberry_pi_pico_w -b raspberry_pi_pico2 \ + -b adafruit_fruit_jam -b stm32h743nucleo -b stm32g0b1nucleo +``` +Notes for the executor: +- `hil_test.py` takes the config as a positional arg and `-b` per board; it holds board locks itself (that is the board-lock protocol in CI — do not also hold manual locks around `hil_test.py` runs). +- max32666fthr is **not** in this run: its `flash_openocd_adi()` path uses the hardcoded `OPENCOD_ADI_PATH = ~/app/openocd_adi` (`hil_test.py:408`), which the shim can't intercept. Handle it in Step 4b instead. Do not edit `hil_test.py` for this — the adi path disappears at cutover (Task 10 flips `tinyusb.json`'s flasher entry to plain `openocd` with `-f interface/cmsis-dap.cfg -f target/max32665.cfg`). +- Expected: every board PASS in the report. Any failure: stop, diagnose (consult the `hil` skill), do not proceed to Task 5. + +- [ ] **Step 4b: max32666fthr — manual flash with the fork, then tests with `--skip-flash`** + +```bash +python3 ~/code/tinyusb/test/hil/board_lock.py hold max32666fthr --reason "openocd-unified verify" +~/app/openocd_tinyusb/bin/openocd -c "adapter serial E6614C311B597D32" \ + -f interface/cmsis-dap.cfg -f target/max32665.cfg \ + -c "program /home/hathach/code/tinyusb/examples/cmake-build-max32666fthr/device/cdc_msc/cdc_msc.elf verify reset exit" +python3 ~/code/tinyusb/test/hil/board_lock.py release max32666fthr +cd ~/code/tinyusb && python3 test/hil/hil_test.py test/hil/tinyusb.json -b max32666fthr -sf +``` +Expected: `** Verified OK **` on the flash, then PASS with `-sf` (tests run against the firmware just flashed). + +- [ ] **Step 5: RTT smoke on the pico** (mainline RTT was verified 2026-07-27; re-confirm on the fork build — `target-debug` skill has the full flow) + +Expected: RTT control block found, events stream, overflow 0. + +--- + +### Task 5: Repoint the rig default `openocd` + +**Files:** +- Modify: `/usr/local/bin/openocd` (→ symlink), remove Debian `openocd` package + +**Interfaces:** +- Consumes: Task 4 all-green. +- Produces: `which openocd` → fork for every rig user (hil_test.py, skills, CI). Rollback: restore `/usr/local/bin/openocd.rpi-backup-20260727`. + +- [ ] **Step 1: Back up and repoint** + +```bash +sudo cp -a /usr/local/bin/openocd /usr/local/bin/openocd.rpi-backup-20260727 +sudo ln -sf $HOME/app/openocd_tinyusb/bin/openocd /usr/local/bin/openocd +openocd --version 2>&1 | head -1 +``` +Expected: fork version string (matches Task 2 Step 2). + +- [ ] **Step 2: Drop the Debian openocd** (installed 2026-07-27 only to get a jlink-capable OpenOCD; the fork has `--enable-jlink`) + +```bash +sudo apt-get remove -y openocd +which -a openocd +``` +Expected: only `/usr/local/bin/openocd` remains. + +- [ ] **Step 3: Re-verify through the default path (no shim)** + +```bash +cd ~/code/tinyusb +python3 test/hil/hil_test.py test/hil/tinyusb.json -b raspberry_pi_pico -b stm32g0b1nucleo -b raspberry_pi_pico2 +``` +Expected: 3/3 PASS. If CI kicks a workflow mid-way, board locks arbitrate — just wait. + +--- + +### Task 6: WCH part 1 — port the `wlinke` adapter + `sdi` transport (compiles, detects probe) + +**Files (all in `~/app/openocd`, sources from `~/app/riscv-openocd-wch` @ `ccb04d7` — this copy already carries the GCC-14 fixes):** +- Create: `src/jtag/drivers/wlinke.c` (2041 lines, copy), `src/jtag/sdi.c` (~130 lines, port), `src/jtag/sdi.h` (if the fork has one — check `ls ~/app/riscv-openocd-wch/src/jtag/sdi*`) +- Modify: `src/transport/transport.h` (new transport id), `src/jtag/interface.h` (add `sdi_ops` to `struct adapter_driver` + `struct sdi_driver` decl), `src/jtag/interfaces.c` (register driver), `src/jtag/drivers/Makefile.am`, `src/jtag/Makefile.am`, `configure.ac` (`--enable-wlinke`) + +**Interfaces:** +- Consumes: fork clone + build tree. +- Produces: `openocd -c "adapter driver wlinke"` works; `wlink_*` C exports (`wlink_erase`, `wlink_write`, `wlink_getromram`, `wlink_reset`, `wlink_chip_reset`, `wlink_clean`, `wlink_flash_protect`, …) available for Task 8's flash driver; `sdi` transport selectable. Commit stays **amend-in-progress** — Tasks 6–8 squash into downstream commit #3. + +Port notes gathered up front (verified against both trees 2026-07-27): +- Fork wiring to replicate: `configure.ac:117` (adapter list entry `[[wlinke],[WLINKE Programmer],[WLINKE]]`), `:284-286` (`AC_ARG_ENABLE`), `:537`, `:737` (`AM_CONDITIONAL`); `src/jtag/drivers/Makefile.am:189` (`DRIVERFILES += %D%/wlinke.c`); `src/jtag/interfaces.c:154,274` (extern + table entry). +- Mainline transports are now a **fixed bitmask enum** (`src/transport/transport.h:19-25`: `TRANSPORT_JTAG BIT(0)` … `TRANSPORT_SWIM BIT(6)`, plus `TRANSPORT_VALID_MASK`), and `struct transport` selects by `unsigned int id`, not name. Add `#define TRANSPORT_SDI BIT(7)`, extend `TRANSPORT_VALID_MASK`, and port `sdi.c`'s `transport_register` to the id-based struct. +- **SWIM is the exact precedent** — ST's proprietary single-wire transport, wired upstream the same way this needs: `swim_ops` field at `src/jtag/interface.h:363`, its own transport bit, own command namespace. Mirror how `grep -rn swim src/transport/ src/jtag/interface.h src/jtag/swim.c` is structured wherever the fork's 0.11-era pattern no longer matches mainline. +- The fork's `sdi` op is a raw RISC-V DMI transfer: `adapter_driver->sdi_ops->transfer(iIndex, iAddr, iData, iOP, oAddr, oData, oOP)` (`src/jtag/sdi.c:20-22`) — keep that signature; Task 7 builds on it. +- `wlinke.c` includes `"cmsis_dap.h"`, `"hidapi.h"`, `"libusb_helper.h"` and (spuriously) `` — drop/guard the windows include; hidapi + libusb helpers exist in mainline's drivers dir. + +- [ ] **Step 1: Copy `wlinke.c` and `sdi.c` in; make the wiring edits above** + +- [ ] **Step 2: Reconfigure with wlinke and build** + +```bash +cd ~/app/openocd +./configure --prefix=$HOME/app/openocd_tinyusb \ + --enable-jlink --enable-cmsis-dap --enable-stlink --enable-wlinke --disable-werror +make -j$(nproc) && make install +``` +Expected: clean build (`--disable-werror` tolerates the fork's warning-dirty code; do fix outright errors). + +- [ ] **Step 3: Probe-detection test against real hardware** (nanoch32v203's WCH-LinkE, serial `EBCA8F0670AF`) + +```bash +python3 ~/code/tinyusb/test/hil/board_lock.py hold nanoch32v203 --reason "wlinke port bring-up" +~/app/openocd_tinyusb/bin/openocd -c "adapter driver wlinke" \ + -c "adapter serial EBCA8F0670AF" -c "transport select sdi" \ + -c "init" -c "shutdown" +``` +Expected: log lines identifying the WCH-Link probe (firmware version print from `wlink_init`), no crash. `init` may complain about missing target — probe identification is the pass signal. Keep the lock held into Task 7 (same board). + +- [ ] **Step 4: Snapshot as work-in-progress commit** (will be amended/squashed through Task 8) + +```bash +cd ~/app/openocd && git add -A && git commit -m "WIP: wch port (squash into single downstream commit before push)" +``` +**Do not push** until Task 8 squashes. + +--- + +### Task 7: WCH part 2 — target spike: mainline `riscv` over wlink DMI + +**The hypothesis (from the handoff, sharpened by code reading):** WCH-LinkE's `sdi` op *is* a raw DMI transfer, and mainline's riscv-013 target is just a DMI client. If mainline's riscv target can be fed by wlink DMI transfers, we skip porting `wch_riscv.c`/`wch_riscv-013.c` (~3.5k lines that `#include ` 0.11-era internals — the worst possible port surface). + +**Files:** +- Modify: `src/jtag/drivers/wlinke.c` (add the DTM bridge), possibly `src/target/riscv/riscv-013.c` shim hooks — decided by Step 1's reading. + +**Interfaces:** +- Consumes: Task 6's working adapter (lock on nanoch32v203 still held). +- Produces: a `target create ... riscv` (or, on fallback, `wch_riscv`) config shape that Task 8's flash/RTT/HIL work builds on. Records the decision in the WIP commit message. + +- [ ] **Step 1: Read mainline's DMI plumbing before writing anything** + +Read `src/target/riscv/riscv-013.c` (the `dmi_op`/`riscv_batch` layer) and `src/target/riscv/riscv.c`'s `riscv dmi_read`/`dmi_write` command handlers (they exist — mainline's `tcl/target/esp32c6.cfg` calls them). Determine the narrowest insertion point, in order of preference: +1. an existing DTM/DMI abstraction the adapter can implement directly (best); +2. a jtag-DTM emulation inside `wlinke.c`: expose `jtag_ops` whose queue executor decodes IR=DTMCS/DMI DR scans into `sdi` transfers (the esp_usb_jtag-style approach, one level up); +3. nothing viable → fallback (Step 4). + +- [ ] **Step 2: Implement the chosen bridge; build** + +Same build command as Task 6 Step 2. + +- [ ] **Step 3: Hypothesis test on nanoch32v203** (write the test cfg to the scratchpad, not the repo) + +```tcl +# wch-mainline-riscv-test.cfg +adapter driver wlinke +adapter speed 6000 +transport select sdi ;# or jtag, if Step 1 chose the jtag-DTM emulation +wlink_set_address 0x00000000 +sdi newtap ch32 cpu -irlen 5 -expected-id 0x00001 +target create ch32.cpu riscv -chain-position ch32.cpu +ch32.cpu configure -work-area-phys 0x20000000 -work-area-size 0x2800 -work-area-backup 1 +init +``` + +Evidence criteria — **all four must hold** to call the hypothesis confirmed: +``` +halt → "Target halted" with a sane pc +riscv dmi_read 0x11 → plausible dmstatus (nonzero, version field = 2 or 3) +mdw 0x20000000 4 → reads SRAM without error +resume → target runs again (LED blink / CDC re-enumerates) +``` + +- [ ] **Step 4: Decision checkpoint — STOP if the hypothesis fails** + +If any criterion fails for reasons that look architectural (wlink protocol can't express raw DMI reads, QingKe deviates from the RISC-V debug spec in ways mainline won't tolerate), **stop and report to the user** with the evidence. The two fallback options, costed: +- (a) Port the fork's full WCH target stack: `src/target/wch_riscv.c` (3033 ln) + `wch_riscv-013.c` + `wch_riscv.h`, plus the fork's core patches (all findable via `grep -rn 'riscvchip\|wlink_' src/` in the fork: `src/flash/nor/tcl.c` 5 hits, `src/target/target.c` 5, `src/server/gdb_server.c` 2). Hard: these files include 0.11-era `target/riscv/*` headers that clash with mainline's current riscv internals. +- (b) Ship the unified fork **without** WCH C support and keep `openocd_wch` as the rig's CH32 flasher indefinitely. +Do not silently pick (a). + +--- + +### Task 8: WCH part 3 — flash drivers, RTT, 4-board HIL green, squash to downstream commit #3 + +**Files:** +- Create: `src/flash/nor/wchriscv.c` (324 ln, copy), `src/flash/nor/wcharm.c` (897 ln, copy — CH32F ARM parts; self-contained memory-mapped driver, zero wlink deps), `src/jtag/drivers/wlinke.h` (new — prototypes for the `wlink_*` exports; the fork relied on implicit declarations) +- Modify: `src/flash/nor/drivers.c` (extern + table entries, fork pattern at its lines 93-94/170-171), `src/flash/nor/Makefile.am` (fork pattern at lines 78-79) +- Modify (TinyUSB repo, separate branch): `test/hil/hil_test.py` WCH cfg template (~line 381) — only if Task 7 landed on the mainline-riscv target shape + +**Interfaces:** +- Consumes: Task 7's confirmed target shape + `wlink_*` exports from Task 6. +- Produces: downstream commit #3 (single squashed commit, pushed); `~/.local/bin/openocd_wch` repointed at the fork; hil_test.py template branch `claude/hil-openocd-unified` in the TinyUSB repo (unpushed — user pushes; "hold pushes" applies to the TinyUSB repo). + +- [ ] **Step 1: Copy the flash drivers, add `wlinke.h`, wire `drivers.c`/`Makefile.am`; build** + +Keep the flash driver's registered name **`wch_riscv`** — the rig's generated per-probe cfg does `flash bank ... wch_riscv ...` and Task 8 Step 4's template keeps working. +Fork quirk to *not* copy: the fork patched `src/flash/nor/tcl.c` (`handle_flash_protect_check_command`, its line ~414) to call `wlink_softreset()`/`wlnik_protect_check()` for WCH banks. Implement that inside `wchriscv.c`'s own `protect_check` op instead — no core-file patch. +Check the fork's `src/server/gdb_server.c` 2 `wlink_` hits (`grep -n 'riscvchip\|wlink_' ~/app/riscv-openocd-wch/src/server/gdb_server.c`) — port the behavior into the driver/target layer if it matters for our flow (flash + RTT, no gdb needed on the rig for WCH), else document-and-skip in the commit message. + +- [ ] **Step 2: Flash test on nanoch32v203** (lock held; cfg = Task 7's test cfg + flash bank line) + +```tcl +set _FLASHNAME ch32.flash +flash bank $_FLASHNAME wch_riscv 0x00000000 0 0 0 ch32.cpu +``` +```bash +~/app/openocd_tinyusb/bin/openocd -c "adapter serial EBCA8F0670AF" \ + -f wch-mainline-riscv-test.cfg \ + -c "program /home/hathach/code/tinyusb/examples/cmake-build-nanoch32v203-usbfs/device/cdc_msc/cdc_msc.elf verify reset exit" +``` +Expected: `** Verified OK **`; board re-enumerates as CDC (`lsusb | grep -i cafe` or dmesg). + +- [ ] **Step 3: RTT test on nanoch32v203** (rig rule: `rtt polling_interval 1`, **never `reset run`**) + +RTT server start → capture a few seconds → nonzero events. The `target-debug` skill documents the WCH RTT route. + +- [ ] **Step 4: Update the rig's WCH flow** + +If Task 7 confirmed the mainline-riscv shape, the generated cfg template in `test/hil/hil_test.py` (~line 381: `adapter driver wlinke` … `target create $_TARGETNAME.0 wch_riscv …`) must switch to the Task 7 cfg shape. Do this on a TinyUSB branch: +```bash +cd ~/code/tinyusb && git worktree add .worktrees/claude/hil-openocd-unified -b claude/hil-openocd-unified +# edit test/hil/hil_test.py template in the worktree; commit there; DO NOT push +``` +Then repoint the rig's WCH binary (symlink, so scripts resolve): +```bash +ln -sf ~/app/openocd_tinyusb/bin/openocd ~/.local/bin/openocd_wch +``` +(Old target `~/app/openocd_wch_new/bin/…` and `~/.local/bin/openocd_wch.bak-20260727` stay as rollback.) + +- [ ] **Step 5: HIL green on all four WCH boards** (run from the worktree so the new template is used) + +```bash +cd ~/code/tinyusb/.worktrees/claude/hil-openocd-unified +python3 test/hil/hil_test.py test/hil/tinyusb.json \ + -b nanoch32v203 -b ch32v103r_r1_1v0 -b ch32v307v_r1_1v0 -b ch582m_evt +``` +Expected: 4/4 PASS. Firmware for missing `cmake-build-` sets: build first (nanoch32v203 sets exist; ch32v103/307/ch582m may need `tools/get_deps.py -b ` + the examples build). Known flake: ch32v103r throughput is ~40% flaky historically — retry before blaming the port. If ch582m misbehaves specifically, note it and check `wlinke.c`'s riscvchip dispatch for CH58x. + +- [ ] **Step 6: Squash Tasks 6–8 into downstream commit #3 and push** + +```bash +cd ~/app/openocd +git reset --soft $(git log --grep='WIP: wch port' --format=%H | tail -1)^ +git commit -m "jtag, flash: add WCH-LinkE adapter, sdi transport and CH32 flash drivers + +Ported from hathach/riscv-openocd-wch @ ccb04d7 (originally +dragonlock2/miscboards WCH SDK, base openocd 0.11.0): +- src/jtag/drivers/wlinke.c: WCH-Link/LinkE USB adapter (GCC-14 fixes included) +- src/jtag/sdi.c: WCH single-wire debug transport, re-worked onto + mainline's id-based transport API (TRANSPORT_SDI) +- src/flash/nor/wchriscv.c, wcharm.c: CH32V/CH5xx (wlink protocol) and + CH32F (memory-mapped) flash drivers +CH32 cores are driven by mainline's riscv target over wlink DMI +transfers; the fork's wch_riscv target stack is not needed. +The fork's core patches (flash/nor/tcl.c protect-check hack) moved into +the wch_riscv flash driver's protect_check op. + +Verified on ci rig: nanoch32v203, ch32v103r_r1_1v0, ch32v307v_r1_1v0, +ch582m_evt - flash + verify + HIL suite + RTT (nanoch32v203)." +git push +``` +(Amend the target-stack paragraph if the fallback path was taken instead.) +Release the nanoch32v203 lock if still held. + +--- + +### Task 9: Espressif — ESP32-P4 configs, S3 attach verification, downstream commit #4 + +Mainline already has: `src/target/espressif/` (esp32/s2/s3 xtensa targets + apptrace/semihosting), the `esp_usb_jtag` adapter driver, and builtin cfgs for c2/c3/c6/h2/s3. Missing vs the rig: anything ESP32-P4. Flash stays esptool (rig flashes ESP via `idf.py`/esptool; the espressif fork's flash-stub stack is explicitly out of scope). + +**Files:** +- Create: `~/app/openocd/tcl/target/esp32p4.cfg`, `~/app/openocd/tcl/board/esp32p4-builtin.cfg` + +**Interfaces:** +- Consumes: install prefix; espressif fork cfgs fetched from GitHub. +- Produces: downstream commit #4; P4 + S3 debug-attach evidence. + +- [ ] **Step 1: Verify S3 attach with pure mainline inheritance** (no new files; proves the "espressif support" baseline) + +```bash +python3 ~/code/tinyusb/test/hil/board_lock.py hold espressif_s3_devkitm --reason "openocd-unified esp verify" +~/app/openocd_tinyusb/bin/openocd -f board/esp32s3-builtin.cfg -c "init; halt" +``` +Expected: both xtensa cores detected over USB-Serial-JTAG (303a:1001), `Target halted`. Then `resume; shutdown`, release lock. Gotchas live in the `esp-target-debug` skill (S3's debug port can be occupied when TinyUSB firmware owns the USB peripheral — use the same recovery steps as that skill). + +- [ ] **Step 2: Fetch and adapt the P4 configs (write both files)** + +```bash +curl -fsSL https://raw.githubusercontent.com/espressif/openocd-esp32/master/tcl/target/esp32p4.cfg -o /tmp/claude-1000/-home-hathach-code-tinyusb/7dee5f9e-874b-4680-bb09-01a5d13fbd37/scratchpad/esp32p4-espressif.cfg +curl -fsSL https://raw.githubusercontent.com/espressif/openocd-esp32/master/tcl/board/esp32p4-builtin.cfg -o /tmp/claude-1000/-home-hathach-code-tinyusb/7dee5f9e-874b-4680-bb09-01a5d13fbd37/scratchpad/esp32p4-builtin-espressif.cfg +``` +Espressif's cfg creates an `esp32p4`-type target (their `esp_riscv` C stack — not in mainline). Rewrite `tcl/target/esp32p4.cfg` following **mainline's own ESP RISC-V pattern** — `tcl/target/esp32c6.cfg` + `esp_common.cfg` (generic `riscv` target create, chip quirks via `riscv dmi_write` with the `_RISCV_*` register constants from `esp_common.cfg`) — carrying over from Espressif's file: `_CPUTAPID`, memory map/workarea, the dual-core SMP topology (P4 is 2× RV32 — model on how mainline handles SMP, and on Espressif's `_ESP_SMP_TARGET`), and the `_ESP_EFUSE_MAC_ADDR_REG` value. `tcl/board/esp32p4-builtin.cfg` = `esp_usb_jtag` adapter + `transport select jtag` + source the target cfg (mirror `board/esp32c6-builtin.cfg`, adjusting `ESP_USB_JTAG_*` ids to Espressif's P4 values). +Also check `src/jtag/drivers/esp_usb_jtag.c` accepts the P4 (VID/PID 303a:1001 is shared; verify any chip-id gating). + +- [ ] **Step 3: P4 attach test** + +```bash +python3 ~/code/tinyusb/test/hil/board_lock.py hold espressif_p4_function_ev --reason "openocd-unified esp verify" +cd ~/app/openocd && make install +~/app/openocd_tinyusb/bin/openocd -f board/esp32p4-builtin.cfg -c "init; halt" +``` +Evidence criteria: both HP cores halt, `mdw 0x4ff00000 4` (P4 HP TCM/SRAM — cross-check the address against Espressif's cfg memory map before running) reads, `resume` works. Known nuance from prior sessions: P4 attach can need the reset-into-attach dance — the `esp-target-debug` skill documents it; an attach that only works with that dance still counts as pass (note it in the commit). +**Decision checkpoint:** if the generic-riscv shape cannot attach P4 for architectural reasons (needs Espressif's C-level `esp_riscv` assist), stop and report — options are cherry-picking their `esp_riscv` stack (large) vs shipping P4 as esptool-flash-only with debug via ESP-IDF's openocd as today. Do not silently pick either. + +- [ ] **Step 4: Commit (downstream commit #4) and push** + +```bash +cd ~/app/openocd +git add tcl/target/esp32p4.cfg tcl/board/esp32p4-builtin.cfg +git commit -m "tcl: add ESP32-P4 target/board configs adapted from espressif/openocd-esp32 + +Adapted from espressif/openocd-esp32 master onto mainline's generic +RISC-V ESP pattern (tcl/target/esp32c6.cfg + esp_common.cfg): generic +riscv targets over esp_usb_jtag instead of the fork's esp_riscv C +stack. Flash programming stays with esptool, matching how the rig +flashes all Espressif boards. ESP32/S2/S3/C3/C6/H2 were already +supported by mainline. + +Verified on ci rig: espressif_p4_function_ev and espressif_s3_devkitm +attach/halt/resume over built-in USB-Serial-JTAG." +git push +``` +Release both ESP board locks. + +--- + +### Task 10: Final sweep, README truth-up, rig config flip + +**Files:** +- Modify: `~/app/openocd/README.md` (only if scope shifted in Tasks 7–9) +- Modify (TinyUSB worktree from Task 8): `test/hil/tinyusb.json` — max32666fthr flasher `openocd_adi` → `openocd` with args `-f interface/cmsis-dap.cfg -f target/max32665.cfg` (plain openocd now serves it) + +**Interfaces:** +- Consumes: everything green from Tasks 4–9. +- Produces: the finished fork; TinyUSB branch `claude/hil-openocd-unified` with hil_test.py + tinyusb.json changes, committed, **unpushed** (user pushes per standing instruction). + +- [ ] **Step 1: Full HIL regression across every openocd-family board** + +```bash +cd ~/code/tinyusb/.worktrees/claude/hil-openocd-unified +python3 test/hil/hil_test.py test/hil/tinyusb.json \ + -b raspberry_pi_pico -b raspberry_pi_pico_w -b raspberry_pi_pico2 \ + -b adafruit_fruit_jam -b stm32h743nucleo -b stm32g0b1nucleo -b max32666fthr \ + -b nanoch32v203 -b ch32v103r_r1_1v0 -b ch32v307v_r1_1v0 -b ch582m_evt +``` +Expected: 11/11 PASS (ch32v103r throughput may need its usual retries). + +- [ ] **Step 2: README truth-up** + +Re-read `README.md` against what actually landed (WCH target route, P4 outcome). Fix any row that no longer matches; amend into the README commit or add +`git commit -m "README: reflect verified scope"`. Push. + +- [ ] **Step 3: Verify the one-commit-per-fork shape** + +Run: `git -C ~/app/openocd log --oneline upstream/master..tinyusb` +Expected: exactly 5 commits (or 6 with a README truth-up): README, RPi configs, ADI config, WCH port, ESP32-P4 configs. If not, interactive-free cleanup: `git rebase --onto` / `reset --soft` re-squash, then `git push --force-with-lease` (fork branch, ours alone — safe). + +- [ ] **Step 4: Commit the TinyUSB-side changes in the worktree (do not push)** + +```bash +cd ~/code/tinyusb/.worktrees/claude/hil-openocd-unified +git add test/hil/hil_test.py test/hil/tinyusb.json +git commit -m "test(hil): drive WCH boards and max32666fthr through the unified openocd" +``` +Leave for the user to push/PR. + +- [ ] **Step 5: Leftovers report** (no deletions now) + +Write a short status into `OPENOCD_UNIFIED_FORK_HANDOFF.md` (append a "2026-07-XX outcome" section): what was repointed, rollback paths (`/usr/local/bin/openocd.rpi-backup-20260727`, `~/.local/bin/openocd_wch.bak-20260727`), and that `~/app/openocd_rpi`, `~/app/openocd_adi`, `~/app/openocd-mainline`, `~/app/openocd_mainline`, `~/app/openocd_wch_new`, `~/app/riscv-openocd-wch` can be retired **after a week of green CI** — not now. diff --git a/tools/codespell/ignore-words.txt b/tools/codespell/ignore-words.txt index 0b1aa284a..6b301d27b 100644 --- a/tools/codespell/ignore-words.txt +++ b/tools/codespell/ignore-words.txt @@ -5,6 +5,7 @@ endianess fro hsi inout +linke mot ore pris