Linux |
|||||||||||||||||||||
Device Driver Programming
One of the biggest role of any operating system is to manage and operate that are connected to the system (e.g, PC or Embedded system board etc). Operating System do these jobs using specific software called device driver. Personally to me, Operating System is a collection of device drivers. I know this would be a little bit misleading, but it is for sure that device driver would be biggest part of Operating System. Understanding the details of device driver would be the best way to understanding Operating System. One of the best way to understand 'something' is to make it on your own. This is the main motivation for me to study on Device Driver Programming.. at least it was the biggest driver for my initial study. I have gone through many documents from the internet, some free tutorials on YouTube and even from paid on-line lectures. Among all of these that I went through, the best one for me was the series of articles listed in the reference section in this page. I guess a lot of other tutorials that I found eleswhere seemed to be based on these articles.
Before you jump into programming, it would be a good idea to go through following pages to have general understandings of device drivers and the related tools/commands. I would suggest just read through and don't spend too much time on it even if there is things which doesn't look clear to you. Once you try programming and then go back to these pages, thing would get clearer.
What I write on this page is mostly to follow through the articles in the reference sections with a little bit of modification and a little bit of my comments, but in much compressed way so that I can refer to this page as a cheat sheet. You may use my note as a quick reference as well, but I strongly recommend you to read full contents of the articles (at least the first 5 or 6 articles) in the reference section.
This is just to show you the minimum code structure of device driver and procedure to load the driver to the system and unload it from the system. I will not explain much about the meaning of each line of code. Just follow this procedure and see if it works with your system. Once it works and you can get a general idea of how to load/unload the driver (module) and then try to understand the detailed meaning of source code and makefile.
In any directory on your system, create two files as shown below. $ ls -al
-rwxrwxr-x 1 sharetechnote sharetechnote 145 Jan 21 21:21 Makefile -rwxrwxrwx 1 sharetechnote sharetechnote 443 Jan 21 21:24 testdriver.c
Contents of these two files are shown as below.
Followings are short comments about the key functions and macros in this source.
NOTE : Make it sure that the following highlighted part is 'TAB' (not whitespace). If you put whitespace here, you would have error during compilation with make program. default: $(MAKE) -C $(KDIR) M=$(PWD) modules
Then build the files using a make program as shown below.
$ make
// Following is the result of make on my Ubuntu. I am putting this output for your reference. make -C /lib/modules/4.13.0-26-generic/build M=/home/sharetechnote/driver/ex1 modules make[1]: Entering directory '/usr/src/linux-headers-4.13.0-26-generic' CC [M] /home/sharetechnote/driver/ex1/testdriver.o Building modules, stage 2. MODPOST 1 modules CC /home/sharetechnote/driver/ex1/testdriver.mod.o LD [M] /home/sharetechnote/driver/ex1/testdriver.ko make[1]: Leaving directory '/usr/src/linux-headers-4.13.0-26-generic'
Now list the files in the directory and see what kind of files are created. Followings are the list of files created in my system. testdriver.ko is the binary file for the sample driver.
$ ls -al
total 168 drwxrwxrwx 3 sharetechnote sharetechnote 4096 Jan 21 21:24 . drwxrwxr-x 4 sharetechnote sharetechnote 4096 Jan 21 21:11 .. -rw-rw-r-- 1 sharetechnote sharetechnote 243 Jan 21 21:18 .helloworld.ko.cmd -rw-rw-r-- 1 sharetechnote sharetechnote 28315 Jan 21 21:18 .helloworld.mod.o.cmd -rw-rw-r-- 1 sharetechnote sharetechnote 28207 Jan 21 21:18 .helloworld.o.cmd -rwxrwxr-x 1 sharetechnote sharetechnote 145 Jan 21 21:21 Makefile -rwxrwxr-x 1 sharetechnote sharetechnote 145 Jan 21 21:18 Makefile~ -rw-rw-r-- 1 sharetechnote sharetechnote 47 Jan 21 21:24 modules.order -rw-rw-r-- 1 sharetechnote sharetechnote 0 Jan 21 21:24 Module.symvers -rwxrwxrwx 1 sharetechnote sharetechnote 443 Jan 21 21:24 testdriver.c -rwxrwxrwx 1 sharetechnote sharetechnote 384 Jan 18 23:13 testdriver.c~ -rw-rw-r-- 1 sharetechnote sharetechnote 3824 Jan 21 21:24 testdriver.ko -rw-rw-r-- 1 sharetechnote sharetechnote 243 Jan 21 21:24 .testdriver.ko.cmd -rw-rw-r-- 1 sharetechnote sharetechnote 542 Jan 21 21:24 testdriver.mod.c -rw-rw-r-- 1 sharetechnote sharetechnote 2536 Jan 21 21:24 testdriver.mod.o -rw-rw-r-- 1 sharetechnote sharetechnote 28315 Jan 21 21:24 .testdriver.mod.o.cmd -rw-rw-r-- 1 sharetechnote sharetechnote 2136 Jan 21 21:24 testdriver.o -rw-rw-r-- 1 sharetechnote sharetechnote 28207 Jan 21 21:24 .testdriver.o.cmd drwxrwxr-x 2 sharetechnote sharetechnote 4096 Jan 21 21:24 .tmp_versions
Now you can load the driver file into the system using insmod command as shown below. $ sudo insmod testdriver.ko
Then you can confirm whether it is successfully loaded or not using lsmod command as shown below. $ lsmod | grep 'testdriver'
testdriver 16384 0
Now you can unload the driver file from the system using rmmod command as shown below. $ sudo rmmod testdriver
Confirm that the driver is not found from the lsmod output. $ lsmod | grep 'testdriver'
Now you can confirm on what has happended during the load and unload process of the driver using dmesg command as shown below. $ dmesg ..... [16172.548779] Test Driver Initialized [16211.160144] Test Driver Being Removed
This example showed the minimized code just for initializing/closing and loading/unloading the driver. However, with this code, the driver is not really running as a device driver. To confirm this, you may try following $ cat < /proc/device | grep 'test' You wouldn't get any output for this meaning that the driver is not really running now. In next example, we will see how we can register it as a running process.
NOTE : For next tutorial, I would suggest you do followings (manual cleanup) before you move to the next section. $ sudo rmmod testdriver
The Makefile in previous section was written to be as simple as possible. But it builds the program only when there is some changes in the source code. However, sometimes you may see following error when you try to load the module (insmod) again. insmod: ERROR: could not insert module xxxxx.ko: Invalid module format One possible cause for this error is that your kernel has been upgraded after you built the module. If the cause of the problem is due to the kernel upgrade, the easy fix is that delete the all intermediate files and rebuid the code and than try 'insmod'.
With the following Makefile, you can easily removes all the intermediate files and rebuild the code.
Then try followings to remove the intermediate files from previous build. $ make clean Then try followings to build the code. $ make
Now let's revise our code as follows. The part highlighed in red is the new code that are added in this section.
Now build(make) the code and then load the driver file into the system using insmod command as shown below. $ sudo insmod testdriver.ko
Then you can confirm whether it is successfully loaded or not using lsmod command as shown below. $ lsmod | grep 'testdriver'
testdriver 16384 0
Now you are getting a process assigned to this driver as shown below. $ cat /proc/devices | grep "Test" 244 TestDriver <-- this is from alloc_chrdev_region(&first, 0, 3, "TestDriver") // By this function, Kernel load the driver and assigns a Major number and return the number // to the parameter 'first'. The Kernel assigns the name 'TestDriver' to the driver. // this function request kernel 3 minor numbers starting from number 0. // If you are not familiar with Major / Minor number, see this topic.
Now you can confirm on what has happended during the load and unload process of the driver using dmesg command as shown below. $ dmesg ..... [58699.029689] Test Driver Initialized [58699.029690] <Major, Minor>: <244, 0> <-- Kernel assigns 244 as Major number of the device // assigns 0 as the first minor number of the device
Now let's check if this device is created in /dev directly. You can check on this as follows. $ ls -l /dev | grep '244' You would not see any output for this command meaning that the 'testdriver' is not created in /dev. In the next section, we will see how to make this happen.
NOTE : For next tutorial, I would suggest you do followings (manual cleanup) before you move to the next section. $ sudo rmmod testdriver
Now let's revise our code as follows. The part highlighed in red is the new code that are added in this section.
Now build(make) the code and then load the driver file into the system using insmod command as shown below. $ sudo insmod testdriver.ko
Then you can confirm whether it is successfully loaded or not using lsmod command as shown below. $ lsmod | grep 'testdriver'
testdriver 16384 0
Now you are getting a process assigned to this driver as shown below. $ cat /proc/devices | grep "Test" 244 TestDriver <-- this is from alloc_chrdev_region(&first, 0, 3, "TestDriver")
Now let's check if this device is created in /dev directly. You can check on this as follows. $ ls -l /dev | grep '244'
crw------- 1 root root 244, 0 Jan 22 12:21 testdriver // this result is obtained by device_create(cl, NULL, first, NULL, "testdriver")
NOTE : For next tutorial, I would suggest you do followings (manual cleanup) before you move to the next section. $ sudo rmmod testdriver
Completing the Dummy Driver Template
Now let's revise our code as follows. The part highlighed in red is the new code that are added in this section.
Now build(make) the code and then load the driver file into the system using insmod command as shown below. $ sudo insmod testdriver.ko
Then you can confirm whether it is successfully loaded or not using lsmod command as shown below. $ lsmod | grep 'testdriver'
testdriver 16384 0
Now you are getting a process assigned to this driver as shown below. $ cat /proc/devices | grep "Test" 244 TestDriver <-- this is from alloc_chrdev_region(&first, 0, 3, "TestDriver")
Now let's check if this device is created in /dev directly. You can check on this as follows. $ ls -l /dev | grep '244'
crw------- 1 root root 244, 0 Jan 22 12:21 testdriver // this result is obtained by device_create(cl, NULL, first, NULL, "testdriver")
NOTE : now I will do some read/write test for the driver, but to do this you need to get root authority for the system. In some Linux, you can obtain root authority by following command and type in password $ su But in my Ubuntu, su command always returned 'Authentication failure' error even though I typed in the correct password. With some Googling, I learned that switching to root with su command is blocked by default and we need to use following command. It worked. $ sudo -i
Now let's check what will happen when we write something to the device driver we implemented. You can write something to the driver using echo command as below.
$ echo 'test write' > /dev/testdriver
You wouldn't see anything with this command because there is nothing returned from the driver to user level. But you can at least check on the result of printk() using dmesg command as shown below.
$ dmesg | tail -10 ... [93260.880872] Driver: open() [93260.880890] Driver: write() [93260.880894] Driver: close()
Now let's check what will happen when we read from the device driver we implemented. You can write something to the driver using echo command as below. $ cat /dev/testdriver
You wouldn't see anything with this command because there is nothing returned from the driver to user level. But you can at least check on the result of printk() using dmesg command as shown below. $ dmesg | tail -10 ... [93260.880872] Driver: open() [93260.880890] Driver: write() [93260.880894] Driver: close()
Accessing the driver from a User Level C Program
In most case, the major usage of device driver is to provide an interface software between Linux kernel and user level program. So in this section, I would expand the driver code a little bit further and show you how to verify the functionaliry with a user level C program. This code still does not get any hardware involved as most of device driver do, but this can be a minimum template for writing device driver for any hardware.
Now let's revise our code as follows. The part highlighed in red is the new code that are added in this section.
Now build(make) the code and then load the driver file into the system using insmod command as shown below. $ sudo insmod testdriver.ko
Then you can confirm whether it is successfully loaded or not using lsmod command as shown below. $ lsmod | grep 'testdriver'
testdriver 16384 0
Now you are getting a process assigned to this driver as shown below. $ cat /proc/devices | grep "Test" 244 TestDriver <-- this is from alloc_chrdev_region(&first, 0, 3, "TestDriver")
Now let's check if this device is created in /dev directly. You can check on this as follows. $ ls -l /dev | grep '244'
crw------- 1 root root 244, 0 Jan 22 12:21 testdriver // this result is obtained by device_create(cl, NULL, first, NULL, "testdriver")
Now we have a very important steps to do. For a user level program to get access to the device driver, the driver file should get proper properties. The simplest way to do it is to change mode as shown below.
$ sudo chmod 777 /dev/testdriver
Confirm that the driver file gets the proper attribute as below.
$ ls -al /dev/testdriver
crwxrwxrwx 1 root root 244, 0 Jan 23 17:32 /dev/testdriver
Now write a user level C programming (ordinary C programming you would usually wright) as bellow.
Then compile the code using gcc as follows (In Ubuntu that I am using, gcc was installed by defaut. But you can install it manually if gcc compiler is not installed on your system).
$ gcc rwtest.c -o rwtest
If the code is compiled OK, run the program as below.
$ ./rwtest
Hello World ! <-- this is done by read() which is calling testdriver_read() in driver program <-- you don't see any result of write() which is calling testdriver_write() in driver program
You don't see any result for write() function, it is because in the driver program 'testdriver_write()' write something using printk(). To confirm on the result of write() function you need to use dmesg command as shown below.
$ dmesg | tail -10
[ 5561.387036] Test Driver Initialized [ 5561.387037] <Major, Minor>: <244, 0> [ 5618.999309] Driver: open() [ 5618.999312] Driver: read() - Hello World ! [ 5618.999343] Driver: write() - Hello [ 5618.999344] Driver: close()
Changing Permission to dev/testdriver
Now let's revise our code as follows. The part highlighed in red is the new code that are added in this section.
Now build(make) the code and then load the driver file into the system using insmod command as shown below. $ sudo insmod testdriver.ko
Then you can confirm whether it is successfully loaded or not using lsmod command as shown below. $ lsmod | grep 'testdriver'
testdriver 16384 0
Now you are getting a process assigned to this driver as shown below. $ cat /proc/devices | grep "Test" 244 TestDriver <-- this is from alloc_chrdev_region(&first, 0, 3, "TestDriver")
Now let's check if this device is created in /dev directly. You can check on this as follows. $ ls -l /dev | grep '244'
crw-rw-rw- 1 root root 244, 0 Jan 22 12:21 testdriver // this result is obtained by device_create(cl, NULL, first, NULL, "testdriver") // rw-rw-rw- is set by testdriver_uevent()
Now write a user level C programming (ordinary C programming you would usually wright) as bellow.
Then compile the code using gcc as follows (In Ubuntu that I am using, gcc was installed by defaut. But you can install it manually if gcc compiler is not installed on your system).
$ gcc rwtest.c -o rwtest
If the code is compiled OK, run the program as below.
$ ./rwtest
Hello World ! <-- this is done by read() which is calling testdriver_read() in driver program <-- you don't see any result of write() which is calling testdriver_write() in driver program
You don't see any result for write() function, it is because in the driver program 'testdriver_write()' write something using printk(). To confirm on the result of write() function you need to use dmesg command as shown below.
$ dmesg | tail -10
[ 5561.387036] Test Driver Initialized [ 5561.387037] <Major, Minor>: <244, 0> [ 5618.999309] Driver: open() [ 5618.999312] Driver: read() - Hello World ! [ 5618.999343] Driver: write() - Hello [ 5618.999344] Driver: close()
Reference :
[ 1] Device Drivers, Part 1: Linux Device Drivers for Your Girl Friend [ 2] Device Drivers, Part 2: Writing Your First Linux Driver in the Classroom [ 3] Device Drivers, Part 3: Kernel C Extras in a Linux Driver [ 4] Device Drivers, Part 4: Linux Character Drivers [ 5] Device Drivers, Part 5: Character Device Files — Creation & Operations [ 6] Device Drivers, Part 6: Decoding Character Device File Operations [ 7] Device Drivers, Part 7: Generic Hardware Access in Linux [ 8] Device Drivers, Part 8: Accessing x86-Specific I/O-Mapped Hardware [ 9] Device Drivers, Part 9: I/O Control in Linux [10] Device Drivers, Part 10: Kernel-Space Debuggers in Linux [11] Device Drivers, Part 11: USB Drivers in Linux [12] Device Drivers, Part 12: USB Drivers in Linux Continued [13] Device Drivers, Part 13: Data Transfer to and from USB Devices [14] Device Drivers, Part 14: A Dive Inside the Hard Disk for Understanding Partitions [15] Device Drivers, Part 15: Disk on RAM — Playing with Block Drivers [16] Device Drivers, Part 16: Kernel Window — Peeping through /proc [17] Device Drivers, Part 17: Module Interactions [18] Writing a Linux Kernel Module — Part 1: Introduction [19] Writing a Linux Kernel Module — Part 2: A Character Device [20] Writing a Linux Kernel Module — Part 3: Buttons and LEDs [21] Linux Device Drivers Training 03, Modules from Multiple C Files, MODULE_LICENSE(), __init (YouTube) [22] Linux Device Drivers Training 04, Exporting Symbols (YouTube) [23] Linux Device Drivers Training 05, Module Parameters(YouTube) [24] Linux Device Drivers Training 06, Simple Character Driver (YouTube) [25] Yocto Linux #4 - Kernel Module read, write, ioctl (YouTube)
|
|||||||||||||||||||||