diff options
| author | hathach <[email protected]> | 2020-03-22 17:19:57 +0700 |
|---|---|---|
| committer | hathach <[email protected]> | 2020-03-22 17:19:57 +0700 |
| commit | 4f871063fd33db2b55c6f78130c723a8854e0b11 (patch) | |
| tree | ddc78bd68c16a0895e0a7b4f9d1aff6380a20484 /src/host | |
| parent | 1ab3a1035c997e66b2014e8edfb92e10202cd988 (diff) | |
| parent | e47f9921570c75afd3288407601897c5b283f043 (diff) | |
Merge branch 'master' into cr1901-msp430f5529
Diffstat (limited to 'src/host')
| -rw-r--r-- | src/host/ehci/ehci.c | 1712 | ||||
| -rw-r--r-- | src/host/hcd.h | 293 | ||||
| -rw-r--r-- | src/host/hub.c | 500 | ||||
| -rw-r--r-- | src/host/hub.h | 388 | ||||
| -rw-r--r-- | src/host/ohci/ohci.c | 1302 | ||||
| -rw-r--r-- | src/host/ohci/ohci.h | 582 | ||||
| -rw-r--r-- | src/host/usbh.c | 1352 | ||||
| -rw-r--r-- | src/host/usbh.h | 3 |
8 files changed, 3068 insertions, 3064 deletions
diff --git a/src/host/ehci/ehci.c b/src/host/ehci/ehci.c index 63e87dd54..9ddffaf2c 100644 --- a/src/host/ehci/ehci.c +++ b/src/host/ehci/ehci.c @@ -1,856 +1,856 @@ -/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2019 Ha Thach (tinyusb.org)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * This file is part of the TinyUSB stack.
- */
-
-#include "common/tusb_common.h"
-
-#if TUSB_OPT_HOST_ENABLED && (CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_LPC18XX)
-//--------------------------------------------------------------------+
-// INCLUDE
-//--------------------------------------------------------------------+
-#include "osal/osal.h"
-
-#include "../hcd.h"
-#include "../usbh_hcd.h"
-#include "ehci.h"
-
-//--------------------------------------------------------------------+
-// MACRO CONSTANT TYPEDEF
-//--------------------------------------------------------------------+
-
-//--------------------------------------------------------------------+
-// INTERNAL OBJECT & FUNCTION DECLARATION
-//--------------------------------------------------------------------+
-// Periodic frame list must be 4K alignment
-CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(4096) static ehci_data_t ehci_data;
-
-// EHCI portable
-uint32_t hcd_ehci_register_addr(uint8_t rhport);
-
-//--------------------------------------------------------------------+
-// PROTOTYPE
-//--------------------------------------------------------------------+
-static inline ehci_link_t* get_period_head(uint8_t rhport, uint8_t interval_ms)
-{
- (void) rhport;
- return (ehci_link_t*) &ehci_data.period_head_arr[ tu_log2( tu_min8(EHCI_FRAMELIST_SIZE, interval_ms) ) ];
-}
-
-static inline ehci_qhd_t* qhd_control(uint8_t dev_addr)
-{
- return &ehci_data.control[dev_addr].qhd;
-}
-
-static inline ehci_qhd_t* qhd_async_head(uint8_t rhport)
-{
- (void) rhport;
- return qhd_control(0); // control qhd of dev0 is used as async head
-}
-
-static inline ehci_qtd_t* qtd_control(uint8_t dev_addr)
-{
- return &ehci_data.control[dev_addr].qtd;
-}
-
-
-static inline ehci_qhd_t* qhd_next (ehci_qhd_t const * p_qhd);
-static inline ehci_qhd_t* qhd_find_free (void);
-static inline ehci_qhd_t* qhd_get_from_addr (uint8_t dev_addr, uint8_t ep_addr);
-
-// determine if a queue head has bus-related error
-static inline bool qhd_has_xact_error (ehci_qhd_t * p_qhd)
-{
- return (p_qhd->qtd_overlay.buffer_err || p_qhd->qtd_overlay.babble_err || p_qhd->qtd_overlay.xact_err);
- //p_qhd->qtd_overlay.non_hs_period_missed_uframe || p_qhd->qtd_overlay.pingstate_err TODO split transaction error
-}
-
-static void qhd_init (ehci_qhd_t *p_qhd, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc);
-
-static inline ehci_qtd_t* qtd_find_free (void);
-static inline ehci_qtd_t* qtd_next (ehci_qtd_t const * p_qtd);
-static inline void qtd_insert_to_qhd (ehci_qhd_t *p_qhd, ehci_qtd_t *p_qtd_new);
-static inline void qtd_remove_1st_from_qhd (ehci_qhd_t *p_qhd);
-static void qtd_init (ehci_qtd_t* p_qtd, void* buffer, uint16_t total_bytes);
-
-static inline void list_insert (ehci_link_t *current, ehci_link_t *new, uint8_t new_type);
-static inline ehci_link_t* list_next (ehci_link_t *p_link_pointer);
-
-static bool ehci_init (uint8_t rhport);
-
-//--------------------------------------------------------------------+
-// HCD API
-//--------------------------------------------------------------------+
-bool hcd_init(void)
-{
- tu_memclr(&ehci_data, sizeof(ehci_data_t));
- return ehci_init(TUH_OPT_RHPORT);
-}
-
-void hcd_port_reset(uint8_t rhport)
-{
- (void) rhport;
-
- ehci_registers_t* regs = ehci_data.regs;
-
- regs->portsc_bm.port_enabled = 0; // disable port before reset
- regs->portsc_bm.port_reset = 1;
-}
-
-bool hcd_port_connect_status(uint8_t rhport)
-{
- (void) rhport;
- return ehci_data.regs->portsc_bm.current_connect_status;
-}
-
-tusb_speed_t hcd_port_speed_get(uint8_t rhport)
-{
- (void) rhport;
- return (tusb_speed_t) ehci_data.regs->portsc_bm.nxp_port_speed; // NXP specific port speed
-}
-
-static void list_remove_qhd_by_addr(ehci_link_t* list_head, uint8_t dev_addr)
-{
- for(ehci_link_t* prev = list_head;
- !prev->terminate && (tu_align32(prev->address) != (uint32_t) list_head);
- prev = list_next(prev) )
- {
- // TODO check type for ISO iTD and siTD
- ehci_qhd_t* qhd = (ehci_qhd_t*) list_next(prev);
- if ( qhd->dev_addr == dev_addr )
- {
- // TODO deactive all TD, wait for QHD to inactive before removal
- prev->address = qhd->next.address;
-
- // EHCI 4.8.2 link the removed qhd to async head (which always reachable by Host Controller)
- qhd->next.address = ((uint32_t) list_head) | (EHCI_QTYPE_QHD << 1);
-
- if ( qhd->int_smask )
- {
- // period list queue element is guarantee to be free in the next frame (1 ms)
- qhd->used = 0;
- }else
- {
- // async list use async advance handshake
- // mark as removing, will completely re-usable when async advance isr occurs
- qhd->removing = 1;
- }
- }
- }
-}
-
-// Close all opened endpoint belong to this device
-void hcd_device_close(uint8_t rhport, uint8_t dev_addr)
-{
- // skip dev0
- if (dev_addr == 0) return;
-
- // Remove from async list
- list_remove_qhd_by_addr( (ehci_link_t*) qhd_async_head(rhport), dev_addr );
-
- // Remove from all interval period list
- for(uint8_t i = 0; i < TU_ARRAY_SZIE(ehci_data.period_head_arr); i++)
- {
- list_remove_qhd_by_addr( (ehci_link_t*) &ehci_data.period_head_arr[i], dev_addr);
- }
-
- // Async doorbell (EHCI 4.8.2 for operational details)
- ehci_data.regs->command_bm.async_adv_doorbell = 1;
-}
-
-// EHCI controller init
-static bool ehci_init(uint8_t rhport)
-{
- ehci_data.regs = (ehci_registers_t* ) hcd_ehci_register_addr(rhport);
-
- ehci_registers_t* regs = ehci_data.regs;
-
- //------------- CTRLDSSEGMENT Register (skip) -------------//
- //------------- USB INT Register -------------//
- regs->inten = 0; // 1. disable all the interrupt
- regs->status = EHCI_INT_MASK_ALL; // 2. clear all status
-
- regs->inten = EHCI_INT_MASK_ERROR | EHCI_INT_MASK_PORT_CHANGE | EHCI_INT_MASK_ASYNC_ADVANCE |
- EHCI_INT_MASK_NXP_PERIODIC | EHCI_INT_MASK_NXP_ASYNC ;
-
- //------------- Asynchronous List -------------//
- ehci_qhd_t * const async_head = qhd_async_head(rhport);
- tu_memclr(async_head, sizeof(ehci_qhd_t));
-
- async_head->next.address = (uint32_t) async_head; // circular list, next is itself
- async_head->next.type = EHCI_QTYPE_QHD;
- async_head->head_list_flag = 1;
- async_head->qtd_overlay.halted = 1; // inactive most of time
- async_head->qtd_overlay.next.terminate = 1; // TODO removed if verified
-
- regs->async_list_addr = (uint32_t) async_head;
-
- //------------- Periodic List -------------//
- // Build the polling interval tree with 1 ms, 2 ms, 4 ms and 8 ms (framesize) only
- for(uint32_t i=0; i<4; i++)
- {
- ehci_data.period_head_arr[i].int_smask = 1; // queue head in period list must have smask non-zero
- ehci_data.period_head_arr[i].qtd_overlay.halted = 1; // dummy node, always inactive
- }
-
- ehci_link_t * const framelist = ehci_data.period_framelist;
- ehci_link_t * const period_1ms = get_period_head(rhport, 1);
- // all links --> period_head_arr[0] (1ms)
- // 0, 2, 4, 6 etc --> period_head_arr[1] (2ms)
- // 1, 5 --> period_head_arr[2] (4ms)
- // 3 --> period_head_arr[3] (8ms)
-
- // TODO EHCI_FRAMELIST_SIZE with other size than 8
- for(uint32_t i=0; i<EHCI_FRAMELIST_SIZE; i++)
- {
- framelist[i].address = (uint32_t) period_1ms;
- framelist[i].type = EHCI_QTYPE_QHD;
- }
-
- for(uint32_t i=0; i<EHCI_FRAMELIST_SIZE; i+=2)
- {
- list_insert(framelist + i, get_period_head(rhport, 2), EHCI_QTYPE_QHD);
- }
-
- for(uint32_t i=1; i<EHCI_FRAMELIST_SIZE; i+=4)
- {
- list_insert(framelist + i, get_period_head(rhport, 4), EHCI_QTYPE_QHD);
- }
-
- list_insert(framelist+3, get_period_head(rhport, 8), EHCI_QTYPE_QHD);
-
- period_1ms->terminate = 1;
-
- regs->periodic_list_base = (uint32_t) framelist;
-
- //------------- TT Control (NXP only) -------------//
- regs->nxp_tt_control = 0;
-
- //------------- USB CMD Register -------------//
- regs->command |= TU_BIT(EHCI_USBCMD_POS_RUN_STOP) | TU_BIT(EHCI_USBCMD_POS_ASYNC_ENABLE)
- | TU_BIT(EHCI_USBCMD_POS_PERIOD_ENABLE) // TODO enable period list only there is int/iso endpoint
- | ((EHCI_CFG_FRAMELIST_SIZE_BITS & TU_BIN8(011)) << EHCI_USBCMD_POS_FRAMELIST_SZIE)
- | ((EHCI_CFG_FRAMELIST_SIZE_BITS >> 2) << EHCI_USBCMD_POS_NXP_FRAMELIST_SIZE_MSB);
-
- //------------- ConfigFlag Register (skip) -------------//
- regs->portsc_bm.port_power = 1; // enable port power
-
- return true;
-}
-
-#if 0
-static void ehci_stop(uint8_t rhport)
-{
- (void) rhport;
-
- ehci_registers_t* regs = ehci_data.regs;
-
- regs->command_bm.run_stop = 0;
-
- // USB Spec: controller has to stop within 16 uframe = 2 frames
- while( regs->status_bm.hc_halted == 0 ) {}
-}
-#endif
-
-//--------------------------------------------------------------------+
-// CONTROL PIPE API
-//--------------------------------------------------------------------+
-bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen)
-{
- (void) rhport;
-
- uint8_t const epnum = tu_edpt_number(ep_addr);
- uint8_t const dir = tu_edpt_dir(ep_addr);
-
- // FIXME control only for now
- if ( epnum == 0 )
- {
- ehci_qhd_t* qhd = qhd_control(dev_addr);
- ehci_qtd_t* qtd = qtd_control(dev_addr);
-
- qtd_init(qtd, buffer, buflen);
-
- // first first data toggle is always 1 (data & setup stage)
- qtd->data_toggle = 1;
- qtd->pid = dir ? EHCI_PID_IN : EHCI_PID_OUT;
- qtd->int_on_complete = 1;
- qtd->next.terminate = 1;
-
- // sw region
- qhd->p_qtd_list_head = qtd;
- qhd->p_qtd_list_tail = qtd;
-
- // attach TD
- qhd->qtd_overlay.next.address = (uint32_t) qtd;
- }
-
- return true;
-}
-
-bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8])
-{
- (void) rhport;
-
- ehci_qhd_t* qhd = &ehci_data.control[dev_addr].qhd;
- ehci_qtd_t* td = &ehci_data.control[dev_addr].qtd;
-
- qtd_init(td, (void*) setup_packet, 8);
- td->pid = EHCI_PID_SETUP;
- td->int_on_complete = 1;
- td->next.terminate = 1;
-
- // sw region
- qhd->p_qtd_list_head = td;
- qhd->p_qtd_list_tail = td;
-
- // attach TD
- qhd->qtd_overlay.next.address = (uint32_t) td;
-
- return true;
-}
-
-//--------------------------------------------------------------------+
-// BULK/INT/ISO PIPE API
-//--------------------------------------------------------------------+
-bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc)
-{
- (void) rhport;
-
- // TODO not support ISO yet
- TU_ASSERT (ep_desc->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS);
-
- //------------- Prepare Queue Head -------------//
- ehci_qhd_t * p_qhd;
-
- if ( ep_desc->bEndpointAddress == 0 )
- {
- p_qhd = qhd_control(dev_addr);
- }else
- {
- p_qhd = qhd_find_free();
- }
- TU_ASSERT(p_qhd);
-
- qhd_init(p_qhd, dev_addr, ep_desc);
-
- // control of dev0 is always present as async head
- if ( dev_addr == 0 ) return true;
-
- // Insert to list
- ehci_link_t * list_head;
-
- switch (ep_desc->bmAttributes.xfer)
- {
- case TUSB_XFER_CONTROL:
- case TUSB_XFER_BULK:
- list_head = (ehci_link_t*) qhd_async_head(rhport);
- break;
-
- case TUSB_XFER_INTERRUPT:
- list_head = get_period_head(rhport, p_qhd->interval_ms);
- break;
-
- case TUSB_XFER_ISOCHRONOUS:
- // TODO iso is not supported
- break;
-
- default: break;
- }
-
- // TODO might need to disable async/period list
- list_insert( list_head, (ehci_link_t*) p_qhd, EHCI_QTYPE_QHD);
-
- return true;
-}
-
-bool hcd_pipe_queue_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes)
-{
- //------------- set up QTD -------------//
- ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr);
- ehci_qtd_t *p_qtd = qtd_find_free();
-
- TU_ASSERT(p_qtd);
-
- qtd_init(p_qtd, buffer, total_bytes);
- p_qtd->pid = p_qhd->pid;
-
- //------------- insert TD to TD list -------------//
- qtd_insert_to_qhd(p_qhd, p_qtd);
-
- return true;
-}
-
-bool hcd_pipe_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes, bool int_on_complete)
-{
- TU_ASSERT ( hcd_pipe_queue_xfer(dev_addr, ep_addr, buffer, total_bytes) );
-
- ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr);
-
- if ( int_on_complete )
- { // the just added qtd is pointed by list_tail
- p_qhd->p_qtd_list_tail->int_on_complete = 1;
- }
- p_qhd->qtd_overlay.next.address = (uint32_t) p_qhd->p_qtd_list_head; // attach head QTD to QHD start transferring
-
- return true;
-}
-
-bool hcd_edpt_busy(uint8_t dev_addr, uint8_t ep_addr)
-{
- ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr);
- return !p_qhd->qtd_overlay.halted && (p_qhd->p_qtd_list_head != NULL);
-}
-
-bool hcd_edpt_stalled(uint8_t dev_addr, uint8_t ep_addr)
-{
- ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr);
- return p_qhd->qtd_overlay.halted && !qhd_has_xact_error(p_qhd);
-}
-
-bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr)
-{
- ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr);
- p_qhd->qtd_overlay.halted = 0;
- // TODO reset data toggle ?
- return true;
-}
-
-//--------------------------------------------------------------------+
-// EHCI Interrupt Handler
-//--------------------------------------------------------------------+
-
-// async_advance is handshake between usb stack & ehci controller.
-// This isr mean it is safe to modify previously removed queue head from async list.
-// In tinyusb, queue head is only removed when device is unplugged.
-static void async_advance_isr(uint8_t rhport)
-{
- (void) rhport;
-
- ehci_qhd_t* qhd_pool = ehci_data.qhd_pool;
- for(uint32_t i = 0; i < HCD_MAX_ENDPOINT; i++)
- {
- if ( qhd_pool[i].removing )
- {
- qhd_pool[i].removing = 0;
- qhd_pool[i].used = 0;
- }
- }
-}
-
-static void port_connect_status_change_isr(uint8_t hostid)
-{
- // NOTE There is an sequence plug->unplug->…..-> plug if device is powering with pre-plugged device
- if (ehci_data.regs->portsc_bm.current_connect_status)
- {
- hcd_port_reset(hostid);
- hcd_event_device_attach(hostid);
- }else // device unplugged
- {
- hcd_event_device_remove(hostid);
- }
-}
-
-static void qhd_xfer_complete_isr(ehci_qhd_t * p_qhd)
-{
- // free all TDs from the head td to the first active TD
- while(p_qhd->p_qtd_list_head != NULL && !p_qhd->p_qtd_list_head->active)
- {
- // TD need to be freed and removed from qhd, before invoking callback
- bool is_ioc = (p_qhd->p_qtd_list_head->int_on_complete != 0);
- p_qhd->total_xferred_bytes += p_qhd->p_qtd_list_head->expected_bytes - p_qhd->p_qtd_list_head->total_bytes;
-
- p_qhd->p_qtd_list_head->used = 0; // free QTD
- qtd_remove_1st_from_qhd(p_qhd);
-
- if (is_ioc)
- {
- // end of request
- // call USBH callback
- hcd_event_xfer_complete(p_qhd->dev_addr, tu_edpt_addr(p_qhd->ep_number, p_qhd->pid == EHCI_PID_IN ? 1 : 0), XFER_RESULT_SUCCESS, p_qhd->total_xferred_bytes);
- p_qhd->total_xferred_bytes = 0;
- }
- }
-}
-
-static void async_list_xfer_complete_isr(ehci_qhd_t * const async_head)
-{
- ehci_qhd_t *p_qhd = async_head;
- do
- {
- if ( !p_qhd->qtd_overlay.halted ) // halted or error is processed in error isr
- {
- qhd_xfer_complete_isr(p_qhd);
- }
- p_qhd = qhd_next(p_qhd);
- }while(p_qhd != async_head); // async list traversal, stop if loop around
-}
-
-static void period_list_xfer_complete_isr(uint8_t hostid, uint8_t interval_ms)
-{
- uint8_t max_loop = 0;
- uint32_t const period_1ms_addr = (uint32_t) get_period_head(hostid, 1);
- ehci_link_t next_item = * get_period_head(hostid, interval_ms);
-
- // TODO abstract max loop guard for period
- while( !next_item.terminate &&
- !(interval_ms > 1 && period_1ms_addr == tu_align32(next_item.address)) &&
- max_loop < (HCD_MAX_ENDPOINT + EHCI_MAX_ITD + EHCI_MAX_SITD)*CFG_TUSB_HOST_DEVICE_MAX)
- {
- switch ( next_item.type )
- {
- case EHCI_QTYPE_QHD:
- {
- ehci_qhd_t *p_qhd_int = (ehci_qhd_t *) tu_align32(next_item.address);
- if ( !p_qhd_int->qtd_overlay.halted )
- {
- qhd_xfer_complete_isr(p_qhd_int);
- }
- }
- break;
-
- case EHCI_QTYPE_ITD: // TODO support hs/fs ISO
- case EHCI_QTYPE_SITD:
- case EHCI_QTYPE_FSTN:
-
- default: break;
- }
-
- next_item = *list_next(&next_item);
- max_loop++;
- }
-}
-
-static void qhd_xfer_error_isr(ehci_qhd_t * p_qhd)
-{
- if ( (p_qhd->dev_addr != 0 && p_qhd->qtd_overlay.halted) || // addr0 cannot be protocol STALL
- qhd_has_xact_error(p_qhd) )
- {
- // current qhd has error in transaction
- xfer_result_t error_event;
-
- // no error bits are set, endpoint is halted due to STALL
- error_event = qhd_has_xact_error(p_qhd) ? XFER_RESULT_FAILED : XFER_RESULT_STALLED;
-
- p_qhd->total_xferred_bytes += p_qhd->p_qtd_list_head->expected_bytes - p_qhd->p_qtd_list_head->total_bytes;
-
-// if ( XFER_RESULT_FAILED == error_event ) TU_BREAKPOINT(); // TODO skip unplugged device
-
- p_qhd->p_qtd_list_head->used = 0; // free QTD
- qtd_remove_1st_from_qhd(p_qhd);
-
- if ( 0 == p_qhd->ep_number )
- {
- // control cannot be halted --> clear all qtd list
- p_qhd->p_qtd_list_head = NULL;
- p_qhd->p_qtd_list_tail = NULL;
-
- p_qhd->qtd_overlay.next.terminate = 1;
- p_qhd->qtd_overlay.alternate.terminate = 1;
- p_qhd->qtd_overlay.halted = 0;
-
- ehci_qtd_t *p_setup = qtd_control(p_qhd->dev_addr);
- p_setup->used = 0;
- }
-
- // call USBH callback
- hcd_event_xfer_complete(p_qhd->dev_addr, tu_edpt_addr(p_qhd->ep_number, p_qhd->pid == EHCI_PID_IN ? 1 : 0), error_event, p_qhd->total_xferred_bytes);
-
- p_qhd->total_xferred_bytes = 0;
- }
-}
-
-static void xfer_error_isr(uint8_t hostid)
-{
- //------------- async list -------------//
- ehci_qhd_t * const async_head = qhd_async_head(hostid);
- ehci_qhd_t *p_qhd = async_head;
- do
- {
- qhd_xfer_error_isr( p_qhd );
- p_qhd = qhd_next(p_qhd);
- }while(p_qhd != async_head); // async list traversal, stop if loop around
-
- //------------- TODO refractor period list -------------//
- uint32_t const period_1ms_addr = (uint32_t) get_period_head(hostid, 1);
- for (uint8_t interval_ms=1; interval_ms <= EHCI_FRAMELIST_SIZE; interval_ms *= 2)
- {
- ehci_link_t next_item = * get_period_head(hostid, interval_ms);
-
- // TODO abstract max loop guard for period
- while( !next_item.terminate &&
- !(interval_ms > 1 && period_1ms_addr == tu_align32(next_item.address)) )
- {
- switch ( next_item.type )
- {
- case EHCI_QTYPE_QHD:
- {
- ehci_qhd_t *p_qhd_int = (ehci_qhd_t *) tu_align32(next_item.address);
- qhd_xfer_error_isr(p_qhd_int);
- }
- break;
-
- // TODO support hs/fs ISO
- case EHCI_QTYPE_ITD:
- case EHCI_QTYPE_SITD:
- case EHCI_QTYPE_FSTN:
- default: break;
- }
-
- next_item = *list_next(&next_item);
- }
- }
-}
-
-//------------- Host Controller Driver's Interrupt Handler -------------//
-void hal_hcd_isr(uint8_t rhport)
-{
- ehci_registers_t* regs = ehci_data.regs;
-
- uint32_t int_status = regs->status;
- int_status &= regs->inten;
-
- regs->status |= int_status; // Acknowledge handled interrupt
-
- if (int_status == 0) return;
-
- if (int_status & EHCI_INT_MASK_PORT_CHANGE)
- {
- uint32_t port_status = regs->portsc & EHCI_PORTSC_MASK_ALL;
-
- if (regs->portsc_bm.connect_status_change)
- {
- port_connect_status_change_isr(rhport);
- }
-
- regs->portsc |= port_status; // Acknowledge change bits in portsc
- }
-
- if (int_status & EHCI_INT_MASK_ERROR)
- {
- xfer_error_isr(rhport);
- }
-
- //------------- some QTD/SITD/ITD with IOC set is completed -------------//
- if (int_status & EHCI_INT_MASK_NXP_ASYNC)
- {
- async_list_xfer_complete_isr( qhd_async_head(rhport) );
- }
-
- if (int_status & EHCI_INT_MASK_NXP_PERIODIC)
- {
- for (uint8_t i=1; i <= EHCI_FRAMELIST_SIZE; i *= 2)
- {
- period_list_xfer_complete_isr( rhport, i );
- }
- }
-
- //------------- There is some removed async previously -------------//
- if (int_status & EHCI_INT_MASK_ASYNC_ADVANCE) // need to place after EHCI_INT_MASK_NXP_ASYNC
- {
- async_advance_isr(rhport);
- }
-}
-
-//--------------------------------------------------------------------+
-// HELPER
-//--------------------------------------------------------------------+
-
-
-//------------- queue head helper -------------//
-static inline ehci_qhd_t* qhd_find_free (void)
-{
- for (uint32_t i=0; i<HCD_MAX_ENDPOINT; i++)
- {
- if ( !ehci_data.qhd_pool[i].used ) return &ehci_data.qhd_pool[i];
- }
-
- return NULL;
-}
-
-static inline ehci_qhd_t* qhd_next(ehci_qhd_t const * p_qhd)
-{
- return (ehci_qhd_t*) tu_align32(p_qhd->next.address);
-}
-
-static inline ehci_qhd_t* qhd_get_from_addr(uint8_t dev_addr, uint8_t ep_addr)
-{
- ehci_qhd_t* qhd_pool = ehci_data.qhd_pool;
-
- for(uint32_t i=0; i<HCD_MAX_ENDPOINT; i++)
- {
- if ( (qhd_pool[i].dev_addr == dev_addr) &&
- ep_addr == tu_edpt_addr(qhd_pool[i].ep_number, qhd_pool[i].pid) )
- {
- return &qhd_pool[i];
- }
- }
-
- return NULL;
-}
-
-//------------- TD helper -------------//
-static inline ehci_qtd_t* qtd_find_free(void)
-{
- for (uint32_t i=0; i<HCD_MAX_XFER; i++)
- {
- if ( !ehci_data.qtd_pool[i].used ) return &ehci_data.qtd_pool[i];
- }
-
- return NULL;
-}
-
-static inline ehci_qtd_t* qtd_next(ehci_qtd_t const * p_qtd )
-{
- return (ehci_qtd_t*) tu_align32(p_qtd->next.address);
-}
-
-static inline void qtd_remove_1st_from_qhd(ehci_qhd_t *p_qhd)
-{
- if (p_qhd->p_qtd_list_head == p_qhd->p_qtd_list_tail) // last TD --> make it NULL
- {
- p_qhd->p_qtd_list_head = p_qhd->p_qtd_list_tail = NULL;
- }else
- {
- p_qhd->p_qtd_list_head = qtd_next( p_qhd->p_qtd_list_head );
- }
-}
-
-static inline void qtd_insert_to_qhd(ehci_qhd_t *p_qhd, ehci_qtd_t *p_qtd_new)
-{
- if (p_qhd->p_qtd_list_head == NULL) // empty list
- {
- p_qhd->p_qtd_list_head = p_qhd->p_qtd_list_tail = p_qtd_new;
- }else
- {
- p_qhd->p_qtd_list_tail->next.address = (uint32_t) p_qtd_new;
- p_qhd->p_qtd_list_tail = p_qtd_new;
- }
-}
-
-static void qhd_init(ehci_qhd_t *p_qhd, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc)
-{
- // address 0 is used as async head, which always on the list --> cannot be cleared (ehci halted otherwise)
- if (dev_addr != 0)
- {
- tu_memclr(p_qhd, sizeof(ehci_qhd_t));
- }
-
- uint8_t const xfer_type = ep_desc->bmAttributes.xfer;
- uint8_t const interval = ep_desc->bInterval;
-
- p_qhd->dev_addr = dev_addr;
- p_qhd->fl_inactive_next_xact = 0;
- p_qhd->ep_number = tu_edpt_number(ep_desc->bEndpointAddress);
- p_qhd->ep_speed = _usbh_devices[dev_addr].speed;
- p_qhd->data_toggle_control= (xfer_type == TUSB_XFER_CONTROL) ? 1 : 0;
- p_qhd->head_list_flag = (dev_addr == 0) ? 1 : 0; // addr0's endpoint is the static asyn list head
- p_qhd->max_packet_size = ep_desc->wMaxPacketSize.size;
- p_qhd->fl_ctrl_ep_flag = ((xfer_type == TUSB_XFER_CONTROL) && (p_qhd->ep_speed != TUSB_SPEED_HIGH)) ? 1 : 0;
- p_qhd->nak_reload = 0;
-
- // Bulk/Control -> smask = cmask = 0
- // TODO Isochronous
- if (TUSB_XFER_INTERRUPT == xfer_type)
- {
- if (TUSB_SPEED_HIGH == p_qhd->ep_speed)
- {
- TU_ASSERT( interval <= 16, );
- if ( interval < 4) // sub milisecond interval
- {
- p_qhd->interval_ms = 0;
- p_qhd->int_smask = (interval == 1) ? TU_BIN8(11111111) :
- (interval == 2) ? TU_BIN8(10101010) : TU_BIN8(01000100);
- }else
- {
- p_qhd->interval_ms = (uint8_t) tu_min16( 1 << (interval-4), 255 );
- p_qhd->int_smask = TU_BIT(interval % 8);
- }
- }else
- {
- TU_ASSERT( 0 != interval, );
- // Full/Low: 4.12.2.1 (EHCI) case 1 schedule start split at 1 us & complete split at 2,3,4 uframes
- p_qhd->int_smask = 0x01;
- p_qhd->fl_int_cmask = TU_BIN8(11100);
- p_qhd->interval_ms = interval;
- }
- }else
- {
- p_qhd->int_smask = p_qhd->fl_int_cmask = 0;
- }
-
- p_qhd->fl_hub_addr = _usbh_devices[dev_addr].hub_addr;
- p_qhd->fl_hub_port = _usbh_devices[dev_addr].hub_port;
- p_qhd->mult = 1; // TODO not use high bandwidth/park mode yet
-
- //------------- HCD Management Data -------------//
- p_qhd->used = 1;
- p_qhd->removing = 0;
- p_qhd->p_qtd_list_head = NULL;
- p_qhd->p_qtd_list_tail = NULL;
- p_qhd->pid = tu_edpt_dir(ep_desc->bEndpointAddress) ? EHCI_PID_IN : EHCI_PID_OUT; // PID for TD under this endpoint
-
- //------------- active, but no TD list -------------//
- p_qhd->qtd_overlay.halted = 0;
- p_qhd->qtd_overlay.next.terminate = 1;
- p_qhd->qtd_overlay.alternate.terminate = 1;
- if (TUSB_XFER_BULK == xfer_type && p_qhd->ep_speed == TUSB_SPEED_HIGH && p_qhd->pid == EHCI_PID_OUT)
- {
- p_qhd->qtd_overlay.ping_err = 1; // do PING for Highspeed Bulk OUT, EHCI section 4.11
- }
-}
-
-static void qtd_init(ehci_qtd_t* p_qtd, void* buffer, uint16_t total_bytes)
-{
- tu_memclr(p_qtd, sizeof(ehci_qtd_t));
-
- p_qtd->used = 1;
-
- p_qtd->next.terminate = 1; // init to null
- p_qtd->alternate.terminate = 1; // not used, always set to terminated
- p_qtd->active = 1;
- p_qtd->err_count = 3; // TODO 3 consecutive errors tolerance
- p_qtd->data_toggle = 0;
- p_qtd->total_bytes = total_bytes;
- p_qtd->expected_bytes = total_bytes;
-
- p_qtd->buffer[0] = (uint32_t) buffer;
- for(uint8_t i=1; i<5; i++)
- {
- p_qtd->buffer[i] |= tu_align4k( p_qtd->buffer[i-1] ) + 4096;
- }
-}
-
-//------------- List Managing Helper -------------//
-static inline void list_insert(ehci_link_t *current, ehci_link_t *new, uint8_t new_type)
-{
- new->address = current->address;
- current->address = ((uint32_t) new) | (new_type << 1);
-}
-
-static inline ehci_link_t* list_next(ehci_link_t *p_link_pointer)
-{
- return (ehci_link_t*) tu_align32(p_link_pointer->address);
-}
-
-#endif
+/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#include "common/tusb_common.h" + +#if TUSB_OPT_HOST_ENABLED && (CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_LPC18XX) +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "osal/osal.h" + +#include "../hcd.h" +#include "../usbh_hcd.h" +#include "ehci.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +// Periodic frame list must be 4K alignment +CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(4096) static ehci_data_t ehci_data; + +// EHCI portable +uint32_t hcd_ehci_register_addr(uint8_t rhport); + +//--------------------------------------------------------------------+ +// PROTOTYPE +//--------------------------------------------------------------------+ +static inline ehci_link_t* get_period_head(uint8_t rhport, uint8_t interval_ms) +{ + (void) rhport; + return (ehci_link_t*) &ehci_data.period_head_arr[ tu_log2( tu_min8(EHCI_FRAMELIST_SIZE, interval_ms) ) ]; +} + +static inline ehci_qhd_t* qhd_control(uint8_t dev_addr) +{ + return &ehci_data.control[dev_addr].qhd; +} + +static inline ehci_qhd_t* qhd_async_head(uint8_t rhport) +{ + (void) rhport; + return qhd_control(0); // control qhd of dev0 is used as async head +} + +static inline ehci_qtd_t* qtd_control(uint8_t dev_addr) +{ + return &ehci_data.control[dev_addr].qtd; +} + + +static inline ehci_qhd_t* qhd_next (ehci_qhd_t const * p_qhd); +static inline ehci_qhd_t* qhd_find_free (void); +static inline ehci_qhd_t* qhd_get_from_addr (uint8_t dev_addr, uint8_t ep_addr); + +// determine if a queue head has bus-related error +static inline bool qhd_has_xact_error (ehci_qhd_t * p_qhd) +{ + return (p_qhd->qtd_overlay.buffer_err || p_qhd->qtd_overlay.babble_err || p_qhd->qtd_overlay.xact_err); + //p_qhd->qtd_overlay.non_hs_period_missed_uframe || p_qhd->qtd_overlay.pingstate_err TODO split transaction error +} + +static void qhd_init (ehci_qhd_t *p_qhd, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc); + +static inline ehci_qtd_t* qtd_find_free (void); +static inline ehci_qtd_t* qtd_next (ehci_qtd_t const * p_qtd); +static inline void qtd_insert_to_qhd (ehci_qhd_t *p_qhd, ehci_qtd_t *p_qtd_new); +static inline void qtd_remove_1st_from_qhd (ehci_qhd_t *p_qhd); +static void qtd_init (ehci_qtd_t* p_qtd, void* buffer, uint16_t total_bytes); + +static inline void list_insert (ehci_link_t *current, ehci_link_t *new, uint8_t new_type); +static inline ehci_link_t* list_next (ehci_link_t *p_link_pointer); + +static bool ehci_init (uint8_t rhport); + +//--------------------------------------------------------------------+ +// HCD API +//--------------------------------------------------------------------+ +bool hcd_init(void) +{ + tu_memclr(&ehci_data, sizeof(ehci_data_t)); + return ehci_init(TUH_OPT_RHPORT); +} + +void hcd_port_reset(uint8_t rhport) +{ + (void) rhport; + + ehci_registers_t* regs = ehci_data.regs; + + regs->portsc_bm.port_enabled = 0; // disable port before reset + regs->portsc_bm.port_reset = 1; +} + +bool hcd_port_connect_status(uint8_t rhport) +{ + (void) rhport; + return ehci_data.regs->portsc_bm.current_connect_status; +} + +tusb_speed_t hcd_port_speed_get(uint8_t rhport) +{ + (void) rhport; + return (tusb_speed_t) ehci_data.regs->portsc_bm.nxp_port_speed; // NXP specific port speed +} + +static void list_remove_qhd_by_addr(ehci_link_t* list_head, uint8_t dev_addr) +{ + for(ehci_link_t* prev = list_head; + !prev->terminate && (tu_align32(prev->address) != (uint32_t) list_head); + prev = list_next(prev) ) + { + // TODO check type for ISO iTD and siTD + ehci_qhd_t* qhd = (ehci_qhd_t*) list_next(prev); + if ( qhd->dev_addr == dev_addr ) + { + // TODO deactive all TD, wait for QHD to inactive before removal + prev->address = qhd->next.address; + + // EHCI 4.8.2 link the removed qhd to async head (which always reachable by Host Controller) + qhd->next.address = ((uint32_t) list_head) | (EHCI_QTYPE_QHD << 1); + + if ( qhd->int_smask ) + { + // period list queue element is guarantee to be free in the next frame (1 ms) + qhd->used = 0; + }else + { + // async list use async advance handshake + // mark as removing, will completely re-usable when async advance isr occurs + qhd->removing = 1; + } + } + } +} + +// Close all opened endpoint belong to this device +void hcd_device_close(uint8_t rhport, uint8_t dev_addr) +{ + // skip dev0 + if (dev_addr == 0) return; + + // Remove from async list + list_remove_qhd_by_addr( (ehci_link_t*) qhd_async_head(rhport), dev_addr ); + + // Remove from all interval period list + for(uint8_t i = 0; i < TU_ARRAY_SIZE(ehci_data.period_head_arr); i++) + { + list_remove_qhd_by_addr( (ehci_link_t*) &ehci_data.period_head_arr[i], dev_addr); + } + + // Async doorbell (EHCI 4.8.2 for operational details) + ehci_data.regs->command_bm.async_adv_doorbell = 1; +} + +// EHCI controller init +static bool ehci_init(uint8_t rhport) +{ + ehci_data.regs = (ehci_registers_t* ) hcd_ehci_register_addr(rhport); + + ehci_registers_t* regs = ehci_data.regs; + + //------------- CTRLDSSEGMENT Register (skip) -------------// + //------------- USB INT Register -------------// + regs->inten = 0; // 1. disable all the interrupt + regs->status = EHCI_INT_MASK_ALL; // 2. clear all status + + regs->inten = EHCI_INT_MASK_ERROR | EHCI_INT_MASK_PORT_CHANGE | EHCI_INT_MASK_ASYNC_ADVANCE | + EHCI_INT_MASK_NXP_PERIODIC | EHCI_INT_MASK_NXP_ASYNC ; + + //------------- Asynchronous List -------------// + ehci_qhd_t * const async_head = qhd_async_head(rhport); + tu_memclr(async_head, sizeof(ehci_qhd_t)); + + async_head->next.address = (uint32_t) async_head; // circular list, next is itself + async_head->next.type = EHCI_QTYPE_QHD; + async_head->head_list_flag = 1; + async_head->qtd_overlay.halted = 1; // inactive most of time + async_head->qtd_overlay.next.terminate = 1; // TODO removed if verified + + regs->async_list_addr = (uint32_t) async_head; + + //------------- Periodic List -------------// + // Build the polling interval tree with 1 ms, 2 ms, 4 ms and 8 ms (framesize) only + for(uint32_t i=0; i<4; i++) + { + ehci_data.period_head_arr[i].int_smask = 1; // queue head in period list must have smask non-zero + ehci_data.period_head_arr[i].qtd_overlay.halted = 1; // dummy node, always inactive + } + + ehci_link_t * const framelist = ehci_data.period_framelist; + ehci_link_t * const period_1ms = get_period_head(rhport, 1); + // all links --> period_head_arr[0] (1ms) + // 0, 2, 4, 6 etc --> period_head_arr[1] (2ms) + // 1, 5 --> period_head_arr[2] (4ms) + // 3 --> period_head_arr[3] (8ms) + + // TODO EHCI_FRAMELIST_SIZE with other size than 8 + for(uint32_t i=0; i<EHCI_FRAMELIST_SIZE; i++) + { + framelist[i].address = (uint32_t) period_1ms; + framelist[i].type = EHCI_QTYPE_QHD; + } + + for(uint32_t i=0; i<EHCI_FRAMELIST_SIZE; i+=2) + { + list_insert(framelist + i, get_period_head(rhport, 2), EHCI_QTYPE_QHD); + } + + for(uint32_t i=1; i<EHCI_FRAMELIST_SIZE; i+=4) + { + list_insert(framelist + i, get_period_head(rhport, 4), EHCI_QTYPE_QHD); + } + + list_insert(framelist+3, get_period_head(rhport, 8), EHCI_QTYPE_QHD); + + period_1ms->terminate = 1; + + regs->periodic_list_base = (uint32_t) framelist; + + //------------- TT Control (NXP only) -------------// + regs->nxp_tt_control = 0; + + //------------- USB CMD Register -------------// + regs->command |= TU_BIT(EHCI_USBCMD_POS_RUN_STOP) | TU_BIT(EHCI_USBCMD_POS_ASYNC_ENABLE) + | TU_BIT(EHCI_USBCMD_POS_PERIOD_ENABLE) // TODO enable period list only there is int/iso endpoint + | ((EHCI_CFG_FRAMELIST_SIZE_BITS & TU_BIN8(011)) << EHCI_USBCMD_POS_FRAMELIST_SZIE) + | ((EHCI_CFG_FRAMELIST_SIZE_BITS >> 2) << EHCI_USBCMD_POS_NXP_FRAMELIST_SIZE_MSB); + + //------------- ConfigFlag Register (skip) -------------// + regs->portsc_bm.port_power = 1; // enable port power + + return true; +} + +#if 0 +static void ehci_stop(uint8_t rhport) +{ + (void) rhport; + + ehci_registers_t* regs = ehci_data.regs; + + regs->command_bm.run_stop = 0; + + // USB Spec: controller has to stop within 16 uframe = 2 frames + while( regs->status_bm.hc_halted == 0 ) {} +} +#endif + +//--------------------------------------------------------------------+ +// CONTROL PIPE API +//--------------------------------------------------------------------+ +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) +{ + (void) rhport; + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + // FIXME control only for now + if ( epnum == 0 ) + { + ehci_qhd_t* qhd = qhd_control(dev_addr); + ehci_qtd_t* qtd = qtd_control(dev_addr); + + qtd_init(qtd, buffer, buflen); + + // first first data toggle is always 1 (data & setup stage) + qtd->data_toggle = 1; + qtd->pid = dir ? EHCI_PID_IN : EHCI_PID_OUT; + qtd->int_on_complete = 1; + qtd->next.terminate = 1; + + // sw region + qhd->p_qtd_list_head = qtd; + qhd->p_qtd_list_tail = qtd; + + // attach TD + qhd->qtd_overlay.next.address = (uint32_t) qtd; + } + + return true; +} + +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) +{ + (void) rhport; + + ehci_qhd_t* qhd = &ehci_data.control[dev_addr].qhd; + ehci_qtd_t* td = &ehci_data.control[dev_addr].qtd; + + qtd_init(td, (void*) setup_packet, 8); + td->pid = EHCI_PID_SETUP; + td->int_on_complete = 1; + td->next.terminate = 1; + + // sw region + qhd->p_qtd_list_head = td; + qhd->p_qtd_list_tail = td; + + // attach TD + qhd->qtd_overlay.next.address = (uint32_t) td; + + return true; +} + +//--------------------------------------------------------------------+ +// BULK/INT/ISO PIPE API +//--------------------------------------------------------------------+ +bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc) +{ + (void) rhport; + + // TODO not support ISO yet + TU_ASSERT (ep_desc->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS); + + //------------- Prepare Queue Head -------------// + ehci_qhd_t * p_qhd; + + if ( ep_desc->bEndpointAddress == 0 ) + { + p_qhd = qhd_control(dev_addr); + }else + { + p_qhd = qhd_find_free(); + } + TU_ASSERT(p_qhd); + + qhd_init(p_qhd, dev_addr, ep_desc); + + // control of dev0 is always present as async head + if ( dev_addr == 0 ) return true; + + // Insert to list + ehci_link_t * list_head; + + switch (ep_desc->bmAttributes.xfer) + { + case TUSB_XFER_CONTROL: + case TUSB_XFER_BULK: + list_head = (ehci_link_t*) qhd_async_head(rhport); + break; + + case TUSB_XFER_INTERRUPT: + list_head = get_period_head(rhport, p_qhd->interval_ms); + break; + + case TUSB_XFER_ISOCHRONOUS: + // TODO iso is not supported + break; + + default: break; + } + + // TODO might need to disable async/period list + list_insert( list_head, (ehci_link_t*) p_qhd, EHCI_QTYPE_QHD); + + return true; +} + +bool hcd_pipe_queue_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes) +{ + //------------- set up QTD -------------// + ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr); + ehci_qtd_t *p_qtd = qtd_find_free(); + + TU_ASSERT(p_qtd); + + qtd_init(p_qtd, buffer, total_bytes); + p_qtd->pid = p_qhd->pid; + + //------------- insert TD to TD list -------------// + qtd_insert_to_qhd(p_qhd, p_qtd); + + return true; +} + +bool hcd_pipe_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes, bool int_on_complete) +{ + TU_ASSERT ( hcd_pipe_queue_xfer(dev_addr, ep_addr, buffer, total_bytes) ); + + ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr); + + if ( int_on_complete ) + { // the just added qtd is pointed by list_tail + p_qhd->p_qtd_list_tail->int_on_complete = 1; + } + p_qhd->qtd_overlay.next.address = (uint32_t) p_qhd->p_qtd_list_head; // attach head QTD to QHD start transferring + + return true; +} + +bool hcd_edpt_busy(uint8_t dev_addr, uint8_t ep_addr) +{ + ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr); + return !p_qhd->qtd_overlay.halted && (p_qhd->p_qtd_list_head != NULL); +} + +bool hcd_edpt_stalled(uint8_t dev_addr, uint8_t ep_addr) +{ + ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr); + return p_qhd->qtd_overlay.halted && !qhd_has_xact_error(p_qhd); +} + +bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr) +{ + ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr); + p_qhd->qtd_overlay.halted = 0; + // TODO reset data toggle ? + return true; +} + +//--------------------------------------------------------------------+ +// EHCI Interrupt Handler +//--------------------------------------------------------------------+ + +// async_advance is handshake between usb stack & ehci controller. +// This isr mean it is safe to modify previously removed queue head from async list. +// In tinyusb, queue head is only removed when device is unplugged. +static void async_advance_isr(uint8_t rhport) +{ + (void) rhport; + + ehci_qhd_t* qhd_pool = ehci_data.qhd_pool; + for(uint32_t i = 0; i < HCD_MAX_ENDPOINT; i++) + { + if ( qhd_pool[i].removing ) + { + qhd_pool[i].removing = 0; + qhd_pool[i].used = 0; + } + } +} + +static void port_connect_status_change_isr(uint8_t hostid) +{ + // NOTE There is an sequence plug->unplug->…..-> plug if device is powering with pre-plugged device + if (ehci_data.regs->portsc_bm.current_connect_status) + { + hcd_port_reset(hostid); + hcd_event_device_attach(hostid); + }else // device unplugged + { + hcd_event_device_remove(hostid); + } +} + +static void qhd_xfer_complete_isr(ehci_qhd_t * p_qhd) +{ + // free all TDs from the head td to the first active TD + while(p_qhd->p_qtd_list_head != NULL && !p_qhd->p_qtd_list_head->active) + { + // TD need to be freed and removed from qhd, before invoking callback + bool is_ioc = (p_qhd->p_qtd_list_head->int_on_complete != 0); + p_qhd->total_xferred_bytes += p_qhd->p_qtd_list_head->expected_bytes - p_qhd->p_qtd_list_head->total_bytes; + + p_qhd->p_qtd_list_head->used = 0; // free QTD + qtd_remove_1st_from_qhd(p_qhd); + + if (is_ioc) + { + // end of request + // call USBH callback + hcd_event_xfer_complete(p_qhd->dev_addr, tu_edpt_addr(p_qhd->ep_number, p_qhd->pid == EHCI_PID_IN ? 1 : 0), XFER_RESULT_SUCCESS, p_qhd->total_xferred_bytes); + p_qhd->total_xferred_bytes = 0; + } + } +} + +static void async_list_xfer_complete_isr(ehci_qhd_t * const async_head) +{ + ehci_qhd_t *p_qhd = async_head; + do + { + if ( !p_qhd->qtd_overlay.halted ) // halted or error is processed in error isr + { + qhd_xfer_complete_isr(p_qhd); + } + p_qhd = qhd_next(p_qhd); + }while(p_qhd != async_head); // async list traversal, stop if loop around +} + +static void period_list_xfer_complete_isr(uint8_t hostid, uint8_t interval_ms) +{ + uint8_t max_loop = 0; + uint32_t const period_1ms_addr = (uint32_t) get_period_head(hostid, 1); + ehci_link_t next_item = * get_period_head(hostid, interval_ms); + + // TODO abstract max loop guard for period + while( !next_item.terminate && + !(interval_ms > 1 && period_1ms_addr == tu_align32(next_item.address)) && + max_loop < (HCD_MAX_ENDPOINT + EHCI_MAX_ITD + EHCI_MAX_SITD)*CFG_TUSB_HOST_DEVICE_MAX) + { + switch ( next_item.type ) + { + case EHCI_QTYPE_QHD: + { + ehci_qhd_t *p_qhd_int = (ehci_qhd_t *) tu_align32(next_item.address); + if ( !p_qhd_int->qtd_overlay.halted ) + { + qhd_xfer_complete_isr(p_qhd_int); + } + } + break; + + case EHCI_QTYPE_ITD: // TODO support hs/fs ISO + case EHCI_QTYPE_SITD: + case EHCI_QTYPE_FSTN: + + default: break; + } + + next_item = *list_next(&next_item); + max_loop++; + } +} + +static void qhd_xfer_error_isr(ehci_qhd_t * p_qhd) +{ + if ( (p_qhd->dev_addr != 0 && p_qhd->qtd_overlay.halted) || // addr0 cannot be protocol STALL + qhd_has_xact_error(p_qhd) ) + { + // current qhd has error in transaction + xfer_result_t error_event; + + // no error bits are set, endpoint is halted due to STALL + error_event = qhd_has_xact_error(p_qhd) ? XFER_RESULT_FAILED : XFER_RESULT_STALLED; + + p_qhd->total_xferred_bytes += p_qhd->p_qtd_list_head->expected_bytes - p_qhd->p_qtd_list_head->total_bytes; + +// if ( XFER_RESULT_FAILED == error_event ) TU_BREAKPOINT(); // TODO skip unplugged device + + p_qhd->p_qtd_list_head->used = 0; // free QTD + qtd_remove_1st_from_qhd(p_qhd); + + if ( 0 == p_qhd->ep_number ) + { + // control cannot be halted --> clear all qtd list + p_qhd->p_qtd_list_head = NULL; + p_qhd->p_qtd_list_tail = NULL; + + p_qhd->qtd_overlay.next.terminate = 1; + p_qhd->qtd_overlay.alternate.terminate = 1; + p_qhd->qtd_overlay.halted = 0; + + ehci_qtd_t *p_setup = qtd_control(p_qhd->dev_addr); + p_setup->used = 0; + } + + // call USBH callback + hcd_event_xfer_complete(p_qhd->dev_addr, tu_edpt_addr(p_qhd->ep_number, p_qhd->pid == EHCI_PID_IN ? 1 : 0), error_event, p_qhd->total_xferred_bytes); + + p_qhd->total_xferred_bytes = 0; + } +} + +static void xfer_error_isr(uint8_t hostid) +{ + //------------- async list -------------// + ehci_qhd_t * const async_head = qhd_async_head(hostid); + ehci_qhd_t *p_qhd = async_head; + do + { + qhd_xfer_error_isr( p_qhd ); + p_qhd = qhd_next(p_qhd); + }while(p_qhd != async_head); // async list traversal, stop if loop around + + //------------- TODO refractor period list -------------// + uint32_t const period_1ms_addr = (uint32_t) get_period_head(hostid, 1); + for (uint8_t interval_ms=1; interval_ms <= EHCI_FRAMELIST_SIZE; interval_ms *= 2) + { + ehci_link_t next_item = * get_period_head(hostid, interval_ms); + + // TODO abstract max loop guard for period + while( !next_item.terminate && + !(interval_ms > 1 && period_1ms_addr == tu_align32(next_item.address)) ) + { + switch ( next_item.type ) + { + case EHCI_QTYPE_QHD: + { + ehci_qhd_t *p_qhd_int = (ehci_qhd_t *) tu_align32(next_item.address); + qhd_xfer_error_isr(p_qhd_int); + } + break; + + // TODO support hs/fs ISO + case EHCI_QTYPE_ITD: + case EHCI_QTYPE_SITD: + case EHCI_QTYPE_FSTN: + default: break; + } + + next_item = *list_next(&next_item); + } + } +} + +//------------- Host Controller Driver's Interrupt Handler -------------// +void hcd_isr(uint8_t rhport) +{ + ehci_registers_t* regs = ehci_data.regs; + + uint32_t int_status = regs->status; + int_status &= regs->inten; + + regs->status |= int_status; // Acknowledge handled interrupt + + if (int_status == 0) return; + + if (int_status & EHCI_INT_MASK_PORT_CHANGE) + { + uint32_t port_status = regs->portsc & EHCI_PORTSC_MASK_ALL; + + if (regs->portsc_bm.connect_status_change) + { + port_connect_status_change_isr(rhport); + } + + regs->portsc |= port_status; // Acknowledge change bits in portsc + } + + if (int_status & EHCI_INT_MASK_ERROR) + { + xfer_error_isr(rhport); + } + + //------------- some QTD/SITD/ITD with IOC set is completed -------------// + if (int_status & EHCI_INT_MASK_NXP_ASYNC) + { + async_list_xfer_complete_isr( qhd_async_head(rhport) ); + } + + if (int_status & EHCI_INT_MASK_NXP_PERIODIC) + { + for (uint8_t i=1; i <= EHCI_FRAMELIST_SIZE; i *= 2) + { + period_list_xfer_complete_isr( rhport, i ); + } + } + + //------------- There is some removed async previously -------------// + if (int_status & EHCI_INT_MASK_ASYNC_ADVANCE) // need to place after EHCI_INT_MASK_NXP_ASYNC + { + async_advance_isr(rhport); + } +} + +//--------------------------------------------------------------------+ +// HELPER +//--------------------------------------------------------------------+ + + +//------------- queue head helper -------------// +static inline ehci_qhd_t* qhd_find_free (void) +{ + for (uint32_t i=0; i<HCD_MAX_ENDPOINT; i++) + { + if ( !ehci_data.qhd_pool[i].used ) return &ehci_data.qhd_pool[i]; + } + + return NULL; +} + +static inline ehci_qhd_t* qhd_next(ehci_qhd_t const * p_qhd) +{ + return (ehci_qhd_t*) tu_align32(p_qhd->next.address); +} + +static inline ehci_qhd_t* qhd_get_from_addr(uint8_t dev_addr, uint8_t ep_addr) +{ + ehci_qhd_t* qhd_pool = ehci_data.qhd_pool; + + for(uint32_t i=0; i<HCD_MAX_ENDPOINT; i++) + { + if ( (qhd_pool[i].dev_addr == dev_addr) && + ep_addr == tu_edpt_addr(qhd_pool[i].ep_number, qhd_pool[i].pid) ) + { + return &qhd_pool[i]; + } + } + + return NULL; +} + +//------------- TD helper -------------// +static inline ehci_qtd_t* qtd_find_free(void) +{ + for (uint32_t i=0; i<HCD_MAX_XFER; i++) + { + if ( !ehci_data.qtd_pool[i].used ) return &ehci_data.qtd_pool[i]; + } + + return NULL; +} + +static inline ehci_qtd_t* qtd_next(ehci_qtd_t const * p_qtd ) +{ + return (ehci_qtd_t*) tu_align32(p_qtd->next.address); +} + +static inline void qtd_remove_1st_from_qhd(ehci_qhd_t *p_qhd) +{ + if (p_qhd->p_qtd_list_head == p_qhd->p_qtd_list_tail) // last TD --> make it NULL + { + p_qhd->p_qtd_list_head = p_qhd->p_qtd_list_tail = NULL; + }else + { + p_qhd->p_qtd_list_head = qtd_next( p_qhd->p_qtd_list_head ); + } +} + +static inline void qtd_insert_to_qhd(ehci_qhd_t *p_qhd, ehci_qtd_t *p_qtd_new) +{ + if (p_qhd->p_qtd_list_head == NULL) // empty list + { + p_qhd->p_qtd_list_head = p_qhd->p_qtd_list_tail = p_qtd_new; + }else + { + p_qhd->p_qtd_list_tail->next.address = (uint32_t) p_qtd_new; + p_qhd->p_qtd_list_tail = p_qtd_new; + } +} + +static void qhd_init(ehci_qhd_t *p_qhd, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc) +{ + // address 0 is used as async head, which always on the list --> cannot be cleared (ehci halted otherwise) + if (dev_addr != 0) + { + tu_memclr(p_qhd, sizeof(ehci_qhd_t)); + } + + uint8_t const xfer_type = ep_desc->bmAttributes.xfer; + uint8_t const interval = ep_desc->bInterval; + + p_qhd->dev_addr = dev_addr; + p_qhd->fl_inactive_next_xact = 0; + p_qhd->ep_number = tu_edpt_number(ep_desc->bEndpointAddress); + p_qhd->ep_speed = _usbh_devices[dev_addr].speed; + p_qhd->data_toggle_control= (xfer_type == TUSB_XFER_CONTROL) ? 1 : 0; + p_qhd->head_list_flag = (dev_addr == 0) ? 1 : 0; // addr0's endpoint is the static asyn list head + p_qhd->max_packet_size = ep_desc->wMaxPacketSize.size; + p_qhd->fl_ctrl_ep_flag = ((xfer_type == TUSB_XFER_CONTROL) && (p_qhd->ep_speed != TUSB_SPEED_HIGH)) ? 1 : 0; + p_qhd->nak_reload = 0; + + // Bulk/Control -> smask = cmask = 0 + // TODO Isochronous + if (TUSB_XFER_INTERRUPT == xfer_type) + { + if (TUSB_SPEED_HIGH == p_qhd->ep_speed) + { + TU_ASSERT( interval <= 16, ); + if ( interval < 4) // sub milisecond interval + { + p_qhd->interval_ms = 0; + p_qhd->int_smask = (interval == 1) ? TU_BIN8(11111111) : + (interval == 2) ? TU_BIN8(10101010) : TU_BIN8(01000100); + }else + { + p_qhd->interval_ms = (uint8_t) tu_min16( 1 << (interval-4), 255 ); + p_qhd->int_smask = TU_BIT(interval % 8); + } + }else + { + TU_ASSERT( 0 != interval, ); + // Full/Low: 4.12.2.1 (EHCI) case 1 schedule start split at 1 us & complete split at 2,3,4 uframes + p_qhd->int_smask = 0x01; + p_qhd->fl_int_cmask = TU_BIN8(11100); + p_qhd->interval_ms = interval; + } + }else + { + p_qhd->int_smask = p_qhd->fl_int_cmask = 0; + } + + p_qhd->fl_hub_addr = _usbh_devices[dev_addr].hub_addr; + p_qhd->fl_hub_port = _usbh_devices[dev_addr].hub_port; + p_qhd->mult = 1; // TODO not use high bandwidth/park mode yet + + //------------- HCD Management Data -------------// + p_qhd->used = 1; + p_qhd->removing = 0; + p_qhd->p_qtd_list_head = NULL; + p_qhd->p_qtd_list_tail = NULL; + p_qhd->pid = tu_edpt_dir(ep_desc->bEndpointAddress) ? EHCI_PID_IN : EHCI_PID_OUT; // PID for TD under this endpoint + + //------------- active, but no TD list -------------// + p_qhd->qtd_overlay.halted = 0; + p_qhd->qtd_overlay.next.terminate = 1; + p_qhd->qtd_overlay.alternate.terminate = 1; + if (TUSB_XFER_BULK == xfer_type && p_qhd->ep_speed == TUSB_SPEED_HIGH && p_qhd->pid == EHCI_PID_OUT) + { + p_qhd->qtd_overlay.ping_err = 1; // do PING for Highspeed Bulk OUT, EHCI section 4.11 + } +} + +static void qtd_init(ehci_qtd_t* p_qtd, void* buffer, uint16_t total_bytes) +{ + tu_memclr(p_qtd, sizeof(ehci_qtd_t)); + + p_qtd->used = 1; + + p_qtd->next.terminate = 1; // init to null + p_qtd->alternate.terminate = 1; // not used, always set to terminated + p_qtd->active = 1; + p_qtd->err_count = 3; // TODO 3 consecutive errors tolerance + p_qtd->data_toggle = 0; + p_qtd->total_bytes = total_bytes; + p_qtd->expected_bytes = total_bytes; + + p_qtd->buffer[0] = (uint32_t) buffer; + for(uint8_t i=1; i<5; i++) + { + p_qtd->buffer[i] |= tu_align4k( p_qtd->buffer[i-1] ) + 4096; + } +} + +//------------- List Managing Helper -------------// +static inline void list_insert(ehci_link_t *current, ehci_link_t *new, uint8_t new_type) +{ + new->address = current->address; + current->address = ((uint32_t) new) | (new_type << 1); +} + +static inline ehci_link_t* list_next(ehci_link_t *p_link_pointer) +{ + return (ehci_link_t*) tu_align32(p_link_pointer->address); +} + +#endif diff --git a/src/host/hcd.h b/src/host/hcd.h index 86e7d664b..a39e7fe16 100644 --- a/src/host/hcd.h +++ b/src/host/hcd.h @@ -1,146 +1,147 @@ -/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2019 Ha Thach (tinyusb.org)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * This file is part of the TinyUSB stack.
- */
-
-/** \ingroup group_usbh
- * \defgroup Group_HCD Host Controller Driver (HCD)
- * @{ */
-
-#ifndef _TUSB_HCD_H_
-#define _TUSB_HCD_H_
-
-#include <common/tusb_common.h>
-
-#ifdef __cplusplus
- extern "C" {
-#endif
-
- //--------------------------------------------------------------------+
-// MACRO CONSTANT TYPEDEF
-//--------------------------------------------------------------------+
-typedef enum
-{
- HCD_EVENT_DEVICE_ATTACH,
- HCD_EVENT_DEVICE_REMOVE,
- HCD_EVENT_XFER_COMPLETE,
-} hcd_eventid_t;
-
-typedef struct
-{
- uint8_t rhport;
- uint8_t event_id;
-
- union
- {
- struct
- {
- uint8_t hub_addr;
- uint8_t hub_port;
- } attach, remove;
-
- struct
- {
- uint8_t ep_addr;
- uint8_t result;
- uint32_t len;
- } xfer_complete;
- };
-
-} hcd_event_t;
-
-#if TUSB_OPT_HOST_ENABLED
-// Max number of endpoints per device
-enum {
- HCD_MAX_ENDPOINT = CFG_TUSB_HOST_DEVICE_MAX*(CFG_TUH_HUB + CFG_TUH_HID_KEYBOARD + CFG_TUH_HID_MOUSE + CFG_TUSB_HOST_HID_GENERIC +
- CFG_TUH_MSC*2 + CFG_TUH_CDC*3),
-
- HCD_MAX_XFER = HCD_MAX_ENDPOINT*2,
-};
-
-//#define HCD_MAX_ENDPOINT 16
-//#define HCD_MAX_XFER 16
-#endif
-
-//--------------------------------------------------------------------+
-// HCD API
-//--------------------------------------------------------------------+
-bool hcd_init(void);
-void hcd_int_enable (uint8_t rhport);
-void hcd_int_disable(uint8_t rhport);
-
-// PORT API
-/// return the current connect status of roothub port
-bool hcd_port_connect_status(uint8_t hostid);
-void hcd_port_reset(uint8_t hostid);
-tusb_speed_t hcd_port_speed_get(uint8_t hostid);
-
-// HCD closes all opened endpoints belong to this device
-void hcd_device_close(uint8_t rhport, uint8_t dev_addr);
-
-//--------------------------------------------------------------------+
-// Event function
-//--------------------------------------------------------------------+
-void hcd_event_handler(hcd_event_t const* event, bool in_isr);
-
-// Helper to send device attach event
-void hcd_event_device_attach(uint8_t rhport);
-
-// Helper to send device removal event
-void hcd_event_device_remove(uint8_t rhport);
-
-// Helper to send USB transfer event
-void hcd_event_xfer_complete(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes);
-
-//--------------------------------------------------------------------+
-// Endpoints API
-//--------------------------------------------------------------------+
-bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]);
-bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc);
-
-bool hcd_edpt_busy(uint8_t dev_addr, uint8_t ep_addr);
-bool hcd_edpt_stalled(uint8_t dev_addr, uint8_t ep_addr);
-bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr);
-
-// TODO merge with pipe_xfer
-bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen);
-
-//--------------------------------------------------------------------+
-// PIPE API
-//--------------------------------------------------------------------+
-// TODO control xfer should be used via usbh layer
-bool hcd_pipe_queue_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes); // only queue, not transferring yet
-bool hcd_pipe_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes, bool int_on_complete);
-
-#if 0
-tusb_error_t hcd_pipe_cancel();
-#endif
-
-#ifdef __cplusplus
- }
-#endif
-
-#endif /* _TUSB_HCD_H_ */
-
-/// @}
+/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +/** \ingroup group_usbh + * \defgroup Group_HCD Host Controller Driver (HCD) + * @{ */ + +#ifndef _TUSB_HCD_H_ +#define _TUSB_HCD_H_ + +#include <common/tusb_common.h> + +#ifdef __cplusplus + extern "C" { +#endif + + //--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef enum +{ + HCD_EVENT_DEVICE_ATTACH, + HCD_EVENT_DEVICE_REMOVE, + HCD_EVENT_XFER_COMPLETE, +} hcd_eventid_t; + +typedef struct +{ + uint8_t rhport; + uint8_t event_id; + + union + { + struct + { + uint8_t hub_addr; + uint8_t hub_port; + } attach, remove; + + struct + { + uint8_t ep_addr; + uint8_t result; + uint32_t len; + } xfer_complete; + }; + +} hcd_event_t; + +#if TUSB_OPT_HOST_ENABLED +// Max number of endpoints per device +enum { + HCD_MAX_ENDPOINT = CFG_TUSB_HOST_DEVICE_MAX*(CFG_TUH_HUB + CFG_TUH_HID_KEYBOARD + CFG_TUH_HID_MOUSE + CFG_TUSB_HOST_HID_GENERIC + + CFG_TUH_MSC*2 + CFG_TUH_CDC*3), + + HCD_MAX_XFER = HCD_MAX_ENDPOINT*2, +}; + +//#define HCD_MAX_ENDPOINT 16 +//#define HCD_MAX_XFER 16 +#endif + +//--------------------------------------------------------------------+ +// HCD API +//--------------------------------------------------------------------+ +bool hcd_init(void); +void hcd_isr(uint8_t hostid); +void hcd_int_enable (uint8_t rhport); +void hcd_int_disable(uint8_t rhport); + +// PORT API +/// return the current connect status of roothub port +bool hcd_port_connect_status(uint8_t hostid); +void hcd_port_reset(uint8_t hostid); +tusb_speed_t hcd_port_speed_get(uint8_t hostid); + +// HCD closes all opened endpoints belong to this device +void hcd_device_close(uint8_t rhport, uint8_t dev_addr); + +//--------------------------------------------------------------------+ +// Event function +//--------------------------------------------------------------------+ +void hcd_event_handler(hcd_event_t const* event, bool in_isr); + +// Helper to send device attach event +void hcd_event_device_attach(uint8_t rhport); + +// Helper to send device removal event +void hcd_event_device_remove(uint8_t rhport); + +// Helper to send USB transfer event +void hcd_event_xfer_complete(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); + +//--------------------------------------------------------------------+ +// Endpoints API +//--------------------------------------------------------------------+ +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]); +bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc); + +bool hcd_edpt_busy(uint8_t dev_addr, uint8_t ep_addr); +bool hcd_edpt_stalled(uint8_t dev_addr, uint8_t ep_addr); +bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr); + +// TODO merge with pipe_xfer +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen); + +//--------------------------------------------------------------------+ +// PIPE API +//--------------------------------------------------------------------+ +// TODO control xfer should be used via usbh layer +bool hcd_pipe_queue_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes); // only queue, not transferring yet +bool hcd_pipe_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes, bool int_on_complete); + +#if 0 +tusb_error_t hcd_pipe_cancel(); +#endif + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_HCD_H_ */ + +/// @} diff --git a/src/host/hub.c b/src/host/hub.c index a2d4e8429..00450b704 100644 --- a/src/host/hub.c +++ b/src/host/hub.c @@ -1,250 +1,250 @@ -/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2019 Ha Thach (tinyusb.org)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * This file is part of the TinyUSB stack.
- */
-
-#include "tusb_option.h"
-
-#if (TUSB_OPT_HOST_ENABLED && CFG_TUH_HUB)
-
-//--------------------------------------------------------------------+
-// INCLUDE
-//--------------------------------------------------------------------+
-#include "hub.h"
-
-//--------------------------------------------------------------------+
-// MACRO CONSTANT TYPEDEF
-//--------------------------------------------------------------------+
-typedef struct
-{
- uint8_t itf_num;
- uint8_t ep_status;
- uint8_t port_number;
- uint8_t status_change; // data from status change interrupt endpoint
-}usbh_hub_t;
-
-CFG_TUSB_MEM_SECTION static usbh_hub_t hub_data[CFG_TUSB_HOST_DEVICE_MAX];
-TU_ATTR_ALIGNED(4) CFG_TUSB_MEM_SECTION static uint8_t hub_enum_buffer[sizeof(descriptor_hub_desc_t)];
-
-//OSAL_SEM_DEF(hub_enum_semaphore);
-//static osal_semaphore_handle_t hub_enum_sem_hdl;
-
-//--------------------------------------------------------------------+
-// HUB
-//--------------------------------------------------------------------+
-bool hub_port_clear_feature_subtask(uint8_t hub_addr, uint8_t hub_port, uint8_t feature)
-{
- TU_ASSERT(HUB_FEATURE_PORT_CONNECTION_CHANGE <= feature && feature <= HUB_FEATURE_PORT_RESET_CHANGE);
-
- tusb_control_request_t request = {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_OUT },
- .bRequest = HUB_REQUEST_CLEAR_FEATURE,
- .wValue = feature,
- .wIndex = hub_port,
- .wLength = 0
- };
-
- //------------- Clear Port Feature request -------------//
- TU_ASSERT( usbh_control_xfer( hub_addr, &request, NULL ) );
-
- //------------- Get Port Status to check if feature is cleared -------------//
- request = (tusb_control_request_t ) {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN },
- .bRequest = HUB_REQUEST_GET_STATUS,
- .wValue = 0,
- .wIndex = hub_port,
- .wLength = 4
- };
-
- TU_ASSERT( usbh_control_xfer( hub_addr, &request, hub_enum_buffer ) );
-
- //------------- Check if feature is cleared -------------//
- hub_port_status_response_t * p_port_status;
- p_port_status = (hub_port_status_response_t *) hub_enum_buffer;
-
- TU_ASSERT( !tu_bit_test(p_port_status->status_change.value, feature-16) );
-
- return true;
-}
-
-bool hub_port_reset_subtask(uint8_t hub_addr, uint8_t hub_port)
-{
- enum { RESET_DELAY = 200 }; // USB specs say only 50ms but many devices require much longer
-
- //------------- Set Port Reset -------------//
- tusb_control_request_t request = {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_OUT },
- .bRequest = HUB_REQUEST_SET_FEATURE,
- .wValue = HUB_FEATURE_PORT_RESET,
- .wIndex = hub_port,
- .wLength = 0
- };
-
- TU_ASSERT( usbh_control_xfer( hub_addr, &request, NULL ) );
-
- osal_task_delay(RESET_DELAY); // TODO Hub wait for Status Endpoint on Reset Change
-
- //------------- Get Port Status to check if port is enabled, powered and reset_change -------------//
- request = (tusb_control_request_t ) {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN },
- .bRequest = HUB_REQUEST_GET_STATUS,
- .wValue = 0,
- .wIndex = hub_port,
- .wLength = 4
- };
-
- TU_ASSERT( usbh_control_xfer( hub_addr, &request, hub_enum_buffer ) );
-
- hub_port_status_response_t * p_port_status;
- p_port_status = (hub_port_status_response_t *) hub_enum_buffer;
-
- TU_ASSERT ( p_port_status->status_change.reset && p_port_status->status_current.connect_status &&
- p_port_status->status_current.port_power && p_port_status->status_current.port_enable);
-
- return true;
-}
-
-// can only get the speed RIGHT AFTER hub_port_reset_subtask call
-tusb_speed_t hub_port_get_speed(void)
-{
- hub_port_status_response_t * p_port_status = (hub_port_status_response_t *) hub_enum_buffer;
- return (p_port_status->status_current.high_speed_device_attached) ? TUSB_SPEED_HIGH :
- (p_port_status->status_current.low_speed_device_attached ) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL;
-}
-
-//--------------------------------------------------------------------+
-// CLASS-USBH API (don't require to verify parameters)
-//--------------------------------------------------------------------+
-void hub_init(void)
-{
- tu_memclr(hub_data, CFG_TUSB_HOST_DEVICE_MAX*sizeof(usbh_hub_t));
-// hub_enum_sem_hdl = osal_semaphore_create( OSAL_SEM_REF(hub_enum_semaphore) );
-}
-
-bool hub_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t *p_length)
-{
- // not support multiple TT yet
- if ( itf_desc->bInterfaceProtocol > 1 ) return false;
-
- //------------- Open Interrupt Status Pipe -------------//
- tusb_desc_endpoint_t const *ep_desc;
- ep_desc = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc);
-
- TU_ASSERT(TUSB_DESC_ENDPOINT == ep_desc->bDescriptorType);
- TU_ASSERT(TUSB_XFER_INTERRUPT == ep_desc->bmAttributes.xfer);
-
- TU_ASSERT(hcd_edpt_open(rhport, dev_addr, ep_desc));
-
- hub_data[dev_addr-1].itf_num = itf_desc->bInterfaceNumber;
- hub_data[dev_addr-1].ep_status = ep_desc->bEndpointAddress;
-
- (*p_length) = sizeof(tusb_desc_interface_t) + sizeof(tusb_desc_endpoint_t);
-
- //------------- Get Hub Descriptor -------------//
- tusb_control_request_t request = {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN },
- .bRequest = HUB_REQUEST_GET_DESCRIPTOR,
- .wValue = 0,
- .wIndex = 0,
- .wLength = sizeof(descriptor_hub_desc_t)
- };
-
- TU_ASSERT( usbh_control_xfer( dev_addr, &request, hub_enum_buffer ) );
-
- // only care about this field in hub descriptor
- hub_data[dev_addr-1].port_number = ((descriptor_hub_desc_t*) hub_enum_buffer)->bNbrPorts;
-
- //------------- Set Port_Power on all ports -------------//
- // TODO may only power port with attached
- request = (tusb_control_request_t ) {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_OUT },
- .bRequest = HUB_REQUEST_SET_FEATURE,
- .wValue = HUB_FEATURE_PORT_POWER,
- .wIndex = 0,
- .wLength = 0
- };
-
- for(uint8_t i=1; i <= hub_data[dev_addr-1].port_number; i++)
- {
- request.wIndex = i;
- TU_ASSERT( usbh_control_xfer( dev_addr, &request, NULL ) );
- }
-
- //------------- Queue the initial Status endpoint transfer -------------//
- TU_ASSERT( hcd_pipe_xfer(dev_addr, hub_data[dev_addr-1].ep_status, &hub_data[dev_addr-1].status_change, 1, true) );
-
- return true;
-}
-
-// is the response of interrupt endpoint polling
-#include "usbh_hcd.h" // FIXME remove
-void hub_isr(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes)
-{
- (void) xferred_bytes; // TODO can be more than 1 for hub with lots of ports
- (void) ep_addr;
-
- usbh_hub_t * p_hub = &hub_data[dev_addr-1];
-
- if ( event == XFER_RESULT_SUCCESS )
- {
- for (uint8_t port=1; port <= p_hub->port_number; port++)
- {
- // TODO HUB ignore bit0 hub_status_change
- if ( tu_bit_test(p_hub->status_change, port) )
- {
- hcd_event_t event =
- {
- .rhport = _usbh_devices[dev_addr].rhport,
- .event_id = HCD_EVENT_DEVICE_ATTACH
- };
-
- event.attach.hub_addr = dev_addr;
- event.attach.hub_port = port;
-
- hcd_event_handler(&event, true);
- break; // handle one port at a time, next port if any will be handled in the next cycle
- }
- }
- // NOTE: next status transfer is queued by usbh.c after handling this request
- }
- else
- {
- // TODO [HUB] check if hub is still plugged before polling status endpoint since failed usually mean hub unplugged
-// TU_ASSERT ( hub_status_pipe_queue(dev_addr) );
- }
-}
-
-void hub_close(uint8_t dev_addr)
-{
- tu_memclr(&hub_data[dev_addr-1], sizeof(usbh_hub_t));
-// osal_semaphore_reset(hub_enum_sem_hdl);
-}
-
-bool hub_status_pipe_queue(uint8_t dev_addr)
-{
- return hcd_pipe_xfer(dev_addr, hub_data[dev_addr-1].ep_status, &hub_data[dev_addr-1].status_change, 1, true);
-}
-
-
-#endif
+/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if (TUSB_OPT_HOST_ENABLED && CFG_TUH_HUB) + +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "hub.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef struct +{ + uint8_t itf_num; + uint8_t ep_status; + uint8_t port_number; + uint8_t status_change; // data from status change interrupt endpoint +}usbh_hub_t; + +CFG_TUSB_MEM_SECTION static usbh_hub_t hub_data[CFG_TUSB_HOST_DEVICE_MAX]; +TU_ATTR_ALIGNED(4) CFG_TUSB_MEM_SECTION static uint8_t hub_enum_buffer[sizeof(descriptor_hub_desc_t)]; + +//OSAL_SEM_DEF(hub_enum_semaphore); +//static osal_semaphore_handle_t hub_enum_sem_hdl; + +//--------------------------------------------------------------------+ +// HUB +//--------------------------------------------------------------------+ +bool hub_port_clear_feature_subtask(uint8_t hub_addr, uint8_t hub_port, uint8_t feature) +{ + TU_ASSERT(HUB_FEATURE_PORT_CONNECTION_CHANGE <= feature && feature <= HUB_FEATURE_PORT_RESET_CHANGE); + + tusb_control_request_t request = { + .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_OUT }, + .bRequest = HUB_REQUEST_CLEAR_FEATURE, + .wValue = feature, + .wIndex = hub_port, + .wLength = 0 + }; + + //------------- Clear Port Feature request -------------// + TU_ASSERT( usbh_control_xfer( hub_addr, &request, NULL ) ); + + //------------- Get Port Status to check if feature is cleared -------------// + request = (tusb_control_request_t ) { + .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN }, + .bRequest = HUB_REQUEST_GET_STATUS, + .wValue = 0, + .wIndex = hub_port, + .wLength = 4 + }; + + TU_ASSERT( usbh_control_xfer( hub_addr, &request, hub_enum_buffer ) ); + + //------------- Check if feature is cleared -------------// + hub_port_status_response_t * p_port_status; + p_port_status = (hub_port_status_response_t *) hub_enum_buffer; + + TU_ASSERT( !tu_bit_test(p_port_status->status_change.value, feature-16) ); + + return true; +} + +bool hub_port_reset_subtask(uint8_t hub_addr, uint8_t hub_port) +{ + enum { RESET_DELAY = 200 }; // USB specs say only 50ms but many devices require much longer + + //------------- Set Port Reset -------------// + tusb_control_request_t request = { + .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_OUT }, + .bRequest = HUB_REQUEST_SET_FEATURE, + .wValue = HUB_FEATURE_PORT_RESET, + .wIndex = hub_port, + .wLength = 0 + }; + + TU_ASSERT( usbh_control_xfer( hub_addr, &request, NULL ) ); + + osal_task_delay(RESET_DELAY); // TODO Hub wait for Status Endpoint on Reset Change + + //------------- Get Port Status to check if port is enabled, powered and reset_change -------------// + request = (tusb_control_request_t ) { + .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN }, + .bRequest = HUB_REQUEST_GET_STATUS, + .wValue = 0, + .wIndex = hub_port, + .wLength = 4 + }; + + TU_ASSERT( usbh_control_xfer( hub_addr, &request, hub_enum_buffer ) ); + + hub_port_status_response_t * p_port_status; + p_port_status = (hub_port_status_response_t *) hub_enum_buffer; + + TU_ASSERT ( p_port_status->status_change.reset && p_port_status->status_current.connect_status && + p_port_status->status_current.port_power && p_port_status->status_current.port_enable); + + return true; +} + +// can only get the speed RIGHT AFTER hub_port_reset_subtask call +tusb_speed_t hub_port_get_speed(void) +{ + hub_port_status_response_t * p_port_status = (hub_port_status_response_t *) hub_enum_buffer; + return (p_port_status->status_current.high_speed_device_attached) ? TUSB_SPEED_HIGH : + (p_port_status->status_current.low_speed_device_attached ) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL; +} + +//--------------------------------------------------------------------+ +// CLASS-USBH API (don't require to verify parameters) +//--------------------------------------------------------------------+ +void hub_init(void) +{ + tu_memclr(hub_data, CFG_TUSB_HOST_DEVICE_MAX*sizeof(usbh_hub_t)); +// hub_enum_sem_hdl = osal_semaphore_create( OSAL_SEM_REF(hub_enum_semaphore) ); +} + +bool hub_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t *p_length) +{ + // not support multiple TT yet + if ( itf_desc->bInterfaceProtocol > 1 ) return false; + + //------------- Open Interrupt Status Pipe -------------// + tusb_desc_endpoint_t const *ep_desc; + ep_desc = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); + + TU_ASSERT(TUSB_DESC_ENDPOINT == ep_desc->bDescriptorType); + TU_ASSERT(TUSB_XFER_INTERRUPT == ep_desc->bmAttributes.xfer); + + TU_ASSERT(hcd_edpt_open(rhport, dev_addr, ep_desc)); + + hub_data[dev_addr-1].itf_num = itf_desc->bInterfaceNumber; + hub_data[dev_addr-1].ep_status = ep_desc->bEndpointAddress; + + (*p_length) = sizeof(tusb_desc_interface_t) + sizeof(tusb_desc_endpoint_t); + + //------------- Get Hub Descriptor -------------// + tusb_control_request_t request = { + .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN }, + .bRequest = HUB_REQUEST_GET_DESCRIPTOR, + .wValue = 0, + .wIndex = 0, + .wLength = sizeof(descriptor_hub_desc_t) + }; + + TU_ASSERT( usbh_control_xfer( dev_addr, &request, hub_enum_buffer ) ); + + // only care about this field in hub descriptor + hub_data[dev_addr-1].port_number = ((descriptor_hub_desc_t*) hub_enum_buffer)->bNbrPorts; + + //------------- Set Port_Power on all ports -------------// + // TODO may only power port with attached + request = (tusb_control_request_t ) { + .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_OUT }, + .bRequest = HUB_REQUEST_SET_FEATURE, + .wValue = HUB_FEATURE_PORT_POWER, + .wIndex = 0, + .wLength = 0 + }; + + for(uint8_t i=1; i <= hub_data[dev_addr-1].port_number; i++) + { + request.wIndex = i; + TU_ASSERT( usbh_control_xfer( dev_addr, &request, NULL ) ); + } + + //------------- Queue the initial Status endpoint transfer -------------// + TU_ASSERT( hcd_pipe_xfer(dev_addr, hub_data[dev_addr-1].ep_status, &hub_data[dev_addr-1].status_change, 1, true) ); + + return true; +} + +// is the response of interrupt endpoint polling +#include "usbh_hcd.h" // FIXME remove +void hub_isr(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) +{ + (void) xferred_bytes; // TODO can be more than 1 for hub with lots of ports + (void) ep_addr; + + usbh_hub_t * p_hub = &hub_data[dev_addr-1]; + + if ( event == XFER_RESULT_SUCCESS ) + { + for (uint8_t port=1; port <= p_hub->port_number; port++) + { + // TODO HUB ignore bit0 hub_status_change + if ( tu_bit_test(p_hub->status_change, port) ) + { + hcd_event_t event = + { + .rhport = _usbh_devices[dev_addr].rhport, + .event_id = HCD_EVENT_DEVICE_ATTACH + }; + + event.attach.hub_addr = dev_addr; + event.attach.hub_port = port; + + hcd_event_handler(&event, true); + break; // handle one port at a time, next port if any will be handled in the next cycle + } + } + // NOTE: next status transfer is queued by usbh.c after handling this request + } + else + { + // TODO [HUB] check if hub is still plugged before polling status endpoint since failed usually mean hub unplugged +// TU_ASSERT ( hub_status_pipe_queue(dev_addr) ); + } +} + +void hub_close(uint8_t dev_addr) +{ + tu_memclr(&hub_data[dev_addr-1], sizeof(usbh_hub_t)); +// osal_semaphore_reset(hub_enum_sem_hdl); +} + +bool hub_status_pipe_queue(uint8_t dev_addr) +{ + return hcd_pipe_xfer(dev_addr, hub_data[dev_addr-1].ep_status, &hub_data[dev_addr-1].status_change, 1, true); +} + + +#endif diff --git a/src/host/hub.h b/src/host/hub.h index 51d33d4e5..621356315 100644 --- a/src/host/hub.h +++ b/src/host/hub.h @@ -1,194 +1,194 @@ -/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2019 Ha Thach (tinyusb.org)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * This file is part of the TinyUSB stack.
- */
-
-/** \ingroup group_class
- * \defgroup ClassDriver_Hub Hub (Host only)
- * \details Like most PC's OS, Hub support is completely hidden from Application. In fact, application cannot determine whether
- * a device is mounted directly via roothub or via a hub's port. All Hub-related procedures are performed and managed
- * by tinyusb stack. Unless you are trying to develop the stack itself, there are nothing else can be used by Application.
- * \note Due to my laziness, only 1-level of Hub is supported. In other way, the stack cannot mount a hub via another hub.
- * @{
- */
-
-#ifndef _TUSB_HUB_H_
-#define _TUSB_HUB_H_
-
-#include <common/tusb_common.h>
-#include "usbh.h"
-
-#ifdef __cplusplus
- extern "C" {
-#endif
-
-//D1...D0: Logical Power Switching Mode
-//00: Ganged power switching (all ports’power at
-//once)
-//01: Individual port power switching
-//1X: Reserved. Used only on 1.0 compliant hubs
-//that implement no power switching
-//D2: Identifies a Compound Device
-//0: Hub is not part of a compound device.
-//1: Hub is part of a compound device.
-//D4...D3: Over-current Protection Mode
-//00: Global Over-current Protection. The hub
-//reports over-current as a summation of all
-//ports’current draw, without a breakdown of
-//individual port over-current status.
-//01: Individual Port Over-current Protection. The
-//hub reports over-current on a per-port basis.
-//Each port has an over-current status.
-//1X: No Over-current Protection. This option is
-//allowed only for bus-powered hubs that do not
-//implement over-current protection.
-//
-//D6...D5: TT Think TIme
-//00: TT requires at most 8 FS bit times of inter
-//transaction gap on a full-/low-speed
-//downstream bus.
-//01: TT requires at most 16 FS bit times.
-//10: TT requires at most 24 FS bit times.
-//11: TT requires at most 32 FS bit times.
-//D7: Port Indicators Supported
-//0: Port Indicators are not supported on its
-//downstream facing ports and the
-//PORT_INDICATOR request has no effect.
-//1: Port Indicators are supported on its
-//downstream facing ports and the
-//PORT_INDICATOR request controls the
-//indicators. See Section 11.5.3.
-//D15...D8: Reserved
-
-typedef struct TU_ATTR_PACKED{
- uint8_t bLength ; ///< Size of descriptor
- uint8_t bDescriptorType ; ///< Other_speed_Configuration Type
- uint8_t bNbrPorts;
- uint16_t wHubCharacteristics;
- uint8_t bPwrOn2PwrGood;
- uint8_t bHubContrCurrent;
- uint8_t DeviceRemovable; // bitmap each bit for a port (from bit1)
- uint8_t PortPwrCtrlMask; // just for compatibility, should be 0xff
-} descriptor_hub_desc_t;
-
-TU_VERIFY_STATIC( sizeof(descriptor_hub_desc_t) == 9, "size is not correct");
-
-enum {
- HUB_REQUEST_GET_STATUS = 0 ,
- HUB_REQUEST_CLEAR_FEATURE = 1 ,
-
- HUB_REQUEST_SET_FEATURE = 3 ,
-
- HUB_REQUEST_GET_DESCRIPTOR = 6 ,
- HUB_REQUEST_SET_DESCRIPTOR = 7 ,
- HUB_REQUEST_CLEAR_TT_BUFFER = 8 ,
- HUB_REQUEST_RESET_TT = 9 ,
- HUB_REQUEST_GET_TT_STATE = 10 ,
- HUB_REQUEST_STOP_TT = 11
-};
-
-enum {
- HUB_FEATURE_HUB_LOCAL_POWER_CHANGE = 0,
- HUB_FEATURE_HUB_OVER_CURRENT_CHANGE
-};
-
-enum{
- HUB_FEATURE_PORT_CONNECTION = 0,
- HUB_FEATURE_PORT_ENABLE = 1,
- HUB_FEATURE_PORT_SUSPEND = 2,
- HUB_FEATURE_PORT_OVER_CURRENT = 3,
- HUB_FEATURE_PORT_RESET = 4,
-
- HUB_FEATURE_PORT_POWER = 8,
- HUB_FEATURE_PORT_LOW_SPEED = 9,
-
- HUB_FEATURE_PORT_CONNECTION_CHANGE = 16,
- HUB_FEATURE_PORT_ENABLE_CHANGE = 17,
- HUB_FEATURE_PORT_SUSPEND_CHANGE = 18,
- HUB_FEATURE_PORT_OVER_CURRENT_CHANGE = 19,
- HUB_FEATURE_PORT_RESET_CHANGE = 20,
- HUB_FEATURE_PORT_TEST = 21,
- HUB_FEATURE_PORT_INDICATOR = 22
-};
-
-// data in response of HUB_REQUEST_GET_STATUS, wIndex = 0 (hub)
-typedef struct {
- union{
- struct TU_ATTR_PACKED {
- uint16_t local_power_source : 1;
- uint16_t over_current : 1;
- uint16_t : 14;
- };
-
- uint16_t value;
- } status, status_change;
-} hub_status_response_t;
-
-TU_VERIFY_STATIC( sizeof(hub_status_response_t) == 4, "size is not correct");
-
-// data in response of HUB_REQUEST_GET_STATUS, wIndex = Port num
-typedef struct {
- union {
- struct TU_ATTR_PACKED {
- uint16_t connect_status : 1;
- uint16_t port_enable : 1;
- uint16_t suspend : 1;
- uint16_t over_current : 1;
- uint16_t reset : 1;
-
- uint16_t : 3;
- uint16_t port_power : 1;
- uint16_t low_speed_device_attached : 1;
- uint16_t high_speed_device_attached : 1;
- uint16_t port_test_mode : 1;
- uint16_t port_indicator_control : 1;
- uint16_t : 0;
- };
-
- uint16_t value;
- } status_current, status_change;
-} hub_port_status_response_t;
-
-TU_VERIFY_STATIC( sizeof(hub_port_status_response_t) == 4, "size is not correct");
-
-bool hub_port_reset_subtask(uint8_t hub_addr, uint8_t hub_port);
-bool hub_port_clear_feature_subtask(uint8_t hub_addr, uint8_t hub_port, uint8_t feature);
-tusb_speed_t hub_port_get_speed(void);
-bool hub_status_pipe_queue(uint8_t dev_addr);
-
-//--------------------------------------------------------------------+
-// Internal Class Driver API
-//--------------------------------------------------------------------+
-void hub_init(void);
-bool hub_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t *p_length);
-void hub_isr(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes);
-void hub_close(uint8_t dev_addr);
-
-#ifdef __cplusplus
- }
-#endif
-
-#endif /* _TUSB_HUB_H_ */
-
-/** @} */
+/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +/** \ingroup group_class + * \defgroup ClassDriver_Hub Hub (Host only) + * \details Like most PC's OS, Hub support is completely hidden from Application. In fact, application cannot determine whether + * a device is mounted directly via roothub or via a hub's port. All Hub-related procedures are performed and managed + * by tinyusb stack. Unless you are trying to develop the stack itself, there are nothing else can be used by Application. + * \note Due to my laziness, only 1-level of Hub is supported. In other way, the stack cannot mount a hub via another hub. + * @{ + */ + +#ifndef _TUSB_HUB_H_ +#define _TUSB_HUB_H_ + +#include <common/tusb_common.h> +#include "usbh.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//D1...D0: Logical Power Switching Mode +//00: Ganged power switching (all ports’power at +//once) +//01: Individual port power switching +//1X: Reserved. Used only on 1.0 compliant hubs +//that implement no power switching +//D2: Identifies a Compound Device +//0: Hub is not part of a compound device. +//1: Hub is part of a compound device. +//D4...D3: Over-current Protection Mode +//00: Global Over-current Protection. The hub +//reports over-current as a summation of all +//ports’current draw, without a breakdown of +//individual port over-current status. +//01: Individual Port Over-current Protection. The +//hub reports over-current on a per-port basis. +//Each port has an over-current status. +//1X: No Over-current Protection. This option is +//allowed only for bus-powered hubs that do not +//implement over-current protection. +// +//D6...D5: TT Think TIme +//00: TT requires at most 8 FS bit times of inter +//transaction gap on a full-/low-speed +//downstream bus. +//01: TT requires at most 16 FS bit times. +//10: TT requires at most 24 FS bit times. +//11: TT requires at most 32 FS bit times. +//D7: Port Indicators Supported +//0: Port Indicators are not supported on its +//downstream facing ports and the +//PORT_INDICATOR request has no effect. +//1: Port Indicators are supported on its +//downstream facing ports and the +//PORT_INDICATOR request controls the +//indicators. See Section 11.5.3. +//D15...D8: Reserved + +typedef struct TU_ATTR_PACKED{ + uint8_t bLength ; ///< Size of descriptor + uint8_t bDescriptorType ; ///< Other_speed_Configuration Type + uint8_t bNbrPorts; + uint16_t wHubCharacteristics; + uint8_t bPwrOn2PwrGood; + uint8_t bHubContrCurrent; + uint8_t DeviceRemovable; // bitmap each bit for a port (from bit1) + uint8_t PortPwrCtrlMask; // just for compatibility, should be 0xff +} descriptor_hub_desc_t; + +TU_VERIFY_STATIC( sizeof(descriptor_hub_desc_t) == 9, "size is not correct"); + +enum { + HUB_REQUEST_GET_STATUS = 0 , + HUB_REQUEST_CLEAR_FEATURE = 1 , + + HUB_REQUEST_SET_FEATURE = 3 , + + HUB_REQUEST_GET_DESCRIPTOR = 6 , + HUB_REQUEST_SET_DESCRIPTOR = 7 , + HUB_REQUEST_CLEAR_TT_BUFFER = 8 , + HUB_REQUEST_RESET_TT = 9 , + HUB_REQUEST_GET_TT_STATE = 10 , + HUB_REQUEST_STOP_TT = 11 +}; + +enum { + HUB_FEATURE_HUB_LOCAL_POWER_CHANGE = 0, + HUB_FEATURE_HUB_OVER_CURRENT_CHANGE +}; + +enum{ + HUB_FEATURE_PORT_CONNECTION = 0, + HUB_FEATURE_PORT_ENABLE = 1, + HUB_FEATURE_PORT_SUSPEND = 2, + HUB_FEATURE_PORT_OVER_CURRENT = 3, + HUB_FEATURE_PORT_RESET = 4, + + HUB_FEATURE_PORT_POWER = 8, + HUB_FEATURE_PORT_LOW_SPEED = 9, + + HUB_FEATURE_PORT_CONNECTION_CHANGE = 16, + HUB_FEATURE_PORT_ENABLE_CHANGE = 17, + HUB_FEATURE_PORT_SUSPEND_CHANGE = 18, + HUB_FEATURE_PORT_OVER_CURRENT_CHANGE = 19, + HUB_FEATURE_PORT_RESET_CHANGE = 20, + HUB_FEATURE_PORT_TEST = 21, + HUB_FEATURE_PORT_INDICATOR = 22 +}; + +// data in response of HUB_REQUEST_GET_STATUS, wIndex = 0 (hub) +typedef struct { + union{ + struct TU_ATTR_PACKED { + uint16_t local_power_source : 1; + uint16_t over_current : 1; + uint16_t : 14; + }; + + uint16_t value; + } status, status_change; +} hub_status_response_t; + +TU_VERIFY_STATIC( sizeof(hub_status_response_t) == 4, "size is not correct"); + +// data in response of HUB_REQUEST_GET_STATUS, wIndex = Port num +typedef struct { + union { + struct TU_ATTR_PACKED { + uint16_t connect_status : 1; + uint16_t port_enable : 1; + uint16_t suspend : 1; + uint16_t over_current : 1; + uint16_t reset : 1; + + uint16_t : 3; + uint16_t port_power : 1; + uint16_t low_speed_device_attached : 1; + uint16_t high_speed_device_attached : 1; + uint16_t port_test_mode : 1; + uint16_t port_indicator_control : 1; + uint16_t : 0; + }; + + uint16_t value; + } status_current, status_change; +} hub_port_status_response_t; + +TU_VERIFY_STATIC( sizeof(hub_port_status_response_t) == 4, "size is not correct"); + +bool hub_port_reset_subtask(uint8_t hub_addr, uint8_t hub_port); +bool hub_port_clear_feature_subtask(uint8_t hub_addr, uint8_t hub_port, uint8_t feature); +tusb_speed_t hub_port_get_speed(void); +bool hub_status_pipe_queue(uint8_t dev_addr); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void hub_init(void); +bool hub_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t *p_length); +void hub_isr(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); +void hub_close(uint8_t dev_addr); + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_HUB_H_ */ + +/** @} */ diff --git a/src/host/ohci/ohci.c b/src/host/ohci/ohci.c index ea553baa2..547622edf 100644 --- a/src/host/ohci/ohci.c +++ b/src/host/ohci/ohci.c @@ -1,651 +1,651 @@ -/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2019 Ha Thach (tinyusb.org)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * This file is part of the TinyUSB stack.
- */
-
-#include <common/tusb_common.h>
-
-#if TUSB_OPT_HOST_ENABLED && (CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC40XX)
-//--------------------------------------------------------------------+
-// INCLUDE
-//--------------------------------------------------------------------+
-#include "osal/osal.h"
-
-#include "../hcd.h"
-#include "../usbh_hcd.h"
-#include "ohci.h"
-
-// TODO remove
-#include "chip.h"
-
-//--------------------------------------------------------------------+
-// MACRO CONSTANT TYPEDEF
-//--------------------------------------------------------------------+
-#define OHCI_REG ((ohci_registers_t *) LPC_USB_BASE)
-
-enum {
- OHCI_CONTROL_FUNCSTATE_RESET = 0,
- OHCI_CONTROL_FUNCSTATE_RESUME,
- OHCI_CONTROL_FUNCSTATE_OPERATIONAL,
- OHCI_CONTROL_FUNCSTATE_SUSPEND
-};
-
-enum {
- OHCI_CONTROL_CONTROL_BULK_RATIO = 3, ///< This specifies the service ratio between Control and Bulk EDs. 0 = 1:1, 3 = 4:1
- OHCI_CONTROL_LIST_PERIODIC_ENABLE_MASK = TU_BIT(2),
- OHCI_CONTROL_LIST_ISOCHRONOUS_ENABLE_MASK = TU_BIT(3),
- OHCI_CONTROL_LIST_CONTROL_ENABLE_MASK = TU_BIT(4),
- OHCI_CONTROL_LIST_BULK_ENABLE_MASK = TU_BIT(5),
-};
-
-enum {
- OHCI_FMINTERVAL_FI = 0x2EDF, // 7.3.1 nominal (reset) value
- OHCI_FMINTERVAL_FSMPS = (6*(OHCI_FMINTERVAL_FI-210)) / 7, // 5.4 calculated based on maximum overhead + bit stuffing
-};
-
-enum {
- OHCI_PERIODIC_START = 0x3E67
-};
-
-enum {
- OHCI_INT_SCHEDULING_OVERUN_MASK = TU_BIT(0),
- OHCI_INT_WRITEBACK_DONEHEAD_MASK = TU_BIT(1),
- OHCI_INT_SOF_MASK = TU_BIT(2),
- OHCI_INT_RESUME_DETECTED_MASK = TU_BIT(3),
- OHCI_INT_UNRECOVERABLE_ERROR_MASK = TU_BIT(4),
- OHCI_INT_FRAME_OVERFLOW_MASK = TU_BIT(5),
- OHCI_INT_RHPORT_STATUS_CHANGE_MASK = TU_BIT(6),
-
- OHCI_INT_OWNERSHIP_CHANGE_MASK = TU_BIT(30),
- OHCI_INT_MASTER_ENABLE_MASK = TU_BIT(31),
-};
-
-enum {
- OHCI_RHPORT_CURRENT_CONNECT_STATUS_MASK = TU_BIT(0),
- OHCI_RHPORT_PORT_ENABLE_STATUS_MASK = TU_BIT(1),
- OHCI_RHPORT_PORT_SUSPEND_STATUS_MASK = TU_BIT(2),
- OHCI_RHPORT_PORT_OVER_CURRENT_INDICATOR_MASK = TU_BIT(3),
- OHCI_RHPORT_PORT_RESET_STATUS_MASK = TU_BIT(4), ///< write '1' to reset port
-
- OHCI_RHPORT_PORT_POWER_STATUS_MASK = TU_BIT(8),
- OHCI_RHPORT_LOW_SPEED_DEVICE_ATTACHED_MASK = TU_BIT(9),
-
- OHCI_RHPORT_CONNECT_STATUS_CHANGE_MASK = TU_BIT(16),
- OHCI_RHPORT_PORT_ENABLE_CHANGE_MASK = TU_BIT(17),
- OHCI_RHPORT_PORT_SUSPEND_CHANGE_MASK = TU_BIT(18),
- OHCI_RHPORT_OVER_CURRENT_CHANGE_MASK = TU_BIT(19),
- OHCI_RHPORT_PORT_RESET_CHANGE_MASK = TU_BIT(20),
-
- OHCI_RHPORT_ALL_CHANGE_MASK = OHCI_RHPORT_CONNECT_STATUS_CHANGE_MASK | OHCI_RHPORT_PORT_ENABLE_CHANGE_MASK |
- OHCI_RHPORT_PORT_SUSPEND_CHANGE_MASK | OHCI_RHPORT_OVER_CURRENT_CHANGE_MASK | OHCI_RHPORT_PORT_RESET_CHANGE_MASK
-};
-
-enum {
- OHCI_CCODE_NO_ERROR = 0,
- OHCI_CCODE_CRC = 1,
- OHCI_CCODE_BIT_STUFFING = 2,
- OHCI_CCODE_DATA_TOGGLE_MISMATCH = 3,
- OHCI_CCODE_STALL = 4,
- OHCI_CCODE_DEVICE_NOT_RESPONDING = 5,
- OHCI_CCODE_PID_CHECK_FAILURE = 6,
- OHCI_CCODE_UNEXPECTED_PID = 7,
- OHCI_CCODE_DATA_OVERRUN = 8,
- OHCI_CCODE_DATA_UNDERRUN = 9,
- OHCI_CCODE_BUFFER_OVERRUN = 12,
- OHCI_CCODE_BUFFER_UNDERRUN = 13,
- OHCI_CCODE_NOT_ACCESSED = 14,
-};
-
-enum {
- OHCI_INT_ON_COMPLETE_YES = 0,
- OHCI_INT_ON_COMPLETE_NO = TU_BIN8(111)
-};
-//--------------------------------------------------------------------+
-// INTERNAL OBJECT & FUNCTION DECLARATION
-//--------------------------------------------------------------------+
-CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(256) static ohci_data_t ohci_data;
-
-static ohci_ed_t * const p_ed_head[] =
-{
- [TUSB_XFER_CONTROL] = &ohci_data.control[0].ed,
- [TUSB_XFER_BULK ] = &ohci_data.bulk_head_ed,
- [TUSB_XFER_INTERRUPT] = &ohci_data.period_head_ed,
- [TUSB_XFER_ISOCHRONOUS] = NULL // TODO Isochronous
-};
-
-static void ed_list_insert(ohci_ed_t * p_pre, ohci_ed_t * p_ed);
-static void ed_list_remove_by_addr(ohci_ed_t * p_head, uint8_t dev_addr);
-
-//--------------------------------------------------------------------+
-// USBH-HCD API
-//--------------------------------------------------------------------+
-// Initialization according to 5.1.1.4
-bool hcd_init(void)
-{
- //------------- Data Structure init -------------//
- tu_memclr(&ohci_data, sizeof(ohci_data_t));
- for(uint8_t i=0; i<32; i++)
- { // assign all interrupt pointes to period head ed
- ohci_data.hcca.interrupt_table[i] = (uint32_t) &ohci_data.period_head_ed;
- }
-
- ohci_data.control[0].ed.skip = 1;
- ohci_data.bulk_head_ed.skip = 1;
- ohci_data.period_head_ed.skip = 1;
-
- // reset controller
- OHCI_REG->command_status_bit.controller_reset = 1;
- while( OHCI_REG->command_status_bit.controller_reset ) {} // should not take longer than 10 us
-
- //------------- init ohci registers -------------//
- OHCI_REG->control_head_ed = (uint32_t) &ohci_data.control[0].ed;
- OHCI_REG->bulk_head_ed = (uint32_t) &ohci_data.bulk_head_ed;
- OHCI_REG->hcca = (uint32_t) &ohci_data.hcca;
-
- OHCI_REG->interrupt_disable = OHCI_REG->interrupt_enable; // disable all interrupts
- OHCI_REG->interrupt_status = OHCI_REG->interrupt_status; // clear current set bits
- OHCI_REG->interrupt_enable = OHCI_INT_WRITEBACK_DONEHEAD_MASK | OHCI_INT_RESUME_DETECTED_MASK |
- OHCI_INT_UNRECOVERABLE_ERROR_MASK | /*OHCI_INT_FRAME_OVERFLOW_MASK |*/ OHCI_INT_RHPORT_STATUS_CHANGE_MASK |
- OHCI_INT_MASTER_ENABLE_MASK;
-
- OHCI_REG->control |= OHCI_CONTROL_CONTROL_BULK_RATIO | OHCI_CONTROL_LIST_CONTROL_ENABLE_MASK |
- OHCI_CONTROL_LIST_BULK_ENABLE_MASK | OHCI_CONTROL_LIST_PERIODIC_ENABLE_MASK; // TODO Isochronous
-
- OHCI_REG->frame_interval = (OHCI_FMINTERVAL_FSMPS << 16) | OHCI_FMINTERVAL_FI;
- OHCI_REG->periodic_start = (OHCI_FMINTERVAL_FI * 9) / 10; // Periodic start is 90% of frame interval
-
- OHCI_REG->control_bit.hc_functional_state = OHCI_CONTROL_FUNCSTATE_OPERATIONAL; // make HC's state to operational state TODO use this to suspend (save power)
- OHCI_REG->rh_status_bit.local_power_status_change = 1; // set global power for ports
-
- return true;
-}
-
-//--------------------------------------------------------------------+
-// PORT API
-//--------------------------------------------------------------------+
-void hcd_port_reset(uint8_t hostid)
-{
- (void) hostid;
- OHCI_REG->rhport_status[0] = OHCI_RHPORT_PORT_RESET_STATUS_MASK;
-}
-
-bool hcd_port_connect_status(uint8_t hostid)
-{
- (void) hostid;
- return OHCI_REG->rhport_status_bit[0].current_connect_status;
-}
-
-tusb_speed_t hcd_port_speed_get(uint8_t hostid)
-{
- (void) hostid;
- return OHCI_REG->rhport_status_bit[0].low_speed_device_attached ? TUSB_SPEED_LOW : TUSB_SPEED_FULL;
-}
-
-// endpoints are tied to an address, which only reclaim after a long delay when enumerating
-// thus there is no need to make sure ED is not in HC's cahed as it will not for sure
-void hcd_device_close(uint8_t rhport, uint8_t dev_addr)
-{
- // TODO OHCI
- (void) rhport;
-
- // addr0 serves as static head --> only set skip bit
- if ( dev_addr == 0 )
- {
- ohci_data.control[0].ed.skip = 1;
- }else
- {
- // remove control
- ed_list_remove_by_addr( p_ed_head[TUSB_XFER_CONTROL], dev_addr);
-
- // remove bulk
- ed_list_remove_by_addr(p_ed_head[TUSB_XFER_BULK], dev_addr);
-
- // remove interrupt
- ed_list_remove_by_addr(p_ed_head[TUSB_XFER_INTERRUPT], dev_addr);
-
- // TODO remove ISO
- }
-}
-
-//--------------------------------------------------------------------+
-// Controller API
-//--------------------------------------------------------------------+
-
-//--------------------------------------------------------------------+
-// CONTROL PIPE API
-//--------------------------------------------------------------------+
-static inline tusb_xfer_type_t ed_get_xfer_type(ohci_ed_t const * const p_ed)
-{
- return (p_ed->ep_number == 0 ) ? TUSB_XFER_CONTROL :
- (p_ed->is_iso ) ? TUSB_XFER_ISOCHRONOUS :
- (p_ed->is_interrupt_xfer ) ? TUSB_XFER_INTERRUPT : TUSB_XFER_BULK;
-}
-
-static void ed_init(ohci_ed_t *p_ed, uint8_t dev_addr, uint16_t max_packet_size, uint8_t endpoint_addr, uint8_t xfer_type, uint8_t interval)
-{
- (void) interval;
-
- // address 0 is used as async head, which always on the list --> cannot be cleared
- if (dev_addr != 0)
- {
- tu_memclr(p_ed, sizeof(ohci_ed_t));
- }
-
- p_ed->dev_addr = dev_addr;
- p_ed->ep_number = endpoint_addr & 0x0F;
- p_ed->pid = (xfer_type == TUSB_XFER_CONTROL) ? OHCI_PID_SETUP : ( (endpoint_addr & TUSB_DIR_IN_MASK) ? OHCI_PID_IN : OHCI_PID_OUT );
- p_ed->speed = _usbh_devices[dev_addr].speed;
- p_ed->is_iso = (xfer_type == TUSB_XFER_ISOCHRONOUS) ? 1 : 0;
- p_ed->max_packet_size = max_packet_size;
-
- p_ed->used = 1;
- p_ed->is_interrupt_xfer = (xfer_type == TUSB_XFER_INTERRUPT ? 1 : 0);
-}
-
-static void gtd_init(ohci_gtd_t* p_td, void* data_ptr, uint16_t total_bytes)
-{
- tu_memclr(p_td, sizeof(ohci_gtd_t));
-
- p_td->used = 1;
- p_td->expected_bytes = total_bytes;
-
- p_td->buffer_rounding = 1; // less than queued length is not a error
- p_td->delay_interrupt = OHCI_INT_ON_COMPLETE_NO;
- p_td->condition_code = OHCI_CCODE_NOT_ACCESSED;
-
- p_td->current_buffer_pointer = data_ptr;
- p_td->buffer_end = total_bytes ? (((uint8_t*) data_ptr) + total_bytes-1) : NULL;
-}
-
-bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8])
-{
- (void) rhport;
-
- ohci_ed_t* p_ed = &ohci_data.control[dev_addr].ed;
- ohci_gtd_t *p_setup = &ohci_data.control[dev_addr].gtd;
-
- gtd_init(p_setup, (void*) setup_packet, 8);
- p_setup->index = dev_addr;
- p_setup->pid = OHCI_PID_SETUP;
- p_setup->data_toggle = TU_BIN8(10); // DATA0
- p_setup->delay_interrupt = OHCI_INT_ON_COMPLETE_YES;
-
- //------------- Attach TDs list to Control Endpoint -------------//
- p_ed->td_head.address = (uint32_t) p_setup;
-
- OHCI_REG->command_status_bit.control_list_filled = 1;
-
- return true;
-}
-
-bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen)
-{
- (void) rhport;
-
- uint8_t const epnum = tu_edpt_number(ep_addr);
- uint8_t const dir = tu_edpt_dir(ep_addr);
-
- // FIXME control only for now
- if ( epnum == 0 )
- {
- ohci_ed_t* const p_ed = &ohci_data.control[dev_addr].ed;
- ohci_gtd_t *p_data = &ohci_data.control[dev_addr].gtd;
-
- gtd_init(p_data, buffer, buflen);
-
- p_data->index = dev_addr;
- p_data->pid = dir ? OHCI_PID_IN : OHCI_PID_OUT;
- p_data->data_toggle = TU_BIN8(11); // DATA1
- p_data->delay_interrupt = OHCI_INT_ON_COMPLETE_YES;
-
- p_ed->td_head.address = (uint32_t) p_data;
-
- OHCI_REG->command_status_bit.control_list_filled = 1;
- }
-
- return true;
-}
-
-//--------------------------------------------------------------------+
-// BULK/INT/ISO PIPE API
-//--------------------------------------------------------------------+
-static inline ohci_ed_t * ed_from_addr(uint8_t dev_addr, uint8_t ep_addr)
-{
- if ( tu_edpt_number(ep_addr) == 0 ) return &ohci_data.control[dev_addr].ed;
-
- ohci_ed_t* ed_pool = ohci_data.ed_pool;
-
- for(uint32_t i=0; i<HCD_MAX_ENDPOINT; i++)
- {
- if ( (ed_pool[i].dev_addr == dev_addr) &&
- ep_addr == tu_edpt_addr(ed_pool[i].ep_number, ed_pool[i].pid == OHCI_PID_IN) )
- {
- return &ed_pool[i];
- }
- }
-
- return NULL;
-}
-
-static inline ohci_ed_t * ed_find_free(void)
-{
- ohci_ed_t* ed_pool = ohci_data.ed_pool;
-
- for(uint8_t i = 0; i < HCD_MAX_ENDPOINT; i++)
- {
- if ( !ed_pool[i].used ) return &ed_pool[i];
- }
-
- return NULL;
-}
-
-static void ed_list_insert(ohci_ed_t * p_pre, ohci_ed_t * p_ed)
-{
- p_ed->next = p_pre->next;
- p_pre->next = (uint32_t) p_ed;
-}
-
-static void ed_list_remove_by_addr(ohci_ed_t * p_head, uint8_t dev_addr)
-{
- ohci_ed_t* p_prev = p_head;
-
- while( p_prev->next )
- {
- ohci_ed_t* ed = (ohci_ed_t*) p_prev->next;
-
- if (ed->dev_addr == dev_addr)
- {
- // unlink ed
- p_prev->next = ed->next;
-
- // point the removed ED's next pointer to list head to make sure HC can always safely move away from this ED
- ed->next = (uint32_t) p_head;
- ed->used = 0;
- }
-
- // check next valid since we could remove it
- if (p_prev->next) p_prev = (ohci_ed_t*) p_prev->next;
- }
-}
-
-bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc)
-{
- (void) rhport;
-
- // TODO iso support
- TU_ASSERT(ep_desc->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS);
-
- //------------- Prepare Queue Head -------------//
- ohci_ed_t * p_ed;
-
- if ( ep_desc->bEndpointAddress == 0 )
- {
- p_ed = &ohci_data.control[dev_addr].ed;
- }else
- {
- p_ed = ed_find_free();
- }
- TU_ASSERT(p_ed);
-
- ed_init( p_ed, dev_addr, ep_desc->wMaxPacketSize.size, ep_desc->bEndpointAddress,
- ep_desc->bmAttributes.xfer, ep_desc->bInterval );
-
- // control of dev0 is used as static async head
- if ( dev_addr == 0 )
- {
- p_ed->skip = 0; // only need to clear skip bit
- return true;
- }
-
- ed_list_insert( p_ed_head[ep_desc->bmAttributes.xfer], p_ed );
-
- return true;
-}
-
-static ohci_gtd_t * gtd_find_free(void)
-{
- for(uint8_t i=0; i < HCD_MAX_XFER; i++)
- {
- if ( !ohci_data.gtd_pool[i].used ) return &ohci_data.gtd_pool[i];
- }
-
- return NULL;
-}
-
-static void td_insert_to_ed(ohci_ed_t* p_ed, ohci_gtd_t * p_gtd)
-{
- // tail is always NULL
- if ( tu_align16(p_ed->td_head.address) == 0 )
- { // TD queue is empty --> head = TD
- p_ed->td_head.address |= (uint32_t) p_gtd;
- }
- else
- { // TODO currently only support queue up to 2 TD each endpoint at a time
- ((ohci_gtd_t*) tu_align16(p_ed->td_head.address))->next = (uint32_t) p_gtd;
- }
-}
-
-static bool pipe_queue_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes, bool int_on_complete)
-{
- ohci_ed_t* const p_ed = ed_from_addr(dev_addr, ep_addr);
-
- // not support ISO yet
- TU_VERIFY ( !p_ed->is_iso );
-
- ohci_gtd_t * const p_gtd = gtd_find_free();
- TU_ASSERT(p_gtd); // not enough gtd
-
- gtd_init(p_gtd, buffer, total_bytes);
- p_gtd->index = p_ed-ohci_data.ed_pool;
-
- if ( int_on_complete ) p_gtd->delay_interrupt = OHCI_INT_ON_COMPLETE_YES;
-
- td_insert_to_ed(p_ed, p_gtd);
-
- return true;
-}
-
-bool hcd_pipe_queue_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes)
-{
- return pipe_queue_xfer(dev_addr, ep_addr, buffer, total_bytes, false);
-}
-
-bool hcd_pipe_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes, bool int_on_complete)
-{
- (void) int_on_complete;
- TU_ASSERT( pipe_queue_xfer(dev_addr, ep_addr, buffer, total_bytes, true) );
-
- tusb_xfer_type_t xfer_type = ed_get_xfer_type( ed_from_addr(dev_addr, ep_addr) );
-
- if (TUSB_XFER_BULK == xfer_type) OHCI_REG->command_status_bit.bulk_list_filled = 1;
-
- return true;
-}
-
-bool hcd_edpt_busy(uint8_t dev_addr, uint8_t ep_addr)
-{
- ohci_ed_t const * const p_ed = ed_from_addr(dev_addr, ep_addr);
- return tu_align16(p_ed->td_head.address) != tu_align16(p_ed->td_tail);
-}
-
-bool hcd_edpt_stalled(uint8_t dev_addr, uint8_t ep_addr)
-{
- ohci_ed_t const * const p_ed = ed_from_addr(dev_addr, ep_addr);
- return p_ed->td_head.halted && p_ed->is_stalled;
-}
-
-bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr)
-{
- ohci_ed_t * const p_ed = ed_from_addr(dev_addr, ep_addr);
-
- p_ed->is_stalled = 0;
- p_ed->td_tail &= 0x0Ful; // set tail pointer back to NULL
-
- p_ed->td_head.toggle = 0; // reset data toggle
- p_ed->td_head.halted = 0;
-
- if ( TUSB_XFER_BULK == ed_get_xfer_type(p_ed) ) OHCI_REG->command_status_bit.bulk_list_filled = 1;
-
- return true;
-}
-
-
-//--------------------------------------------------------------------+
-// OHCI Interrupt Handler
-//--------------------------------------------------------------------+
-static ohci_td_item_t* list_reverse(ohci_td_item_t* td_head)
-{
- ohci_td_item_t* td_reverse_head = NULL;
-
- while(td_head != NULL)
- {
- uint32_t next = td_head->next;
-
- // make current's item become reverse's first item
- td_head->next = (uint32_t) td_reverse_head;
- td_reverse_head = td_head;
-
- td_head = (ohci_td_item_t*) next; // advance to next item
- }
-
- return td_reverse_head;
-}
-
-static inline bool gtd_is_control(ohci_gtd_t const * const p_qtd)
-{
- return ((uint32_t) p_qtd) < ((uint32_t) ohci_data.gtd_pool); // check ohci_data_t for memory layout
-}
-
-static inline ohci_ed_t* gtd_get_ed(ohci_gtd_t const * const p_qtd)
-{
- if ( gtd_is_control(p_qtd) )
- {
- return &ohci_data.control[p_qtd->index].ed;
- }else
- {
- return &ohci_data.ed_pool[p_qtd->index];
- }
-}
-
-static inline uint32_t gtd_xfer_byte_left(uint32_t buffer_end, uint32_t current_buffer)
-{ // 5.2.9 OHCI sample code
- return (tu_align4k(buffer_end ^ current_buffer) ? 0x1000 : 0) +
- tu_offset4k(buffer_end) - tu_offset4k(current_buffer) + 1;
-}
-
-static void done_queue_isr(uint8_t hostid)
-{
- (void) hostid;
-
- // done head is written in reversed order of completion --> need to reverse the done queue first
- ohci_td_item_t* td_head = list_reverse ( (ohci_td_item_t*) tu_align16(ohci_data.hcca.done_head) );
-
- while( td_head != NULL )
- {
- // TODO check if td_head is iso td
- //------------- Non ISO transfer -------------//
- ohci_gtd_t * const p_qtd = (ohci_gtd_t *) td_head;
- xfer_result_t const event = (p_qtd->condition_code == OHCI_CCODE_NO_ERROR) ? XFER_RESULT_SUCCESS :
- (p_qtd->condition_code == OHCI_CCODE_STALL) ? XFER_RESULT_STALLED : XFER_RESULT_FAILED;
-
- p_qtd->used = 0; // free TD
- if ( (p_qtd->delay_interrupt == OHCI_INT_ON_COMPLETE_YES) || (event != XFER_RESULT_SUCCESS) )
- {
- ohci_ed_t * const p_ed = gtd_get_ed(p_qtd);
-
- uint32_t const xferred_bytes = p_qtd->expected_bytes - gtd_xfer_byte_left((uint32_t) p_qtd->buffer_end, (uint32_t) p_qtd->current_buffer_pointer);
-
- // NOTE Assuming the current list is BULK and there is no other EDs in the list has queued TDs.
- // When there is a error resulting this ED is halted, and this EP still has other queued TD
- // --> the Bulk list only has this halted EP queueing TDs (remaining)
- // --> Bulk list will be considered as not empty by HC !!! while there is no attempt transaction on this list
- // --> HC will not process Control list (due to service ratio when Bulk list not empty)
- // To walk-around this, the halted ED will have TailP = HeadP (empty list condition), when clearing halt
- // the TailP must be set back to NULL for processing remaining TDs
- if ((event != XFER_RESULT_SUCCESS))
- {
- p_ed->td_tail &= 0x0Ful;
- p_ed->td_tail |= tu_align16(p_ed->td_head.address); // mark halted EP as empty queue
- if ( event == XFER_RESULT_STALLED ) p_ed->is_stalled = 1;
- }
-
- hcd_event_xfer_complete(p_ed->dev_addr,
- tu_edpt_addr(p_ed->ep_number, p_ed->pid == OHCI_PID_IN),
- event, xferred_bytes);
- }
-
- td_head = (ohci_td_item_t*) td_head->next;
- }
-}
-
-void hal_hcd_isr(uint8_t hostid)
-{
- uint32_t const int_en = OHCI_REG->interrupt_enable;
- uint32_t const int_status = OHCI_REG->interrupt_status & int_en;
-
- if (int_status == 0) return;
-
- //------------- RootHub status -------------//
- if ( int_status & OHCI_INT_RHPORT_STATUS_CHANGE_MASK )
- {
- uint32_t const rhport_status = OHCI_REG->rhport_status[0] & OHCI_RHPORT_ALL_CHANGE_MASK;
-
- // TODO dual port is not yet supported
- if ( rhport_status & OHCI_RHPORT_CONNECT_STATUS_CHANGE_MASK )
- {
- // TODO check if remote wake-up
- if ( OHCI_REG->rhport_status_bit[0].current_connect_status )
- {
- // TODO reset port immediately, without this controller will got 2-3 (debouncing connection status change)
- OHCI_REG->rhport_status[0] = OHCI_RHPORT_PORT_RESET_STATUS_MASK;
- hcd_event_device_attach(0);
- }else
- {
- hcd_event_device_remove(0);
- }
- }
-
- if ( rhport_status & OHCI_RHPORT_PORT_SUSPEND_CHANGE_MASK)
- {
-
- }
-
- OHCI_REG->rhport_status[0] = rhport_status; // acknowledge all interrupt
- }
-
- //------------- Transfer Complete -------------//
- if ( int_status & OHCI_INT_WRITEBACK_DONEHEAD_MASK)
- {
- done_queue_isr(hostid);
- }
-
- OHCI_REG->interrupt_status = int_status; // Acknowledge handled interrupt
-}
-//--------------------------------------------------------------------+
-// HELPER
-//--------------------------------------------------------------------+
-
-
-#endif
-
+/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#include <common/tusb_common.h> + +#if TUSB_OPT_HOST_ENABLED && (CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC40XX) +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "osal/osal.h" + +#include "../hcd.h" +#include "../usbh_hcd.h" +#include "ohci.h" + +// TODO remove +#include "chip.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +#define OHCI_REG ((ohci_registers_t *) LPC_USB_BASE) + +enum { + OHCI_CONTROL_FUNCSTATE_RESET = 0, + OHCI_CONTROL_FUNCSTATE_RESUME, + OHCI_CONTROL_FUNCSTATE_OPERATIONAL, + OHCI_CONTROL_FUNCSTATE_SUSPEND +}; + +enum { + OHCI_CONTROL_CONTROL_BULK_RATIO = 3, ///< This specifies the service ratio between Control and Bulk EDs. 0 = 1:1, 3 = 4:1 + OHCI_CONTROL_LIST_PERIODIC_ENABLE_MASK = TU_BIT(2), + OHCI_CONTROL_LIST_ISOCHRONOUS_ENABLE_MASK = TU_BIT(3), + OHCI_CONTROL_LIST_CONTROL_ENABLE_MASK = TU_BIT(4), + OHCI_CONTROL_LIST_BULK_ENABLE_MASK = TU_BIT(5), +}; + +enum { + OHCI_FMINTERVAL_FI = 0x2EDF, // 7.3.1 nominal (reset) value + OHCI_FMINTERVAL_FSMPS = (6*(OHCI_FMINTERVAL_FI-210)) / 7, // 5.4 calculated based on maximum overhead + bit stuffing +}; + +enum { + OHCI_PERIODIC_START = 0x3E67 +}; + +enum { + OHCI_INT_SCHEDULING_OVERUN_MASK = TU_BIT(0), + OHCI_INT_WRITEBACK_DONEHEAD_MASK = TU_BIT(1), + OHCI_INT_SOF_MASK = TU_BIT(2), + OHCI_INT_RESUME_DETECTED_MASK = TU_BIT(3), + OHCI_INT_UNRECOVERABLE_ERROR_MASK = TU_BIT(4), + OHCI_INT_FRAME_OVERFLOW_MASK = TU_BIT(5), + OHCI_INT_RHPORT_STATUS_CHANGE_MASK = TU_BIT(6), + + OHCI_INT_OWNERSHIP_CHANGE_MASK = TU_BIT(30), + OHCI_INT_MASTER_ENABLE_MASK = TU_BIT(31), +}; + +enum { + OHCI_RHPORT_CURRENT_CONNECT_STATUS_MASK = TU_BIT(0), + OHCI_RHPORT_PORT_ENABLE_STATUS_MASK = TU_BIT(1), + OHCI_RHPORT_PORT_SUSPEND_STATUS_MASK = TU_BIT(2), + OHCI_RHPORT_PORT_OVER_CURRENT_INDICATOR_MASK = TU_BIT(3), + OHCI_RHPORT_PORT_RESET_STATUS_MASK = TU_BIT(4), ///< write '1' to reset port + + OHCI_RHPORT_PORT_POWER_STATUS_MASK = TU_BIT(8), + OHCI_RHPORT_LOW_SPEED_DEVICE_ATTACHED_MASK = TU_BIT(9), + + OHCI_RHPORT_CONNECT_STATUS_CHANGE_MASK = TU_BIT(16), + OHCI_RHPORT_PORT_ENABLE_CHANGE_MASK = TU_BIT(17), + OHCI_RHPORT_PORT_SUSPEND_CHANGE_MASK = TU_BIT(18), + OHCI_RHPORT_OVER_CURRENT_CHANGE_MASK = TU_BIT(19), + OHCI_RHPORT_PORT_RESET_CHANGE_MASK = TU_BIT(20), + + OHCI_RHPORT_ALL_CHANGE_MASK = OHCI_RHPORT_CONNECT_STATUS_CHANGE_MASK | OHCI_RHPORT_PORT_ENABLE_CHANGE_MASK | + OHCI_RHPORT_PORT_SUSPEND_CHANGE_MASK | OHCI_RHPORT_OVER_CURRENT_CHANGE_MASK | OHCI_RHPORT_PORT_RESET_CHANGE_MASK +}; + +enum { + OHCI_CCODE_NO_ERROR = 0, + OHCI_CCODE_CRC = 1, + OHCI_CCODE_BIT_STUFFING = 2, + OHCI_CCODE_DATA_TOGGLE_MISMATCH = 3, + OHCI_CCODE_STALL = 4, + OHCI_CCODE_DEVICE_NOT_RESPONDING = 5, + OHCI_CCODE_PID_CHECK_FAILURE = 6, + OHCI_CCODE_UNEXPECTED_PID = 7, + OHCI_CCODE_DATA_OVERRUN = 8, + OHCI_CCODE_DATA_UNDERRUN = 9, + OHCI_CCODE_BUFFER_OVERRUN = 12, + OHCI_CCODE_BUFFER_UNDERRUN = 13, + OHCI_CCODE_NOT_ACCESSED = 14, +}; + +enum { + OHCI_INT_ON_COMPLETE_YES = 0, + OHCI_INT_ON_COMPLETE_NO = TU_BIN8(111) +}; +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(256) static ohci_data_t ohci_data; + +static ohci_ed_t * const p_ed_head[] = +{ + [TUSB_XFER_CONTROL] = &ohci_data.control[0].ed, + [TUSB_XFER_BULK ] = &ohci_data.bulk_head_ed, + [TUSB_XFER_INTERRUPT] = &ohci_data.period_head_ed, + [TUSB_XFER_ISOCHRONOUS] = NULL // TODO Isochronous +}; + +static void ed_list_insert(ohci_ed_t * p_pre, ohci_ed_t * p_ed); +static void ed_list_remove_by_addr(ohci_ed_t * p_head, uint8_t dev_addr); + +//--------------------------------------------------------------------+ +// USBH-HCD API +//--------------------------------------------------------------------+ +// Initialization according to 5.1.1.4 +bool hcd_init(void) +{ + //------------- Data Structure init -------------// + tu_memclr(&ohci_data, sizeof(ohci_data_t)); + for(uint8_t i=0; i<32; i++) + { // assign all interrupt pointes to period head ed + ohci_data.hcca.interrupt_table[i] = (uint32_t) &ohci_data.period_head_ed; + } + + ohci_data.control[0].ed.skip = 1; + ohci_data.bulk_head_ed.skip = 1; + ohci_data.period_head_ed.skip = 1; + + // reset controller + OHCI_REG->command_status_bit.controller_reset = 1; + while( OHCI_REG->command_status_bit.controller_reset ) {} // should not take longer than 10 us + + //------------- init ohci registers -------------// + OHCI_REG->control_head_ed = (uint32_t) &ohci_data.control[0].ed; + OHCI_REG->bulk_head_ed = (uint32_t) &ohci_data.bulk_head_ed; + OHCI_REG->hcca = (uint32_t) &ohci_data.hcca; + + OHCI_REG->interrupt_disable = OHCI_REG->interrupt_enable; // disable all interrupts + OHCI_REG->interrupt_status = OHCI_REG->interrupt_status; // clear current set bits + OHCI_REG->interrupt_enable = OHCI_INT_WRITEBACK_DONEHEAD_MASK | OHCI_INT_RESUME_DETECTED_MASK | + OHCI_INT_UNRECOVERABLE_ERROR_MASK | /*OHCI_INT_FRAME_OVERFLOW_MASK |*/ OHCI_INT_RHPORT_STATUS_CHANGE_MASK | + OHCI_INT_MASTER_ENABLE_MASK; + + OHCI_REG->control |= OHCI_CONTROL_CONTROL_BULK_RATIO | OHCI_CONTROL_LIST_CONTROL_ENABLE_MASK | + OHCI_CONTROL_LIST_BULK_ENABLE_MASK | OHCI_CONTROL_LIST_PERIODIC_ENABLE_MASK; // TODO Isochronous + + OHCI_REG->frame_interval = (OHCI_FMINTERVAL_FSMPS << 16) | OHCI_FMINTERVAL_FI; + OHCI_REG->periodic_start = (OHCI_FMINTERVAL_FI * 9) / 10; // Periodic start is 90% of frame interval + + OHCI_REG->control_bit.hc_functional_state = OHCI_CONTROL_FUNCSTATE_OPERATIONAL; // make HC's state to operational state TODO use this to suspend (save power) + OHCI_REG->rh_status_bit.local_power_status_change = 1; // set global power for ports + + return true; +} + +//--------------------------------------------------------------------+ +// PORT API +//--------------------------------------------------------------------+ +void hcd_port_reset(uint8_t hostid) +{ + (void) hostid; + OHCI_REG->rhport_status[0] = OHCI_RHPORT_PORT_RESET_STATUS_MASK; +} + +bool hcd_port_connect_status(uint8_t hostid) +{ + (void) hostid; + return OHCI_REG->rhport_status_bit[0].current_connect_status; +} + +tusb_speed_t hcd_port_speed_get(uint8_t hostid) +{ + (void) hostid; + return OHCI_REG->rhport_status_bit[0].low_speed_device_attached ? TUSB_SPEED_LOW : TUSB_SPEED_FULL; +} + +// endpoints are tied to an address, which only reclaim after a long delay when enumerating +// thus there is no need to make sure ED is not in HC's cahed as it will not for sure +void hcd_device_close(uint8_t rhport, uint8_t dev_addr) +{ + // TODO OHCI + (void) rhport; + + // addr0 serves as static head --> only set skip bit + if ( dev_addr == 0 ) + { + ohci_data.control[0].ed.skip = 1; + }else + { + // remove control + ed_list_remove_by_addr( p_ed_head[TUSB_XFER_CONTROL], dev_addr); + + // remove bulk + ed_list_remove_by_addr(p_ed_head[TUSB_XFER_BULK], dev_addr); + + // remove interrupt + ed_list_remove_by_addr(p_ed_head[TUSB_XFER_INTERRUPT], dev_addr); + + // TODO remove ISO + } +} + +//--------------------------------------------------------------------+ +// Controller API +//--------------------------------------------------------------------+ + +//--------------------------------------------------------------------+ +// CONTROL PIPE API +//--------------------------------------------------------------------+ +static inline tusb_xfer_type_t ed_get_xfer_type(ohci_ed_t const * const p_ed) +{ + return (p_ed->ep_number == 0 ) ? TUSB_XFER_CONTROL : + (p_ed->is_iso ) ? TUSB_XFER_ISOCHRONOUS : + (p_ed->is_interrupt_xfer ) ? TUSB_XFER_INTERRUPT : TUSB_XFER_BULK; +} + +static void ed_init(ohci_ed_t *p_ed, uint8_t dev_addr, uint16_t max_packet_size, uint8_t endpoint_addr, uint8_t xfer_type, uint8_t interval) +{ + (void) interval; + + // address 0 is used as async head, which always on the list --> cannot be cleared + if (dev_addr != 0) + { + tu_memclr(p_ed, sizeof(ohci_ed_t)); + } + + p_ed->dev_addr = dev_addr; + p_ed->ep_number = endpoint_addr & 0x0F; + p_ed->pid = (xfer_type == TUSB_XFER_CONTROL) ? OHCI_PID_SETUP : ( (endpoint_addr & TUSB_DIR_IN_MASK) ? OHCI_PID_IN : OHCI_PID_OUT ); + p_ed->speed = _usbh_devices[dev_addr].speed; + p_ed->is_iso = (xfer_type == TUSB_XFER_ISOCHRONOUS) ? 1 : 0; + p_ed->max_packet_size = max_packet_size; + + p_ed->used = 1; + p_ed->is_interrupt_xfer = (xfer_type == TUSB_XFER_INTERRUPT ? 1 : 0); +} + +static void gtd_init(ohci_gtd_t* p_td, void* data_ptr, uint16_t total_bytes) +{ + tu_memclr(p_td, sizeof(ohci_gtd_t)); + + p_td->used = 1; + p_td->expected_bytes = total_bytes; + + p_td->buffer_rounding = 1; // less than queued length is not a error + p_td->delay_interrupt = OHCI_INT_ON_COMPLETE_NO; + p_td->condition_code = OHCI_CCODE_NOT_ACCESSED; + + p_td->current_buffer_pointer = data_ptr; + p_td->buffer_end = total_bytes ? (((uint8_t*) data_ptr) + total_bytes-1) : NULL; +} + +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) +{ + (void) rhport; + + ohci_ed_t* p_ed = &ohci_data.control[dev_addr].ed; + ohci_gtd_t *p_setup = &ohci_data.control[dev_addr].gtd; + + gtd_init(p_setup, (void*) setup_packet, 8); + p_setup->index = dev_addr; + p_setup->pid = OHCI_PID_SETUP; + p_setup->data_toggle = TU_BIN8(10); // DATA0 + p_setup->delay_interrupt = OHCI_INT_ON_COMPLETE_YES; + + //------------- Attach TDs list to Control Endpoint -------------// + p_ed->td_head.address = (uint32_t) p_setup; + + OHCI_REG->command_status_bit.control_list_filled = 1; + + return true; +} + +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) +{ + (void) rhport; + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + // FIXME control only for now + if ( epnum == 0 ) + { + ohci_ed_t* const p_ed = &ohci_data.control[dev_addr].ed; + ohci_gtd_t *p_data = &ohci_data.control[dev_addr].gtd; + + gtd_init(p_data, buffer, buflen); + + p_data->index = dev_addr; + p_data->pid = dir ? OHCI_PID_IN : OHCI_PID_OUT; + p_data->data_toggle = TU_BIN8(11); // DATA1 + p_data->delay_interrupt = OHCI_INT_ON_COMPLETE_YES; + + p_ed->td_head.address = (uint32_t) p_data; + + OHCI_REG->command_status_bit.control_list_filled = 1; + } + + return true; +} + +//--------------------------------------------------------------------+ +// BULK/INT/ISO PIPE API +//--------------------------------------------------------------------+ +static inline ohci_ed_t * ed_from_addr(uint8_t dev_addr, uint8_t ep_addr) +{ + if ( tu_edpt_number(ep_addr) == 0 ) return &ohci_data.control[dev_addr].ed; + + ohci_ed_t* ed_pool = ohci_data.ed_pool; + + for(uint32_t i=0; i<HCD_MAX_ENDPOINT; i++) + { + if ( (ed_pool[i].dev_addr == dev_addr) && + ep_addr == tu_edpt_addr(ed_pool[i].ep_number, ed_pool[i].pid == OHCI_PID_IN) ) + { + return &ed_pool[i]; + } + } + + return NULL; +} + +static inline ohci_ed_t * ed_find_free(void) +{ + ohci_ed_t* ed_pool = ohci_data.ed_pool; + + for(uint8_t i = 0; i < HCD_MAX_ENDPOINT; i++) + { + if ( !ed_pool[i].used ) return &ed_pool[i]; + } + + return NULL; +} + +static void ed_list_insert(ohci_ed_t * p_pre, ohci_ed_t * p_ed) +{ + p_ed->next = p_pre->next; + p_pre->next = (uint32_t) p_ed; +} + +static void ed_list_remove_by_addr(ohci_ed_t * p_head, uint8_t dev_addr) +{ + ohci_ed_t* p_prev = p_head; + + while( p_prev->next ) + { + ohci_ed_t* ed = (ohci_ed_t*) p_prev->next; + + if (ed->dev_addr == dev_addr) + { + // unlink ed + p_prev->next = ed->next; + + // point the removed ED's next pointer to list head to make sure HC can always safely move away from this ED + ed->next = (uint32_t) p_head; + ed->used = 0; + } + + // check next valid since we could remove it + if (p_prev->next) p_prev = (ohci_ed_t*) p_prev->next; + } +} + +bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc) +{ + (void) rhport; + + // TODO iso support + TU_ASSERT(ep_desc->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS); + + //------------- Prepare Queue Head -------------// + ohci_ed_t * p_ed; + + if ( ep_desc->bEndpointAddress == 0 ) + { + p_ed = &ohci_data.control[dev_addr].ed; + }else + { + p_ed = ed_find_free(); + } + TU_ASSERT(p_ed); + + ed_init( p_ed, dev_addr, ep_desc->wMaxPacketSize.size, ep_desc->bEndpointAddress, + ep_desc->bmAttributes.xfer, ep_desc->bInterval ); + + // control of dev0 is used as static async head + if ( dev_addr == 0 ) + { + p_ed->skip = 0; // only need to clear skip bit + return true; + } + + ed_list_insert( p_ed_head[ep_desc->bmAttributes.xfer], p_ed ); + + return true; +} + +static ohci_gtd_t * gtd_find_free(void) +{ + for(uint8_t i=0; i < HCD_MAX_XFER; i++) + { + if ( !ohci_data.gtd_pool[i].used ) return &ohci_data.gtd_pool[i]; + } + + return NULL; +} + +static void td_insert_to_ed(ohci_ed_t* p_ed, ohci_gtd_t * p_gtd) +{ + // tail is always NULL + if ( tu_align16(p_ed->td_head.address) == 0 ) + { // TD queue is empty --> head = TD + p_ed->td_head.address |= (uint32_t) p_gtd; + } + else + { // TODO currently only support queue up to 2 TD each endpoint at a time + ((ohci_gtd_t*) tu_align16(p_ed->td_head.address))->next = (uint32_t) p_gtd; + } +} + +static bool pipe_queue_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes, bool int_on_complete) +{ + ohci_ed_t* const p_ed = ed_from_addr(dev_addr, ep_addr); + + // not support ISO yet + TU_VERIFY ( !p_ed->is_iso ); + + ohci_gtd_t * const p_gtd = gtd_find_free(); + TU_ASSERT(p_gtd); // not enough gtd + + gtd_init(p_gtd, buffer, total_bytes); + p_gtd->index = p_ed-ohci_data.ed_pool; + + if ( int_on_complete ) p_gtd->delay_interrupt = OHCI_INT_ON_COMPLETE_YES; + + td_insert_to_ed(p_ed, p_gtd); + + return true; +} + +bool hcd_pipe_queue_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes) +{ + return pipe_queue_xfer(dev_addr, ep_addr, buffer, total_bytes, false); +} + +bool hcd_pipe_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes, bool int_on_complete) +{ + (void) int_on_complete; + TU_ASSERT( pipe_queue_xfer(dev_addr, ep_addr, buffer, total_bytes, true) ); + + tusb_xfer_type_t xfer_type = ed_get_xfer_type( ed_from_addr(dev_addr, ep_addr) ); + + if (TUSB_XFER_BULK == xfer_type) OHCI_REG->command_status_bit.bulk_list_filled = 1; + + return true; +} + +bool hcd_edpt_busy(uint8_t dev_addr, uint8_t ep_addr) +{ + ohci_ed_t const * const p_ed = ed_from_addr(dev_addr, ep_addr); + return tu_align16(p_ed->td_head.address) != tu_align16(p_ed->td_tail); +} + +bool hcd_edpt_stalled(uint8_t dev_addr, uint8_t ep_addr) +{ + ohci_ed_t const * const p_ed = ed_from_addr(dev_addr, ep_addr); + return p_ed->td_head.halted && p_ed->is_stalled; +} + +bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr) +{ + ohci_ed_t * const p_ed = ed_from_addr(dev_addr, ep_addr); + + p_ed->is_stalled = 0; + p_ed->td_tail &= 0x0Ful; // set tail pointer back to NULL + + p_ed->td_head.toggle = 0; // reset data toggle + p_ed->td_head.halted = 0; + + if ( TUSB_XFER_BULK == ed_get_xfer_type(p_ed) ) OHCI_REG->command_status_bit.bulk_list_filled = 1; + + return true; +} + + +//--------------------------------------------------------------------+ +// OHCI Interrupt Handler +//--------------------------------------------------------------------+ +static ohci_td_item_t* list_reverse(ohci_td_item_t* td_head) +{ + ohci_td_item_t* td_reverse_head = NULL; + + while(td_head != NULL) + { + uint32_t next = td_head->next; + + // make current's item become reverse's first item + td_head->next = (uint32_t) td_reverse_head; + td_reverse_head = td_head; + + td_head = (ohci_td_item_t*) next; // advance to next item + } + + return td_reverse_head; +} + +static inline bool gtd_is_control(ohci_gtd_t const * const p_qtd) +{ + return ((uint32_t) p_qtd) < ((uint32_t) ohci_data.gtd_pool); // check ohci_data_t for memory layout +} + +static inline ohci_ed_t* gtd_get_ed(ohci_gtd_t const * const p_qtd) +{ + if ( gtd_is_control(p_qtd) ) + { + return &ohci_data.control[p_qtd->index].ed; + }else + { + return &ohci_data.ed_pool[p_qtd->index]; + } +} + +static inline uint32_t gtd_xfer_byte_left(uint32_t buffer_end, uint32_t current_buffer) +{ // 5.2.9 OHCI sample code + return (tu_align4k(buffer_end ^ current_buffer) ? 0x1000 : 0) + + tu_offset4k(buffer_end) - tu_offset4k(current_buffer) + 1; +} + +static void done_queue_isr(uint8_t hostid) +{ + (void) hostid; + + // done head is written in reversed order of completion --> need to reverse the done queue first + ohci_td_item_t* td_head = list_reverse ( (ohci_td_item_t*) tu_align16(ohci_data.hcca.done_head) ); + + while( td_head != NULL ) + { + // TODO check if td_head is iso td + //------------- Non ISO transfer -------------// + ohci_gtd_t * const p_qtd = (ohci_gtd_t *) td_head; + xfer_result_t const event = (p_qtd->condition_code == OHCI_CCODE_NO_ERROR) ? XFER_RESULT_SUCCESS : + (p_qtd->condition_code == OHCI_CCODE_STALL) ? XFER_RESULT_STALLED : XFER_RESULT_FAILED; + + p_qtd->used = 0; // free TD + if ( (p_qtd->delay_interrupt == OHCI_INT_ON_COMPLETE_YES) || (event != XFER_RESULT_SUCCESS) ) + { + ohci_ed_t * const p_ed = gtd_get_ed(p_qtd); + + uint32_t const xferred_bytes = p_qtd->expected_bytes - gtd_xfer_byte_left((uint32_t) p_qtd->buffer_end, (uint32_t) p_qtd->current_buffer_pointer); + + // NOTE Assuming the current list is BULK and there is no other EDs in the list has queued TDs. + // When there is a error resulting this ED is halted, and this EP still has other queued TD + // --> the Bulk list only has this halted EP queueing TDs (remaining) + // --> Bulk list will be considered as not empty by HC !!! while there is no attempt transaction on this list + // --> HC will not process Control list (due to service ratio when Bulk list not empty) + // To walk-around this, the halted ED will have TailP = HeadP (empty list condition), when clearing halt + // the TailP must be set back to NULL for processing remaining TDs + if ((event != XFER_RESULT_SUCCESS)) + { + p_ed->td_tail &= 0x0Ful; + p_ed->td_tail |= tu_align16(p_ed->td_head.address); // mark halted EP as empty queue + if ( event == XFER_RESULT_STALLED ) p_ed->is_stalled = 1; + } + + hcd_event_xfer_complete(p_ed->dev_addr, + tu_edpt_addr(p_ed->ep_number, p_ed->pid == OHCI_PID_IN), + event, xferred_bytes); + } + + td_head = (ohci_td_item_t*) td_head->next; + } +} + +void hcd_isr(uint8_t hostid) +{ + uint32_t const int_en = OHCI_REG->interrupt_enable; + uint32_t const int_status = OHCI_REG->interrupt_status & int_en; + + if (int_status == 0) return; + + //------------- RootHub status -------------// + if ( int_status & OHCI_INT_RHPORT_STATUS_CHANGE_MASK ) + { + uint32_t const rhport_status = OHCI_REG->rhport_status[0] & OHCI_RHPORT_ALL_CHANGE_MASK; + + // TODO dual port is not yet supported + if ( rhport_status & OHCI_RHPORT_CONNECT_STATUS_CHANGE_MASK ) + { + // TODO check if remote wake-up + if ( OHCI_REG->rhport_status_bit[0].current_connect_status ) + { + // TODO reset port immediately, without this controller will got 2-3 (debouncing connection status change) + OHCI_REG->rhport_status[0] = OHCI_RHPORT_PORT_RESET_STATUS_MASK; + hcd_event_device_attach(0); + }else + { + hcd_event_device_remove(0); + } + } + + if ( rhport_status & OHCI_RHPORT_PORT_SUSPEND_CHANGE_MASK) + { + + } + + OHCI_REG->rhport_status[0] = rhport_status; // acknowledge all interrupt + } + + //------------- Transfer Complete -------------// + if ( int_status & OHCI_INT_WRITEBACK_DONEHEAD_MASK) + { + done_queue_isr(hostid); + } + + OHCI_REG->interrupt_status = int_status; // Acknowledge handled interrupt +} +//--------------------------------------------------------------------+ +// HELPER +//--------------------------------------------------------------------+ + + +#endif + diff --git a/src/host/ohci/ohci.h b/src/host/ohci/ohci.h index a8aae251b..6f6ef9676 100644 --- a/src/host/ohci/ohci.h +++ b/src/host/ohci/ohci.h @@ -1,291 +1,291 @@ -/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2019 Ha Thach (tinyusb.org)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * This file is part of the TinyUSB stack.
- */
-
-/** \ingroup Group_HCD
- * @{
- * \defgroup OHCI
- * \brief OHCI driver. All documents sources mentioned here (eg section 3.5) is referring to OHCI Specs unless state otherwise
- * @{ */
-
-#ifndef _TUSB_OHCI_H_
-#define _TUSB_OHCI_H_
-
-#ifdef __cplusplus
- extern "C" {
-#endif
-
-#include "common/tusb_common.h"
-
-//--------------------------------------------------------------------+
-// OHCI CONFIGURATION & CONSTANTS
-//--------------------------------------------------------------------+
-#define HOST_HCD_XFER_INTERRUPT // TODO interrupt is used widely, should always be enalbed
-#define OHCI_PERIODIC_LIST (defined HOST_HCD_XFER_INTERRUPT || defined HOST_HCD_XFER_ISOCHRONOUS)
-
-// TODO merge OHCI with EHCI
-enum {
- OHCI_MAX_ITD = 4
-};
-
-enum {
- OHCI_PID_SETUP = 0,
- OHCI_PID_OUT,
- OHCI_PID_IN,
-};
-
-//--------------------------------------------------------------------+
-// OHCI Data Structure
-//--------------------------------------------------------------------+
-typedef struct {
- uint32_t interrupt_table[32];
- volatile uint16_t frame_number;
- volatile uint16_t frame_pad;
- volatile uint32_t done_head;
- uint8_t reserved[116+4]; // TODO try to make use of this area if possible, extra 4 byte to make the whole struct size = 256
-}ohci_hcca_t; // TU_ATTR_ALIGNED(256)
-
-TU_VERIFY_STATIC( sizeof(ohci_hcca_t) == 256, "size is not correct" );
-
-typedef struct {
- uint32_t reserved[2];
- volatile uint32_t next;
- uint32_t reserved2;
-}ohci_td_item_t;
-
-
-typedef struct TU_ATTR_ALIGNED(16)
-{
- // Word 0
- uint32_t used : 1;
- uint32_t index : 4; // endpoint index the td belongs to, or device address in case of control xfer
- uint32_t expected_bytes : 13; // TODO available for hcd
-
- uint32_t buffer_rounding : 1;
- uint32_t pid : 2;
- uint32_t delay_interrupt : 3;
- volatile uint32_t data_toggle : 2;
- volatile uint32_t error_count : 2;
- volatile uint32_t condition_code : 4;
-
- // Word 1
- volatile uint8_t* current_buffer_pointer;
-
- // Word 2 : next TD
- volatile uint32_t next;
-
- // Word 3
- uint8_t* buffer_end;
-} ohci_gtd_t;
-
-TU_VERIFY_STATIC( sizeof(ohci_gtd_t) == 16, "size is not correct" );
-
-typedef struct TU_ATTR_ALIGNED(16)
-{
- // Word 0
- uint32_t dev_addr : 7;
- uint32_t ep_number : 4;
- uint32_t pid : 2; // 00b from TD, 01b Out, 10b In
- uint32_t speed : 1;
- uint32_t skip : 1;
- uint32_t is_iso : 1;
- uint32_t max_packet_size : 11;
- // HCD: make use of 5 reserved bits
- uint32_t used : 1;
- uint32_t is_interrupt_xfer : 1;
- uint32_t is_stalled : 1;
- uint32_t : 2;
-
- // Word 1
- uint32_t td_tail;
-
- // Word 2
- volatile union {
- uint32_t address;
- struct {
- uint32_t halted : 1;
- uint32_t toggle : 1;
- uint32_t : 30;
- };
- }td_head;
-
- // Word 3: next ED
- uint32_t next;
-} ohci_ed_t;
-
-TU_VERIFY_STATIC( sizeof(ohci_ed_t) == 16, "size is not correct" );
-
-typedef struct TU_ATTR_ALIGNED(32)
-{
- /*---------- Word 1 ----------*/
- uint32_t starting_frame : 16;
- uint32_t : 5; // can be used
- uint32_t delay_interrupt : 3;
- uint32_t frame_count : 3;
- uint32_t : 1; // can be used
- volatile uint32_t condition_code : 4;
-
- /*---------- Word 2 ----------*/
- uint32_t buffer_page0; // 12 lsb bits can be used
-
- /*---------- Word 3 ----------*/
- volatile uint32_t next;
-
- /*---------- Word 4 ----------*/
- uint32_t buffer_end;
-
- /*---------- Word 5-8 ----------*/
- volatile uint16_t offset_packetstatus[8];
-} ochi_itd_t;
-
-TU_VERIFY_STATIC( sizeof(ochi_itd_t) == 32, "size is not correct" );
-
-// structure with member alignment required from large to small
-typedef struct TU_ATTR_ALIGNED(256)
-{
- ohci_hcca_t hcca;
-
- ohci_ed_t bulk_head_ed; // static bulk head (dummy)
- ohci_ed_t period_head_ed; // static periodic list head (dummy)
-
- // control endpoints has reserved resources
- struct {
- ohci_ed_t ed;
- ohci_gtd_t gtd;
- }control[CFG_TUSB_HOST_DEVICE_MAX+1];
-
- // ochi_itd_t itd[OHCI_MAX_ITD]; // itd requires alignment of 32
- ohci_ed_t ed_pool[HCD_MAX_ENDPOINT];
- ohci_gtd_t gtd_pool[HCD_MAX_XFER];
-
-} ohci_data_t;
-
-//--------------------------------------------------------------------+
-// OHCI Operational Register
-//--------------------------------------------------------------------+
-
-
-//--------------------------------------------------------------------+
-// OHCI Data Organization
-//--------------------------------------------------------------------+
-typedef volatile struct
-{
- uint32_t revision;
-
- union {
- uint32_t control;
- struct {
- uint32_t control_bulk_service_ratio : 2;
- uint32_t periodic_list_enable : 1;
- uint32_t isochronous_enable : 1;
- uint32_t control_list_enable : 1;
- uint32_t bulk_list_enable : 1;
- uint32_t hc_functional_state : 2;
- uint32_t interrupt_routing : 1;
- uint32_t remote_wakeup_connected : 1;
- uint32_t remote_wakeup_enale : 1;
- uint32_t : 0;
- }control_bit;
- };
-
- union {
- uint32_t command_status;
- struct {
- uint32_t controller_reset : 1;
- uint32_t control_list_filled : 1;
- uint32_t bulk_list_filled : 1;
- uint32_t ownership_change_request : 1;
- uint32_t : 12;
- uint32_t scheduling_overrun_count : 2;
- }command_status_bit;
- };
-
- uint32_t interrupt_status;
- uint32_t interrupt_enable;
- uint32_t interrupt_disable;
-
- uint32_t hcca;
- uint32_t period_current_ed;
- uint32_t control_head_ed;
- uint32_t control_current_ed;
- uint32_t bulk_head_ed;
- uint32_t bulk_current_ed;
- uint32_t done_head;
-
- uint32_t frame_interval;
- uint32_t frame_remaining;
- uint32_t frame_number;
- uint32_t periodic_start;
- uint32_t lowspeed_threshold;
-
- uint32_t rh_descriptorA;
- uint32_t rh_descriptorB;
-
- union {
- uint32_t rh_status;
- struct {
- uint32_t local_power_status : 1; // read Local Power Status; write: Clear Global Power
- uint32_t over_current_indicator : 1;
- uint32_t : 13;
- uint32_t device_remote_wakeup_enable : 1;
- uint32_t local_power_status_change : 1;
- uint32_t over_current_indicator_change : 1;
- uint32_t : 13;
- uint32_t clear_remote_wakeup_enable : 1;
- }rh_status_bit;
- };
-
- union {
- uint32_t rhport_status[2]; // TODO NXP OHCI controller only has 2 ports
- struct {
- uint32_t current_connect_status : 1;
- uint32_t port_enable_status : 1;
- uint32_t port_suspend_status : 1;
- uint32_t port_over_current_indicator : 1;
- uint32_t port_reset_status : 1;
- uint32_t : 3;
- uint32_t port_power_status : 1;
- uint32_t low_speed_device_attached : 1;
- uint32_t : 6;
- uint32_t connect_status_change : 1;
- uint32_t port_enable_status_change : 1;
- uint32_t port_suspend_status_change : 1;
- uint32_t port_over_current_indicator_change : 1;
- uint32_t port_reset_status_change : 1;
- uint32_t : 0;
- }rhport_status_bit[2];
- };
-}ohci_registers_t;
-
-TU_VERIFY_STATIC( sizeof(ohci_registers_t) == 0x5c, "size is not correct");
-
-#ifdef __cplusplus
- }
-#endif
-
-#endif /* _TUSB_OHCI_H_ */
-
-/** @} */
-/** @} */
+/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +/** \ingroup Group_HCD + * @{ + * \defgroup OHCI + * \brief OHCI driver. All documents sources mentioned here (eg section 3.5) is referring to OHCI Specs unless state otherwise + * @{ */ + +#ifndef _TUSB_OHCI_H_ +#define _TUSB_OHCI_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +#include "common/tusb_common.h" + +//--------------------------------------------------------------------+ +// OHCI CONFIGURATION & CONSTANTS +//--------------------------------------------------------------------+ +#define HOST_HCD_XFER_INTERRUPT // TODO interrupt is used widely, should always be enalbed +#define OHCI_PERIODIC_LIST (defined HOST_HCD_XFER_INTERRUPT || defined HOST_HCD_XFER_ISOCHRONOUS) + +// TODO merge OHCI with EHCI +enum { + OHCI_MAX_ITD = 4 +}; + +enum { + OHCI_PID_SETUP = 0, + OHCI_PID_OUT, + OHCI_PID_IN, +}; + +//--------------------------------------------------------------------+ +// OHCI Data Structure +//--------------------------------------------------------------------+ +typedef struct { + uint32_t interrupt_table[32]; + volatile uint16_t frame_number; + volatile uint16_t frame_pad; + volatile uint32_t done_head; + uint8_t reserved[116+4]; // TODO try to make use of this area if possible, extra 4 byte to make the whole struct size = 256 +}ohci_hcca_t; // TU_ATTR_ALIGNED(256) + +TU_VERIFY_STATIC( sizeof(ohci_hcca_t) == 256, "size is not correct" ); + +typedef struct { + uint32_t reserved[2]; + volatile uint32_t next; + uint32_t reserved2; +}ohci_td_item_t; + + +typedef struct TU_ATTR_ALIGNED(16) +{ + // Word 0 + uint32_t used : 1; + uint32_t index : 4; // endpoint index the td belongs to, or device address in case of control xfer + uint32_t expected_bytes : 13; // TODO available for hcd + + uint32_t buffer_rounding : 1; + uint32_t pid : 2; + uint32_t delay_interrupt : 3; + volatile uint32_t data_toggle : 2; + volatile uint32_t error_count : 2; + volatile uint32_t condition_code : 4; + + // Word 1 + volatile uint8_t* current_buffer_pointer; + + // Word 2 : next TD + volatile uint32_t next; + + // Word 3 + uint8_t* buffer_end; +} ohci_gtd_t; + +TU_VERIFY_STATIC( sizeof(ohci_gtd_t) == 16, "size is not correct" ); + +typedef struct TU_ATTR_ALIGNED(16) +{ + // Word 0 + uint32_t dev_addr : 7; + uint32_t ep_number : 4; + uint32_t pid : 2; // 00b from TD, 01b Out, 10b In + uint32_t speed : 1; + uint32_t skip : 1; + uint32_t is_iso : 1; + uint32_t max_packet_size : 11; + // HCD: make use of 5 reserved bits + uint32_t used : 1; + uint32_t is_interrupt_xfer : 1; + uint32_t is_stalled : 1; + uint32_t : 2; + + // Word 1 + uint32_t td_tail; + + // Word 2 + volatile union { + uint32_t address; + struct { + uint32_t halted : 1; + uint32_t toggle : 1; + uint32_t : 30; + }; + }td_head; + + // Word 3: next ED + uint32_t next; +} ohci_ed_t; + +TU_VERIFY_STATIC( sizeof(ohci_ed_t) == 16, "size is not correct" ); + +typedef struct TU_ATTR_ALIGNED(32) +{ + /*---------- Word 1 ----------*/ + uint32_t starting_frame : 16; + uint32_t : 5; // can be used + uint32_t delay_interrupt : 3; + uint32_t frame_count : 3; + uint32_t : 1; // can be used + volatile uint32_t condition_code : 4; + + /*---------- Word 2 ----------*/ + uint32_t buffer_page0; // 12 lsb bits can be used + + /*---------- Word 3 ----------*/ + volatile uint32_t next; + + /*---------- Word 4 ----------*/ + uint32_t buffer_end; + + /*---------- Word 5-8 ----------*/ + volatile uint16_t offset_packetstatus[8]; +} ochi_itd_t; + +TU_VERIFY_STATIC( sizeof(ochi_itd_t) == 32, "size is not correct" ); + +// structure with member alignment required from large to small +typedef struct TU_ATTR_ALIGNED(256) +{ + ohci_hcca_t hcca; + + ohci_ed_t bulk_head_ed; // static bulk head (dummy) + ohci_ed_t period_head_ed; // static periodic list head (dummy) + + // control endpoints has reserved resources + struct { + ohci_ed_t ed; + ohci_gtd_t gtd; + }control[CFG_TUSB_HOST_DEVICE_MAX+1]; + + // ochi_itd_t itd[OHCI_MAX_ITD]; // itd requires alignment of 32 + ohci_ed_t ed_pool[HCD_MAX_ENDPOINT]; + ohci_gtd_t gtd_pool[HCD_MAX_XFER]; + +} ohci_data_t; + +//--------------------------------------------------------------------+ +// OHCI Operational Register +//--------------------------------------------------------------------+ + + +//--------------------------------------------------------------------+ +// OHCI Data Organization +//--------------------------------------------------------------------+ +typedef volatile struct +{ + uint32_t revision; + + union { + uint32_t control; + struct { + uint32_t control_bulk_service_ratio : 2; + uint32_t periodic_list_enable : 1; + uint32_t isochronous_enable : 1; + uint32_t control_list_enable : 1; + uint32_t bulk_list_enable : 1; + uint32_t hc_functional_state : 2; + uint32_t interrupt_routing : 1; + uint32_t remote_wakeup_connected : 1; + uint32_t remote_wakeup_enale : 1; + uint32_t : 0; + }control_bit; + }; + + union { + uint32_t command_status; + struct { + uint32_t controller_reset : 1; + uint32_t control_list_filled : 1; + uint32_t bulk_list_filled : 1; + uint32_t ownership_change_request : 1; + uint32_t : 12; + uint32_t scheduling_overrun_count : 2; + }command_status_bit; + }; + + uint32_t interrupt_status; + uint32_t interrupt_enable; + uint32_t interrupt_disable; + + uint32_t hcca; + uint32_t period_current_ed; + uint32_t control_head_ed; + uint32_t control_current_ed; + uint32_t bulk_head_ed; + uint32_t bulk_current_ed; + uint32_t done_head; + + uint32_t frame_interval; + uint32_t frame_remaining; + uint32_t frame_number; + uint32_t periodic_start; + uint32_t lowspeed_threshold; + + uint32_t rh_descriptorA; + uint32_t rh_descriptorB; + + union { + uint32_t rh_status; + struct { + uint32_t local_power_status : 1; // read Local Power Status; write: Clear Global Power + uint32_t over_current_indicator : 1; + uint32_t : 13; + uint32_t device_remote_wakeup_enable : 1; + uint32_t local_power_status_change : 1; + uint32_t over_current_indicator_change : 1; + uint32_t : 13; + uint32_t clear_remote_wakeup_enable : 1; + }rh_status_bit; + }; + + union { + uint32_t rhport_status[2]; // TODO NXP OHCI controller only has 2 ports + struct { + uint32_t current_connect_status : 1; + uint32_t port_enable_status : 1; + uint32_t port_suspend_status : 1; + uint32_t port_over_current_indicator : 1; + uint32_t port_reset_status : 1; + uint32_t : 3; + uint32_t port_power_status : 1; + uint32_t low_speed_device_attached : 1; + uint32_t : 6; + uint32_t connect_status_change : 1; + uint32_t port_enable_status_change : 1; + uint32_t port_suspend_status_change : 1; + uint32_t port_over_current_indicator_change : 1; + uint32_t port_reset_status_change : 1; + uint32_t : 0; + }rhport_status_bit[2]; + }; +}ohci_registers_t; + +TU_VERIFY_STATIC( sizeof(ohci_registers_t) == 0x5c, "size is not correct"); + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_OHCI_H_ */ + +/** @} */ +/** @} */ diff --git a/src/host/usbh.c b/src/host/usbh.c index 42a99f3d2..f1b88259c 100644 --- a/src/host/usbh.c +++ b/src/host/usbh.c @@ -1,676 +1,676 @@ -/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2019 Ha Thach (tinyusb.org)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * This file is part of the TinyUSB stack.
- */
-
-#include "common/tusb_common.h"
-
-#if TUSB_OPT_HOST_ENABLED
-
-#ifndef CFG_TUH_TASK_QUEUE_SZ
-#define CFG_TUH_TASK_QUEUE_SZ 16
-#endif
-
-//--------------------------------------------------------------------+
-// INCLUDE
-//--------------------------------------------------------------------+
-#include "tusb.h"
-#include "hub.h"
-#include "usbh_hcd.h"
-
-//--------------------------------------------------------------------+
-// MACRO CONSTANT TYPEDEF
-//--------------------------------------------------------------------+
-static host_class_driver_t const usbh_class_drivers[] =
-{
- #if CFG_TUH_CDC
- {
- .class_code = TUSB_CLASS_CDC,
- .init = cdch_init,
- .open = cdch_open,
- .isr = cdch_isr,
- .close = cdch_close
- },
- #endif
-
- #if CFG_TUH_MSC
- {
- .class_code = TUSB_CLASS_MSC,
- .init = msch_init,
- .open = msch_open,
- .isr = msch_isr,
- .close = msch_close
- },
- #endif
-
- #if HOST_CLASS_HID
- {
- .class_code = TUSB_CLASS_HID,
- .init = hidh_init,
- .open = hidh_open_subtask,
- .isr = hidh_isr,
- .close = hidh_close
- },
- #endif
-
- #if CFG_TUH_HUB
- {
- .class_code = TUSB_CLASS_HUB,
- .init = hub_init,
- .open = hub_open,
- .isr = hub_isr,
- .close = hub_close
- },
- #endif
-
- #if CFG_TUH_VENDOR
- {
- .class_code = TUSB_CLASS_VENDOR_SPECIFIC,
- .init = cush_init,
- .open = cush_open_subtask,
- .isr = cush_isr,
- .close = cush_close
- }
- #endif
-};
-
-enum { USBH_CLASS_DRIVER_COUNT = TU_ARRAY_SZIE(usbh_class_drivers) };
-
-//--------------------------------------------------------------------+
-// INTERNAL OBJECT & FUNCTION DECLARATION
-//--------------------------------------------------------------------+
-
-// including zero-address
-CFG_TUSB_MEM_SECTION usbh_device_t _usbh_devices[CFG_TUSB_HOST_DEVICE_MAX+1];
-
-// Event queue
-// role device/host is used by OS NONE for mutex (disable usb isr) only
-OSAL_QUEUE_DEF(OPT_MODE_HOST, _usbh_qdef, CFG_TUH_TASK_QUEUE_SZ, hcd_event_t);
-static osal_queue_t _usbh_q;
-
-CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(4) static uint8_t _usbh_ctrl_buf[CFG_TUSB_HOST_ENUM_BUFFER_SIZE];
-
-//------------- Reporter Task Data -------------//
-
-//------------- Helper Function Prototypes -------------//
-static inline uint8_t get_new_address(void);
-static inline uint8_t get_configure_number_for_device(tusb_desc_device_t* dev_desc);
-static void mark_interface_endpoint(uint8_t ep2drv[8][2], uint8_t const* p_desc, uint16_t desc_len, uint8_t driver_id);
-
-//--------------------------------------------------------------------+
-// PUBLIC API (Parameter Verification is required)
-//--------------------------------------------------------------------+
-tusb_device_state_t tuh_device_get_state (uint8_t const dev_addr)
-{
- TU_ASSERT( dev_addr <= CFG_TUSB_HOST_DEVICE_MAX, TUSB_DEVICE_STATE_UNPLUG);
- return (tusb_device_state_t) _usbh_devices[dev_addr].state;
-}
-
-//--------------------------------------------------------------------+
-// CLASS-USBD API (don't require to verify parameters)
-//--------------------------------------------------------------------+
-bool usbh_init(void)
-{
- tu_memclr(_usbh_devices, sizeof(usbh_device_t)*(CFG_TUSB_HOST_DEVICE_MAX+1));
-
- //------------- Enumeration & Reporter Task init -------------//
- _usbh_q = osal_queue_create( &_usbh_qdef );
- TU_ASSERT(_usbh_q != NULL);
-
- //------------- Semaphore, Mutex for Control Pipe -------------//
- for(uint8_t i=0; i<CFG_TUSB_HOST_DEVICE_MAX+1; i++) // including address zero
- {
- usbh_device_t * const dev = &_usbh_devices[i];
-
- dev->control.sem_hdl = osal_semaphore_create(&dev->control.sem_def);
- TU_ASSERT(dev->control.sem_hdl != NULL);
-
- dev->control.mutex_hdl = osal_mutex_create(&dev->control.mutex_def);
- TU_ASSERT(dev->control.mutex_hdl != NULL);
-
- memset(dev->itf2drv, 0xff, sizeof(dev->itf2drv)); // invalid mapping
- memset(dev->ep2drv , 0xff, sizeof(dev->ep2drv )); // invalid mapping
- }
-
- // Class drivers init
- for (uint8_t drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++) usbh_class_drivers[drv_id].init();
-
- TU_ASSERT(hcd_init());
- hcd_int_enable(TUH_OPT_RHPORT);
-
- return true;
-}
-
-//------------- USBH control transfer -------------//
-bool usbh_control_xfer (uint8_t dev_addr, tusb_control_request_t* request, uint8_t* data)
-{
- usbh_device_t* dev = &_usbh_devices[dev_addr];
- const uint8_t rhport = dev->rhport;
-
- TU_ASSERT(osal_mutex_lock(dev->control.mutex_hdl, OSAL_TIMEOUT_NORMAL));
-
- dev->control.request = *request;
- dev->control.pipe_status = 0;
-
- // Setup Stage
- hcd_setup_send(rhport, dev_addr, (uint8_t*) &dev->control.request);
- TU_VERIFY(osal_semaphore_wait(dev->control.sem_hdl, OSAL_TIMEOUT_NORMAL));
-
- // Data stage : first data toggle is always 1
- if ( request->wLength )
- {
- hcd_edpt_xfer(rhport, dev_addr, tu_edpt_addr(0, request->bmRequestType_bit.direction), data, request->wLength);
- TU_VERIFY(osal_semaphore_wait(dev->control.sem_hdl, OSAL_TIMEOUT_NORMAL));
- }
-
- // Status : data toggle is always 1
- hcd_edpt_xfer(rhport, dev_addr, tu_edpt_addr(0, 1-request->bmRequestType_bit.direction), NULL, 0);
- TU_VERIFY(osal_semaphore_wait(dev->control.sem_hdl, OSAL_TIMEOUT_NORMAL));
-
- osal_mutex_unlock(dev->control.mutex_hdl);
-
- if ( XFER_RESULT_STALLED == dev->control.pipe_status ) return false;
- if ( XFER_RESULT_FAILED == dev->control.pipe_status ) return false;
-
- return true;
-}
-
-tusb_error_t usbh_pipe_control_open(uint8_t dev_addr, uint8_t max_packet_size)
-{
- osal_semaphore_reset( _usbh_devices[dev_addr].control.sem_hdl );
- //osal_mutex_reset( usbh_devices[dev_addr].control.mutex_hdl );
-
- tusb_desc_endpoint_t ep0_desc =
- {
- .bLength = sizeof(tusb_desc_endpoint_t),
- .bDescriptorType = TUSB_DESC_ENDPOINT,
- .bEndpointAddress = 0,
- .bmAttributes = { .xfer = TUSB_XFER_CONTROL },
- .wMaxPacketSize = { .size = max_packet_size },
- .bInterval = 0
- };
-
- hcd_edpt_open(_usbh_devices[dev_addr].rhport, dev_addr, &ep0_desc);
-
- return TUSB_ERROR_NONE;
-}
-
-//--------------------------------------------------------------------+
-// USBH-HCD ISR/Callback API
-//--------------------------------------------------------------------+
-// interrupt caused by a TD (with IOC=1) in pipe of class class_code
-void hcd_event_xfer_complete(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes)
-{
- usbh_device_t* dev = &_usbh_devices[ dev_addr ];
-
- if (0 == tu_edpt_number(ep_addr))
- {
- dev->control.pipe_status = event;
-// usbh_devices[ pipe_hdl.dev_addr ].control.xferred_bytes = xferred_bytes; not yet neccessary
- osal_semaphore_post( dev->control.sem_hdl, true );
- }
- else
- {
- uint8_t drv_id = dev->ep2drv[tu_edpt_number(ep_addr)][tu_edpt_dir(ep_addr)];
- TU_ASSERT(drv_id < USBH_CLASS_DRIVER_COUNT, );
-
- if (usbh_class_drivers[drv_id].isr)
- {
- usbh_class_drivers[drv_id].isr(dev_addr, ep_addr, event, xferred_bytes);
- }
- else
- {
- TU_BREAKPOINT(); // something wrong, no one claims the isr's source
- }
- }
-}
-
-void hcd_event_device_attach(uint8_t rhport)
-{
- hcd_event_t event =
- {
- .rhport = rhport,
- .event_id = HCD_EVENT_DEVICE_ATTACH
- };
-
- event.attach.hub_addr = 0;
- event.attach.hub_port = 0;
-
- hcd_event_handler(&event, true);
-}
-
-void hcd_event_handler(hcd_event_t const* event, bool in_isr)
-{
- switch (event->event_id)
- {
- default:
- osal_queue_send(_usbh_q, event, in_isr);
- break;
- }
-}
-
-void hcd_event_device_remove(uint8_t hostid)
-{
- hcd_event_t event =
- {
- .rhport = hostid,
- .event_id = HCD_EVENT_DEVICE_REMOVE
- };
-
- event.attach.hub_addr = 0;
- event.attach.hub_port = 0;
-
- hcd_event_handler(&event, true);
-}
-
-
-// a device unplugged on hostid, hub_addr, hub_port
-// return true if found and unmounted device, false if cannot find
-static void usbh_device_unplugged(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port)
-{
- //------------- find the all devices (star-network) under port that is unplugged -------------//
- for (uint8_t dev_addr = 0; dev_addr <= CFG_TUSB_HOST_DEVICE_MAX; dev_addr ++)
- {
- usbh_device_t* dev = &_usbh_devices[dev_addr];
-
- // TODO Hub multiple level
- if (dev->rhport == rhport &&
- (hub_addr == 0 || dev->hub_addr == hub_addr) && // hub_addr == 0 & hub_port == 0 means roothub
- (hub_port == 0 || dev->hub_port == hub_port) &&
- dev->state != TUSB_DEVICE_STATE_UNPLUG)
- {
- // Invoke callback before close driver
- if (tuh_umount_cb) tuh_umount_cb(dev_addr);
-
- // Close class driver
- for (uint8_t drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++) usbh_class_drivers[drv_id].close(dev_addr);
-
- memset(dev->itf2drv, 0xff, sizeof(dev->itf2drv)); // invalid mapping
- memset(dev->ep2drv , 0xff, sizeof(dev->ep2drv )); // invalid mapping
-
- hcd_device_close(rhport, dev_addr);
-
- dev->state = TUSB_DEVICE_STATE_UNPLUG;
- }
- }
-}
-
-//--------------------------------------------------------------------+
-// ENUMERATION TASK
-//--------------------------------------------------------------------+
-
-bool enum_task(hcd_event_t* event)
-{
- enum {
-#if 1
- // FIXME ohci LPC1769 xpresso + debugging to have 1st control xfer to work, some kind of timing or ohci driver issue !!!
- POWER_STABLE_DELAY = 100,
- RESET_DELAY = 500
-#else
- POWER_STABLE_DELAY = 500,
- RESET_DELAY = 200, // USB specs say only 50ms but many devices require much longer
-#endif
- };
-
- // for OSAL_NONE local variable won't retain value after blocking service sem_wait/queue_recv
- static uint8_t configure_selected = 1; // TODO move
-
- usbh_device_t* dev0 = &_usbh_devices[0];
- tusb_control_request_t request;
-
- dev0->rhport = event->rhport; // TODO refractor integrate to device_pool
- dev0->hub_addr = event->attach.hub_addr;
- dev0->hub_port = event->attach.hub_port;
- dev0->state = TUSB_DEVICE_STATE_UNPLUG;
-
- //------------- connected/disconnected directly with roothub -------------//
- if ( dev0->hub_addr == 0)
- {
- if( hcd_port_connect_status(dev0->rhport) )
- {
- // connection event
- osal_task_delay(POWER_STABLE_DELAY); // wait until device is stable. Increase this if the first 8 bytes is failed to get
-
- // exit if device unplugged while delaying
- if ( !hcd_port_connect_status(dev0->rhport) ) return true;
-
- hcd_port_reset( dev0->rhport ); // port must be reset to have correct speed operation
- osal_task_delay(RESET_DELAY);
-
- dev0->speed = hcd_port_speed_get( dev0->rhport );
- }
- else
- {
- // disconnection event
- usbh_device_unplugged(dev0->rhport, 0, 0);
- return true; // restart task
- }
- }
- #if CFG_TUH_HUB
- //------------- connected/disconnected via hub -------------//
- else
- {
- //------------- Get Port Status -------------//
- request = (tusb_control_request_t ) {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN },
- .bRequest = HUB_REQUEST_GET_STATUS,
- .wValue = 0,
- .wIndex = dev0->hub_port,
- .wLength = 4
- };
- // TODO hub refractor
- TU_VERIFY_HDLR( usbh_control_xfer( dev0->hub_addr, &request, _usbh_ctrl_buf ), hub_status_pipe_queue( dev0->hub_addr) );
-
- // Acknowledge Port Connection Change
- hub_port_clear_feature_subtask(dev0->hub_addr, dev0->hub_port, HUB_FEATURE_PORT_CONNECTION_CHANGE);
-
- hub_port_status_response_t * p_port_status;
- p_port_status = ((hub_port_status_response_t *) _usbh_ctrl_buf);
-
- if ( ! p_port_status->status_change.connect_status ) return true; // only handle connection change
-
- if ( ! p_port_status->status_current.connect_status )
- {
- // Disconnection event
- usbh_device_unplugged(dev0->rhport, dev0->hub_addr, dev0->hub_port);
-
- (void) hub_status_pipe_queue( dev0->hub_addr ); // done with hub, waiting for next data on status pipe
- return true; // restart task
- }
- else
- {
- // Connection Event
- TU_VERIFY_HDLR(hub_port_reset_subtask(dev0->hub_addr, dev0->hub_port),
- hub_status_pipe_queue( dev0->hub_addr) ); // TODO hub refractor
-
- dev0->speed = hub_port_get_speed();
-
- // Acknowledge Port Reset Change
- hub_port_clear_feature_subtask(dev0->hub_addr, dev0->hub_port, HUB_FEATURE_PORT_RESET_CHANGE);
- }
- }
- #endif
-
- TU_ASSERT_ERR( usbh_pipe_control_open(0, 8) );
-
- //------------- Get first 8 bytes of device descriptor to get Control Endpoint Size -------------//
- request = (tusb_control_request_t ) {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_IN },
- .bRequest = TUSB_REQ_GET_DESCRIPTOR,
- .wValue = TUSB_DESC_DEVICE << 8,
- .wIndex = 0,
- .wLength = 8
- };
- bool is_ok = usbh_control_xfer(0, &request, _usbh_ctrl_buf);
-
- //------------- Reset device again before Set Address -------------//
- if (dev0->hub_addr == 0)
- {
- // connected directly to roothub
- TU_ASSERT(is_ok); // TODO some slow device is observed to fail the very fist controller xfer, can try more times
- hcd_port_reset( dev0->rhport ); // reset port after 8 byte descriptor
- osal_task_delay(RESET_DELAY);
- }
- #if CFG_TUH_HUB
- else
- {
- // connected via a hub
- TU_VERIFY_HDLR(is_ok, hub_status_pipe_queue( dev0->hub_addr) ); // TODO hub refractor
-
- if ( hub_port_reset_subtask(dev0->hub_addr, dev0->hub_port) )
- {
- // Acknowledge Port Reset Change if Reset Successful
- hub_port_clear_feature_subtask(dev0->hub_addr, dev0->hub_port, HUB_FEATURE_PORT_RESET_CHANGE);
- }
-
- (void) hub_status_pipe_queue( dev0->hub_addr ); // done with hub, waiting for next data on status pipe
- }
- #endif
-
- //------------- Set new address -------------//
- uint8_t const new_addr = get_new_address();
- TU_ASSERT(new_addr <= CFG_TUSB_HOST_DEVICE_MAX); // TODO notify application we reach max devices
-
- request = (tusb_control_request_t ) {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_OUT },
- .bRequest = TUSB_REQ_SET_ADDRESS,
- .wValue = new_addr,
- .wIndex = 0,
- .wLength = 0
- };
- TU_ASSERT(usbh_control_xfer(0, &request, NULL));
-
- //------------- update port info & close control pipe of addr0 -------------//
- usbh_device_t* new_dev = &_usbh_devices[new_addr];
- new_dev->rhport = dev0->rhport;
- new_dev->hub_addr = dev0->hub_addr;
- new_dev->hub_port = dev0->hub_port;
- new_dev->speed = dev0->speed;
-
- hcd_device_close(dev0->rhport, 0); // close device 0
- dev0->state = TUSB_DEVICE_STATE_UNPLUG;
-
- // open control pipe for new address
- TU_ASSERT_ERR ( usbh_pipe_control_open(new_addr, ((tusb_desc_device_t*) _usbh_ctrl_buf)->bMaxPacketSize0 ) );
-
- //------------- Get full device descriptor -------------//
- request = (tusb_control_request_t ) {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_IN },
- .bRequest = TUSB_REQ_GET_DESCRIPTOR,
- .wValue = TUSB_DESC_DEVICE << 8,
- .wIndex = 0,
- .wLength = 18
- };
- TU_ASSERT(usbh_control_xfer(new_addr, &request, _usbh_ctrl_buf));
-
- // update device info TODO alignment issue
- new_dev->vendor_id = ((tusb_desc_device_t*) _usbh_ctrl_buf)->idVendor;
- new_dev->product_id = ((tusb_desc_device_t*) _usbh_ctrl_buf)->idProduct;
- new_dev->configure_count = ((tusb_desc_device_t*) _usbh_ctrl_buf)->bNumConfigurations;
-
- configure_selected = get_configure_number_for_device((tusb_desc_device_t*) _usbh_ctrl_buf);
- TU_ASSERT(configure_selected <= new_dev->configure_count); // TODO notify application when invalid configuration
-
- //------------- Get 9 bytes of configuration descriptor -------------//
- request = (tusb_control_request_t ) {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_IN },
- .bRequest = TUSB_REQ_GET_DESCRIPTOR,
- .wValue = (TUSB_DESC_CONFIGURATION << 8) | (configure_selected - 1),
- .wIndex = 0,
- .wLength = 9
- };
- TU_ASSERT( usbh_control_xfer(new_addr, &request, _usbh_ctrl_buf));
-
- // TODO not enough buffer to hold configuration descriptor
- TU_ASSERT( CFG_TUSB_HOST_ENUM_BUFFER_SIZE >= ((tusb_desc_configuration_t*)_usbh_ctrl_buf)->wTotalLength );
-
- //------------- Get full configuration descriptor -------------//
- request.wLength = ((tusb_desc_configuration_t*)_usbh_ctrl_buf)->wTotalLength; // full length
- TU_ASSERT( usbh_control_xfer( new_addr, &request, _usbh_ctrl_buf ) );
-
- // update configuration info
- new_dev->interface_count = ((tusb_desc_configuration_t*) _usbh_ctrl_buf)->bNumInterfaces;
-
- //------------- Set Configure -------------//
- request = (tusb_control_request_t ) {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_OUT },
- .bRequest = TUSB_REQ_SET_CONFIGURATION,
- .wValue = configure_selected,
- .wIndex = 0,
- .wLength = 0
- };
- TU_ASSERT(usbh_control_xfer( new_addr, &request, NULL ));
-
- new_dev->state = TUSB_DEVICE_STATE_CONFIGURED;
-
- //------------- TODO Get String Descriptors -------------//
-
- //------------- parse configuration & install drivers -------------//
- uint8_t const* p_desc = _usbh_ctrl_buf + sizeof(tusb_desc_configuration_t);
-
- // parse each interfaces
- while( p_desc < _usbh_ctrl_buf + ((tusb_desc_configuration_t*)_usbh_ctrl_buf)->wTotalLength )
- {
- // skip until we see interface descriptor
- if ( TUSB_DESC_INTERFACE != tu_desc_type(p_desc) )
- {
- p_desc = tu_desc_next(p_desc); // skip the descriptor, increase by the descriptor's length
- }else
- {
- tusb_desc_interface_t* desc_itf = (tusb_desc_interface_t*) p_desc;
-
- // Check if class is supported
- uint8_t drv_id;
- for (drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++)
- {
- if ( usbh_class_drivers[drv_id].class_code == desc_itf->bInterfaceClass ) break;
- }
-
- if( drv_id >= USBH_CLASS_DRIVER_COUNT )
- {
- // skip unsupported class
- p_desc = tu_desc_next(p_desc);
- }
- else
- {
- // Interface number must not be used already TODO alternate interface
- TU_ASSERT( new_dev->itf2drv[desc_itf->bInterfaceNumber] == 0xff );
- new_dev->itf2drv[desc_itf->bInterfaceNumber] = drv_id;
-
- if (desc_itf->bInterfaceClass == TUSB_CLASS_HUB && new_dev->hub_addr != 0)
- {
- // TODO Attach hub to Hub is not currently supported
- // skip this interface
- p_desc = tu_desc_next(p_desc);
- }
- else
- {
- uint16_t itf_len = 0;
-
- if ( usbh_class_drivers[drv_id].open(new_dev->rhport, new_addr, desc_itf, &itf_len) )
- {
- mark_interface_endpoint(new_dev->ep2drv, p_desc, itf_len, drv_id);
- }
-
- TU_ASSERT( itf_len >= sizeof(tusb_desc_interface_t) );
- p_desc += itf_len;
- }
- }
- }
- }
-
- if (tuh_mount_cb) tuh_mount_cb(new_addr);
-
- return true;
-}
-
-/* USB Host Driver task
- * This top level thread manages all host controller event and delegates events to class-specific drivers.
- * This should be called periodically within the mainloop or rtos thread.
- *
- @code
- int main(void)
- {
- application_init();
- tusb_init();
-
- while(1) // the mainloop
- {
- application_code();
-
- tuh_task(); // tinyusb host task
- }
- }
- @endcode
- */
-void tuh_task(void)
-{
- // Skip if stack is not initialized
- if ( !tusb_inited() ) return;
-
- // Loop until there is no more events in the queue
- while (1)
- {
- hcd_event_t event;
- if ( !osal_queue_receive(_usbh_q, &event) ) return;
-
- switch (event.event_id)
- {
- case HCD_EVENT_DEVICE_ATTACH:
- case HCD_EVENT_DEVICE_REMOVE:
- enum_task(&event);
- break;
-
- default: break;
- }
- }
-}
-
-//--------------------------------------------------------------------+
-// INTERNAL HELPER
-//--------------------------------------------------------------------+
-static inline uint8_t get_new_address(void)
-{
- for (uint8_t addr=1; addr <= CFG_TUSB_HOST_DEVICE_MAX; addr++)
- {
- if (_usbh_devices[addr].state == TUSB_DEVICE_STATE_UNPLUG) return addr;
- }
- return CFG_TUSB_HOST_DEVICE_MAX+1;
-}
-
-static inline uint8_t get_configure_number_for_device(tusb_desc_device_t* dev_desc)
-{
- uint8_t config_num = 1;
-
- // invoke callback to ask user which configuration to select
- if (tuh_device_attached_cb)
- {
- config_num = tu_min8(1, tuh_device_attached_cb(dev_desc) );
- }
-
- return config_num;
-}
-
-// Helper marking endpoint of interface belongs to class driver
-// TODO merge with usbd
-static void mark_interface_endpoint(uint8_t ep2drv[8][2], uint8_t const* p_desc, uint16_t desc_len, uint8_t driver_id)
-{
- uint16_t len = 0;
-
- while( len < desc_len )
- {
- if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) )
- {
- uint8_t const ep_addr = ((tusb_desc_endpoint_t const*) p_desc)->bEndpointAddress;
-
- ep2drv[ tu_edpt_number(ep_addr) ][ tu_edpt_dir(ep_addr) ] = driver_id;
- }
-
- len += tu_desc_len(p_desc);
- p_desc = tu_desc_next(p_desc);
- }
-}
-
-
-#endif
+/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#include "common/tusb_common.h" + +#if TUSB_OPT_HOST_ENABLED + +#ifndef CFG_TUH_TASK_QUEUE_SZ +#define CFG_TUH_TASK_QUEUE_SZ 16 +#endif + +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "tusb.h" +#include "hub.h" +#include "usbh_hcd.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +static host_class_driver_t const usbh_class_drivers[] = +{ + #if CFG_TUH_CDC + { + .class_code = TUSB_CLASS_CDC, + .init = cdch_init, + .open = cdch_open, + .isr = cdch_isr, + .close = cdch_close + }, + #endif + + #if CFG_TUH_MSC + { + .class_code = TUSB_CLASS_MSC, + .init = msch_init, + .open = msch_open, + .isr = msch_isr, + .close = msch_close + }, + #endif + + #if HOST_CLASS_HID + { + .class_code = TUSB_CLASS_HID, + .init = hidh_init, + .open = hidh_open_subtask, + .isr = hidh_isr, + .close = hidh_close + }, + #endif + + #if CFG_TUH_HUB + { + .class_code = TUSB_CLASS_HUB, + .init = hub_init, + .open = hub_open, + .isr = hub_isr, + .close = hub_close + }, + #endif + + #if CFG_TUH_VENDOR + { + .class_code = TUSB_CLASS_VENDOR_SPECIFIC, + .init = cush_init, + .open = cush_open_subtask, + .isr = cush_isr, + .close = cush_close + } + #endif +}; + +enum { USBH_CLASS_DRIVER_COUNT = TU_ARRAY_SIZE(usbh_class_drivers) }; + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ + +// including zero-address +CFG_TUSB_MEM_SECTION usbh_device_t _usbh_devices[CFG_TUSB_HOST_DEVICE_MAX+1]; + +// Event queue +// role device/host is used by OS NONE for mutex (disable usb isr) only +OSAL_QUEUE_DEF(OPT_MODE_HOST, _usbh_qdef, CFG_TUH_TASK_QUEUE_SZ, hcd_event_t); +static osal_queue_t _usbh_q; + +CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(4) static uint8_t _usbh_ctrl_buf[CFG_TUSB_HOST_ENUM_BUFFER_SIZE]; + +//------------- Reporter Task Data -------------// + +//------------- Helper Function Prototypes -------------// +static inline uint8_t get_new_address(void); +static inline uint8_t get_configure_number_for_device(tusb_desc_device_t* dev_desc); +static void mark_interface_endpoint(uint8_t ep2drv[8][2], uint8_t const* p_desc, uint16_t desc_len, uint8_t driver_id); + +//--------------------------------------------------------------------+ +// PUBLIC API (Parameter Verification is required) +//--------------------------------------------------------------------+ +tusb_device_state_t tuh_device_get_state (uint8_t const dev_addr) +{ + TU_ASSERT( dev_addr <= CFG_TUSB_HOST_DEVICE_MAX, TUSB_DEVICE_STATE_UNPLUG); + return (tusb_device_state_t) _usbh_devices[dev_addr].state; +} + +//--------------------------------------------------------------------+ +// CLASS-USBD API (don't require to verify parameters) +//--------------------------------------------------------------------+ +bool usbh_init(void) +{ + tu_memclr(_usbh_devices, sizeof(usbh_device_t)*(CFG_TUSB_HOST_DEVICE_MAX+1)); + + //------------- Enumeration & Reporter Task init -------------// + _usbh_q = osal_queue_create( &_usbh_qdef ); + TU_ASSERT(_usbh_q != NULL); + + //------------- Semaphore, Mutex for Control Pipe -------------// + for(uint8_t i=0; i<CFG_TUSB_HOST_DEVICE_MAX+1; i++) // including address zero + { + usbh_device_t * const dev = &_usbh_devices[i]; + + dev->control.sem_hdl = osal_semaphore_create(&dev->control.sem_def); + TU_ASSERT(dev->control.sem_hdl != NULL); + + dev->control.mutex_hdl = osal_mutex_create(&dev->control.mutex_def); + TU_ASSERT(dev->control.mutex_hdl != NULL); + + memset(dev->itf2drv, 0xff, sizeof(dev->itf2drv)); // invalid mapping + memset(dev->ep2drv , 0xff, sizeof(dev->ep2drv )); // invalid mapping + } + + // Class drivers init + for (uint8_t drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++) usbh_class_drivers[drv_id].init(); + + TU_ASSERT(hcd_init()); + hcd_int_enable(TUH_OPT_RHPORT); + + return true; +} + +//------------- USBH control transfer -------------// +bool usbh_control_xfer (uint8_t dev_addr, tusb_control_request_t* request, uint8_t* data) +{ + usbh_device_t* dev = &_usbh_devices[dev_addr]; + const uint8_t rhport = dev->rhport; + + TU_ASSERT(osal_mutex_lock(dev->control.mutex_hdl, OSAL_TIMEOUT_NORMAL)); + + dev->control.request = *request; + dev->control.pipe_status = 0; + + // Setup Stage + hcd_setup_send(rhport, dev_addr, (uint8_t*) &dev->control.request); + TU_VERIFY(osal_semaphore_wait(dev->control.sem_hdl, OSAL_TIMEOUT_NORMAL)); + + // Data stage : first data toggle is always 1 + if ( request->wLength ) + { + hcd_edpt_xfer(rhport, dev_addr, tu_edpt_addr(0, request->bmRequestType_bit.direction), data, request->wLength); + TU_VERIFY(osal_semaphore_wait(dev->control.sem_hdl, OSAL_TIMEOUT_NORMAL)); + } + + // Status : data toggle is always 1 + hcd_edpt_xfer(rhport, dev_addr, tu_edpt_addr(0, 1-request->bmRequestType_bit.direction), NULL, 0); + TU_VERIFY(osal_semaphore_wait(dev->control.sem_hdl, OSAL_TIMEOUT_NORMAL)); + + osal_mutex_unlock(dev->control.mutex_hdl); + + if ( XFER_RESULT_STALLED == dev->control.pipe_status ) return false; + if ( XFER_RESULT_FAILED == dev->control.pipe_status ) return false; + + return true; +} + +tusb_error_t usbh_pipe_control_open(uint8_t dev_addr, uint8_t max_packet_size) +{ + osal_semaphore_reset( _usbh_devices[dev_addr].control.sem_hdl ); + //osal_mutex_reset( usbh_devices[dev_addr].control.mutex_hdl ); + + tusb_desc_endpoint_t ep0_desc = + { + .bLength = sizeof(tusb_desc_endpoint_t), + .bDescriptorType = TUSB_DESC_ENDPOINT, + .bEndpointAddress = 0, + .bmAttributes = { .xfer = TUSB_XFER_CONTROL }, + .wMaxPacketSize = { .size = max_packet_size }, + .bInterval = 0 + }; + + hcd_edpt_open(_usbh_devices[dev_addr].rhport, dev_addr, &ep0_desc); + + return TUSB_ERROR_NONE; +} + +//--------------------------------------------------------------------+ +// USBH-HCD ISR/Callback API +//--------------------------------------------------------------------+ +// interrupt caused by a TD (with IOC=1) in pipe of class class_code +void hcd_event_xfer_complete(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) +{ + usbh_device_t* dev = &_usbh_devices[ dev_addr ]; + + if (0 == tu_edpt_number(ep_addr)) + { + dev->control.pipe_status = event; +// usbh_devices[ pipe_hdl.dev_addr ].control.xferred_bytes = xferred_bytes; not yet neccessary + osal_semaphore_post( dev->control.sem_hdl, true ); + } + else + { + uint8_t drv_id = dev->ep2drv[tu_edpt_number(ep_addr)][tu_edpt_dir(ep_addr)]; + TU_ASSERT(drv_id < USBH_CLASS_DRIVER_COUNT, ); + + if (usbh_class_drivers[drv_id].isr) + { + usbh_class_drivers[drv_id].isr(dev_addr, ep_addr, event, xferred_bytes); + } + else + { + TU_BREAKPOINT(); // something wrong, no one claims the isr's source + } + } +} + +void hcd_event_device_attach(uint8_t rhport) +{ + hcd_event_t event = + { + .rhport = rhport, + .event_id = HCD_EVENT_DEVICE_ATTACH + }; + + event.attach.hub_addr = 0; + event.attach.hub_port = 0; + + hcd_event_handler(&event, true); +} + +void hcd_event_handler(hcd_event_t const* event, bool in_isr) +{ + switch (event->event_id) + { + default: + osal_queue_send(_usbh_q, event, in_isr); + break; + } +} + +void hcd_event_device_remove(uint8_t hostid) +{ + hcd_event_t event = + { + .rhport = hostid, + .event_id = HCD_EVENT_DEVICE_REMOVE + }; + + event.attach.hub_addr = 0; + event.attach.hub_port = 0; + + hcd_event_handler(&event, true); +} + + +// a device unplugged on hostid, hub_addr, hub_port +// return true if found and unmounted device, false if cannot find +static void usbh_device_unplugged(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port) +{ + //------------- find the all devices (star-network) under port that is unplugged -------------// + for (uint8_t dev_addr = 0; dev_addr <= CFG_TUSB_HOST_DEVICE_MAX; dev_addr ++) + { + usbh_device_t* dev = &_usbh_devices[dev_addr]; + + // TODO Hub multiple level + if (dev->rhport == rhport && + (hub_addr == 0 || dev->hub_addr == hub_addr) && // hub_addr == 0 & hub_port == 0 means roothub + (hub_port == 0 || dev->hub_port == hub_port) && + dev->state != TUSB_DEVICE_STATE_UNPLUG) + { + // Invoke callback before close driver + if (tuh_umount_cb) tuh_umount_cb(dev_addr); + + // Close class driver + for (uint8_t drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++) usbh_class_drivers[drv_id].close(dev_addr); + + memset(dev->itf2drv, 0xff, sizeof(dev->itf2drv)); // invalid mapping + memset(dev->ep2drv , 0xff, sizeof(dev->ep2drv )); // invalid mapping + + hcd_device_close(rhport, dev_addr); + + dev->state = TUSB_DEVICE_STATE_UNPLUG; + } + } +} + +//--------------------------------------------------------------------+ +// ENUMERATION TASK +//--------------------------------------------------------------------+ + +bool enum_task(hcd_event_t* event) +{ + enum { +#if 1 + // FIXME ohci LPC1769 xpresso + debugging to have 1st control xfer to work, some kind of timing or ohci driver issue !!! + POWER_STABLE_DELAY = 100, + RESET_DELAY = 500 +#else + POWER_STABLE_DELAY = 500, + RESET_DELAY = 200, // USB specs say only 50ms but many devices require much longer +#endif + }; + + // for OSAL_NONE local variable won't retain value after blocking service sem_wait/queue_recv + static uint8_t configure_selected = 1; // TODO move + + usbh_device_t* dev0 = &_usbh_devices[0]; + tusb_control_request_t request; + + dev0->rhport = event->rhport; // TODO refractor integrate to device_pool + dev0->hub_addr = event->attach.hub_addr; + dev0->hub_port = event->attach.hub_port; + dev0->state = TUSB_DEVICE_STATE_UNPLUG; + + //------------- connected/disconnected directly with roothub -------------// + if ( dev0->hub_addr == 0) + { + if( hcd_port_connect_status(dev0->rhport) ) + { + // connection event + osal_task_delay(POWER_STABLE_DELAY); // wait until device is stable. Increase this if the first 8 bytes is failed to get + + // exit if device unplugged while delaying + if ( !hcd_port_connect_status(dev0->rhport) ) return true; + + hcd_port_reset( dev0->rhport ); // port must be reset to have correct speed operation + osal_task_delay(RESET_DELAY); + + dev0->speed = hcd_port_speed_get( dev0->rhport ); + } + else + { + // disconnection event + usbh_device_unplugged(dev0->rhport, 0, 0); + return true; // restart task + } + } + #if CFG_TUH_HUB + //------------- connected/disconnected via hub -------------// + else + { + //------------- Get Port Status -------------// + request = (tusb_control_request_t ) { + .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN }, + .bRequest = HUB_REQUEST_GET_STATUS, + .wValue = 0, + .wIndex = dev0->hub_port, + .wLength = 4 + }; + // TODO hub refractor + TU_VERIFY_HDLR( usbh_control_xfer( dev0->hub_addr, &request, _usbh_ctrl_buf ), hub_status_pipe_queue( dev0->hub_addr) ); + + // Acknowledge Port Connection Change + hub_port_clear_feature_subtask(dev0->hub_addr, dev0->hub_port, HUB_FEATURE_PORT_CONNECTION_CHANGE); + + hub_port_status_response_t * p_port_status; + p_port_status = ((hub_port_status_response_t *) _usbh_ctrl_buf); + + if ( ! p_port_status->status_change.connect_status ) return true; // only handle connection change + + if ( ! p_port_status->status_current.connect_status ) + { + // Disconnection event + usbh_device_unplugged(dev0->rhport, dev0->hub_addr, dev0->hub_port); + + (void) hub_status_pipe_queue( dev0->hub_addr ); // done with hub, waiting for next data on status pipe + return true; // restart task + } + else + { + // Connection Event + TU_VERIFY_HDLR(hub_port_reset_subtask(dev0->hub_addr, dev0->hub_port), + hub_status_pipe_queue( dev0->hub_addr) ); // TODO hub refractor + + dev0->speed = hub_port_get_speed(); + + // Acknowledge Port Reset Change + hub_port_clear_feature_subtask(dev0->hub_addr, dev0->hub_port, HUB_FEATURE_PORT_RESET_CHANGE); + } + } + #endif + + TU_ASSERT_ERR( usbh_pipe_control_open(0, 8) ); + + //------------- Get first 8 bytes of device descriptor to get Control Endpoint Size -------------// + request = (tusb_control_request_t ) { + .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_IN }, + .bRequest = TUSB_REQ_GET_DESCRIPTOR, + .wValue = TUSB_DESC_DEVICE << 8, + .wIndex = 0, + .wLength = 8 + }; + bool is_ok = usbh_control_xfer(0, &request, _usbh_ctrl_buf); + + //------------- Reset device again before Set Address -------------// + if (dev0->hub_addr == 0) + { + // connected directly to roothub + TU_ASSERT(is_ok); // TODO some slow device is observed to fail the very fist controller xfer, can try more times + hcd_port_reset( dev0->rhport ); // reset port after 8 byte descriptor + osal_task_delay(RESET_DELAY); + } + #if CFG_TUH_HUB + else + { + // connected via a hub + TU_VERIFY_HDLR(is_ok, hub_status_pipe_queue( dev0->hub_addr) ); // TODO hub refractor + + if ( hub_port_reset_subtask(dev0->hub_addr, dev0->hub_port) ) + { + // Acknowledge Port Reset Change if Reset Successful + hub_port_clear_feature_subtask(dev0->hub_addr, dev0->hub_port, HUB_FEATURE_PORT_RESET_CHANGE); + } + + (void) hub_status_pipe_queue( dev0->hub_addr ); // done with hub, waiting for next data on status pipe + } + #endif + + //------------- Set new address -------------// + uint8_t const new_addr = get_new_address(); + TU_ASSERT(new_addr <= CFG_TUSB_HOST_DEVICE_MAX); // TODO notify application we reach max devices + + request = (tusb_control_request_t ) { + .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_OUT }, + .bRequest = TUSB_REQ_SET_ADDRESS, + .wValue = new_addr, + .wIndex = 0, + .wLength = 0 + }; + TU_ASSERT(usbh_control_xfer(0, &request, NULL)); + + //------------- update port info & close control pipe of addr0 -------------// + usbh_device_t* new_dev = &_usbh_devices[new_addr]; + new_dev->rhport = dev0->rhport; + new_dev->hub_addr = dev0->hub_addr; + new_dev->hub_port = dev0->hub_port; + new_dev->speed = dev0->speed; + + hcd_device_close(dev0->rhport, 0); // close device 0 + dev0->state = TUSB_DEVICE_STATE_UNPLUG; + + // open control pipe for new address + TU_ASSERT_ERR ( usbh_pipe_control_open(new_addr, ((tusb_desc_device_t*) _usbh_ctrl_buf)->bMaxPacketSize0 ) ); + + //------------- Get full device descriptor -------------// + request = (tusb_control_request_t ) { + .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_IN }, + .bRequest = TUSB_REQ_GET_DESCRIPTOR, + .wValue = TUSB_DESC_DEVICE << 8, + .wIndex = 0, + .wLength = 18 + }; + TU_ASSERT(usbh_control_xfer(new_addr, &request, _usbh_ctrl_buf)); + + // update device info TODO alignment issue + new_dev->vendor_id = ((tusb_desc_device_t*) _usbh_ctrl_buf)->idVendor; + new_dev->product_id = ((tusb_desc_device_t*) _usbh_ctrl_buf)->idProduct; + new_dev->configure_count = ((tusb_desc_device_t*) _usbh_ctrl_buf)->bNumConfigurations; + + configure_selected = get_configure_number_for_device((tusb_desc_device_t*) _usbh_ctrl_buf); + TU_ASSERT(configure_selected <= new_dev->configure_count); // TODO notify application when invalid configuration + + //------------- Get 9 bytes of configuration descriptor -------------// + request = (tusb_control_request_t ) { + .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_IN }, + .bRequest = TUSB_REQ_GET_DESCRIPTOR, + .wValue = (TUSB_DESC_CONFIGURATION << 8) | (configure_selected - 1), + .wIndex = 0, + .wLength = 9 + }; + TU_ASSERT( usbh_control_xfer(new_addr, &request, _usbh_ctrl_buf)); + + // TODO not enough buffer to hold configuration descriptor + TU_ASSERT( CFG_TUSB_HOST_ENUM_BUFFER_SIZE >= ((tusb_desc_configuration_t*)_usbh_ctrl_buf)->wTotalLength ); + + //------------- Get full configuration descriptor -------------// + request.wLength = ((tusb_desc_configuration_t*)_usbh_ctrl_buf)->wTotalLength; // full length + TU_ASSERT( usbh_control_xfer( new_addr, &request, _usbh_ctrl_buf ) ); + + // update configuration info + new_dev->interface_count = ((tusb_desc_configuration_t*) _usbh_ctrl_buf)->bNumInterfaces; + + //------------- Set Configure -------------// + request = (tusb_control_request_t ) { + .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_OUT }, + .bRequest = TUSB_REQ_SET_CONFIGURATION, + .wValue = configure_selected, + .wIndex = 0, + .wLength = 0 + }; + TU_ASSERT(usbh_control_xfer( new_addr, &request, NULL )); + + new_dev->state = TUSB_DEVICE_STATE_CONFIGURED; + + //------------- TODO Get String Descriptors -------------// + + //------------- parse configuration & install drivers -------------// + uint8_t const* p_desc = _usbh_ctrl_buf + sizeof(tusb_desc_configuration_t); + + // parse each interfaces + while( p_desc < _usbh_ctrl_buf + ((tusb_desc_configuration_t*)_usbh_ctrl_buf)->wTotalLength ) + { + // skip until we see interface descriptor + if ( TUSB_DESC_INTERFACE != tu_desc_type(p_desc) ) + { + p_desc = tu_desc_next(p_desc); // skip the descriptor, increase by the descriptor's length + }else + { + tusb_desc_interface_t* desc_itf = (tusb_desc_interface_t*) p_desc; + + // Check if class is supported + uint8_t drv_id; + for (drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++) + { + if ( usbh_class_drivers[drv_id].class_code == desc_itf->bInterfaceClass ) break; + } + + if( drv_id >= USBH_CLASS_DRIVER_COUNT ) + { + // skip unsupported class + p_desc = tu_desc_next(p_desc); + } + else + { + // Interface number must not be used already TODO alternate interface + TU_ASSERT( new_dev->itf2drv[desc_itf->bInterfaceNumber] == 0xff ); + new_dev->itf2drv[desc_itf->bInterfaceNumber] = drv_id; + + if (desc_itf->bInterfaceClass == TUSB_CLASS_HUB && new_dev->hub_addr != 0) + { + // TODO Attach hub to Hub is not currently supported + // skip this interface + p_desc = tu_desc_next(p_desc); + } + else + { + uint16_t itf_len = 0; + + if ( usbh_class_drivers[drv_id].open(new_dev->rhport, new_addr, desc_itf, &itf_len) ) + { + mark_interface_endpoint(new_dev->ep2drv, p_desc, itf_len, drv_id); + } + + TU_ASSERT( itf_len >= sizeof(tusb_desc_interface_t) ); + p_desc += itf_len; + } + } + } + } + + if (tuh_mount_cb) tuh_mount_cb(new_addr); + + return true; +} + +/* USB Host Driver task + * This top level thread manages all host controller event and delegates events to class-specific drivers. + * This should be called periodically within the mainloop or rtos thread. + * + @code + int main(void) + { + application_init(); + tusb_init(); + + while(1) // the mainloop + { + application_code(); + + tuh_task(); // tinyusb host task + } + } + @endcode + */ +void tuh_task(void) +{ + // Skip if stack is not initialized + if ( !tusb_inited() ) return; + + // Loop until there is no more events in the queue + while (1) + { + hcd_event_t event; + if ( !osal_queue_receive(_usbh_q, &event) ) return; + + switch (event.event_id) + { + case HCD_EVENT_DEVICE_ATTACH: + case HCD_EVENT_DEVICE_REMOVE: + enum_task(&event); + break; + + default: break; + } + } +} + +//--------------------------------------------------------------------+ +// INTERNAL HELPER +//--------------------------------------------------------------------+ +static inline uint8_t get_new_address(void) +{ + for (uint8_t addr=1; addr <= CFG_TUSB_HOST_DEVICE_MAX; addr++) + { + if (_usbh_devices[addr].state == TUSB_DEVICE_STATE_UNPLUG) return addr; + } + return CFG_TUSB_HOST_DEVICE_MAX+1; +} + +static inline uint8_t get_configure_number_for_device(tusb_desc_device_t* dev_desc) +{ + uint8_t config_num = 1; + + // invoke callback to ask user which configuration to select + if (tuh_device_attached_cb) + { + config_num = tu_min8(1, tuh_device_attached_cb(dev_desc) ); + } + + return config_num; +} + +// Helper marking endpoint of interface belongs to class driver +// TODO merge with usbd +static void mark_interface_endpoint(uint8_t ep2drv[8][2], uint8_t const* p_desc, uint16_t desc_len, uint8_t driver_id) +{ + uint16_t len = 0; + + while( len < desc_len ) + { + if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) ) + { + uint8_t const ep_addr = ((tusb_desc_endpoint_t const*) p_desc)->bEndpointAddress; + + ep2drv[ tu_edpt_number(ep_addr) ][ tu_edpt_dir(ep_addr) ] = driver_id; + } + + len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } +} + + +#endif diff --git a/src/host/usbh.h b/src/host/usbh.h index 311b2514d..42a2bd095 100644 --- a/src/host/usbh.h +++ b/src/host/usbh.h @@ -68,6 +68,9 @@ typedef struct { //--------------------------------------------------------------------+ void tuh_task(void); +// Interrupt handler, name alias to HCD +#define tuh_isr hcd_isr + tusb_device_state_t tuh_device_get_state (uint8_t dev_addr); static inline bool tuh_device_is_configured(uint8_t dev_addr) { |
