EmbLogic's Blog

30.01.30 : Taruna

Character-driven drivers are device drivers that operate on characters (bytes) as the basic unit of input and output. They are accessed in a sequential, non-random manner.

 

I started writing my first character driver by writing the simplest module – hello world module.

 

Character devices have to register themselves with the kernel and provide it with the information that enables the kernel to invoke the correct functions when applications wish to interact with the device. I registered my driver using alloc_chrdev_region() and register_chrdev_region(). When we register our driver for the first time , we use the former method and to prevent its re-registeration when a new device is connected , we use the latter… The older method of registering a driver i the kernel is : register_chrdev().

The syntax of these are as follows:

int alloc_chrdev_region(dev_t * , unsigned , unsigned , const char *);

 

int register_chrdev_region(dev_t , unsigned , const char *);

 

We also need to include the required libraries in our header file.These are <linux/init.h> , <linux/module.h> , <linux/fs.h> , and others will be mentioned as per requirement. In our module we require two functions : module_init and module_exit.

module_init is used for calling the initialization function while module_exit is needed for the cleanup function. I also used a special macro MODULE_LICENSE() to tell the kernel that my module bears a free license.

 

To unregister the driver we use unregister_chrdev_region() in our cleanup function. The syntax of unregister_chrdev_region is:

 

unregister_chrdev_region(dev_t , unsigned);

 

Loading and unloading the driver

 

To load and unload our driver we need special utilities / commands. These are insmod and rmmod.

To insert my driver into the kernel I used insmod and to unload it I used rmmod. Also we can pass parameters while doing insmode. For that we use macro: module_param and include the header file moduleparam.h.

module_param uses three parameters : name of the variable to be passed , its type and permissions. For eg: I used it as:

module_param(nod , int , S_IRUGO). Here nod stands for number of devices which is of integer type.

After inserting our driver , we can perform various tasks like open , read , write and close. Rmmod simply tells the kernel that the driver is not there and it cannot perform these tasks.

Getting memory for Scull Device

 

The very next thing we need to do is to create the scull.

This is done using kmalloc in the following manner:

kmalloc(size_t , gfp_t flags);

The flag that we used is GFP_KERNEL.

 

The header file for kmalloc is <linux/slab.h>. This is done in the initialization function.

In our cleanup function we need to undo everything we do in initialization function in the reverse order. So to free this allocated memory we use kfree().

Also its better to memset this allocated memory using memset.

 

Major and Minor numbers

 

We know that in linux everything is a file. Even the device is a file. The character devices are accessed through names in file system. We call them nodes.

The major number identifies the driver for the device while the minor number is used by the kernel to determine exactly which device is being referred to.

The device is internally referred by device numbers.dev_t is used to hold these device numbers. To get the major and minor numbers we use special macros:

MAJOR(dev_t);

MINOR(dev_t);

 

To convert the major and minor numbers into device numbers , we use MKNOD.

MKNOD(majorno , minorno);

So the device number is a 32 bit combination of major and minor numbers.

12 bits are for major number and 20 bits for minor number.

 

The alloc_chrdev_region() and register_chrdev_region() are used by the kernel to allocate device numbers for our driver.

 

 

Initializing the cdev structure

 

The cdev structure is used to represent our char device internally.

I used cdev_init () to initialize the struct cdev. In this function , I initialized all the members of this structure:

set cdev.owner to THIS_MODULE

set cdev.dev to dev

set fileoperations to fops.

After these initializations we need to tell the kernel about the cdev structure . To do so , I used cdev_add(). In my cleanup function I used cdev_del() .

The syntax of these functios are as follows:

 

void cdev_init(struct cdev *, const struct file_operations *);

 

int cdev_add(struct cdev *, dev_t, unsigned);

 

void cdev_del(struct cdev *);

 

Initializing the struct ScullDev

 

The struct ScullDev is a user defined structure to represent the device.

We need to initialize the members of struct ScullDev also.

The members are : qset size , quantum size , device size and data size.

I used my own function init_scull() to make these initializations.

 

After doing upto this , our device is ready to be opened and we can perform read write operations on it. These operations are performed through an application that uses system calls: open , write , read and close.

We then map these system calls to our own routines and use struct file_operations to do the mapping.

 

I made an application and made it to interact with my driver to perform the read write and open close operations through a script.

Apart from this we need to trim our device when we open it in write only mode. For this i made a scull_trim() function.

I am able to perform writing and reading and also lseek.

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>