/* * SPDX-FileCopyrightText: Copyright (c) 2025 HiFiPhile (Zixun LI) * SPDX-FileCopyrightText: Copyright (c) 2025 Ha Thach (tinyusb.org) * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. */ /********************************************** * This driver provides USB Host controller support for STM32 MCUs with "USB A"/"PCD"/"HCD" peripheral. * This covers these MCU families: * * C0 2048 byte buffer; 32-bit bus; host mode * G0 2048 byte buffer; 32-bit bus; host mode * U3 2048 byte buffer; 32-bit bus; host mode * H5 2048 byte buffer; 32-bit bus; host mode * U535, U545 2048 byte buffer; 32-bit bus; host mode * */ #include "tusb_option.h" #if CFG_TUH_ENABLED && defined(TUP_USBIP_FSDEV) && defined(TUP_USBIP_FSDEV_DRD) #include "host/hcd.h" #include "host/usbh.h" #include "fsdev_common.h" //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ // Debug level for FSDEV #define FSDEV_DEBUG 3 // Max number of endpoints application can open, can be larger than FSDEV_EP_COUNT #ifndef CFG_TUH_FSDEV_ENDPOINT_MAX #define CFG_TUH_FSDEV_ENDPOINT_MAX 16u #endif TU_VERIFY_STATIC(CFG_TUH_FSDEV_ENDPOINT_MAX <= 255, "currently only use 8-bit for index"); enum { HCD_XFER_ERROR_MAX = 3, HCD_XFER_NAK_MAX = 15, HCD_XFER_NAK_DEFAULT = 3, }; // Host driver struct for each opened endpoint typedef struct { uint8_t *buffer; uint16_t buflen; uint16_t queued_len; uint16_t max_packet_size; uint8_t dev_addr; uint8_t ep_addr; uint8_t ep_type; uint8_t interval; struct TU_ATTR_PACKED { uint8_t ls_pre : 1; uint8_t allocated : 1; uint8_t next_setup : 1; uint8_t pid : 1; }; } hcd_endpoint_t; // Channel direction state typedef struct { hcd_endpoint_t* edpt; struct TU_ATTR_PACKED { uint8_t allocated : 1; uint8_t retry : 3; uint8_t nak : 4; // Max NAK count in current frame }; } hcd_channel_dir_t; // Additional info for each channel when it is active typedef struct { uint8_t dev_addr; uint8_t ep_num; uint8_t ep_type; hcd_channel_dir_t out, in; } hcd_channel_t; static struct { hcd_channel_t channel[FSDEV_EP_COUNT]; hcd_endpoint_t edpt[CFG_TUH_FSDEV_ENDPOINT_MAX]; bool connected; } _hcd_data; static tuh_configure_fsdev_t _tuh_cfg = { .max_nak = HCD_XFER_NAK_DEFAULT, }; //--------------------------------------------------------------------+ // Prototypes //--------------------------------------------------------------------+ static uint8_t endpoint_alloc(void); static uint8_t endpoint_find(uint8_t dev_addr, uint8_t ep_addr); static uint32_t hcd_pma_alloc(uint8_t channel, tusb_dir_t dir, uint16_t len); static uint8_t channel_alloc(uint8_t dev_addr, uint8_t ep_addr, uint8_t ep_type); static bool edpt_xfer_kickoff(uint8_t ep_id); static bool channel_xfer_start(uint8_t ch_id, tusb_dir_t dir); static void edpoint_close(uint8_t ep_id); static void port_status_handler(uint8_t rhport, bool in_isr); static void ch_handle_ack(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir); static void ch_handle_nak(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir); static void ch_handle_stall(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir); static void ch_handle_error(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir); //--------------------------------------------------------------------+ // Inline Functions //--------------------------------------------------------------------+ static inline void endpoint_dealloc(hcd_endpoint_t* edpt) { edpt->allocated = 0; } static inline void channel_dealloc(hcd_channel_t* ch, tusb_dir_t dir) { if (dir == TUSB_DIR_OUT) { ch->out.allocated = 0; } else { ch->in.allocated = 0; } } // Write channel state in specified direction static inline void channel_write_status(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir, ep_stat_t state, bool need_exclusive) { ch_reg &= U_EPREG_MASK | CH_STAT_MASK(dir); ch_change_status(&ch_reg, dir, state); ch_write(ch_id, ch_reg, need_exclusive); } static inline uint16_t channel_get_rx_count(uint8_t ch_id) { uint32_t ch_reg = ch_read(ch_id); const bool is_low_speed = (FSDEV_REG->ISTR & U_ISTR_LS_DCONN) || (ch_reg & U_EP_LSEP); fsdev_btable_workaround_delay(is_low_speed); return btable_get_count(ch_id, BTABLE_BUF_RX); } //--------------------------------------------------------------------+ // Controller API //--------------------------------------------------------------------+ // Optional HCD configuration, called by tuh_configure() bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) { (void) rhport; TU_VERIFY(cfg_id == TUH_CFGID_FSDEV && cfg_param != NULL); tuh_configure_param_t const* cfg = (tuh_configure_param_t const*) cfg_param; _tuh_cfg.max_nak = tu_min8(cfg->fsdev.max_nak, HCD_XFER_NAK_MAX); return true; } // Initialize controller to host mode bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { (void) rh_init; fsdev_core_reset(); FSDEV_REG->CNTR = U_CNTR_HOST; // Enable USB in Host mode 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 |= U_CNTR_DCON | U_CNTR_CTRM | U_CNTR_SOFM | U_CNTR_ERRM | U_CNTR_PMAOVRM; // Initialize port state _hcd_data.connected = false; fsdev_connect(rhport); // If DCON_STAT is already set, the controller sometimes misses the initial connection interrupt if (FSDEV_REG->ISTR & U_ISTR_DCON_STAT) { tusb_time_delay_ms_api(2); port_status_handler(rhport, false); } return true; } bool hcd_deinit(uint8_t rhport) { (void)rhport; fsdev_disconnect(rhport); fsdev_deinit(); return true; } //--------------------------------------------------------------------+ // Interrupt Helper Functions //--------------------------------------------------------------------+ static inline void sof_handler(void) { // Reset NAK counters for all active channels for (uint8_t ch_id = 0; ch_id < FSDEV_EP_COUNT; ch_id++) { hcd_channel_t* channel = &_hcd_data.channel[ch_id]; if (channel->out.allocated) { channel->out.nak = 0; } if (channel->in.allocated) { channel->in.nak = 0; } } } static void port_status_handler(uint8_t rhport, bool in_isr) { uint32_t const fnr_reg = FSDEV_REG->FNR; uint32_t const istr_reg = FSDEV_REG->ISTR; // SE0 detected USB Disconnected state if ((fnr_reg & (U_FNR_RXDP | U_FNR_RXDM)) == 0U) { _hcd_data.connected = false; hcd_event_device_remove(rhport, in_isr); return; } if (!_hcd_data.connected) { // J-state or K-state detected & LastState=Disconnected if (((fnr_reg & U_FNR_RXDP) != 0U) || ((istr_reg & U_ISTR_LS_DCONN) != 0U)) { _hcd_data.connected = true; hcd_event_device_attach(rhport, in_isr); } } else { // J-state or K-state detected & lastState=Connected: a Missed disconnection is detected if (((fnr_reg & U_FNR_RXDP) != 0U) || ((istr_reg & U_ISTR_LS_DCONN) != 0U)) { _hcd_data.connected = false; hcd_event_device_remove(rhport, in_isr); } } } // Handle ACK response static void ch_handle_ack(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir) { uint8_t const ep_num = ch_reg & U_EPADDR_FIELD; uint8_t const daddr = (ch_reg & U_EP_DEVADDR) >> U_EP_DEVADDR_Pos; uint8_t ep_id = endpoint_find(daddr, ep_num | (dir == TUSB_DIR_IN ? TUSB_DIR_IN_MASK : 0)); if (ep_id == TUSB_INDEX_INVALID_8) { return; } hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id]; hcd_channel_t *channel = &_hcd_data.channel[ch_id]; if (dir == TUSB_DIR_OUT) { // OUT/TX direction if (edpt->buflen != edpt->queued_len) { // More data to send uint16_t const len = tu_min16(edpt->buflen - edpt->queued_len, edpt->max_packet_size); uint16_t pma_addr = (uint16_t) btable_get_addr(ch_id, BTABLE_BUF_TX); tu_hwfifo_write(PMA_BUF_AT(pma_addr), &(edpt->buffer[edpt->queued_len]), len, NULL); btable_set_count(ch_id, BTABLE_BUF_TX, len); edpt->queued_len += len; channel_write_status(ch_id, ch_reg, TUSB_DIR_OUT, EP_STAT_VALID, false); channel->out.nak = 0; } else { // Transfer complete channel_dealloc(channel, TUSB_DIR_OUT); edpt->pid = (ch_reg & U_EP_DTOG_TX) ? 1 : 0; hcd_event_xfer_complete(daddr, ep_num, edpt->queued_len, XFER_RESULT_SUCCESS, true); } } else { // IN/RX direction uint16_t const rx_count = channel_get_rx_count(ch_id); uint16_t pma_addr = (uint16_t) btable_get_addr(ch_id, BTABLE_BUF_RX); tu_hwfifo_read(PMA_BUF_AT(pma_addr), edpt->buffer + edpt->queued_len, rx_count, NULL); edpt->queued_len += rx_count; if ((rx_count < edpt->max_packet_size) || (edpt->queued_len >= edpt->buflen)) { // Transfer complete (short packet or all bytes received) channel_dealloc(channel, TUSB_DIR_IN); edpt->pid = (ch_reg & U_EP_DTOG_RX) ? 1 : 0; hcd_event_xfer_complete(daddr, ep_num | TUSB_DIR_IN_MASK, edpt->queued_len, XFER_RESULT_SUCCESS, true); } else { // More data expected uint16_t const cnt = tu_min16(edpt->buflen - edpt->queued_len, edpt->max_packet_size); btable_set_rx_bufsize(ch_id, BTABLE_BUF_RX, cnt); channel_write_status(ch_id, ch_reg, TUSB_DIR_IN, EP_STAT_VALID, false); channel->in.nak = 0; } } } // Handle NAK response static void ch_handle_nak(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir) { uint8_t const ep_num = ch_reg & U_EPADDR_FIELD; uint8_t const daddr = (ch_reg & U_EP_DEVADDR) >> U_EP_DEVADDR_Pos; uint8_t ep_id = endpoint_find(daddr, ep_num | (dir == TUSB_DIR_IN ? TUSB_DIR_IN_MASK : 0)); if (ep_id == TUSB_INDEX_INVALID_8) return; hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id]; // Retry non-periodic transfer immediately if NAK count not exceeded // Periodic transfer will be retried by next frame automatically if (edpt->ep_type == TUSB_XFER_CONTROL || edpt->ep_type == TUSB_XFER_BULK) { hcd_channel_dir_t* channel_dir = (dir == TUSB_DIR_OUT) ? &(_hcd_data.channel[ch_id].out) : &(_hcd_data.channel[ch_id].in); if (channel_dir->nak < HCD_XFER_NAK_MAX) { channel_dir->nak++; } if (channel_dir->nak < _tuh_cfg.max_nak || _tuh_cfg.max_nak == 0) { channel_write_status(ch_id, ch_reg, dir, EP_STAT_VALID, false); } } } // Handle STALL response static void ch_handle_stall(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir) { uint8_t const ep_num = ch_reg & U_EPADDR_FIELD; uint8_t const daddr = (ch_reg & U_EP_DEVADDR) >> U_EP_DEVADDR_Pos; uint8_t ep_id = endpoint_find(daddr, ep_num | (dir == TUSB_DIR_IN ? TUSB_DIR_IN_MASK : 0)); if (ep_id == TUSB_INDEX_INVALID_8) return; hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id]; hcd_channel_t* channel = &_hcd_data.channel[ch_id]; channel_dealloc(channel, dir); channel_write_status(ch_id, ch_reg, dir, EP_STAT_DISABLED, false); hcd_event_xfer_complete(daddr, ep_num | (dir == TUSB_DIR_IN ? TUSB_DIR_IN_MASK : 0), edpt->queued_len, XFER_RESULT_STALLED, true); } // Handle error response static void ch_handle_error(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir) { uint8_t const ep_num = ch_reg & U_EPADDR_FIELD; uint8_t const daddr = (ch_reg & U_EP_DEVADDR) >> U_EP_DEVADDR_Pos; uint8_t ep_id = endpoint_find(daddr, ep_num | (dir == TUSB_DIR_IN ? TUSB_DIR_IN_MASK : 0)); if (ep_id == TUSB_INDEX_INVALID_8) return; hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id]; hcd_channel_t* channel = &_hcd_data.channel[ch_id]; ch_reg &= U_EPREG_MASK | CH_STAT_MASK(dir); ch_reg &= ~(dir == TUSB_DIR_OUT ? U_EP_ERRTX : U_EP_ERRRX); hcd_channel_dir_t* channel_dir = (dir == TUSB_DIR_OUT) ? &(_hcd_data.channel[ch_id].out) : &(_hcd_data.channel[ch_id].in); if (channel_dir->retry < HCD_XFER_ERROR_MAX) { // Retry channel_dir->retry++; ch_change_status(&ch_reg, dir, EP_STAT_VALID); } else { // Failed after retries channel_dealloc(channel, dir); ch_change_status(&ch_reg, dir, EP_STAT_DISABLED); hcd_event_xfer_complete(daddr, ep_num | (dir == TUSB_DIR_IN ? TUSB_DIR_IN_MASK : 0), edpt->queued_len, XFER_RESULT_FAILED, true); } ch_write(ch_id, ch_reg, false); } // Handle CTR interrupt for the TX/OUT direction static inline void handle_ctr_tx(uint32_t ch_id) { uint32_t ch_reg = ch_read(ch_id) | U_EP_CTR_TX | U_EP_CTR_RX; hcd_channel_t* channel = &_hcd_data.channel[ch_id]; TU_VERIFY(channel->out.allocated == 1,); if ((ch_reg & U_EP_ERRTX) == 0U) { // No error if ((ch_reg & U_CH_TX_STTX) == U_CH_TX_ACK_SBUF) { ch_handle_ack(ch_id, ch_reg, TUSB_DIR_OUT); } else if ((ch_reg & U_CH_TX_STTX) == U_CH_TX_NAK) { ch_handle_nak(ch_id, ch_reg, TUSB_DIR_OUT); } else if ((ch_reg & U_CH_TX_STTX) == U_CH_TX_STALL) { ch_handle_stall(ch_id, ch_reg, TUSB_DIR_OUT); } } else { ch_handle_error(ch_id, ch_reg, TUSB_DIR_OUT); } } // Handle CTR interrupt for the RX/IN direction static inline void handle_ctr_rx(uint32_t ch_id) { uint32_t ch_reg = ch_read(ch_id) | U_EP_CTR_TX | U_EP_CTR_RX; hcd_channel_t* channel = &_hcd_data.channel[ch_id]; TU_VERIFY(channel->in.allocated == 1,); if ((ch_reg & U_EP_ERRRX) == 0U) { // No error if ((ch_reg & U_CH_RX_STRX) == U_CH_RX_ACK_SBUF) { ch_handle_ack(ch_id, ch_reg, TUSB_DIR_IN); } else if ((ch_reg & U_CH_RX_STRX) == U_CH_RX_NAK) { ch_handle_nak(ch_id, ch_reg, TUSB_DIR_IN); } else if ((ch_reg & U_CH_RX_STRX) == U_CH_RX_STALL){ ch_handle_stall(ch_id, ch_reg, TUSB_DIR_IN); } } else { ch_handle_error(ch_id, ch_reg, TUSB_DIR_IN); } } // Interrupt Handler void hcd_int_handler(uint8_t rhport, bool in_isr) { uint32_t int_status = FSDEV_REG->ISTR; // Start of Frame if (int_status & U_ISTR_SOF) { FSDEV_REG->ISTR = (fsdev_bus_t)~U_ISTR_SOF; sof_handler(); } // Port Change Detected (Connection/Disconnection) if (int_status & U_ISTR_DCON) { FSDEV_REG->ISTR = (fsdev_bus_t)~U_ISTR_DCON; port_status_handler(rhport, in_isr); } // Handle transfer complete (CTR) while (FSDEV_REG->ISTR & U_ISTR_CTR) { uint32_t const ch_id = FSDEV_REG->ISTR & U_ISTR_EP_ID; uint32_t const ch_reg = ch_read(ch_id); if (ch_reg & U_EP_CTR_RX) { ch_write_clear_ctr(ch_id, TUSB_DIR_IN); handle_ctr_rx(ch_id); } if (ch_reg & U_EP_CTR_TX) { ch_write_clear_ctr(ch_id, TUSB_DIR_OUT); handle_ctr_tx(ch_id); } } if (int_status & U_ISTR_ERR) { FSDEV_REG->ISTR = (fsdev_bus_t)~U_ISTR_ERR; // TODO: Handle error } if (int_status & U_ISTR_PMAOVR) { TU_BREAKPOINT(); FSDEV_REG->ISTR = (fsdev_bus_t)~U_ISTR_PMAOVR; } } // Enable USB interrupt void hcd_int_enable(uint8_t rhport) { fsdev_int_enable(rhport); } // Disable USB interrupt void hcd_int_disable(uint8_t rhport) { fsdev_int_disable(rhport); } // Get frame number (1ms) uint32_t hcd_frame_number(uint8_t rhport) { (void) rhport; return FSDEV_REG->FNR & U_FNR_FN; } //--------------------------------------------------------------------+ // Port API //--------------------------------------------------------------------+ // Get the current connect status of roothub port bool hcd_port_connect_status(uint8_t rhport) { (void) rhport; return _hcd_data.connected; } // Reset USB bus on the port void hcd_port_reset(uint8_t rhport) { (void) rhport; FSDEV_REG->CNTR |= U_CNTR_FRES; } // Complete bus reset sequence void hcd_port_reset_end(uint8_t rhport) { (void) rhport; FSDEV_REG->CNTR &= ~U_CNTR_FRES; } // Get port link speed tusb_speed_t hcd_port_speed_get(uint8_t rhport) { (void) rhport; if ((FSDEV_REG->ISTR & U_ISTR_LS_DCONN) != 0U) { return TUSB_SPEED_LOW; } else { return TUSB_SPEED_FULL; } } // HCD closes all opened endpoints belonging to this device void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { (void) rhport; // Close all endpoints for this device for(uint32_t i = 0; i < CFG_TUH_FSDEV_ENDPOINT_MAX; i++) { hcd_endpoint_t* edpt = &_hcd_data.edpt[i]; if (edpt->allocated == 1 && edpt->dev_addr == dev_addr) { edpoint_close(i); } } } //--------------------------------------------------------------------+ // Endpoints API //--------------------------------------------------------------------+ // Open an endpoint 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; uint16_t const packet_size = tu_edpt_packet_size(ep_desc); uint8_t const ep_type = ep_desc->bmAttributes.xfer; uint8_t const ep_id = endpoint_alloc(); TU_ASSERT(ep_id != TUSB_INDEX_INVALID_8); hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id]; edpt->dev_addr = dev_addr; edpt->ep_addr = ep_addr; edpt->ep_type = ep_type; edpt->max_packet_size = packet_size; edpt->interval = ep_desc->bInterval; edpt->pid = 0; edpt->ls_pre = (hcd_port_speed_get(rhport) == TUSB_SPEED_FULL && tuh_speed_get(dev_addr) == TUSB_SPEED_LOW) ? 1 : 0; return true; } bool hcd_edpt_close(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { (void) rhport; uint8_t const ep_id = endpoint_find(dev_addr, ep_addr); TU_ASSERT(ep_id != TUSB_INDEX_INVALID_8); edpoint_close(ep_id); return true; } // Submit a transfer bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { (void) rhport; TU_LOG(FSDEV_DEBUG, "hcd_edpt_xfer addr=%u ep=0x%02X len=%u\r\n", dev_addr, ep_addr, buflen); uint8_t const ep_id = endpoint_find(dev_addr, ep_addr); TU_ASSERT(ep_id != TUSB_INDEX_INVALID_8); hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id]; edpt->buffer = buffer; edpt->buflen = buflen; edpt->queued_len = 0; uint8_t const ep_num = tu_edpt_number(ep_addr); if (ep_num == 0) { // update ep_dir since control endpoint can switch direction edpt->ep_addr = ep_addr; } return edpt_xfer_kickoff(ep_id); } // Abort a queued transfer bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { (void) rhport; uint8_t const ep_id = endpoint_find(dev_addr, ep_addr); TU_ASSERT(ep_id != TUSB_INDEX_INVALID_8); tusb_dir_t const dir = tu_edpt_dir(ep_addr); for (uint8_t i = 0; i < FSDEV_EP_COUNT; i++) { hcd_channel_t* channel = &_hcd_data.channel[i]; uint8_t const allocated = (dir == TUSB_DIR_OUT) ? channel->out.allocated : channel->in.allocated; if (allocated == 1 && channel->dev_addr == dev_addr && channel->ep_num == tu_edpt_number(ep_addr)) { channel_dealloc(channel, dir); uint32_t ch_reg = ch_read(i) | U_EP_CTR_TX | U_EP_CTR_RX; channel_write_status(i, ch_reg, dir, EP_STAT_DISABLED, true); } } return true; } // Submit a special transfer to send 8-byte Setup Packet bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) { (void) rhport; uint8_t const ep_id = endpoint_find(dev_addr, 0); TU_ASSERT(ep_id != TUSB_INDEX_INVALID_8); hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id]; edpt->next_setup = true; edpt->pid = 0; return hcd_edpt_xfer(rhport, dev_addr, 0, (uint8_t*)(uintptr_t) setup_packet, 8); } // Clear stall, data toggle is also reset to DATA0 bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { (void) rhport; uint8_t const ep_id = endpoint_find(dev_addr, ep_addr); TU_ASSERT(ep_id != TUSB_INDEX_INVALID_8); hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id]; edpt->pid = 0; return true; } //--------------------------------------------------------------------+ // Helper Functions //--------------------------------------------------------------------+ static uint8_t endpoint_alloc(void) { for (uint32_t i = 0; i < CFG_TUH_FSDEV_ENDPOINT_MAX; i++) { hcd_endpoint_t* edpt = &_hcd_data.edpt[i]; if (edpt->allocated == 0) { edpt->allocated = 1; return i; } } return TUSB_INDEX_INVALID_8; } static uint8_t endpoint_find(uint8_t dev_addr, uint8_t ep_addr) { uint8_t const ep_num = tu_edpt_number(ep_addr); tusb_dir_t const ep_dir = tu_edpt_dir(ep_addr); for (uint32_t i = 0; i < (uint32_t)CFG_TUH_FSDEV_ENDPOINT_MAX; i++) { hcd_endpoint_t* edpt = &_hcd_data.edpt[i]; tusb_dir_t const dir = tu_edpt_dir(edpt->ep_addr); uint8_t const num = tu_edpt_number(edpt->ep_addr); // Match both ep_num and ep_dir, or match ep_num 0 (control endpoint) if (edpt->allocated == 1 && edpt->dev_addr == dev_addr && num == ep_num && (dir == ep_dir || ep_num == 0)) { return i; } } return TUSB_INDEX_INVALID_8; } // close an opened endpoint static void edpoint_close(uint8_t ep_id) { hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id]; endpoint_dealloc(edpt); // disable active channel belong to this endpoint for (uint8_t i = 0; i < FSDEV_EP_COUNT; i++) { hcd_channel_t* channel = &_hcd_data.channel[i]; uint32_t ch_reg = ch_read(i) | U_EP_CTR_TX | U_EP_CTR_RX; if (channel->out.allocated == 1 && channel->out.edpt == edpt) { channel_dealloc(channel, TUSB_DIR_OUT); channel_write_status(i, ch_reg, TUSB_DIR_OUT, EP_STAT_DISABLED, true); } if (channel->in.allocated == 1 && channel->in.edpt == edpt) { channel_dealloc(channel, TUSB_DIR_IN); channel_write_status(i, ch_reg, TUSB_DIR_IN, EP_STAT_DISABLED, true); } } } // Allocate PMA buffer static uint32_t hcd_pma_alloc(uint8_t channel, tusb_dir_t dir, uint16_t len) { (void) len; // Simple static allocation as we are unlikely to handle ISO endpoints in host mode // We just give each channel two buffers of max packet size (64 bytes) for IN and OUT uint16_t addr = FSDEV_BTABLE_BASE + 8 * FSDEV_EP_COUNT; addr += channel * TUSB_EPSIZE_BULK_FS * 2 + (dir == TUSB_DIR_IN ? TUSB_EPSIZE_BULK_FS : 0); TU_ASSERT(addr <= CFG_TUSB_FSDEV_PMA_SIZE, 0xFFFF); return addr; } // Allocate hardware channel static uint8_t channel_alloc(uint8_t dev_addr, uint8_t ep_addr, uint8_t ep_type) { uint8_t const ep_num = tu_edpt_number(ep_addr); tusb_dir_t const dir = tu_edpt_dir(ep_addr); // Find channel allocate for same ep_num but other direction tusb_dir_t const other_dir = (dir == TUSB_DIR_IN) ? TUSB_DIR_OUT : TUSB_DIR_IN; for (uint8_t i = 0; i < FSDEV_EP_COUNT; i++) { uint8_t const allocated_dir = (dir == TUSB_DIR_OUT) ? _hcd_data.channel[i].out.allocated : _hcd_data.channel[i].in.allocated; uint8_t const allocated_other = (other_dir == TUSB_DIR_OUT) ? _hcd_data.channel[i].out.allocated : _hcd_data.channel[i].in.allocated; if (allocated_dir == 0 && allocated_other == 1 && _hcd_data.channel[i].dev_addr == dev_addr && _hcd_data.channel[i].ep_num == ep_num && _hcd_data.channel[i].ep_type == ep_type) { if (dir == TUSB_DIR_OUT) { _hcd_data.channel[i].out.allocated = 1; _hcd_data.channel[i].out.retry = 0; } else { _hcd_data.channel[i].in.allocated = 1; _hcd_data.channel[i].in.retry = 0; } return i; } } // Find free channel for (uint8_t i = 0; i < FSDEV_EP_COUNT; i++) { if (_hcd_data.channel[i].out.allocated == 0 && _hcd_data.channel[i].in.allocated == 0) { _hcd_data.channel[i].dev_addr = dev_addr; _hcd_data.channel[i].ep_num = ep_num; _hcd_data.channel[i].ep_type = ep_type; if (dir == TUSB_DIR_OUT) { _hcd_data.channel[i].out.allocated = 1; _hcd_data.channel[i].out.retry = 0; } else { _hcd_data.channel[i].in.allocated = 1; _hcd_data.channel[i].in.retry = 0; } return i; } } // Allocation failed return TUSB_INDEX_INVALID_8; } // kick-off transfer with an endpoint static bool edpt_xfer_kickoff(uint8_t ep_id) { hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id]; uint8_t ch_id = channel_alloc(edpt->dev_addr, edpt->ep_addr, edpt->ep_type); TU_ASSERT(ch_id != TUSB_INDEX_INVALID_8); // all channel are in used tusb_dir_t const dir = tu_edpt_dir(edpt->ep_addr); hcd_channel_t* channel = &_hcd_data.channel[ch_id]; if (dir == TUSB_DIR_OUT) { channel->out.edpt = edpt; } else { channel->in.edpt = edpt; } return channel_xfer_start(ch_id, dir); } static bool channel_xfer_start(uint8_t ch_id, tusb_dir_t dir) { hcd_channel_t* channel = &_hcd_data.channel[ch_id]; hcd_endpoint_t* edpt = (dir == TUSB_DIR_OUT) ? channel->out.edpt : channel->in.edpt; uint32_t ch_reg = ch_read(ch_id) & ~U_EPREG_MASK; ch_reg |= tu_edpt_number(edpt->ep_addr) | edpt->dev_addr << U_EP_DEVADDR_Pos | U_EP_CTR_TX | U_EP_CTR_RX; // Set type switch (edpt->ep_type) { case TUSB_XFER_BULK: ch_reg |= U_EP_BULK; break; case TUSB_XFER_INTERRUPT: ch_reg |= U_EP_INTERRUPT; break; case TUSB_XFER_CONTROL: ch_reg |= U_EP_CONTROL; break; default: // Note: ISO endpoint is unsupported TU_ASSERT(false); } /* Create a packet memory buffer area. */ uint16_t pma_addr = hcd_pma_alloc(ch_id, dir, edpt->max_packet_size); btable_set_addr(ch_id, dir == TUSB_DIR_OUT ? BTABLE_BUF_TX : BTABLE_BUF_RX, pma_addr); if (dir == TUSB_DIR_OUT) { uint16_t const len = tu_min16(edpt->buflen - edpt->queued_len, edpt->max_packet_size); tu_hwfifo_write(PMA_BUF_AT(pma_addr), &(edpt->buffer[edpt->queued_len]), len, NULL); btable_set_count(ch_id, BTABLE_BUF_TX, len); edpt->queued_len += len; } else { btable_set_rx_bufsize(ch_id, BTABLE_BUF_RX, edpt->max_packet_size); } if (edpt->ls_pre == 1) { ch_reg |= U_EP_LSEP; } else { ch_reg &= ~U_EP_LSEP; } // Setup DATA/STATUS phase start with DATA1 if (tu_edpt_number(edpt->ep_addr) == 0) { edpt->pid = 1; } if (edpt->next_setup) { edpt->next_setup = false; ch_reg |= U_EP_SETUP; edpt->pid = 0; } ch_change_status(&ch_reg, dir, EP_STAT_VALID); ch_change_dtog(&ch_reg, dir, edpt->pid); ch_reg &= U_EPREG_MASK | CH_STAT_MASK(dir) | CH_DTOG_MASK(dir); ch_write(ch_id, ch_reg, true); return true; } #endif