Linux USB Device Driver Model

Linux kernel source: include/linux/usb.h
Linux kernel source: drivers/hid/usbhid/usbmouse.c

1. BUS/DEV/DRV Model

Linux USB Device Driver Model

“USB Interface” is a logical USB device, and the usb_driver driver written supports the “USB interface”:

Linux USB Device Driver Model
  • After the USB controller or Hub recognizes the USB device, it will create and register usb_device

  • Once usb_device is claimed by the driver in “drivers/usb/core/generic.c”, it will select and set a configuration

  • Interfaces under this configuration will allocate, set up, and register a usb_interface

  • If the left usb_driver matches the right usb_interface, usb_driver.probe will be called

2. Interface Functions

In the USB device driver, all usable USB functions are in this header file: include/linux/usb.h.

2.1 Pipe

The main purpose of using these interface functions is to transfer data, the object of data transfer is an endpoint in the USB device, which is called a pipe:

/* Create various pipes... */
#define usb_sndctrlpipe(dev, endpoint) \
 ((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint))
#define usb_rcvctrlpipe(dev, endpoint) \
 ((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndisocpipe(dev, endpoint) \
 ((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint))
#define usb_rcvisocpipe(dev, endpoint) \
 ((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndbulkpipe(dev, endpoint) \
 ((PIPE_BULK << 30) | __create_pipe(dev, endpoint))
#define usb_rcvbulkpipe(dev, endpoint) \
 ((PIPE_BULK << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndintpipe(dev, endpoint) \
 ((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint))
#define usb_rcvintpipe(dev, endpoint) \
 ((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)

2.2 Synchronous Transfer Functions

For control transfer, bulk transfer, and interrupt transfer, there are 3 synchronous functions that can be used to initiate the transfer directly. These functions internally create, populate, and submit a URB (“usb request block”), and wait for it to complete or time out.

The function prototypes are as follows:

int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
      __u8 requesttype, __u16 value, __u16 index, void *data,
      __u16 size, int timeout);

int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,
   void *data, int len, int *actual_length, int timeout);

int usb_interrupt_msg(struct usb_device *usb_dev, unsigned int pipe,
        void *data, int len, int *actual_length, int timeout);

2.3 Asynchronous Transfer Functions

When transferring with URB, it is asynchronous: you need to first allocate, construct, and submit a URB (“usb request block”), and when the transfer is complete, its callback function is called.

The key lies in filling the URB:

  • dev: who to transfer data with
  • pipe: which pipe to transfer data with
  • buffer: contains the data to be sent or used to receive the data to be read
  • data length
  • callback function

2.3.1 Allocate and Free URB

The function prototypes are as follows:

struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);

void usb_free_urb(struct urb *urb);

2.3.2 Allocate/Free DMA Buffer

When initiating USB transfer, data is stored in the buffer. This buffer can be a regular buffer or a DMA buffer.

For a regular buffer, a DMA buffer is temporarily allocated when submitting the URB:

  • When sending data: the function internally copies data from the regular buffer to the DMA buffer before submitting it to the USB controller
  • When reading data: the USB controller first sends data to the DMA buffer, and the function internally copies the data from the DMA buffer to the regular buffer
  • This adds an additional data copy, which is inefficient

We can directly use the DMA buffer, the function prototypes are as follows:

void *usb_alloc_coherent(struct usb_device *dev, size_t size, gfp_t mem_flags,dma_addr_t *dma);

void usb_free_coherent(struct usb_device *dev, size_t size, void *addr,dma_addr_t dma);

2.3.3 Fill URB

For control transfer, bulk transfer, and interrupt transfer, the following functions are available:

static inline void usb_fill_control_urb(struct urb *urb,
     struct usb_device *dev,
     unsigned int pipe,
     unsigned char *setup_packet,
     void *transfer_buffer,
     int buffer_length,
     usb_complete_t complete_fn,
     void *context);

static inline void usb_fill_bulk_urb(struct urb *urb,
         struct usb_device *dev,
         unsigned int pipe,
         void *transfer_buffer,
         int buffer_length,
         usb_complete_t complete_fn,
         void *context);

static inline void usb_fill_int_urb(struct urb *urb,
        struct usb_device *dev,
        unsigned int pipe,
        void *transfer_buffer,
        int buffer_length,
        usb_complete_t complete_fn,
        void *context,
        int interval);

If the URB uses a DMA buffer, then it is also necessary to set a flag to indicate this:

urb->transfer_dma = DMA address of buffer; // output parameter of usb_alloc_coherent
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

2.3.4 Submit URB

After constructing the URB, it needs to be submitted to the USB system to start the transfer.

int usb_submit_urb(struct urb *urb, gfp_t mem_flags);

2.3.5 Cancel URB

Submitted URB can be canceled, there are 2 functions:

  • usb_kill_urb: this is a synchronous function, it will wait for the URB to finish
  • usb_unlink_urb: this is an asynchronous function, it will not wait for the URB to finish, the USB controller driver will call its callback function
void usb_kill_urb(struct urb *urb);

int usb_unlink_urb(struct urb *urb);

Leave a Comment