diff options
| author | HiFiPhile <[email protected]> | 2026-03-06 11:59:25 +0100 |
|---|---|---|
| committer | HiFiPhile <[email protected]> | 2026-03-06 11:59:25 +0100 |
| commit | c186063a6387ab6649fcc3e6e450ced854d20d03 (patch) | |
| tree | ddd40172328355b10da04b84a5f97b5d706fe914 /src | |
| parent | ee6f4f6f2a1f07db7c145551b779ce8cf878c2ac (diff) | |
| parent | 06a4c6d7190ef83f76b2a65496f2a48a54a8c134 (diff) | |
Merge remote-tracking branch 'tinyusb/master' into samx7x_update
Diffstat (limited to 'src')
41 files changed, 871 insertions, 270 deletions
diff --git a/src/class/dfu/dfu_device.c b/src/class/dfu/dfu_device.c index d3cc53918..a09c53b7e 100644 --- a/src/class/dfu/dfu_device.c +++ b/src/class/dfu/dfu_device.c @@ -327,7 +327,7 @@ bool dfu_moded_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control default: if (stage == CONTROL_STAGE_SETUP) { - return reply_getstatus(rhport, request, _dfu_ctx.state, _dfu_ctx.status, 0); + return reply_getstatus(rhport, request, (dfu_state_t) _dfu_ctx.state, (dfu_status_t) _dfu_ctx.status, 0); } break; } @@ -376,7 +376,7 @@ static bool process_download_get_status(uint8_t rhport, uint8_t stage, const tus timeout = 0; } - return reply_getstatus(rhport, request, next_state, _dfu_ctx.status, timeout); + return reply_getstatus(rhport, request, next_state, (dfu_status_t) _dfu_ctx.status, timeout); } else if (stage == CONTROL_STAGE_ACK) { if (_dfu_ctx.flashing_in_progress) { _dfu_ctx.state = DFU_DNBUSY; @@ -405,7 +405,7 @@ static bool process_manifest_get_status(uint8_t rhport, uint8_t stage, const tus timeout = 0; } - return reply_getstatus(rhport, request, next_state, _dfu_ctx.status, timeout); + return reply_getstatus(rhport, request, next_state, (dfu_status_t) _dfu_ctx.status, timeout); } else if (stage == CONTROL_STAGE_ACK) { if (_dfu_ctx.flashing_in_progress) { _dfu_ctx.state = DFU_MANIFEST; diff --git a/src/class/midi/midi_device.c b/src/class/midi/midi_device.c index 023a81595..a49cd725b 100644 --- a/src/class/midi/midi_device.c +++ b/src/class/midi/midi_device.c @@ -168,6 +168,108 @@ uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void *buffer, ui return total_read; } +// Note: this function shares stream->buffer with tud_midi_n_stream_read(). +// Do not mix calls to both functions on the same interface. +uint32_t tud_midi_n_demux_stream_read(uint8_t itf, uint8_t *p_cable_num, void *buffer, uint32_t bufsize) { + TU_VERIFY(p_cable_num != NULL && buffer != NULL && bufsize > 0, 0); + + midid_interface_t *p_midi = &_midid_itf[itf]; + midi_driver_stream_t *stream = &p_midi->stream_read; + tu_edpt_stream_t *ep_str = &p_midi->ep_stream.rx; + + uint8_t *buf8 = (uint8_t *)buffer; + uint32_t total_read = 0; + + // Initialize to invalid cable so callers can detect "no data" even when + // the return value is 0. + *p_cable_num = 0xff; + + // If there are leftover bytes from a previous partial read, return them first + if (stream->total > 0) { + *p_cable_num = (stream->buffer[0] >> 4) & 0x0f; + const uint8_t count = (uint8_t)tu_min32((uint32_t)(stream->total - stream->index), bufsize); + TU_VERIFY(0 == tu_memcpy_s(buf8, bufsize, stream->buffer + 1 + stream->index, count)); + + total_read += count; + stream->index += count; + buf8 += count; + bufsize -= count; + + if (stream->total == stream->index) { + stream->index = 0; + stream->total = 0; + } + + if (bufsize == 0) { + return total_read; + } + } + + while (bufsize > 0) { + // Peek at next packet header to get cable number without consuming + uint8_t one_byte; + if (!tu_edpt_stream_peek(ep_str, &one_byte)) { + break; + } + + const uint8_t next_cable = (one_byte >> 4) & 0x0f; + + // Stop if cable changed (covers both leftover-originated reads and + // freshly consumed packets — total_read > 0 in either case) + if (total_read > 0 && next_cable != *p_cable_num) { + break; + } + *p_cable_num = next_cable; + + // Consume the packet + if (!tud_midi_n_packet_read(itf, stream->buffer)) { + break; + } + + const uint8_t code_index = stream->buffer[0] & 0x0f; + uint8_t msg_bytes; + + // MIDI 1.0 Table 4-1: Code Index Number Classifications + switch (code_index) { + case MIDI_CIN_MISC: + case MIDI_CIN_CABLE_EVENT: + // Reserved and unused, skip this packet + continue; + + case MIDI_CIN_SYSEX_END_1BYTE: + case MIDI_CIN_1BYTE_DATA: + msg_bytes = 1; + break; + + case MIDI_CIN_SYSCOM_2BYTE: + case MIDI_CIN_SYSEX_END_2BYTE: + case MIDI_CIN_PROGRAM_CHANGE: + case MIDI_CIN_CHANNEL_PRESSURE: + msg_bytes = 2; + break; + + default: + msg_bytes = 3; + break; + } + + const uint8_t count = (uint8_t)tu_min32((uint32_t)msg_bytes, bufsize); + TU_VERIFY(0 == tu_memcpy_s(buf8, bufsize, stream->buffer + 1, count)); + + total_read += count; + buf8 += count; + bufsize -= count; + + if (count < msg_bytes) { + // Output buffer full, save remaining for next call + stream->total = msg_bytes; + stream->index = count; + } + } + + return total_read; +} + bool tud_midi_n_packet_read(uint8_t itf, uint8_t packet[4]) { midid_interface_t *p_midi = &_midid_itf[itf]; tu_edpt_stream_t *ep_str = &p_midi->ep_stream.rx; diff --git a/src/class/midi/midi_device.h b/src/class/midi/midi_device.h index ddbc2f9f0..b80ad544a 100644 --- a/src/class/midi/midi_device.h +++ b/src/class/midi/midi_device.h @@ -66,6 +66,13 @@ uint32_t tud_midi_n_available(uint8_t itf, uint8_t cable_num); // Read byte stream (legacy) uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void *buffer, uint32_t bufsize); +// Read byte stream with cable demultiplexing: returns the cable number of the +// data that was read. Reads from a single cable per call; stops when the next +// packet belongs to a different cable so the caller can dispatch per-cable. +// Note: shares internal state with tud_midi_n_stream_read(); do not mix both +// on the same interface. +uint32_t tud_midi_n_demux_stream_read(uint8_t itf, uint8_t *p_cable_num, void *buffer, uint32_t bufsize); + // Write byte Stream (legacy) uint32_t tud_midi_n_stream_write(uint8_t itf, uint8_t cable_num, const uint8_t *buffer, uint32_t bufsize); @@ -97,6 +104,11 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_midi_stream_read(void *buffer, } TU_ATTR_ALWAYS_INLINE static inline uint32_t +tud_midi_demux_stream_read(uint8_t *p_cable_num, void *buffer, uint32_t bufsize) { + return tud_midi_n_demux_stream_read(0, p_cable_num, buffer, bufsize); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_midi_stream_write(uint8_t cable_num, const uint8_t *buffer, uint32_t bufsize) { return tud_midi_n_stream_write(0, cable_num, buffer, bufsize); } diff --git a/src/common/tusb_fifo.c b/src/common/tusb_fifo.c index 9f188f296..8bd79e56d 100644 --- a/src/common/tusb_fifo.c +++ b/src/common/tusb_fifo.c @@ -30,11 +30,6 @@ #define TU_FIFO_DBG 0 -// Suppress IAR warning -// Warning[Pa082]: undefined behavior: the order of volatile accesses is undefined in this statement -#if defined(__ICCARM__) - #pragma diag_suppress = Pa082 -#endif #if OSAL_MUTEX_REQUIRED @@ -496,7 +491,9 @@ uint16_t tu_fifo_peek_n_access_mode(tu_fifo_t *f, void *p_buffer, uint16_t n, ui // Read n items without removing it from the FIFO, correct read pointer if overflowed uint16_t tu_fifo_peek_n(tu_fifo_t *f, void *p_buffer, uint16_t n) { ff_lock(f->mutex_rd); - const uint16_t ret = tu_fifo_peek_n_access_mode(f, p_buffer, n, f->wr_idx, f->rd_idx, NULL); + const uint16_t wr_idx = f->wr_idx; + const uint16_t rd_idx = f->rd_idx; + const uint16_t ret = tu_fifo_peek_n_access_mode(f, p_buffer, n, wr_idx, rd_idx, NULL); ff_unlock(f->mutex_rd); return ret; } @@ -506,7 +503,8 @@ uint16_t tu_fifo_read_n_access_mode(tu_fifo_t *f, void *buffer, uint16_t n, cons ff_lock(f->mutex_rd); // Peek the data: f->rd_idx might get modified in case of an overflow so we can not use a local variable - n = tu_fifo_peek_n_access_mode(f, buffer, n, f->wr_idx, f->rd_idx, access_mode); + const uint16_t wr_idx = f->wr_idx; + n = tu_fifo_peek_n_access_mode(f, buffer, n, wr_idx, f->rd_idx, access_mode); f->rd_idx = advance_index(f->depth, f->rd_idx, n); ff_unlock(f->mutex_rd); @@ -633,7 +631,8 @@ static bool ff_peek_local(tu_fifo_t *f, void *buf, uint16_t wr_idx, uint16_t rd_ bool tu_fifo_read(tu_fifo_t *f, void *buffer) { // Peek the data // f->rd_idx might get modified in case of an overflow so we can not use a local variable - const bool ret = ff_peek_local(f, buffer, f->wr_idx, f->rd_idx); + const uint16_t wr_idx = f->wr_idx; + const bool ret = ff_peek_local(f, buffer, wr_idx, f->rd_idx); if (ret) { ff_lock(f->mutex_rd); f->rd_idx = advance_index(f->depth, f->rd_idx, 1); @@ -645,7 +644,9 @@ bool tu_fifo_read(tu_fifo_t *f, void *buffer) { // Read one item without removing it from the FIFO, correct read index if overflowed bool tu_fifo_peek(tu_fifo_t *f, void *p_buffer) { - return ff_peek_local(f, p_buffer, f->wr_idx, f->rd_idx); + const uint16_t wr_idx = f->wr_idx; + const uint16_t rd_idx = f->rd_idx; + return ff_peek_local(f, p_buffer, wr_idx, rd_idx); } // Write one element into the buffer diff --git a/src/common/tusb_fifo.h b/src/common/tusb_fifo.h index 86ba59059..b31a0802e 100644 --- a/src/common/tusb_fifo.h +++ b/src/common/tusb_fifo.h @@ -120,9 +120,9 @@ typedef struct { uint8_t *buffer; // buffer pointer uint16_t depth; // max items bool overwritable; // overwritable when full - // 1 byte padding here + // 1 byte padding here - volatile uint16_t wr_idx; // write index TODO maybe can drop volatile + volatile uint16_t wr_idx; // write index volatile uint16_t rd_idx; // read index #if OSAL_MUTEX_REQUIRED @@ -291,16 +291,22 @@ TU_ATTR_ALWAYS_INLINE static inline bool tu_fifo_empty(const tu_fifo_t *f) { // return number of items in fifo, capped to fifo's depth TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_fifo_count(const tu_fifo_t *f) { - return tu_min16(tu_ff_overflow_count(f->depth, f->wr_idx, f->rd_idx), f->depth); + const uint16_t wr_idx = f->wr_idx; + const uint16_t rd_idx = f->rd_idx; + return tu_min16(tu_ff_overflow_count(f->depth, wr_idx, rd_idx), f->depth); } // check if fifo is full TU_ATTR_ALWAYS_INLINE static inline bool tu_fifo_full(const tu_fifo_t *f) { - return tu_ff_overflow_count(f->depth, f->wr_idx, f->rd_idx) >= f->depth; + const uint16_t wr_idx = f->wr_idx; + const uint16_t rd_idx = f->rd_idx; + return tu_ff_overflow_count(f->depth, wr_idx, rd_idx) >= f->depth; } TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_fifo_remaining(const tu_fifo_t *f) { - return tu_ff_remaining_local(f->depth, f->wr_idx, f->rd_idx); + const uint16_t wr_idx = f->wr_idx; + const uint16_t rd_idx = f->rd_idx; + return tu_ff_remaining_local(f->depth, wr_idx, rd_idx); } #ifdef __cplusplus diff --git a/src/common/tusb_private.h b/src/common/tusb_private.h index 43ce7a1df..5a51dfc37 100644 --- a/src/common/tusb_private.h +++ b/src/common/tusb_private.h @@ -24,8 +24,8 @@ * This file is part of the TinyUSB stack. */ -#ifndef TUSB_PRIVATE_H_ -#define TUSB_PRIVATE_H_ +#ifndef TUSB_PRIVATE_H +#define TUSB_PRIVATE_H // Internal Helper used by Host and Device Stack @@ -33,9 +33,11 @@ extern "C" { #endif -//--------------------------------------------------------------------+ -// Configuration -//--------------------------------------------------------------------+ +typedef void (*tusb_defer_func_t)(uintptr_t param); + + //--------------------------------------------------------------------+ + // Configuration + //--------------------------------------------------------------------+ #define TUP_USBIP_CONTROLLER_NUM 2 extern tusb_role_t _tusb_rhport_role[TUP_USBIP_CONTROLLER_NUM]; diff --git a/src/device/usbd.c b/src/device/usbd.c index cca4169d7..127a7bd62 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -585,7 +585,7 @@ bool tud_deinit(uint8_t rhport) { // Deinit device controller driver dcd_int_disable(rhport); dcd_disconnect(rhport); - TU_VERIFY(dcd_deinit(rhport)); + TU_ASSERT(dcd_deinit(rhport)); // Deinit class drivers for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) { diff --git a/src/host/hcd.h b/src/host/hcd.h index 36a7f5da5..47d672f9e 100644 --- a/src/host/hcd.h +++ b/src/host/hcd.h @@ -59,7 +59,7 @@ typedef enum { HCD_EVENT_XFER_COMPLETE, USBH_EVENT_FUNC_CALL, // Not an HCD event - HCD_EVENT_COUNT + HCD_EVENT_INVALID } hcd_eventid_t; typedef struct { @@ -72,7 +72,6 @@ typedef struct { struct { uint8_t hub_addr; uint8_t hub_port; - uint8_t speed; } connection; // XFER_COMPLETE diff --git a/src/host/usbh.c b/src/host/usbh.c index d702e9186..33d74862f 100644 --- a/src/host/usbh.c +++ b/src/host/usbh.c @@ -169,6 +169,12 @@ static OSAL_SPINLOCK_DEF(_usbh_spin, usbh_int_set); OSAL_QUEUE_DEF(usbh_int_set, _usbh_qdef, CFG_TUH_TASK_QUEUE_SZ, hcd_event_t); static osal_queue_t _usbh_q; + #if CFG_TUH_HUB +// Deferred attachment queue, only needed when using hub +OSAL_QUEUE_DEF(usbh_int_set, _usbh_daqdef, CFG_TUH_HUB, hcd_event_t); +static osal_queue_t _usbh_daq; + #endif + // Control transfers: since most controllers do not support multiple control transfers // on multiple devices concurrently and control transfers are not used much except for // enumeration, we will only execute control transfers one at a time. @@ -184,11 +190,18 @@ typedef struct { } usbh_ctrl_xfer_info_t; typedef struct { + tusb_defer_func_t func; + uintptr_t arg; + uint32_t at_ms; +} usbh_call_after_t; + +typedef struct { uint8_t controller_id; // controller ID uint8_t enumerating_daddr; // device address of the device being enumerated uint8_t attach_debouncing_bm; // bitmask for roothub port attach debouncing tuh_bus_info_t dev0_bus; // bus info for dev0 in enumeration usbh_ctrl_xfer_info_t ctrl_xfer_info; // control transfer + usbh_call_after_t call_after; } usbh_data_t; static usbh_data_t _usbh_data = { @@ -311,7 +324,8 @@ TU_ATTR_ALWAYS_INLINE static inline usbh_class_driver_t const *get_driver(uint8_ //--------------------------------------------------------------------+ // Function Inline and Prototypes //--------------------------------------------------------------------+ -static bool enum_new_device(hcd_event_t* event); +static void enum_new_device(hcd_event_t* event); +static void enum_delay_async(uintptr_t state); static void process_remove_event(hcd_event_t *event); static void remove_device_tree(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port); static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size); @@ -349,6 +363,15 @@ TU_ATTR_ALWAYS_INLINE static inline bool usbh_setup_send(uint8_t daddr, const ui return ret; } +bool usbh_defer_func_ms_async(uint32_t ms, tusb_defer_func_t func, uintptr_t param) { + TU_ASSERT(_usbh_data.call_after.func == NULL); + TU_LOG_USBH("USBH schedule function after %u ms\r\n", (unsigned int)ms); + _usbh_data.call_after.func = func; + _usbh_data.call_after.arg = param; + _usbh_data.call_after.at_ms = tusb_time_millis_api() + ms; + return true; +} + TU_ATTR_ALWAYS_INLINE static inline void usbh_device_close(uint8_t rhport, uint8_t daddr) { hcd_device_close(rhport, daddr); @@ -360,6 +383,10 @@ TU_ATTR_ALWAYS_INLINE static inline void usbh_device_close(uint8_t rhport, uint8 // invalidate if enumerating if (daddr == _usbh_data.enumerating_daddr) { _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; + // clear enum delay function of the device being removed + if (_usbh_data.call_after.func == enum_delay_async) { + _usbh_data.call_after.func = NULL; + } } } @@ -493,11 +520,17 @@ bool tuh_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { _usbh_q = osal_queue_create(&_usbh_qdef); TU_ASSERT(_usbh_q != NULL); -#if OSAL_MUTEX_REQUIRED + #if CFG_TUH_HUB + // Deferred attachment queue + _usbh_daq = osal_queue_create(&_usbh_daqdef); + TU_ASSERT(_usbh_daq != NULL); + #endif + + #if OSAL_MUTEX_REQUIRED // Init mutex _usbh_mutex = osal_mutex_create(&_usbh_mutexdef); TU_ASSERT(_usbh_mutex); -#endif + #endif // Get application driver if available _app_driver = usbh_app_driver_get_cb(&_app_driver_count); @@ -538,7 +571,7 @@ bool tuh_deinit(uint8_t rhport) { // deinit host controller hcd_int_disable(rhport); - hcd_deinit(rhport); + TU_ASSERT(hcd_deinit(rhport)); _usbh_data.controller_id = TUSB_INDEX_INVALID_8; // remove all devices on this rhport (hub_addr = 0, hub_port = 0) @@ -558,11 +591,16 @@ bool tuh_deinit(uint8_t rhport) { osal_queue_delete(_usbh_q); _usbh_q = NULL; - #if OSAL_MUTEX_REQUIRED + #if CFG_TUH_HUB + osal_queue_delete(_usbh_daq); + _usbh_daq = NULL; + #endif + + #if OSAL_MUTEX_REQUIRED // TODO make sure there is no task waiting on this mutex osal_mutex_delete(_usbh_mutex); _usbh_mutex = NULL; - #endif + #endif } return true; @@ -570,9 +608,19 @@ bool tuh_deinit(uint8_t rhport) { bool tuh_task_event_ready(void) { if (!tuh_inited()) { - return false; // Skip if stack is not initialized + return false; // Skip if tusb stack is not initialized } - return !osal_queue_empty(_usbh_q); + if (!osal_queue_empty(_usbh_q)) { + return true; + } + + #if CFG_TUH_HUB + if (!osal_queue_empty(_usbh_daq)) { + return true; + } + #endif + + return false; } /* USB Host Driver task @@ -592,51 +640,87 @@ bool tuh_task_event_ready(void) { @endcode */ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { - (void) in_isr; // not implemented yet - // Skip if stack is not initialized if (!tuh_inited()) { return; } + (void) in_isr; // not implemented yet + // Loop until there are no more events in the queue or CFG_TUH_TASK_EVENTS_PER_RUN is reached for (unsigned epr = 0;; epr++) { -#if CFG_TUH_TASK_EVENTS_PER_RUN > 0 + #if CFG_TUH_TASK_EVENTS_PER_RUN > 0 if (epr >= CFG_TUH_TASK_EVENTS_PER_RUN) { TU_LOG_USBH("USBH event limit (" TU_XSTRING(CFG_TUH_TASK_EVENTS_PER_RUN) ") reached\r\n"); break; } -#endif + #endif + + // Process call_after_ms function if ms is reached + tusb_defer_func_t after_cb = _usbh_data.call_after.func; + if (after_cb) { + int32_t remain_ms = (int32_t)(_usbh_data.call_after.at_ms - tusb_time_millis_api()); + if (remain_ms <= 0) { + // delay expired, run callback now + TU_LOG_USBH("USBH invoke scheduled function\r\n"); + _usbh_data.call_after.func = NULL; + after_cb(_usbh_data.call_after.arg); + } + + // above after_cb() can re-schedule another function, we need to re-check and reduce timeout of + // the main event timeout to make sure we aren't blocking more than call_after remaining ms. + if (_usbh_data.call_after.func != NULL) { + remain_ms = (int32_t) (_usbh_data.call_after.at_ms - tusb_time_millis_api()); + if (remain_ms <= 0) { + timeout_ms = 0; // expired already + } else if (timeout_ms > (uint32_t)remain_ms) { + timeout_ms = (uint32_t)remain_ms; + } + } + } + hcd_event_t event; - if (!osal_queue_receive(_usbh_q, &event, timeout_ms)) { return; } + + #if CFG_TUH_HUB + // Get deferred device attachments if none is enumerating + bool has_deferred_attach = false; + if (_usbh_data.enumerating_daddr == TUSB_INDEX_INVALID_8) { + // zero wait to avoid blocking the main event queue + has_deferred_attach = osal_queue_receive(_usbh_daq, &event, 0); + } + + if (!has_deferred_attach) // skip event queue to process deferred attach + #endif + { + if (!osal_queue_receive(_usbh_q, &event, timeout_ms)) { + return; + } + } switch (event.event_id) { case HCD_EVENT_DEVICE_ATTACH: // Should we miss the hub detach event due to high traffic, Or due to physical debouncing, some devices can - // cause multiple attaches (actually reset) without detach event. + // cause multiple attaches (actually reset) without a detached event. // Force remove currently mounted with the same bus info (rhport, hub addr, hub port) if exists process_remove_event(&event); // due to the shared control buffer, we must fully complete enumerating one device first. - // TODO better to have an separated queue for newly attached devices if (_usbh_data.enumerating_daddr == TUSB_INDEX_INVALID_8) { // New device attached and we are ready TU_LOG_USBH("[%u:] USBH Device Attach\r\n", event.rhport); _usbh_data.enumerating_daddr = 0; // enumerate new device with address 0 enum_new_device(&event); - } else { - // currently enumerating another device + } + #if CFG_TUH_HUB + else { TU_LOG_USBH("[%u:] USBH Defer Attach until current enumeration complete\r\n", event.rhport); - const bool is_empty = osal_queue_empty(_usbh_q); - queue_event(&event, in_isr); - if (is_empty) { - return; // Exit if this is the only event in the queue, otherwise we loop forever - } + TU_ASSERT(osal_queue_send(_usbh_daq, &event, in_isr), ); } + #endif break; case HCD_EVENT_DEVICE_REMOVE: - TU_LOG_USBH("[%u:%u:%u] USBH DEVICE REMOVED\r\n", event.rhport, event.connection.hub_addr, event.connection.hub_port); + TU_LOG_USBH("[%u:%u:%u] USBH Device Removed\r\n", event.rhport, event.connection.hub_addr, event.connection.hub_port); process_remove_event(&event); break; @@ -709,10 +793,8 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { break; } -#if CFG_TUSB_OS != OPT_OS_NONE && CFG_TUSB_OS != OPT_OS_PICO - // return if there is no more events, for application to run other background - if (osal_queue_empty(_usbh_q)) return; -#endif + // allow to exit tuh_task() if there is no event in the next run + timeout_ms = 0; } } @@ -770,10 +852,8 @@ bool tuh_control_xfer (tuh_xfer_t* xfer) { while (result == XFER_RESULT_INVALID) { // Note: this can be called within an callback ie. part of tuh_task() - // therefore event with RTOS tuh_task() still need to be invoked - if (tuh_task_event_ready()) { - tuh_task(); - } + // therefore even with RTOS tuh_task_ext() still need to be invoked + tuh_task_ext(0, false); // TODO probably some timeout to prevent hanged } @@ -1423,10 +1503,10 @@ enum { // USB 2.0 specs 7.1.7 for timing enum { ENUM_IDLE, ENUM_HUB_RERSET, - ENUM_HUB_GET_STATUS_AFTER_RESET, + ENUM_HUB_RESET_COMPLETE, ENUM_HUB_CLEAR_RESET, + ENUM_HUB_CLEAR_RESET_RETRY, // 2nd attempt waiting for hub reset ENUM_HUB_CLEAR_RESET_COMPLETE, - ENUM_ADDR0_DEVICE_DESC, ENUM_SET_ADDR, ENUM_GET_DEVICE_DESC, @@ -1445,104 +1525,139 @@ enum { }; static uint8_t enum_get_new_address(bool is_hub); -static bool enum_parse_configuration_desc (uint8_t dev_addr, tusb_desc_configuration_t const* desc_cfg); -static void enum_full_complete(bool success); -static void process_enumeration(tuh_xfer_t* xfer); +static bool enum_parse_configuration_desc(uint8_t dev_addr, const tusb_desc_configuration_t *desc_cfg); +static void enum_full_complete(bool success); +static void process_enumeration(tuh_xfer_t *xfer); -// start a new enumeration process -static bool enum_new_device(hcd_event_t* event) { - tuh_bus_info_t* dev0_bus = &_usbh_data.dev0_bus; - dev0_bus->rhport = event->rhport; - dev0_bus->hub_addr = event->connection.hub_addr; - dev0_bus->hub_port = event->connection.hub_port; +enum { + ENUM_AFTER_DEBOUNCING_DELAY, + ENUM_AFTER_RESET_ROOT_DELAY, + ENUM_AFTER_RESET_ROOT_POST_DELAY, + ENUM_AFTER_RESET_HUB_DELAY, + ENUM_AFTER_RESET_HUB_DELAY_RETRY, + ENUM_AFTER_RESET_RECOVERY_DELAY, + ENUM_AFTER_SET_ADDRESS_RECOVERY_DELAY, +}; - // wait until device connection is stable TODO non blocking - tusb_time_delay_ms_api(ENUM_DEBOUNCING_DELAY_MS); +// process async delay in enumeration +static void enum_delay_async(uintptr_t state) { + tuh_bus_info_t *dev0_bus = &_usbh_data.dev0_bus; + switch (state) { + case ENUM_AFTER_DEBOUNCING_DELAY: + #if CFG_TUH_HUB + if (dev0_bus->hub_addr != 0) { + // connected via hub + TU_VERIFY(dev0_bus->hub_port != 0, ); + TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration, + ENUM_HUB_RERSET), ); + } else + #endif + { + // connected directly to roothub + _usbh_data.attach_debouncing_bm &= (uint8_t)~TU_BIT(dev0_bus->rhport); // clear roothub debouncing delay + if (!hcd_port_connect_status(dev0_bus->rhport)) { + TU_LOG_USBH("Device unplugged while debouncing\r\n"); + enum_full_complete(false); + return; + } + hcd_port_reset(dev0_bus->rhport); // reset port + usbh_defer_func_ms_async(ENUM_RESET_ROOT_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_ROOT_DELAY); + } + break; - if (dev0_bus->hub_addr == 0) { - // connected directly to roothub - // USB bus not active and frame number is not available yet. - // need to depend on tusb_time_millis_api() TODO non blocking + case ENUM_AFTER_RESET_ROOT_DELAY: + hcd_port_reset_end(dev0_bus->rhport); + usbh_defer_func_ms_async(ENUM_RESET_ROOT_POST_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_ROOT_POST_DELAY); + break; - _usbh_data.attach_debouncing_bm &= (uint8_t) ~TU_BIT(dev0_bus->rhport); // clear roothub debouncing delay + case ENUM_AFTER_RESET_ROOT_POST_DELAY: + if (!hcd_port_connect_status(dev0_bus->rhport)) { + // device unplugged while delaying + enum_full_complete(false); + return; + } - if (!hcd_port_connect_status(dev0_bus->rhport)) { - TU_LOG_USBH("Device unplugged while debouncing\r\n"); - enum_full_complete(false); - return true; - } + dev0_bus->speed = hcd_port_speed_get(dev0_bus->rhport); + TU_LOG_USBH("%s Speed\r\n", tu_str_speed[dev0_bus->speed]); - // reset device - hcd_port_reset(dev0_bus->rhport); - tusb_time_delay_ms_api(ENUM_RESET_ROOT_DELAY_MS); - hcd_port_reset_end(dev0_bus->rhport); - tusb_time_delay_ms_api(ENUM_RESET_ROOT_POST_DELAY_MS); + // fake transfer to kick-off the enumeration process + tuh_xfer_t xfer; + xfer.daddr = 0; + xfer.result = XFER_RESULT_SUCCESS; + xfer.user_data = ENUM_ADDR0_DEVICE_DESC; + process_enumeration(&xfer); + break; - if (!hcd_port_connect_status(dev0_bus->rhport)) { - // device unplugged while delaying - enum_full_complete(false); - return true; - } + #if CFG_TUH_HUB + case ENUM_AFTER_RESET_HUB_DELAY: + case ENUM_AFTER_RESET_HUB_DELAY_RETRY: + // get status after reset complete to check for reset change + TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration, + state == ENUM_AFTER_RESET_HUB_DELAY ? ENUM_HUB_CLEAR_RESET + : ENUM_HUB_CLEAR_RESET_RETRY), ); + break; + #endif - dev0_bus->speed = hcd_port_speed_get(dev0_bus->rhport); - TU_LOG_USBH("%s Speed\r\n", tu_str_speed[dev0_bus->speed]); + case ENUM_AFTER_RESET_RECOVERY_DELAY: + // TODO probably doesn't need to open/close each enumeration + if (!usbh_edpt_control_open(0, 8)) { + TU_LOG_USBH("Failed to open dev0's control endpoint\r\n"); + enum_full_complete(false); // Stop enumeration gracefully + return; + } + // Get first 8 bytes of device descriptor for control endpoint size + TU_LOG_USBH("Get 8 byte of Device Descriptor\r\n"); + TU_ASSERT(tuh_descriptor_get_device(0, _usbh_epbuf.ctrl, 8, process_enumeration, ENUM_SET_ADDR), ); + break; - // fake transfer to kick-off the enumeration process - tuh_xfer_t xfer; - xfer.daddr = 0; - xfer.result = XFER_RESULT_SUCCESS; - xfer.user_data = ENUM_ADDR0_DEVICE_DESC; - process_enumeration(&xfer); - } - #if CFG_TUH_HUB - else { - // connected via hub - TU_VERIFY(dev0_bus->hub_port != 0); - TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, - process_enumeration, ENUM_HUB_RERSET)); + case ENUM_AFTER_SET_ADDRESS_RECOVERY_DELAY: { + const uint8_t new_addr = _usbh_data.enumerating_daddr; + usbh_device_t *new_dev = get_device(new_addr); + TU_ASSERT(new_dev, ); + if (!usbh_edpt_control_open(new_addr, new_dev->bMaxPacketSize0)) { + TU_LOG_USBH("Failed to open new device's control endpoint\r\n"); + clear_device(new_dev); + enum_full_complete(false); + return; + } + TU_LOG_USBH("Get Device Descriptor\r\n"); + TU_ASSERT(tuh_descriptor_get_device(new_addr, _usbh_epbuf.ctrl, sizeof(tusb_desc_device_t), process_enumeration, + ENUM_GET_STRING_LANGUAGE_ID_LEN), ); + break; + } + + default: + break; } - #endif // hub +} - return true; +// start a new enumeration process +static void enum_new_device(hcd_event_t *event) { + tuh_bus_info_t *dev0_bus = &_usbh_data.dev0_bus; + dev0_bus->rhport = event->rhport; + dev0_bus->hub_addr = event->connection.hub_addr; + dev0_bus->hub_port = event->connection.hub_port; + usbh_defer_func_ms_async(ENUM_DEBOUNCING_DELAY_MS, enum_delay_async, ENUM_AFTER_DEBOUNCING_DELAY); } // process device enumeration -static void process_enumeration(tuh_xfer_t* xfer) { - // Retry a few times while enumerating since device can be unstable when starting up - static uint8_t failed_count = 0; +static void process_enumeration(tuh_xfer_t *xfer) { if (XFER_RESULT_FAILED == xfer->result) { - enum { - ATTEMPT_COUNT_MAX = 3, - ATTEMPT_DELAY_MS = 100 - }; - - // retry if not reaching max attempt - failed_count++; - bool retry = (_usbh_data.enumerating_daddr != TUSB_INDEX_INVALID_8) && (failed_count < ATTEMPT_COUNT_MAX); - if (retry) { - tusb_time_delay_ms_api(ATTEMPT_DELAY_MS); // delay a bit - TU_LOG_USBH("Enumeration attempt %u/%u\r\n", failed_count+1, ATTEMPT_COUNT_MAX); - retry = tuh_control_xfer(xfer); - } - - if (!retry) { - enum_full_complete(false); // complete as failed - } + enum_full_complete(false); // failed to enum return; } - failed_count = 0; - uint8_t const daddr = xfer->daddr; - uintptr_t const state = xfer->user_data; - usbh_device_t* dev = get_device(daddr); - tuh_bus_info_t* dev0_bus = &_usbh_data.dev0_bus; + const uint8_t daddr = xfer->daddr; + const uintptr_t state = xfer->user_data; + usbh_device_t *dev = get_device(daddr); + tuh_bus_info_t *dev0_bus = &_usbh_data.dev0_bus; if (daddr > 0) { TU_ASSERT(dev != NULL,); } uint16_t langid = 0x0409; // default is English switch (state) { - #if CFG_TUH_HUB + #if CFG_TUH_HUB case ENUM_HUB_RERSET: { hub_port_status_response_t port_status; hub_port_get_status_local(dev0_bus->hub_addr, dev0_bus->hub_port, &port_status); @@ -1553,30 +1668,31 @@ static void process_enumeration(tuh_xfer_t* xfer) { return; } - TU_ASSERT(hub_port_reset(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration, ENUM_HUB_GET_STATUS_AFTER_RESET),); + TU_ASSERT(hub_port_reset(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration, ENUM_HUB_RESET_COMPLETE), ); break; } - case ENUM_HUB_GET_STATUS_AFTER_RESET: { - tusb_time_delay_ms_api(ENUM_RESET_HUB_DELAY_MS); // wait for reset to take effect - - // get status to check for reset change - TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration, ENUM_HUB_CLEAR_RESET),); + case ENUM_HUB_RESET_COMPLETE: + // wait for reset to take effect + usbh_defer_func_ms_async(ENUM_RESET_HUB_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_HUB_DELAY); break; - } - case ENUM_HUB_CLEAR_RESET: { + case ENUM_HUB_CLEAR_RESET: + case ENUM_HUB_CLEAR_RESET_RETRY: { hub_port_status_response_t port_status; hub_port_get_status_local(dev0_bus->hub_addr, dev0_bus->hub_port, &port_status); if (1 == port_status.change.reset) { // Acknowledge Port Reset Change - TU_ASSERT(hub_port_clear_reset_change(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration, ENUM_HUB_CLEAR_RESET_COMPLETE),); + TU_ASSERT(hub_port_clear_reset_change(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration, + ENUM_HUB_CLEAR_RESET_COMPLETE), ); + } else if (state == ENUM_HUB_CLEAR_RESET) { + // retry one more time if reset change not set yet + usbh_defer_func_ms_async(ENUM_RESET_HUB_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_HUB_DELAY_RETRY); } else { - // maybe retry if reset change not set but we need timeout to prevent infinite loop - // TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration, ENUM_HUB_CLEAR_RESET_COMPLETE),); + // retry but still not set --> failed + enum_full_complete(false); } - break; } @@ -1590,30 +1706,17 @@ static void process_enumeration(tuh_xfer_t* xfer) { return; } - dev0_bus->speed = (port_status.status.high_speed) ? TUSB_SPEED_HIGH : - (port_status.status.low_speed) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL; + dev0_bus->speed = (port_status.status.high_speed) ? TUSB_SPEED_HIGH + : (port_status.status.low_speed) ? TUSB_SPEED_LOW + : TUSB_SPEED_FULL; TU_ATTR_FALLTHROUGH; } - #endif - - case ENUM_ADDR0_DEVICE_DESC: { - tusb_time_delay_ms_api(ENUM_RESET_RECOVERY_DELAY_MS); // reset recovery - - // TODO probably doesn't need to open/close each enumeration - uint8_t const addr0 = 0; - if (!usbh_edpt_control_open(addr0, 8)) { - // Stop enumeration gracefully - enum_full_complete(false); - TU_ASSERT(false,); - } + #endif - // Get first 8 bytes of device descriptor for control endpoint size - TU_LOG_USBH("Get 8 byte of Device Descriptor\r\n"); - TU_ASSERT(tuh_descriptor_get_device(addr0, _usbh_epbuf.ctrl, 8, - process_enumeration, ENUM_SET_ADDR),); + case ENUM_ADDR0_DEVICE_DESC: + usbh_defer_func_ms_async(ENUM_RESET_RECOVERY_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_RECOVERY_DELAY); break; - } case ENUM_SET_ADDR: { const tusb_desc_device_t *desc_device = (const tusb_desc_device_t *) _usbh_epbuf.ctrl; @@ -1625,31 +1728,19 @@ static void process_enumeration(tuh_xfer_t* xfer) { new_dev->connected = 1; new_dev->bMaxPacketSize0 = desc_device->bMaxPacketSize0; - TU_ASSERT(tuh_address_set(0, new_addr, process_enumeration, ENUM_GET_DEVICE_DESC),); + TU_ASSERT(tuh_address_set(0, new_addr, process_enumeration, ENUM_GET_DEVICE_DESC), ); break; } case ENUM_GET_DEVICE_DESC: { - tusb_time_delay_ms_api(ENUM_SET_ADDRESS_RECOVERY_DELAY_MS); // set address recovery - - const uint8_t new_addr = (uint8_t) tu_le16toh(xfer->setup->wValue); - usbh_device_t* new_dev = get_device(new_addr); - TU_ASSERT(new_dev,); - new_dev->addressed = 1; + const uint8_t new_addr = (uint8_t)tu_le16toh(xfer->setup->wValue); + usbh_device_t *new_dev = get_device(new_addr); + TU_ASSERT(new_dev, ); + new_dev->addressed = 1; _usbh_data.enumerating_daddr = new_addr; usbh_device_close(dev0_bus->rhport, 0); // close dev0 - - if (!usbh_edpt_control_open(new_addr, new_dev->bMaxPacketSize0)) { // open new control endpoint - // Stop enumeration gracefully - clear_device(new_dev); - enum_full_complete(false); - TU_ASSERT(false,); - } - - TU_LOG_USBH("Get Device Descriptor\r\n"); - TU_ASSERT(tuh_descriptor_get_device(new_addr, _usbh_epbuf.ctrl, sizeof(tusb_desc_device_t), - process_enumeration, ENUM_GET_STRING_LANGUAGE_ID_LEN),); + usbh_defer_func_ms_async(ENUM_SET_ADDRESS_RECOVERY_DELAY_MS, enum_delay_async, ENUM_AFTER_SET_ADDRESS_RECOVERY_DELAY); break; } @@ -1799,11 +1890,12 @@ static void process_enumeration(tuh_xfer_t* xfer) { TU_LOG_USBH("Device configured\r\n"); dev->configured = 1; - #if CFG_TUH_HUB + #if CFG_TUH_HUB + // get next hub status now since device can be unplugged before set_configure() is complete if (_usbh_data.dev0_bus.hub_addr != 0) { - hub_edpt_status_xfer(_usbh_data.dev0_bus.hub_addr); // get next hub status + hub_edpt_status_xfer(_usbh_data.dev0_bus.hub_addr); } - #endif + #endif // Parse configuration & set up drivers // driver_open() must not make any usb transfer @@ -1858,10 +1950,10 @@ static bool enum_parse_configuration_desc(uint8_t dev_addr, tusb_desc_configurat TU_LOG_USBH("Parsing Configuration descriptor (wTotalLength = %u)\r\n", total_len); - // parse each interfaces + // parse all interfaces while (tu_desc_in_bounds(p_desc, desc_end)) { if (0 == tu_desc_len(p_desc)) { - // A zero length descriptor indicates that the device is off spec (e.g. wrong wTotalLength). + // A zero-length descriptor indicates that the device is off spec (e.g. wrong wTotalLength). // Parsed interfaces should still be usable TU_LOG_USBH("Encountered a zero-length descriptor after %" PRIu32 " bytes\r\n", (uint32_t)p_desc - (uint32_t)desc_cfg); break; @@ -1877,7 +1969,7 @@ static bool enum_parse_configuration_desc(uint8_t dev_addr, tusb_desc_configurat // uint16_t const drv_len = tu_desc_get_interface_total_len(desc_itf, assoc_itf_count, (uint16_t) // (desc_end-p_desc)); TU_ASSERT(drv_len >= sizeof(tusb_desc_interface_t)); - // Find driver for this interface + // Find a driver for this interface const uint16_t remaining_len = (uint16_t)(desc_end - p_desc); uint8_t drv_id; for (drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) { @@ -1925,7 +2017,7 @@ void usbh_driver_set_config_complete(uint8_t dev_addr, uint8_t itf_num) { } } - // all interface are configured + // all interfaces are configured if (itf_num == CFG_TUH_INTERFACE_MAX) { enum_full_complete(true); @@ -1940,16 +2032,17 @@ void usbh_driver_set_config_complete(uint8_t dev_addr, uint8_t itf_num) { static void enum_full_complete(bool success) { (void)success; - // mark enumeration as complete - _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; + TU_LOG_USBH("Enumeration complete: success = %u\r\n", success); -#if CFG_TUH_HUB + _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; // mark enumeration as complete + _usbh_data.call_after.func = NULL; + + #if CFG_TUH_HUB // Hub status is already requested in case of successful enumeration - if (_usbh_data.dev0_bus.hub_addr != 0 && !success) { - hub_edpt_status_xfer(_usbh_data.dev0_bus.hub_addr); // get next hub status + if (!success && _usbh_data.dev0_bus.hub_addr != 0) { + hub_edpt_status_xfer(_usbh_data.dev0_bus.hub_addr); } -#endif - + #endif } #endif diff --git a/src/host/usbh_pvt.h b/src/host/usbh_pvt.h index 57428e3c5..adb6a8c44 100644 --- a/src/host/usbh_pvt.h +++ b/src/host/usbh_pvt.h @@ -68,8 +68,12 @@ uint8_t* usbh_get_enum_buf(void); void usbh_int_set(bool enabled); +// Invoke this function later in tuh_task() by putting it into task queue void usbh_defer_func(osal_task_func_t func, void *param, bool in_isr); +// Schedules a function to be called after certain time asynchronously +bool usbh_defer_func_ms_async(uint32_t ms, tusb_defer_func_t func, uintptr_t param); + void usbh_spin_lock(bool in_isr); void usbh_spin_unlock(bool in_isr); diff --git a/src/osal/osal.h b/src/osal/osal.h index 44521620f..4840463f3 100644 --- a/src/osal/osal.h +++ b/src/osal/osal.h @@ -65,6 +65,8 @@ typedef void (*osal_task_func_t)(void* param); #include "osal_rtx4.h" #elif CFG_TUSB_OS == OPT_OS_ZEPHYR #include "osal_zephyr.h" +#elif CFG_TUSB_OS == OPT_OS_THREADX + #include "osal_threadx.h" #elif CFG_TUSB_OS == OPT_OS_CUSTOM #include "tusb_os_custom.h" // implemented by application #else @@ -74,6 +76,8 @@ typedef void (*osal_task_func_t)(void* param); /*-------------------------------------------------------------------- OSAL Porting API Should be implemented as static inline function in osal_port.h header + uint32_t osal_time_millis(void); + void osal_spin_init(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); diff --git a/src/osal/osal_freertos.h b/src/osal/osal_freertos.h index 9aeda4d01..db724179d 100644 --- a/src/osal/osal_freertos.h +++ b/src/osal/osal_freertos.h @@ -99,6 +99,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { vTaskDelay(pdMS_TO_TICKS(msec)); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return pdTICKS_TO_MS(xTaskGetTickCount()); +} + //--------------------------------------------------------------------+ // Spinlock API //--------------------------------------------------------------------+ @@ -137,11 +141,12 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *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 == 0) { - (void) ctx; - return; // single core MCU does not need to lock in ISR - } + #if TUP_MCU_MULTIPLE_CORE *ctx = taskENTER_CRITICAL_FROM_ISR(); + #else + (void) ctx; + return; // single core MCU does not need to lock in ISR + #endif } else { taskENTER_CRITICAL(); } @@ -149,11 +154,12 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bo TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { if (in_isr) { - if (TUP_MCU_MULTIPLE_CORE == 0) { - (void) ctx; - return; // single core MCU does not need to lock in ISR - } + #if TUP_MCU_MULTIPLE_CORE taskEXIT_CRITICAL_FROM_ISR(*ctx); + #else + (void) ctx; + return; // single core MCU does not need to lock in ISR + #endif } else { taskEXIT_CRITICAL(); } diff --git a/src/osal/osal_mynewt.h b/src/osal/osal_mynewt.h index 6d51f8ec3..94124ca81 100644 --- a/src/osal/osal_mynewt.h +++ b/src/osal/osal_mynewt.h @@ -40,6 +40,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { os_time_delay( os_time_ms_to_ticks32(msec) ); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return os_time_ticks_to_ms32(os_time_get()); +} + //--------------------------------------------------------------------+ // Spinlock API //--------------------------------------------------------------------+ diff --git a/src/osal/osal_none.h b/src/osal/osal_none.h index 6ab18ace8..7bf6029d6 100644 --- a/src/osal/osal_none.h +++ b/src/osal/osal_none.h @@ -31,6 +31,8 @@ extern "C" { #endif +// osal_time_millis() is not provided, tusb_time_millis_api() must be implemented by user application + //--------------------------------------------------------------------+ // Spinlock API //--------------------------------------------------------------------+ @@ -184,7 +186,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, v (void) msec; // not used, always behave as msec = 0 qhdl->interrupt_set(false); - const bool success = tu_fifo_read_n(&qhdl->ff, data, qhdl->item_size); + const bool success = (tu_fifo_read_n(&qhdl->ff, data, qhdl->item_size) > 0); qhdl->interrupt_set(true); return success; @@ -195,7 +197,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void qhdl->interrupt_set(false); } - const bool success = tu_fifo_write_n(&qhdl->ff, data, qhdl->item_size); + const bool success = (tu_fifo_write_n(&qhdl->ff, data, qhdl->item_size) > 0); if (!in_isr) { qhdl->interrupt_set(true); diff --git a/src/osal/osal_pico.h b/src/osal/osal_pico.h index 79b728e9a..6a0a21bb3 100644 --- a/src/osal/osal_pico.h +++ b/src/osal/osal_pico.h @@ -43,6 +43,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { sleep_ms(msec); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return to_ms_since_boot(get_absolute_time()); +} + //--------------------------------------------------------------------+ // Spinlock API //--------------------------------------------------------------------+ diff --git a/src/osal/osal_rtthread.h b/src/osal/osal_rtthread.h index a778f5425..f560281c5 100644 --- a/src/osal/osal_rtthread.h +++ b/src/osal/osal_rtthread.h @@ -42,6 +42,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { rt_thread_mdelay(msec); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return (uint32_t)((((uint64_t)rt_tick_get()) * 1000) / RT_TICK_PER_SECOND); +} + //--------------------------------------------------------------------+ // Spinlock API //--------------------------------------------------------------------+ diff --git a/src/osal/osal_rtx4.h b/src/osal/osal_rtx4.h index 35860ddd5..e1930c96c 100644 --- a/src/osal/osal_rtx4.h +++ b/src/osal/osal_rtx4.h @@ -46,6 +46,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { os_dly_wait(lo); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return os_time_get(); +} + TU_ATTR_ALWAYS_INLINE static inline uint16_t msec2wait(uint32_t msec) { if (msec == OSAL_TIMEOUT_WAIT_FOREVER) { return 0xFFFF; diff --git a/src/osal/osal_threadx.h b/src/osal/osal_threadx.h new file mode 100644 index 000000000..6bcf9c5ab --- /dev/null +++ b/src/osal/osal_threadx.h @@ -0,0 +1,201 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_OSAL_THREADX_H_ +#define TUSB_OSAL_THREADX_H_ + +// ThreadX Headers +#include "tx_api.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// TASK API +//--------------------------------------------------------------------+ + +TU_ATTR_ALWAYS_INLINE static inline uint32_t _osal_ms2tick(uint32_t msec) { + if ( msec == TX_WAIT_FOREVER ) { + return TX_WAIT_FOREVER; + } + if ( msec == 0 ) { + return 0; + } + + uint32_t ticks = msec * TX_TIMER_TICKS_PER_SECOND / 1000; + + // TX_TIMER_TICKS_PER_SECOND is less than 1000 and 1 tick > 1 ms + // we still need to delay at least 1 tick + if ( ticks == 0 ) { + ticks = 1; + } + + return ticks; +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return (uint32_t)((uint64_t) tx_time_get() * 1000u / TX_TIMER_TICKS_PER_SECOND); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { + tx_thread_sleep(_osal_ms2tick(msec)); +} + +//--------------------------------------------------------------------+ +// Spinlock API +//--------------------------------------------------------------------+ +//--------------------------------------------------------------------+ +// Spinlock API +//--------------------------------------------------------------------+ +typedef struct { + void (* interrupt_set)(bool); +} osal_spinlock_t; + +// For SMP, spinlock must be locked by hardware, cannot just use interrupt +#define OSAL_SPINLOCK_DEF(_name, _int_set) \ + osal_spinlock_t _name = { .interrupt_set = _int_set } + +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_lock(osal_spinlock_t *ctx, bool in_isr) { + if (!in_isr) { + ctx->interrupt_set(false); + } +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { + if (!in_isr) { + ctx->interrupt_set(true); + } +} + + +//--------------------------------------------------------------------+ +// Binary Semaphore API (act) +//--------------------------------------------------------------------+ +// Note: semaphores are not used in tinyusb for now, and their API has not been tested + +typedef TX_SEMAPHORE osal_semaphore_def_t, * osal_semaphore_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t *semdef) { + tx_semaphore_create(semdef, TX_NULL, 0); + return semdef; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_delete(osal_semaphore_t sem_hdl) { + (void) sem_hdl; + return TX_SUCCESS == tx_semaphore_delete(sem_hdl); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) { + (void) in_isr; + return TX_SUCCESS == tx_semaphore_put(sem_hdl); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec) { + return TX_SUCCESS == tx_semaphore_get(sem_hdl, _osal_ms2tick(msec)); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_semaphore_reset(osal_semaphore_t sem_hdl) { + (void) sem_hdl; +} + +//--------------------------------------------------------------------+ +// MUTEX API +//--------------------------------------------------------------------+ +typedef TX_MUTEX osal_mutex_def_t, *osal_mutex_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t *mdef) { + if (TX_SUCCESS == tx_mutex_create(mdef, mdef->tx_mutex_name, TX_NO_INHERIT)) { + return mdef; + } else { + return NULL; + } +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_delete(osal_mutex_t mutex_hdl) { + (void) mutex_hdl; + return true; // nothing to do +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec) { + return TX_SUCCESS == tx_mutex_get(mutex_hdl, _osal_ms2tick(msec)); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) { + return TX_SUCCESS == tx_mutex_put(mutex_hdl); +} + +//--------------------------------------------------------------------+ +// QUEUE API +//--------------------------------------------------------------------+ + +typedef TX_QUEUE osal_queue_def_t, * osal_queue_t; + +// _int_set is not used with an RTOS _usbd_qdef + +#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \ +static _type _name##_buf[_depth]; \ +osal_queue_def_t _name = { \ + .tx_queue_name = (CHAR*)(uintptr_t)#_name, \ + .tx_queue_message_size = (sizeof(_type) + 3) / 4, \ + .tx_queue_capacity = _depth, \ + .tx_queue_start = (ULONG *) _name##_buf } + + +TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) { + return TX_SUCCESS == + tx_queue_create(qdef, qdef->tx_queue_name, qdef->tx_queue_message_size, qdef->tx_queue_start, qdef->tx_queue_capacity * qdef->tx_queue_message_size * 4) + ? qdef : 0; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_delete(osal_queue_t qhdl) { + (void) qhdl; + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec) { + return 0 == tx_queue_receive(qhdl, data, _osal_ms2tick(msec)); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void const *data, bool in_isr) { + return 0 == tx_queue_send(qhdl, (VOID *)(uintptr_t) data, in_isr ? TX_NO_WAIT : TX_WAIT_FOREVER); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) { + ULONG enqueued; + tx_queue_info_get(qhdl, 0, &enqueued, 0, 0, 0, 0); + return enqueued == 0; +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/osal/osal_zephyr.h b/src/osal/osal_zephyr.h index 91f225f79..900ac786c 100644 --- a/src/osal/osal_zephyr.h +++ b/src/osal/osal_zephyr.h @@ -35,6 +35,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { k_msleep(msec); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return k_uptime_get_32(); +} + //--------------------------------------------------------------------+ // Spinlock API //--------------------------------------------------------------------+ diff --git a/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h b/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h index 178eec419..c22aea887 100644 --- a/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h +++ b/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h @@ -47,10 +47,10 @@ static const ci_hs_controller_t _ci_controller[] = #define CI_HS_REG(_port) ((ci_hs_regs_t*) _ci_controller[_port].reg_base) -#define CI_DCD_INT_ENABLE(_p) NVIC_EnableIRQ (_ci_controller[_p].irqnum) -#define CI_DCD_INT_DISABLE(_p) NVIC_DisableIRQ(_ci_controller[_p].irqnum) +#define CI_DCD_INT_ENABLE(_p) NVIC_EnableIRQ ((IRQn_Type)_ci_controller[_p].irqnum) +#define CI_DCD_INT_DISABLE(_p) NVIC_DisableIRQ((IRQn_Type)_ci_controller[_p].irqnum) -#define CI_HCD_INT_ENABLE(_p) NVIC_EnableIRQ (_ci_controller[_p].irqnum) -#define CI_HCD_INT_DISABLE(_p) NVIC_DisableIRQ(_ci_controller[_p].irqnum) +#define CI_HCD_INT_ENABLE(_p) NVIC_EnableIRQ ((IRQn_Type)_ci_controller[_p].irqnum) +#define CI_HCD_INT_DISABLE(_p) NVIC_DisableIRQ((IRQn_Type)_ci_controller[_p].irqnum) #endif diff --git a/src/portable/chipidea/ci_hs/dcd_ci_hs.c b/src/portable/chipidea/ci_hs/dcd_ci_hs.c index 2bba32ada..b9f6a8a7b 100644 --- a/src/portable/chipidea/ci_hs/dcd_ci_hs.c +++ b/src/portable/chipidea/ci_hs/dcd_ci_hs.c @@ -285,6 +285,23 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { return true; } +bool dcd_deinit(uint8_t rhport) { + ci_hs_regs_t* dcd_reg = CI_HS_REG(rhport); + + // disable all interrupt + dcd_reg->USBINTR = 0; + + // unattach from bus + dcd_reg->USBCMD &= ~USBCMD_RUN_STOP; + + // flush all endpoints + while (dcd_reg->ENDPTPRIME) {} + dcd_reg->ENDPTFLUSH = 0xFFFFFFFF; + while (dcd_reg->ENDPTFLUSH) {} + + return true; +} + void dcd_int_enable(uint8_t rhport) { CI_DCD_INT_ENABLE(rhport); } diff --git a/src/portable/chipidea/ci_hs/hcd_ci_hs.c b/src/portable/chipidea/ci_hs/hcd_ci_hs.c index 29ce0cd7f..c0d14fe57 100644 --- a/src/portable/chipidea/ci_hs/hcd_ci_hs.c +++ b/src/portable/chipidea/ci_hs/hcd_ci_hs.c @@ -93,21 +93,25 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { hcd_reg->USBCMD |= USBCMD_RESET; while (hcd_reg->USBCMD & USBCMD_RESET) {} - // Set mode to device, must be set immediately after reset + // Set mode to host, must be set immediately after reset #if CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX // LPC18XX/43XX need to set VBUS Power Select to HIGH - // RHPORT1 is fullspeed only (need external PHY for Highspeed) hcd_reg->USBMODE = USBMODE_CM_HOST | USBMODE_VBUS_POWER_SELECT; - if (rhport == 1) { - hcd_reg->PORTSC1 |= PORTSC1_FORCE_FULL_SPEED; - } #else hcd_reg->USBMODE = USBMODE_CM_HOST; #endif + #if !TUH_OPT_HIGH_SPEED + hcd_reg->PORTSC1 |= PORTSC1_FORCE_FULL_SPEED; + #endif + return ehci_init(rhport, (uint32_t)&hcd_reg->CAPLENGTH, (uint32_t)&hcd_reg->USBCMD); } +bool hcd_deinit(uint8_t rhport) { + return ehci_deinit(rhport); +} + void hcd_int_enable(uint8_t rhport) { CI_HCD_INT_ENABLE(rhport); } diff --git a/src/portable/ehci/ehci.c b/src/portable/ehci/ehci.c index 9b2cf98be..03c3b91fd 100644 --- a/src/portable/ehci/ehci.c +++ b/src/portable/ehci/ehci.c @@ -411,17 +411,22 @@ bool ehci_init(uint8_t rhport, uint32_t capability_reg, uint32_t operatial_reg) return true; } -#if 0 -static void ehci_stop(uint8_t rhport) { +bool ehci_deinit(uint8_t rhport) { (void) rhport; ehci_registers_t* regs = ehci_data.regs; + + // Disable all the interrupt + regs->inten = 0; + + // Disable schedules regs->command_bm.run_stop = 0; // USB Spec: controller has to stop within 16 uframe = 2 frames while( regs->status_bm.hc_halted == 0 ) {} + + return true; } -#endif //--------------------------------------------------------------------+ // Endpoint API diff --git a/src/portable/ehci/ehci_api.h b/src/portable/ehci/ehci_api.h index 79fbe702a..e9018639f 100644 --- a/src/portable/ehci/ehci_api.h +++ b/src/portable/ehci/ehci_api.h @@ -38,6 +38,9 @@ // Initialize EHCI driver bool ehci_init(uint8_t rhport, uint32_t capability_reg, uint32_t operatial_reg); +// De-initialize EHCI driver +bool ehci_deinit(uint8_t rhport); + #ifdef __cplusplus } #endif diff --git a/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c index acdeccf6d..1813ef70b 100644 --- a/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c +++ b/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c @@ -223,9 +223,13 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { tu_memclr(&_hcd_data, sizeof(_hcd_data)); + // Clear pending interrupts + // Normally no interrupts should be pending here since we just reset the core, + // but device mode suspend needs to cleared by WKUP flag + FSDEV_REG->ISTR = 0; + // Enable interrupts for host mode - FSDEV_REG->CNTR |= USB_CNTR_RESETM | USB_CNTR_CTRM | USB_CNTR_SOFM | USB_CNTR_SUSPM | - USB_CNTR_WKUPM | USB_CNTR_ERRM | USB_CNTR_PMAOVRM; + FSDEV_REG->CNTR |= USB_CNTR_DCON | USB_CNTR_CTRM | USB_CNTR_SOFM | USB_CNTR_ERRM | USB_CNTR_PMAOVRM; // Initialize port state _hcd_data.connected = false; diff --git a/src/portable/sunxi/dcd_sunxi_musb.c b/src/portable/sunxi/dcd_sunxi_musb.c index b413121a5..9fac0bc1c 100644 --- a/src/portable/sunxi/dcd_sunxi_musb.c +++ b/src/portable/sunxi/dcd_sunxi_musb.c @@ -36,9 +36,6 @@ #include <device/dcd.h> #include "musb_def.h" -//#include "bsp/board_api.h" -extern uint32_t board_millis(void); // TODO remove - typedef uint32_t u32; typedef uint16_t u16; typedef uint8_t u8; diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index 2e2b050bc..8685ec6dc 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -492,6 +492,12 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { return true; } +bool dcd_deinit(uint8_t rhport) { + dcd_disconnect(rhport); + dwc2_core_deinit(rhport); + return true; +} + void dcd_int_enable(uint8_t rhport) { dwc2_dcd_int_enable(rhport); } @@ -527,34 +533,38 @@ void dcd_remote_wakeup(uint8_t rhport) { } void dcd_connect(uint8_t rhport) { - (void) rhport; dwc2_regs_t* dwc2 = DWC2_REG(rhport); #ifdef TUP_USBIP_DWC2_ESP32 - usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf; - conf.pad_pull_override = 0; - conf.dp_pullup = 0; - conf.dp_pulldown = 0; - conf.dm_pullup = 0; - conf.dm_pulldown = 0; - USB_WRAP.otg_conf = conf; + // On ESP32-P4 HS PHY, do not write to USB_WRAP register which belongs to FS PHY + if (rhport == 0) { + usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf; + conf.pad_pull_override = 0; + conf.dp_pullup = 0; + conf.dp_pulldown = 0; + conf.dm_pullup = 0; + conf.dm_pulldown = 0; + USB_WRAP.otg_conf = conf; + } #endif dwc2->dctl &= ~DCTL_SDIS; } void dcd_disconnect(uint8_t rhport) { - (void) rhport; dwc2_regs_t* dwc2 = DWC2_REG(rhport); #ifdef TUP_USBIP_DWC2_ESP32 - usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf; - conf.pad_pull_override = 1; - conf.dp_pullup = 0; - conf.dp_pulldown = 1; - conf.dm_pullup = 0; - conf.dm_pulldown = 1; - USB_WRAP.otg_conf = conf; + // On ESP32-P4 HS PHY, do not write to USB_WRAP register which belongs to FS PHY + if (rhport == 0) { + usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf; + conf.pad_pull_override = 1; + conf.dp_pullup = 0; + conf.dp_pulldown = 1; + conf.dm_pullup = 0; + conf.dm_pulldown = 1; + USB_WRAP.otg_conf = conf; + } #endif dwc2->dctl |= DCTL_SDIS; diff --git a/src/portable/synopsys/dwc2/dwc2_at32.h b/src/portable/synopsys/dwc2/dwc2_at32.h index 10824ae92..fa6d10c12 100644 --- a/src/portable/synopsys/dwc2/dwc2_at32.h +++ b/src/portable/synopsys/dwc2/dwc2_at32.h @@ -112,6 +112,12 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t *dwc2, uint8_ } } +// MCU specific PHY deinit, disable PHY power +TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t *dwc2, uint8_t hs_phy_type) { + (void) hs_phy_type; + dwc2->stm32_gccfg &= ~(STM32_GCCFG_PWRDWN | STM32_GCCFG_DCDEN | STM32_GCCFG_PDEN); +} + // MCU specific PHY update, it is called AFTER init() and core reset TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t *dwc2, uint8_t hs_phy_type) { (void) dwc2; diff --git a/src/portable/synopsys/dwc2/dwc2_bcm.h b/src/portable/synopsys/dwc2/dwc2_bcm.h index df6d4a852..00842bba2 100644 --- a/src/portable/synopsys/dwc2/dwc2_bcm.h +++ b/src/portable/synopsys/dwc2/dwc2_bcm.h @@ -73,6 +73,13 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type) // nothing to do } +// MCU specific PHY deinit, disable PHY power +static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { + (void) dwc2; + (void) hs_phy_type; + // nothing to do +} + // MCU specific PHY update, it is called AFTER init() and core reset static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { diff --git a/src/portable/synopsys/dwc2/dwc2_common.c b/src/portable/synopsys/dwc2/dwc2_common.c index 27dda44ee..33eabaeab 100644 --- a/src/portable/synopsys/dwc2/dwc2_common.c +++ b/src/portable/synopsys/dwc2/dwc2_common.c @@ -39,14 +39,11 @@ static void reset_core(dwc2_regs_t* dwc2) { while (!(dwc2->grstctl & GRSTCTL_AHBIDL)) { } - // load gsnpsid (it is not readable after reset is asserted) - const uint32_t gsnpsid = dwc2->gsnpsid; - - // reset core - dwc2->grstctl |= GRSTCTL_CSRST; + const uint32_t gsnpsid = dwc2->gsnpsid; // preload gsnpsid which is not readable while resetting + dwc2->grstctl |= GRSTCTL_CSRST; // reset core if ((gsnpsid & DWC2_CORE_REV_MASK) < (DWC2_CORE_REV_4_20a & DWC2_CORE_REV_MASK)) { - // prior v4.20a: CSRST is self-clearing and the core clears this bit after all the necessary logic is reset in + // prior v4.20a: CSRST is self-clearing, and the core clears this bit after all the necessary logic is reset in // the core, which can take several clocks, depending on the current state of the core. Once this bit has been // cleared, the software must wait at least 3 PHY clocks before accessing the PHY domain (synchronization delay). while (dwc2->grstctl & GRSTCTL_CSRST) {} @@ -88,8 +85,7 @@ static void phy_fs_init(dwc2_regs_t* dwc2) { } /* dwc2 has 2 highspeed PHYs options - * - UTMI+ is internal highspeed PHY, can be clocked at 30/60 Mhz for fullspeed or 60 Mhz for highspeed. Can be either - * 8 or 16-bit interface. + * - UTMI+ is internal highspeed PHY, can be clocked at 30 Mhz (8-bit) or 60 Mhz (16-bit). * - ULPI is external highspeed PHY, clocked at 60Mhz with 8-bit interface. * * In addition, UTMI+/ULPI can be shared to run at fullspeed mode with 48Mhz @@ -247,6 +243,24 @@ bool dwc2_core_init(uint8_t rhport, bool is_hs_phy, bool is_dma) { return true; } +void dwc2_core_deinit(uint8_t rhport) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + + // Disable global interrupt + dwc2->gahbcfg &= ~GAHBCFG_GINT; + + // Reset core: this also flushes FIFOs and clears all interrupt registers + reset_core(dwc2); + + // Stop PHY clock and gate HCLK for power saving (per databook chapter 14) + dwc2->pcgcctl |= PCGCCTL_STOPPCLK | PCGCCTL_GATEHCLK; + + // MCU-specific PHY deinit (disable PHY power) + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + const uint8_t hs_phy_type = (dwc2->gusbcfg & GUSBCFG_PHYSEL) ? GHWCFG2_HSPHY_NOT_SUPPORTED : ghwcfg2.hs_phy_type; + dwc2_phy_deinit(dwc2, hs_phy_type); +} + // void dwc2_core_handle_common_irq(uint8_t rhport, bool in_isr) { // (void) in_isr; // dwc2_regs_t * const dwc2 = DWC2_REG(rhport); diff --git a/src/portable/synopsys/dwc2/dwc2_common.h b/src/portable/synopsys/dwc2/dwc2_common.h index 1947173b2..9f28ab2e0 100644 --- a/src/portable/synopsys/dwc2/dwc2_common.h +++ b/src/portable/synopsys/dwc2/dwc2_common.h @@ -42,6 +42,7 @@ // - _dwc2_controller[]: array of controllers // - DWC2_EP_MAX: largest EP counts of all controllers // - dwc2_phy_init/dwc2_phy_update: phy init called before and after core reset +// - dwc2_phy_deinit(dwc2, hs_phy_type): phy deinit to disable PHY power, only deinit the phy used by core // - dwc2_dcd_int_enable/dwc2_dcd_int_disable // - dwc2_remote_wakeup_delay @@ -87,6 +88,7 @@ TU_ATTR_ALWAYS_INLINE static inline dwc2_regs_t* DWC2_REG(uint8_t rhport) { // check if highspeed phy should be used bool dwc2_core_is_highspeed_phy(dwc2_regs_t* dwc2, bool prefer_hs_phy); bool dwc2_core_init(uint8_t rhport, bool is_hs_phy, bool is_dma); +void dwc2_core_deinit(uint8_t rhport); void dwc2_core_handle_common_irq(uint8_t rhport, bool in_isr); //--------------------------------------------------------------------+ diff --git a/src/portable/synopsys/dwc2/dwc2_efm32.h b/src/portable/synopsys/dwc2/dwc2_efm32.h index 0e3570cbb..e1cb7c769 100644 --- a/src/portable/synopsys/dwc2/dwc2_efm32.h +++ b/src/portable/synopsys/dwc2/dwc2_efm32.h @@ -72,6 +72,14 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type) USB->ROUTE = USB_ROUTE_PHYPEN; } +// MCU specific PHY deinit, disable PHY power +static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { + (void) dwc2; + (void) hs_phy_type; + // Disable PHY pin + USB->ROUTE = 0; +} + // MCU specific PHY update, it is called AFTER init() and core reset static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { diff --git a/src/portable/synopsys/dwc2/dwc2_esp32.h b/src/portable/synopsys/dwc2/dwc2_esp32.h index a4e0d1770..ff9f216bd 100644 --- a/src/portable/synopsys/dwc2/dwc2_esp32.h +++ b/src/portable/synopsys/dwc2/dwc2_esp32.h @@ -121,6 +121,13 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_ } +// MCU specific PHY deinit, disable PHY power +TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { + (void)dwc2; + (void)hs_phy_type; + // PHY managed by ESP-IDF +} + // MCU specific PHY update, it is called AFTER init() and core reset TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { (void)dwc2; diff --git a/src/portable/synopsys/dwc2/dwc2_gd32.h b/src/portable/synopsys/dwc2/dwc2_gd32.h index 0375fffe4..ccbf93a76 100644 --- a/src/portable/synopsys/dwc2/dwc2_gd32.h +++ b/src/portable/synopsys/dwc2/dwc2_gd32.h @@ -85,6 +85,13 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type) // nothing to do } +// MCU specific PHY deinit, disable PHY power +static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { + (void) dwc2; + (void) hs_phy_type; + // nothing to do +} + // MCU specific PHY update, it is called AFTER init() and core reset static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { diff --git a/src/portable/synopsys/dwc2/dwc2_nrf.h b/src/portable/synopsys/dwc2/dwc2_nrf.h index b93571f16..51f2d684f 100644 --- a/src/portable/synopsys/dwc2/dwc2_nrf.h +++ b/src/portable/synopsys/dwc2/dwc2_nrf.h @@ -52,6 +52,12 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_ (void)hs_phy_type; } +// MCU specific PHY deinit, disable PHY power +TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { + (void)dwc2; + (void)hs_phy_type; +} + // MCU specific PHY update, it is called AFTER init() and core reset TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { (void)dwc2; diff --git a/src/portable/synopsys/dwc2/dwc2_stm32.h b/src/portable/synopsys/dwc2/dwc2_stm32.h index 753917a20..259ad21b9 100644 --- a/src/portable/synopsys/dwc2/dwc2_stm32.h +++ b/src/portable/synopsys/dwc2/dwc2_stm32.h @@ -264,6 +264,22 @@ static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { } } +// MCU specific PHY deinit, disable PHY power +static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { + if (hs_phy_type == GHWCFG2_HSPHY_NOT_SUPPORTED) { + // Disable on-chip FS PHY + dwc2->stm32_gccfg &= ~STM32_GCCFG_PWRDWN; + } else { + // Disable HS PHY + #ifdef USB_HS_PHYC + dwc2->stm32_gccfg &= ~STM32_GCCFG_PHYHSEN; + // Disable PLL and LDO + USB_HS_PHYC->USB_HS_PHYC_PLL &= ~USB_HS_PHYC_PLL_PLLEN; + USB_HS_PHYC->USB_HS_PHYC_LDO &= ~USB_HS_PHYC_LDO_ENABLE; + #endif + } +} + // MCU specific PHY update, it is called AFTER init() and core reset static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { // used to set turnaround time for fullspeed, nothing to do in highspeed mode diff --git a/src/portable/synopsys/dwc2/dwc2_xmc.h b/src/portable/synopsys/dwc2/dwc2_xmc.h index 63419abf7..aca3873df 100644 --- a/src/portable/synopsys/dwc2/dwc2_xmc.h +++ b/src/portable/synopsys/dwc2/dwc2_xmc.h @@ -71,6 +71,13 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type) //USB->ROUTE = USB_ROUTE_PHYPEN; } +// MCU specific PHY deinit, disable PHY power +static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { + (void) dwc2; + (void) hs_phy_type; + // nothing to do +} + // MCU specific PHY update, it is called AFTER init() and core reset static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { diff --git a/src/portable/synopsys/dwc2/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c index 0fd60b35d..e12e44a41 100644 --- a/src/portable/synopsys/dwc2/hcd_dwc2.c +++ b/src/portable/synopsys/dwc2/hcd_dwc2.c @@ -416,11 +416,6 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { // force host mode and wait for mode switch dwc2->gusbcfg = (dwc2->gusbcfg & ~GUSBCFG_FDMOD) | GUSBCFG_FHMOD; - #if CFG_TUSB_MCU == OPT_MCU_STM32N6 - // No hardware detection of Vbus B-session is available on the STM32N6 - dwc2->stm32_gccfg &= ~STM32_GCCFG_VBVALOVAL; - #endif - while ((dwc2->gintsts & GINTSTS_CMOD) != GINTSTS_CMODE_HOST) {} #ifdef TUP_USBIP_DWC2_STM32 @@ -440,7 +435,7 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { dwc2->hprt = HPRT_POWER; // turn on VBUS // Enable required interrupts - dwc2->gintmsk |= GINTSTS_OTGINT | GINTSTS_CONIDSTSCHNG | GINTSTS_HPRTINT | GINTSTS_HCINT | GINTSTS_DISCINT; + dwc2->gintmsk |= GINTSTS_OTGINT | GINTSTS_HPRTINT | GINTSTS_HCINT | GINTSTS_DISCINT; // NPTX can hold at least 2 packet, change interrupt level to half-empty uint32_t gahbcfg = dwc2->gahbcfg & ~GAHBCFG_TX_FIFO_EPMTY_LVL; @@ -450,6 +445,17 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { return true; } +bool hcd_deinit(uint8_t rhport) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + + // Turn off VBUS + dwc2->hprt = HPRT_W1_MASK; // clear w1c bits without side effects + // HPRT_POWER is not set -> VBUS off + + dwc2_core_deinit(rhport); + return true; +} + // Enable USB interrupt void hcd_int_enable (uint8_t rhport) { dwc2_int_set(rhport, TUSB_ROLE_HOST, true); @@ -874,7 +880,7 @@ static void handle_rxflvl_irq(uint8_t rhport) { break; case GRXSTS_PKTSTS_HOST_DATATOGGLE_ERR: - TU_ASSERT(0, ); // maybe try to change DToggle + // handle in channel interrupt break; case GRXSTS_PKTSTS_HOST_CHANNEL_HALTED: @@ -1014,8 +1020,11 @@ static bool handle_channel_in_slave(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t h channel_xfer_in_retry(dwc2, ch_id, hcint); } } else if (hcint & HCINT_DATATOGGLE_ERR) { + channel->hcintmsk &= ~HCINT_DATATOGGLE_ERR; xfer->err_count = 0; - TU_ASSERT(false); + hcsplt.split_compl = 0; // restart with start-split + channel->hcsplt = hcsplt.value; + channel_disable(dwc2, channel); } else { // nothing to do } @@ -1351,6 +1360,17 @@ static bool handle_sof_irq(uint8_t rhport, bool in_isr) { } // Config HCFG FS/LS clock and HFIR for SOF interval according to link speed (value is in PHY clock unit) +// Databook Table 2-2: System Clock Speeds +// +-----------+------------------+----------+-----------+-------------------+ +// | PHY | PHY Clock (MHz) | Width | HCFG.Sel | HFIR (clk cycles) | +// +-----------+------------------+----------+-----------+-------------------+ +// | HS UTMI+ | 30 | 16-bit | 30_60 | HS:3749 FS:29999 | +// | HS UTMI+ | 60 | 8-bit | 30_60 | HS:7499 FS:59999 | +// | HS ULPI | 60 | 8-bit | 30_60 | HS:7499 FS:59999 | +// | FS (dead.) | 48 | internal | 48 | FS:47999 | +// | LS via FS | 48 (6 effective) | internal | 6 | LS:47999 | +// +-----------+------------------+----------+-----------+-------------------+ +// HFIR = (interval_us * phy_clock) - 1, where interval is 125us (HS) or 1000us (FS/LS) static void port0_enable(dwc2_regs_t* dwc2, tusb_speed_t speed) { uint32_t hcfg = dwc2->hcfg & ~HCFG_FSLS_PHYCLK_SEL; @@ -1443,16 +1463,6 @@ void hcd_int_handler(uint8_t rhport, bool in_isr) { // TU_LOG1_HEX(gintsts); - if (gintsts & GINTSTS_CONIDSTSCHNG) { - // Connector ID status change - dwc2->gintsts = GINTSTS_CONIDSTSCHNG; - - //if (dwc2->gotgctl) - // dwc2->hprt = HPRT_POWER; // power on port to turn on VBUS - //dwc2->gintmsk |= GINTMSK_PRTIM; - // TODO wait for SRP if OTG - } - if (gintsts & GINTSTS_SOF) { const bool more_sof = handle_sof_irq(rhport, in_isr); if (!more_sof) { diff --git a/src/tusb.c b/src/tusb.c index 6075e9db4..5241fcf3d 100644 --- a/src/tusb.c +++ b/src/tusb.c @@ -39,23 +39,38 @@ #include "host/usbh_pvt.h" #endif +// Suppress IAR warning +// Warning[Pe111]: statement is unreachable +#if defined(__ICCARM__) +#pragma diag_suppress = Pe111 +#endif + tusb_role_t _tusb_rhport_role[TUP_USBIP_CONTROLLER_NUM] = { TUSB_ROLE_INVALID }; //-------------------------------------------------------------------- // Weak/Default API, can be overwritten by Application //-------------------------------------------------------------------- -TU_ATTR_WEAK void tusb_time_delay_ms_api(uint32_t ms) { #if CFG_TUSB_OS != OPT_OS_NONE +TU_ATTR_WEAK uint32_t tusb_time_millis_api(void) { + return osal_time_millis(); +} + +TU_ATTR_WEAK void tusb_time_delay_ms_api(uint32_t ms) { osal_task_delay(ms); +} + #else - // delay using millis() (if implemented) and/or frame number if possible +// tusb_time_millis_api() must be implemented by user application. + +TU_ATTR_WEAK void tusb_time_delay_ms_api(uint32_t ms) { + // delay using millis() const uint32_t time_ms = tusb_time_millis_api(); while ((tusb_time_millis_api() - time_ms) < ms) {} -#endif } +#endif -TU_ATTR_WEAK void* tusb_app_virt_to_phys(void *virt_addr) { +TU_ATTR_WEAK void *tusb_app_virt_to_phys(void *virt_addr) { return virt_addr; } diff --git a/src/tusb.h b/src/tusb.h index 62b3b9783..3876bf863 100644 --- a/src/tusb.h +++ b/src/tusb.h @@ -135,7 +135,7 @@ //--------------------------------------------------------------------+ -// User API +// Application API //--------------------------------------------------------------------+ #if CFG_TUH_ENABLED || CFG_TUD_ENABLED diff --git a/src/tusb_option.h b/src/tusb_option.h index 8b4d3dcd5..9c7d779fb 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h @@ -238,6 +238,7 @@ #define OPT_OS_RTTHREAD 6 ///< RT-Thread #define OPT_OS_RTX4 7 ///< Keil RTX 4 #define OPT_OS_ZEPHYR 8 ///< Zephyr +#define OPT_OS_THREADX 9 ///< ThreadX //--------------------------------------------------------------------+ // Mode and Speed @@ -487,7 +488,6 @@ #define TUP_MCU_STRICT_ALIGN 0 #endif - //--------------------------------------------------------------------+ // Common Options (Default) //--------------------------------------------------------------------+ @@ -532,6 +532,10 @@ #define CFG_TUSB_OS OPT_OS_NONE #endif +#ifndef CFG_TUSB_OS_HAS_SCHEDULER + #define CFG_TUSB_OS_HAS_SCHEDULER (CFG_TUSB_OS != OPT_OS_NONE && CFG_TUSB_OS != OPT_OS_PICO) +#endif + #ifndef CFG_TUSB_OS_INC_PATH #ifndef CFG_TUSB_OS_INC_PATH_DEFAULT #define CFG_TUSB_OS_INC_PATH_DEFAULT |
