# USB subsystem

Dusk OS has support for the Universal Serial Bus which was initially ported
from Plan 9. It lives in [drv/usb].

The broad idea of USB is that there's a host that communicates with devices
through one or more port. A device can be a hub which itself has more ports,
each of which being able to have an attached device.

When a device is connected, it's being given an address by the host. An address
is a simple number between 1 and 127 (address 0 is special and cannot be
assigned).

Each device can have up to 16 endpoints, which are channels through which the
host can communicate with the device. Endpoint 0 is special and is the
"control" endpoint. Every USB device has a 0 endpoint and it's through this
endpoint that initial configuration of each device is done.

A computer system can have more than one host. Theoretically, each host could
have its own addressing space, but we don't do that in Dusk. The address space
is shared through all USB hosts of the system.

## Underpowered but simple

Because Dusk has no concurrency, it kind of makes inoperant the idea, central to
USB, of multiple devices sharing frames in an efficient manner.

Because there is no concurrent thread, there can pretty much only every be one
device being acted upon at once. At least until some kind of async mechanism
is put in place.

This means that USB frames will pretty much always contain at most a single USB
transfer, and the system is going to wait for the frame to be finished before
acting upon it.

Even the idea of queueing multiple transfers for the Host to execute doesn't
really work because there's the data toggle to worry about. Keeping track of the
toggle, considering that a transfer can result in a NAK, while also queuing them
makes the logic fuzzy and complicated. It's better to have only one transfer at
once.

So yeah, underpowered (not slow *per se*, but can only do one thing at once) but
simple.

## Loading the drivers

The USB drivers come in three broad parts. First, there's the host driver. This
driver is specific to each chipset implementing a USB host. For example,
[drv/usb/dwc.fs] contains the driver for the DWC USB host.

Then, there're the generic host driver. Different USB hosts all have their
specificities, but they all adhere to the USB specifications, which makes them
behave in the same manner in many regards. For example, enumeration logic works
the same with all USB host. This logic is implemented in [drv/usb/usb.fs].

Lastly, there's USB device drivers. USB devices can have many different behavior
and one needs a particular driver for each type of behavior. For example, if you
want to drive a USB keyboard, you'll use [drv/usb/kbd.fs].

In a typical system, the first thing you'll want to load the driver for your
USB host first, which will typically auto-load the generic part of the USB
mechanism.

Once your host is initialized, you can do attachment detection and enumeration
with `usbwork`. This checks each port of each hub for the "present and changed"
flags and do device initialization for each newly connected device.

If you want to see USB status, you can call `lsusb`.

Then, the rest if open ended and depends on the kind of device you want to use.

## Keyboards

The keyboard driver lives at [drv/usb/kbd.fs]. This unit supplies two important
words. `usbkbd$ ( -- )` iterates through attached devices until it finds a
keyboard (class 1, subclass 3) and initialize the device as a keyboard.

Then, there's the "USBKeyboard" struct which is created through
":new ( -- kbd )" and conforms to [doc/io/kbd]. Therefore, once you've
initialized the USB keyboard driver with "usbkbd$", you can bind it to the
system keyboard with a line that looks like:

  USBKeyboard :new keyboard' !

## Structures

The USB subsystem revolves around a handful of structures, defined in
[drv/usb/struct.fs] . We could say that the most "useful" among them is Ep,
which represents an endpoint. Most words of the USB API need a reference to this
structure.

There's not really a useful "handle" the the device itself. There's the Dev
struct, but it's only a "info repository", not really a handle to actions upon
the device. When we want to act on the device, we will usually do so through
its "ep0", the control endpoint, which is a Ep struct.

IEp and IDev are a information repository directly coming from descriptors fed
to us by the device.

Hub and Port manage, well, hubs and ports! All hubs live in the "hubs" LL, in
which all links have a single element: a pointer to a Hub struct.

Lastly, there's Hci, the Host Controller Interface, with pointers to method
implemented by individual device drivers (for example [drv/usb/uhci] and
[drv/rpi/dwc] ).

How you'll typically create those structs is by first creating and initializing
a Hci. The initialization of the Hci will include the creation of a root Hub,
added to "hubs". When a device is attached, it will be picked up by "usbwork",
which will call "newdev" to yield a Ep and then configure it. With a device
attached to a Hub's Port, you can access it.

## API

epopen ( ep -- )
  Tell the Hci to prepare whatever it has to prepare to for "ep" to be
  functional. Aborts on error.

epread ( n a ep -- n )
  Read n bytes from ep in buffer a. Aborts on non-recoverable errors (some Hci
  yield -1, but that's going away). Yielded n is the number of bytes actually
  read. On NAK, n=0.

epwrite ( n a ep -- n )
  Write n bytes from buffer a to endpoint ep. If ep is the control endpoint,
  the "a" buffer contains the REQSZ-wide request followed directly by the
  associated data (which can be absent depending on the request).

  Aborts on error. On success, yielded "n" will be the same as input "n".

usbcmd ( n a index value req type ep -- n )
  Given a control endpoint "ep", construct a request with the specified fields.
  If the request has no associated data, "a" and "n" are 0. If the request has
  to be accompanied by OUT data, the range "a n" is that data. If the request
  involves IN data, "a" is a buffer to receive that data and "n" is the expected
  length of that data.

  Aborts on error. On success, yielded "n" is the same as input "n".

usbwork ( -- )
  Run the USB enumeration loop once. If new devices are detected by the hub
  ports, initialize them. After a "usbwork" call, "lsusb" should show all
  attached devices.

lshub ( -- )
  Go through all root hubs and recursively print a textual description of
  attached devices.

finddevofclass ( subclass class -- ep-or-0 )
  Go through all attached devices and yield the first device (well, its ep0)
  where the class/subclass info correspond to the specified ones.

  Yield 0 if no such device was found.
