Skip to content

Commit f5b2459

Browse files
committed
Add critsec to rp2040 xfer, check endpoint status
- Implemented a critical section for the rp2040 port, which now prevents an IRQ from clearing the endpoint data structures while a transfer was in progress, which would then lead to a null pointer derefence. - Fixed a null-pointer dereference regarding ep->endpoint_control for endpoint 0, which does not have a control register.
1 parent cb22301 commit f5b2459

File tree

3 files changed

+30
-9
lines changed

3 files changed

+30
-9
lines changed

src/portable/raspberrypi/rp2040/dcd_rp2040.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ static void hw_endpoint_init(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t t
129129
ep->endpoint_control = &usb_dpram->ep_ctrl[num - 1].out;
130130
}
131131
}
132+
ep->configured = true;
132133
}
133134

134135
// Init, allocate buffer and enable endpoint

src/portable/raspberrypi/rp2040/rp2040_usb.c

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,8 @@ static uint32_t __tusb_irq_path_func(prepare_ep_buffer)(struct hw_endpoint* ep,
152152
}
153153

154154
// Prepare buffer control register value
155-
void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint* ep) {
156-
uint32_t ep_ctrl = *ep->endpoint_control;
155+
void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint *ep) {
156+
uint32_t ep_ctrl = ep->endpoint_control ? *ep->endpoint_control : 0;
157157

158158
// always compute and start with buffer 0
159159
uint32_t buf_ctrl = prepare_ep_buffer(ep, 0) | USB_BUF_CTRL_SEL;
@@ -182,7 +182,11 @@ void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint* ep)
182182
ep_ctrl |= EP_CTRL_INTERRUPT_PER_BUFFER;
183183
}
184184

185-
*ep->endpoint_control = ep_ctrl;
185+
uint8_t epnum = tu_edpt_number(ep->ep_addr);
186+
// There's no endpoint control for endpoint 0
187+
if (epnum != 0) {
188+
*ep->endpoint_control = ep_ctrl;
189+
}
186190

187191
TU_LOG(3, " Prepare BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(buf_ctrl), tu_u32_high16(buf_ctrl));
188192

@@ -194,7 +198,12 @@ void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint* ep)
194198
void hw_endpoint_xfer_start(struct hw_endpoint* ep, uint8_t* buffer, uint16_t total_len) {
195199
hw_endpoint_lock_update(ep, 1);
196200

197-
if (ep->active) {
201+
// We need to make sure the ep didn't get cleared from under us by an IRQ
202+
if ( !ep->configured ) {
203+
return;
204+
}
205+
206+
if ( ep->active ) {
198207
// TODO: Is this acceptable for interrupt packets?
199208
TU_LOG(1, "WARN: starting new transfer on already active ep %02X\r\n", ep->ep_addr);
200209
hw_endpoint_reset_transfer(ep);
@@ -263,7 +272,7 @@ static void __tusb_irq_path_func(_hw_endpoint_xfer_sync)(struct hw_endpoint* ep)
263272
uint16_t buf0_bytes = sync_ep_buffer(ep, 0);
264273

265274
// sync buffer 1 if double buffered
266-
if ((*ep->endpoint_control) & EP_CTRL_DOUBLE_BUFFERED_BITS) {
275+
if ( ep->endpoint_control && ((*ep->endpoint_control) & EP_CTRL_DOUBLE_BUFFERED_BITS) ) {
267276
if (buf0_bytes == ep->wMaxPacketSize) {
268277
// sync buffer 1 if not short packet
269278
sync_ep_buffer(ep, 1);

src/portable/raspberrypi/rp2040/rp2040_usb.h

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "common/tusb_common.h"
99

1010
#include "pico.h"
11+
#include "pico/critical_section.h"
1112
#include "hardware/structs/usb.h"
1213
#include "hardware/irq.h"
1314
#include "hardware/resets.h"
@@ -104,10 +105,20 @@ bool hw_endpoint_xfer_continue(struct hw_endpoint *ep);
104105
void hw_endpoint_reset_transfer(struct hw_endpoint *ep);
105106
void hw_endpoint_start_next_buffer(struct hw_endpoint *ep);
106107

107-
TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct hw_endpoint * ep, __unused int delta) {
108-
// todo add critsec as necessary to prevent issues between worker and IRQ...
109-
// note that this is perhaps as simple as disabling IRQs because it would make
110-
// sense to have worker and IRQ on same core, however I think using critsec is about equivalent.
108+
TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct hw_endpoint * ep, int delta) {
109+
static critical_section_t hw_endpoint_crit_sec;
110+
static int hw_endpoint_crit_sec_ref = 0;
111+
if ( !critical_section_is_initialized(&hw_endpoint_crit_sec) ) {
112+
critical_section_init(&hw_endpoint_crit_sec);
113+
}
114+
115+
if ( delta > 0 && !hw_endpoint_crit_sec_ref ) {
116+
critical_section_enter_blocking(&hw_endpoint_crit_sec);
117+
}
118+
hw_endpoint_crit_sec_ref += delta;
119+
if ( hw_endpoint_crit_sec_ref == 0 ) {
120+
critical_section_exit(&hw_endpoint_crit_sec);
121+
}
111122
}
112123

113124
void _hw_endpoint_buffer_control_update32(struct hw_endpoint *ep, uint32_t and_mask, uint32_t or_mask);

0 commit comments

Comments
 (0)