diff options
Diffstat (limited to 'src/portable/nxp')
| -rw-r--r-- | src/portable/nxp/khci/dcd_khci.c | 560 | ||||
| -rw-r--r-- | src/portable/nxp/khci/hcd_khci.c | 628 | ||||
| -rw-r--r-- | src/portable/nxp/lpc17_40/dcd_lpc17_40.c | 281 | ||||
| -rw-r--r-- | src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c | 189 |
4 files changed, 391 insertions, 1267 deletions
diff --git a/src/portable/nxp/khci/dcd_khci.c b/src/portable/nxp/khci/dcd_khci.c deleted file mode 100644 index 9f61bd236..000000000 --- a/src/portable/nxp/khci/dcd_khci.c +++ /dev/null @@ -1,560 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2020 Koji Kitayama - * SPDX-FileCopyrightText: Copyright (c) 2020 Ha Thach (tinyusb.org) - * SPDX-License-Identifier: MIT - * - * This file is part of the TinyUSB stack. - */ - -#include "tusb_option.h" - -#if CFG_TUD_ENABLED && defined(TUP_USBIP_CHIPIDEA_FS) - -#ifdef TUP_USBIP_CHIPIDEA_FS_KINETIS - #include "fsl_device_registers.h" - #define KHCI USB0 -#else - #error "MCU is not supported" -#endif - -#include "device/dcd.h" - -//--------------------------------------------------------------------+ -// MACRO TYPEDEF CONSTANT ENUM DECLARATION -//--------------------------------------------------------------------+ - -enum { - TOK_PID_OUT = 0x1u, - TOK_PID_IN = 0x9u, - TOK_PID_SETUP = 0xDu, -}; - -typedef struct TU_ATTR_PACKED -{ - union { - uint32_t head; - struct { - union { - struct { - uint16_t : 2; - __IO uint16_t tok_pid : 4; - uint16_t data : 1; - __IO uint16_t own : 1; - uint16_t : 8; - }; - struct { - uint16_t : 2; - uint16_t bdt_stall : 1; - uint16_t dts : 1; - uint16_t ninc : 1; - uint16_t keep : 1; - uint16_t : 10; - }; - }; - __IO uint16_t bc : 10; - uint16_t : 6; - }; - }; - uint8_t *addr; -}buffer_descriptor_t; - -TU_VERIFY_STATIC( sizeof(buffer_descriptor_t) == 8, "size is not correct" ); - -typedef struct TU_ATTR_PACKED -{ - union { - uint32_t state; - struct { - uint32_t max_packet_size :11; - uint32_t : 5; - uint32_t odd : 1; - uint32_t :15; - }; - }; - uint16_t length; - uint16_t remaining; -}endpoint_state_t; - -TU_VERIFY_STATIC( sizeof(endpoint_state_t) == 8, "size is not correct" ); - -typedef struct -{ - union { - /* [#EP][OUT,IN][EVEN,ODD] */ - buffer_descriptor_t bdt[16][2][2]; - uint16_t bda[512]; - }; - TU_ATTR_ALIGNED(4) union { - endpoint_state_t endpoint[16][2]; - endpoint_state_t endpoint_unified[16 * 2]; - }; - uint8_t setup_packet[8]; - uint8_t addr; -}dcd_data_t; - -//--------------------------------------------------------------------+ -// INTERNAL OBJECT & FUNCTION DECLARATION -//--------------------------------------------------------------------+ -// BDT(Buffer Descriptor Table) must be 256-byte aligned -CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(512) static dcd_data_t _dcd; - -TU_VERIFY_STATIC( sizeof(_dcd.bdt) == 512, "size is not correct" ); - -static void prepare_next_setup_packet(uint8_t rhport) -{ - const unsigned out_odd = _dcd.endpoint[0][0].odd; - const unsigned in_odd = _dcd.endpoint[0][1].odd; - TU_ASSERT(0 == _dcd.bdt[0][0][out_odd].own, ); - - _dcd.bdt[0][0][out_odd].data = 0; - _dcd.bdt[0][0][out_odd ^ 1].data = 1; - _dcd.bdt[0][1][in_odd].data = 1; - _dcd.bdt[0][1][in_odd ^ 1].data = 0; - dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_OUT), - _dcd.setup_packet, sizeof(_dcd.setup_packet), false); -} - -static void process_stall(uint8_t rhport) -{ - for (int i = 0; i < 16; ++i) { - unsigned const endpt = KHCI->ENDPOINT[i].ENDPT; - - if (endpt & USB_ENDPT_EPSTALL_MASK) { - // prepare next setup if endpoint0 - if ( i == 0 ) prepare_next_setup_packet(rhport); - - // clear stall bit - KHCI->ENDPOINT[i].ENDPT = endpt & ~USB_ENDPT_EPSTALL_MASK; - } - } -} - -static void process_tokdne(uint8_t rhport) -{ - const unsigned s = KHCI->STAT; - KHCI->ISTAT = USB_ISTAT_TOKDNE_MASK; /* fetch the next token if received */ - - uint8_t const epnum = (s >> USB_STAT_ENDP_SHIFT); - uint8_t const dir = (s & USB_STAT_TX_MASK) >> USB_STAT_TX_SHIFT; - unsigned const odd = (s & USB_STAT_ODD_MASK) ? 1 : 0; - - buffer_descriptor_t *bd = (buffer_descriptor_t *)&_dcd.bda[s]; - endpoint_state_t *ep = &_dcd.endpoint_unified[s >> 3]; - - /* fetch pid before discarded by the next steps */ - const unsigned pid = bd->tok_pid; - - /* reset values for a next transfer */ - bd->bdt_stall = 0; - bd->dts = 1; - bd->ninc = 0; - bd->keep = 0; - /* update the odd variable to prepare for the next transfer */ - ep->odd = odd ^ 1; - if (pid == TOK_PID_SETUP) { - dcd_event_setup_received(rhport, bd->addr, true); - KHCI->CTL &= ~USB_CTL_TXSUSPENDTOKENBUSY_MASK; - return; - } - - const unsigned bc = bd->bc; - const unsigned remaining = ep->remaining - bc; - if (remaining && bc == ep->max_packet_size) { - /* continue the transferring consecutive data */ - ep->remaining = remaining; - const int next_remaining = remaining - ep->max_packet_size; - if (next_remaining > 0) { - /* prepare to the after next transfer */ - bd->addr += ep->max_packet_size * 2; - bd->bc = next_remaining > ep->max_packet_size ? ep->max_packet_size: next_remaining; - __DSB(); - bd->own = 1; /* the own bit must set after addr */ - } - return; - } - const unsigned length = ep->length; - dcd_event_xfer_complete(rhport, - tu_edpt_addr(epnum, dir), - length - remaining, XFER_RESULT_SUCCESS, true); - if (0 == epnum && 0 == length) { - /* After completion a ZLP of control transfer, - * it prepares for the next steup transfer. */ - if (_dcd.addr) { - /* When the transfer was the SetAddress, - * the device address should be updated here. */ - KHCI->ADDR = _dcd.addr; - _dcd.addr = 0; - } - prepare_next_setup_packet(rhport); - } -} - -static void process_bus_reset(uint8_t rhport) -{ - KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK; - KHCI->CTL |= USB_CTL_ODDRST_MASK; - KHCI->ADDR = 0; - KHCI->INTEN = USB_INTEN_USBRSTEN_MASK | USB_INTEN_TOKDNEEN_MASK | USB_INTEN_SLEEPEN_MASK | - USB_INTEN_ERROREN_MASK | USB_INTEN_STALLEN_MASK; - - KHCI->ENDPOINT[0].ENDPT = USB_ENDPT_EPHSHK_MASK | USB_ENDPT_EPRXEN_MASK | USB_ENDPT_EPTXEN_MASK; - for (unsigned i = 1; i < 16; ++i) { - KHCI->ENDPOINT[i].ENDPT = 0; - } - buffer_descriptor_t *bd = _dcd.bdt[0][0]; - for (unsigned i = 0; i < sizeof(_dcd.bdt)/sizeof(*bd); ++i, ++bd) { - bd->head = 0; - } - const endpoint_state_t ep0 = { - .max_packet_size = CFG_TUD_ENDPOINT0_SIZE, - .odd = 0, - .length = 0, - .remaining = 0, - }; - _dcd.endpoint[0][0] = ep0; - _dcd.endpoint[0][1] = ep0; - tu_memclr(_dcd.endpoint[1], sizeof(_dcd.endpoint) - sizeof(_dcd.endpoint[0])); - _dcd.addr = 0; - prepare_next_setup_packet(rhport); - KHCI->CTL &= ~USB_CTL_ODDRST_MASK; - dcd_event_bus_reset(rhport, TUSB_SPEED_FULL, true); -} - -static void process_bus_sleep(uint8_t rhport) -{ - // Enable resume & disable suspend interrupt - const unsigned inten = KHCI->INTEN; - - KHCI->INTEN = (inten & ~USB_INTEN_SLEEPEN_MASK) | USB_INTEN_RESUMEEN_MASK; - KHCI->USBTRC0 |= USB_USBTRC0_USBRESMEN_MASK; - KHCI->USBCTRL |= USB_USBCTRL_SUSP_MASK; - - dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true); -} - -static void process_bus_resume(uint8_t rhport) -{ - // Enable suspend & disable resume interrupt - const unsigned inten = KHCI->INTEN; - - KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK; // will also clear USB_USBTRC0_USB_RESUME_INT_MASK - KHCI->USBTRC0 &= ~USB_USBTRC0_USBRESMEN_MASK; - KHCI->INTEN = (inten & ~USB_INTEN_RESUMEEN_MASK) | USB_INTEN_SLEEPEN_MASK; - - dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); -} - -/*------------------------------------------------------------------*/ -/* Device API - *------------------------------------------------------------------*/ -bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { - (void) rhport; - (void) rh_init; - - // save crystal-less setting (if available) - #if defined(FSL_FEATURE_USB_KHCI_IRC48M_MODULE_CLOCK_ENABLED) && FSL_FEATURE_USB_KHCI_IRC48M_MODULE_CLOCK_ENABLED == 1 - uint32_t clk_recover_irc_en = KHCI->CLK_RECOVER_IRC_EN; - uint32_t clk_recover_ctrl = KHCI->CLK_RECOVER_CTRL; - #endif - - KHCI->USBTRC0 |= USB_USBTRC0_USBRESET_MASK; - while (KHCI->USBTRC0 & USB_USBTRC0_USBRESET_MASK); - - // restore crystal-less setting (if available) - #if defined(FSL_FEATURE_USB_KHCI_IRC48M_MODULE_CLOCK_ENABLED) && FSL_FEATURE_USB_KHCI_IRC48M_MODULE_CLOCK_ENABLED == 1 - KHCI->CLK_RECOVER_IRC_EN = clk_recover_irc_en; - KHCI->CLK_RECOVER_CTRL |= clk_recover_ctrl; - #endif - - tu_memclr(&_dcd, sizeof(_dcd)); - KHCI->USBTRC0 |= TU_BIT(6); /* software must set this bit to 1 */ - KHCI->BDTPAGE1 = (uint8_t)((uintptr_t)_dcd.bdt >> 8); - KHCI->BDTPAGE2 = (uint8_t)((uintptr_t)_dcd.bdt >> 16); - KHCI->BDTPAGE3 = (uint8_t)((uintptr_t)_dcd.bdt >> 24); - - KHCI->INTEN = USB_INTEN_USBRSTEN_MASK; - - dcd_connect(rhport); - NVIC_ClearPendingIRQ(USB0_IRQn); - - return true; -} - -void dcd_int_enable(uint8_t rhport) -{ - (void) rhport; - NVIC_EnableIRQ(USB0_IRQn); -} - -void dcd_int_disable(uint8_t rhport) -{ - (void) rhport; - NVIC_DisableIRQ(USB0_IRQn); -} - -void dcd_set_address(uint8_t rhport, uint8_t dev_addr) -{ - _dcd.addr = dev_addr & 0x7F; - /* Response with status first before changing device address */ - dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0, false); -} - -void dcd_remote_wakeup(uint8_t rhport) -{ - (void) rhport; - - KHCI->CTL |= USB_CTL_RESUME_MASK; - - unsigned cnt = SystemCoreClock / 1000; - while (cnt--) __NOP(); - - KHCI->CTL &= ~USB_CTL_RESUME_MASK; -} - -void dcd_connect(uint8_t rhport) -{ - (void) rhport; - KHCI->USBCTRL = 0; - KHCI->CONTROL |= USB_CONTROL_DPPULLUPNONOTG_MASK; - KHCI->CTL |= USB_CTL_USBENSOFEN_MASK; -} - -void dcd_disconnect(uint8_t rhport) -{ - (void) rhport; - KHCI->CTL = 0; - KHCI->CONTROL &= ~USB_CONTROL_DPPULLUPNONOTG_MASK; -} - -void dcd_sof_enable(uint8_t rhport, bool en) -{ - (void) rhport; - (void) en; - - // TODO implement later -} - -//--------------------------------------------------------------------+ -// Endpoint API -//--------------------------------------------------------------------+ -static bool edpt_open(uint8_t rhport, uint8_t ep_addr, uint16_t max_packet_size, tusb_xfer_type_t xfer) { - (void)rhport; - - const unsigned epn = tu_edpt_number(ep_addr); - const unsigned dir = tu_edpt_dir(ep_addr); - endpoint_state_t *ep = &_dcd.endpoint[epn][dir]; - const unsigned odd = ep->odd; - buffer_descriptor_t *bd = _dcd.bdt[epn][dir]; - - /* No support for control transfer */ - TU_ASSERT(epn && (xfer != TUSB_XFER_CONTROL)); - - ep->max_packet_size = max_packet_size; - unsigned val = USB_ENDPT_EPCTLDIS_MASK; - val |= (xfer != TUSB_XFER_ISOCHRONOUS) ? USB_ENDPT_EPHSHK_MASK : 0; - val |= dir ? USB_ENDPT_EPTXEN_MASK : USB_ENDPT_EPRXEN_MASK; - KHCI->ENDPOINT[epn].ENDPT |= val; - - if (xfer != TUSB_XFER_ISOCHRONOUS) { - bd[odd].dts = 1; - bd[odd].data = 0; - bd[odd ^ 1].dts = 1; - bd[odd ^ 1].data = 1; - } - - return true; -} - -bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc) { - return edpt_open(rhport, ep_desc->bEndpointAddress, tu_edpt_packet_size(ep_desc), ep_desc->bmAttributes.xfer); -} - -bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { - return edpt_open(rhport, ep_addr, largest_packet_size, TUSB_XFER_ISOCHRONOUS); -} - -bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc) { - const unsigned epn = tu_edpt_number(ep_desc->bEndpointAddress); - const unsigned dir = tu_edpt_dir(ep_desc->bEndpointAddress); - endpoint_state_t *ep = &_dcd.endpoint[epn][dir]; - - dcd_int_disable(rhport); - ep->max_packet_size = tu_edpt_packet_size(ep_desc); - dcd_int_enable(rhport); - - return true; -} - -void dcd_edpt_close_all(uint8_t rhport) -{ - (void) rhport; - const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn); - NVIC_DisableIRQ(USB0_IRQn); - for (unsigned i = 1; i < 16; ++i) { - KHCI->ENDPOINT[i].ENDPT = 0; - } - if (ie) NVIC_EnableIRQ(USB0_IRQn); - buffer_descriptor_t *bd = _dcd.bdt[1][0]; - for (unsigned i = 2; i < sizeof(_dcd.bdt)/sizeof(*bd); ++i, ++bd) { - bd->head = 0; - } - endpoint_state_t *ep = &_dcd.endpoint[1][0]; - for (unsigned i = 2; i < sizeof(_dcd.endpoint)/sizeof(*ep); ++i, ++ep) { - /* Clear except the odd */ - ep->max_packet_size = 0; - ep->length = 0; - ep->remaining = 0; - } -} - -bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) -{ - (void) rhport; - (void) is_isr; - const unsigned epn = tu_edpt_number(ep_addr); - const unsigned dir = tu_edpt_dir(ep_addr); - endpoint_state_t *ep = &_dcd.endpoint[epn][dir]; - buffer_descriptor_t *bd = &_dcd.bdt[epn][dir][ep->odd]; - TU_ASSERT(0 == bd->own); - - const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn); - NVIC_DisableIRQ(USB0_IRQn); - - ep->length = total_bytes; - ep->remaining = total_bytes; - - const unsigned mps = ep->max_packet_size; - if (total_bytes > mps) { - buffer_descriptor_t *next = ep->odd ? bd - 1: bd + 1; - /* When total_bytes is greater than the max packet size, - * it prepares to the next transfer to avoid NAK in advance. */ - next->bc = total_bytes >= 2 * mps ? mps: total_bytes - mps; - next->addr = buffer + mps; - next->own = 1; - } - bd->bc = total_bytes >= mps ? mps: total_bytes; - bd->addr = buffer; - __DSB(); - bd->own = 1; /* This bit must be set last */ - - if (ie) NVIC_EnableIRQ(USB0_IRQn); - return true; -} - -void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - const unsigned epn = tu_edpt_number(ep_addr); - - if (0 == epn) { - KHCI->ENDPOINT[epn].ENDPT |= USB_ENDPT_EPSTALL_MASK; - } else { - const unsigned dir = tu_edpt_dir(ep_addr); - const unsigned odd = _dcd.endpoint[epn][dir].odd; - buffer_descriptor_t *bd = &_dcd.bdt[epn][dir][odd]; - TU_ASSERT(0 == bd->own,); - - const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn); - NVIC_DisableIRQ(USB0_IRQn); - - bd->bdt_stall = 1; - __DSB(); - bd->own = 1; /* This bit must be set last */ - - if (ie) NVIC_EnableIRQ(USB0_IRQn); - } -} - -void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - const unsigned epn = tu_edpt_number(ep_addr); - TU_VERIFY(epn,); - const unsigned dir = tu_edpt_dir(ep_addr); - const unsigned odd = _dcd.endpoint[epn][dir].odd; - buffer_descriptor_t *bd = _dcd.bdt[epn][dir]; - TU_VERIFY(bd[odd].own,); - - const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn); - NVIC_DisableIRQ(USB0_IRQn); - - bd[odd].own = 0; - __DSB(); - - // clear stall - bd[odd].bdt_stall = 0; - - // Reset data toggle - bd[odd ].data = 0; - bd[odd ^ 1].data = 1; - - // We already cleared this in ISR, but just clear it here to be safe - const unsigned endpt = KHCI->ENDPOINT[epn].ENDPT; - if (endpt & USB_ENDPT_EPSTALL_MASK) { - KHCI->ENDPOINT[epn].ENDPT = endpt & ~USB_ENDPT_EPSTALL_MASK; - } - - if (ie) NVIC_EnableIRQ(USB0_IRQn); -} - -//--------------------------------------------------------------------+ -// ISR -//--------------------------------------------------------------------+ -void dcd_int_handler(uint8_t rhport) -{ - uint32_t is = KHCI->ISTAT; - uint32_t msk = KHCI->INTEN; - - // clear non-enabled interrupts - KHCI->ISTAT = is & ~msk; - is &= msk; - - if (is & USB_ISTAT_ERROR_MASK) { - /* TODO: */ - uint32_t es = KHCI->ERRSTAT; - KHCI->ERRSTAT = es; - KHCI->ISTAT = is; /* discard any pending events */ - } - - if (is & USB_ISTAT_USBRST_MASK) { - KHCI->ISTAT = is; /* discard any pending events */ - process_bus_reset(rhport); - } - - if (is & USB_ISTAT_SLEEP_MASK) { - // TU_LOG3("Suspend: "); TU_LOG2_HEX(is); - - // Note Host usually has extra delay after bus reset (without SOF), which could falsely - // detected as Sleep event. Though usbd has debouncing logic so we are good - KHCI->ISTAT = USB_ISTAT_SLEEP_MASK; - process_bus_sleep(rhport); - } - -#if 0 // ISTAT_RESUME never trigger, probably for host mode ? - if (is & USB_ISTAT_RESUME_MASK) { - // TU_LOG2("ISTAT Resume: "); TU_LOG2_HEX(is); - KHCI->ISTAT = USB_ISTAT_RESUME_MASK; - process_bus_resume(rhport); - } -#endif - - if (KHCI->USBTRC0 & USB_USBTRC0_USB_RESUME_INT_MASK) { - // TU_LOG2("USBTRC0 Resume: "); TU_LOG2_HEX(is); TU_LOG2_HEX(KHCI->USBTRC0); - process_bus_resume(rhport); - } - - if (is & USB_ISTAT_SOFTOK_MASK) { - KHCI->ISTAT = USB_ISTAT_SOFTOK_MASK; - dcd_event_sof(rhport, tu_u16(KHCI->FRMNUMH, KHCI->FRMNUML), true); - } - - if (is & USB_ISTAT_STALL_MASK) { - KHCI->ISTAT = USB_ISTAT_STALL_MASK; - process_stall(rhport); - } - - if (is & USB_ISTAT_TOKDNE_MASK) { - process_tokdne(rhport); - } -} -#endif diff --git a/src/portable/nxp/khci/hcd_khci.c b/src/portable/nxp/khci/hcd_khci.c deleted file mode 100644 index 209940656..000000000 --- a/src/portable/nxp/khci/hcd_khci.c +++ /dev/null @@ -1,628 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2021 Koji Kitayama - * SPDX-FileCopyrightText: Copyright (c) 2021 Ha Thach (tinyusb.org) - * SPDX-License-Identifier: MIT - * - * This file is part of the TinyUSB stack. - */ - -#include "tusb_option.h" - -#if CFG_TUH_ENABLED && defined(TUP_USBIP_CHIPIDEA_FS) - -#ifdef TUP_USBIP_CHIPIDEA_FS_KINETIS - #include "fsl_device_registers.h" - #define KHCI USB0 -#else - #error "MCU is not supported" -#endif - -#include "host/hcd.h" -#include "host/usbh.h" - -//--------------------------------------------------------------------+ -// MACRO TYPEDEF CONSTANT ENUM DECLARATION -//--------------------------------------------------------------------+ - -enum { - TOK_PID_OUT = 0x1u, - TOK_PID_IN = 0x9u, - TOK_PID_SETUP = 0xDu, - TOK_PID_DATA0 = 0x3u, - TOK_PID_DATA1 = 0xbu, - TOK_PID_ACK = 0x2u, - TOK_PID_STALL = 0xeu, - TOK_PID_NAK = 0xau, - TOK_PID_BUSTO = 0x0u, - TOK_PID_ERR = 0xfu, -}; - -typedef struct TU_ATTR_PACKED -{ - union { - uint32_t head; - struct { - union { - struct { - uint16_t : 2; - __IO uint16_t tok_pid : 4; - uint16_t data : 1; - __IO uint16_t own : 1; - uint16_t : 8; - }; - struct { - uint16_t : 2; - uint16_t bdt_stall : 1; - uint16_t dts : 1; - uint16_t ninc : 1; - uint16_t keep : 1; - uint16_t : 10; - }; - }; - __IO uint16_t bc : 10; - uint16_t : 6; - }; - }; - uint8_t *addr; -}buffer_descriptor_t; - -TU_VERIFY_STATIC( sizeof(buffer_descriptor_t) == 8, "size is not correct" ); - -typedef struct TU_ATTR_PACKED -{ - union { - uint32_t state; - struct { - uint32_t pipenum:16; - uint32_t odd : 1; - uint32_t : 0; - }; - }; - uint8_t *buffer; - uint16_t length; - uint16_t remaining; -} endpoint_state_t; - -typedef struct TU_ATTR_PACKED -{ - uint8_t dev_addr; - uint8_t ep_addr; - uint16_t max_packet_size; - union { - uint8_t flags; - struct { - uint8_t data : 1; - uint8_t xfer : 2; - uint8_t : 0; - }; - }; - uint8_t *buffer; - uint16_t length; - uint16_t remaining; -} pipe_state_t; - - -typedef struct -{ - union { - /* [OUT,IN][EVEN,ODD] */ - buffer_descriptor_t bdt[2][2]; - uint16_t bda[2*2]; - }; - endpoint_state_t endpoint[2]; - pipe_state_t pipe[CFG_TUH_ENDPOINT_MAX * 2]; - uint32_t in_progress; /* Bitmap. Each bit indicates that a transfer of the corresponding pipe is in progress */ - uint32_t pending; /* Bitmap. Each bit indicates that a transfer of the corresponding pipe will be resume the next frame */ - bool need_reset; /* The device has not been reset after connection. */ -} hcd_data_t; - -//--------------------------------------------------------------------+ -// INTERNAL OBJECT & FUNCTION DECLARATION -//--------------------------------------------------------------------+ -// BDT(Buffer Descriptor Table) must be 256-byte aligned -CFG_TUH_MEM_SECTION TU_ATTR_ALIGNED(512) static hcd_data_t _hcd; -//CFG_TUH_MEM_SECTION TU_ATTR_ALIGNED(4) static uint8_t _rx_buf[1024]; - -static int find_pipe(uint8_t dev_addr, uint8_t ep_addr) -{ - /* Find the target pipe */ - int num; - for (num = 0; num < CFG_TUH_ENDPOINT_MAX * 2; ++num) { - pipe_state_t *p = &_hcd.pipe[num]; - if ((p->dev_addr == dev_addr) && (p->ep_addr == ep_addr)) - return num; - } - return -1; -} - -static int prepare_packets(int pipenum) -{ - pipe_state_t *pipe = &_hcd.pipe[pipenum]; - unsigned const dir_tx = tu_edpt_dir(pipe->ep_addr) ? 0 : 1; - endpoint_state_t *ep = &_hcd.endpoint[dir_tx]; - unsigned const odd = ep->odd; - buffer_descriptor_t *bd = _hcd.bdt[dir_tx]; - TU_ASSERT(0 == bd[odd].own, -1); - - // TU_LOG1(" %p dir %d odd %d data %d\r\n", &bd[odd], dir_tx, odd, pipe->data); - - ep->pipenum = pipenum; - - bd[odd ].data = pipe->data; - bd[odd ^ 1].data = pipe->data ^ 1; - bd[odd ^ 1].own = 0; - /* reset values for a next transfer */ - - int num_tokens = 0; /* The number of prepared packets */ - unsigned const mps = pipe->max_packet_size; - unsigned const rem = pipe->remaining; - if (rem > mps) { - /* When total_bytes is greater than the max packet size, - * it prepares to the next transfer to avoid NAK in advance. */ - bd[odd ^ 1].bc = rem >= 2 * mps ? mps: rem - mps; - bd[odd ^ 1].addr = pipe->buffer + mps; - bd[odd ^ 1].own = 1; - if (dir_tx) ++num_tokens; - } - bd[odd].bc = rem >= mps ? mps: rem; - bd[odd].addr = pipe->buffer; - __DSB(); - bd[odd].own = 1; /* This bit must be set last */ - ++num_tokens; - return num_tokens; -} - -static int select_next_pipenum(int pipenum) -{ - unsigned wip = _hcd.in_progress & ~_hcd.pending; - if (!wip) return -1; - unsigned msk = TU_GENMASK(31, pipenum); - int next = __builtin_ctz(wip & msk); - if (next) return next; - msk = TU_GENMASK(pipenum, 0); - next = __builtin_ctz(wip & msk); - return next; -} - -/* When transfer is completed, return true. */ -static bool continue_transfer(int pipenum, buffer_descriptor_t *bd) -{ - pipe_state_t *pipe = &_hcd.pipe[pipenum]; - unsigned const bc = bd->bc; - unsigned const rem = pipe->remaining - bc; - - pipe->remaining = rem; - if (rem && bc == pipe->max_packet_size) { - int const next_rem = rem - pipe->max_packet_size; - if (next_rem > 0) { - /* Prepare to the after next transfer */ - bd->addr += pipe->max_packet_size * 2; - bd->bc = next_rem > pipe->max_packet_size ? pipe->max_packet_size: next_rem; - __DSB(); - bd->own = 1; /* This bit must be set last */ - while (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ; - KHCI->TOKEN = KHCI->TOKEN; /* Queue the same token as the last */ - } else if (TUSB_DIR_IN == tu_edpt_dir(pipe->ep_addr)) { /* IN */ - while (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ; - KHCI->TOKEN = KHCI->TOKEN; - } - return true; - } - pipe->data = bd->data ^ 1; - return false; -} - -static bool resume_transfer(int pipenum) -{ - int num_tokens = prepare_packets(pipenum); - TU_ASSERT(0 <= num_tokens); - - const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn); - NVIC_DisableIRQ(USB0_IRQn); - pipe_state_t *pipe = &_hcd.pipe[pipenum]; - - unsigned flags = KHCI->ENDPOINT[0].ENDPT & USB_ENDPT_HOSTWOHUB_MASK; - flags |= USB_ENDPT_EPRXEN_MASK | USB_ENDPT_EPTXEN_MASK; - switch (pipe->xfer) { - case TUSB_XFER_CONTROL: - flags |= USB_ENDPT_EPHSHK_MASK; - break; - case TUSB_XFER_ISOCHRONOUS: - flags |= USB_ENDPT_EPCTLDIS_MASK | USB_ENDPT_RETRYDIS_MASK; - break; - default: - flags |= USB_ENDPT_EPHSHK_MASK | USB_ENDPT_EPCTLDIS_MASK | USB_ENDPT_RETRYDIS_MASK; - break; - } - // TU_LOG1(" resume pipenum %d flags %x\r\n", pipenum, flags); - - KHCI->ENDPOINT[0].ENDPT = flags; - KHCI->ADDR = (KHCI->ADDR & USB_ADDR_LSEN_MASK) | pipe->dev_addr; - - unsigned const token = tu_edpt_number(pipe->ep_addr) | - ((tu_edpt_dir(pipe->ep_addr) ? TOK_PID_IN: TOK_PID_OUT) << USB_TOKEN_TOKENPID_SHIFT); - do { - while (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ; - KHCI->TOKEN = token; - } while (--num_tokens); - if (ie) NVIC_EnableIRQ(USB0_IRQn); - return true; -} - -static void suspend_transfer(int pipenum, buffer_descriptor_t *bd) -{ - pipe_state_t *pipe = &_hcd.pipe[pipenum]; - pipe->buffer = bd->addr; - pipe->data = bd->data ^ 1; - if ((TUSB_XFER_INTERRUPT == pipe->xfer) || - (TUSB_XFER_BULK == pipe->xfer)) { - _hcd.pending |= TU_BIT(pipenum); - KHCI->INTEN |= USB_ISTAT_SOFTOK_MASK; - } -} - -static void process_tokdne(uint8_t rhport) -{ - (void)rhport; - const unsigned s = KHCI->STAT; - KHCI->ISTAT = USB_ISTAT_TOKDNE_MASK; /* fetch the next token if received */ - uint8_t const dir_in = (s & USB_STAT_TX_MASK) ? TUSB_DIR_OUT: TUSB_DIR_IN; - unsigned const odd = (s & USB_STAT_ODD_MASK) ? 1 : 0; - - buffer_descriptor_t *bd = (buffer_descriptor_t *)&_hcd.bda[s]; - endpoint_state_t *ep = &_hcd.endpoint[s >> 3]; - - /* fetch status before discarded by the next steps */ - const unsigned pid = bd->tok_pid; - - /* reset values for a next transfer */ - bd->bdt_stall = 0; - bd->dts = 1; - bd->ninc = 0; - bd->keep = 0; - /* Update the odd variable to prepare for the next transfer */ - ep->odd = odd ^ 1; - - int pipenum = ep->pipenum; - int next_pipenum; - // TU_LOG1("TOKDNE %x PID %x pipe %d\r\n", s, pid, pipenum); - - xfer_result_t result; - switch (pid) { - default: - if (continue_transfer(pipenum, bd)) - return; - result = XFER_RESULT_SUCCESS; - break; - case TOK_PID_NAK: - suspend_transfer(pipenum, bd); - next_pipenum = select_next_pipenum(pipenum); - if (0 <= next_pipenum) - resume_transfer(next_pipenum); - return; - case TOK_PID_STALL: - result = XFER_RESULT_STALLED; - break; - case TOK_PID_ERR: /* mismatch toggle bit */ - case TOK_PID_BUSTO: - result = XFER_RESULT_FAILED; - break; - } - _hcd.in_progress &= ~TU_BIT(pipenum); - pipe_state_t *pipe = &_hcd.pipe[ep->pipenum]; - hcd_event_xfer_complete(pipe->dev_addr, - tu_edpt_addr(KHCI->TOKEN & USB_TOKEN_TOKENENDPT_MASK, dir_in), - pipe->length - pipe->remaining, - result, true); - next_pipenum = select_next_pipenum(pipenum); - if (0 <= next_pipenum) - resume_transfer(next_pipenum); -} - -static void process_attach(uint8_t rhport) -{ - unsigned ctl = KHCI->CTL; - if (!(ctl & USB_CTL_JSTATE_MASK)) { - /* The attached device is a low speed device. */ - KHCI->ADDR = USB_ADDR_LSEN_MASK; - KHCI->ENDPOINT[0].ENDPT = USB_ENDPT_HOSTWOHUB_MASK; - } - hcd_event_device_attach(rhport, true); -} - -static void process_bus_reset(uint8_t rhport) -{ - KHCI->ISTAT = USB_ISTAT_TOKDNE_MASK; - KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK; - KHCI->CTL &= ~USB_CTL_USBENSOFEN_MASK; - KHCI->ADDR = 0; - KHCI->ENDPOINT[0].ENDPT = 0; - - hcd_event_device_remove(rhport, true); - - _hcd.in_progress = 0; - _hcd.pending = 0; - buffer_descriptor_t *bd = &_hcd.bdt[0][0]; - for (unsigned i = 0; i < 2; ++i, ++bd) { - bd->head = 0; - } -} - -/*------------------------------------------------------------------*/ -/* Host API - *------------------------------------------------------------------*/ -bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { - (void) rhport; - (void) rh_init; - KHCI->USBTRC0 |= USB_USBTRC0_USBRESET_MASK; - while (KHCI->USBTRC0 & USB_USBTRC0_USBRESET_MASK); - - tu_memclr(&_hcd, sizeof(_hcd)); - KHCI->USBTRC0 |= TU_BIT(6); /* software must set this bit to 1 */ - KHCI->BDTPAGE1 = (uint8_t)((uintptr_t)_hcd.bdt >> 8); - KHCI->BDTPAGE2 = (uint8_t)((uintptr_t)_hcd.bdt >> 16); - KHCI->BDTPAGE3 = (uint8_t)((uintptr_t)_hcd.bdt >> 24); - - KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK; - KHCI->CTL |= USB_CTL_ODDRST_MASK; - for (unsigned i = 0; i < 16; ++i) { - KHCI->ENDPOINT[i].ENDPT = 0; - } - KHCI->CTL &= ~USB_CTL_ODDRST_MASK; - - KHCI->SOFTHLD = 74; /* for 64-byte packets */ - // KHCI->SOFTHLD = 144; /* for low speed 8-byte packets */ - KHCI->CTL = USB_CTL_HOSTMODEEN_MASK | USB_CTL_SE0_MASK; - KHCI->USBCTRL = USB_USBCTRL_PDE_MASK; - - NVIC_ClearPendingIRQ(USB0_IRQn); - KHCI->INTEN = USB_INTEN_ATTACHEN_MASK | USB_INTEN_TOKDNEEN_MASK | - USB_INTEN_USBRSTEN_MASK | USB_INTEN_ERROREN_MASK | USB_INTEN_STALLEN_MASK; - KHCI->ERREN = 0xff; - - return true; -} - -void hcd_int_enable(uint8_t rhport) -{ - (void)rhport; - NVIC_EnableIRQ(USB0_IRQn); -} - -void hcd_int_disable(uint8_t rhport) -{ - (void)rhport; - NVIC_DisableIRQ(USB0_IRQn); -} - -uint32_t hcd_frame_number(uint8_t rhport) -{ - (void)rhport; - /* The device must be reset at least once after connection - * in order to start the frame counter. */ - if (_hcd.need_reset) hcd_port_reset(rhport); - uint32_t frmnum = KHCI->FRMNUML; - frmnum |= KHCI->FRMNUMH << 8u; - return frmnum; -} - -/*--------------------------------------------------------------------+ - * Port API - *--------------------------------------------------------------------+ */ -bool hcd_port_connect_status(uint8_t rhport) -{ - (void)rhport; - if (KHCI->ISTAT & USB_ISTAT_ATTACH_MASK) - return true; - return false; -} - -void hcd_port_reset(uint8_t rhport) -{ - (void)rhport; - KHCI->CTL &= ~USB_CTL_USBENSOFEN_MASK; - KHCI->CTL |= USB_CTL_RESET_MASK; - unsigned cnt = SystemCoreClock / 100; - while (cnt--) __NOP(); - KHCI->CTL &= ~USB_CTL_RESET_MASK; - KHCI->CTL |= USB_CTL_USBENSOFEN_MASK; - _hcd.need_reset = false; -} - -void hcd_port_reset_end(uint8_t rhport) { - (void) rhport; -} - -tusb_speed_t hcd_port_speed_get(uint8_t rhport) -{ - (void)rhport; - tusb_speed_t speed = TUSB_SPEED_FULL; - const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn); - NVIC_DisableIRQ(USB0_IRQn); - if (KHCI->ADDR & USB_ADDR_LSEN_MASK) - speed = TUSB_SPEED_LOW; - if (ie) NVIC_EnableIRQ(USB0_IRQn); - return speed; -} - -void hcd_device_close(uint8_t rhport, uint8_t dev_addr) -{ - (void)rhport; - const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn); - NVIC_DisableIRQ(USB0_IRQn); - pipe_state_t *p = &_hcd.pipe[0]; - pipe_state_t *end = &_hcd.pipe[CFG_TUH_ENDPOINT_MAX * 2]; - for (;p != end; ++p) { - if (p->dev_addr == dev_addr) - tu_memclr(p, sizeof(*p)); - } - if (ie) NVIC_EnableIRQ(USB0_IRQn); -} - -//--------------------------------------------------------------------+ -// Endpoints API -//--------------------------------------------------------------------+ -bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) -{ - (void)rhport; - // TU_LOG1("SETUP %u\r\n", dev_addr); - TU_ASSERT(0 == (_hcd.in_progress & TU_BIT(0))); - - int pipenum = find_pipe(dev_addr, 0); - if (pipenum < 0) return false; - - pipe_state_t *pipe = &_hcd.pipe[pipenum]; - pipe[0].data = 0; - pipe[0].buffer = (uint8_t*)(uintptr_t)setup_packet; - pipe[0].length = 8; - pipe[0].remaining = 8; - pipe[1].data = 1; - - if (1 != prepare_packets(pipenum)) - return false; - - _hcd.in_progress |= TU_BIT(pipenum); - - unsigned hostwohub = KHCI->ENDPOINT[0].ENDPT & USB_ENDPT_HOSTWOHUB_MASK; - KHCI->ENDPOINT[0].ENDPT = hostwohub | - USB_ENDPT_EPHSHK_MASK | USB_ENDPT_EPRXEN_MASK | USB_ENDPT_EPTXEN_MASK; - KHCI->ADDR = (KHCI->ADDR & USB_ADDR_LSEN_MASK) | dev_addr; - while (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ; - KHCI->TOKEN = (TOK_PID_SETUP << USB_TOKEN_TOKENPID_SHIFT); - return true; -} - -bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc) -{ - (void)rhport; - uint8_t const ep_addr = ep_desc->bEndpointAddress; - // TU_LOG1("O %u %x\r\n", dev_addr, ep_addr); - /* Find a free pipe */ - pipe_state_t *p = &_hcd.pipe[0]; - pipe_state_t *end = &_hcd.pipe[CFG_TUH_ENDPOINT_MAX * 2]; - if (dev_addr || ep_addr) { - p += 2; - for (; p < end && (p->dev_addr || p->ep_addr); ++p) ; - if (p == end) return false; - } - p->dev_addr = dev_addr; - p->ep_addr = ep_addr; - p->max_packet_size = ep_desc->wMaxPacketSize; - p->xfer = ep_desc->bmAttributes.xfer; - p->data = 0; - if (!ep_addr) { - /* Open one more pipe for Control IN transfer */ - TU_ASSERT(TUSB_XFER_CONTROL == p->xfer); - pipe_state_t *q = p + 1; - TU_ASSERT(!q->dev_addr && !q->ep_addr); - q->dev_addr = dev_addr; - q->ep_addr = tu_edpt_addr(0, TUSB_DIR_IN); - q->max_packet_size = ep_desc->wMaxPacketSize; - q->xfer = ep_desc->bmAttributes.xfer; - q->data = 1; - } - return true; -} - -bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { - (void) rhport; (void) daddr; (void) ep_addr; - return false; // TODO not implemented yet -} - -/* The address of buffer must be aligned to 4 byte boundary. And it must be at least 4 bytes long. - * DMA writes data in 4 byte unit */ -bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) -{ - (void)rhport; - // TU_LOG1("X %u %x %x %d\r\n", dev_addr, ep_addr, (uintptr_t)buffer, buflen); - - int pipenum = find_pipe(dev_addr, ep_addr); - TU_ASSERT(0 <= pipenum); - - TU_ASSERT(0 == (_hcd.in_progress & TU_BIT(pipenum))); - unsigned const ie = NVIC_GetEnableIRQ(USB0_IRQn); - NVIC_DisableIRQ(USB0_IRQn); - pipe_state_t *pipe = &_hcd.pipe[pipenum]; - pipe->buffer = buffer; - pipe->length = buflen; - pipe->remaining = buflen; - _hcd.in_progress |= TU_BIT(pipenum); - _hcd.pending |= TU_BIT(pipenum); /* Send at the next Frame */ - KHCI->INTEN |= USB_ISTAT_SOFTOK_MASK; - if (ie) NVIC_EnableIRQ(USB0_IRQn); - return true; -} - -bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { - (void) rhport; - (void) dev_addr; - (void) ep_addr; - // TODO not implemented yet - return false; -} - -bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { - (void) rhport; - if (!tu_edpt_number(ep_addr)) return true; - int num = find_pipe(dev_addr, ep_addr); - if (num < 0) return false; - pipe_state_t *p = &_hcd.pipe[num]; - p->data = 0; /* Reset data toggle */ - return true; -} - -/*--------------------------------------------------------------------+ - * ISR - *--------------------------------------------------------------------+*/ -void hcd_int_handler(uint8_t rhport, bool in_isr) -{ - (void) in_isr; - uint32_t is = KHCI->ISTAT; - uint32_t msk = KHCI->INTEN; - - // TU_LOG1("S %lx\r\n", is); - - /* clear disabled interrupts */ - KHCI->ISTAT = (is & ~msk & ~USB_ISTAT_TOKDNE_MASK) | USB_ISTAT_SOFTOK_MASK; - is &= msk; - - if (is & USB_ISTAT_ERROR_MASK) { - unsigned err = KHCI->ERRSTAT; - if (err) { - TU_LOG1(" ERR %x\r\n", err); - KHCI->ERRSTAT = err; - } else { - KHCI->INTEN &= ~USB_ISTAT_ERROR_MASK; - } - } - - if (is & USB_ISTAT_USBRST_MASK) { - KHCI->INTEN = (msk & ~USB_INTEN_USBRSTEN_MASK) | USB_INTEN_ATTACHEN_MASK; - process_bus_reset(rhport); - return; - } - if (is & USB_ISTAT_ATTACH_MASK) { - KHCI->INTEN = (msk & ~USB_INTEN_ATTACHEN_MASK) | USB_INTEN_USBRSTEN_MASK; - _hcd.need_reset = true; - process_attach(rhport); - return; - } - if (is & USB_ISTAT_STALL_MASK) { - KHCI->ISTAT = USB_ISTAT_STALL_MASK; - } - if (is & USB_ISTAT_SOFTOK_MASK) { - msk &= ~USB_ISTAT_SOFTOK_MASK; - KHCI->INTEN = msk; - if (_hcd.pending) { - int pipenum = __builtin_ctz(_hcd.pending); - _hcd.pending = 0; - if (!(is & USB_ISTAT_TOKDNE_MASK)) - resume_transfer(pipenum); - } - } - if (is & USB_ISTAT_TOKDNE_MASK) { - process_tokdne(rhport); - } -} - -#endif diff --git a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c index 182710016..b577d0e9f 100644 --- a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c +++ b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c @@ -19,6 +19,12 @@ //--------------------------------------------------------------------+ #define DCD_ENDPOINT_MAX 32 +// The iso machinery (5th DD word + packet-size memory) costs USB RAM on every build; +// compile it only when a class that can open an iso endpoint is enabled. Keep this in +// sync with the classes that actually arm an iso endpoint: audio, video, BTH (voice), +// and vendor (its optional CFG_TUD_VENDOR_EP_ISO_* endpoints, exercised by usbtest). +#define DCD_ISO_ENABLED (CFG_TUD_AUDIO || CFG_TUD_VIDEO || CFG_TUD_VENDOR || CFG_TUD_BTH) + typedef struct TU_ATTR_ALIGNED(4) { //------------- Word 0 -------------// @@ -48,11 +54,37 @@ typedef struct TU_ATTR_ALIGNED(4) volatile uint16_t present_count; // For non-iso : The number of bytes transferred by the DMA engine // For iso : number of packets +#if DCD_ISO_ENABLED //------------- Word 4 -------------// - // uint32_t iso_packet_size_addr; // iso only, can be omitted for non-iso + volatile uint32_t iso_packet_size_addr; // iso only: pointer into iso packet-size memory, + // advanced by hardware after each packet +#endif }dma_desc_t; -TU_VERIFY_STATIC( sizeof(dma_desc_t) == 16, "size is not correct"); // TODO not support ISO for now +TU_VERIFY_STATIC( sizeof(dma_desc_t) == (DCD_ISO_ENABLED ? 20 : 16), "size is not correct"); + +// Hardware fixes endpoint type by number: 3, 6, 9, 12 are the iso-capable ones. +// Constant per ep_id (= 2*epnum + dir) — unlike dd->isochronous, which dcd_edpt_xfer +// transiently zeroes while rebuilding the DD, this is safe to dispatch on from the ISR. +// TU_ATTR_UNUSED: every caller is under #if DCD_ISO_ENABLED, so non-iso builds don't +// reference it and clang -Wunused-function (fatal) would otherwise reject the build. +TU_ATTR_UNUSED TU_ATTR_ALWAYS_INLINE static inline bool ep_id_is_iso(uint8_t ep_id) { + uint8_t const epnum = (uint8_t)(ep_id >> 1); + return (epnum % 3) == 0 && (epnum != 0) && (epnum != 15); +} + +#if DCD_ISO_ENABLED +// Isochronous packet-size memory (UM10562 12.15.6.3): one word per packet. +// IN : software fills Packet_length (bits 15:0), 0 = ZLP +// OUT: hardware writes Frame_number (31:17) | Packet_valid (16) | Packet_length (15:0) +// Iso-capable endpoint numbers are 3, 6, 9, 12 -> 8 slots (x2 directions). +// One packet moves per FRAME, so a deep queue only adds latency: 8 frames is plenty. +#define ISO_MAX_PACKETS 8 +#define ISO_SLOT_COUNT 8 +TU_ATTR_ALWAYS_INLINE static inline uint8_t iso_slot(uint8_t ep_id) { + return (uint8_t)(((ep_id / 6) - 1) * 2 + (ep_id & 1)); // ep_id = 2*epnum + dir, epnum in {3,6,9,12} +} +#endif typedef struct { @@ -66,11 +98,17 @@ typedef struct { uint8_t* out_buffer; uint8_t out_bytes; + volatile bool out_queued; // an OUT xfer is queued; out_buffer may legitimately be NULL (status ZLP) volatile bool out_received; // indicate if data is already received in endpoint uint8_t in_bytes; } control; +#if DCD_ISO_ENABLED + // iso packet-size memory, must be DMA-reachable like the DDs + volatile uint32_t iso_psize[ISO_SLOT_COUNT][ISO_MAX_PACKETS]; +#endif + } dcd_data_t; CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(128) static dcd_data_t _dcd; @@ -79,6 +117,29 @@ CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(128) static dcd_data_t _dcd; //--------------------------------------------------------------------+ // SIE Command //--------------------------------------------------------------------+ + +// The SIE command protocol (CmdCode + CCEMPTY/CDFULL handshake) and the +// slave-mode Ctrl/RxData/TxData registers are shared between thread-mode API +// calls and dcd_int_handler, and are not reentrant: an ISR preempting a +// thread-mode SIE sequence consumes its handshake flags and overwrites +// CmdCode (symptom: EP0 wedges/answers stale data right after SET_INTERFACE +// stall/clear-stall bursts overlapping bulk EOT interrupts). Mask only the +// USB interrupt around those sequences; safe to nest, including from the ISR. +static inline bool usb_irq_lock(void) +{ + bool const enabled = NVIC_GetEnableIRQ(USB_IRQn) != 0; + if (enabled) + { + NVIC_DisableIRQ(USB_IRQn); // CMSIS already ends this with DSB+ISB + } + return enabled; +} + +static inline void usb_irq_unlock(bool enabled) +{ + if (enabled) NVIC_EnableIRQ(USB_IRQn); +} + static void sie_cmd_code (sie_cmdphase_t phase, uint8_t code_data) { LPC_USB->DevIntClr = (DEV_INT_COMMAND_CODE_EMPTY_MASK | DEV_INT_COMMAND_DATA_FULL_MASK); @@ -92,19 +153,28 @@ static void sie_cmd_code (sie_cmdphase_t phase, uint8_t code_data) static void sie_write (uint8_t cmd_code, uint8_t data_len, uint8_t data) { + bool const lock = usb_irq_lock(); + sie_cmd_code(SIE_CMDPHASE_COMMAND, cmd_code); if (data_len) { sie_cmd_code(SIE_CMDPHASE_WRITE, data); } + + usb_irq_unlock(lock); } static uint8_t sie_read (uint8_t cmd_code) { + bool const lock = usb_irq_lock(); + sie_cmd_code(SIE_CMDPHASE_COMMAND , cmd_code); sie_cmd_code(SIE_CMDPHASE_READ , cmd_code); - return (uint8_t) LPC_USB->CmdData; + uint8_t const data = (uint8_t) LPC_USB->CmdData; + + usb_irq_unlock(lock); + return data; } //--------------------------------------------------------------------+ @@ -117,6 +187,11 @@ static inline uint8_t ep_addr2idx(uint8_t ep_addr) static void set_ep_size(uint8_t ep_id, uint16_t max_packet_size) { + // ReEp RMW + the EP_RLZED handshake share DevIntSt with the ISR: a bus reset + // from dcd_int_handler writes DevIntClr = 0xFFFFFFFF and would consume the + // flag this spin waits on, hanging it forever -> same lock as the SIE paths. + bool const lock = usb_irq_lock(); + // follows example in 11.10.4.2 LPC_USB->ReEp |= TU_BIT(ep_id); LPC_USB->EpInd = ep_id; // select index before setting packet size @@ -124,6 +199,8 @@ static void set_ep_size(uint8_t ep_id, uint16_t max_packet_size) while ((LPC_USB->DevIntSt & DEV_INT_ENDPOINT_REALIZED_MASK) == 0) {} LPC_USB->DevIntClr = DEV_INT_ENDPOINT_REALIZED_MASK; + + usb_irq_unlock(lock); } @@ -230,6 +307,7 @@ static inline uint8_t byte2dword(uint8_t bytes) static void control_ep_write(void const * buffer, uint8_t len) { uint32_t const * buf32 = (uint32_t const *) buffer; + bool const lock = usb_irq_lock(); // Ctrl/TxData + SIE sequence must not interleave with the ISR LPC_USB->Ctrl = USBCTRL_WRITE_ENABLE_MASK; // logical endpoint = 0 LPC_USB->TxPLen = (uint32_t) len; @@ -245,10 +323,14 @@ static void control_ep_write(void const * buffer, uint8_t len) // select control IN & validate the endpoint sie_write(SIE_CMDCODE_ENDPOINT_SELECT+1, 0, 0); sie_write(SIE_CMDCODE_BUFFER_VALIDATE , 0, 0); + + usb_irq_unlock(lock); } static uint8_t control_ep_read(void * buffer, uint8_t len) { + bool const lock = usb_irq_lock(); // Ctrl/RxData + SIE sequence must not interleave with the ISR + LPC_USB->Ctrl = USBCTRL_READ_ENABLE_MASK; // logical endpoint = 0 while ((LPC_USB->RxPLen & USBRXPLEN_PACKET_READY_MASK) == 0) {} // TODO blocking, should have timeout @@ -267,6 +349,7 @@ static uint8_t control_ep_read(void * buffer, uint8_t len) sie_write(SIE_CMDCODE_ENDPOINT_SELECT+0, 0, 0); sie_write(SIE_CMDCODE_BUFFER_CLEAR , 0, 0); + usb_irq_unlock(lock); return len; } @@ -281,8 +364,9 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) uint8_t const epnum = tu_edpt_number(p_endpoint_desc->bEndpointAddress); uint8_t const ep_id = ep_addr2idx(p_endpoint_desc->bEndpointAddress); - // Endpoint type is fixed to endpoint number - // 1: interrupt, 2: Bulk, 3: Iso and so on + // Endpoint type is fixed to endpoint number (1 interrupt, 2 bulk, 3 iso, ...). + // Iso endpoints are armed via dcd_edpt_iso_alloc/activate, never through here + // (TUP_DCD_EDPT_ISO_ALLOC is defined for this IP), so only bulk/interrupt land here. switch ( p_endpoint_desc->bmAttributes.xfer ) { case TUSB_XFER_INTERRUPT: @@ -293,10 +377,6 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) TU_ASSERT((epnum % 3) == 2 || (epnum == 15)); break; - case TUSB_XFER_ISOCHRONOUS: - TU_ASSERT((epnum % 3) == 0 && (epnum != 0) && (epnum != 15)); - break; - default: break; } @@ -307,9 +387,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) //------------- first DD prepare -------------// dma_desc_t* const dd = &_dcd.dd[ep_id]; - tu_memclr(dd, sizeof(dma_desc_t)); - - dd->isochronous = (p_endpoint_desc->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) ? 1 : 0; + tu_memclr(dd, sizeof(dma_desc_t)); // non-iso: isochronous stays 0 dd->max_packet_size = ep_size; dd->retired = 1; // invalid at first @@ -319,16 +397,54 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) } bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { +#if DCD_ISO_ENABLED (void)rhport; - (void)ep_addr; - (void)largest_packet_size; + uint8_t const ep_id = ep_addr2idx(ep_addr); + + // hardware fixes iso to endpoint numbers 3, 6, 9, 12 + TU_ASSERT(ep_id_is_iso(ep_id)); + TU_ASSERT(largest_packet_size > 0); + + set_ep_size(ep_id, largest_packet_size); + + dma_desc_t* const dd = &_dcd.dd[ep_id]; + tu_memclr(dd, sizeof(dma_desc_t)); + dd->isochronous = 1; + dd->max_packet_size = largest_packet_size; + dd->retired = 1; // invalid at first + + sie_write(SIE_CMDCODE_ENDPOINT_SET_STATUS + ep_id, 1, 0); + return true; +#else + (void)rhport; (void)ep_addr; (void)largest_packet_size; return false; +#endif } bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { +#if DCD_ISO_ENABLED (void)rhport; - (void)desc_ep; + uint8_t const ep_id = ep_addr2idx(desc_ep->bEndpointAddress); + dma_desc_t* const dd = &_dcd.dd[ep_id]; + + // same fixed-number rule as alloc: without it a rejected-but-ignored alloc (classes + // discard that return) would set isochronous on a non-iso ep_id and underflow iso_slot() + TU_ASSERT(ep_id_is_iso(ep_id)); + + // kill any armed transfer from a previous alternate setting + LPC_USB->EpDMADis = TU_BIT(ep_id); + _dcd.udca[ep_id] = NULL; + + dd->isochronous = 1; + dd->max_packet_size = tu_edpt_packet_size(desc_ep); + dd->retired = 1; + + sie_write(SIE_CMDCODE_ENDPOINT_SET_STATUS + ep_id, 1, 0); + return true; +#else + (void)rhport; (void)desc_ep; return false; +#endif } void dcd_edpt_close_all (uint8_t rhport) @@ -369,19 +485,28 @@ static bool control_xact(uint8_t rhport, uint8_t dir, uint8_t * buffer, uint8_t control_ep_write(buffer, len); }else { + // guard the out_received/out_buffer handshake against the EP0 OUT ISR + bool const lock = usb_irq_lock(); + if ( _dcd.control.out_received ) { // Already received the DATA OUT packet _dcd.control.out_received = false; - _dcd.control.out_buffer = NULL; - _dcd.control.out_bytes = 0; uint8_t received = control_ep_read(buffer, len); + // event queued with in_isr=true, which skips the queue's own locking: keep the + // USB IRQ masked across it, or a real ISR completion could interleave the write dcd_event_xfer_complete(0, 0, received, XFER_RESULT_SUCCESS, true); + usb_irq_unlock(lock); }else { + // buffer is NULL for a status-stage ZLP: signal the pending xfer explicitly, + // NOT via out_buffer != NULL — a NULL-buffer queue mistaken for "nothing queued" + // leaves out_received stale and poisons the next control OUT data stage. _dcd.control.out_buffer = buffer; _dcd.control.out_bytes = len; + _dcd.control.out_queued = true; + usb_irq_unlock(lock); } } @@ -406,26 +531,68 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t t uint16_t const ep_size = dd->max_packet_size; uint8_t is_iso = dd->isochronous; - tu_memclr(dd, sizeof(dma_desc_t)); - dd->isochronous = is_iso; - dd->max_packet_size = ep_size; - dd->buffer = (uint32_t) buffer; - dd->buflen = total_bytes; +#if DCD_ISO_ENABLED + if ( is_iso ) + { + // iso: buflen counts packets; per-packet sizes live in the packet-size memory. + // One packet moves per frame (UM10562 12.15.6: DMA request is raised for + // DMA-enabled iso endpoints on every FRAME interrupt, both directions). + // Validate BEFORE touching the DD: bailing out mid-rebuild would leave a + // zeroed (retired=0 -> serviceable) descriptor armed for the frame engine. + TU_ASSERT(ep_size > 0); + uint16_t const packets = (total_bytes > 0) ? (uint16_t) tu_div_ceil(total_bytes, ep_size) : 1; + TU_ASSERT(packets <= ISO_MAX_PACKETS); + + uint8_t const slot = iso_slot(ep_id); + uint16_t remain = total_bytes; + for ( uint16_t i = 0; i < packets; i++ ) + { + uint16_t const pkt_len = tu_min16(remain, ep_size); + // IN: length to send (0 = ZLP). OUT: hardware writes back + // Frame_number|Packet_valid|Packet_length -- prefill 0 so a frame the + // hardware never wrote (missed/invalid) cannot read back as data. + _dcd.iso_psize[slot][i] = (ep_id & 1) ? pkt_len : 0; + remain = (uint16_t)(remain - pkt_len); + } - _dcd.udca[ep_id] = dd; + tu_memclr(dd, sizeof(dma_desc_t)); + dd->isochronous = 1; + dd->max_packet_size = ep_size; + dd->buffer = (uint32_t) buffer; + dd->buflen = packets; + dd->iso_packet_size_addr = (uint32_t) &_dcd.iso_psize[slot][0]; - if ( ep_id % 2 ) + _dcd.udca[ep_id] = dd; + LPC_USB->EpDMAEn = TU_BIT(ep_id); // frame-triggered: no DMARSet, no EpIntEn + } + else +#else + (void) is_iso; +#endif { - // Clear EP interrupt before Enable DMA - LPC_USB->EpIntEn &= ~TU_BIT(ep_id); - LPC_USB->EpDMAEn = TU_BIT(ep_id); + tu_memclr(dd, sizeof(dma_desc_t)); + dd->max_packet_size = ep_size; + dd->buffer = (uint32_t) buffer; + dd->buflen = total_bytes; - // endpoint IN need to actively raise DMA request - LPC_USB->DMARSet = TU_BIT(ep_id); - }else - { - // Enable DMA - LPC_USB->EpDMAEn = TU_BIT(ep_id); + _dcd.udca[ep_id] = dd; + + if ( ep_id % 2 ) + { + // Clear EP interrupt before Enable DMA + // EpIntEn read-modify-write races the ISR's own RMWs -> lock + bool const lock = usb_irq_lock(); + LPC_USB->EpIntEn &= ~TU_BIT(ep_id); + LPC_USB->EpDMAEn = TU_BIT(ep_id); + usb_irq_unlock(lock); + + // endpoint IN need to actively raise DMA request + LPC_USB->DMARSet = TU_BIT(ep_id); + }else + { + // Enable DMA + LPC_USB->EpDMAEn = TU_BIT(ep_id); + } } return true; @@ -451,13 +618,20 @@ static void control_xfer_isr(uint8_t rhport, uint32_t ep_int_status) uint8_t setup_packet[8]; control_ep_read(setup_packet, 8); // TODO read before clear setup above + // a new SETUP voids any half-finished control state + _dcd.control.out_queued = false; + _dcd.control.out_received = false; + _dcd.control.out_buffer = NULL; + _dcd.control.out_bytes = 0; + dcd_event_setup_received(rhport, setup_packet, true); } - else if ( _dcd.control.out_buffer ) + else if ( _dcd.control.out_queued ) { - // software queued transfer previously + // software queued transfer previously (out_buffer NULL = status ZLP) uint8_t received = control_ep_read(_dcd.control.out_buffer, _dcd.control.out_bytes); + _dcd.control.out_queued = false; _dcd.control.out_buffer = NULL; _dcd.control.out_bytes = 0; @@ -513,7 +687,32 @@ static void dd_complete_isr(uint8_t rhport, uint8_t ep_id) uint8_t result = (dd->status == DD_STATUS_NORMAL || dd->status == DD_STATUS_DATA_UNDERUN) ? XFER_RESULT_SUCCESS : XFER_RESULT_FAILED; uint8_t const ep_addr = (ep_id / 2) | ((ep_id & 0x01) ? TUSB_DIR_IN_MASK : 0); - dcd_event_xfer_complete(rhport, ep_addr, dd->present_count, result, true); + uint32_t xferred_bytes; +#if DCD_ISO_ENABLED + if ( ep_id_is_iso(ep_id) ) + { + // present_count is in packets; actual byte counts are in the packet-size memory + // (IN: as programmed by us, OUT: Packet_length written back by hardware, + // guarded by Packet_valid -- a frame with no packet must count as 0) + uint8_t const slot = iso_slot(ep_id); + uint16_t const packets = tu_min16(dd->present_count, ISO_MAX_PACKETS); + xferred_bytes = 0; + for (uint16_t i = 0; i < packets; i++) + { + uint32_t const psize = _dcd.iso_psize[slot][i]; + if ( (ep_id & 1) || (psize & TU_BIT(16)) ) + { + xferred_bytes += (psize & 0xFFFFu); + } + } + } + else +#endif + { + xferred_bytes = dd->present_count; + } + + dcd_event_xfer_complete(rhport, ep_addr, (uint16_t) xferred_bytes, result, true); } // main USB IRQ handler @@ -569,6 +768,16 @@ void dcd_int_handler(uint8_t rhport) { if ( tu_bit_test(eot, ep_id) ) { + // dispatch on the hardware's fixed ep-number/type map, NOT dd->isochronous: + // thread-mode dcd_edpt_xfer transiently zeroes the DD while rebuilding it +#if DCD_ISO_ENABLED + if ( ep_id_is_iso(ep_id) ) + { + // iso: last packet already left with its frame; complete both directions here + dd_complete_isr(rhport, ep_id); + } + else +#endif if ( ep_id & 0x01 ) { // IN enable EpInt for end of usb transfer diff --git a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c index aa0307d25..42f6750b1 100644 --- a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c +++ b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c @@ -87,6 +87,10 @@ enum { DEVCMDSTAT_SUSPEND_CHANGE_MASK = TU_BIT(25), DEVCMDSTAT_RESET_CHANGE_MASK = TU_BIT(26), DEVCMDSTAT_VBUS_DEBOUNCED_MASK = TU_BIT(28), + + // write-1-to-clear latches + DEVCMDSTAT_W1C_MASK = DEVCMDSTAT_SETUP_RECEIVED_MASK | DEVCMDSTAT_CONNECT_CHANGE_MASK | + DEVCMDSTAT_SUSPEND_CHANGE_MASK | DEVCMDSTAT_RESET_CHANGE_MASK, }; enum { @@ -158,6 +162,10 @@ typedef struct // - 55 usb0 (FS) has 5x2 endpoints, usb1 (HS) has 6x2 endpoints #define MAX_EP_PAIRS 6 +// Bounded spin waiting for hardware to clear an EPSKIP bit when retiring a still-armed endpoint on +// reopen (dcd_edpt_open). Hardware clears it within a (micro)frame; the guard only avoids a hang. +#define IP3511_EPSKIP_SPIN 100000u + // NOTE data will be transferred as soon as dcd get request by dcd_pipe(_queue)_xfer using double buffering. // current_td is used to keep track of number of remaining & xferred bytes of the current request. typedef struct @@ -167,7 +175,9 @@ typedef struct ep_cmd_sts_t ep[2*MAX_EP_PAIRS][2]; xfer_dma_t dma[2*MAX_EP_PAIRS]; - TU_ATTR_ALIGNED(64) uint8_t setup_packet[8]; + // volatile: the controller DMAs a new setup packet into this buffer as soon as the SETUP + // latch is cleared, so reads of it must stay ordered against the register accesses around them + TU_ATTR_ALIGNED(64) volatile uint8_t setup_packet[8]; }dcd_data_t; // EP list must be 256-byte aligned @@ -176,8 +186,12 @@ typedef struct // Use CFG_TUD_MEM_SECTION to place it accordingly. CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(256) static dcd_data_t _dcd; -// Dummy buffer to fix ZLPs overwriting the buffer (probably an USB/DMA controller bug) -// TODO find way to save memory +// Dummy buffer to fix ZLPs overwriting the buffer: Errata LPC55S6x USB.5 / LPC55S2x USB.4 - the +// HS device controller always DMA-writes OUT data in 8-byte units, so up to 7 bytes land past the +// received length. This redirects the ZLP case; the general short-OUT case is unhandled here +// (TinyUSB's own endpoint buffers are sized/aligned so the spill stays inside them, but a tight +// caller buffer can be overrun by up to 7 bytes - the SDK's documented workaround is a bounce +// buffer). TODO find way to save memory CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(64) static uint8_t dummy[8]; //--------------------------------------------------------------------+ @@ -217,7 +231,7 @@ static const dcd_controller_t _dcd_controller[] = { // INTERNAL OBJECT & FUNCTION DECLARATION //--------------------------------------------------------------------+ -TU_ATTR_ALWAYS_INLINE static inline uint16_t get_buf_offset(void const * buffer) { +TU_ATTR_ALWAYS_INLINE static inline uint16_t get_buf_offset(void const volatile * buffer) { uint32_t addr = (uint32_t) buffer; TU_ASSERT( (addr & 0x3f) == 0, 0 ); return ( (addr >> 6) & 0xFFFFUL ) ; @@ -243,6 +257,16 @@ TU_ATTR_ALWAYS_INLINE static inline bool rhport_is_highspeed(uint8_t rhport) { return _dcd_controller[rhport].is_highspeed; } + +// DEVCMDSTAT mixes RW fields with write-1-to-clear latches (SETUP + the 3 change bits): a blind +// RMW writes a pending latch back as 1 and silently clears it (a SETUP eaten this way strands +// EP0). Mask the latches on every update; pass one in set_mask only to clear it. +TU_ATTR_ALWAYS_INLINE static inline void devcmdstat_update(dcd_registers_t* dcd_reg, + uint32_t clear_mask, uint32_t set_mask) { + const uint32_t v = dcd_reg->DEVCMDSTAT & ~(DEVCMDSTAT_W1C_MASK | clear_mask); + dcd_reg->DEVCMDSTAT = v | set_mask; +} + //--------------------------------------------------------------------+ // CONTROLLER API //--------------------------------------------------------------------+ @@ -280,8 +304,10 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { dcd_reg->DATABUFSTART = tu_align((uint32_t) &_dcd, TU_BIT(22)); // 22-bit alignment dcd_reg->INTSTAT = dcd_reg->INTSTAT; // clear all pending interrupt dcd_reg->INTEN = INT_DEVICE_STATUS_MASK; - dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_DEVICE_ENABLE_MASK | DEVCMDSTAT_DEVICE_CONNECT_MASK | - DEVCMDSTAT_RESET_CHANGE_MASK | DEVCMDSTAT_CONNECT_CHANGE_MASK | DEVCMDSTAT_SUSPEND_CHANGE_MASK; + // deliberately clear every latch (incl. a SETUP left by a bootloader/warm start) for a + // deterministic init state + devcmdstat_update(dcd_reg, 0, DEVCMDSTAT_DEVICE_ENABLE_MASK | DEVCMDSTAT_DEVICE_CONNECT_MASK | + DEVCMDSTAT_W1C_MASK); NVIC_ClearPendingIRQ(_dcd_controller[rhport].irqnum); @@ -305,8 +331,7 @@ void dcd_set_address(uint8_t rhport, uint8_t dev_addr) // Response with status first before changing device address dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0, false); - dcd_reg->DEVCMDSTAT &= ~DEVCMDSTAT_DEVICE_ADDR_MASK; - dcd_reg->DEVCMDSTAT |= dev_addr; + devcmdstat_update(dcd_reg, DEVCMDSTAT_DEVICE_ADDR_MASK, dev_addr); } void dcd_remote_wakeup(uint8_t rhport) @@ -317,13 +342,13 @@ void dcd_remote_wakeup(uint8_t rhport) void dcd_connect(uint8_t rhport) { dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; - dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_DEVICE_CONNECT_MASK; + devcmdstat_update(dcd_reg, 0, DEVCMDSTAT_DEVICE_CONNECT_MASK); } void dcd_disconnect(uint8_t rhport) { dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; - dcd_reg->DEVCMDSTAT &= ~DEVCMDSTAT_DEVICE_CONNECT_MASK; + devcmdstat_update(dcd_reg, DEVCMDSTAT_DEVICE_CONNECT_MASK, 0); } void dcd_sof_enable(uint8_t rhport, bool en) @@ -337,13 +362,37 @@ void dcd_sof_enable(uint8_t rhport, bool en) //--------------------------------------------------------------------+ // DCD Endpoint Port //--------------------------------------------------------------------+ +// Retire a still-armed (Active) endpoint before reconfiguring it (reopen across SET_INTERFACE). +// UM11126 §41.7.6/§41.8.3: write EPSKIP and wait for hardware to clear the bit, then Active is +// safe to clear. EPSKIP raises the endpoint interrupt as it clears Active, delivered as a +// (partial) transfer completion. Here that is sanctioned — usbd_edpt_close() documents "in +// progress transfers may be delivered after this call", and that completion is what clears the +// stale usbd busy flag (ISO_ALLOC close is a no-op) so the class can re-arm the reopened +// endpoint. NOT for the stall/iso-activate paths: there the class re-arms from the completion +// callback and the endpoint ends up Active+Stall, which never sends a STALL handshake (usbtest +// case 13 regression on LPC11u37) — those paths must clear Active directly instead. +// Bounded: hardware clears EPSKIP within a (micro)frame. +static void edpt_skip_active(uint8_t rhport, uint8_t ep_id) { + ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id); + if ( ep_cs[0].cmd_sts.active || ep_cs[1].cmd_sts.active ) { + dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; + dcd_reg->EPSKIP |= TU_BIT(ep_id); + uint32_t guard = IP3511_EPSKIP_SPIN; + while ( (dcd_reg->EPSKIP & TU_BIT(ep_id)) && guard-- ) {} + } + ep_cs[0].cmd_sts.active = ep_cs[1].cmd_sts.active = 0; +} + void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { (void) rhport; - // TODO cannot able to STALL Control OUT endpoint !!!!! FIXME try some walk-around uint8_t const ep_id = ep_addr2id(ep_addr); - _dcd.ep[ep_id][0].cmd_sts.stall = 1; + // Clear Active directly before setting Stall (no EPSKIP — see edpt_skip_active): the hardware + // services an armed buffer instead of returning STALL, so a halt requested while a transfer is + // queued would not actually stall the endpoint (usbtest case 13). + _dcd.ep[ep_id][0].cmd_sts.active = 0; + _dcd.ep[ep_id][0].cmd_sts.stall = 1; } void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) @@ -352,9 +401,17 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) uint8_t const ep_id = ep_addr2id(ep_addr); + // Preserve rf_tv: for non-control endpoints it is a TYPE bit, not the toggle value (UM11126: + // T=1 + RF 1/0 = interrupt/iso). Zeroing it here turned HS periodic interrupt endpoints into + // isochronous - no handshake on OUT, dead IN (usbtest cases 25/26 on lpc55 HS port). + // TODO implement the Errata LPC546xx USB.13 work-around (same semantics in UM11126): with RF/TV preserved at 1, TR + // loads the toggle from TV, so an HS interrupt endpoint restarts on DATA1 after clear-halt and + // the host discards one packet as a retransmission. The documented workaround needs an + // interrupt-on-NAK state machine (park as generic TR=1/TV=0, wait for a NAKed token to latch + // toggle 0 via EPTOGGLE, restore the type) - deferred; one lost packet beats the fully broken + // endpoint the old rf_tv clear caused. _dcd.ep[ep_id][0].cmd_sts.stall = 0; _dcd.ep[ep_id][0].cmd_sts.toggle_reset = 1; - _dcd.ep[ep_id][0].cmd_sts.rf_tv = 0; } bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) @@ -362,9 +419,15 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) //------------- Prepare Queue Head -------------// uint8_t ep_id = ep_addr2id(p_endpoint_desc->bEndpointAddress); ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id); + dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; - // Check if endpoint is available - TU_ASSERT( ep_cs[0].cmd_sts.disable && ep_cs[1].cmd_sts.disable ); + // usbd_edpt_close() is a no-op on ISO_ALLOC ports, so an endpoint a class closed then reopened + // across SET_INTERFACE (e.g. the video notification or audio streaming endpoint) is still armed + // here rather than disabled. Retire it (edpt_skip_active) before reconfiguring. + if ( !(ep_cs[0].cmd_sts.disable && ep_cs[1].cmd_sts.disable) ) { + edpt_skip_active(rhport, ep_id); + ep_cs[0].cmd_sts.disable = ep_cs[1].cmd_sts.disable = 1; + } edpt_reset(rhport, ep_id); @@ -389,7 +452,6 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) } // Enable EP interrupt - dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; dcd_reg->INTEN |= TU_BIT(ep_id); return true; @@ -399,34 +461,40 @@ void dcd_edpt_close_all (uint8_t rhport) { for (uint8_t ep_id = 0; ep_id < 2*_dcd_controller[rhport].ep_pairs; ++ep_id) { - _dcd.ep[ep_id][0].cmd_sts.active = _dcd.ep[ep_id][0].cmd_sts.active = 0; // TODO proper way is to EPSKIP then wait ep[][].active then write ep[][].disable (see table 778 in LPC55S69 Use Manual) + _dcd.ep[ep_id][0].cmd_sts.active = _dcd.ep[ep_id][1].cmd_sts.active = 0; // TODO proper way is to EPSKIP then wait ep[][].active then write ep[][].disable (see table 778 in LPC55S69 Use Manual) _dcd.ep[ep_id][0].cmd_sts.disable = _dcd.ep[ep_id][1].cmd_sts.disable = 1; } } -void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - +bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { + (void) largest_packet_size; + // Reserve the endpoint command/status entry once (persists across altsetting changes); the + // buffer pointer is filled per-transfer, so nothing to pre-allocate. Mirrors the ISO branch of + // dcd_edpt_open(). uint8_t ep_id = ep_addr2id(ep_addr); - _dcd.ep[ep_id][0].cmd_sts.active = _dcd.ep[ep_id][0].cmd_sts.active = 0; // TODO proper way is to EPSKIP then wait ep[][].active then write ep[][].disable (see table 778 in LPC55S69 Use Manual) - _dcd.ep[ep_id][0].cmd_sts.disable = _dcd.ep[ep_id][1].cmd_sts.disable = 1; -} + ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id); + TU_ASSERT( ep_cs[0].cmd_sts.disable && ep_cs[1].cmd_sts.disable ); -#if 0 -bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { - (void)rhport; - (void)ep_addr; - (void)largest_packet_size; - return false; + edpt_reset(rhport, ep_id); + ep_cs[0].cmd_sts.type = 1; // ISO + + dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; + dcd_reg->INTEN |= TU_BIT(ep_id); + return true; } bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { - (void)rhport; - (void)desc_ep; - return false; + // (Re)activate on altsetting selection: abort a transfer still armed from the previous + // altsetting (the hardware keeps servicing an Active buffer across SET_INTERFACE, fighting the + // fresh transfer the class queues), clear stall and reset the data toggle. Direct Active=0, not + // EPSKIP (see edpt_skip_active). The class re-arms via dcd_edpt_xfer(). + uint8_t ep_id = ep_addr2id(desc_ep->bEndpointAddress); + ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id); + ep_cs[0].cmd_sts.active = 0; + ep_cs[1].cmd_sts.active = 0; + dcd_edpt_clear_stall(rhport, desc_ep->bEndpointAddress); + return true; } -#endif static void prepare_ep_xfer(uint8_t rhport, uint8_t ep_id, uint16_t buf_offset, uint16_t total_bytes) { uint16_t nbytes; @@ -499,7 +567,7 @@ static void bus_reset(uint8_t rhport) dcd_reg->EPSKIP = 0xFFFFFFFF; dcd_reg->INTSTAT = dcd_reg->INTSTAT; // clear all pending interrupt - dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_SETUP_RECEIVED_MASK; // clear setup received interrupt + devcmdstat_update(dcd_reg, 0, DEVCMDSTAT_SETUP_RECEIVED_MASK); // clear setup received interrupt dcd_reg->INTEN = INT_DEVICE_STATUS_MASK | TU_BIT(0) | TU_BIT(1); // enable device status & control endpoints } @@ -558,18 +626,25 @@ void dcd_int_handler(uint8_t rhport) { dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; - uint32_t const cmd_stat = dcd_reg->DEVCMDSTAT; - uint32_t int_status = dcd_reg->INTSTAT; - int_status &= dcd_reg->INTEN; + int_status &= dcd_reg->INTEN; dcd_reg->INTSTAT = int_status; // Acknowledge handled interrupt if (int_status == 0) return; + // Snapshot after the INTSTAT ack: latch bits persist (RWC) so nothing is lost, while the reverse + // order could consume INTSTAT bit0 for a SETUP not yet visible in the snapshot - stranding the + // SETUP (INTSTAT is edge-latched) and feeding bit0 to process_xfer_isr as a bogus completion. + uint32_t const cmd_stat = dcd_reg->DEVCMDSTAT; + //------------- Device Status -------------// if ( int_status & INT_DEVICE_STATUS_MASK ) { - dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_RESET_CHANGE_MASK | DEVCMDSTAT_CONNECT_CHANGE_MASK | DEVCMDSTAT_SUSPEND_CHANGE_MASK; + // clear only the change latches observed in the snapshot: one latched by hardware between the + // snapshot and this write would be acknowledged unseen (its DEV_INT re-latches and dispatches + // next pass instead) + devcmdstat_update(dcd_reg, 0, cmd_stat & + (DEVCMDSTAT_RESET_CHANGE_MASK | DEVCMDSTAT_CONNECT_CHANGE_MASK | DEVCMDSTAT_SUSPEND_CHANGE_MASK)); if ( cmd_stat & DEVCMDSTAT_RESET_CHANGE_MASK) // bus reset { @@ -614,15 +689,43 @@ void dcd_int_handler(uint8_t rhport) _dcd.ep[0][0].cmd_sts.active = _dcd.ep[1][0].cmd_sts.active = 0; _dcd.ep[0][0].cmd_sts.stall = _dcd.ep[1][0].cmd_sts.stall = 0; - dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_SETUP_RECEIVED_MASK; + // UM flow: ack the latch FIRST, then read the payload. This IP has no setup lockout, so a + // back-to-back SETUP can overwrite _dcd.setup_packet at any time - but with the latch already + // released, any such overwrite re-latches SETUP_RECEIVED and is redelivered (worst case a + // superseded duplicate, absorbed by usbd's queued-setup counter). The reverse order can + // consume the newer SETUP's latch unseen and lose it. + devcmdstat_update(dcd_reg, 0, DEVCMDSTAT_SETUP_RECEIVED_MASK); - dcd_event_setup_received(rhport, _dcd.setup_packet, true); + // UM11126 Fig 163 (control EP0 flowchart) requires clearing the EP0IN interrupt here: a + // control IN completion latched before this SETUP must not reach usbd after it, where it + // would be applied to the new request and arm its status stage early. EP0OUT goes with it - + // bit0 is set by SETUP reception too, and left set it would replay next pass as a phantom + // completion. Neither can discard live work: the SETUP latch NAKs all EP0 traffic until the + // update above, and both EP0 Active bits were cleared a few lines up. + dcd_reg->INTSTAT = TU_BIT(0) | TU_BIT(1); + + // Copied a byte at a time rather than with memcpy: C orders volatile accesses only against + // each other, so a non-volatile copy of this buffer may be sunk below the guard read that + // follows - gcc does exactly that at -O2 and -O3, leaving only -Os correct. + uint8_t setup_copy[8]; + for (uint8_t i = 0; i < sizeof(setup_copy); i++) { + setup_copy[i] = _dcd.setup_packet[i]; + } + + // a SETUP that raced in after the acks (its bit0 consumed above) makes this copy suspect: + // its latch is visible again, so re-raise the endpoint interrupt and let the next pass + // deliver the newer payload rather than passing up bytes that may be torn between the two + if (dcd_reg->DEVCMDSTAT & DEVCMDSTAT_SETUP_RECEIVED_MASK) { + dcd_reg->INTSETSTAT = TU_BIT(0); + } else { + dcd_event_setup_received(rhport, setup_copy, true); + } // keep waiting for next setup prepare_setup_packet(rhport); - // clear bit0 - int_status = tu_bit_clear(int_status, 0); + // drop both EP0 bits: acked above, and neither belongs to the request this SETUP starts + int_status &= ~(TU_BIT(0) | TU_BIT(1)); } // Endpoint transfer complete interrupt |
