To start with writing a linux kernel driver, we need to know about some of the fundamentals. This post (or series of post) will deal with starting with a basic kernel module with writing Kconfig, IOCTL's. memory address (virtual & physical), dynamic memory allocation, mutex/spinlock way of synchronization, kernel data structure, sys/class interface of a module, etc.
#include <linux/module.h>
MODULE_LICENSE("GPL");
A module may carry a license which specify the rights of the user of the license. For an example some vendor could restrict the right up-to using the license, without modifying it.
MODULE_AUTHOR("Sourabh Das <sourabhemsec@outlook.com>");
MODULE_DESCRIPTION("Our First Character Driver");
#include <linux/module.h>
config DEMO_MOD
tristate "A kernel device driver for demonstration purpose"
default y
help
This is a demo kernel module.
1. Types of Linux kernel Drivers
Based on the access of amount of data, the Kernel drivers are classified into two types:
- Character Driver
- Block Driver
Character driver provides access of data only as a stream, generally of character i.e bytes. Block Driver on the other hand are addressable in device specific chunks (also called blocks).
Example of Character driver may be an I2C driver, which provides data at a byte at a time.
On the other hand a filesystem driver (say ext3, ext4) is a good example of a block driver where access of data is done on chunk basis like 1KB, 512MB etc.
Infact all the device driver that are neither filesystem related nor network related are one form of character driver.
Infact all the device driver that are neither filesystem related nor network related are one form of character driver.
In this article we will focus on writing a character driver.
2. Writing a very basic Kernel Driver
To start with writing the basic device driver, we need to know the entry point of the driver. An entry point is like a constructor, where we need to do all the necessary initialization required for the driver to work, like allocating memory, initialization some set of registers etc.
i) Module Entry-points
module_init(module_constructor)
int module_constructor(void);
module_exit(module_destructor)
void module_destructor(void);
The module_init/exit are required to initialize/deinitialize the module. The function pointer is the argument for both the Kernel Macro. Both are defined inside linux/module.h, thus the header must be included.
A module may carry a license which specify the rights of the user of the license. For an example some vendor could restrict the right up-to using the license, without modifying it.
MODULE_AUTHOR("Sourabh Das <sourabhemsec@outlook.com>");
MODULE_DESCRIPTION("Our First Character Driver");
This provides the information of owner of the module and description.
ii) Putting the codes altogether
#include <linux/module.h>
static int module_constructor(void) /* Constructor */
{
printk(KERN_INFO "Initializing demo module\n");
return 0;
}
static void module_destructor(void) /* Destructor */
{
printk(KERN_INFO "Deinitializing the module\n");
}
module_init(module_constructor);
module_exit(module_destructor);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sourabh Das <sourabhemsec@outlook.com>");
MODULE_DESCRIPTION("Our First Character Driver");
Note that printk is very likely (you can say big-brother) to printf (in standard C library), only syntactical difference being the macro (here KERN_INFO) specifies what kind of information it is. For example KERN_DEBUG specifies debug level message. The advantage of this kind of implementation (which is unlike printf) is that the unnecessary logs can be suppressed by setting the proper loglevel in the bootloader, that could ultimately helpful to reduce the kernel boot time.
iii) Makefile changes
Since my driver is under drivers/misc/demo.c, I will make changes inside the drivers/misc/Makefile in order to build the module:
obj-$(CONFIG_DEMO_MOD) += demo.o
Kindly note that the CONFIG_DEMO_MOD is the flag that could be enabled or disabled in order to tell the kernel build system that to compile demo.c or not.
iii) Writing the Kconfig file
A Kconfig file consist the description about the way the the kernel module could be builded (modular of static), the dependent modules and the descriptions. I have added the contents inside drivers/misc/Kconfig:
config DEMO_MOD
tristate "A kernel device driver for demonstration purpose"
default y
help
This is a demo kernel module.
Note that tristate means my module could be either statically compiled with kernel (y), or dynamic loadable moule (m) or not selected (n). A driver could be bool also, when the dynamic loading is not applicable.
default selects the default build-able property.
3. Linux Character Drivers
A character driver which we discussed above is a driver which is used to do byte oriented operation. Now let's say we have a I2C driver which is a character driver. How the communication will be done between the linux application to the I2C device?
For the whole operation the major players are:
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/device.h>
static dev_t demo_devt;
struct class *cl;
static int module_constructor(void) /* Constructor */
{
int ret;
struct device *dev_ret;
printk(KERN_INFO "Initializing demo module\n");
ret = alloc_chrdev_region(&demo_devt, 0, 3, "demodevt");
if (ret) {
printk(KERN_ERR "failed to allocate char device\n");
return ret;
}
printk(KERN_INFO "Major = %d minor = %d\n", MAJOR(demo_devt), MINOR(demo_devt));
if (IS_ERR(cl = class_create(THIS_MODULE, "democharcl"))) {
unregister_chrdev_region(demo_devt, 1);
return PTR_ERR(cl);
}
if (IS_ERR(dev_ret = device_create(cl, NULL, demo_devt, NULL, "demochardrv"))) {
class_destroy(cl);
unregister_chrdev_region(demo_devt, 1);
return PTR_ERR(dev_ret);
}
printk(KERN_INFO"DEMO module initialization SUCCESS\n");
return 0;
}
static void module_destructor(void) /* Destructor */
{
printk(KERN_INFO "Deinitializing the module\n");
device_destroy(cl, demo_devt);
class_destroy(cl);
unregister_chrdev_region(demo_devt, 3);
}
For the whole operation the major players are:
- Application (linux compiled a.out)
- Character Device file (/dev/<node_name>)
- Character Device Driver
- Device
Application get connected to the device file by invoking the open() system call. Device file are lined to the driver by specific registration by the driver. Note that in between device file and the driver the Virtual File System comes in picture. That means the application operates on device file, those operation are translated into the corresponding function into the linked character device driver by the VFS. Finally device driver does the low level operation on the device by setting the corresponding registers.
ii) Major and Minor number
The connection between the application and the device file is established with the name of the device file. However the device driver don't recognize the device file with the name. It is recognized by the device driver with the help of pair of number which are Major and minor number of a driver.
After Linux Kernel 2.6, a major number could be common between multiple driver, however in this case the distinction is based on the Minor number.
#include <linux/types.h>
static dev_t;
This is a 32 bit integer value which contain both the Major and Minor number.
The major and minor number could be specify for a driver using any of the two ways:
1. Fixed number Specified by the driver developer
int register_chrdev_region(dev_t first, unsigned int cnt, char *name);
2. Let the kernel allocate some unused pairs (recommended):
int alloc_chrdev_region(
dev_t *first, unsigned int firstminor, unsigned int cnt, char *name);
We have used the second option to register the major and minor number.
As of now still after building and running the kernel, we will not be able to find the character device file inside /dev/. We need to create that using mknod command, whose Usage can be read from here.
To check and ensure the major and minor number allocated, we can do a cat /proc/devices
As we can find that we have got major number as 251, for our device named demodevt, we can now create the device using the following command:
$ mknod /dev/demodevt c 251 0
struct class *cl = class_create(THIS_MODULE, "<device class name>");
device_create(cl, NULL, demodevt, NULL, "<device name format>");
device_destroy(cl, demodevt);
class_destroy(cl);
As of now still after building and running the kernel, we will not be able to find the character device file inside /dev/. We need to create that using mknod command, whose Usage can be read from here.
To check and ensure the major and minor number allocated, we can do a cat /proc/devices
As we can find that we have got major number as 251, for our device named demodevt, we can now create the device using the following command:
$ mknod /dev/demodevt c 251 0
iii) Character device file creation by driver
In a linux kernel system, /sys/class/ are the hardware specific entries and information. These information are maintained by the kernel to be used by or signal the user level application for any hardware change. Furthermore they also contain information about the hierarchy of the hardware.
A common example is udev that create the runtime devices w.r.t the rules.
One more example is how the /dev/sdX nodes appear when we plug the pendrive into our linux PC. So these are the hotplug feature that udev or similar user application use to track on the hardware changes in /sys/class entry, thus creating the device files. We will not focus more on this topic.
If we want our driver to create the /sys/class and /dev entry, we need to add some more steps.
struct class *cl = class_create(THIS_MODULE, "<device class name>");
device_create(cl, NULL, demodevt, NULL, "<device name format>");
device_destroy(cl, demodevt);
class_destroy(cl);
Putting them all together:
#include <linux/module.h>#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/device.h>
static dev_t demo_devt;
struct class *cl;
static int module_constructor(void) /* Constructor */
{
int ret;
struct device *dev_ret;
printk(KERN_INFO "Initializing demo module\n");
ret = alloc_chrdev_region(&demo_devt, 0, 3, "demodevt");
if (ret) {
printk(KERN_ERR "failed to allocate char device\n");
return ret;
}
printk(KERN_INFO "Major = %d minor = %d\n", MAJOR(demo_devt), MINOR(demo_devt));
if (IS_ERR(cl = class_create(THIS_MODULE, "democharcl"))) {
unregister_chrdev_region(demo_devt, 1);
return PTR_ERR(cl);
}
if (IS_ERR(dev_ret = device_create(cl, NULL, demo_devt, NULL, "demochardrv"))) {
class_destroy(cl);
unregister_chrdev_region(demo_devt, 1);
return PTR_ERR(dev_ret);
}
printk(KERN_INFO"DEMO module initialization SUCCESS\n");
return 0;
}
static void module_destructor(void) /* Destructor */
{
printk(KERN_INFO "Deinitializing the module\n");
device_destroy(cl, demo_devt);
class_destroy(cl);
unregister_chrdev_region(demo_devt, 3);
}
So far we have developed our very minimal character device driver. Our device's class is listed under /sys/class/democharcl/ and device is listed as /dev/demochardrv.
Now in this bare-minimal rocket skeleton we need to add the engine, payload, wings, and a lot of thing to make the rocket land in the Moon.
Comments
Post a Comment