How to write a linux device driver

Javier
6 min readAug 20, 2017

A beautiful guide for the Hello World of the device driver programming

Introduction

I am writing this guide to lend a hand to everyone who has some curiosity about the device driver programming or like to play a bit with the kernel and write some sample kernel modules.

Coding for the kernel is not the same that developing in user space. It has other implications. The kernel is the chunk of executable program that manages of the available resources of a machine (CPU, memory, file system, I/O, networking…) for all the programs running over it. The access to all of the resources, included the piece of HW for which you are writing the driver, is done by system calls (which is an API for the user programs to access to this resources). Hence the kernel is very structured and when you code in the kernel space you have to meet some special requirements and procedures. Otherwise you could end up in a kernel panic.

Scared poor penguin because of a kernel panic

But don’t worry In here we will go over it.

There are plenty of others guides out there. I have gone through some of them but, although their content is useful and accurate, the format is a bit old and the explanations usually are very light since it assumes you have a considerable knowledge about how the kernel works.

Prerequisites

Before start coding in your favorite editor you will need:

Kernel headers

The kernel headers for your current kernel: normally you won’t have it on your system unless you have compiled your own kernel or you have an insane liking for crashing your system and repairing it. To install them issue as root or with sudo and according to your linux distro:

# Arch, my fav :)
$ pacman -S linux-headers
# Debian & Ubuntu
$ apt-get install linux-headers-$(uname -r)

After a successfully install you can find them in

/usr/lib/modules/$(uname -r)/build/include/linux 

The $(uname -r) is a bash command that will expand to your current kernel version.

Editor

I like Atom with some C plugins for small projects and testing, it is fast to install and the plugins are easily available and installable.

Regarding to the C spelling checker, most of the editors won’t recognize the #include <linux/...> so I recommend to copy the Linux to the development directory just to make happy the C spelling checker of our editor. Assuming you are inside your development directory:

cp -r /usr/lib/modules/$(uname -r)/build/include/linux .

The program

This is the hello world of the device driver programming. Note that the code is not mine, you can find it on internet in several places. So in your development directory create a file called myDriver.c with this content:

#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
static int hello_init(void){
printk(KERN_ALERT "Hello, world\n");
return 0;
}
static void hello_exit(void){
printk(KERN_ALERT "Goodbye, cruel world\n");
}
module_init(hello_init);
module_exit(hello_exit);

Let’s break it down, this module basically does:

  1. Load the libraries init.h and module.h. The first one provide macros for initialized data and the second defines the functions module_init() and module_exit()
  2. MODULE_LICENSE("GPL") it basically tells the kernel that this driver is under GPL license. If the module is licensed under a proprietary license the kernel will complain saying it is tainted. Read more here.
  3. hello_init(void) prints “Hello, world” in the kernel messages log with a log level of KERN_ALERT (this level is set this severe to make it visible in the logs, lesser server level may pass unnoticed)
  4. hello_exit(void) prints “Goodbye, cruel world” when called in the kernel log with same log level.
  5. module_init() tells the kernel which function to call when the module is loaded. Yeap, you guessed it right, it will be hello_init() ;)
  6. module_exit() defines which function to call when the module is removed. It is usually used for clean up all the things (memory reserved and stuff) you did in module_init()

Compiling

Unfortunately, compiling kernel modules isn’t as easy as gcc -W -Wall myDriver.c -o myDriver . To compile kernel modules, the proper approach is using kbuild. However this may lead into very complicated makefiles, so for the purposes of this guide we will use a very simplified makefile. Create a file called makefile and write on it:

obj-m += myDriver.oall:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Once you have this you should be able to see in your working directory:

$ ls
linux Makefile myDriver.c

Note that the linux folder is there only so the C spelling checker doesn’t complain. For compiling we just usemake . You will be able to see something like this:

$ make 
make -C /lib/modules/4.12.8-2-ARCH/build M=/path/to/your/working/directory modules
make[1]: se entra en el directorio '/usr/lib/modules/4.12.8-2-ARCH/build'
CC [M] /path/to/your/working/directory/myDriver.o
Building modules, stage 2.
MODPOST 1 modules
CC /path/to/your/working/directory/myDriver.mod.o
LD [M] /path/to/your/working/directory/myDriver.ko
make[1]: se sale del directorio '/usr/lib/modules/4.12.8-2-ARCH/build'

You may have guessed that my kernel version (4.12.8–2-ARCH) very likely won’t match yours, and the /path/to/your/working/directory/ will be substituted with your current path (also in the variable $PWD ). Now we are ready for loading our fantastic, great, feature rich module!! ;)

Loading and removing the module

For loading and removing the modules there are different tools: insmod, rmmod and modprobe. We are going to use the first two because are simpler. modprobe is more intelligent and includes many features such as loading an inserted module at next boot and many others, and it is intended more for module management rather than module development. In here we are going to use insmod for loading and rmmod for removing modules.

For loading the module, assuming you are in your working directory:

$ sudo insmod myDriver.ko

After issuing this command you will be able to see “Hello, world” at the end of the kernel messages log. For reading the kernel log type:

$ dmesg | tail
[11715.453239] . . .
[11715.944197] . . .
[11716.972902] . . .
[11716.996946] . . .
[11716.999696] . . .
[11717.001283] . . .
[11717.048479] . . .
[11717.049223] . . .
[11717.049305] . . .
[12149.377145] Hello, world

dmesg shows the kernel log and since it is too big we filter it to show only the last 10 lines with tail . At the end (and if nothing else is generating kernel log messages) you will be able to see your string.

You can also check how the module is effectively loaded into the kernel with lsmod or displaying the content of the /proc/modules file:

$ lsmod
Module Size Used by
myDriver 16384 0
hid_generic 16384 0
usbhid 45056 0
...
$ cat /proc/modules
myDriver 16384 0 - Live 0xffffffffc0c8b000 (O)
hid_generic 16384 0 - Live 0xffffffffc0c86000
usbhid 45056 0 - Live 0xffffffffc0c92000
...

Finally, and when you are run out of tears of happiness for having been able to load your first module you may remove it ;)

$ sudo rmmod myDriver

Note that if you press Tab to autocomplete after rmmod it will list all the possibles modules to remove, including yours. Also note that the module is removed by its name myDriver no by is module file name myDriver.ko . Now let’s check the kernel log again:

$ dmesg | tail
[11715.944197] . . .
[11716.972902] . . .
[11716.996946] . . .
[11716.999696] . . .
[11717.001283] . . .
[11717.048479] . . .
[11717.049223] . . .
[11717.049305] . . .
[12149.377145] Hello, world
[13368.510294] Goodbye, cruel world

At the end of the log, we can see how the module printed “Goodbye, cruel world” when it was removed.

Last words

I hope this little superficial tutorial has shed some light into how to get your first linux module loaded and removed.

I think that one of the barriers for starting to program device drivers is the complex set up you have to perform before starting to code/debug. I wish this guide will make the start up process a bit less rough for others.

--

--

Javier

AI Research Engineer in Deep Learning. Living between the math and the code. A philosophic seeker interested in the meaning of everything from quarks to AI.