6 Commits
Author SHA1 Message Date
bstanwell c13e2fa475 Update bootloader.c 2026-08-19 18:07:15 +10:00
bstanwell 4d7681a053 Update usbDesc.h 2026-08-19 18:06:59 +10:00
bstanwell e6122b1845 Add usbDesc.h 2026-08-19 17:34:44 +10:00
R 81b5955a5c Perturb USB de-config to better match vendor behavior
This was one of many changes along the path of discovering that `wfi`
seems to just break USBD.

This change is probably not necessary, but it feels more elegant.
2026-07-07 00:06:50 +01:00
R 03496cb28e Fix incorrect computation of USB descriptor sizes 2026-07-07 00:06:48 +01:00
R 2d8a534f14 Correct the README wrt supported chips
Closes #1
2024-08-18 22:07:45 +01:00
3 changed files with 269 additions and 24 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ This code implements a [uf2](https://github.com/microsoft/uf2) bootloader for RI
- Runs off of internal 8 MHz oscillator (despite stability concerns) - Runs off of internal 8 MHz oscillator (despite stability concerns)
- The HSI oscillator's tolerance (-1.0% to +1.6%) is well outside of the range specified for USB full speed ($\pm$ 0.25%), but it seems to work in practice n=1 ¯\\\_(ツ)\_/¯ - The HSI oscillator's tolerance (-1.0% to +1.6%) is well outside of the range specified for USB full speed ($\pm$ 0.25%), but it seems to work in practice n=1 ¯\\\_(ツ)\_/¯
- The vendor's ISP ROM bootloader also does this - The vendor's ISP ROM bootloader also does this
- Should work across most of the CH32V2xx and CH32V3xx family with appropriate changes to hardcoded constants. - Should work across CH32V2xx family with appropriate changes to hardcoded constants. Does *not* support CH32V3xx (which doesn't have USBD).
- Allows both download to flash and to SRAM with the "not main flash" flag (this is how the RP2040 bootrom works) - Allows both download to flash and to SRAM with the "not main flash" flag (this is how the RP2040 bootrom works)
- Flash download address must be 08xxxxxx (i.e. not starting at 0) - Flash download address must be 08xxxxxx (i.e. not starting at 0)
- SRAM download address must be 20xxxxxx - SRAM download address must be 20xxxxxx
+25 -23
View File
@@ -1,7 +1,7 @@
// CH32V UF2 bootloader, size-optimized (target: <= 4096 bytes) // CH32V UF2 bootloader, size-optimized (target: <= 4096 bytes)
#include <stdint.h> #include <stdint.h>
#include "usbDesc.h"
typedef struct USBD_descriptor { typedef struct USBD_descriptor {
uint32_t addr_tx; uint32_t addr_tx;
uint32_t count_tx; uint32_t count_tx;
@@ -47,11 +47,11 @@ typedef struct USBD_descriptor {
// Device descriptor // Device descriptor
const uint8_t USB_DEV_DESC[18] __attribute__((aligned(2))) = { const uint8_t USB_DEV_DESC[18] __attribute__((aligned(2))) = {
18, // bLength 18, // bLength
1, // bDescriptorType DESCRIPTOR_DEVICE, // bDescriptorType
0x00, 0x02, // bcdUSB (uint_8)USB20, (uint_8)(USB20 << 8), // bcdUSB
0, // bDeviceClass USB_CLASS_PER_INTERFACE, // bDeviceClass
0, // bDeviceSubClass USB_CLASS_PER_INTERFACE, // bDeviceSubClass
0, // bDeviceProtocol USB_CLASS_PER_INTERFACE, // bDeviceProtocol
8, // bMaxPacketSize0 8, // bMaxPacketSize0
0x55, 0xf0, // idVendor 0x55, 0xf0, // idVendor
0x00, 0x00, // idProduct 0x00, 0x00, // idProduct
@@ -65,38 +65,38 @@ const uint8_t USB_DEV_DESC[18] __attribute__((aligned(2))) = {
const uint8_t USB_CONF_DESC[0x20] __attribute__((aligned(2))) = { const uint8_t USB_CONF_DESC[0x20] __attribute__((aligned(2))) = {
// Configuration descriptor // Configuration descriptor
9, // bLength 9, // bLength
2, // bDescriptorType DESCRIPTOR_CONFIGURATION, // bDescriptorType
0x20, 0x00, // wTotalLength 0x20, 0x00, // wTotalLength
1, // bNumInterfaces 1, // bNumInterfaces
1, // bConfigurationValue 1, // bConfigurationValue
0, // iConfiguration 0, // iConfiguration
0x80, // bmAttributes 0x80, // bmAttributes // Too lazy to enum
250, // bMaxPower 250, // bMaxPower
// Interface descriptor // Interface descriptor
9, // bLength 9, // bLength
4, // bDescriptorType DESCRIPTOR_INTERFACE, // bDescriptorType
0, // bInterfaceNumber 0, // bInterfaceNumber
0, // bAlternateSetting 0, // bAlternateSetting
2, // bNumEndpoints 2, // bNumEndpoints
0x08, // bInterfaceClass USB_CLASS_MASS_STORAGE, // bInterfaceClass
0x05, // bInterfaceSubClass MSC_SUBCLASS_SFF8070I, // bInterfaceSubClass
0x50, // bInterfaceProtocol MSC_PROTO_BBB, // bInterfaceProtocol
0, // iInterface 0, // iInterface
// EP1 IN endpoint descriptor // EP1 IN endpoint descriptor
7, // bLength 7, // bLength
5, // bDescriptorType DESCRIPTOR_ENDPOINT, // bDescriptorType
0x81, // bEndpointAddress 0x81, // bEndpointAddress
2, // bmAttributes TRANSFER_BULK, // bmAttributes
0x40, 0x00, // wMaxPacketSize 0x40, 0x00, // wMaxPacketSize
0, // bInterval 0, // bInterval
// EP1 OUT endpoint descriptor // EP1 OUT endpoint descriptor
7, // bLength 7, // bLength
5, // bDescriptorType DESCRIPTOR_ENDPOINT, // bDescriptorType
0x01, // bEndpointAddress 0x01, // bEndpointAddress
2, // bmAttributes TRANSFER_BULK, // bmAttributes
0x40, 0x00, // wMaxPacketSize 0x40, 0x00, // wMaxPacketSize
0, // bInterval 0, // bInterval
}; };
@@ -112,12 +112,12 @@ const uint8_t HEXLUT[16] = "0123456789ABCDEF";
// SCSI INQUIRY standard response // SCSI INQUIRY standard response
const uint8_t INQUIRY_RESPONSE[36] __attribute__((aligned(2))) = { const uint8_t INQUIRY_RESPONSE[36] __attribute__((aligned(2))) = {
0x00, // direct access block device SCSI_INQ_DEV_TYPE_DIRECT_ACCESS, // direct access block device
0x80, // RMB removable media SCSI_INQ_RMB_REMOVABLE, // RMB removable media
// (if you don't set the RMB flag, macOS will show an orange drive // (if you don't set the RMB flag, macOS will show an orange drive
// instead of a grey drive, contrary to expectations of flash drives) // instead of a grey drive, contrary to expectations of flash drives)
0x04, // SPC-2 SCSI_INQ_VER_SPC_2, // SPC-2
0x02, // fixed, response data format SCSI_INQ_FMT_SCSI2, // fixed, response data format
0x1F, // additional length 0x1F, // additional length
0x00, // xxx mostly obsolete junk 0x00, // xxx mostly obsolete junk
0x00, 0x00,
@@ -468,10 +468,10 @@ __attribute__((naked)) int main(void) {
R16_USBD_CNTR = 1; R16_USBD_CNTR = 1;
USB_DESCS[0].addr_tx = 0x30 / 2; USB_DESCS[0].addr_tx = 0x30 / 2;
USB_DESCS[0].addr_rx = 0x20 / 2; USB_DESCS[0].addr_rx = 0x20 / 2;
USB_DESCS[0].count_rx = (8 << 10); USB_DESCS[0].count_rx = ((8 / 2) << 10);
USB_DESCS[1].addr_tx = 0xc0 / 2; USB_DESCS[1].addr_tx = 0xc0 / 2;
USB_DESCS[1].addr_rx = 0x40 / 2; USB_DESCS[1].addr_rx = 0x40 / 2;
USB_DESCS[1].count_rx = (2 << 10) | (1 << 15); USB_DESCS[1].count_rx = (1 << 10) | (1 << 15);
// XXX because the table is at offset 0 don't bother writing this // XXX because the table is at offset 0 don't bother writing this
// Attach USB // Attach USB
@@ -995,8 +995,10 @@ __attribute__((naked)) int main(void) {
while (!(STK_SR & 1)) {} while (!(STK_SR & 1)) {}
STK_CTLR = 0; STK_CTLR = 0;
STK_SR = 0; STK_SR = 0;
R16_USBD_CNTR = 0b11;
R32_EXTEN_CTR &= ~(1 << 1); R32_EXTEN_CTR &= ~(1 << 1);
R16_USBD_CNTR = 1;
R16_USBD_ISTR = 0;
R16_USBD_CNTR = 0b11;
if (ADDRESS_HI >> 8 == 0x20) { if (ADDRESS_HI >> 8 == 0x20) {
// ram boot, go back to original clock settings // ram boot, go back to original clock settings
R32_RCC_CFGR0 = (R32_RCC_CFGR0 & ~0b11) | 0b00; R32_RCC_CFGR0 = (R32_RCC_CFGR0 & ~0b11) | 0b00;
+243
View File
@@ -0,0 +1,243 @@
enum USB_Class_Codes {
USB_CLASS_PER_INTERFACE = 0x00, // Use class info in Interface Descriptors
USB_CLASS_AUDIO = 0x01, // Audio
USB_CLASS_COMM_AND_CDC = 0x02, // Communications and CDC Control
USB_CLASS_HID = 0x03, // Human Interface Device
USB_CLASS_PHYSICAL = 0x05, // Physical
USB_CLASS_IMAGE = 0x06, // Image
USB_CLASS_PRINTER = 0x07, // Printer
USB_CLASS_MASS_STORAGE = 0x08, // Mass Storage
USB_CLASS_HUB = 0x09, // Hub
USB_CLASS_CDC_DATA = 0x0A, // CDC-Data
USB_CLASS_SMART_CARD = 0x0B, // Smart Card
USB_CLASS_CONTENT_SECURITY = 0x0D, // Content Security
USB_CLASS_VIDEO = 0x0E, // Video
USB_CLASS_PERSONAL_HEALTHCARE = 0x0F, // Personal Healthcare
USB_CLASS_AUDIO_VIDEO = 0x10, // Audio/Video Devices
USB_CLASS_BILLBOARD = 0x11, // Billboard Device Class
USB_CLASS_TYPE_C_BRIDGE = 0x12, // USB Type-C Bridge Class
USB_CLASS_BULK_DISPLAY = 0x13, // USB Bulk Display Protocol Device Class
USB_CLASS_MCTP = 0x14, // MCTP over USB Protocol Endpoint Device Class
USB_CLASS_I3C = 0x3C, // I3C Device Class
USB_CLASS_DIAGNOSTIC = 0xDC, // Diagnostic Device
USB_CLASS_WIRELESS = 0xE0, // Wireless Controller
USB_CLASS_MISCELLANEOUS = 0xEF, // Miscellaneous
USB_CLASS_APPLICATION_SPEC = 0xFE, // Application Specific
USB_CLASS_VENDOR_SPEC = 0xFF // Vendor Specific
};
enum USB_MassStorage_InterfaceProtocol {
MSC_PROTO_CBI_INT = 0x00, // Control/Bulk/Interrupt (with completion INT)
MSC_PROTO_CBI_NO_INT = 0x01, // Control/Bulk/Interrupt (without completion INT)
MSC_PROTO_ZIP = 0x02, // ZIP Drive Protocol (Legacy)
MSC_PROTO_BBB = 0x50, // Bulk-Only Transport (BOT / BBB)
MSC_PROTO_BOT = 0x50, // Alias for Bulk-Only Transport
MSC_PROTO_UAS = 0x62, // USB Attached SCSI Protocol (UASP)
MSC_PROTO_VENDOR = 0xFF // Vendor Specific Protocol
};
enum USB_HID_InterfaceProtocol {
HID_PROTO_NONE = 0x00, // No specific boot protocol / Report Protocol
HID_PROTO_KEYBOARD = 0x01, // Boot Keyboard Protocol
HID_PROTO_MOUSE = 0x02 // Boot Mouse Protocol
};
enum USB_MassStorage_SubClass {
MSC_SUBCLASS_RBC = 0x01, // Reduced Block Commands
MSC_SUBCLASS_ATAPI = 0x02, // SFF-8020i / MMC-2 (CD/DVD)
MSC_SUBCLASS_QIC157 = 0x03, // QIC-157 (Tape drive)
MSC_SUBCLASS_UFI = 0x04, // Uniform Floppy Interface
MSC_SUBCLASS_SFF8070I = 0x05, // SFF-8070i
MSC_SUBCLASS_SCSI = 0x06, // SCSI Transparent Command Set (Most Common)
MSC_SUBCLASS_LESD = 0x07, // Lockable Storage Devices
MSC_SUBCLASS_IEEE1667 = 0x08, // IEEE 1667 Security
MSC_SUBCLASS_VENDOR = 0xFF // Vendor Specific
};
enum USB_HID_SubClass {
HID_SUBCLASS_NONE = 0x00, // No SubClass (Standard HID device)
HID_SUBCLASS_BOOT = 0x01 // Boot Interface Subclass (Keyboards/Mice supported by BIOS)
};
enum USB_bDescriptorType {
DESCRIPTOR_DEVICE = 0x1,
DESCRIPTOR_CONFIGURATION = 0x2,
DESCRIPTOR_INTERFACE = 0x4,
DESCRIPTOR_ENDPOINT = 0x5
};
enum USB_Type_BCD {
USB20 = 0x0200,
USB11 = 0x0110,
USB10 = 0x0100
};
enum USB_PID {
// Token Packets
OUT_TOKEN = 0b0001,
IN_TOKEN = 0b1001,
SOF_TOKEN = 0b0101,
SETUP_TOKEN = 0b1101,
// Data Packets
DATA0_DATA = 0b0011,
DATA1_DATA = 0b1011,
DATA2_DATA = 0b0111,
MDATA_DATA = 0b1111,
// Handshake Packets
ACK_HANDSHAKE = 0b0010,
NAK_HANDSHAKE = 0b1010,
STALL_HANDSHAKE = 0b1110,
NYET_HANDSHAKE = 0b0110,
// Special & Other Packets
PRE_SPECIAL = 0b1100, // PREamble shares value with ERR
ERR_SPECIAL = 0b1100,
SPLIT_SPECIAL = 0b1000,
PING_SPECIAL = 0b0100
};
enum Configuration_bmAttributes_Values {
// Standard Bits
CONFIG_ATTR_RESERVED_BIT7 = (1 << 7), // Always set to 1 (legacy USB 1.0 bus-powered indicator)
CONFIG_ATTR_SELF_POWERED = (1 << 6), // 1 = Self-powered, 0 = Bus-powered
CONFIG_ATTR_REMOTE_WAKEUP = (1 << 5), // 1 = Supports remote wakeup
// Masks
CONFIG_ATTR_POWER_MASK = (1 << 6),
CONFIG_ATTR_WAKEUP_MASK = (1 << 5),
CONFIG_ATTR_RESERVED_MASK = 0b00011111 // Bits 4..0 reserved (set to 0)
};
enum Endpoint_bmAttributes {
// Bits 1..0: Transfer Type
TRANSFER_TYPE_MASK = 0b00000011,
TRANSFER_CONTROL = 0b00000000,
TRANSFER_ISOCHRONOUS = 0b00000001,
TRANSFER_BULK = 0b00000010,
TRANSFER_INTERRUPT = 0b00000011,
// Bits 3..2: Synchronization Type (Isochronous Mode only)
SYNC_TYPE_MASK = 0b00001100,
SYNC_NOSYNC = (0b00 << 2), // No Synchronization
SYNC_ASYNC = (0b01 << 2), // Asynchronous
SYNC_ADAPTIVE = (0b10 << 2), // Adaptive
SYNC_SYNCHRONOUS = (0b11 << 2), // Synchronous
// Bits 5..4: Usage Type (Isochronous Mode only)
USAGE_TYPE_MASK = 0b00110000,
USAGE_DATA = (0b00 << 4), // Data Endpoint
USAGE_FEEDBACK = (0b01 << 4), // Feedback Endpoint
USAGE_EXPLICIT_FEEDBACK = (0b10 << 4), // Explicit Feedback Data Endpoint
USAGE_RESERVED = (0b11 << 4) // Reserved
};
enum Setup_bmRequestType {
// Bit 7: Data Phase Transfer Direction
DIR_MASK = (1 << 7),
HOST_TO_DEVICE = (0 << 7),
DEVICE_TO_HOST = (1 << 7),
// Bits 6..5: Type
TYPE_MASK = (0b11 << 5),
TYPE_STANDARD = (0b00 << 5),
TYPE_CLASS = (0b01 << 5),
TYPE_VENDOR = (0b10 << 5),
TYPE_RESERVED = (0b11 << 5),
// Bits 4..0: Recipient
RECIPIENT_MASK = 0b11111,
RECIPIENT_DEVICE = 0,
RECIPIENT_INTERFACE = 1,
RECIPIENT_ENDPOINT = 2,
RECIPIENT_OTHER = 3
};
// Byte 0: Peripheral Qualifier (Bits 7..5) & Peripheral Device Type (Bits 4..0)
enum SCSI_Inquiry_Byte0 {
// Peripheral Qualifier (Bits 7..5)
SCSI_INQ_QUAL_CONNECTED = (0b000 << 5), // Connected to target device
SCSI_INQ_QUAL_NOT_CONNECTED = (0b001 << 5), // Supported, but device not connected
SCSI_INQ_QUAL_NOT_CAPABLE = (0b011 << 5), // Not capable of supporting a physical device
// Peripheral Device Type (Bits 4..0)
SCSI_INQ_DEV_TYPE_DIRECT_ACCESS = 0x00, // Direct access block device (Flash drive, HDD, SSD)
SCSI_INQ_DEV_TYPE_SEQUENTIAL = 0x01, // Sequential-access device (Tape)
SCSI_INQ_DEV_TYPE_PRINTER = 0x02, // Printer device
SCSI_INQ_DEV_TYPE_PROCESSOR = 0x03, // Processor device
SCSI_INQ_DEV_TYPE_CD_DVD = 0x05, // CD/DVD-ROM device
SCSI_INQ_DEV_TYPE_OPTICAL = 0x07, // Optical memory device
SCSI_INQ_DEV_TYPE_UNKNOWN = 0x1F // Unknown or no device type
};
// Byte 1: Control & Removable Media Flag
enum SCSI_Inquiry_Byte1 {
SCSI_INQ_RMB_FIXED = (0 << 7), // Fixed (non-removable) drive
SCSI_INQ_RMB_REMOVABLE = (1 << 7) // Removable media (sets gray icon on macOS)
};
// Byte 2: ANSI/SPC Standard Compliance Version
enum SCSI_Inquiry_Byte2_Version {
SCSI_INQ_VER_NONE = 0x00, // No standard compliance claimed
SCSI_INQ_VER_SCSI1 = 0x01, // ANSI X3.131-1986 (SCSI-1)
SCSI_INQ_VER_SCSI2 = 0x02, // ANSI X3.131-1994 (SCSI-2)
SCSI_INQ_VER_SPC = 0x03, // SPC (SCSI Primary Commands)
SCSI_INQ_VER_SPC_2 = 0x04, // SPC-2
SCSI_INQ_VER_SPC_3 = 0x05, // SPC-3
SCSI_INQ_VER_SPC_4 = 0x06 // SPC-4
};
// Byte 3: Response Data Format & Control Flags
enum SCSI_Inquiry_Byte3 {
SCSI_INQ_FMT_SCSI1 = 0x00,
SCSI_INQ_FMT_CCS = 0x01, // Common Command Set
SCSI_INQ_FMT_SCSI2 = 0x02, // Standard Response Data Format (SCSI-2 / SPC)
SCSI_INQ_HISUP = (1 << 4), // Hierarchical Support
SCSI_INQ_NORMACA = (1 << 5) // Normal ACA Supported
};
enum Standard__Device_Requests {
GET_STATUS = 0x00,
/**
* GET_STATUS will reuturn a 16bit word.
* D0 is "Self Powered" bit, if D0 == 0, then the device is powered by the bus
* D1 is the "Remote Wakeup" bit, if D1 == 1, then the host can wake up this device from suspend
*/
CLEAR_FEATURE = 0x01,
SET_FEATURE = 0x03,
SET_ADDRESS = 0x05,
GET_DESCRIPTOR = 0x06,
SET_DESCRIPTOR = 0x07,
GET_CONFIGURATION = 0x08,
SET_CONFIGURATION = 0x09
};
enum Standard_Interface_Requests {
GET_STATUS = 0x00,
CLEAR_FEATURE = 0x01,
SET_FEATURE = 0x03,
GET_INTERFACE = 0x0A,
SET_INTERFACE = 0x11,
};
enum Standard_Endpoint_Requests {
GET_STATUS = 0x00,
CLEAR_FEATURE = 0x01,
SET_FEATURE = 0x03,
/**
* Clear Feature and Set Feature are used to set Endpoint Features.
* The standard currently defines one endpoint feature selector,
* ENDPOINT_HALT (0x00) which allows the host to stall and clear an endpoint.
*/
SYNCH_FRAME = 0x12,
};