first pass at comments

This commit is contained in:
R
2024-04-21 05:51:35 +01:00
parent ef659206e2
commit 7affb1b209
2 changed files with 85 additions and 16 deletions
+13
View File
@@ -1 +1,14 @@
# WCH RISC-V UF2 Bootloader
- Fits in $\leq$ 4096 bytes
- Tested with MounRiver GCC V1.91
- Code size is improved through use of "XW" instructions
- Supports USBD peripheral *only* (i.e. not USBFS)
- USBD and USBFS are completely different, and the QFN28 package (which is available in largest quantities on LCSC) only bonds out USBD
- 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 ¯\\\_(ツ)\_/¯
- Should work across most of the CH32V2xx and CH32V3xx family with appropriate changes to hardcoded constants.
- Allows both download to flash and to SRAM with the "not main flash" flag (this is how the RP2040 bootrom works)
- The entire size of the SRAM can be used, as USBD contains its own buffer memory independent of the main SRAM
- Auto-reboot on complete download, _but_ **only** works if the download is sufficiently small. Larger downloads will of course still flash, but they will not trigger auto-reboot and a manual reboot will be required.
- Lots of nasty code golfing tricks -- see comments in [bootloader.c](bootloader.c)
+69 -13
View File
@@ -1,3 +1,5 @@
// CH32V UF2 bootloader, size-optimized (target: <= 4096 bytes)
#include <stdint.h>
typedef struct USBD_descriptor {
@@ -25,7 +27,13 @@ typedef struct USBD_descriptor {
// 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.
// 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
@@ -44,6 +52,7 @@ const uint8_t USB_DEV_DESC[18] __attribute__((aligned(2))) = {
};
const uint8_t USB_CONF_DESC[0x20] __attribute__((aligned(2))) = {
// Configuration descriptor
9, // bLength
2, // bDescriptorType
0x20, 0x00, // wTotalLength
@@ -53,6 +62,7 @@ const uint8_t USB_CONF_DESC[0x20] __attribute__((aligned(2))) = {
0x80, // bmAttributes
250, // bMaxPower
// Interface descriptor
9, // bLength
4, // bDescriptorType
0, // bInterfaceNumber
@@ -63,6 +73,7 @@ const uint8_t USB_CONF_DESC[0x20] __attribute__((aligned(2))) = {
0x50, // bInterfaceProtocol
0, // iInterface
// EP1 IN endpoint descriptor
7, // bLength
5, // bDescriptorType
0x81, // bEndpointAddress
@@ -70,6 +81,7 @@ const uint8_t USB_CONF_DESC[0x20] __attribute__((aligned(2))) = {
0x40, 0x00, // wMaxPacketSize
0, // bInterval
// EP1 OUT endpoint descriptor
7, // bLength
5, // bDescriptorType
0x01, // bEndpointAddress
@@ -78,24 +90,34 @@ const uint8_t USB_CONF_DESC[0x20] __attribute__((aligned(2))) = {
0, // bInterval
};
// Must be UTF-16, and the first character is a *manually-calculated* total length
// (combined with a 0x03 string descriptor type)
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,
0x80,
0x04,
0x02,
0x1F,
0x00,
0x00, // direct access block device
0x80, // RMB removable media
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
'A', 'r', 'c', 'a', 'n', 'e', 'N', 'b', // oem name
@@ -116,13 +138,19 @@ const uint8_t BOOT_SECTOR[0x3e] __attribute__((aligned(2))) = {
'F', 'A', 'T', '1', '6', ' ', ' ', ' ', // fs type
};
// 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))) = "<!doctype html>\n<html><body><script>location.replace(\"https://github.com/ArcaneNibble/wch-uf2\")</script></body></html>\n";
// Change here for different chips
#define THIS_CHIP_FLASH_MAX_SZ_BYTES (224 * 1024)
#define BOOTLOADER_RESERVED_SZ_BYTES (4 * 1024)
#define THIS_CHIP_RAM_MAX_SZ_BYTES (20 * 1024)
#define FAMILY_ID 0xdeadbeef
#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)
@@ -131,7 +159,7 @@ const uint8_t ROOT_DIR[32 * 3] __attribute__((aligned(2))) = {
0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // timestamps
0x00, 0x00, // start cluster
0x00, 0x00, 0x00, 0x00, // file size]
0x00, 0x00, 0x00, 0x00, // file size
'I', 'N', 'F', 'O', '_', 'U', 'F', '2', 'T', 'X', 'T', // name
0x01, // attributes (RO)
@@ -155,6 +183,12 @@ const uint8_t ROOT_DIR[32 * 3] __attribute__((aligned(2))) = {
#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];
@@ -175,9 +209,12 @@ 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 MAX_AUTO_BOOT_BLOCKS (36 * 16 - 1)
extern uint32_t UF2_GOT_BLOCKS[36];
// Constants for USBD registers
#define USB_EPTYPE_BULK 0b00
#define USB_EPTYPE_CONTROL 0b01
@@ -186,6 +223,7 @@ extern uint32_t UF2_GOT_BLOCKS[36];
#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)
@@ -200,6 +238,7 @@ extern uint32_t UF2_GOT_BLOCKS[36];
#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
extern volatile uint32_t R16_USBD_EPR[16];
extern volatile uint16_t R16_USBD_CNTR;
@@ -220,6 +259,9 @@ extern volatile uint16_t R16_USBD_DADDR;
#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
@@ -228,6 +270,11 @@ extern volatile uint16_t R16_USBD_DADDR;
#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
@@ -238,7 +285,19 @@ extern volatile uint16_t R16_USBD_DADDR;
// state[10:8] = sector fragment
#define STATE_WAITING_FOR_WRITE 0x05
// WARNING: only one level of non-inline subroutine calls are possible
// 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) {
uint32_t val = R16_USBD_EPR[epidx];
@@ -389,9 +448,6 @@ __attribute__((naked)) int main(void) {
CTRL_XFER_DESC_SZ = 0;
ADDRESS_HI = 0;
// bits [31:24] = additional sense code
// bits [23:20] = sense key
// bits [7:0] = state
uint32_t msc_state = STATE_WANT_CBW;
UF2_GOT_BLOCKS[35] = 0;