DEV Community

Cover image for Writing USB driver for Linux in C
server side digest
server side digest

Posted on

Writing USB driver for Linux in C

How USB got detected when we plug it in

I am running Ubuntu in VM:- https://www.youtube.com/watch?v=O19mv1pe76M (How to setup on Mac m1)

When we plug in our USB, ever thought what happens. Well, whenever we connect devices like USB in our PCI slot (Peripheral component interface) it gets detected in kernel space by the device controller which doesn't depend on the driver existence.

Although, default drivers exist in most of the linux distros. After plugging the device, hardware controller driver do detect the USB and it translates the low level information for the higher layers adhering to the USB protocol.

This information is detected and passed towards generic USB core layer(usbcore) in kernel layer which completes the device detection.

These controller drivers are typically coming from these categories:-

  • EHCI (Enhanced Host Controller Interface) for USB 2.0.
  • XHCI (eXtensible Host Controller Interface) for USB 3.0 and later.
  • OHCI (Open Host Controller Interface) for older USB 1.1 systems.
  • UHCI (Universal Host Controller Interface) for certain USB 1.1 implementations.

Let's go in some more depth, how information is detected and retrieved

USB Driver

Install some of the things if not installed already

sudo apt get make
sudo apt-get install linux-headers-$(uname -r)
sudo apt install build-essential
sudo apt install gcc-13
sudo apt install g++-13
Enter fullscreen mode Exit fullscreen mode
  • Device Firmware Initialization: The PCI device's firmware writes its configuration data (like Vendor ID, BARs) into its PCI configuration space.
  • PCI Controller Enumeration: The PCI controller scans devices and collects this configuration data.
  • Kernel PCI Subsystem: The Linux kernel reads the configuration space data during boot or when a hot-plugged device is detected.
  • Device Drivers: The kernel matches the Vendor ID and Device ID with the appropriate driver and initializes it.

Note:- every driver, code, documentation and implementation can be referred in linux source code.

follow this to build linux system in local system for the reference:-
https://phoenixnap.com/kb/build-linux-kernel

We will be writing USB driver for our Sandisk Pendrive which will talk to the USB controller using the interface given in the linux.

  • To register and de-register the usb we have usbcore apis which are present in linux/usb.h
int usb_register(struct usb_driver *driver);
void usb_deregister(struct usb_driver *);
Enter fullscreen mode Exit fullscreen mode
// #include <stdio.h> Cant use them as they operate in user mode, in kernel mode we cant use them
#include <linux/usb.h>
#include <linux/module.h>

static struct usb_device *device;

static struct usb_device_id skel_table[] = {
    {USB_DEVICE(0x0781, 0x5567)},
    {}};
MODULE_DEVICE_TABLE(usb, skel_table);

static int skel_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
    printk(KERN_INFO "Pen drive probed\n");

    return 0;
}

static void skel_disconnect(struct usb_interface *interface)
{
    usb_put_dev(device);
    printk(KERN_INFO "Pen drive removed\n");
}

static struct usb_driver skel_driver =
    {
        .name = "usb_driver",
        .probe = skel_probe,
        .disconnect = skel_disconnect,
        .id_table = skel_table,
        .supports_autosuspend = 1,
};

static int __init usb_skel_init(void)
{
    int result;
    result = usb_register(&skel_driver);
    if (result < 0)
    {
        pr_err("usb registeration failed with %s\n", skel_driver.name);
        return -1;
    }
    printk(KERN_INFO "USB initialised\n");
    return 0;
}

module_init(usb_skel_init);

static void __exit usb_skel_exit(void)
{
    usb_deregister(&skel_driver);
}

module_exit(usb_skel_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("lovepreet");
MODULE_DESCRIPTION("USB pendrive registration driver");

Enter fullscreen mode Exit fullscreen mode

If getting
insmod: ERROR: could not insert module usb_driver.ko: Key was rejected by service

follow:- https://askubuntu.com/questions/762254/why-do-i-get-required-key-not-available-when-install-3rd-party-kernel-modules

Understanding more about usb structs (defined in linux/usb.h though) in sequence :-

struct usb_device
{
    ...
    struct usb_device_descriptor descriptor;
    struct usb_host_config *config, *actconfig;
    ...
};
struct usb_host_config
{
    struct usb_config_descriptor desc;
    ...
    struct usb_interface *interface[USB_MAXINTERFACES];
    ...
};
struct usb_interface
{
    struct usb_host_interface *altsetting /* array */, *cur_altsetting;
    ...
};
struct usb_host_interface
{
    struct usb_interface_descriptor desc;
    struct usb_host_endpoint *endpoint /* array */;
    ...
};
struct usb_host_endpoint
{
    struct usb_endpoint_descriptor  desc;
    ...
};
Enter fullscreen mode Exit fullscreen mode

Now we can use these hierarchical structs to print some more information about the USB

Complete code for the driver is:-

// #include <stdio.h> Cant use them as they operate in user mode, in kernel mode we cant use them
#include <linux/usb.h>
#include <linux/module.h>

static struct usb_device *device;

static struct usb_device_id skel_table[] = {
    {USB_DEVICE(0x0781, 0x5567)},
    {}};
MODULE_DEVICE_TABLE(usb, skel_table);

static int skel_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
    struct usb_host_interface *iface_desc;
    struct usb_endpoint_descriptor *endpoint;
    int i;
    iface_desc = interface->cur_altsetting;
    printk(KERN_INFO "Pen i/f %d now probed: (%04X:%04X)\n",
           iface_desc->desc.bInterfaceNumber,
           id->idVendor, id->idProduct);
    printk(KERN_INFO "ID->bNumEndpoints: %02X\n",
            iface_desc->desc.bNumEndpoints);
    printk(KERN_INFO "ID->bInterfaceClass: %02X\n",
            iface_desc->desc.bInterfaceClass);

    for(i = 0; i< iface_desc->desc.bNumEndpoints; i++){
        endpoint = &iface_desc->endpoint[i].desc;
        printk(KERN_INFO "ED[%d]->bEndpointAddress: 0x%02X\n",
                i, endpoint->bEndpointAddress);
        printk(KERN_INFO "ED[%d]->bmAttributes: 0x%02X\n",
                i, endpoint->bmAttributes);
        printk(KERN_INFO "ED[%d]->wMaxPacketSize: 0x%04X (%d)\n",
                i, endpoint->wMaxPacketSize,
                endpoint->wMaxPacketSize);
    }
    device = interface_to_usbdev(interface);

    return 0;
}

static void skel_disconnect(struct usb_interface *interface)
{
    usb_put_dev(device);
    printk(KERN_INFO "Pen drive removed\n");
}

static struct usb_driver skel_driver =
    {
        .name = "usb_driver",
        .probe = skel_probe,
        .disconnect = skel_disconnect,
        .id_table = skel_table,
        .supports_autosuspend = 1,
};

static int __init usb_skel_init(void)
{
    int result;
    result = usb_register(&skel_driver);
    if (result < 0)
    {
        pr_err("usb registeration failed with %s\n", skel_driver.name);
        return -1;
    }
    printk(KERN_INFO "USB initialised\n");
    return 0;
}

module_init(usb_skel_init);

static void __exit usb_skel_exit(void)
{
    usb_deregister(&skel_driver);
}

module_exit(usb_skel_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("lovepreet");
MODULE_DESCRIPTION("USB pendrive registration driver");

Enter fullscreen mode Exit fullscreen mode

Manually binding the USB with own driver instead of usb-storage driver

If you are not able to see the usb being attached with usb_driver and getting attached with usb-storage

lsusb -t
Enter fullscreen mode Exit fullscreen mode

Then follow these steps:-

  • plug pen drive
  • use sudo dmesg and see what is the usb sequence like
[   68.923193] systemd-journald[338]: Time jumped backwards, rotating.
[  113.120352] usb 1-5: new high-speed USB device number 3 using xhci_hcd
[  113.246983] usb 1-5: New USB device found, idVendor=0781, idProduct=5567, bcdDevice= 1.00
[  113.246989] usb 1-5: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[  113.246990] usb 1-5: Product: Cruzer Blade
Enter fullscreen mode Exit fullscreen mode

Here we can see that it is usb 1-5, so we have to use this and have to unbind usb-storage

run these commands

  • sudo su
  • sudo echo -n "1-5" > /sys/bus/usb/drivers/usb/unbind (eject the usb)
  • sudo echo -n "1-5" > /sys/bus/usb/drivers/usb/bind (connect/binds again)
  • echo -n "1-5" > /sys/bus/usb/drivers/usb-storage/unbind
  • Now restart the OS

Now, try

  • sudo insmod usb_driver.ko
  • plug in your pen drive and check sudo dmesg
  • You will be able to see the USB logs being probed and all

Interesting Fact:- Now USB drive will not show in the files to transfer data because our custom driver hasn't implemented data read and write yet.

USB driver testing

resources:-
https://sysplay.github.io/books/LinuxDrivers/book/Content/Part11.html
https://docs.kernel.org/driver-api/usb/writing_usb_driver.html

Top comments (0)