mirror of
https://github.com/gdsports/USBHost_t36
synced 2024-11-24 01:52:23 -05:00
5737d12b62
I am not sure if you can also get the same hang in the periodic update list as well, but to be on the safe side added same checks as the one that was hanging.
1349 lines
44 KiB
C++
1349 lines
44 KiB
C++
/* USB EHCI Host for Teensy 3.6
|
|
* Copyright 2017 Paul Stoffregen (paul@pjrc.com)
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <Arduino.h>
|
|
#include "USBHost_t36.h" // Read this header first for key info
|
|
|
|
// All USB EHCI controller hardware access is done from this file's code.
|
|
// Hardware services are made available to the rest of this library by
|
|
// three structures:
|
|
//
|
|
// Pipe_t: Every USB endpoint is accessed by a pipe. new_Pipe()
|
|
// sets up the EHCI to support the pipe/endpoint, and delete_Pipe()
|
|
// removes this configuration.
|
|
//
|
|
// Transfer_t: These are used for all communication. Data transfers
|
|
// are placed into work queues, to be executed by the EHCI in
|
|
// the future. Transfer_t only manages data. The actual data
|
|
// is stored in a separate buffer (usually from a device driver)
|
|
// which is referenced from Transfer_t. All data transfer is queued,
|
|
// never done with blocking functions that wait. When transfers
|
|
// complete, a driver-supplied callback function is called to notify
|
|
// the driver.
|
|
//
|
|
// USBDriverTimer: Some drivers require timers. These allow drivers
|
|
// to share the hardware timer, with each USBDriverTimer object
|
|
// able to schedule a callback function a configurable number of
|
|
// microseconds in the future.
|
|
//
|
|
// In addition to these 3 services, the EHCI interrupt also responds
|
|
// to changes on the main port, creating and deleting the root device.
|
|
// See enumeration.cpp for all device-level code.
|
|
|
|
// Size of the periodic list, in milliseconds. This determines the
|
|
// slowest rate we can poll interrupt endpoints. Each entry uses
|
|
// 12 bytes (4 for a pointer, 8 for bandwidth management).
|
|
// Supported values: 8, 16, 32, 64, 128, 256, 512, 1024
|
|
#if defined(USBHS_PERIODIC_LIST_SIZE)
|
|
#define PERIODIC_LIST_SIZE (USBHS_PERIODIC_LIST_SIZE)
|
|
#else
|
|
#define PERIODIC_LIST_SIZE 32
|
|
#endif
|
|
|
|
// The EHCI periodic schedule, used for interrupt pipes/endpoints
|
|
static uint32_t periodictable[PERIODIC_LIST_SIZE] __attribute__ ((aligned(4096), used));
|
|
static uint8_t uframe_bandwidth[PERIODIC_LIST_SIZE*8];
|
|
|
|
// State of the 1 and only physical USB host port on Teensy 3.6
|
|
static uint8_t port_state;
|
|
#define PORT_STATE_DISCONNECTED 0
|
|
#define PORT_STATE_DEBOUNCE 1
|
|
#define PORT_STATE_RESET 2
|
|
#define PORT_STATE_RECOVERY 3
|
|
#define PORT_STATE_ACTIVE 4
|
|
|
|
// The device currently connected, or NULL when no device
|
|
static Device_t *rootdev=NULL;
|
|
|
|
// List of all queued transfers in the asychronous schedule (control & bulk).
|
|
// When the EHCI completes these transfers, this list is how we locate them
|
|
// in memory.
|
|
static Transfer_t *async_followup_first=NULL;
|
|
static Transfer_t *async_followup_last=NULL;
|
|
|
|
// List of all queued transfers in the asychronous schedule (interrupt endpoints)
|
|
// When the EHCI completes these transfers, this list is how we locate them
|
|
// in memory.
|
|
static Transfer_t *periodic_followup_first=NULL;
|
|
static Transfer_t *periodic_followup_last=NULL;
|
|
|
|
// List of all pending timers. This double linked list is stored in
|
|
// chronological order. Each timer is stored with the number of
|
|
// microseconds which need to elapsed from the prior timer on this
|
|
// list, to allow efficient servicing from the timer interrupt.
|
|
static USBDriverTimer *active_timers=NULL;
|
|
|
|
|
|
static void init_qTD(volatile Transfer_t *t, void *buf, uint32_t len,
|
|
uint32_t pid, uint32_t data01, bool irq);
|
|
static void add_to_async_followup_list(Transfer_t *first, Transfer_t *last);
|
|
static void remove_from_async_followup_list(Transfer_t *transfer);
|
|
static void add_to_periodic_followup_list(Transfer_t *first, Transfer_t *last);
|
|
static void remove_from_periodic_followup_list(Transfer_t *transfer);
|
|
|
|
#define print USBHost::print_
|
|
#define println USBHost::println_
|
|
|
|
void USBHost::begin()
|
|
{
|
|
// Teensy 3.6 has USB host power controlled by PTE6
|
|
PORTE_PCR6 = PORT_PCR_MUX(1);
|
|
GPIOE_PDDR |= (1<<6);
|
|
GPIOE_PSOR = (1<<6); // turn on USB host power
|
|
delay(10);
|
|
println("sizeof Device = ", sizeof(Device_t));
|
|
println("sizeof Pipe = ", sizeof(Pipe_t));
|
|
println("sizeof Transfer = ", sizeof(Transfer_t));
|
|
if ((sizeof(Pipe_t) & 0x1F) || (sizeof(Transfer_t) & 0x1F)) {
|
|
println("ERROR: Pipe_t & Transfer_t must be multiples of 32 bytes!");
|
|
while (1) ; // die here
|
|
}
|
|
|
|
// configure the MPU to allow USBHS DMA to access memory
|
|
MPU_RGDAAC0 |= 0x30000000;
|
|
//println("MPU_RGDAAC0 = ", MPU_RGDAAC0, HEX);
|
|
|
|
// turn on clocks
|
|
MCG_C1 |= MCG_C1_IRCLKEN; // enable MCGIRCLK 32kHz
|
|
OSC0_CR |= OSC_ERCLKEN;
|
|
SIM_SOPT2 |= SIM_SOPT2_USBREGEN; // turn on USB regulator
|
|
SIM_SOPT2 &= ~SIM_SOPT2_USBSLSRC; // use IRC for slow clock
|
|
println("power up USBHS PHY");
|
|
SIM_USBPHYCTL |= SIM_USBPHYCTL_USBDISILIM; // disable USB current limit
|
|
//SIM_USBPHYCTL = SIM_USBPHYCTL_USBDISILIM | SIM_USBPHYCTL_USB3VOUTTRG(6); // pg 237
|
|
SIM_SCGC3 |= SIM_SCGC3_USBHSDCD | SIM_SCGC3_USBHSPHY | SIM_SCGC3_USBHS;
|
|
USBHSDCD_CLOCK = 33 << 2;
|
|
//print("init USBHS PHY & PLL");
|
|
// init process: page 1681-1682
|
|
USBPHY_CTRL_CLR = (USBPHY_CTRL_SFTRST | USBPHY_CTRL_CLKGATE); // // CTRL pg 1698
|
|
USBPHY_CTRL_SET = USBPHY_CTRL_ENUTMILEVEL2 | USBPHY_CTRL_ENUTMILEVEL3;
|
|
//USBPHY_CTRL_SET = USBPHY_CTRL_FSDLL_RST_EN; // TODO: what does this do??
|
|
USBPHY_TRIM_OVERRIDE_EN_SET = 1;
|
|
USBPHY_PLL_SIC = USBPHY_PLL_SIC_PLL_POWER | USBPHY_PLL_SIC_PLL_ENABLE |
|
|
USBPHY_PLL_SIC_PLL_DIV_SEL(1) | USBPHY_PLL_SIC_PLL_EN_USB_CLKS;
|
|
// wait for the PLL to lock
|
|
int count=0;
|
|
while ((USBPHY_PLL_SIC & USBPHY_PLL_SIC_PLL_LOCK) == 0) {
|
|
count++;
|
|
}
|
|
//println("PLL locked, waited ", count);
|
|
|
|
// turn on power to PHY
|
|
USBPHY_PWD = 0;
|
|
delay(10);
|
|
|
|
// sanity check, connect 470K pullup & 100K pulldown and watch D+ voltage change
|
|
//USBPHY_ANACTRL_CLR = (1<<10); // turn off both 15K pulldowns... works! :)
|
|
|
|
// sanity check, output clocks on pin 9 for testing
|
|
//SIM_SOPT2 = SIM_SOPT2 & (~SIM_SOPT2_CLKOUTSEL(7)) | SIM_SOPT2_CLKOUTSEL(3); // LPO 1kHz
|
|
//SIM_SOPT2 = SIM_SOPT2 & (~SIM_SOPT2_CLKOUTSEL(7)) | SIM_SOPT2_CLKOUTSEL(2); // Flash
|
|
//SIM_SOPT2 = SIM_SOPT2 & (~SIM_SOPT2_CLKOUTSEL(7)) | SIM_SOPT2_CLKOUTSEL(6); // XTAL
|
|
//SIM_SOPT2 = SIM_SOPT2 & (~SIM_SOPT2_CLKOUTSEL(7)) | SIM_SOPT2_CLKOUTSEL(7); // IRC 48MHz
|
|
//SIM_SOPT2 = SIM_SOPT2 & (~SIM_SOPT2_CLKOUTSEL(7)) | SIM_SOPT2_CLKOUTSEL(4); // MCGIRCLK
|
|
//CORE_PIN9_CONFIG = PORT_PCR_MUX(5); // CLKOUT on PTC3 Alt5 (Arduino pin 9)
|
|
|
|
// now with the PHY up and running, start up USBHS
|
|
//print("begin ehci reset");
|
|
USBHS_USBCMD |= USBHS_USBCMD_RST;
|
|
//count = 0;
|
|
while (USBHS_USBCMD & USBHS_USBCMD_RST) {
|
|
//count++;
|
|
}
|
|
//println(" reset waited ", count);
|
|
|
|
init_Device_Pipe_Transfer_memory();
|
|
for (int i=0; i < PERIODIC_LIST_SIZE; i++) {
|
|
periodictable[i] = 1;
|
|
}
|
|
memset(uframe_bandwidth, 0, sizeof(uframe_bandwidth));
|
|
port_state = PORT_STATE_DISCONNECTED;
|
|
|
|
USBHS_USB_SBUSCFG = 1; // System Bus Interface Configuration
|
|
|
|
// turn on the USBHS controller
|
|
//USBHS_USBMODE = USBHS_USBMODE_TXHSD(5) | USBHS_USBMODE_CM(3); // host mode
|
|
USBHS_USBMODE = USBHS_USBMODE_CM(3); // host mode
|
|
USBHS_USBINTR = 0;
|
|
USBHS_PERIODICLISTBASE = (uint32_t)periodictable;
|
|
USBHS_FRINDEX = 0;
|
|
USBHS_ASYNCLISTADDR = 0;
|
|
USBHS_USBCMD = USBHS_USBCMD_ITC(8) | USBHS_USBCMD_RS |
|
|
USBHS_USBCMD_ASP(3) | USBHS_USBCMD_ASPE | USBHS_USBCMD_PSE |
|
|
#if PERIODIC_LIST_SIZE == 8
|
|
USBHS_USBCMD_FS2 | USBHS_USBCMD_FS(3);
|
|
#elif PERIODIC_LIST_SIZE == 16
|
|
USBHS_USBCMD_FS2 | USBHS_USBCMD_FS(2);
|
|
#elif PERIODIC_LIST_SIZE == 32
|
|
USBHS_USBCMD_FS2 | USBHS_USBCMD_FS(1);
|
|
#elif PERIODIC_LIST_SIZE == 64
|
|
USBHS_USBCMD_FS2 | USBHS_USBCMD_FS(0);
|
|
#elif PERIODIC_LIST_SIZE == 128
|
|
USBHS_USBCMD_FS(3);
|
|
#elif PERIODIC_LIST_SIZE == 256
|
|
USBHS_USBCMD_FS(2);
|
|
#elif PERIODIC_LIST_SIZE == 512
|
|
USBHS_USBCMD_FS(1);
|
|
#elif PERIODIC_LIST_SIZE == 1024
|
|
USBHS_USBCMD_FS(0);
|
|
#else
|
|
#error "Unsupported PERIODIC_LIST_SIZE"
|
|
#endif
|
|
|
|
// turn on the USB port
|
|
//USBHS_PORTSC1 = USBHS_PORTSC_PP;
|
|
USBHS_PORTSC1 |= USBHS_PORTSC_PP;
|
|
//USBHS_PORTSC1 |= USBHS_PORTSC_PFSC; // force 12 Mbit/sec
|
|
//USBHS_PORTSC1 |= USBHS_PORTSC_PHCD; // phy off
|
|
|
|
//println("USBHS_ASYNCLISTADDR = ", USBHS_ASYNCLISTADDR, HEX);
|
|
//println("USBHS_PERIODICLISTBASE = ", USBHS_PERIODICLISTBASE, HEX);
|
|
//println("periodictable = ", (uint32_t)periodictable, HEX);
|
|
|
|
// enable interrupts, after this point interruts to all the work
|
|
attachInterruptVector(IRQ_USBHS, isr);
|
|
NVIC_ENABLE_IRQ(IRQ_USBHS);
|
|
USBHS_USBINTR = USBHS_USBINTR_PCE | USBHS_USBINTR_TIE0 | USBHS_USBINTR_TIE1;
|
|
USBHS_USBINTR |= USBHS_USBINTR_UEE | USBHS_USBINTR_SEE;
|
|
USBHS_USBINTR |= USBHS_USBINTR_UPIE | USBHS_USBINTR_UAIE;
|
|
|
|
}
|
|
|
|
|
|
// EHCI registers page default
|
|
// -------------- ---- -------
|
|
// USBHS_USBCMD 1599 00080000 USB Command
|
|
// USBHS_USBSTS 1602 00000000 USB Status
|
|
// USBHS_USBINTR 1606 00000000 USB Interrupt Enable
|
|
// USBHS_FRINDEX 1609 00000000 Frame Index Register
|
|
// USBHS_PERIODICLISTBASE 1610 undefine Periodic Frame List Base Address
|
|
// USBHS_ASYNCLISTADDR 1612 undefine Asynchronous List Address
|
|
// USBHS_PORTSC1 1619 00002000 Port Status and Control
|
|
// USBHS_USBMODE 1629 00005000 USB Mode
|
|
// USBHS_GPTIMERnCTL 1591 00000000 General Purpose Timer n Control
|
|
|
|
// PORT_STATE_DISCONNECTED 0
|
|
// PORT_STATE_DEBOUNCE 1
|
|
// PORT_STATE_RESET 2
|
|
// PORT_STATE_RECOVERY 3
|
|
// PORT_STATE_ACTIVE 4
|
|
|
|
|
|
void USBHost::isr()
|
|
{
|
|
uint32_t stat = USBHS_USBSTS;
|
|
USBHS_USBSTS = stat; // clear pending interrupts
|
|
//stat &= USBHS_USBINTR; // mask away unwanted interrupts
|
|
#if 0
|
|
println();
|
|
println("ISR: ", stat, HEX);
|
|
//if (stat & USBHS_USBSTS_UI) println(" USB Interrupt");
|
|
if (stat & USBHS_USBSTS_UEI) println(" USB Error");
|
|
if (stat & USBHS_USBSTS_PCI) println(" Port Change");
|
|
//if (stat & USBHS_USBSTS_FRI) println(" Frame List Rollover");
|
|
if (stat & USBHS_USBSTS_SEI) println(" System Error");
|
|
//if (stat & USBHS_USBSTS_AAI) println(" Async Advance (doorbell)");
|
|
if (stat & USBHS_USBSTS_URI) println(" Reset Recv");
|
|
//if (stat & USBHS_USBSTS_SRI) println(" SOF");
|
|
if (stat & USBHS_USBSTS_SLI) println(" Suspend");
|
|
if (stat & USBHS_USBSTS_HCH) println(" Host Halted");
|
|
//if (stat & USBHS_USBSTS_RCL) println(" Reclamation");
|
|
//if (stat & USBHS_USBSTS_PS) println(" Periodic Sched En");
|
|
//if (stat & USBHS_USBSTS_AS) println(" Async Sched En");
|
|
if (stat & USBHS_USBSTS_NAKI) println(" NAK");
|
|
if (stat & USBHS_USBSTS_UAI) println(" USB Async");
|
|
if (stat & USBHS_USBSTS_UPI) println(" USB Periodic");
|
|
if (stat & USBHS_USBSTS_TI0) println(" Timer0");
|
|
if (stat & USBHS_USBSTS_TI1) println(" Timer1");
|
|
#endif
|
|
|
|
if (stat & USBHS_USBSTS_UAI) { // completed qTD(s) from the async schedule
|
|
//println("Async Followup");
|
|
//print(async_followup_first, async_followup_last);
|
|
Transfer_t *p = async_followup_first;
|
|
while (p) {
|
|
if (followup_Transfer(p)) {
|
|
// transfer completed
|
|
Transfer_t *next = p->next_followup;
|
|
remove_from_async_followup_list(p);
|
|
free_Transfer(p);
|
|
p = next;
|
|
} else {
|
|
// transfer still pending
|
|
p = p->next_followup;
|
|
}
|
|
}
|
|
//print(async_followup_first, async_followup_last);
|
|
}
|
|
if (stat & USBHS_USBSTS_UPI) { // completed qTD(s) from the periodic schedule
|
|
//println("Periodic Followup");
|
|
Transfer_t *p = periodic_followup_first;
|
|
while (p) {
|
|
if (followup_Transfer(p)) {
|
|
// transfer completed
|
|
Transfer_t *next = p->next_followup;
|
|
remove_from_periodic_followup_list(p);
|
|
free_Transfer(p);
|
|
p = next;
|
|
} else {
|
|
// transfer still pending
|
|
p = p->next_followup;
|
|
}
|
|
}
|
|
}
|
|
if (stat & USBHS_USBSTS_UEI) {
|
|
followup_Error();
|
|
}
|
|
|
|
if (stat & USBHS_USBSTS_PCI) { // port change detected
|
|
const uint32_t portstat = USBHS_PORTSC1;
|
|
println("port change: ", portstat, HEX);
|
|
USBHS_PORTSC1 = portstat | (USBHS_PORTSC_OCC|USBHS_PORTSC_PEC|USBHS_PORTSC_CSC);
|
|
if (portstat & USBHS_PORTSC_OCC) {
|
|
println(" overcurrent change");
|
|
}
|
|
if (portstat & USBHS_PORTSC_CSC) {
|
|
if (portstat & USBHS_PORTSC_CCS) {
|
|
println(" connect");
|
|
if (port_state == PORT_STATE_DISCONNECTED
|
|
|| port_state == PORT_STATE_DEBOUNCE) {
|
|
// 100 ms debounce (USB 2.0: TATTDB, page 150 & 188)
|
|
port_state = PORT_STATE_DEBOUNCE;
|
|
USBHS_GPTIMER0LD = 100000; // microseconds
|
|
USBHS_GPTIMER0CTL =
|
|
USBHS_GPTIMERCTL_RST | USBHS_GPTIMERCTL_RUN;
|
|
stat &= ~USBHS_USBSTS_TI0;
|
|
}
|
|
} else {
|
|
println(" disconnect");
|
|
port_state = PORT_STATE_DISCONNECTED;
|
|
USBPHY_CTRL_CLR = USBPHY_CTRL_ENHOSTDISCONDETECT;
|
|
disconnect_Device(rootdev);
|
|
rootdev = NULL;
|
|
}
|
|
}
|
|
if (portstat & USBHS_PORTSC_PEC) {
|
|
// PEC bit only detects disable
|
|
println(" disable");
|
|
} else if (port_state == PORT_STATE_RESET && portstat & USBHS_PORTSC_PE) {
|
|
println(" port enabled");
|
|
port_state = PORT_STATE_RECOVERY;
|
|
// 10 ms reset recover (USB 2.0: TRSTRCY, page 151 & 188)
|
|
USBHS_GPTIMER0LD = 10000; // microseconds
|
|
USBHS_GPTIMER0CTL = USBHS_GPTIMERCTL_RST | USBHS_GPTIMERCTL_RUN;
|
|
if (USBHS_PORTSC1 & USBHS_PORTSC_HSP) {
|
|
// turn on high-speed disconnect detector
|
|
USBPHY_CTRL_SET = USBPHY_CTRL_ENHOSTDISCONDETECT;
|
|
}
|
|
}
|
|
if (portstat & USBHS_PORTSC_FPR) {
|
|
println(" force resume");
|
|
|
|
}
|
|
}
|
|
if (stat & USBHS_USBSTS_TI0) { // timer 0 - used for built-in port events
|
|
//println("timer0");
|
|
if (port_state == PORT_STATE_DEBOUNCE) {
|
|
port_state = PORT_STATE_RESET;
|
|
// Since we have only 1 port, no other device can
|
|
// be in reset or enumeration. If multiple ports
|
|
// are ever supported, we would need to remain in
|
|
// debounce if any other port was resetting or
|
|
// enumerating a device.
|
|
USBHS_PORTSC1 |= USBHS_PORTSC_PR; // begin reset sequence
|
|
println(" begin reset");
|
|
} else if (port_state == PORT_STATE_RECOVERY) {
|
|
port_state = PORT_STATE_ACTIVE;
|
|
println(" end recovery");
|
|
// HCSPARAMS TTCTRL page 1671
|
|
uint32_t speed = (USBHS_PORTSC1 >> 26) & 3;
|
|
rootdev = new_Device(speed, 0, 0);
|
|
}
|
|
}
|
|
if (stat & USBHS_USBSTS_TI1) { // timer 1 - used for USBDriverTimer
|
|
//println("timer1");
|
|
USBDriverTimer *timer = active_timers;
|
|
if (timer) {
|
|
USBDriverTimer *next = timer->next;
|
|
active_timers = next;
|
|
if (next) {
|
|
// more timers scheduled
|
|
next->prev = NULL;
|
|
USBHS_GPTIMER1LD = next->usec - 1;
|
|
USBHS_GPTIMER1CTL = USBHS_GPTIMERCTL_RST | USBHS_GPTIMERCTL_RUN;
|
|
}
|
|
// TODO: call multiple timers if 0 elapsed between them?
|
|
timer->driver->timer_event(timer); // call driver's timer()
|
|
}
|
|
}
|
|
}
|
|
|
|
void USBDriverTimer::start(uint32_t microseconds)
|
|
{
|
|
#if 0
|
|
USBHost::print_("start_timer, us = ");
|
|
USBHost::print_(microseconds);
|
|
USBHost::print_(", driver = ");
|
|
USBHost::print_((uint32_t)driver, HEX);
|
|
USBHost::print_(", this = ");
|
|
USBHost::println_((uint32_t)this, HEX);
|
|
#endif
|
|
if (!driver) return;
|
|
if (microseconds < 100) return; // minimum timer duration
|
|
started_micros = micros();
|
|
if (active_timers == NULL) {
|
|
// schedule is empty, just add this timer
|
|
usec = microseconds;
|
|
next = NULL;
|
|
prev = NULL;
|
|
active_timers = this;
|
|
USBHS_GPTIMER1LD = microseconds - 1;
|
|
USBHS_GPTIMER1CTL = USBHS_GPTIMERCTL_RST | USBHS_GPTIMERCTL_RUN;
|
|
return;
|
|
}
|
|
uint32_t remain = USBHS_GPTIMER1CTL & 0xFFFFFF;
|
|
//Serial.print("remain = ");
|
|
//Serial.println(remain);
|
|
if (microseconds < remain) {
|
|
// this timer event is before any on the schedule
|
|
__disable_irq();
|
|
USBHS_GPTIMER1CTL = 0;
|
|
USBHS_USBSTS = USBHS_USBSTS_TI1; // TODO: UPI & UAI safety?!
|
|
usec = microseconds;
|
|
next = active_timers;
|
|
prev = NULL;
|
|
active_timers->usec = remain - microseconds;
|
|
active_timers->prev = this;
|
|
active_timers = this;
|
|
USBHS_GPTIMER1LD = microseconds - 1;
|
|
USBHS_GPTIMER1CTL = USBHS_GPTIMERCTL_RST | USBHS_GPTIMERCTL_RUN;
|
|
__enable_irq();
|
|
return;
|
|
}
|
|
// add this timer to the schedule, somewhere after the first timer
|
|
microseconds -= remain;
|
|
USBDriverTimer *list = active_timers;
|
|
while (list->next) {
|
|
list = list->next;
|
|
if (microseconds < list->usec) {
|
|
// add timer into middle of list
|
|
list->usec -= microseconds;
|
|
usec = microseconds;
|
|
next = list;
|
|
prev = list->prev;
|
|
list->prev = this;
|
|
prev->next = this;
|
|
return;
|
|
}
|
|
microseconds -= list->usec;
|
|
}
|
|
// add timer to the end of the schedule
|
|
usec = microseconds;
|
|
next = NULL;
|
|
prev = list;
|
|
list->next = this;
|
|
}
|
|
|
|
void USBDriverTimer::stop()
|
|
{
|
|
__disable_irq();
|
|
if (active_timers) {
|
|
if (active_timers == this) {
|
|
USBHS_GPTIMER1CTL = 0;
|
|
if (next) {
|
|
uint32_t usec_til_next = USBHS_GPTIMER1CTL & 0xFFFFFF;
|
|
usec_til_next += next->usec;
|
|
next->usec = usec_til_next;
|
|
USBHS_GPTIMER1LD = usec_til_next;
|
|
USBHS_GPTIMER1CTL = USBHS_GPTIMERCTL_RST | USBHS_GPTIMERCTL_RUN;
|
|
next->prev = NULL;
|
|
active_timers = next;
|
|
} else {
|
|
active_timers = NULL;
|
|
}
|
|
} else {
|
|
for (USBDriverTimer *t = active_timers->next; t; t = t->next) {
|
|
if (t == this) {
|
|
t->prev->next = t->next;
|
|
if (t->next) {
|
|
t->next->usec += t->usec;
|
|
t->next->prev = t->prev;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
__enable_irq();
|
|
}
|
|
|
|
|
|
static uint32_t QH_capabilities1(uint32_t nak_count_reload, uint32_t control_endpoint_flag,
|
|
uint32_t max_packet_length, uint32_t head_of_list, uint32_t data_toggle_control,
|
|
uint32_t speed, uint32_t endpoint_number, uint32_t inactivate, uint32_t address)
|
|
{
|
|
return ( (nak_count_reload << 28) | (control_endpoint_flag << 27) |
|
|
(max_packet_length << 16) | (head_of_list << 15) |
|
|
(data_toggle_control << 14) | (speed << 12) | (endpoint_number << 8) |
|
|
(inactivate << 7) | (address << 0) );
|
|
}
|
|
|
|
static uint32_t QH_capabilities2(uint32_t high_bw_mult, uint32_t hub_port_number,
|
|
uint32_t hub_address, uint32_t split_completion_mask, uint32_t interrupt_schedule_mask)
|
|
{
|
|
return ( (high_bw_mult << 30) | (hub_port_number << 23) | (hub_address << 16) |
|
|
(split_completion_mask << 8) | (interrupt_schedule_mask << 0) );
|
|
}
|
|
|
|
|
|
|
|
// Create a new pipe. It's QH is added to the async or periodic schedule,
|
|
// and a halt qTD is added to the QH, so we can grow the qTD list later.
|
|
// dev: device owning this pipe/endpoint
|
|
// type: 0=control, 2=bulk, 3=interrupt
|
|
// endpoint: 0 for control, 1-15 for bulk or interrupt
|
|
// direction: 0=OUT, 1=IN (unused for control)
|
|
// maxlen: maximum packet size
|
|
// interval: polling interval for interrupt, power of 2, unused if control or bulk
|
|
//
|
|
Pipe_t * USBHost::new_Pipe(Device_t *dev, uint32_t type, uint32_t endpoint,
|
|
uint32_t direction, uint32_t maxlen, uint32_t interval)
|
|
{
|
|
Pipe_t *pipe;
|
|
Transfer_t *halt;
|
|
uint32_t c=0, dtc=0;
|
|
|
|
println("new_Pipe");
|
|
pipe = allocate_Pipe();
|
|
if (!pipe) return NULL;
|
|
halt = allocate_Transfer();
|
|
if (!halt) {
|
|
free_Pipe(pipe);
|
|
return NULL;
|
|
}
|
|
memset(pipe, 0, sizeof(Pipe_t));
|
|
memset(halt, 0, sizeof(Transfer_t));
|
|
halt->qtd.next = 1;
|
|
halt->qtd.token = 0x40;
|
|
pipe->device = dev;
|
|
pipe->qh.next = (uint32_t)halt;
|
|
pipe->qh.alt_next = 1;
|
|
pipe->direction = direction;
|
|
pipe->type = type;
|
|
if (type == 3) {
|
|
// interrupt transfers require bandwidth & microframe scheduling
|
|
if (!allocate_interrupt_pipe_bandwidth(pipe, maxlen, interval)) {
|
|
free_Transfer(halt);
|
|
free_Pipe(pipe);
|
|
return NULL;
|
|
}
|
|
}
|
|
if (endpoint > 0) {
|
|
// if non-control pipe, update dev->data_pipes list
|
|
Pipe_t *p = dev->data_pipes;
|
|
if (p == NULL) {
|
|
dev->data_pipes = pipe;
|
|
} else {
|
|
while (p->next) p = p->next;
|
|
p->next = pipe;
|
|
}
|
|
}
|
|
if (type == 0) {
|
|
// control
|
|
if (dev->speed < 2) c = 1;
|
|
dtc = 1;
|
|
} else if (type == 2) {
|
|
// bulk
|
|
} else if (type == 3) {
|
|
// interrupt
|
|
//pipe->qh.token = 0x80000000; // TODO: OUT starts with DATA0 or DATA1?
|
|
}
|
|
pipe->qh.capabilities[0] = QH_capabilities1(15, c, maxlen, 0,
|
|
dtc, dev->speed, endpoint, 0, dev->address);
|
|
pipe->qh.capabilities[1] = QH_capabilities2(1, dev->hub_port,
|
|
dev->hub_address, pipe->complete_mask, pipe->start_mask);
|
|
|
|
if (type == 0 || type == 2) {
|
|
// control or bulk: add to async queue
|
|
Pipe_t *list = (Pipe_t *)USBHS_ASYNCLISTADDR;
|
|
if (list == NULL) {
|
|
pipe->qh.capabilities[0] |= 0x8000; // H bit
|
|
pipe->qh.horizontal_link = (uint32_t)&(pipe->qh) | 2; // 2=QH
|
|
USBHS_ASYNCLISTADDR = (uint32_t)&(pipe->qh);
|
|
USBHS_USBCMD |= USBHS_USBCMD_ASE; // enable async schedule
|
|
//println(" first in async list");
|
|
} else {
|
|
// EHCI 1.0: section 4.8.1, page 72
|
|
pipe->qh.horizontal_link = list->qh.horizontal_link;
|
|
list->qh.horizontal_link = (uint32_t)&(pipe->qh) | 2;
|
|
//println(" added to async list");
|
|
}
|
|
} else if (type == 3) {
|
|
// interrupt: add to periodic schedule
|
|
add_qh_to_periodic_schedule(pipe);
|
|
}
|
|
return pipe;
|
|
}
|
|
|
|
|
|
|
|
// Fill in the qTD fields (token & data)
|
|
// t the Transfer qTD to initialize
|
|
// buf data to transfer
|
|
// len length of data
|
|
// pid type of packet: 0=OUT, 1=IN, 2=SETUP
|
|
// data01 value of DATA0/DATA1 toggle on 1st packet
|
|
// irq whether to generate an interrupt when transfer complete
|
|
//
|
|
static void init_qTD(volatile Transfer_t *t, void *buf, uint32_t len,
|
|
uint32_t pid, uint32_t data01, bool irq)
|
|
{
|
|
t->qtd.alt_next = 1; // 1=terminate
|
|
if (data01) data01 = 0x80000000;
|
|
t->qtd.token = data01 | (len << 16) | (irq ? 0x8000 : 0) | (pid << 8) | 0x80;
|
|
uint32_t addr = (uint32_t)buf;
|
|
t->qtd.buffer[0] = addr;
|
|
addr &= 0xFFFFF000;
|
|
t->qtd.buffer[1] = addr + 0x1000;
|
|
t->qtd.buffer[2] = addr + 0x2000;
|
|
t->qtd.buffer[3] = addr + 0x3000;
|
|
t->qtd.buffer[4] = addr + 0x4000;
|
|
}
|
|
|
|
|
|
|
|
// Create a Control Transfer and queue it
|
|
//
|
|
bool USBHost::queue_Control_Transfer(Device_t *dev, setup_t *setup, void *buf, USBDriver *driver)
|
|
{
|
|
Transfer_t *transfer, *data, *status;
|
|
uint32_t status_direction;
|
|
|
|
//println("new_Control_Transfer");
|
|
if (setup->wLength > 16384) return false; // max 16K data for control
|
|
transfer = allocate_Transfer();
|
|
if (!transfer) {
|
|
println(" error allocating setup transfer");
|
|
return false;
|
|
}
|
|
status = allocate_Transfer();
|
|
if (!status) {
|
|
println(" error allocating status transfer");
|
|
free_Transfer(transfer);
|
|
return false;
|
|
}
|
|
if (setup->wLength > 0) {
|
|
data = allocate_Transfer();
|
|
if (!data) {
|
|
println(" error allocating data transfer");
|
|
free_Transfer(transfer);
|
|
free_Transfer(status);
|
|
return false;
|
|
}
|
|
uint32_t pid = (setup->bmRequestType & 0x80) ? 1 : 0;
|
|
init_qTD(data, buf, setup->wLength, pid, 1, false);
|
|
transfer->qtd.next = (uint32_t)data;
|
|
data->qtd.next = (uint32_t)status;
|
|
status_direction = pid ^ 1;
|
|
} else {
|
|
transfer->qtd.next = (uint32_t)status;
|
|
status_direction = 1; // always IN, USB 2.0 page 226
|
|
}
|
|
//println("setup address ", (uint32_t)setup, HEX);
|
|
init_qTD(transfer, setup, 8, 2, 0, false);
|
|
init_qTD(status, NULL, 0, status_direction, 1, true);
|
|
status->pipe = dev->control_pipe;
|
|
status->buffer = buf;
|
|
status->length = setup->wLength;
|
|
status->setup.word1 = setup->word1;
|
|
status->setup.word2 = setup->word2;
|
|
status->driver = driver;
|
|
status->qtd.next = 1;
|
|
return queue_Transfer(dev->control_pipe, transfer);
|
|
}
|
|
|
|
|
|
// Create a Bulk or Interrupt Transfer and queue it
|
|
//
|
|
bool USBHost::queue_Data_Transfer(Pipe_t *pipe, void *buffer, uint32_t len, USBDriver *driver)
|
|
{
|
|
Transfer_t *transfer, *data, *next;
|
|
uint8_t *p = (uint8_t *)buffer;
|
|
uint32_t count;
|
|
bool last = false;
|
|
|
|
// TODO: option for zero length packet? Maybe in Pipe_t fields?
|
|
|
|
//println("new_Data_Transfer");
|
|
// allocate qTDs
|
|
transfer = allocate_Transfer();
|
|
if (!transfer) return false;
|
|
data = transfer;
|
|
for (count=(len >> 14); count; count--) {
|
|
next = allocate_Transfer();
|
|
if (!next) {
|
|
// free already-allocated qTDs
|
|
while (1) {
|
|
next = (Transfer_t *)transfer->qtd.next;
|
|
free_Transfer(transfer);
|
|
if (transfer == data) break;
|
|
transfer = next;
|
|
}
|
|
return false;
|
|
}
|
|
data->qtd.next = (uint32_t)next;
|
|
data = next;
|
|
}
|
|
// last qTD needs info for followup
|
|
data->qtd.next = 1;
|
|
data->pipe = pipe;
|
|
data->buffer = buffer;
|
|
data->length = len;
|
|
data->setup.word1 = 0;
|
|
data->setup.word2 = 0;
|
|
data->driver = driver;
|
|
// initialize all qTDs
|
|
data = transfer;
|
|
while (1) {
|
|
uint32_t count = len;
|
|
if (count > 16384) {
|
|
count = 16384;
|
|
} else {
|
|
last = true;
|
|
}
|
|
init_qTD(data, p, count, pipe->direction, 0, last);
|
|
if (last) break;
|
|
p += count;
|
|
len -= count;
|
|
data = (Transfer_t *)(data->qtd.next);
|
|
}
|
|
return queue_Transfer(pipe, transfer);
|
|
}
|
|
|
|
|
|
bool USBHost::queue_Transfer(Pipe_t *pipe, Transfer_t *transfer)
|
|
{
|
|
// find halt qTD
|
|
Transfer_t *halt = (Transfer_t *)(pipe->qh.next);
|
|
while (!(halt->qtd.token & 0x40)) halt = (Transfer_t *)(halt->qtd.next);
|
|
// transfer's token
|
|
uint32_t token = transfer->qtd.token;
|
|
// transfer becomes new halt qTD
|
|
transfer->qtd.token = 0x40;
|
|
// copy transfer non-token fields to halt
|
|
halt->qtd.next = transfer->qtd.next;
|
|
halt->qtd.alt_next = transfer->qtd.alt_next;
|
|
halt->qtd.buffer[0] = transfer->qtd.buffer[0]; // TODO: optimize memcpy, all
|
|
halt->qtd.buffer[1] = transfer->qtd.buffer[1]; // fields except token
|
|
halt->qtd.buffer[2] = transfer->qtd.buffer[2];
|
|
halt->qtd.buffer[3] = transfer->qtd.buffer[3];
|
|
halt->qtd.buffer[4] = transfer->qtd.buffer[4];
|
|
halt->pipe = pipe;
|
|
halt->buffer = transfer->buffer;
|
|
halt->length = transfer->length;
|
|
halt->setup = transfer->setup;
|
|
halt->driver = transfer->driver;
|
|
// find the last qTD we're adding
|
|
Transfer_t *last = halt;
|
|
while ((uint32_t)(last->qtd.next) != 1) last = (Transfer_t *)(last->qtd.next);
|
|
// last points to transfer (which becomes new halt)
|
|
last->qtd.next = (uint32_t)transfer;
|
|
transfer->qtd.next = 1;
|
|
// link all the new qTD by next_followup & prev_followup
|
|
Transfer_t *prev = NULL;
|
|
Transfer_t *p = halt;
|
|
while (p->qtd.next != (uint32_t)transfer) {
|
|
Transfer_t *next = (Transfer_t *)p->qtd.next;
|
|
p->prev_followup = prev;
|
|
p->next_followup = next;
|
|
prev = p;
|
|
p = next;
|
|
}
|
|
p->prev_followup = prev;
|
|
p->next_followup = NULL;
|
|
//print(halt, p);
|
|
// add them to a followup list
|
|
if (pipe->type == 0 || pipe->type == 2) {
|
|
// control or bulk
|
|
add_to_async_followup_list(halt, p);
|
|
} else {
|
|
// interrupt
|
|
add_to_periodic_followup_list(halt, p);
|
|
}
|
|
// old halt becomes new transfer, this commits all new qTDs to QH
|
|
halt->qtd.token = token;
|
|
return true;
|
|
}
|
|
|
|
bool USBHost::followup_Transfer(Transfer_t *transfer)
|
|
{
|
|
//print(" Followup ", (uint32_t)transfer, HEX);
|
|
//println(" token=", transfer->qtd.token, HEX);
|
|
|
|
if (!(transfer->qtd.token & 0x80)) {
|
|
// TODO: check error status
|
|
if (transfer->qtd.token & 0x8000) {
|
|
// this transfer caused an interrupt
|
|
if (transfer->pipe->callback_function) {
|
|
// do the callback
|
|
(*(transfer->pipe->callback_function))(transfer);
|
|
}
|
|
}
|
|
// do callback function...
|
|
//println(" completed");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void USBHost::followup_Error(void)
|
|
{
|
|
println("ERROR Followup");
|
|
Transfer_t *p = async_followup_first;
|
|
while (p) {
|
|
if (followup_Transfer(p)) {
|
|
// transfer completed
|
|
Transfer_t *next = p->next_followup;
|
|
remove_from_async_followup_list(p);
|
|
println(" remove from followup list");
|
|
if (p->qtd.token & 0x40) {
|
|
Pipe_t *haltedpipe = p->pipe;
|
|
free_Transfer(p);
|
|
// traverse the rest of the list for unfinished work
|
|
// from this halted pipe. Remove from the followup
|
|
// list and put onto our own temporary list
|
|
Transfer_t *first = NULL;
|
|
Transfer_t *last = NULL;
|
|
p = next;
|
|
while (p) {
|
|
Transfer_t *next2 = p->next_followup;
|
|
if (p->pipe == haltedpipe) {
|
|
println(" stray halted ", (uint32_t)p, HEX);
|
|
remove_from_async_followup_list(p);
|
|
if (first == NULL) {
|
|
first = p;
|
|
last = p;
|
|
} else {
|
|
last->next_followup = p;
|
|
}
|
|
p->next_followup = NULL;
|
|
if (next == p) next = next2;
|
|
}
|
|
p = next2;
|
|
}
|
|
// halted pipe (probably) still has unfinished transfers
|
|
// find the halted pipe's dummy halt transfer
|
|
p = (Transfer_t *)(haltedpipe->qh.next & ~0x1F);
|
|
while (p && ((p->qtd.token & 0x40) == 0)) {
|
|
print(" qtd: ", (uint32_t)p, HEX);
|
|
print(", token=", (uint32_t)p->qtd.token, HEX);
|
|
println(", next=", (uint32_t)p->qtd.next, HEX);
|
|
p = (Transfer_t *)(p->qtd.next & ~0x1F);
|
|
}
|
|
if (p) {
|
|
// unhalt the pipe, "forget" unfinished transfers
|
|
// hopefully they're all on the list we made!
|
|
println(" dummy halt: ", (uint32_t)p, HEX);
|
|
haltedpipe->qh.next = (uint32_t)p;
|
|
haltedpipe->qh.current = 0;
|
|
haltedpipe->qh.token = 0;
|
|
} else {
|
|
println(" no dummy halt found, yikes!");
|
|
// TODO: this should never happen, but what if it does?
|
|
}
|
|
|
|
// Do any driver callbacks belonging to the unfinished
|
|
// transfers. This is done last, after retoring the
|
|
// pipe to a working state (if possible) so the driver
|
|
// callback can use the pipe.
|
|
p = first;
|
|
while (p) {
|
|
uint32_t token = p->qtd.token;
|
|
if (token & 0x8000 && haltedpipe->callback_function) {
|
|
// driver expects a callback
|
|
p->qtd.token = token | 0x40;
|
|
(*(p->pipe->callback_function))(p);
|
|
}
|
|
Transfer_t *next2 = p->next_followup;
|
|
free_Transfer(p);
|
|
p = next2;
|
|
}
|
|
} else {
|
|
free_Transfer(p);
|
|
}
|
|
p = next;
|
|
} else {
|
|
// transfer still pending
|
|
println(" remain on followup list");
|
|
p = p->next_followup;
|
|
}
|
|
}
|
|
// TODO: handle errors from periodic schedule!
|
|
}
|
|
|
|
static void add_to_async_followup_list(Transfer_t *first, Transfer_t *last)
|
|
{
|
|
last->next_followup = NULL; // always add to end of list
|
|
if (async_followup_last == NULL) {
|
|
first->prev_followup = NULL;
|
|
async_followup_first = first;
|
|
} else {
|
|
first->prev_followup = async_followup_last;
|
|
async_followup_last->next_followup = first;
|
|
}
|
|
async_followup_last = last;
|
|
}
|
|
|
|
static void remove_from_async_followup_list(Transfer_t *transfer)
|
|
{
|
|
Transfer_t *next = transfer->next_followup;
|
|
Transfer_t *prev = transfer->prev_followup;
|
|
if (prev) {
|
|
prev->next_followup = next;
|
|
} else {
|
|
async_followup_first = next;
|
|
}
|
|
if (next) {
|
|
next->prev_followup = prev;
|
|
} else {
|
|
async_followup_last = prev;
|
|
}
|
|
}
|
|
|
|
static void add_to_periodic_followup_list(Transfer_t *first, Transfer_t *last)
|
|
{
|
|
last->next_followup = NULL; // always add to end of list
|
|
if (periodic_followup_last == NULL) {
|
|
first->prev_followup = NULL;
|
|
periodic_followup_first = first;
|
|
} else {
|
|
first->prev_followup = periodic_followup_last;
|
|
periodic_followup_last->next_followup = first;
|
|
}
|
|
periodic_followup_last = last;
|
|
}
|
|
|
|
static void remove_from_periodic_followup_list(Transfer_t *transfer)
|
|
{
|
|
Transfer_t *next = transfer->next_followup;
|
|
Transfer_t *prev = transfer->prev_followup;
|
|
if (prev) {
|
|
prev->next_followup = next;
|
|
} else {
|
|
periodic_followup_first = next;
|
|
}
|
|
if (next) {
|
|
next->prev_followup = prev;
|
|
} else {
|
|
periodic_followup_last = prev;
|
|
}
|
|
}
|
|
|
|
|
|
static uint32_t max4(uint32_t n1, uint32_t n2, uint32_t n3, uint32_t n4)
|
|
{
|
|
if (n1 > n2) {
|
|
// can't be n2
|
|
if (n1 > n3) {
|
|
// can't be n3
|
|
if (n1 > n4) return n1;
|
|
} else {
|
|
// can't be n1
|
|
if (n3 > n4) return n3;
|
|
}
|
|
} else {
|
|
// can't be n1
|
|
if (n2 > n3) {
|
|
// can't be n3
|
|
if (n2 > n4) return n2;
|
|
} else {
|
|
// can't be n2
|
|
if (n3 > n4) return n3;
|
|
}
|
|
}
|
|
return n4;
|
|
}
|
|
|
|
static uint32_t round_to_power_of_two(uint32_t n, uint32_t maxnum)
|
|
{
|
|
for (uint32_t pow2num=1; pow2num < maxnum; pow2num <<= 1) {
|
|
if (n <= (pow2num | (pow2num >> 1))) return pow2num;
|
|
}
|
|
return maxnum;
|
|
}
|
|
|
|
// Allocate bandwidth for an interrupt pipe. Given the packet size
|
|
// and other parameters, find the best place to schedule this pipe.
|
|
// Returns true if enough bandwidth is available, and the best
|
|
// frame offset, smask and cmask. Or returns false if no group
|
|
// of microframes has enough bandwidth available.
|
|
//
|
|
// pipe:
|
|
// device->speed [in] 0=full speed, 1=low speed, 2=high speed
|
|
// direction [in] 0=OUT, 1=IN
|
|
// start_mask [out] uframes to start transfer
|
|
// complete_mask [out] uframes to complete transfer (FS & LS only)
|
|
// periodic_interval [out] fream repeat level: 1, 2, 4, 8... PERIODIC_LIST_SIZE
|
|
// periodic_offset [out] frame repeat offset: 0 to periodic_interval-1
|
|
// maxlen: [in] maximum packet length
|
|
// interval: [in] polling interval: LS+FS: frames, HS: 2^(n-1) uframes
|
|
//
|
|
bool USBHost::allocate_interrupt_pipe_bandwidth(Pipe_t *pipe, uint32_t maxlen, uint32_t interval)
|
|
{
|
|
println("allocate_interrupt_pipe_bandwidth");
|
|
if (interval == 0) interval = 1;
|
|
maxlen = (maxlen * 76459) >> 16; // worst case bit stuffing
|
|
if (pipe->device->speed == 2) {
|
|
// high speed 480 Mbit/sec
|
|
println(" ep interval = ", interval);
|
|
if (interval > 15) interval = 15;
|
|
interval = 1 << (interval - 1);
|
|
if (interval > PERIODIC_LIST_SIZE*8) interval = PERIODIC_LIST_SIZE*8;
|
|
println(" interval = ", interval);
|
|
uint32_t pinterval = interval >> 3;
|
|
pipe->periodic_interval = (pinterval > 0) ? pinterval : 1;
|
|
uint32_t stime = (55 + 32 + maxlen) >> 5; // time units: 32 bytes or 533 ns
|
|
uint32_t best_offset = 0xFFFFFFFF;
|
|
uint32_t best_bandwidth = 0xFFFFFFFF;
|
|
for (uint32_t offset=0; offset < interval; offset++) {
|
|
// for each possible uframe offset, find the worst uframe bandwidth
|
|
uint32_t max_bandwidth = 0;
|
|
for (uint32_t i=offset; i < PERIODIC_LIST_SIZE*8; i += interval) {
|
|
uint32_t bandwidth = uframe_bandwidth[i] + stime;
|
|
if (bandwidth > max_bandwidth) max_bandwidth = bandwidth;
|
|
}
|
|
// remember which uframe offset is the best
|
|
if (max_bandwidth < best_bandwidth) {
|
|
best_bandwidth = max_bandwidth;
|
|
best_offset = offset;
|
|
}
|
|
}
|
|
print(" best_bandwidth = ", best_bandwidth);
|
|
//print(best_bandwidth);
|
|
println(", at offset = ", best_offset);
|
|
//println(best_offset);
|
|
// a 125 us micro frame can fit 7500 bytes, or 234 of our 32-byte units
|
|
// fail if the best found needs more than 80% (234 * 0.8) in any uframe
|
|
if (best_bandwidth > 187) return false;
|
|
for (uint32_t i=best_offset; i < PERIODIC_LIST_SIZE*8; i += interval) {
|
|
uframe_bandwidth[i] += stime;
|
|
}
|
|
if (interval == 1) {
|
|
pipe->start_mask = 0xFF;
|
|
} else if (interval == 2) {
|
|
pipe->start_mask = 0x55 << (best_offset & 1);
|
|
} else if (interval <= 4) {
|
|
pipe->start_mask = 0x11 << (best_offset & 3);
|
|
} else {
|
|
pipe->start_mask = 0x01 << (best_offset & 7);
|
|
}
|
|
pipe->periodic_offset = best_offset >> 3;
|
|
pipe->complete_mask = 0;
|
|
} else {
|
|
// full speed 12 Mbit/sec or low speed 1.5 Mbit/sec
|
|
interval = round_to_power_of_two(interval, PERIODIC_LIST_SIZE);
|
|
pipe->periodic_interval = interval;
|
|
uint32_t stime, ctime;
|
|
if (pipe->direction == 0) {
|
|
// for OUT direction, SSPLIT will carry the data payload
|
|
// TODO: how much time to SSPLIT & CSPLIT actually take?
|
|
// they're not documented in 5.7 or 5.11.3.
|
|
stime = (100 + 32 + maxlen) >> 5;
|
|
ctime = (55 + 32) >> 5;
|
|
} else {
|
|
// for IN direction, data payload in CSPLIT
|
|
stime = (40 + 32) >> 5;
|
|
ctime = (70 + 32 + maxlen) >> 5;
|
|
}
|
|
// TODO: should we take Single-TT hubs into account, avoid
|
|
// scheduling overlapping SSPLIT & CSPLIT to the same hub?
|
|
// TODO: even if Multi-TT, do we need to worry about packing
|
|
// too many into the same uframe?
|
|
uint32_t best_shift = 0;
|
|
uint32_t best_offset = 0xFFFFFFFF;
|
|
uint32_t best_bandwidth = 0xFFFFFFFF;
|
|
for (uint32_t offset=0; offset < interval; offset++) {
|
|
// for each 1ms frame offset, compute the worst uframe usage
|
|
uint32_t max_bandwidth = 0;
|
|
for (uint32_t i=offset; i < PERIODIC_LIST_SIZE; i += interval) {
|
|
for (uint32_t j=0; j <= 3; j++) { // max 3 without FSTN
|
|
// at each location, find worst uframe usage
|
|
// for SSPLIT+CSPLITs
|
|
uint32_t n = (i << 3) + j;
|
|
uint32_t bw1 = uframe_bandwidth[n+0] + stime;
|
|
uint32_t bw2 = uframe_bandwidth[n+2] + ctime;
|
|
uint32_t bw3 = uframe_bandwidth[n+3] + ctime;
|
|
uint32_t bw4 = uframe_bandwidth[n+4] + ctime;
|
|
max_bandwidth = max4(bw1, bw2, bw3, bw4);
|
|
// remember the best usage found
|
|
if (max_bandwidth < best_bandwidth) {
|
|
best_bandwidth = max_bandwidth;
|
|
best_offset = i;
|
|
best_shift = j;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
print(" best_bandwidth = ", best_bandwidth);
|
|
//println(best_bandwidth);
|
|
print(", at offset = ", best_offset);
|
|
//print(best_offset);
|
|
println(", shift= ", best_shift);
|
|
//println(best_shift);
|
|
// a 125 us micro frame can fit 7500 bytes, or 234 of our 32-byte units
|
|
// fail if the best found needs more than 80% (234 * 0.8) in any uframe
|
|
if (best_bandwidth > 187) return false;
|
|
for (uint32_t i=best_offset; i < PERIODIC_LIST_SIZE; i += interval) {
|
|
uint32_t n = (i << 3) + best_shift;
|
|
uframe_bandwidth[n+0] += stime;
|
|
uframe_bandwidth[n+2] += ctime;
|
|
uframe_bandwidth[n+3] += ctime;
|
|
uframe_bandwidth[n+4] += ctime;
|
|
}
|
|
pipe->start_mask = 0x01 << best_shift;
|
|
pipe->complete_mask = 0x1C << best_shift;
|
|
pipe->periodic_offset = best_offset;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// put a new pipe into the periodic schedule tree
|
|
// according to periodic_interval and periodic_offset
|
|
//
|
|
void USBHost::add_qh_to_periodic_schedule(Pipe_t *pipe)
|
|
{
|
|
// quick hack for testing, just put it into the first table entry
|
|
//println("add_qh_to_periodic_schedule: ", (uint32_t)pipe, HEX);
|
|
#if 0
|
|
pipe->qh.horizontal_link = periodictable[0];
|
|
periodictable[0] = (uint32_t)&(pipe->qh) | 2; // 2=QH
|
|
println("init periodictable with ", periodictable[0], HEX);
|
|
#else
|
|
uint32_t interval = pipe->periodic_interval;
|
|
uint32_t offset = pipe->periodic_offset;
|
|
//println(" interval = ", interval);
|
|
//println(" offset = ", offset);
|
|
|
|
// By an interative miracle, hopefully make an inverted tree of EHCI figure 4-18, page 93
|
|
for (uint32_t i=offset; i < PERIODIC_LIST_SIZE; i += interval) {
|
|
//print(" old slot ", i);
|
|
//print(": ");
|
|
//print_qh_list((Pipe_t *)(periodictable[i] & 0xFFFFFFE0));
|
|
uint32_t num = periodictable[i];
|
|
Pipe_t *node = (Pipe_t *)(num & 0xFFFFFFE0);
|
|
if ((num & 1) || ((num & 6) == 2 && node->periodic_interval < interval)) {
|
|
//println(" add to slot ", i);
|
|
pipe->qh.horizontal_link = num;
|
|
periodictable[i] = (uint32_t)&(pipe->qh) | 2; // 2=QH
|
|
} else {
|
|
//println(" traverse list ", i);
|
|
// TODO: skip past iTD, siTD when/if we support isochronous
|
|
while (node->periodic_interval >= interval) {
|
|
if (node == pipe) goto nextslot;
|
|
//print(" num ", num, HEX);
|
|
//print(" node ", (uint32_t)node, HEX);
|
|
//println("->", node->qh.horizontal_link, HEX);
|
|
if (node->qh.horizontal_link & 1) break;
|
|
num = node->qh.horizontal_link;
|
|
node = (Pipe_t *)(num & 0xFFFFFFE0);
|
|
}
|
|
Pipe_t *n = node;
|
|
do {
|
|
if (n == pipe) goto nextslot;
|
|
n = (Pipe_t *)(n->qh.horizontal_link & 0xFFFFFFE0);
|
|
} while (n != NULL);
|
|
//print(" adding at node ", (uint32_t)node, HEX);
|
|
//print(", num=", num, HEX);
|
|
//println(", node->qh.horizontal_link=", node->qh.horizontal_link, HEX);
|
|
pipe->qh.horizontal_link = node->qh.horizontal_link;
|
|
node->qh.horizontal_link = (uint32_t)pipe | 2; // 2=QH
|
|
// TODO: is it really necessary to keep doing the outer
|
|
// loop? Does adding it here satisfy all cases? If so
|
|
// we could avoid extra work by just returning here.
|
|
}
|
|
nextslot:
|
|
//print(" new slot ", i);
|
|
//print(": ");
|
|
//print_qh_list((Pipe_t *)(periodictable[i] & 0xFFFFFFE0));
|
|
{}
|
|
}
|
|
#endif
|
|
#if 0
|
|
println("Periodic Schedule:");
|
|
for (uint32_t i=0; i < PERIODIC_LIST_SIZE; i++) {
|
|
if (i < 10) print(" ");
|
|
print(i);
|
|
print(": ");
|
|
print_qh_list((Pipe_t *)(periodictable[i] & 0xFFFFFFE0));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void USBHost::delete_Pipe(Pipe_t *pipe)
|
|
{
|
|
println("delete_Pipe ", (uint32_t)pipe, HEX);
|
|
|
|
// halt pipe, find and free all Transfer_t
|
|
|
|
// EHCI 1.0, 4.8.2 page 72: "Software should first deactivate
|
|
// all active qTDs, wait for the queue head to go inactive"
|
|
//
|
|
// http://www.spinics.net/lists/linux-usb/msg131607.html
|
|
// http://www.spinics.net/lists/linux-usb/msg131936.html
|
|
//
|
|
// In practice it's not feasible to wait for an active QH to become
|
|
// inactive before removing it, for several reasons. For one, the QH may
|
|
// _never_ become inactive (if the endpoint NAKs indefinitely). For
|
|
// another, the procedure given in the spec (deactivate the qTDs on the
|
|
// queue) is racy, since the controller can perform a new overlay or
|
|
// writeback at any time.
|
|
|
|
bool isasync = (pipe->type == 0 || pipe->type == 2);
|
|
if (isasync) {
|
|
// find the next QH in the async schedule loop
|
|
Pipe_t *next = (Pipe_t *)(pipe->qh.horizontal_link & 0xFFFFFFE0);
|
|
if (next == pipe) {
|
|
// removing the only QH, so just shut down the async schedule
|
|
println(" shut down async schedule");
|
|
USBHS_USBCMD &= ~USBHS_USBCMD_ASE; // disable async schedule
|
|
while (USBHS_USBSTS & USBHS_USBSTS_AS) ; // busy loop wait
|
|
USBHS_ASYNCLISTADDR = 0;
|
|
} else {
|
|
// find the previous QH in the async schedule loop
|
|
println(" remove QH from async schedule");
|
|
Pipe_t *prev = next;
|
|
while (1) {
|
|
Pipe_t *n = (Pipe_t *)(prev->qh.horizontal_link & 0xFFFFFFE0);
|
|
if (n == pipe) break;
|
|
prev = n;
|
|
}
|
|
// if removing the one with H bit, set another
|
|
if (pipe->qh.capabilities[0] & 0x8000) {
|
|
prev->qh.capabilities[0] |= 0x8000; // set H bit
|
|
}
|
|
// link the previous QH, we're no longer in the loop
|
|
prev->qh.horizontal_link = pipe->qh.horizontal_link;
|
|
// do the Async Advance Doorbell handshake to wait to be
|
|
// sure the EHCI no longer references the removed QH
|
|
USBHS_USBCMD |= USBHS_USBCMD_IAA;
|
|
while (!(USBHS_USBSTS & USBHS_USBSTS_AAI)) ; // busy loop wait
|
|
USBHS_USBSTS = USBHS_USBSTS_AAI;
|
|
// TODO: does this write interfere UPI & UAI (bits 18 & 19) ??
|
|
}
|
|
// find & free all the transfers which completed
|
|
println(" Free transfers");
|
|
Transfer_t *t = async_followup_first;
|
|
while (t) {
|
|
print(" * ", (uint32_t)t);
|
|
Transfer_t *next = t->next_followup;
|
|
if (t->pipe == pipe) {
|
|
print(" * remove");
|
|
remove_from_async_followup_list(t);
|
|
|
|
// Only free if not in QH list
|
|
Transfer_t *tr = (Transfer_t *)(pipe->qh.next);
|
|
while (((uint32_t)tr & 0xFFFFFFE0) && (tr != t)){
|
|
tr = (Transfer_t *)(tr->qtd.next);
|
|
}
|
|
if (tr == t) {
|
|
println(" * defer free until QH");
|
|
} else {
|
|
println(" * free");
|
|
free_Transfer(t); // The later code should actually free it...
|
|
}
|
|
} else {
|
|
println("");
|
|
}
|
|
t = next;
|
|
}
|
|
} else {
|
|
// remove from the periodic schedule
|
|
for (uint32_t i=0; i < PERIODIC_LIST_SIZE; i++) {
|
|
uint32_t num = periodictable[i];
|
|
if (num & 1) continue;
|
|
Pipe_t *node = (Pipe_t *)(num & 0xFFFFFFE0);
|
|
if (node == pipe) {
|
|
periodictable[i] = pipe->qh.horizontal_link;
|
|
continue;
|
|
}
|
|
Pipe_t *prev = node;
|
|
while (1) {
|
|
num = node->qh.horizontal_link;
|
|
if (num & 1) break;
|
|
node = (Pipe_t *)(num & 0xFFFFFFE0);
|
|
if (node == pipe) {
|
|
prev->qh.horizontal_link = node->qh.horizontal_link;
|
|
break;
|
|
}
|
|
prev = node;
|
|
}
|
|
}
|
|
// TODO: subtract bandwidth from uframe_bandwidth array
|
|
|
|
// find & free all the transfers which completed
|
|
println(" Free transfers");
|
|
Transfer_t *t = periodic_followup_first;
|
|
while (t) {
|
|
print(" * ", (uint32_t)t);
|
|
Transfer_t *next = t->next_followup;
|
|
if (t->pipe == pipe) {
|
|
print(" * remove");
|
|
remove_from_periodic_followup_list(t);
|
|
|
|
// Only free if not in QH list
|
|
Transfer_t *tr = (Transfer_t *)(pipe->qh.next);
|
|
while (((uint32_t)tr & 0xFFFFFFE0) && (tr != t)){
|
|
tr = (Transfer_t *)(tr->qtd.next);
|
|
}
|
|
if (tr == t) {
|
|
println(" * defer free until QH");
|
|
} else {
|
|
println(" * free");
|
|
free_Transfer(t); // The later code should actually free it...
|
|
}
|
|
} else {
|
|
println("");
|
|
}
|
|
t = next;
|
|
}
|
|
}
|
|
//
|
|
// TODO: do we need to look at pipe->qh.current ??
|
|
//
|
|
// free all the transfers still attached to the QH
|
|
println(" Free transfers attached to QH");
|
|
Transfer_t *tr = (Transfer_t *)(pipe->qh.next);
|
|
while ((uint32_t)tr & 0xFFFFFFE0) {
|
|
println(" * ", (uint32_t)tr);
|
|
Transfer_t *next = (Transfer_t *)(tr->qtd.next);
|
|
free_Transfer(tr);
|
|
tr = next;
|
|
}
|
|
// hopefully we found everything...
|
|
free_Pipe(pipe);
|
|
println("* Delete Pipe completed");
|
|
}
|
|
|
|
|