From f7617168e2ce2e7ea2e49cba5962b69aed04900e Mon Sep 17 00:00:00 2001 From: renjie Date: Sun, 21 Jun 2026 14:06:23 +0800 Subject: fix(host): release spinlock in tuh_deinit to fix multi-rebuild panic tuh_rhport_init() calls osal_spin_init(&_usbh_spin), which under OPT_OS_PICO claims a hardware spinlock via critical_section_init(). There was no osal_spin_deinit(), so tuh_deinit() never released it: every host init/deinit cycle leaked one spinlock. RP2350 has a small spinlock pool, so a few usb_host rebuilds exhaust it and hw_claim_unused_from_range() panics (the long-standing "crashes on the 4th rebuild" bug). Add osal_spin_deinit() to all OSAL backends (critical_section_deinit for pico; no-op for none/freertos) and call it in tuh_deinit(). Verified 20/20 deinit+rebuild cycles on RP2350-Zero and Waveshare RP2350-USB-A (previously panicked on the 4th). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/host/usbh.c | 5 +++++ src/osal/osal_freertos.h | 8 ++++++++ src/osal/osal_none.h | 4 ++++ src/osal/osal_pico.h | 7 +++++++ 4 files changed, 24 insertions(+) diff --git a/src/host/usbh.c b/src/host/usbh.c index 6bafde368..d60feebb4 100644 --- a/src/host/usbh.c +++ b/src/host/usbh.c @@ -557,6 +557,11 @@ bool tuh_deinit(uint8_t rhport) { osal_mutex_delete(_usbh_mutex); _usbh_mutex = NULL; #endif + + // Release the spinlock claimed by osal_spin_init() in tuh_rhport_init(). + // Pairs with that init; without it each init/deinit cycle leaks a hardware + // spinlock and a few host rebuilds exhaust the pool (panic in claim). + osal_spin_deinit(&_usbh_spin); } return true; diff --git a/src/osal/osal_freertos.h b/src/osal/osal_freertos.h index bde5ec010..d9e084241 100644 --- a/src/osal/osal_freertos.h +++ b/src/osal/osal_freertos.h @@ -113,6 +113,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { spinlock_initialize(ctx); } +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_deinit(osal_spinlock_t *ctx) { + (void) ctx; +} + TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { if (!TUP_MCU_MULTIPLE_CORE && in_isr) { return; // single core MCU does not need to lock in ISR @@ -135,6 +139,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { (void) ctx; } +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_deinit(osal_spinlock_t *ctx) { + (void) ctx; +} + TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { if (in_isr) { if (!TUP_MCU_MULTIPLE_CORE) { diff --git a/src/osal/osal_none.h b/src/osal/osal_none.h index 3e397ef35..0893bb55c 100644 --- a/src/osal/osal_none.h +++ b/src/osal/osal_none.h @@ -46,6 +46,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { (void) ctx; } +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_deinit(osal_spinlock_t *ctx) { + (void) ctx; +} + TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { if (!in_isr) { ctx->interrupt_set(false); diff --git a/src/osal/osal_pico.h b/src/osal/osal_pico.h index ace5907d7..f1b48f172 100644 --- a/src/osal/osal_pico.h +++ b/src/osal/osal_pico.h @@ -54,6 +54,13 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { critical_section_init(ctx); } +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_deinit(osal_spinlock_t *ctx) { + // Release the hardware spinlock claimed by critical_section_init. Without this, + // every tuh_init()/tuh_deinit() cycle leaks one spinlock (RP2350's pool is + // small), so a few host rebuilds exhaust it and hw_claim_unused_from_range panics. + critical_section_deinit(ctx); +} + TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { (void) in_isr; critical_section_enter_blocking(ctx); -- cgit v1.3.1 From f9698aedaebe9b6edd4a13670cd1bfcb35883136 Mon Sep 17 00:00:00 2001 From: HiFiPhile Date: Mon, 22 Jun 2026 21:23:49 +0200 Subject: osal: add missing osal_spin_deinit() Signed-off-by: HiFiPhile --- src/osal/osal.h | 3 ++- src/osal/osal_mynewt.h | 4 ++++ src/osal/osal_rtthread.h | 4 ++++ src/osal/osal_rtx4.h | 4 ++++ src/osal/osal_zephyr.h | 4 ++++ 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/osal/osal.h b/src/osal/osal.h index a33280425..3fa1ff6bf 100644 --- a/src/osal/osal.h +++ b/src/osal/osal.h @@ -76,7 +76,8 @@ typedef void (*osal_task_func_t)( void * ); // Should be implemented as static inline function in osal_port.h header /* void osal_spin_init(osal_spinlock_t *ctx); - void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) + void osal_spin_deinit(osal_spinlock_t *ctx); + void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr); void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr); osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef); diff --git a/src/osal/osal_mynewt.h b/src/osal/osal_mynewt.h index 6d51f8ec3..86279f56e 100644 --- a/src/osal/osal_mynewt.h +++ b/src/osal/osal_mynewt.h @@ -52,6 +52,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { (void) ctx; } +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_deinit(osal_spinlock_t *ctx) { + (void) ctx; +} + TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { if (!TUP_MCU_MULTIPLE_CORE && in_isr) { return; // single core MCU does not need to lock in ISR diff --git a/src/osal/osal_rtthread.h b/src/osal/osal_rtthread.h index a778f5425..e126a2907 100644 --- a/src/osal/osal_rtthread.h +++ b/src/osal/osal_rtthread.h @@ -54,6 +54,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { rt_spin_lock_init(ctx); } +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_deinit(osal_spinlock_t *ctx) { + (void) ctx; +} + TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { if (!TUP_MCU_MULTIPLE_CORE && in_isr) { return; // single core MCU does not need to lock in ISR diff --git a/src/osal/osal_rtx4.h b/src/osal/osal_rtx4.h index 35860ddd5..813b351ff 100644 --- a/src/osal/osal_rtx4.h +++ b/src/osal/osal_rtx4.h @@ -67,6 +67,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { (void) ctx; } +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_deinit(osal_spinlock_t *ctx) { + (void) ctx; +} + TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { (void) ctx; (void) in_isr; } diff --git a/src/osal/osal_zephyr.h b/src/osal/osal_zephyr.h index 91f225f79..f0e1a6a2e 100644 --- a/src/osal/osal_zephyr.h +++ b/src/osal/osal_zephyr.h @@ -50,6 +50,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { (void) ctx; } +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_deinit(osal_spinlock_t *ctx) { + (void) ctx; +} + TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { if (!TUP_MCU_MULTIPLE_CORE && in_isr) { return; // single core MCU does not need to lock in ISR -- cgit v1.3.1 From f94e1f575e86a5f926e96f0b35f77fdb26228e60 Mon Sep 17 00:00:00 2001 From: HiFiPhile Date: Mon, 22 Jun 2026 21:25:06 +0200 Subject: usbh: remove Pico specific comment Signed-off-by: HiFiPhile --- src/host/usbh.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/host/usbh.c b/src/host/usbh.c index d60feebb4..9a7511fc8 100644 --- a/src/host/usbh.c +++ b/src/host/usbh.c @@ -558,9 +558,6 @@ bool tuh_deinit(uint8_t rhport) { _usbh_mutex = NULL; #endif - // Release the spinlock claimed by osal_spin_init() in tuh_rhport_init(). - // Pairs with that init; without it each init/deinit cycle leaks a hardware - // spinlock and a few host rebuilds exhaust the pool (panic in claim). osal_spin_deinit(&_usbh_spin); } -- cgit v1.3.1 From 41e9eaa65a935136085d78ec4b99c81ff991b560 Mon Sep 17 00:00:00 2001 From: HiFiPhile Date: Mon, 22 Jun 2026 21:27:05 +0200 Subject: usbd: add osal_spin_deinit() to tud_deinit() Signed-off-by: HiFiPhile --- src/device/usbd.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/device/usbd.c b/src/device/usbd.c index 339ccf4b4..abf74b1f5 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -593,6 +593,8 @@ bool tud_deinit(uint8_t rhport) { _usbd_mutex = NULL; #endif + osal_spin_deinit(&_usbd_spin); + _usbd_rhport = RHPORT_INVALID; return true; -- cgit v1.3.1 From 0d7318de1b7c711359b2fff2b70e64eb8f0c2e91 Mon Sep 17 00:00:00 2001 From: HiFiPhile Date: Mon, 22 Jun 2026 21:39:36 +0200 Subject: address review --- src/osal/osal_pico.h | 3 --- src/osal/osal_threadx.h | 4 ++++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/osal/osal_pico.h b/src/osal/osal_pico.h index a2fd470e4..12dce839f 100644 --- a/src/osal/osal_pico.h +++ b/src/osal/osal_pico.h @@ -65,9 +65,6 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { } TU_ATTR_ALWAYS_INLINE static inline void osal_spin_deinit(osal_spinlock_t *ctx) { - // Release the hardware spinlock claimed by critical_section_init. Without this, - // every tuh_init()/tuh_deinit() cycle leaks one spinlock (RP2350's pool is - // small), so a few host rebuilds exhaust it and hw_claim_unused_from_range panics. critical_section_deinit(ctx); } diff --git a/src/osal/osal_threadx.h b/src/osal/osal_threadx.h index cca4eb487..020fd26aa 100644 --- a/src/osal/osal_threadx.h +++ b/src/osal/osal_threadx.h @@ -88,6 +88,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { (void) ctx; } +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_deinit(osal_spinlock_t *ctx) { + (void) ctx; +} + TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { if (!in_isr) { ctx->interrupt_set(false); -- cgit v1.3.1