🐸 pepe:curious

Writing your own USB driver 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:-

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

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.

int usb_register(struct usb_driver *driver);
void usb_deregister(struct usb_driver *);
// #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");

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;
    ...
};

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");

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

Then follow these steps:-

[   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

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

Now, try

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