Add EPX transfer scheduling when hcd_edpt_xfer() is called while epx is busy

This commit is contained in:
hathach
2026-03-19 22:59:22 +07:00
parent 0fd8fd45b5
commit 0c6fa9bd7f
3 changed files with 116 additions and 12 deletions

View File

@ -106,6 +106,9 @@ TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) {
return hcd_port_speed_get(0) != tuh_speed_get(dev_addr);
}
// forward declaration
static void __tusb_irq_path_func(edpt_schedule_next)(void);
static void __tusb_irq_path_func(hw_xfer_complete)(hw_endpoint_t *ep, xfer_result_t xfer_result) {
// Mark transfer as done before we tell the tinyusb stack
uint8_t dev_addr = ep->dev_addr;
@ -113,6 +116,11 @@ static void __tusb_irq_path_func(hw_xfer_complete)(hw_endpoint_t *ep, xfer_resul
uint xferred_len = ep->xferred_len;
hw_endpoint_reset_transfer(ep);
hcd_event_xfer_complete(dev_addr, ep_addr, xferred_len, xfer_result, true);
// Schedule next pending EPX transfer (only for non-interrupt endpoints)
if (ep == epx) {
edpt_schedule_next();
}
}
static void __tusb_irq_path_func(handle_hwbuf_status_bit)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg) {
@ -169,8 +177,7 @@ static void __tusb_irq_path_func(handle_hwbuf_status)(void) {
}
// All non-interrupt endpoints use shared EPX.
// static void edpt_scheduler(void) {
// }
// Forward declared above hw_xfer_complete, defined after edpt_xfer below.
static void __tusb_irq_path_func(hcd_rp2040_irq)(void) {
const uint32_t status = usb_hw->ints;
@ -206,7 +213,7 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) {
if (status & USB_INTS_TRANS_COMPLETE_BITS) {
usb_hw_clear->sie_status = USB_SIE_STATUS_TRANS_COMPLETE_BITS;
// only handle setup packet
// only handle a setup packet
if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS) {
epx->xferred_len = 8;
hw_xfer_complete(epx, XFER_RESULT_SUCCESS);
@ -215,6 +222,30 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) {
}
}
// if (status & USB_INTS_EP_STALL_NAK_BITS) {
// const uint32_t ep_stall_nak = usb_hw->ep_status_stall_nak;
// usb_hw->ep_status_stall_nak = ep_stall_nak; // clear by writing back (WC)
//
// // Preempt non-control EPX bulk transfer when a different ep is pending
// if (epx->active && tu_edpt_number(epx->ep_addr) != 0) {
// for (uint i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) {
// hw_endpoint_t *ep = &ep_pool[i];
// if (ep->pending && ep != epx) {
// // Stop current transaction
// usb_hw_set->sie_ctrl = USB_SIE_CTRL_STOP_TRANS_BITS;
//
// // Mark current EPX as pending to resume later
// epx->pending = 1;
// epx->active = false;
//
// // Start the next pending transfer
// edpt_schedule_next();
// break;
// }
// }
// }
// }
if (status & USB_INTS_ERROR_RX_TIMEOUT_BITS) {
usb_hw_clear->sie_status = USB_SIE_STATUS_RX_TIMEOUT_BITS;
}
@ -250,7 +281,7 @@ static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t dev_addr, const tusb_des
// from 15 interrupt endpoints pool
uint8_t int_idx;
for (int_idx = 0; int_idx < USB_HOST_INTERRUPT_ENDPOINTS; int_idx++) {
if (!tu_bit_test(usb_hw_set->int_ep_ctrl, 1 + int_idx)) {
if (!tu_bit_test(usb_hw->int_ep_ctrl, 1 + int_idx)) {
ep->interrupt_num = int_idx + 1;
break;
}
@ -313,6 +344,7 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
USB_INTE_HOST_RESUME_BITS |
USB_INTE_STALL_BITS |
USB_INTE_TRANS_COMPLETE_BITS |
// USB_INTE_EP_STALL_NAK_BITS |
USB_INTE_ERROR_RX_TIMEOUT_BITS |
USB_INTE_ERROR_DATA_SEQ_BITS ;
@ -369,6 +401,8 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) {
for (size_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) {
hw_endpoint_t *ep = &ep_pool[i];
if (ep->dev_addr == dev_addr && ep->max_packet_size > 0) {
ep->pending = 0; // clear any pending transfer
if (ep->interrupt_num) {
// disable interrupt endpoint
usb_hw_clear->int_ep_ctrl = 1u << ep->interrupt_num;
@ -442,10 +476,18 @@ static void edpt_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_
const uint8_t ep_num = tu_edpt_number(ep->ep_addr);
const tusb_dir_t ep_dir = tu_edpt_dir(ep->ep_addr);
// RP2040-E4: USB host writes status to upper half of buffer control in single buffered mode.
// The buffer selector toggles even in single-buffered mode, so the previous transfer's status
// may have been written to BUF1 half, leaving BUF0 with stale AVAILABLE bit. Clear it here.
#if defined(PICO_RP2040) && PICO_RP2040 == 1
usbh_dpram->epx_buf_ctrl = 0;
#endif
// ep control
const uint32_t dpram_offset = hw_data_offset(ep->dpram_buf);
const uint32_t ep_ctrl = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER |
((uint32_t)ep->transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | dpram_offset;
((uint32_t)ep->transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | dpram_offset /*|
(1u << 16)*/; // INTERRUPT_ON_NAK
usbh_dpram->epx_ctrl = ep_ctrl;
io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl;
@ -464,6 +506,40 @@ static void edpt_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_
}
}
// Schedule next pending EPX transfer from ISR context
static void __tusb_irq_path_func(edpt_schedule_next)(void) {
// EPX may already be active if the completion callback started a new transfer
if (epx->active) return;
for (uint i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) {
hw_endpoint_t *ep = &ep_pool[i];
if (ep->pending == 0) continue;
if (ep->pending == 2) {
// Pending setup: DPRAM already has the setup packet
ep->ep_addr = 0;
ep->remaining_len = 8;
ep->xferred_len = 0;
ep->active = true;
ep->pending = 0;
epx = ep;
usb_hw->dev_addr_ctrl = ep->dev_addr;
const uint32_t sie_ctrl = USB_SIE_CTRL_SEND_SETUP_BITS |
(ep->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0);
sie_start_xfer(sie_ctrl);
} else {
// Pending data transfer: preserve partial progress from preemption
uint16_t prev_xferred = ep->xferred_len;
ep->pending = 0;
edpt_xfer(ep, ep->user_buf, NULL, ep->remaining_len);
epx->xferred_len += prev_xferred; // restore partial progress
}
return; // start only one transfer
}
}
bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) {
(void)rhport;
@ -476,6 +552,14 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b
ep->next_pid = 1; // data and status stage start with DATA1
}
// If EPX is busy with another transfer, mark as pending
if (ep->transfer_type != TUSB_XFER_INTERRUPT && epx->active) {
ep->user_buf = buffer;
ep->remaining_len = buflen;
ep->pending = 1;
return true;
}
edpt_xfer(ep, buffer, NULL, buflen);
return true;
@ -492,7 +576,7 @@ bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet[8]) {
(void)rhport;
// Copy data into setup packet buffer
// Copy data into setup packet buffer (usbh only schedules one setup at a time)
for (uint8_t i = 0; i < 8; i++) {
usbh_dpram->setup_packet[i] = setup_packet[i];
}
@ -500,7 +584,14 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet
hw_endpoint_t *ep = edpt_find(dev_addr, 0x00);
TU_ASSERT(ep);
ep->ep_addr = 0; // setup is OUT
ep->ep_addr = 0; // setup is OUT
// If EPX is busy, mark as pending setup (DPRAM already has the packet)
if (epx->active) {
ep->pending = 2;
return true;
}
ep->remaining_len = 8;
ep->xferred_len = 0;
ep->active = true;

View File

@ -110,7 +110,17 @@ void __tusb_irq_path_func(hwbuf_ctrl_update)(io_rw_32 *buf_ctrl_reg, uint32_t an
value |= or_mask;
if (or_mask & USB_BUF_CTRL_AVAIL) {
if (buf_ctrl & USB_BUF_CTRL_AVAIL) {
panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_ctrl_reg);
if (is_host) {
#if defined(PICO_RP2040) && PICO_RP2040 == 1
// RP2040-E4: host buffer selector toggles in single-buffered mode, causing status
// to be written to BUF1 half and leaving stale AVAILABLE in BUF0 half. Clear it.
*buf_ctrl_reg = 0;
#else
panic("buf_ctrl @ 0x%lX already available (host)", (uintptr_t)buf_ctrl_reg);
#endif
} else {
panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_ctrl_reg);
}
}
*buf_ctrl_reg = value & ~USB_BUF_CTRL_AVAIL;

View File

@ -475,7 +475,7 @@ def test_host_cdc_msc_hid(board):
cdc_devs = [d for d in dev_attached if d.get('is_cdc')]
msc_devs = [d for d in dev_attached if d.get('is_msc')]
if not cdc_devs and not msc_devs:
return
return 'skipped'
port = get_serial_dev(flasher["uid"], None, None, 0)
ser = open_serial_dev(port)
@ -568,7 +568,7 @@ def test_host_msc_file_explorer(board):
flasher = board['flasher']
msc_devs = [d for d in board['tests'].get('dev_attached', []) if d.get('is_msc')]
if not msc_devs:
return
return 'skipped'
port = get_serial_dev(flasher["uid"], None, None, 0)
ser = open_serial_dev(port)
@ -1113,8 +1113,11 @@ def test_example(board, f1, example):
ret = globals()[f'flash_{board["flasher"]["name"].lower()}'](board, fw_name)
if ret.returncode == 0:
try:
globals()[f'test_{example.replace("/", "_")}'](board)
print(' OK', end='')
tret = globals()[f'test_{example.replace("/", "_")}'](board)
if tret == 'skipped':
print(f' {STATUS_SKIPPED}', end='')
else:
print(' OK', end='')
break
except Exception as e:
if i == max_rety - 1: