// CH32V UF2 bootloader, size-optimized (target: <= 4096 bytes) #include typedef struct USBD_descriptor { uint32_t addr_tx; uint32_t count_tx; uint32_t addr_rx; uint32_t count_rx; } USBD_descriptor; // USBD memory allocation: // WARNING THIS IS A 16-bit MEMORY LMAO // In other words, the memory contains 16-bit values spaced out into 32-bit words: // xx yy 00 00 | zz ww 00 00 | aa bb 00 00 | .. // This significantly complicates memory copying. // Also, accessing past the end of USBD RAM will lock up the entire core // to the point where debugging stops working. This can be triggered by an "x" // command in GDB (so be careful). // There are indeed 512 bytes of total USBD RAM, but they span 1024 bytes of // address space (because the 00 00 doesn't count). // (It feels like WCH grafted an old core for not-32-bit processors into // these RISC-V parts...) // +0x00 // descriptors (two sets of two endpoints) // +0x20 // EP 0 OUT (8 bytes) // +0x30 // EP 0 IN (8 bytes) // +0x40 // EP 1 OUT (64 bytes) // +0xc0 // EP 1 IN (64 bytes) // +0x140 // ram for checking download completion // +0x1d0 // ram for stashing variables // +0x200 // <256-byte flash page buffer> // In addition to the expected USB buffers, this RAM is used to store program state. // This allows the *entire* SRAM to be used when downloading to SRAM. // Note that every buffer that needs to be sent over USB must be 16-bit aligned // (in code flash). // This is assumed by copying code (which will copy in 16-bit chunks). // Device descriptor const uint8_t USB_DEV_DESC[18] __attribute__((aligned(2))) = { 18, // bLength 1, // bDescriptorType 0x00, 0x02, // bcdUSB 0, // bDeviceClass 0, // bDeviceSubClass 0, // bDeviceProtocol 8, // bMaxPacketSize0 0x55, 0xf0, // idVendor 0x00, 0x00, // idProduct 0x00, 0x00, // bcdDevice 1, // iManufacturer 2, // iProduct 3, // iSerialNumber 1, // bNumConfigurations }; const uint8_t USB_CONF_DESC[0x20] __attribute__((aligned(2))) = { // Configuration descriptor 9, // bLength 2, // bDescriptorType 0x20, 0x00, // wTotalLength 1, // bNumInterfaces 1, // bConfigurationValue 0, // iConfiguration 0x80, // bmAttributes 250, // bMaxPower // Interface descriptor 9, // bLength 4, // bDescriptorType 0, // bInterfaceNumber 0, // bAlternateSetting 2, // bNumEndpoints 0x08, // bInterfaceClass 0x05, // bInterfaceSubClass 0x50, // bInterfaceProtocol 0, // iInterface // EP1 IN endpoint descriptor 7, // bLength 5, // bDescriptorType 0x81, // bEndpointAddress 2, // bmAttributes 0x40, 0x00, // wMaxPacketSize 0, // bInterval // EP1 OUT endpoint descriptor 7, // bLength 5, // bDescriptorType 0x01, // bEndpointAddress 2, // bmAttributes 0x40, 0x00, // wMaxPacketSize 0, // bInterval }; // Must be UTF-16, and the first character is a *manually-calculated* total length // (combined with a 0x03 string descriptor type) // Non-latin is allowed, but LANGID is hardcoded to be 0x0409 (English (United States)) // further down in the code. const uint16_t USB_MANUF[13] = u"\u031aArcaneNibble"; const uint16_t USB_PRODUCT[15] = u"\u031eCH32V UF2 Boot"; // Used for outputting serial number from electronic signature bytes const uint8_t HEXLUT[16] = "0123456789ABCDEF"; // SCSI INQUIRY standard response const uint8_t INQUIRY_RESPONSE[36] __attribute__((aligned(2))) = { 0x00, // direct access block device 0x80, // RMB removable media // (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) 0x04, // SPC-2 0x02, // fixed, response data format 0x1F, // additional length 0x00, // xxx mostly obsolete junk 0x00, 0x00, // Vendor 'A', 'r', 'c', 'a', 'n', 'e', 'N', 'b', // Product 'C', 'H', '3', '2', 'V', ' ', 'U', 'F', '2', ' ', 'B', 'o', 'o', 't', ' ', ' ', // Revision ' ', ' ', ' ', ' ', }; // FAT16 boot sector // Most of these parameters *cannot* be changed, // as synthesize_block assumes a particular layout const uint8_t BOOT_SECTOR[0x3e] __attribute__((aligned(2))) = { 0xeb, 0x3c, 0x90, // jump (seems to be required for some OSes to auto-mount) 'A', 'r', 'c', 'a', 'n', 'e', 'N', 'b', // oem name 0x00, 0x02, // 512 bytes/sector 0x01, // 1 sector/cluster 0x01, 0x00, // 1 reserved sector 0x01, // 1 fat 0x10, 0x00, // 16 root dir entries 0x00, 0x40, // num sectors 0xf8, // media descriptor 0x40, 0x00, // 16384 sectors/clusters -> 64 FAT sectors 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // useless stuff 0x80, // "drive number" 0x00, // reserved 0x29, // magic 0xde, 0xad, 0xbe, 0xef, // serial number 'C', 'H', '3', '2', 'V', ' ', 'U', 'F', '2', ' ', ' ', // volume label 'F', 'A', 'T', '1', '6', ' ', ' ', ' ', // fs type // note that the trailing 55 aa is generated in code // (seemingly also required for auto-mount on some OSes) }; // Change here to change UF2 data files const uint8_t INFO_UF2[70] __attribute__((aligned(2))) = "UF2 Bootloader v0.0.0\nModel: CH32V Generic\nBoard-ID: CH32Vxxx-Generic\n"; const uint8_t INDEX_HTM[119] __attribute__((aligned(2))) = "\n\n"; // Change here for different chips #define THIS_CHIP_FLASH_MAX_SZ_BYTES (224 * 1024) #define THIS_CHIP_RAM_MAX_SZ_BYTES (20 * 1024) #define BOOTLOADER_RESERVED_SZ_BYTES (4 * 1024) #define FAMILY_ID 0x699b62ec // FAT16 root directory entries // Most of these parameters *cannot* be changed, // as synthesize_block assumes a particular layout const uint8_t ROOT_DIR[32 * 3] __attribute__((aligned(2))) = { 'C', 'H', '3', '2', 'V', ' ', 'U', 'F', '2', ' ', ' ', // name 0x08, // attributes (volume label) 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // timestamps 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, // timestamps 0x00, 0x00, // start cluster 0x00, 0x00, 0x00, 0x00, // file size 'I', 'N', 'F', 'O', '_', 'U', 'F', '2', 'T', 'X', 'T', // name 0x01, // attributes (RO) 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // timestamps 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, // timestamps 0x02, 0x00, // start cluster sizeof(INFO_UF2), sizeof(INFO_UF2) >> 8, sizeof(INFO_UF2) >> 16, sizeof(INFO_UF2) >> 24, 'I', 'N', 'D', 'E', 'X', ' ', ' ', ' ', 'H', 'T', 'M', // name 0x01, // attributes (RO) 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // timestamps 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, // timestamps 0x03, 0x00, // start cluster sizeof(INDEX_HTM), sizeof(INDEX_HTM) >> 8, sizeof(INDEX_HTM) >> 16, sizeof(INDEX_HTM) >> 24, }; #define ESIG_UNIID(x) (*(volatile uint8_t*)(0x1FFFF7E8 + (x))) // XXX manual claims 96 bits but only 64 bits seem to actually be programmed? // Note that all of this stuff is declared *extern* // A significant amount of code size is being saved because the gp register // is pointed at 0x40006000, right at the beginning of USBD RAM // (and within reach of the USBD registers). // The linker is then being instructed to perform gp-relative linker relaxation. // Actual addresses for these variables are in the linker.lds file. extern volatile USBD_descriptor USB_DESCS[2]; extern volatile uint32_t USB_EP0_OUT[4]; extern volatile uint32_t USB_EP0_IN[4]; extern volatile uint32_t USB_EP1_OUT[32]; extern volatile uint32_t USB_EP1_IN[32]; // These are the state variables shoved into USBD RAM // (in the area not being used by buffers) extern uint32_t SCSI_XFER_CUR_LBA; extern uint32_t SCSI_XFER_BLK_LEFT; extern uint32_t BLOCKNUM_LO; extern uint32_t TOTBLOCKS_LO; extern uint32_t CSWTAG_LO; extern uint32_t CSWTAG_HI; extern uint32_t ADDRESS_LO; extern uint32_t ADDRESS_HI; extern uint32_t ACTIVE_CONFIG; extern uint32_t CTRL_XFER_STATE; extern uint32_t CTRL_XFER_STATE_X; extern uint32_t CTRL_XFER_DESC_SZ; extern uint32_t USB_SECTOR_STASH[128]; // Files larger than this won't cause an auto-reboot // (there is not enough USBD RAM to mark which blocks have been received) #define AUTO_BOOT_BITMAP_NUM_HWORDS 36 #define MAX_AUTO_BOOT_BLOCKS (AUTO_BOOT_BITMAP_NUM_HWORDS * 16 - 1) extern uint32_t UF2_GOT_BLOCKS[AUTO_BOOT_BITMAP_NUM_HWORDS]; // Constants for USBD registers #define USB_EPTYPE_BULK 0b00 #define USB_EPTYPE_CONTROL 0b01 #define USB_STAT_DISABLED 0b00 #define USB_STAT_STALL 0b01 #define USB_STAT_NAK 0b10 #define USB_STAT_ACK 0b11 // Other registers we need #define R16_BKP_DATAR10 (*(volatile uint32_t*)0x40006C28) #define R32_PWR_CTLR (*(volatile uint32_t*)0x40007000) #define R32_RCC_CTLR (*(volatile uint32_t*)0x40021000) #define R32_RCC_CFGR0 (*(volatile uint32_t*)0x40021004) #define R32_RCC_APB2PCENR (*(volatile uint32_t*)0x40021018) #define R32_RCC_APB1PCENR (*(volatile uint32_t*)0x4002101C) #define R32_RCC_RSTSCKR (*(volatile uint32_t*)0x40021024) #define R32_GPIOA_CFGLR (*(volatile uint32_t*)0x40010800) #define R32_GPIOA_CFGHR (*(volatile uint32_t*)0x40010804) #define R32_GPIOA_BSHR (*(volatile uint32_t*)0x40010810) // USBD registers are within range to perform gp relaxation // XXX wrong access size, but this doesn't seem to matter extern volatile uint32_t R16_USBD_EPR[16]; extern volatile uint16_t R16_USBD_CNTR; extern volatile uint16_t R16_USBD_ISTR; extern volatile uint16_t R16_USBD_DADDR; #define R32_FLASH_KEYR (*(volatile uint32_t*)0x40022004) #define R32_FLASH_STATR (*(volatile uint32_t*)0x4002200C) #define R32_FLASH_CTLR (*(volatile uint32_t*)0x40022010) #define R32_FLASH_ADDR (*(volatile uint32_t*)0x40022014) #define R32_FLASH_MODEKEYR (*(volatile uint32_t*)0x40022024) #define R32_EXTEN_CTR (*(volatile uint32_t*)0x40023800) #define STK_CTLR (*(volatile uint32_t*)0xE000F000) #define STK_SR (*(volatile uint32_t*)0xE000F004) #define STK_CMPLR (*(volatile uint32_t*)0xE000F010) #define PFIC_CFGR (*(volatile uint32_t*)0xE000E048) // Everything in this code is controlled by various state machines // USB device stack control transfer state handling // state_x[7:0] = addr #define STATE_SET_ADDR 0x00 // state_x[15:8] = byte pos // state_x[7:0] = bytes left #define STATE_GET_DESC 0x01 #define STATE_GET_STR_SERIAL 0x02 #define STATE_CTRL_SIMPLE_IN 0x03 // USB MSC state handling // msc_state contains both the state *and* request sense: // state[31:24] = additional sense code // state[23:20] = sense key // state[7:0] = state #define STATE_WANT_CBW 0x00 #define STATE_SENT_CSW 0x01 #define STATE_SENT_CSW_REBOOT 0x02 #define STATE_SENT_DATA_IN 0x03 // state[10:8] = sector fragment #define STATE_SEND_MORE_READ 0x04 // state[12] = uf2 good so far // state[10:8] = sector fragment #define STATE_WAITING_FOR_WRITE 0x05 // Because this code is running completely RAM-less // (other than explicit usage of USBD RAM), // there is no stack, and thus no space to spill registers. // WARNING: This means that only one level of // non-inline subroutine calls are possible. // This is checked by manually searching the assembly listing // for access to the sp register. // Just to be entirely sure, we've marked some functions as // __attribute__((always_inline)). Other functions are // explicitly *outlined* in order to save code size. // This is not entirely stable and relies on hand-checking // the generated assembly. __attribute__((always_inline)) static inline void set_ep_mode(uint32_t epidx, uint32_t epaddr, uint32_t eptype, uint32_t stat_rx, uint32_t stat_tx, uint32_t xtra, int clear_dtog) { // The way the shifts are written has been tweaked // to generate smaller code than some possible alternatives. // "xtra" is used solely to set bit8 (EP_KIND, STATUS_OUT). // This is an optimization present in the core to simplify // CONTROL IN transactions, i.e.: // 1. SETUP (H->D) // 2. IN (D->H) // 3. ZLP OUT (H->D) for status signaling // The bit optimizes step 3 s.t. it is not necessary to // check that a ZLP specifically was received. uint32_t val = R16_USBD_EPR[epidx]; uint32_t cur_stats; if (clear_dtog) cur_stats = val & 0x7070; else cur_stats = val & 0x3030; uint32_t want_stats = (stat_rx << 12) | (stat_tx) << 4; R16_USBD_EPR[epidx] = epaddr | (eptype << 9) | xtra | (cur_stats ^ want_stats); } // Outlining these functions saves code size static void set_ep0_ack_in() { set_ep_mode(0, 0, USB_EPTYPE_CONTROL, USB_STAT_STALL, USB_STAT_ACK, 0, 0); } static void set_ep0_stall() { set_ep_mode(0, 0, USB_EPTYPE_CONTROL, USB_STAT_STALL, USB_STAT_STALL, 0, 0); } static void set_ep1_ack_in() { set_ep_mode(1, 1, USB_EPTYPE_BULK, USB_STAT_STALL, USB_STAT_ACK, 0, 0); } static void set_ep1_ack_out() { set_ep_mode(1, 1, USB_EPTYPE_BULK, USB_STAT_ACK, USB_STAT_STALL, 0, 0); } __attribute__((always_inline)) static inline uint32_t min(uint32_t a, uint32_t b) { if (a <= b) return a; return b; } const uint8_t MODE_SENSE_6[4] __attribute__((aligned(2))) = { 0x03, 0x00, 0x00, 0x00, }; const uint8_t MODE_SENSE_10[8] __attribute__((aligned(2))) = { 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; const uint8_t READ_FORMAT_CAPACITY[12] __attribute__((aligned(2))) = { 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x40, 0x00, // 8 MiB 0x02, // formatted media 0x00, 0x02, 0x00, // 512 bytes / sector }; const uint8_t READ_CAPACITY[8] __attribute__((aligned(2))) = { 0x00, 0x00, 0x3f, 0xff, // 8 MiB 0x00, 0x00, 0x02, 0x00, }; static void ep1_send_hardcoded_response(const uint16_t *data, uint32_t len) { for (uint32_t i = 0; i < (len + 1) / 2; i++) USB_EP1_IN[i] = data[i]; USB_DESCS[1].count_tx = len; set_ep1_ack_in(); } static void synthesize_block(uint32_t block, uint32_t piece) { if (block == 0 || block == 65 || block == 66 || block == 67) { const uint16_t *sector_ptr; uint32_t sector_sz_16bits; if (block == 0) { sector_ptr = (uint16_t*)BOOT_SECTOR; sector_sz_16bits = (sizeof(BOOT_SECTOR) + 1) / 2; } else if (block == 65) { sector_ptr = (uint16_t*)ROOT_DIR; sector_sz_16bits = (sizeof(ROOT_DIR) + 1) / 2; } else if (block == 66) { sector_ptr = (uint16_t*)INFO_UF2; sector_sz_16bits = (sizeof(INFO_UF2) + 1) / 2; } else if (block == 67) { sector_ptr = (uint16_t*)INDEX_HTM; sector_sz_16bits = (sizeof(INDEX_HTM) + 1) / 2; } uint32_t cur_offset_16bits = piece * 32; int usbofs, secofs; for (usbofs = 0, secofs = cur_offset_16bits; usbofs < 32 && secofs < sector_sz_16bits; usbofs++, secofs++) USB_EP1_IN[usbofs] = sector_ptr[secofs]; for (; usbofs < 32; usbofs++) USB_EP1_IN[usbofs] = 0; if (block == 0 && piece == 7) USB_EP1_IN[31] = 0xaa55; } else if (block == 1 && piece == 0) { // special FAT entries USB_EP1_IN[0] = 0xfff8; USB_EP1_IN[1] = 0xffff; // one cluster each for the UF2 files USB_EP1_IN[2] = 0xfff8; USB_EP1_IN[3] = 0xfff8; for (int i = 4; i < 32; i++) USB_EP1_IN[i] = 0; } else { for (int i = 0; i < 32; i++) USB_EP1_IN[i] = 0; } USB_DESCS[1].count_tx = 64; set_ep1_ack_in(); } static void make_msc_csw(uint32_t dCSWTag, uint32_t error) { USB_EP1_IN[0] = 0x5355; USB_EP1_IN[1] = 0x5342; USB_EP1_IN[2] = dCSWTag; USB_EP1_IN[3] = dCSWTag >> 16; USB_EP1_IN[4] = 0; // xxx this isn't very right USB_EP1_IN[5] = 0; USB_EP1_IN[6] = error; USB_DESCS[1].count_tx = 13; set_ep1_ack_in(); } __attribute__((naked)) int main(void) { // Make sure this stuff is enabled // (not every startup.S code path activates them) R32_RCC_APB1PCENR |= (1 << 27) | (1 << 28); R32_PWR_CTLR |= 1 << 8; R16_BKP_DATAR10 = 0; // PLL setup: system clock 96 MHz R32_EXTEN_CTR |= (1 << 4); // sneaky div2 R32_RCC_CFGR0 = (R32_RCC_CFGR0 & ~0xff0000) | (0b01101000 << 16); R32_RCC_CTLR |= (1 << 24); while (!(R32_RCC_CTLR & (1 << 25))) {} R32_RCC_CFGR0 = (R32_RCC_CFGR0 & ~0b11) | 0b10; while ((R32_RCC_CFGR0 & 0b1100) != 0b1000) {} // As recommended in the manual, output low on these pins before enabling USB R32_RCC_APB2PCENR |= (1 << 2); R32_GPIOA_CFGHR = (R32_GPIOA_CFGHR & ~(0xff << 12)) | (0b00100010 << 12); R32_GPIOA_BSHR = (1 << 27) | (1 << 28); // Enable USB R32_RCC_APB1PCENR |= (1 << 23); R16_USBD_CNTR = 1; USB_DESCS[0].addr_tx = 0x30 / 2; USB_DESCS[0].addr_rx = 0x20 / 2; USB_DESCS[0].count_rx = (8 << 10); USB_DESCS[1].addr_tx = 0xc0 / 2; USB_DESCS[1].addr_rx = 0x40 / 2; USB_DESCS[1].count_rx = (2 << 10) | (1 << 15); // XXX because the table is at offset 0 don't bother writing this // Attach USB R16_USBD_CNTR = 0; R32_EXTEN_CTR |= (1 << 1); CTRL_XFER_STATE = 0; ACTIVE_CONFIG = 0; const uint8_t *outputting_desc = 0; CTRL_XFER_DESC_SZ = 0; ADDRESS_HI = 0; uint32_t msc_state = STATE_WANT_CBW; UF2_GOT_BLOCKS[AUTO_BOOT_BITMAP_NUM_HWORDS - 1] = 0; // FIXME: we've tried activating USB interrupts and using WFI/WFE // and haven't managed to get it to work properly with a // "still mostly polling model" // (i.e. WFE, then read interrupt status registers, then loop) // We haven't successfully investigated why. // We also haven't managed to write the code s.t. // variables (outputting_desc and msc_state) are properly stored // in registers when using a separate "IRQ handler" function while (1) { uint32_t usb_int_status = R16_USBD_ISTR; if (usb_int_status & (1 << 10)) { // RESET // set all to STALL, SETUP will come in nonetheless set_ep0_stall(); set_ep_mode(1, 1, USB_EPTYPE_BULK, USB_STAT_DISABLED, USB_STAT_DISABLED, 0, 0); R16_USBD_DADDR = 0x80; R16_USBD_CNTR = 0; } else if (usb_int_status & (1 << 11)) { // suspend // FIXME: note that this polling code makes the core exceed // suspend current limits // (the "Suspend Current Limit Changes ECN" says that 2.5 mA is allowed, but // the CH32V203 datasheet says that supply current in run mode is somewhere // between 3.7 mA to 7.1 mA at a F_{HCLK} of 96 MHz. Ports are usually not // sensitive to this, it merely breaks the rules and wastes battery life.) R16_USBD_CNTR |= 1 << 3; R16_USBD_CNTR |= 1 << 2; } else if (usb_int_status & (1 << 12)) { // wakeup R16_USBD_CNTR = 0; } else if (usb_int_status & (1 << 15)) { uint32_t epidx = usb_int_status & 0xf; uint32_t ep_status = R16_USBD_EPR[epidx]; if ((ep_status & (1 << 15)) && (epidx == 0)) { if (ep_status & (1 << 11)) { // setup, STAT_RX and STAT_TX both now NAK uint16_t bRequest_bmRequestType = USB_EP0_OUT[0]; uint32_t wLength = USB_EP0_OUT[3]; if (bRequest_bmRequestType == 0x0080 || bRequest_bmRequestType == 0x0081) { // GET_STATUS USB_EP0_IN[0] = 0; USB_DESCS[0].count_tx = min(2, wLength); CTRL_XFER_STATE = STATE_CTRL_SIMPLE_IN; set_ep0_ack_in(); } else if (bRequest_bmRequestType == 0x0102) { // CLEAR_FEATURE // XXX how is this supposed to work? // Not implementing this will cause subtle breakage // when an unsupported SCSI command is sent. uint32_t wIndex = USB_EP0_OUT[2]; if (wIndex == 0x81) { uint32_t dCSWTag = CSWTAG_LO | (CSWTAG_HI << 16); make_msc_csw(dCSWTag, 1); msc_state = (msc_state & 0xffffff00) | STATE_SENT_CSW; set_ep0_ack_in(); } else if (wIndex == 0x01) { set_ep1_ack_out(); msc_state = (msc_state & 0xffffff00) | STATE_WANT_CBW; set_ep0_ack_in(); } else { set_ep0_stall(); } } else if (bRequest_bmRequestType == 0x0500) { // SET_ADDRESS CTRL_XFER_STATE_X = USB_EP0_OUT[1]; CTRL_XFER_STATE = STATE_SET_ADDR; USB_DESCS[0].count_tx = 0; set_ep0_ack_in(); } else if (bRequest_bmRequestType == 0x0680) { // GET_DESCRIPTOR uint32_t wValue = USB_EP0_OUT[1]; if (wValue == 0x0100 || wValue == 0x0200 || wValue == 0x0301 || wValue == 0x0302) { if (wValue == 0x0100) { outputting_desc = USB_DEV_DESC; CTRL_XFER_DESC_SZ = sizeof(USB_DEV_DESC); } else if (wValue == 0x0200) { outputting_desc = USB_CONF_DESC; CTRL_XFER_DESC_SZ = sizeof(USB_CONF_DESC); } else if (wValue == 0x0301) { outputting_desc = (uint8_t*)USB_MANUF; CTRL_XFER_DESC_SZ = sizeof(USB_MANUF); } else if (wValue == 0x0302) { outputting_desc = (uint8_t*)USB_PRODUCT; CTRL_XFER_DESC_SZ = sizeof(USB_PRODUCT); } for (int i = 0; i < 8; i+=2) USB_EP0_IN[i / 2] = *((uint16_t*)(outputting_desc + i)); if (wLength < 8) { USB_DESCS[0].count_tx = wLength; CTRL_XFER_STATE_X = 0; CTRL_XFER_STATE = STATE_GET_DESC; } else { USB_DESCS[0].count_tx = 8; CTRL_XFER_STATE_X = (8 << 8) | (wLength - 8); CTRL_XFER_STATE = STATE_GET_DESC; } set_ep0_ack_in(); } else if (wValue == 0x0300) { // string LANGIDs USB_EP0_IN[0] = 0x0304; USB_EP0_IN[1] = 0x0409; USB_DESCS[0].count_tx = min(4, wLength); CTRL_XFER_STATE = STATE_CTRL_SIMPLE_IN; set_ep0_ack_in(); } else if (wValue == 0x0303) { // Note: having a serial number is mandatory for USB MSC // (and is generally nice to have) USB_EP0_IN[0] = 0x0300 | (24 * 2 + 2); USB_EP0_IN[1] = HEXLUT[ESIG_UNIID(0) >> 4]; USB_EP0_IN[2] = HEXLUT[ESIG_UNIID(0) & 0xf]; USB_EP0_IN[3] = HEXLUT[ESIG_UNIID(1) >> 4]; if (wLength < 8) { USB_DESCS[0].count_tx = wLength; CTRL_XFER_STATE_X = 0; CTRL_XFER_STATE = STATE_GET_STR_SERIAL; } else { USB_DESCS[0].count_tx = 8; CTRL_XFER_STATE_X = (8 << 8) | (wLength - 8); CTRL_XFER_STATE = STATE_GET_STR_SERIAL; } set_ep0_ack_in(); } else { // bad descriptor set_ep0_stall(); } } else if (bRequest_bmRequestType == 0x0880) { // GET_CONFIGURATION USB_EP0_IN[0] = ACTIVE_CONFIG; USB_DESCS[0].count_tx = min(1, wLength); CTRL_XFER_STATE = STATE_CTRL_SIMPLE_IN; set_ep0_ack_in(); } else if (bRequest_bmRequestType == 0x0900) { // SET_CONFIGURATION uint32_t wValue = USB_EP0_OUT[1]; if (wValue == 0 || wValue == 1) { ACTIVE_CONFIG = wValue; if (wValue) { // activate, allow OUT set_ep_mode(1, 1, USB_EPTYPE_BULK, USB_STAT_ACK, USB_STAT_STALL, 0, 1); msc_state = STATE_WANT_CBW; } else { // deactivate set_ep_mode(1, 1, USB_EPTYPE_BULK, USB_STAT_DISABLED, USB_STAT_DISABLED, 0, 0); } USB_DESCS[0].count_tx = 0; set_ep0_ack_in(); } else { set_ep0_stall(); } } else if (bRequest_bmRequestType == 0x0A81) { // GET_INTERFACE USB_EP0_IN[0] = 0; USB_DESCS[0].count_tx = min(1, wLength); CTRL_XFER_STATE = STATE_CTRL_SIMPLE_IN; set_ep0_ack_in(); } else { // unknown set_ep0_stall(); } } else { // back to stall for everything, expect SETUP set_ep0_stall(); } } else if ((ep_status & (1 << 7)) && (epidx == 0)) { switch (CTRL_XFER_STATE) { case STATE_SET_ADDR: R16_USBD_DADDR = 0x80 | (CTRL_XFER_STATE_X & 0x7f); // back to stall for everything, expect SETUP set_ep0_stall(); break; case STATE_GET_DESC: case STATE_GET_STR_SERIAL: { uint32_t x = CTRL_XFER_STATE_X; uint32_t byte_pos = (x >> 8) & 0xff; uint32_t bytes_left = x & 0xff; if (bytes_left == 0) { // ACK for OUT ZLP, STALL for IN set_ep_mode(0, 0, USB_EPTYPE_CONTROL, USB_STAT_ACK, USB_STAT_STALL, 1 << 8, 0); } else { uint32_t bytes = bytes_left; if (bytes > 8) bytes = 8; if (CTRL_XFER_STATE == STATE_GET_DESC) { if (byte_pos + bytes > CTRL_XFER_DESC_SZ) bytes = CTRL_XFER_DESC_SZ - byte_pos; for (int i = 0; i < bytes; i+=2) USB_EP0_IN[i / 2] = *((uint16_t*)(outputting_desc + byte_pos + i)); } else { uint32_t serial_no_pos = (byte_pos - 2) / 2; uint32_t ascii_bytes = (bytes + 1) / 2; if (serial_no_pos + ascii_bytes > 24) { ascii_bytes = 24 - serial_no_pos; // xxx this should be fine, reads 8 byte chunks bytes = ascii_bytes * 2; } for (int i = 0; i < ascii_bytes; i++) USB_EP0_IN[i] = HEXLUT[(ESIG_UNIID((serial_no_pos + i) / 2) >> ((1 - ((serial_no_pos + i) % 2)) * 4)) & 0xf]; } if (bytes < 8) { USB_DESCS[0].count_tx = bytes; CTRL_XFER_STATE_X = 0; } else { USB_DESCS[0].count_tx = 8; CTRL_XFER_STATE_X = ((byte_pos + 8) << 8) | (bytes_left - 8); } set_ep0_ack_in(); } } break; case STATE_CTRL_SIMPLE_IN: // ACK for OUT ZLP, STALL for IN set_ep_mode(0, 0, USB_EPTYPE_CONTROL, USB_STAT_ACK, USB_STAT_STALL, 1 << 8, 0); break; } } else if ((ep_status & (1 << 15)) && (epidx == 1)) { // ep 1 out switch (msc_state & 0xff) { case STATE_WANT_CBW: if ((USB_DESCS[1].count_rx & 0x3f) != 0x1f || USB_EP1_OUT[0] != 0x5355 || USB_EP1_OUT[1] != 0x4342) { set_ep1_ack_out(); } else { CSWTAG_LO = USB_EP1_OUT[2]; CSWTAG_HI = USB_EP1_OUT[3]; uint32_t dCSWTag = CSWTAG_LO | (CSWTAG_HI << 16); uint32_t dCBWDataTransferLength = USB_EP1_OUT[4] | (USB_EP1_OUT[5] << 16); uint32_t operation_code = USB_EP1_OUT[7] >> 8; switch (operation_code) { case 0x00: // test unit ready make_msc_csw(dCSWTag, 0); msc_state = STATE_SENT_CSW; break; case 0x03: // request sense USB_EP1_IN[0] = 0x0070; USB_EP1_IN[1] = (msc_state >> 20) & 0xf; USB_EP1_IN[2] = 0; USB_EP1_IN[3] = 0; USB_EP1_IN[4] = 0; USB_EP1_IN[5] = 0; USB_EP1_IN[6] = (msc_state >> 24) & 0xff; USB_EP1_IN[7] = 0; USB_EP1_IN[8] = 0; USB_DESCS[1].count_tx = 18; set_ep1_ack_in(); msc_state = STATE_SENT_DATA_IN; break; case 0x12: // inquiry if (USB_EP1_OUT[8] == 0) { ep1_send_hardcoded_response((uint16_t*)INQUIRY_RESPONSE, sizeof(INQUIRY_RESPONSE)); msc_state = STATE_SENT_DATA_IN; break; } msc_state = STATE_SENT_CSW | (5 << 20) | (0x24 << 24); set_ep_mode(1, 1, USB_EPTYPE_BULK, USB_STAT_STALL, USB_STAT_STALL, 0, 0); break; case 0x1a: // mode sense (6) ep1_send_hardcoded_response((uint16_t*)MODE_SENSE_6, sizeof(MODE_SENSE_6)); msc_state = STATE_SENT_DATA_IN; break; case 0x1b: // start/stop unit uint32_t param = USB_EP1_OUT[9] >> 8; make_msc_csw(dCSWTag, 0); // this flag is used by "eject" in Windows Explorer // (*not* safely remove hardware though, // and also not "drag to trash" in macOS) if (param == 0x02) msc_state = STATE_SENT_CSW_REBOOT; else msc_state = STATE_SENT_CSW; break; case 0x23: // read format capacity // without this command, Windows won't detect the drive // (but Linux and macOS will) ep1_send_hardcoded_response((uint16_t*)READ_FORMAT_CAPACITY, sizeof(READ_FORMAT_CAPACITY)); msc_state = STATE_SENT_DATA_IN; break; case 0x5a: // mode sense (10) ep1_send_hardcoded_response((uint16_t*)MODE_SENSE_10, sizeof(MODE_SENSE_10)); msc_state = STATE_SENT_DATA_IN; break; case 0x25: // READ CAPACITY (10) // xxx don't bother checking the silly fields ep1_send_hardcoded_response((uint16_t*)READ_CAPACITY, sizeof(READ_CAPACITY)); msc_state = STATE_SENT_DATA_IN; break; case 0x28: case 0x2a: { // READ (10) / WRITE (10) // xxx also don't bother checking the flags // @ 16: flags lba3 // @ 18: lba2 lba1 // @ 20: lba0 group // @ 22: len1 len0 uint32_t lba = (USB_EP1_OUT[8] << 16) & 0xff000000; uint32_t tmp = USB_EP1_OUT[9]; lba |= (tmp & 0xff) << 24; lba |= (tmp & 0xff00); tmp = USB_EP1_OUT[10]; lba |= (tmp & 0xff); tmp = USB_EP1_OUT[11]; uint32_t blocks = (tmp >> 8) | ((tmp & 0xff) << 8); // The following two checks are out of paranoia // Hosts don't seem to send this crap if (blocks > 0x4000 || lba >= 0x4000 || (blocks + lba) > 0x4000) { msc_state = STATE_SENT_CSW | (5 << 20) | (0x24 << 24); set_ep_mode(1, 1, USB_EPTYPE_BULK, USB_STAT_STALL, USB_STAT_STALL, 0, 0); break; } if (blocks == 0) { make_msc_csw(dCSWTag, 0); msc_state = STATE_SENT_CSW; break; } SCSI_XFER_BLK_LEFT = blocks; if (operation_code == 0x28) { // READ SCSI_XFER_CUR_LBA = lba; synthesize_block(lba, 0); msc_state = STATE_SEND_MORE_READ; } else { // WRITE // Note: bug that took forever to track down: need to set up IN // for *NAK* and *not* STALL. // Hosts seem to do the BULK IN asynchronously with sending all of the // data on the BULK OUT pipe (which takes multiple USB packets to send). // If the BULK IN (asynchronously) returns STALL, // the host will assume the command failed rather than "not done yet". // This will trigger mysterious bugs: // * Linux will fail using dd oflag=direct or with too much data written, // but you will win the race condition for small transfers. // * Windows will take a long time to detect and will only work // a fraction of the time (USBPcap shows a large number of device resets) set_ep_mode(1, 1, USB_EPTYPE_BULK, USB_STAT_ACK, USB_STAT_NAK, 0, 0); msc_state = STATE_WAITING_FOR_WRITE; } break; } default: // illegal command msc_state = STATE_SENT_CSW | (5 << 20); // Not checking the transfer length here will trigger mysterious bugs, // especially on Windows. Windows will get upset when trying to read // the *SCSI* serial number page using INQUIRY. // Quirks that happen when trying to do data transfer and control plane ops // via the same pipe... if (dCBWDataTransferLength == 0) { make_msc_csw(dCSWTag, 1); } else { set_ep_mode(1, 1, USB_EPTYPE_BULK, USB_STAT_STALL, USB_STAT_STALL, 0, 0); } break; } } break; case STATE_WAITING_FOR_WRITE: uint32_t piece = (msc_state >> 8) & 0b111; if (piece == 0) { if ((USB_EP1_OUT[0] == 0x4655) && (USB_EP1_OUT[1] == 0x0A32) && (USB_EP1_OUT[2] == 0x5157) && (USB_EP1_OUT[3] == 0x9E5D)) { // UF2 magic okay uint32_t flags_lo = USB_EP1_OUT[4]; uint32_t address_lo = USB_EP1_OUT[6]; uint32_t address_hi = USB_EP1_OUT[7]; uint32_t bytes_lo = USB_EP1_OUT[8]; uint32_t bytes_hi = USB_EP1_OUT[9]; uint32_t familyid = USB_EP1_OUT[14] | (USB_EP1_OUT[15] << 16); uint32_t blocknum_hi = USB_EP1_OUT[11]; uint32_t totblocks_hi = USB_EP1_OUT[13]; if (bytes_lo == 256 && bytes_hi == 0 && (address_lo & 0xff) == 0 && blocknum_hi == 0 && totblocks_hi == 0) { if (flags_lo & (0x2000) && familyid == FAMILY_ID) { // uf2 good so far! // *preliminary* bounds check if ((!(flags_lo & 1) && (address_hi >> 8) == 0x08) || ((flags_lo & 1) && (address_hi >> 8) == 0x20)) { ADDRESS_LO = address_lo; ADDRESS_HI = address_hi; BLOCKNUM_LO = USB_EP1_OUT[10]; TOTBLOCKS_LO = USB_EP1_OUT[12]; for (int i = 0; i < 16; i++) USB_SECTOR_STASH[i] = USB_EP1_OUT[16 + i]; msc_state += 0x1000; } } } } } else if (piece >= 1 && piece <= 4) { for (int i = 0; i < (piece != 4 ? 32 : 16); i++) USB_SECTOR_STASH[16 + (piece - 1) * 32 + i] = USB_EP1_OUT[i]; } if (piece != 7) { set_ep_mode(1, 1, USB_EPTYPE_BULK, USB_STAT_ACK, USB_STAT_NAK, 0, 0); msc_state += 0x100; } else { // full sector is done if (msc_state & 0x1000) { if (USB_EP1_OUT[30] == 0x6F30 && USB_EP1_OUT[31] == 0x0AB1) { // uf2 all magics are good! uint32_t address = ADDRESS_LO | (ADDRESS_HI << 16); uint32_t blocknum = BLOCKNUM_LO; uint32_t totblocks = TOTBLOCKS_LO; if (UF2_GOT_BLOCKS[AUTO_BOOT_BITMAP_NUM_HWORDS - 1] & 0x8000) { // not first uf2 block UF2_GOT_BLOCKS[blocknum / 16] |= 1 << (blocknum % 16); } else { // first uf2 block, or too big if (totblocks <= MAX_AUTO_BOOT_BLOCKS) { for (int i = 0; i < AUTO_BOOT_BITMAP_NUM_HWORDS; i++) UF2_GOT_BLOCKS[i] = 0; for (int i = totblocks; i <= MAX_AUTO_BOOT_BLOCKS; i++) { // deliberate off-by-1 to set high bit UF2_GOT_BLOCKS[i / 16] |= 1 << (i % 16); } UF2_GOT_BLOCKS[blocknum / 16] |= 1 << (blocknum % 16); } } if (address >= 0x08000000 + BOOTLOADER_RESERVED_SZ_BYTES && address <= 0x08000000 + THIS_CHIP_FLASH_MAX_SZ_BYTES - 256) { R32_FLASH_KEYR = 0x45670123; R32_FLASH_KEYR = 0xCDEF89AB; R32_FLASH_MODEKEYR = 0x45670123; R32_FLASH_MODEKEYR = 0xCDEF89AB; R32_FLASH_CTLR = 1 << 17; R32_FLASH_ADDR = address; R32_FLASH_CTLR = (1 << 17) | (1 << 6); while (R32_FLASH_STATR & 1) {} R32_FLASH_CTLR = 1 << 16; // Yes, we can program flash while running from it! // (as long as we are in the "zero-wait" area which we are) for (int i = 0; i < 64; i++) { volatile uint32_t *addr = (volatile uint32_t *)(address + i * 4); uint32_t val = USB_SECTOR_STASH[i * 2] | (USB_SECTOR_STASH[i * 2 + 1] << 16); *addr = val; while (R32_FLASH_STATR & 2) {} } R32_FLASH_CTLR = (1 << 16) | (1 << 21); while (R32_FLASH_STATR & 1) {} R32_FLASH_CTLR = (1 << 15) | (1 << 7); } if (address >= 0x20000000 && address <= 0x20000000 + THIS_CHIP_RAM_MAX_SZ_BYTES - 256) { for (int i = 0; i < 64; i++) { volatile uint32_t *addr = (volatile uint32_t *)(address + i * 4); uint32_t val = USB_SECTOR_STASH[i * 2] | (USB_SECTOR_STASH[i * 2 + 1] << 16); *addr = val; } } } } if (SCSI_XFER_BLK_LEFT == 1) { uint32_t dCSWTag = CSWTAG_LO | (CSWTAG_HI << 16); make_msc_csw(dCSWTag, 0); uint32_t all_blocks = 0xffff; for (int i = 0; i < AUTO_BOOT_BITMAP_NUM_HWORDS; i++) all_blocks &= UF2_GOT_BLOCKS[i]; if (all_blocks == 0xffff) msc_state = STATE_SENT_CSW_REBOOT; else msc_state = STATE_SENT_CSW; } else { SCSI_XFER_BLK_LEFT--; set_ep_mode(1, 1, USB_EPTYPE_BULK, USB_STAT_ACK, USB_STAT_NAK, 0, 0); msc_state = STATE_WAITING_FOR_WRITE; } } break; } } else if ((ep_status & (1 << 7)) && (epidx == 1)) { // ep 1 in uint32_t dCSWTag = CSWTAG_LO | (CSWTAG_HI << 16); switch (msc_state & 0xff) { case STATE_SENT_CSW: set_ep1_ack_out(); msc_state = (msc_state & 0xffffff00) | STATE_WANT_CBW; break; case STATE_SENT_CSW_REBOOT: // Microsoft's bootloader claims we need to do this // (but we didn't personally test it) STK_CMPLR = (50 /* ms */ * 12000 /* assume 96 MHz system clock, div8 */); STK_CTLR = 0b111000; STK_CTLR = 0b111001; while (!(STK_SR & 1)) {} STK_CTLR = 0; STK_SR = 0; R16_USBD_CNTR = 0b11; R32_EXTEN_CTR &= ~(1 << 1); if (ADDRESS_HI >> 8 == 0x20) { // ram boot, go back to original clock settings R32_RCC_CFGR0 = (R32_RCC_CFGR0 & ~0b11) | 0b00; while ((R32_RCC_CFGR0 & 0b1100) != 0b0000) {} // xxx note that we didn't reset every other peripheral config asm volatile("la t0, 0x20000000\njr t0\n1:\nj 1b\n"); } else { // wtf why does this work and the other stuff doesn't? // xxx weird things seem to happen if you try to soft reset // (or mess with PLLs) when the debugger is attached // we didn't fully characterize this R16_BKP_DATAR10 = 0x4170; PFIC_CFGR = 0xbeef0080; while (1) { asm volatile(""); } } break; case STATE_SENT_DATA_IN: make_msc_csw(dCSWTag, 0); msc_state = STATE_SENT_CSW; break; case STATE_SEND_MORE_READ: uint32_t piece = (msc_state >> 8) & 0b111; if (piece != 7) { synthesize_block(SCSI_XFER_CUR_LBA, piece + 1); msc_state += 0x100; } else { if (SCSI_XFER_BLK_LEFT == 1) { make_msc_csw(dCSWTag, 0); msc_state = STATE_SENT_CSW; } else { synthesize_block(SCSI_XFER_CUR_LBA + 1, 0); SCSI_XFER_CUR_LBA++; SCSI_XFER_BLK_LEFT--; msc_state = STATE_SEND_MORE_READ; } } break; } } } R16_USBD_ISTR = 0; } }