Monday, June 23, 2014

Basics of Linux Device Drivers

We can define a device driver as a "Specific code that will define the behavior of a device connected to our system" for eg. the driver for touch screen will have code that will define the working of touchscreen and a driver for camera will have all the functioning of camera, similarly for other devices too. A driver provides a software interface to hardware devices, enabling operating systems and other computer programs to access hardware functions without needing to know precise details of the hardware being used.

So the very next question that would come into our mind is-"how does the driver communicate with the device?"

The communication is through the computer bus communication subsystem( say I2C protocol) to which the hardware is connected.

Two more point worth mentioning is that -
 1. The drivers are hardware dependent , for eg. the driver for an accelerometer sensor would be drastically different from the drivers for camera as both the devices are used for different purposes. 

2. The drivers are operating system specific (OS), for eg. the same camera driver for Linux OS would be considerably different from that for Windows OS as different OS exposes different APIs for middlewares. 

Device drivers help high level application programmers to not worry about the specifications of the hardware, they just need to know the API that can be used to communicate to the driver.


For eg.  a high-level application for interacting with a serial port may simply have two functions for sending data-func1 and for receiving data func2.. At a lower level, a device driver implementing these functions would communicate to the particular serial port controller installed on a user's computer. 

Some drivers get loaded automatically when the system starts and some drivers load when we actually insert the device into the system. Almost all of us have seen the "installing the device software" notification in windows when we insert any new device into our system during runtime.


Linux allows the drivers to be loaded/unloaded on runtime also , thats why the drivers are also known as loadable modules or just modules. Due to this very useful feature Linux OS is used in servers also so that required modules can be loaded and unloaded at runtime and  we don't need to restart the system every time.


Drivers can run in both user mode as well as in kernel mode. The main advantage of running in user mode is that if the driver crashes it won't crash the kernel. On the other hand, user/kernel-mode transitions usually impose a considerable performance overhead, thereby prohibiting user-mode drivers for low latency and high throughput requirements.



There are broadly three kinds of device drivers in Linux-


1. Character Device Drivers- eg. Keyboard drivers, camera drivers, sensor drivers- these drivers work on devices which transmit data per byte.


2. Block Device Drivers-  eg. USB, Hard Drive drivers- these drivers work on devices that transmit block of data.

3. Network Device Drivers-  These the the hardware used by networking hardwares , these are similar to block device drivers in a sense that they work on packets but they have some differences too which we will see later.


Since the basics of device drivers is clear so, lets begin by writing a simple loadable module . As we write we will understand the stuffs required to write a device driver .

It is said that to begin any programming chapter we must write a hello world program otherwise something bad might happen to us ,so lets start with it.
Hello world program-
----------------------------------------------------------------------------------------
#include<linux/init.h>
#include<linux/module.h>

MODULE_LICENSE("GPL");

static int hello_init(void)
{
printk(KERN_ALERT "Hello world");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodby");
}

module_init(hello_init);
module_exit(hello_exit);
--------------------------------------------------------------------------------------------------
Lets write this program in vim editor and save as hello.c.



Now, lets see how can we compile and run this program. 
To compile a kernel module we need to create a makefile for the same, and we keep the make file in the same directory as the file else we will have to give the full path of makefile.

Makefile for the code shown above would be 
--------------------------------------------------------------
obj-m +=hello.o
all:
           make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
           make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
----------------------------------------------------------------

obj-m shows that we want to build this program as a module.

 

Makefiles are a unique species in its own, in a sense that we have to follow some strict guidelines otherwise it won't work.The two very important consideration that we need to take care of are- 1. while saving the Makefile its name should have capital M and not small m.  2. make word after all: and clean: should be shifted by one tab from the left end.

The module is compiled and built by giving make command in the same directory (test directory here) as the hello.c and Makefile we can generate hello.ko file.(ko means kernel object).



we can see that hello.ko file got generated.




Lets try to understand the structure of this program-

1. Like all other programs that we have written, here we need to include some headers. These header files live in /include/linux folder. init.h header file is used to start the init process while module.h file is used to add module capability to this program.

2. The next line is used to specify the license that we are using for this program- it's GPL ( GNU Public License).

3. The given program is written in user space. To insert this program into kernel space as a module we give insmod command.
  4. The command would look like insmod hello.ko (hello.ko is the kernel object file gerenrated when we compile this program as a module). 




After inserting the module in Kernel space we can see if it is present or not by giving lsmod command.It is showing in my system-







 5.Similarly rmmod hello.ko command is used to remove the module from kernel space.



If I perform lsmod command I don't see hello modules listed there.

6. When we give insmod hello.ko command the initialization function hello_init gets called and our module gets loaded into kernel space and  when we give rmmod hello.ko command the exit function hello_exit gets called and the module is removed from the kernel space.

7. printk is the kernel space equivalent of user space function printf.

8. KERN_ALERT is a type of kernel log levels( higher the log level more logs will appear while the program runs). We will see other log levels in advanced topic.

You must be wondering where did those logs go that were supposed to get printed when we insert and remove modules. As I told you those are kernel logs thats why they get stored in Kernel log buffer which you can see by giving dmesg command.

 


It is advisable for you to perform all these steps on your linux system.


Initialization and Shutdown Function

Lets delve more into these initialization and shutdown functions.



The module initialization function registers any facility offered by the module. 


The format of initialization function is:

static int __init initialization_function(void)
{
 /* Initialization code here */
}
module_init(initialization_function);

There are some points to be noted about the initialization function:-

1.Initialization functions should be declared static, since they are not meant to be visible outside the specific file.
2. The __init token  is a hint to the kernel that the given function is
used only at initialization time. The module loader drops the initialization function
after the module is loaded, making its memory available for other uses
3. There is a similar tag (__initdata) , this is for data used only during initialization. 
4. Use of __init and __initdata is optional, but it is worth the trouble.

Modules can register many different types of facilities, including different kinds of devices, filesystems, cryptographic transforms, and more. For each facility, there is a specific kernel function that accomplishes this registration. The arguments passed to the kernel registration functions are usually pointers to data structures describing the new facility and the name of the facility being registered. The data structure usually
contains pointers to module functions, which is how functions in the module body get called.

Lets understand this using one opensource file for a camera driver.

https://android.googlesource.com/kernel/samsung/+/529a92f923d124b02c1b2011313b11e2f82a6960/drivers/media/video/s5k4ecgx.c

Lets trace the order in which the functions will be called.

  1. module_init(v4l2_i2c_drv_init);
This function is called when the system boots. The kernel calls this function for all the modules present. We can manually call this by giving insmod command in userspace( which we have previously understood).


  1. static int __init v4l2_i2c_drv_init(void)
  2. {
  3. return i2c_add_driver(&v4l2_i2c_driver);
  4. }
Here we can see that inside the initialization function we are adding our camera sensor as an I2C client( I2C is a communication protocol). Below we can see the structure v4l2_i2c_driver. 


  1. static struct i2c_driver v4l2_i2c_driver = {
  2. .driver.name = S5K4ECGX_DRIVER_NAME,
  3. .probe = s5k4ecgx_probe,
  4. .remove = s5k4ecgx_remove,
  5. .id_table = s5k4ecgx_id,
  6. };
These facilities get registered in the kernel.

After all these steps for module_init is completed then we can say that our driver has been registered with Kernel.

There is special file in the Linux kernel called Device Tree file. This file contains the list of all the devices present in the system in hierarchical manner. We will understand the device tree file and I2C protocol in detail in next chapters.
During the kernel bootup after the modules are registered the kernel traverses the device tree file and if it sees the presence of a device with the same name as driver which we have already registered then the probe function gets called.

In the probe function (from the opensource code provided above) you can see that we are basically doing 2 things 1. getting platform data  2. Registering our camera driver as a V4L2 subdevice(V4L2 is a video capture and output API and driver framework for Linux Kernel).



Similarly we have exit/cleanup function which does the reverse of what we have done in initilazation function.

If our module doesn't define cleanup function then the kernel won't allow it to be unloaded from the system.

This function also has a fixed format and looks like

static void __exit cleanup_function(void)
{
 /* Cleanup code here */
}

module_exit(cleanup_function);


Some important points to be noted for cleanup function

1. Its return value is 0.
2. __exit modifier means that the code is used only for exit purpose of modules.

Advanced topic:




Virtual Device Drivers


Virtual device drivers are used where there is no physical hardware present to perform that operation but we need to give the OS a feel that the hardware is there. The best example would be IPC driver (inter processor communication). When we run an OS in virtualised environment then also we use virtual device drivers.  For example a virtual network adapter is used with a virtual private network, while a virtual disk device is used with iSCSI. One more good example for virtual device drivers can be Daemon Tools.



8 logs level for printk function

KERN_EMERG : This log level gets used only for emergency purpose.Ex: process stops.
KERN_ALERT: To attract the attention.
KERN_ERR :To display the error.
KERN_CRIT : To told about Critical serious hardware or software problem.
KERN_WARNING :warning message of danger.
KERN_NOTICE : Simple notice message.
KERN_INFO : Provide Information
KERN_DEBUG :To see log messages during debugging.


For any clarifications or if you have any suggestion please post comments below.

11 comments:

  1. Your blog is quite informative with covering basic aspect of linux, device driver.
    I feel the only thing this blog lack is a good template, templates with lots of color (in your blog books with red yellow colors) hamper our concentration and detract while focusing on subject. i would recommend you to use some simple white template like used by geeksforgeeks.org or duartes.org

    ReplyDelete
  2. Recently I got job on device drivers, I want to be perfect on basics first of all, after that I start drivers. Can you suggest me how to be perfect in basics.. ?? I am B.Tech EEE fresher

    ReplyDelete
  3. Hi Friedns,

    I found Below Blog for understanding important concepts of Linux.

    Booting Process nicely explain
    http://www.yoinsights.com/blog/step-by-step-red-hat-enterprise-linux-7-booting-process/

    Multipathing Concepts

    http://www.yoinsights.com/blog/linux-multipath-concepts-and-configuration/

    Steps for Basic Network Troubleshooting in RHEL
    http://www.yoinsights.com/blog/basic-network-troubleshooting-linux/

    Step by Step Process of Network Bonding Concepts and Configuration
    http://www.yoinsights.com/blog/configure-network-bonding-rhel-7/

    How to Recover deleted Files
    http://www.yoinsights.com/blog/recover-deleted-files-linux/


    I hope this information will be helpful.

    Cheers!!!

    ReplyDelete
  4. I am glad to find amazing information from the blog. Thanks for sharing the information about the Linux Device Drivers and for more information Please go trough the link : Linux training in Hyderabad

    ReplyDelete
  5. Hi all,
    after 'make' command I am getting below:

    root@linaro-alip:/home/test# make
    make -C /lib/modules/4.14.0-qcom-ifc6309-arm64/build M=/home/test modules
    make[1]: *** /lib/modules/4.14.0-qcom-ifc6309-arm64/build: No such file or directory. Stop.
    make: *** [makefile:4: all] Error 2

    Does any one knows, why ?
    I can see this "/lib/modules/4.14.0-qcom-ifc6309-arm64/build" exist. "build" is saying not a file or directory, pls see below
    root@linaro-alip:/lib/modules/4.14.0-qcom-ifc6309-arm64# cd build
    -bash: cd: build: No such file or directory
    root@linaro-alip:/lib/modules/4.14.0-qcom-ifc6309-arm64# cat build
    cat: build: No such file or directory
    root@linaro-alip:/lib/modules/4.14.0-qcom-ifc6309-arm64# ls
    build modules.builtin modules.devname modules.symbols.bin
    kernel modules.builtin.bin modules.order source
    modules.alias modules.dep modules.softdep
    modules.alias.bin modules.dep.bin modules.symbols
    root@linaro-alip:/lib/modules/4.14.0-qcom-ifc6309-arm64#

    ReplyDelete
  6. Thanks for sharing such an wonderful and useful post.
    also, visit Linux course in Pune

    ReplyDelete