Skip to content

GNU/Linux & USB - Hello World Linux HID device driver

Learn how to write a hello world Linux HID device driver for a tinyUSB device on Raspberry Pi Pico using kernel space programming.

Key Takeaways

  • USB devices are uniquely identified by vendor and product IDs, essential for driver compatibility.
  • Linux device drivers require defining compatible devices, probe and remove functions, and proper registration.
  • Kernel space programming allows direct interaction with USB devices beyond user space APIs.
  • Using kernel logging helps verify driver behavior during device connection and removal.
  • The approach works on any GNU/Linux system with USB support, regardless of architecture.

What the video covers

  • Introduction to converting Raspberry Pi Pico to an HID device using tinyUSB.
  • Transition from user space to kernel space for device driver development.
  • Identification of USB devices by unique vendor and product IDs.
  • Steps to write a Linux device driver: declare compatible devices, implement probe and remove functions, bundle driver info, and register the driver.
  • Implementation details including header includes, device ID struct, and driver struct.
  • Explanation of probe and remove functions with kernel log messages.
  • Use of macros like HID_USB_DEVICE and module_device_table for device compatibility.
  • Compiling the driver module with a Makefile and testing by loading/unloading the driver.
  • Observing kernel logs to verify probe and remove function calls.
  • Behavior of HID raw device creation when driver is removed.

Answers

Questions about this video

What is the purpose of the probe function in a Linux device driver?

The probe function is called when a compatible device is added to the system. It typically allocates resources and initializes the device, returning zero on success or a negative error code if something goes wrong.

How does the Linux kernel identify USB devices for driver compatibility?

The kernel uses a unique combination of vendor ID and product ID to identify USB devices. Drivers declare compatible devices using these IDs to ensure proper matching.

Why is the remove function important in a Linux HID device driver?

The remove function is called when a compatible device is removed from the system. It frees resources allocated during probing and ensures clean driver unloading without errors.

Full Transcript — Download SRT & Markdown

00:00
Speaker A
Welcome to a new episode of my series about GNU/Linux and USB. In my previous videos, I already showed you how we can convert this Raspberry Pi Pico to an HID device by using tinyUSB and how to access the USB device from a host from user space by using the HID raw API. So, now it's time to leave the user space and head into kernel space. And in today's video, I will show you how we can write the hello world HID device driver for our tinyUSB device running on this Raspberry Pi Pico. So, let's go. I will develop this on my Raspberry Pi here, but of course, you can also implement this on any other PC. Nevertheless, if it's x86 or ARM architecture, it should work on any GNU/Linux system with USB support. Okay, so first let's take a look at the connected USB devices, and you can see this device here with the vendor ID coffee and the product ID 4004 is our tinyUSB device. Our tinyUSB HID device. And for this device, we want to write the driver. The cool thing about USB is every device is or can be identified over its unique combination of a product and a vendor ID. So, all these combinations are unique for one type of device. And this is what we need in the driver to recognize it.
00:16
Speaker A
Okay, so let me navigate into my programming folder and let's create a folder. I will call tinyUSB HID tinyUSB.
00:28
Speaker A
And in here, I want to implement my driver. So, the name of the driver should be also HID tinyUSB.c.
00:43
Speaker A
And in this file, I will implement my driver. And now, let's go through the steps which are required to write any Linux device driver, nevertheless if it's for an HID device or for a PCI Express device or an I2C device, it doesn't matter. These steps are always the same. So, the first thing we have to do is we have to name the compatible devices which are supported by the driver.
01:01
Speaker A
Then, we have to implement a probe and a remove function. The probe function is called when we are adding a compatible device to the system. The remove function is called if you're removing a compatible device from the system.
01:21
Speaker A
And at the end, we need to bundle all the information, the compatible devices, the probe, the remove function, some other information like a driver's name and a driver struct, and then we need to register this driver at the operating system. And these are all the steps which are required to write a Linux device driver.
01:28
Speaker A
So, now let's take a look how we can implement these steps for our USB HID device.
01:38
Speaker A
First thing I need to do is I need to add some includes, and here I will include the kernel header HID.c.
01:45
Speaker A
Then, let's declare our TinyUSB HID product vendor ID, which was coffee, and the product ID, which was 4004.
02:00
Speaker A
Okay. And now, we need to add all the compatible devices. Therefore, I will use a list from the type struct HID device ID, and I will call this HID or TinyUSB HID IDs. And important, this is a link because a driver can be compatible with more than just with one device. And the first element here, I will create a valid entry with the macro HID_USB_device, TinyUSB vendor ID, TinyUSB product ID.
02:12
Speaker A
Okay. And then at the end I always need to add an empty element which tells the Linux kernel, "Okay, now I have reached the end of the list." And then I will use the macro module device table to add our new compatible list to the list of all HID devices. So, for us, we are only building a standalone Linux kernel module, this would not be necessary, but if you're compiling the module together with your kernel, at the end you will get a list which tells the kernel where to find a driver for a device. When the device is detected by the system, the kernel can automatically load the correct driver. And that's what this macro is for.
02:24
Speaker A
Okay, so the first step is complete. We have declared all the compatible devices. Next step is to add a probe and a remove function.
02:36
Speaker A
So, let's start with the probe function, which I will call tiny USB HID probe.
02:42
Speaker A
The probe function has two arguments. The first one is from the type HID device and I will call it H dev, and the second argument is a pointer from the type HID device ID. I will call it IDs. And this pointer points to the compatible device in this compatible device table. So, in our case, we just have one compatible device, so this pointer will always point to this entry here. But if we would have multiple devices, the pointer will point to the corresponding compatible device for which the probe function was called. And the cool thing is here, we could also add some device-specific data, and this data can be retrieved over this pointer. That's the whole reason why we are passing this pointer to this struct here. But I won't use it.
02:49
Speaker A
And as a return value, we have an integer because normally in the probe function, we are allocating resources and interrupts which could go wrong. So in case an error occurred, we want to return a negative error code which tells the user space what happened or what went wrong. And when we are returning zero, we're indicating okay, probing was successful.
02:57
Speaker A
And all I will do in the probe function is I will print one line to the kernel's log with the function dev_info. The first argument is a pointer to our struct device which is in the staff field of the HID device struct. And the second argument is what we want to print out.
03:13
Speaker A
Device is probed. Okay, yep, and that's basically it. Okay, the next thing we need to do is we need to implement the remove function.
03:35
Speaker A
The remove function has no return value because in the remove function, we're only freeing resources. There is nothing that can go wrong.
03:54
Speaker A
And one argument which is also pointer to our HID device. And in here, let's also just print one line to the kernel's log.
04:17
Speaker A
Device is removed. Okay. So now we have implemented the probe and the remove functions and we have our compatible devices. Now we have to bundle this information in the driver struct. Herefore, let's declare a struct from the type variable from the type struct HID driver. And in here, we can bundle the information. So we can give it a name.
04:33
Speaker A
We can, we have to pass the pointer to the list with compatible devices. We have to pass a pointer to the probe function.
04:46
Speaker A
And we have a pointer, we have to pass a pointer to the remove function.
04:55
Speaker A
And this way we are bundling all the driver's data in this driver struct here.
05:01
Speaker A
Okay. And now the last step is we need to register the driver. This can be done with the macro module HID driver and we have to pass the driver for which we want to, or which we want to register to it.
05:17
Speaker A
Yeah. And now we would be almost done. The last thing is we need to add a license and some additional meta information to this module.
05:32
Speaker A
So, the author will be me. And let's also pass a description. Hello world Linux HID device driver for tiny USB HID device.
05:48
Speaker A
Okay. Good. And this should be the driver for a hello world HID device driver.
05:54
Speaker A
Next thing I need is I need to write a makefile, but here I'm lazy and I will copy one over from my Linux driver tutorials folder.
06:08
Speaker A
And I will adapt it so it will compile my tiny USB HID or HID tiny tiny USB device or driver. Okay. So, let's see if I can compile the driver or if I made some mistakes.
06:17
Speaker A
Yep, this of course should be a header file, not a C file. HID. So, let's fix this.
06:30
Speaker A
Ah. But now we should be able to compile this module. Yep, it's looking good.
06:37
Speaker A
So, let me fire up tmux to spawn a second terminal in which we will follow the kernel's log. And then let's load our driver.
06:47
Speaker A
So, the driver is loaded and we can see TinyUSB HID device is probed. And now when I go here and when I'm disconnecting the device, we can see the remove function is called. If we're connecting the device again, the probe function will be called again. And now let's unload the module. And if we're unloading the module, the remove function is called again. And this is also interesting, you can see now an HID raw device is created for this device because an HID raw device is only created if for the device there is no corresponding driver, which is the case when we're removing the driver.
06:56
Speaker A
Ok.
07:07
Speaker A
Uh device is removed. Okay. So now we have implemented the probe and the remove functions and we have our compatible devices. Now we have to bundle this information in the driver struct. Herefore, let's declare a struct from the type uh uh
07:24
Speaker A
variable from the type struct HID driver. And in here, we can bundle the information. So we can give it a name.
07:37
Speaker A
We can we have to pass the pointer to the list with compatible devices. We can We have to pass a pointer to the probe function.
07:53
Speaker A
And we have a pointer We have to pass a pointer to the remove function.
07:58
Speaker A
And this way we are bundling all the drivers data in this driver struct here.
08:06
Speaker A
Okay. And now the last step is we need to register the driver. This can be done with the macro module HID driver and we have to pass the driver for which we want to or which we want to register to it.
08:21
Speaker A
Yeah. And now we would be almost done. The last thing is we need to add a license and some additional meta information to this module.
08:31
Speaker A
So, the author will be me. And let's also pass a description. Hello world Linux HID device driver for tiny USB HID device.
08:57
Speaker A
Okay. Good. And this should be the driver for a hello world um HID device driver.
09:04
Speaker A
Next thing I need is I need to write a make file, but here I'm lazy and I will copy one over from my Linux driver tutorials folder.
09:12
Speaker A
And I will adapt it so it will compile my tiny USB HID or HID tiny tiny USB device or driver. Okay. So, let's see if I can compile the driver or if I made some mistakes.
09:30
Speaker A
Yep, this of course should be a header file, not a C file. Um HID So, let's fix this.
09:42
Speaker A
Ah. But now we should be able to compile this module. Yep, it's looking good.
09:50
Speaker A
So, let me fire up tmux to spawn a second terminal in which we will follow the kernel's log. And then let's load our driver.
10:02
Speaker A
So, the driver is loaded and we can see Tinus BHID device is probed. And now when I go here and when I'm disconnecting the device, we can see the remove function is called. If we're connecting the device again,
10:16
Speaker A
the probe function will be called again. And now let's unload the module. And if we're unloading the module, the remove function is called again. And this is also interesting, you can see now an HID raw device is created for
10:31
Speaker A
this device because an HID raw device is only created if for the um device there is no corresponding driver, which is the case when we're removing the driver.
10:42
Speaker A
Okay, cool. So, that's how to implement the Hello World HID device driver for Linux. I hope you've enjoyed the video and learned something. In case you want to support my work, you can buy me a coffee on buymeacoffee.com/linuxforlinux.
10:54
Speaker A
So, thanks for watching and goodbye.
Topics:Linux device driverHID devicetinyUSBRaspberry Pi PicoUSB device driverkernel space programmingprobe functionremove functionLinux kernel moduleUSB vendor ID

Get More with the SozAI App

Transcribe recordings, audio files, and YouTube videos — with AI summaries, speaker detection, and unlimited transcriptions.

Or transcribe another YouTube video here →