summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-06-19 21:37:52 +0700
committerhathach <[email protected]>2026-06-19 21:37:52 +0700
commitdcb060c894713d60ce0ab009733e5d59a32d0d23 (patch)
tree5557c336eac906579b985d246c135831835b12cb /src
parent952ec68753960093066308eb313bd8650d78b7c9 (diff)
dcd/ch58x: complete the EP register map and right-size EP buffers
Tidy the CH58x register/buffer layout the initial port left rough. Register map (USBOTG_FS_TypeDef): - Extend the struct to the full CH583/582 datasheet Table 17-2 map instead of stopping at UEP567_MOD (0x0E) with the per-endpoint registers living only in raw-address macros. - Express the per-endpoint DMA/length/control registers as arrays of 4-byte slots (ch58x_ep_dma_t / ch58x_ep_ctrl_t): EP0-3 DMA at 0x10, EP0-4 ctrl at 0x20, EP5-7 DMA/ctrl split to 0x54/0x64 (EP4 has no DMA register of its own; it shares EP0's). TU_VERIFY_STATIC pins the slot sizes and block offsets, so the EP_TX_LEN/EP_CTRL/EP_DMA macros walk each block by the 4-byte stride (pointer arithmetic off slot 0, so the unused ternary branch can't trip -Warray-bounds). - Gate the two driver sites on CFG_TUSB_MCU == OPT_MCU_CH58X directly rather than the CH32_USBFS_EP_REGS_CUSTOM alias, which was only ever defined in the CH58x branch. EP buffers (the data struct): - Replace buffer[EP_MAX][2][64] on CH58x with named per-endpoint buffers: EP0/EP4 use the dedicated 192B ep0_ep4_buffer, so the old array left buffer[0]/buffer[4] allocated-but-unused. - Drop EP3's oversized iso buffer (out[64] + in[1023]); EP3 is bulk-only on CH58x, so it uses a plain 128-byte buffer like the others. The data struct shrinks from ~2636 to 1292 bytes. - Keep the now uniformly-64-byte buffers safe: dcd_edpt_iso_alloc()/iso_activate() refuse isochronous on CH58x (no iso support; 8-bit T_LEN caps a packet at 255B), and update_in()/update_out() additionally cap each packet copy to 64 bytes so a class that ignores the iso-alloc result cannot run a memcpy past a buffer into a neighbour's. Non-CH58x parts (e.g. ch32v103) keep the struct-based macros, buffer[EP_MAX], and the iso buffer unchanged. Verified on ch582m_evt HIL (ci.lan): all device examples pass; ch32v103 build unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/portable/wch/ch32_usbfs_reg.h59
-rw-r--r--src/portable/wch/dcd_ch32_usbfs.c102
2 files changed, 123 insertions, 38 deletions
diff --git a/src/portable/wch/ch32_usbfs_reg.h b/src/portable/wch/ch32_usbfs_reg.h
index 0d61e183c..9c58c467f 100644
--- a/src/portable/wch/ch32_usbfs_reg.h
+++ b/src/portable/wch/ch32_usbfs_reg.h
@@ -137,27 +137,53 @@
// the EP control/length block sits lower (EP0_CTRL @ +0x22), EP5-7 are split out, EP4
// shares EP0's DMA buffer, and EP5/6/7 mode bits live in one UEP567_MOD. The control/status
// block matches CH32. Two FS controllers exist (USB @ 0x40008000, USB2 @ 0x40008400); the
- // device uses USB0. EP registers are accessed via the CH58X macros below (not the struct).
+ // device uses USB0. Full register map per CH583/582 datasheet Table 17-2; the parameterized
+ // EP_* macros below index off these named fields.
#define CH58X_USBFS_BASE 0x40008000u
+ // Per-endpoint register slots, 4-byte stride each; the EP_* macros index arrays of these.
typedef struct {
- __IO uint8_t BASE_CTRL; // 0x00
- __IO uint8_t UDEV_CTRL; // 0x01
- __IO uint8_t INT_EN; // 0x02
- __IO uint8_t DEV_ADDR; // 0x03
+ __IO uint16_t DMA; // R16_UEPn_DMA: endpoint n buffer start address
+ __IO uint16_t reserved;
+ } ch58x_ep_dma_t;
+ typedef struct {
+ __IO uint8_t T_LEN; // R8_UEPn_T_LEN (+0): transmit length
+ __IO uint8_t reserved0;
+ __IO uint8_t CTRL; // R8_UEPn_CTRL (+2): endpoint control
+ __IO uint8_t reserved1;
+ } ch58x_ep_ctrl_t;
+ typedef struct {
+ __IO uint8_t BASE_CTRL; // 0x00 R8_USB_CTRL
+ __IO uint8_t UDEV_CTRL; // 0x01 R8_UDEV_CTRL
+ __IO uint8_t INT_EN; // 0x02 R8_USB_INT_EN
+ __IO uint8_t DEV_ADDR; // 0x03 R8_USB_DEV_AD
__IO uint8_t Reserve0; // 0x04
- __IO uint8_t MIS_ST; // 0x05
- __IO uint8_t INT_FG; // 0x06
- __IO uint8_t INT_ST; // 0x07
- __IO uint8_t RX_LEN; // 0x08 (8-bit on CH58X)
+ __IO uint8_t MIS_ST; // 0x05 R8_USB_MIS_ST
+ __IO uint8_t INT_FG; // 0x06 R8_USB_INT_FG
+ __IO uint8_t INT_ST; // 0x07 R8_USB_INT_ST
+ __IO uint8_t RX_LEN; // 0x08 R8_USB_RX_LEN (8-bit on CH58X)
__IO uint8_t Reserve1[3]; // 0x09..0x0B
- __IO uint8_t UEP4_1_MOD; // 0x0C
- __IO uint8_t UEP2_3_MOD; // 0x0D
- __IO uint8_t UEP567_MOD; // 0x0E
+ __IO uint8_t UEP4_1_MOD; // 0x0C R8_UEP4_1_MOD
+ __IO uint8_t UEP2_3_MOD; // 0x0D R8_UEP2_3_MOD
+ __IO uint8_t UEP567_MOD; // 0x0E R8_UEP567_MOD
+ __IO uint8_t Reserve2; // 0x0F
+ ch58x_ep_dma_t EP_DMA_0_3[4]; // 0x10 EP0-3 DMA (EP4 has no DMA reg; it shares EP0's, index 0)
+ ch58x_ep_ctrl_t EP_CTRL_0_4[5]; // 0x20 EP0-4 length/control
+ __IO uint8_t Reserve3[0x54u - 0x34u]; // 0x34..0x53
+ ch58x_ep_dma_t EP_DMA_5_7[3]; // 0x54 EP5-7 DMA
+ __IO uint8_t Reserve4[0x64u - 0x60u]; // 0x60..0x63
+ ch58x_ep_ctrl_t EP_CTRL_5_7[3]; // 0x64 EP5-7 length/control
} USBOTG_FS_TypeDef;
#define USBOTG_FS ((USBOTG_FS_TypeDef *) CH58X_USBFS_BASE)
+ // 4-byte slot stride + these block offsets pin every EP register to its datasheet address.
+ TU_VERIFY_STATIC(sizeof(ch58x_ep_dma_t) == 4, "CH58x EP DMA slot must be 4 bytes");
+ TU_VERIFY_STATIC(sizeof(ch58x_ep_ctrl_t) == 4, "CH58x EP ctrl slot must be 4 bytes");
+ TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_DMA_0_3) == 0x10, "CH58x EP_DMA_0_3 @0x10");
+ TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_CTRL_0_4) == 0x20, "CH58x EP_CTRL_0_4 @0x20");
+ TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_DMA_5_7) == 0x54, "CH58x EP_DMA_5_7 @0x54");
+ TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_CTRL_5_7) == 0x64, "CH58x EP_CTRL_5_7 @0x64");
+
#define CH32_USBFS_EP_CTRL_COMBINED 1
- #define CH32_USBFS_EP_REGS_CUSTOM 1 // EP register macros provided here, not by the driver
// CH58x's hardware AUTO_TOG does not stay in sync (notably across clear-stall and multi-packet
// bulk transfers), causing data-toggle mismatch and bus resets. Drive the toggle manually in
// the ISR instead. CH32V103/V20x/V307 keep AUTO_TOG (this macro is undefined for them).
@@ -170,13 +196,6 @@
#define NVIC_EnableIRQ(n) PFIC_EnableIRQ(n)
#define NVIC_DisableIRQ(n) PFIC_DisableIRQ(n)
#endif
-
- // EP register access. EP0-4: T_LEN @ +0x20+ep*4, CTRL @ +0x22+ep*4. EP5-7 split: T_LEN @
- // +0x64, CTRL @ +0x66. DMA: EP0-3 @ +0x10+ep*4, EP5-7 @ +0x54; EP4 shares EP0's buffer
- // (no own DMA reg) so its slot points at a reserved word.
- #define EP_TX_LEN(ep) (*(volatile uint8_t *)(CH58X_USBFS_BASE + ((ep) <= 4u ? 0x20u + (ep)*4u : 0x64u + ((ep)-5u)*4u)))
- #define EP_CTRL(ep) (*(volatile uint8_t *)(CH58X_USBFS_BASE + ((ep) <= 4u ? 0x22u + (ep)*4u : 0x66u + ((ep)-5u)*4u)))
- #define EP_DMA(ep) (*(volatile uint16_t *)(CH58X_USBFS_BASE + ((ep) <= 3u ? 0x10u + (ep)*4u : (ep) == 4u ? 0x40u : 0x54u + ((ep)-5u)*4u)))
#endif
#ifdef __GNUC__
diff --git a/src/portable/wch/dcd_ch32_usbfs.c b/src/portable/wch/dcd_ch32_usbfs.c
index 7beb91e16..12b45c784 100644
--- a/src/portable/wch/dcd_ch32_usbfs.c
+++ b/src/portable/wch/dcd_ch32_usbfs.c
@@ -35,13 +35,25 @@
/* private defines */
#define EP_MAX (8)
- // Struct-based EP register access (uniform layout). Some parts (e.g. CH58X) have a different
- // register map and define EP_DMA/EP_TX_LEN/EP_CTRL themselves in ch32_usbfs_reg.h.
- #ifndef CH32_USBFS_EP_REGS_CUSTOM
- #define EP_DMA(ep) ((&USBOTG_FS->UEP0_DMA)[ep])
- #define EP_TX_LEN(ep) ((&USBOTG_FS->UEP0_TX_LEN)[2 * ep])
- #define EP_TX_CTRL(ep) ((&USBOTG_FS->UEP0_TX_CTRL)[4 * ep])
- #define EP_RX_CTRL(ep) ((&USBOTG_FS->UEP0_RX_CTRL)[4 * ep])
+ // Struct-based EP register access (uniform layout). CH58X has a different register map and
+ // defines EP_DMA/EP_TX_LEN/EP_CTRL itself in ch32_usbfs_reg.h.
+ #if CFG_TUSB_MCU == OPT_MCU_CH58X
+ // CH58X EP registers split into a low block (EP0-4) and a high block (EP5-7). Walk from each
+ // block's first slot by the 4-byte slot stride (pointer arithmetic off slot 0, so the unused
+ // ternary branch's index can't trip -Warray-bounds). EP4 has no DMA register of its own (it
+ // shares EP0's, slot 0) and is never written (see ep_shares_ep0_dma()).
+ #define EP_TX_LEN(ep) (*((ep) <= 4u ? &USBOTG_FS->EP_CTRL_0_4[0].T_LEN + (ep) * 4u \
+ : &USBOTG_FS->EP_CTRL_5_7[0].T_LEN + ((ep) - 5u) * 4u))
+ #define EP_CTRL(ep) (*((ep) <= 4u ? &USBOTG_FS->EP_CTRL_0_4[0].CTRL + (ep) * 4u \
+ : &USBOTG_FS->EP_CTRL_5_7[0].CTRL + ((ep) - 5u) * 4u))
+ #define EP_DMA(ep) (*((ep) <= 3u ? &USBOTG_FS->EP_DMA_0_3[0].DMA + (ep) * 2u \
+ : (ep) == 4u ? &USBOTG_FS->EP_DMA_0_3[0].DMA \
+ : &USBOTG_FS->EP_DMA_5_7[0].DMA + ((ep) - 5u) * 2u))
+ #else
+ #define EP_DMA(ep) ((&USBOTG_FS->UEP0_DMA)[ep])
+ #define EP_TX_LEN(ep) ((&USBOTG_FS->UEP0_TX_LEN)[2 * ep])
+ #define EP_TX_CTRL(ep) ((&USBOTG_FS->UEP0_TX_CTRL)[4 * ep])
+ #define EP_RX_CTRL(ep) ((&USBOTG_FS->UEP0_RX_CTRL)[4 * ep])
#endif
// Endpoint control register access. The newer USBFS IP (CH32V20x/V307/X035) has separate
@@ -116,48 +128,82 @@ static struct {
bool ep0_tog;
bool isochronous[EP_MAX];
struct usb_xfer xfer[EP_MAX][2];
- TU_ATTR_ALIGNED(4) uint8_t buffer[EP_MAX][2][64];
#ifdef CH32_USBFS_EP4_SHARES_EP0
- // CH58X: EP0 and EP4 share one DMA region (EP4 has no DMA register). Layout:
- // EP0 [0:63] (half-duplex, OUT+IN) + EP4 OUT [64:127] + EP4 IN [128:191]. buffer[0]/buffer[4]
- // are left unused for this part.
+ // CH58X buffers laid out by hand so EP0/EP4 don't burn two unused buffer[] slots. EP0 and EP4
+ // share one contiguous 192-byte DMA region (EP4 has no DMA register of its own):
+ // EP0 [0:63] (half-duplex OUT+IN) + EP4 OUT [64:127] + EP4 IN [128:191]. Every other endpoint
+ // (incl. EP3, which is bulk-only here — CH58X has no isochronous support) gets a plain 128-byte
+ // OUT+IN buffer, so no oversized EP3 buffer is needed.
TU_ATTR_ALIGNED(4) uint8_t ep0_ep4_buffer[3 * 64];
-#endif
+ TU_ATTR_ALIGNED(4) uint8_t ep1_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep2_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep3_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep5_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep6_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep7_buffer[2][64];
+#else
+ TU_ATTR_ALIGNED(4) uint8_t buffer[EP_MAX][2][64];
+ // EP3 IN gets an enlarged buffer for full-speed isochronous (packets up to 1023 B).
TU_ATTR_ALIGNED(4) struct {
// OUT transfers >64 bytes will overwrite queued IN data!
uint8_t out[64];
uint8_t in[1023];
uint8_t pad;
} ep3_buffer;
+#endif
} data;
// DMA / copy buffer pointers per endpoint. The WCH USBFS buffer holds OUT (RX) at offset 0 and
// IN (TX) at +64; EP0 is half-duplex and reuses its OUT chunk for IN; EP3 has an enlarged IN
-// buffer for throughput. On CH58X, EP4 overlays EP0's region (see ep0_ep4_buffer above).
-static inline uint32_t ep_dma_addr(uint8_t ep) {
+// buffer for throughput. On CH58X, EP0/EP4 share ep0_ep4_buffer and the regular endpoints use
+// their own named buffer (see the struct above).
#ifdef CH32_USBFS_EP4_SHARES_EP0
- if (ep == 0) { return (uint32_t) &data.ep0_ep4_buffer[0]; }
+// OUT base of the regular CH58X endpoints (EP1/2/3/5/6/7; EP0/EP4 share ep0_ep4_buffer).
+static inline uint8_t* ch58x_ep_buffer(uint8_t ep) {
+ switch (ep) {
+ case 1: return data.ep1_buffer[0];
+ case 2: return data.ep2_buffer[0];
+ case 3: return data.ep3_buffer[0];
+ case 5: return data.ep5_buffer[0];
+ case 6: return data.ep6_buffer[0];
+ default: return data.ep7_buffer[0]; // ep == 7
+ }
+}
#endif
+
+static inline uint32_t ep_dma_addr(uint8_t ep) {
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+ if (ep == 0 || ep == 4) { return (uint32_t) &data.ep0_ep4_buffer[0]; } // EP4 shares EP0's DMA
+ return (uint32_t) ch58x_ep_buffer(ep);
+#else
if (ep == 3) { return (uint32_t) &data.ep3_buffer.out[0]; }
return (uint32_t) &data.buffer[ep][0];
+#endif
}
+
static inline uint8_t* ep_out_buf(uint8_t ep) {
#ifdef CH32_USBFS_EP4_SHARES_EP0
if (ep == 0) { return &data.ep0_ep4_buffer[0]; }
if (ep == 4) { return &data.ep0_ep4_buffer[64]; }
-#endif
+ return ch58x_ep_buffer(ep);
+#else
if (ep == 3) { return data.ep3_buffer.out; }
return data.buffer[ep][TUSB_DIR_OUT];
+#endif
}
+
static inline uint8_t* ep_in_buf(uint8_t ep) {
#ifdef CH32_USBFS_EP4_SHARES_EP0
if (ep == 0) { return &data.ep0_ep4_buffer[0]; } // EP0 half-duplex: IN reuses OUT chunk
if (ep == 4) { return &data.ep0_ep4_buffer[128]; }
-#endif
+ return ch58x_ep_buffer(ep) + 64; // IN at +64 within the endpoint's 128-byte buffer
+#else
if (ep == 0) { return data.buffer[0][TUSB_DIR_OUT]; } // EP0 half-duplex: IN reuses OUT chunk
if (ep == 3) { return data.ep3_buffer.in; }
return data.buffer[ep][TUSB_DIR_IN];
+#endif
}
+
// EP4 on CH58X has no DMA register (shares EP0's); skip its EP_DMA() write.
static inline bool ep_shares_ep0_dma(uint8_t ep) {
#ifdef CH32_USBFS_EP4_SHARES_EP0
@@ -174,6 +220,12 @@ static void update_in(uint8_t rhport, uint8_t ep, bool force) {
if (xfer->valid) {
if (force || xfer->len) {
size_t len = TU_MIN(xfer->max_size, xfer->len);
+#if CFG_TUSB_MCU == OPT_MCU_CH58X
+ // Every CH58x endpoint buffer is 64 bytes. Isochronous (which would push max_size up to 1023)
+ // is refused in dcd_edpt_iso_alloc(), but some classes (e.g. video) ignore that result, so cap
+ // the copy here to guarantee we never write past the buffer into a neighbouring endpoint's.
+ len = TU_MIN(len, 64u);
+#endif
memcpy(ep_in_buf(ep), xfer->buffer, len);
xfer->buffer += len;
xfer->len -= len;
@@ -204,6 +256,9 @@ static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len) {
struct usb_xfer *xfer = &data.xfer[ep][TUSB_DIR_OUT];
if (xfer->valid) {
size_t len = TU_MIN(xfer->max_size, TU_MIN(xfer->len, rx_len));
+#if CFG_TUSB_MCU == OPT_MCU_CH58X
+ len = TU_MIN(len, 64u); // cap to the 64-byte EP buffer (see update_in)
+#endif
memcpy(xfer->buffer, ep_out_buf(ep), len);
xfer->buffer += len;
xfer->len -= len;
@@ -253,7 +308,7 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
// enable other endpoints but NAK everything
USBOTG_FS->UEP4_1_MOD = 0xCC;
USBOTG_FS->UEP2_3_MOD = 0xCC;
-#ifdef CH32_USBFS_EP_REGS_CUSTOM
+#if CFG_TUSB_MCU == OPT_MCU_CH58X
// CH58X: a single mode register enables EP5/6/7 RX+TX (different bit layout than CH32).
USBOTG_FS->UEP567_MOD = RB_UEP5_RX_EN | RB_UEP5_TX_EN | RB_UEP6_RX_EN | RB_UEP6_TX_EN |
RB_UEP7_RX_EN | RB_UEP7_TX_EN;
@@ -413,18 +468,29 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet
(void)rhport;
(void)ep_addr;
(void)largest_packet_size;
+#if CFG_TUSB_MCU == OPT_MCU_CH58X
+ // No isochronous support on CH58x: its 8-bit T_LEN caps a packet at 255B and the endpoints use
+ // plain 64-byte buffers, so accepting an iso max_size (up to 1023) would let update_in()/
+ // update_out() run off the end of the buffer into neighbouring ones. Refuse it outright.
+ return false;
+#else
uint8_t ep = tu_edpt_number(ep_addr);
uint8_t dir = tu_edpt_dir(ep_addr);
data.isochronous[ep] = true;
data.xfer[ep][dir].max_size = largest_packet_size;
return true;
+#endif
}
bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
(void)rhport;
(void)desc_ep;
+#if CFG_TUSB_MCU == OPT_MCU_CH58X
+ return false; // CH58x has no isochronous support (see dcd_edpt_iso_alloc)
+#else
return true;
+#endif
}
bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {