From 3355eab2a260414ba0392878016e39413ea4731d Mon Sep 17 00:00:00 2001 From: PaulStoffregen Date: Mon, 6 Mar 2017 06:03:07 -0800 Subject: [PATCH] Add documentation (in source code comments) --- USBHost_t36.h | 27 +++++++++++++++++++++++++++ ehci.cpp | 46 +++++++++++++++++++++++++++++++++++++++++++++- enumeration.cpp | 37 +++++++++++++++++++++++++++++++++++-- memory.cpp | 34 ++++++++++++++++++++++++++++++---- print.cpp | 5 +++++ 5 files changed, 142 insertions(+), 7 deletions(-) diff --git a/USBHost_t36.h b/USBHost_t36.h index 0e5fafe..3e50ee6 100644 --- a/USBHost_t36.h +++ b/USBHost_t36.h @@ -26,6 +26,33 @@ #include + +// Dear inquisitive reader, USB is a complex protocol defined with +// very specific terminology. To have any chance of understand this +// source code, you absolutely must have solid knowledge of specific +// USB terms such as host, device, endpoint, pipe, enumeration.... +// You really must also have at least a basic knowledge of the +// different USB transfers: control, bulk, interrupt, isochronous. +// +// The USB 2.0 specification explains these in chapter 4 (pages 15 +// to 24), and provides more detail in the first part of chapter 5 +// (pages 25 to 55). The USB spec is published for free at +// www.usb.org. Here is a convenient link to just the main PDF: +// +// https://www.pjrc.com/teensy/beta/usb20.pdf +// +// This is a huge file, but chapter 4 is short and easy to read. +// If you're not familiar with the USB lingo, please do yourself +// a favor by reading at least chapter 4 to get up to speed on the +// meaning of these important USB concepts and terminology. +// +// If you wish to ask questions (which belong on the forum, not +// github issues) or discuss development of this library, you +// ABSOLUTELY MUST know the basic USB terminology from chapter 4. +// Please repect other people's valuable time & effort by making +// your best effort to read chapter 4 before asking USB questions! + + #define USBHOST_PRINT_DEBUG /************************************************/ diff --git a/ehci.cpp b/ehci.cpp index 6ea9260..6dc685a 100644 --- a/ehci.cpp +++ b/ehci.cpp @@ -24,25 +24,69 @@ #include #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). -// may be 8, 16, 32, 64, 128, 256, 512, 1024 +// Supported values: 8, 16, 32, 64, 128, 256, 512, 1024 #define PERIODIC_LIST_SIZE 32 +// 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; diff --git a/enumeration.cpp b/enumeration.cpp index 0d7172d..5539e2b 100644 --- a/enumeration.cpp +++ b/enumeration.cpp @@ -25,21 +25,43 @@ #include "USBHost_t36.h" // Read this header first for key info +// USB devices are managed from this file. + + +// List of all connected devices, regardless of their status. If +// it's connected to the EHCI port or any port on any hub, it needs +// to be linked into this list. +static Device_t *devlist=NULL; + +// List of all inactive drivers. At the end of enumeration, when +// drivers claim the device or its interfaces, they are removed +// from this list and linked into the list of active drivers on +// that device. When devices disconnect, the drivers are returned +// to this list, making them again available for enumeration of new +// devices. static USBDriver *available_drivers = NULL; + +// Static buffers used during enumeration. One a single USB device +// may enumerate at once, because USB address zero is used, and +// because this static buffer & state info can't be shared. static uint8_t enumbuf[256] __attribute__ ((aligned(16))); static setup_t enumsetup __attribute__ ((aligned(16))); static uint16_t enumlen; -static Device_t *devlist=NULL; // True while any device is present but not yet fully configured. // Only one USB device may be in this state at a time (responding // to address zero) and using the enumeration static buffer. volatile bool USBHost::enumeration_busy = false; + + static void pipe_set_maxlen(Pipe_t *pipe, uint32_t maxlen); static void pipe_set_addr(Pipe_t *pipe, uint32_t addr); +// The main user function to cause internal state to update. Since we do +// almost everything with DMA and interrupts, the only work to do here is +// call all the active driver Task() functions. void USBHost::Task() { for (Device_t *dev = devlist; dev; dev = dev->next) { @@ -49,6 +71,12 @@ void USBHost::Task() } } +// Drivers call this after they've completed initialization, so get themselves +// added to the list of inactive drivers available for new devices during +// enumeraton. Typically this is called from constructors, so hardware access +// or even printing debug messages should be avoided here. Just initialize +// lists and return. +// void USBHost::driver_ready_for_device(USBDriver *driver) { driver->device = NULL; @@ -107,7 +135,12 @@ Device_t * USBHost::new_Device(uint32_t speed, uint32_t hub_addr, uint32_t hub_p } - +// Control transfer callback function. ALL control transfers from all +// devices call this function when they complete. When control transfers +// are created by drivers, the driver is called to handle the result. +// Otherwise, the control transfer is part of the enumeration process, +// which is implemented here. +// void USBHost::enumeration(const Transfer_t *transfer) { Device_t *dev; diff --git a/memory.cpp b/memory.cpp index 221558b..2be4ed6 100644 --- a/memory.cpp +++ b/memory.cpp @@ -25,16 +25,42 @@ #include "USBHost_t36.h" // Read this header first for key info -// Memory allocation +// Memory allocation for Device_t, Pipe_t and Transfer_t structures. +// +// To provide an Arduino-friendly experience, the memory allocation of +// these item is primarily done by the instances of device driver objects, +// which are typically created as static objects near the beginning of +// the Arduino sketch. Static allocation allows Arduino's memory usage +// summary to accurately show the amount of RAM this library is using. +// Users can choose which devices they wish to support and how many of +// each by creating more object instances. +// +// Device driver objects "contribute" their copies of these structures. +// When ehci.cpp allocates Pipe_t and Transfer_t, or enumeration.cpp +// allocates Device_t, the memory actually comes from these structures +// physically located within the device driver instances. The usage +// model looks like traditional malloc/free dynamic memory on the heap, +// but in fact it's a simple memory pool from the drivers. +// +// Timing is deterministic and fast, because each pool allocates only +// a single fixed size object. In theory, each driver should contribute +// the number of items it will use, so we should not ever end up with +// a situation where an item can't be allocated when it's needed. Well, +// unless there's a bug or oversight... -static Device_t memory_Device[1]; -static Pipe_t memory_Pipe[1] __attribute__ ((aligned(32))); -static Transfer_t memory_Transfer[4] __attribute__ ((aligned(32))); +// Lists of "free" memory static Device_t * free_Device_list = NULL; static Pipe_t * free_Pipe_list = NULL; static Transfer_t * free_Transfer_list = NULL; +// A small amount of non-driver memory, just to get things started +// TODO: is this really necessary? Can these be eliminated, so we +// use only memory from the drivers? +static Device_t memory_Device[1]; +static Pipe_t memory_Pipe[1] __attribute__ ((aligned(32))); +static Transfer_t memory_Transfer[4] __attribute__ ((aligned(32))); + void USBHost::init_Device_Pipe_Transfer_memory(void) { contribute_Devices(memory_Device, sizeof(memory_Device)/sizeof(Device_t)); diff --git a/print.cpp b/print.cpp index aac2abf..122b2d3 100644 --- a/print.cpp +++ b/print.cpp @@ -24,6 +24,11 @@ #include #include "USBHost_t36.h" // Read this header first for key info +// Printing of specific data structures. When this is enabled, +// a tremendous amount of debug printing occurs. It's done all +// from interrupt context, so this should never normally be +// enabled for regular programs that print from the Arduino sketch. + #ifdef USBHOST_PRINT_DEBUG void USBHost::print(const Transfer_t *transfer)