|newpage|

.. _usb_audio_sec_usb:

Endpoint 0: Management and control
==================================

All USB devices must support a mandatory control endpoint, Endpoint 0.
This controls the management tasks of the USB device.

These tasks can be generally split into enumeration, audio configuration and firmware upgrade requests.

Enumeration
-----------

When the device is first attached to a host, enumeration occurs.
This process involves the host interrogating the device as to its functionality.
The device does this by presenting several interfaces to the host via a set of descriptors.

During the enumeration process the host will issue various commands to the device including assigning the device a unique address on the bus.

The endpoint 0 code runs in its own thread and follows a similar format to that of the USB HID Mouse
Device examples in `lib_xud <www.xmos.com/file/lib_xud>`_
That is, a call is made to ``USB_GetSetupPacket()`` to receive a command from the host.
This populates a ``USB_SetupPacket_t`` structure, which is then parsed.

There are many mandatory requests that a USB Device must support as required by the USB Specification.
Since these are required for all devices in order to function a
``USB_StandardRequests()`` function is provided (see ``xud_device.xc``) which implements all of these requests.
This includes the following items:

- Requests for standard descriptors (Device descriptor, configuration descriptor etc) and string descriptors
- USB GET/SET INTERFACE requests
- USB GET/SET_CONFIGURATION requests
- USB SET_ADDRESS requests

For more information and full documentation, including full worked examples of simple devices,
refer to `lib_xud <www.xmos.com/file/lib_xud>`_.

The ``USB_StandardRequests()`` function takes the device's various descriptors as parameters.
These are passed from data structures found in the ``xud_ep0_descriptors.h`` file.
These data structures are fully customised based on how the design is configured using various defines.

The ``USB_StandardRequests()`` functions returns a ``XUD_Result_t``. ``XUD_RESULT_OKAY`` to indicate that the request was fully handled without error and no further action is required
- The device should move to receiving the next request from the host (via ``USB_GetSetupPacket()``).

The function returns ``XUD_RES_ERR`` if the request was not recognised by the
``USB_StandardRequests()`` function and a ``STALL`` has been issued.

The function may also return ``XUD_RES_RST`` if a bus-reset has been issued onto the bus by the host and communicated from XUD to Endpoint 0.

Since the ``USB_StandardRequests()`` function STALLs an unknown request, the endpoint 0 code must first parse the ``USB_SetupPacket_t`` structure to handle device specific requests and then call ``USB_StandardRequests()`` as required.

Overriding standard requests
-----------------------------

The USB Audio design "overrides" some of the requests handled by ``USB_StandardRequests()``, for
example it uses the ``SET_INTERFACE`` request to indicate if the host is streaming audio to the
device. In this case the setup packet is parsed, the relevant action taken, the
``USB_StandardRequests()`` is still called to handle the response to the host.

Class requests
--------------

Before making the call to ``USB_StandardRequests()`` the setup packet is parsed for Class requests. These are handled in functions such as ``AudioClassRequests_1()``, ``AudioClassRequests_2``, ``dfu_usb_class_int_requests()`` etc depending on the type of request.

Any device specific requests are handled - in this case Audio Class, MIDI class, DFU requests etc.

Some of the common Audio Class requests and their associated behaviour will now be examined.

Audio requests
^^^^^^^^^^^^^^

When the host issues an audio request (e.g. sample rate or volume change), it sends a command to Endpoint 0. Like all requests this is returned from ``USB_GetSetupPacket()``. After some parsing (namely as Class Request to an Audio Interface) the request is handled by either the ``AudioClassRequests_1()`` or ``AudioClassRequests_2()`` function (based on whether the device is running in Audio Class 1.0 or 2.0 mode).

Note, Audio Class 1.0 Sample rate changes are send to the relevant endpoint, rather than the interface - this is handled as a special case in he endpoint 0 request parsing where ``AudioEndpointRequests_1()`` is called.

The ``AudioClassRequests_X()`` functions further parses the request in order to ascertain the correct audio operation to execute.

Audio request: Set sample rate
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The ``AudioClassRequests_2()`` function parses the passed ``USB_SetupPacket_t`` structure for a ``CUR`` request of type ``SAM_FREQ_CONTROL`` to a Clock Unit in the device's topology (as described in the device descriptors).

The new sample frequency is extracted and passed via a channel to the rest of the design - through the buffering code and eventually to the Audio Hub (I²S) thread.
The ``AudioClassRequests_2()`` function waits for a handshake to propagate back through the system before signalling to the host that the
request has completed successfully. Note, during this time the USB library is NAKing the host, essentially holding off further traffic/requests until the sample-rate change is fully complete.

.. _usb_audio_sec_audio-requ-volume:

Audio Request: Volume control
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

When the host requests a volume change, it
sends an audio interface request to Endpoint 0. An array is
maintained in the Endpoint 0 thread that is updated with such a
request.

When changing the volume, Endpoint 0 applies the master volume and
channel volume, producing a single volume value for each channel.
These are stored in the array.

The volume will either be handled by the ``decouple`` thread or the ``mixer``
component (if the mixer component is used). Handling the volume in the
mixer gives the decoupler more performance to handle more channels.

If the effect of the volume control array on the audio input and
output is implemented by the decoupler, the ``decoupler`` thread
reads the volume values from this array. Note that this array is shared
between Endpoint 0 and the decoupler thread. This is done in a safe
manner, since only Endpoint 0 can write to the array, word update
is atomic between threads and the decoupler thread only reads from
the array (ordering between writes and reads is unimportant in this
case). Inline assembly is used by the decoupler thread to access
the array, avoiding the parallel usage checks of XC.

If volume control is implemented in the mixer, Endpoint 0 sends a mixer command
to the mixer to change the volume. Mixer commands
are described in :ref:`usb_audio_sec_mixer`.

Audio endpoints (Endpoint Buffer and Decoupler)
===============================================

Endpoint Buffer
---------------

All endpoints other that Endpoint 0 are handled in one thread. This
thread is implemented in the file ``ep_buffer.xc``. This thread communicates directly with the XUD library.

The USB buffer thread is also responsible for feedback calculation based on USB Start Of Frame
(SOF) notification and reads from the port counter of a port connected to the master clock.

Decouple
--------

The decoupler supplies the USB buffering thread with buffers to
transmit/receive audio data to/from the host. It marshals these buffers into
FIFOs. The data from the FIFOs is then sent over XC channels to
other parts of the system as they need it. In asynchronous mode this thread also
determines the size of each packet of audio to send to the host (thus
matching the audio rate to the USB packet rate). The decoupler is
implemented in the file ``decouple.xc``.

Audio buffering scheme
----------------------

This scheme is executed by co-operation between the buffering
thread, the decouple thread and the XUD library.

For data going from the device to the host the following scheme is
used:

#. The Decouple thread receives samples from the Audio Hub thread and
   puts them into a FIFO. This FIFO is split into packets when data is
   entered into it. Packets are stored in a format consisting of their
   length in bytes followed by the data.

#. When the Endpoint Buffer thread needs a buffer to send to the XUD thread
   (after sending the previous buffer), the Decouple thread is
   signalled (via a shared memory flag).

#. Upon this signal from the Endpoint Buffer thread, the Decouple thread
   passes the next packet from the FIFO to the Endpoint Buffer thread. It also
   signals to the XUD library that the Endpoint Buffer thread is able to send a
   packet.

#. When the Endpoint Buffer thread has sent this buffer, it signals to the
   Decouple thread that the buffer has been sent and the Decouple thread
   moves the read pointer of the FIFO.

For data going from the host to the device the following scheme is
used:

#. The Decouple thread passes a pointer to the Endpoint Buffer thread
   pointing into a FIFO of data and signals to the XUD library that
   the Endpoint Buffer thread is ready to receive.

#. The Endpoint Buffer thread then reads a USB packet into the FIFO and
   signals to the Decouple thread that the packet has been read.

#. Upon receiving this signal the Decouple thread updates the
   write pointer of the FIFO and provides a new pointer to the
   Endpoint Buffer thread to fill.

#. Upon request from the Audio Hub thread, the Decouple thread sends
   samples to the Audio Hub thread by reading samples out of the FIFO.

Decoupler/Audio Hub interaction
-------------------------------

To meet timing requirements of the audio system (i.e Audio Hub/Mixer), the Decoupler
thread must respond to requests from the audio system to
send/receive samples immediately. An interrupt handler
is set up in the decoupler thread to do this. The interrupt handler
is implemented in the function ``handle_audio_request``.

The audio system sends a word over a channel to the decouple thread to
request sample transfer (using the build in ``outuint()`` function).
The receipt of this word in the channel
causes the ``handle_audio_request`` interrupt to fire.

The first operation the interrupt handler does (once it inputs the word that triggered the interrupt)
is to send back a word acknowledging the request (if there was a change of sample frequency
a control token would instead be sent---the audio system uses a testct()
to inspect for this case).

Sample transfer may now take place.  First the Decouple thread sends samples from host to device then the
audio subsystem transfers samples destined for the host.  These transfers always take place
in channel count sized chunks (i.e. ``NUM_USB_CHAN_OUT`` and
``NUM_USB_CHAN_IN``).  That is, if the device has 10 output channels and 8 input channels,
10 samples are sent from the decouple thread and 8 received every interrupt.

The complete communication scheme is shown in :numref:`tab_decouple_audio_comms` (for non sample
frequency change case).

.. _tab_decouple_audio_comms:

.. table::  Decouple/Audio system channel communication

 +-----------------+-----------------+-----------------------------------------+
 | Decouple        | Audio System    | Note                                    |
 +=================+=================+=========================================+
 |                 | outuint()       | Audio system requests sample exchange   |
 +-----------------+-----------------+-----------------------------------------+
 | inuint()        |                 | Interrupt fires and inuint performed    |
 +-----------------+-----------------+-----------------------------------------+
 | outuint()       |                 | Decouple sends ack                      |
 +-----------------+-----------------+-----------------------------------------+
 |                 | testct()        | Checks for CT indicating SF change      |
 +-----------------+-----------------+-----------------------------------------+
 |                 | inuint()        | Word indication ACK input (No SF change)|
 +-----------------+-----------------+-----------------------------------------+
 | inuint()        | outuint()       | Sample transfer (Device to Host)        |
 +-----------------+-----------------+-----------------------------------------+
 | inuint()        | outuint()       |                                         |
 +-----------------+-----------------+-----------------------------------------+
 | inuint()        | outuint()       |                                         |
 +-----------------+-----------------+-----------------------------------------+
 | ...             |                 |                                         |
 +-----------------+-----------------+-----------------------------------------+
 | outuint()       | inuint()        | Sample transfer (Host to Device)        |
 +-----------------+-----------------+-----------------------------------------+
 | outuint()       | inuint()        |                                         |
 +-----------------+-----------------+-----------------------------------------+
 | outuint()       | inuint()        |                                         |
 +-----------------+-----------------+-----------------------------------------+
 | outuint()       | inuint()        |                                         |
 +-----------------+-----------------+-----------------------------------------+
 | ...             |                 |                                         |
 +-----------------+-----------------+-----------------------------------------+

.. note::
    The request and acknowledgement sent to/from the Decouple thread to the Audio System is an "output underflow" sample
    value.  If in PCM mode it will be 0, in DSD mode it will be DSD silence.
    This allows the buffering system to output a suitable underflow value without knowing the format of the stream
    (this is especially advantageous in the DSD over PCM (DoP) case)

Asynchronous feedback
---------------------

When built to operate in Asynchronous mode the device uses a feedback endpoint to report the rate at which
audio is output/input to/from external audio interfaces/devices. This feedback is in accordance with
the *USB 2.0 Specification*.  This calculated feedback value is also used to size packets to the host.

This asynchronous clocking scheme means that the device is the clock master and therefore
a high-quality local master clock or a digital input stream can be used as the clock source.

After each received USB Start Of Frame (SOF) token, the buffering thread takes a time-stamp from a port clocked off
the master clock. By subtracting the time-stamp taken at the previous SOF, the number of master
clock ticks since the last SOF is calculated. From this the number of samples (as a fixed
point number) between SOFs can be calculated.  This count is aggregated over 128 SOFs and used as a
basis for the feedback value.

The sending of feedback to the host is also handled in the Endpoint Buffer thread via an explicit
feedback IN endpoint.

If both input and output is enabled then the feedback can be implicit based on the audio stream
sent to the host. In practice though an explicit feedback endpoint is normally used due to restrictions
in Microsoft Windows operating systems (see ``UAC_FORCE_FEEDBACK_EP``).

USB rate control
----------------

.. _usb_audio_sec_usb-rate-control:

The device must consume data from USB host and provide data to USB host at the correct rate for the
selected sample frequency. When running in asynchronous mode the *USB 2.0 Specification* states
that the maximum variation on USB packets can be +/- 1 sample per USB frame (Synchronous mode
mandates no variation other than that required to match a sample rate that doesn't cleanly divide
the USB SOF period e.g. 44.1kHz)

High-speed USB frames are sent at 8kHz, so on average for 48kHz each packet contains six samples
per channel.

When running in Asynchronous mode, the audio clock may drift and run faster or slower than the
host. Hence, if the audio clock is slightly fast, the device may occasionally input/output seven
samples rather than six. Alternatively, it may be slightly slow and input/output five samples rather
than six. :ref:`usb_audio_samples_per_packet` shows the allowed number of samples per packet for
each example audio frequency in Asynchronous mode.

When running in Synchronous mode the audio clock is synchronised to the USB host SOF clock. Hence,
at 48kHz the device always expects six samples from, and always sends six samples to the host.

See `USB Device Class Definition for Audio Data Formats v2.0 <https://www.usb.org/document-library/audio-devices-rev-20-and-adopters-agreement>`_ section 2.3.1.1 for full details.

.. _usb_audio_samples_per_packet:

.. table::  Allowed samples per packet in Async mode

 +-----------------+-------------+-------------+
 | Frequency (kHz) | Min Packet  | Max Packet  |
 +=================+=============+=============+
 | 44.1            | 5           | 6           |
 +-----------------+-------------+-------------+
 | 48              | 5           | 7           |
 +-----------------+-------------+-------------+
 | 88.2            | 10          | 11          |
 +-----------------+-------------+-------------+
 | 96              | 11          | 13          |
 +-----------------+-------------+-------------+
 | 176.4           | 20          | 21          |
 +-----------------+-------------+-------------+
 | 192             | 23          | 25          |
 +-----------------+-------------+-------------+

To implement this control, the Decoupler thread uses the feedback value calculated in the EP Buffering
thread. This value is used to work out the size of the next packet it will insert into the audio FIFO.

.. note::

    In Synchronous mode the same system is used, but the feedback value simply uses a fixed value
    rather than one derived from the master clock port.

