USBDI(9)

✨ USBDI(9) β€” Universal Serial Bus driver programming interface ✨

okay bestie, sit down, hydrate, because this is the Kernel Developer's Manual page for USBDI(9) and it is long πŸ’…. This is part 1 of 2, so buckle up. We're talking the USB driver programming interface, no cap.


🌸 NAME 🌸

usb_fifo_alloc_buffer, usb_fifo_attach, usb_fifo_detach, usb_fifo_free_buffer, usb_fifo_get_data, usb_fifo_get_data_buffer, usb_fifo_get_data_error, usb_fifo_get_data_linear, usb_fifo_put_bytes_max, usb_fifo_put_data, usb_fifo_put_data_buffer, usb_fifo_put_data_error, usb_fifo_put_data_linear, usb_fifo_reset, usb_fifo_softc, usb_fifo_wakeup, usbd_do_request, usbd_do_request_flags, usbd_errstr, usbd_lookup_id_by_info, usbd_lookup_id_by_uaa, usbd_transfer_clear_stall, usbd_transfer_drain, usbd_transfer_pending, usbd_transfer_poll, usbd_transfer_setup, usbd_transfer_start, usbd_transfer_stop, usbd_transfer_submit, usbd_transfer_unsetup, usbd_xfer_clr_flag, usbd_xfer_frame_data, usbd_xfer_frame_len, usbd_xfer_get_frame, usbd_xfer_get_priv, usbd_xfer_is_stalled, usbd_xfer_max_framelen, usbd_xfer_max_frames, usbd_xfer_max_len, usbd_xfer_set_flag, usbd_xfer_set_frame_data, usbd_xfer_set_frame_len, usbd_xfer_set_frame_offset, usbd_xfer_set_frames, usbd_xfer_set_interval, usbd_xfer_set_priv, usbd_xfer_set_stall, usbd_xfer_set_timeout, usbd_xfer_softc, usbd_xfer_state, usbd_xfer_status β€” Universal Serial Bus driver programming interface

like. the whole squad. every single one. iconic. πŸ‘‘


πŸ¦‹ SYNOPSIS πŸ¦‹

first, the includes (do NOT forget these, they're giving entrance requirements):

#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdi_util.h>

and the four functions this page opens with:

usb_error_t
usbd_transfer_setup(struct usb_device *udev, const uint8_t *ifaces,
    struct usb_xfer **pxfer, const struct usb_config *setup_start,
    uint16_t n_setup, void *priv_sc, struct mtx *priv_mtx);

void
usbd_transfer_unsetup(struct usb_xfer **pxfer, uint16_t n_setup);

void
usbd_transfer_start(struct usb_xfer *xfer);

void
usbd_transfer_stop(struct usb_xfer *xfer);

void
usbd_transfer_drain(struct usb_xfer *xfer);

πŸ’– DESCRIPTION πŸ’–

The Universal Serial Bus (USB) driver programming interface hands USB peripheral drivers a host controller independent API for controlling and communicating with USB peripherals. The usb module supports both USB Host and USB Device side mode. it's giving equal opportunity, we love to see it 🌈


⭐ USB TRANSFER MANAGEMENT FUNCTIONS ⭐

The USB standard defines four types of USB transfers. say it with me:

  • Control transfers
  • Bulk transfers
  • Interrupt transfers
  • Isochronous transfers

All the transfer types are managed using the following five functions. five functions, four types, one vibe check. 🫢

  • usbd_transfer_setup() β€” This function will allocate memory for and initialise an array of USB transfers and all required DMA memory. Heads up bestie: this function can sleep or block waiting for resources to become available. 😴
    • udev is a pointer to "struct usb_device".
    • ifaces is an array of interface index numbers to use. See "if_index".
    • pxfer is a pointer to an array of USB transfer pointers that are initialized to NULL, and then pointed to allocated USB transfers.
    • setup_start is a pointer to an array of USB config structures.
    • n_setup is a number telling the USB system how many USB transfers should be setup.
    • priv_sc is the private softc pointer, which will be used to initialize "xfer->priv_sc".
    • priv_mtx is the private mutex protecting the transfer structure and the softc. This pointer is used to initialize "xfer->priv_mtx".
    • This function returns zero upon success. A non-zero return value indicates failure.
  • usbd_transfer_unsetup() β€” This function will release the given USB transfers and all allocated resources associated with these USB transfers.
    • pxfer is a pointer to an array of USB transfer pointers, that may be NULL, that should be freed by the USB system.
    • n_setup is a number telling the USB system how many USB transfers should be unsetup.
    • This function can sleep waiting for USB transfers to complete.
    • This function is NULL safe with regard to the USB transfer structure pointer.
    • > 🚫 It is not allowed to call this function from the USB transfer callback. do not do it. i'm serious.
  • usbd_transfer_start() β€” This function will start the USB transfer pointed to by xfer, if not already started.
    • This function is always non-blocking and must be called with the so-called private USB mutex locked. πŸ”’
    • This function is NULL safe with regard to the USB transfer structure pointer.
  • usbd_transfer_stop() β€” This function will stop the USB transfer pointed to by xfer, if not already stopped.
    • This function is always non-blocking and must be called with the so-called private USB mutex locked. πŸ”’
    • This function can return before the USB callback has been called.
    • This function is NULL safe with regard to the USB transfer structure pointer.
    • If the transfer was in progress, the callback will called with "USB_ST_ERROR" and "error = USB_ERR_CANCELLED". 😭
  • usbd_transfer_drain() β€” This function will stop an USB transfer, if not already stopped and wait for any additional USB hardware operations to complete.
    • Buffers that are loaded into DMA using "usbd_xfer_set_frame_data()" can safely be freed after that this function has returned. that's the whole point, the waiting is the point 🧚
    • This function can block the caller and will not return before the USB callback has been called.
    • This function is NULL safe with regard to the USB transfer structure pointer.

πŸŽ€ USB TRANSFER CALLBACK πŸŽ€

The USB callback has three states. USB_ST_SETUP, USB_ST_TRANSFERRED and USB_ST_ERROR. three states, one main character callback. πŸ’…

  • USB_ST_SETUP is the initial state. After the callback has been called with this state it will always be called back at a later stage in one of the other two states.
  • > ⚠️ The USB callback should not restart the USB transfer in case the error cause is USB_ERR_CANCELLED.

The USB callback is protected from recursion. That means one can start and stop whatever transfer from the callback of another transfer one desires. Also the transfer that is currently called back. Recursion is handled like this: when the callback that wants to recurse returns, it is called one more time. yes, it comes back. it always comes back. πŸ’€

  • usbd_transfer_submit() β€” This function should only be called from within the USB callback and is used to start the USB hardware. An USB transfer can have multiple frames consisting of one or more USB packets making up an I/O vector for all USB transfer types.

and here's the reference callback, the blueprint, the moment:

void
usb_default_callback(struct usb_xfer *xfer, usb_error_t error)
{
        int actlen;

        usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);

        switch (USB_GET_STATE(xfer)) {
        case USB_ST_SETUP:
                /*
                 * Setup xfer frame lengths/count and data
                 */
                usbd_transfer_submit(xfer);
                break;

        case USB_ST_TRANSFERRED:
                /*
                 * Read usb frame data, if any.
                 * "actlen" has the total length for all frames
                 * transferred.
                 */
                break;

        default: /* Error */
                /*
                 * Print error message and clear stall
                 * for example.
                 */
                break;
        }
        /*
         * Here it is safe to do something without the private
         * USB mutex locked.
         */
        return;
}

πŸ”₯ USB CONTROL TRANSFERS πŸ”₯

An USB control transfer has three parts. First the SETUP packet, then DATA packet(s) and then a STATUS packet. the little trio, the lineup, the holy trinity ✨

  • The SETUP packet is always pointed to by frame 0 and the length is set by usbd_xfer_frame_len() β€” also if there should not be sent any SETUP packet!
  • If an USB control transfer has no DATA stage, then the number of frames should be set to 1.
  • Else the default number of frames is 2.

okay now the examples, screenshot these, they're the whole assignment πŸ“Έ

Example1: SETUP + STATUS

Example1: SETUP + STATUS
 usbd_xfer_set_frames(xfer, 1);
 usbd_xfer_set_frame_len(xfer, 0, 8);
 usbd_transfer_submit(xfer);

Example2: SETUP + DATA + STATUS

Example2: SETUP + DATA + STATUS
 usbd_xfer_set_frames(xfer, 2);
 usbd_xfer_set_frame_len(xfer, 0, 8);
 usbd_xfer_set_frame_len(xfer, 1, 1);
 usbd_transfer_submit(xfer);

Example3: SETUP + DATA + STATUS β€” split

Example3: SETUP + DATA + STATUS - split
1st callback:
 usbd_xfer_set_frames(xfer, 1);
 usbd_xfer_set_frame_len(xfer, 0, 8);
 usbd_transfer_submit(xfer);

2nd callback:
 /* IMPORTANT: frbuffers[0] must still point at the setup packet! */
 usbd_xfer_set_frames(xfer, 2);
 usbd_xfer_set_frame_len(xfer, 0, 0);
 usbd_xfer_set_frame_len(xfer, 1, 1);
 usbd_transfer_submit(xfer);
πŸ‘€ that /* IMPORTANT: frbuffers[0] must still point at the setup packet! */ comment? it is there for a reason. do not lose the setup packet. it will not forgive you.

Example4: SETUP + STATUS β€” split

Example4: SETUP + STATUS - split
1st callback:
 usbd_xfer_set_frames(xfer, 1);
 usbd_xfer_set_frame_len(xfer, 0, 8);
 usbd_xfer_set_flag(xfer, USB_MANUAL_STATUS);
 usbd_transfer_submit(xfer);

2nd callback:
 usbd_xfer_set_frames(xfer, 1);
 usbd_xfer_set_frame_len(xfer, 0, 0);
 usbd_xfer_clr_flag(xfer, USB_MANUAL_STATUS);
 usbd_transfer_submit(xfer);

πŸ’– USB TRANSFER CONFIG πŸ’–

okay bestie, so to make endpoint-hunting way less of a nightmare, the usb module ships a little struct called usb_config where you just... describe the endpoint you want. iconic behavior honestly.

struct usb_config {
        bufsize,
        callback
        direction,
        endpoint,
        frames,
        index flags,
        interval,
        timeout,
        type,
};

let's go field by field, no skips, no shortcuts 🫢

type picks which USB pipe type you want. the valid values are: UE_INTERRUPT, UE_CONTROL, UE_BULK, UE_ISOCHRONOUS. and there's a special one, UE_BULK_INTR, which snags both BULK and INTERRUPT pipes. this field is mandatory β€” no type, no vibes.

endpoint picks the USB endpoint number. if you set it to 0xFF, "-1" or "UE_ADDR_ANY", you get the first matching endpoint that shows up. also mandatory. πŸ’…

direction picks the USB endpoint direction. "UE_DIR_ANY" means "just gimme the first matching one". otherwise your valid values are "UE_DIR_IN" and "UE_DIR_OUT". you can binary OR those with "UE_DIR_SID", which means the direction gets swapped when it's USB_MODE_DEVICE. and heads up: "UE_DIR_IN" refers to the data transfer direction of the "IN" tokens, while "UE_DIR_OUT" refers to the data transfer direction of the "OUT" tokens. yes this field is mandatory too, we stay consistent. ✨

interval picks the interrupt interval, given in milliseconds, and it does not care about device speed. but depending on the endpoint type it means different things, so let's break it down:

  • UE_INTERRUPT β€” "0" means use the default interrupt interval based on the endpoint descriptor. "Else" means use the given value for polling rate.
  • UE_ISOCHRONOUS β€” "0" means use default. "Else" the value is just ignored, rip.
  • UE_BULK
  • UE_CONTROL β€” "0" means no transfer pre-delay. "Else" means a delay of that many milliseconds gets inserted before the hardware is started, when "usbd_transfer_submit()" is called.
NOTE: the transfer timeout, if there is one, starts after the pre-delay has elapsed! don't get caught lacking ⏰

timeout β€” if it's non-zero, it sets the transfer timeout in milliseconds. and if "timeout" is zero and the transfer type is ISOCHRONOUS, you get a timeout of 250ms whether you asked or not.

frames sets the maximum number of frames. if you specify zero, here's the tea:

  • UE_BULK β€” xfer->nframes = 1;
  • UE_INTERRUPT β€” xfer->nframes = 1;
  • UE_CONTROL β€” xfer->nframes = 2;
  • UE_ISOCHRONOUS β€” Not allowed. Will cause an error. πŸ’€

ep_index lets you give a number, in case more endpoints match the description than you bargained for, that selects which matching "ep_index" should actually be used.

if_index lets you select which of the interface numbers in the "ifaces" array parameter passed to "usbd_transfer_setup" should be used when setting up the given USB transfer.

flags is of type "struct usb_xfer_flags" and lets you set the initial flags on a USB transfer. the valid flags are:

  • force_short_xfer β€” forces the last transmitted USB packet to be short. a short packet has a length less than "xfer->max_packet_size", which derives from "wMaxPacketSize". this flag can be changed during operation.
  • short_xfer_ok β€” allows the received transfer length, "xfer->actlen", to be less than "xfer->sumlen" when a transfer completes. this flag can be changed during operation.
  • short_frames_ok β€” allows the reception of multiple short USB frames. this flag only does anything for BULK and INTERRUPT endpoints, and only when the number of frames received is greater than 1. this flag can be changed during operation.
  • pipe_bof β€” makes a failing USB transfer stay first in the PIPE queue, except when "xfer->error" equals "USB_ERR_CANCELLED". no other USB transfers in the affected PIPE queue will start until either:
    1. the failing USB transfer is stopped using "usbd_transfer_stop()".
    2. the failing USB transfer performs a successful transfer.

the whole point of this flag is dodging races when multiple transfers are queued up on one USB endpoint and the first executing transfer fails, which for example creates the need to clear a stall. in that case this flag stops the following USB transfers from running at the same moment the clear-stall command runs on the USB control endpoint. this flag can be changed during operation.

"BOF" is short for "Block On Failure".

NOTE: this flag should be set on all BULK and INTERRUPT USB transfers that use an endpoint which can be shared between userland and kernel. fr fr, don't skip this one.
  • proxy_buffer β€” setting this rounds the total buffer size up to the nearest atomic hardware transfer size. the maximum data length of any USB transfer is always stored in "xfer->max_data_length". for control transfers the USB kernel allocates extra space for the 8-bytes of SETUP header, and those 8 bytes are not counted by the "xfer->max_data_length" variable. this flag cannot be changed during operation.
  • ext_buffer β€” setting this means no data buffer gets allocated at all. instead the USB client has to supply a data buffer itself. this flag cannot be changed during operation.
  • manual_status β€” setting this prevents a USB STATUS stage from being appended to the end of the USB control transfer. if no control data is transferred, this flag must be cleared, otherwise an error gets returned to the USB callback. this flag is mostly useful on the USB device side. this flag can be changed during operation.
  • no_pipe_ok β€” setting this makes the USB_ERR_NO_PIPE error get ignored. this flag cannot be changed during operation.
  • stall_pipe β€”
    • Device Side Mode β€” setting this flag causes STALL pids to be sent to the endpoint belonging to this transfer before the transfer is started. the transfer then starts the moment the host issues a clear-stall command on the STALL'ed endpoint. this flag can be changed during operation.
    • Host Side Mode β€” setting this flag causes a clear-stall control request to be executed on the endpoint before the USB transfer is started.

if you change this flag outside the USB callback function, you have to use the "usbd_xfer_set_stall()" and "usbd_transfer_clear_stall()" functions! this flag is automatically cleared after the stall or clear stall has been executed. ✨

  • pre_scale_frames β€” if this is set, the number of frames specified is assumed to give the buffering time in milliseconds instead of frames. during transfer setup the frames field gets pre-scaled with the corresponding value for the endpoint and rounded to the nearest number of frames greater than zero. this option only has an effect for ISOCHRONOUS transfers.

bufsize sets the total buffer size in bytes. if this field is zero, "wMaxPacketSize" is used, multiplied by the "frames" field when the transfer type is ISOCHRONOUS. super handy for setting up interrupt pipes. this field is mandatory.

NOTE: for control transfers, "bufsize" includes the length of the request structure. don't forget bestie 😭

callback pointer sets the USB callback. this field is mandatory. obviously.


🐧 USB LINUX COMPAT LAYER 🌈

the usb module supports the Linux USB API. that's it, that's the whole section, and honestly? we love a low-effort icon. πŸ‘‘


πŸ‘€ SEE ALSO πŸ‘€


πŸ“œ STANDARDS πŸ“œ

the usb module complies with the USB 2.0 standard. certified, no notes. βœ…


πŸ¦‹ HISTORY πŸ¦‹

the usb module was inspired by the NetBSD USB stack, which was initially written by Lennart Augustsson. the usb module itself was written by Hans Petter Selasky <hselasky@FreeBSD.org>. absolute legends, we stan 🫢


and that's a wrap on this whole page, bestie! this version came from the Debian crew, dated November 14, 2016, and it lives under the name USBDI(9). slay, go write some USB transfers πŸ’…βœ¨πŸ’–

πŸ“¦ where this page comes from