Skip to content

Commit ae077b0

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 b60d0ff commit ae077b0

File tree

3 files changed

+35
-8
lines changed

3 files changed

+35
-8
lines changed

src/portable/raspberrypi/rp2040/dcd_rp2040.c

+5-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@ static void _hw_endpoint_close(struct hw_endpoint *ep)
9898
{
9999
// Clear hardware registers and then zero the struct
100100
// Clears endpoint enable
101-
*ep->endpoint_control = 0;
101+
if (ep->endpoint_control)
102+
{
103+
*ep->endpoint_control = 0;
104+
}
102105
// Clears buffer available, etc
103106
*ep->buffer_control = 0;
104107
// Clear any endpoint state
@@ -178,6 +181,7 @@ static void hw_endpoint_init(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t t
178181
// alloc a buffer and fill in endpoint control register
179182
_hw_endpoint_alloc(ep, transfer_type);
180183
}
184+
ep->configured = true;
181185
}
182186

183187
static void hw_endpoint_xfer(uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes)

src/portable/raspberrypi/rp2040/rp2040_usb.c

+13-3
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ static uint32_t __tusb_irq_path_func(prepare_ep_buffer)(struct hw_endpoint *ep,
172172
// Prepare buffer control register value
173173
void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint *ep)
174174
{
175-
uint32_t ep_ctrl = *ep->endpoint_control;
175+
uint32_t ep_ctrl = ep->endpoint_control ? *ep->endpoint_control : 0;
176176

177177
// always compute and start with buffer 0
178178
uint32_t buf_ctrl = prepare_ep_buffer(ep, 0) | USB_BUF_CTRL_SEL;
@@ -203,7 +203,11 @@ void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint *ep)
203203
ep_ctrl |= EP_CTRL_INTERRUPT_PER_BUFFER;
204204
}
205205

206-
*ep->endpoint_control = ep_ctrl;
206+
uint8_t epnum = tu_edpt_number(ep->ep_addr);
207+
if (epnum != 0) // There's no endpoint control for endpoint 0
208+
{
209+
*ep->endpoint_control = ep_ctrl;
210+
}
207211

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

@@ -216,6 +220,12 @@ void hw_endpoint_xfer_start(struct hw_endpoint *ep, uint8_t *buffer, uint16_t to
216220
{
217221
hw_endpoint_lock_update(ep, 1);
218222

223+
// We need to make sure the ep didn't get cleared from under us by an IRQ
224+
if ( !ep->configured )
225+
{
226+
return;
227+
}
228+
219229
if ( ep->active )
220230
{
221231
// TODO: Is this acceptable for interrupt packets?
@@ -296,7 +306,7 @@ static void __tusb_irq_path_func(_hw_endpoint_xfer_sync) (struct hw_endpoint *ep
296306
uint16_t buf0_bytes = sync_ep_buffer(ep, 0);
297307

298308
// sync buffer 1 if double buffered
299-
if ( (*ep->endpoint_control) & EP_CTRL_DOUBLE_BUFFERED_BITS )
309+
if ( ep->endpoint_control && (*ep->endpoint_control) & EP_CTRL_DOUBLE_BUFFERED_BITS )
300310
{
301311
if (buf0_bytes == ep->wMaxPacketSize)
302312
{

src/portable/raspberrypi/rp2040/rp2040_usb.h

+17-4
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,22 @@ 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+
{
117+
critical_section_enter_blocking(&hw_endpoint_crit_sec);
118+
}
119+
hw_endpoint_crit_sec_ref += delta;
120+
if (hw_endpoint_crit_sec_ref == 0)
121+
{
122+
critical_section_exit(&hw_endpoint_crit_sec);
123+
}
111124
}
112125

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

0 commit comments

Comments
 (0)