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
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
- 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 *);
// #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:-
- 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
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.
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)