You are on page 1of 39

LINUX DEVICE DRIVERS

What is Device Drivers?


A device driver is a collection of subroutines and data within the
kernel that constitutes the software interface to an I/O device.
When the kernel recognizes that a particular action is required
from the device, it calls the appropriate driver routine, which
passes the control from the user process to the driver routine.
Control is returned to the user process when the driver routine has
completed.

software

hardware
A device driver must provide the following features:
● A set of routines that communicate with a hardware device
and provide a uniform interface to the operating system
kernel.
● A self-contained component that can be added to, or removed
from, the operating system dynamically.
● Management of data flow and control between user programs
and a peripheral device.
● Most device drivers are accessed through the file system. In
Linux, “/dev” directory is populated with the device special
files, and its contents look like any other directory.
$ls –l /dev
brw-rw---- 1 root disk 22, 1 Aug 5 2007 hda1
crw-rw---- 1 root lp 6, 0 Aug 5 2007 lp0
Device Driver- user,kernel & hardware interface:
Kernel
The kernel is the central component of most computer
operating systems; it is a bridge between applications and the
actual data processing done at the hardware level. The kernel's
responsibilities include managing the system's resources (the
communication between hardware and software components).
Kernels
.

Monolithic Micro Kernel


Kernel
Kernel Cont..

Monolithic – A monolithic kernel is a single block of


code with different instructions for each and every
hardware platform on which it runs. Whenever a new
component is added, we have to include speciallized
codes in the kernel source and complie it afresh.

Modular – Modular kernel organizes its code in small


chunks that can be loaded into the memory whenever it
is required to operate some hardware and can be
unloaded from the memory when it is no longer in use.
Split Kernel
Kernel Module Programming
Environment
When we are writing an application programs, we may
use certain functions, such as printf,strcpy, and etc.
These functions are part of the standard C library, libc,
and have defined prototypes in appropriate header files.
Application programs are usually linked against libc, and
these symbols are resolved at run time.
Device Drivers (LKM), on the other hand, are linked
against the kernel and are therefore restricted to using
the functions that kernel exports. Therefore a device
driver built as a kernel module is not run in the ordinary
sense, and all module symbols are resolved when we
attempt to load the module into the kernel.
A Simple kernel Module
#include <linux/module.h>
#include <linux/kernel.h>

int init_module(void)
{
printk(KERN_DEBUG “Hello, Kernel!\n”);
return 0;
}
void cleanup_module(void)
{
printk(KERNEL_DEBUG ”Good-bye, kernel!\n”);
}
MODULE_LICENSE(“GPL”);
The kernel equivalent of printf is printk. They are similar usage, but the latter
does not support printing of floating point. The KERN_DEBUG define given to
printk sets the priority of the printed message.
The possible values of priority of the printed message are defined by
linux/kernel.h as follows:
#define KERN_EMERG “<0>” /* system is unusable */
#define KERN_ALERT “<1>” /* action must be taken immediately*/
#define KERN_CRIT “<2>” /* critical conditions */
#define KERN_ERR “<3>” /* error conditions */
#define KERN_WARNING “<4>” /* warning conditions */
#define KERN_NOTICE “<5>” /* normal but significant condition */
#define KERN_INFO “<6>” /* informational */
#define KERN_DEBUG “<7>” /* debug-level message */

So we could have written


printk(“<7> Hello, Kernel!\n”);
As it is written we will probably not able to see the messages directly. Instead
they are stored in the kernel buffer allocated for these kinds of messages. The
program dmesg will show us what is currently stored in the kernel buffer.
Compiling and Loading Module.
gcc –DMODULE –D__KERNEL__
-I/usr/src/linux-2.6.35-28/include
-O2 -c -Wall my_module.c -o my_module.o
For loading the module into the kernel: insmod (insert module)
insmod my_module.o
For unloading the module from the kernel: rmmod (remove
module)
rmmod my_module.o
To prove that the module is loaded, lsmod (list modules) can be
run. It lists the modules that are currently inserted.
lsmod
Module Size Used By
hello 176 0 (unused)
How the kernel supports insmod:
insmod relies on a few system calls defined in
kernel / module.c. The function
sys_create_module allocates kernel memory to
hold a module(this memory is allocated with
vmalloc). The system call get_kernel_syms
returns the kernel symbol table so that kernel
references in the module can be resolved, and
sys_init_module copies the relocated object
code to kernel space and calls the module’s
initialization function.
How the kernel supports rmmod:
To unload a module, we use the rmmod command. Its
task is much simpler than loading, since no linking has to
be performed. The command invokes the
delete_module system call, which calls
cleanup_module in the module itself if the usage count
is zero or returns an error otherwise.
The cleanup_module implementation is in charge of
unregistering every item that was registered by the
module. Only the exported symbols are removed
automatically.
The Usage Count
The system keeps a usage count for every module in order to
determine whether the module can be safely removed. The
system needs this information because a module can’t be
unloaded if it is busy.
The usage count maintenance is done with the following
macros:
MOD_INC_USE_COUNT
MOD_DEC_USE_COUNT
MOD_IN_USE

The macros are defined in <linux/module.h>, and they can


act on internal data structures that shouldn’t be accessed
directly by the programmer.
Classification of Device Drivers
Character Devices
A character device can be accessed as a stream of
bytes, These devices implement at least the open,
read,write and close system calls. Eg.- terminals and line
printers.
Block Devices
A block device is one which can host a filesystem. The
transfer of data is done in blocks of varying lengths. Eg-
Disk Driver.
Network Interfaces
These interfaces are in charge of transferring data in the
form of packets and is driven by the network subsystem
of the kernel.
Character Drivers
Major and Minor Numbers
● Major numbers identify the driver associated with the
device.

● Minor numbers are used by the kernel to determine


exactly which device is being referred to.

crw-rw-rw- 1 root root 1, 3 Apr 11 2002 null


crw-------- 1 root root 10, 1 Apr 11 2002 psaux
crw------- 1 root root 4, 1 Oct 28 03:04 tty1
crw-rw-rw- 1 root tty 4, 64 Apr 11 2002 ttys0
crw-rw---- 1 root uucp 4, 65 Apr 11 2002 ttys1
crw—w---- 1 vcsa tty 7, 1 Apr 11 2002 vcs1
crw—w---- 1 vcsa tty 7, 129 Apr 11 2002 vcsa1
crw-rw-rw- 1 root root 1, 5 Apr 11 2002 zero
Allocating and Freeing Device Number
To obtain a device number to work with, the necessary function is
● int register_chrdev_region(dev_t first, unsigned int count, char
*name);
first is the beginning device no. of the range we like to allocate.
count is the total no of contiguous device no. we are requesting.
name is the name of the device that should be associated with the
number range.

To dynamically allocate a device number by the kernel, use the function

● int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,


unsigned int count, char *name);
dev is an output-only parameter that will, on successful completion,
hold the first number in our located range.
firstminor is the requested first minor number to use; it is usually 0.
The count and name parameters work like those given to
request_chrdev_region
Allocating and Freeing Device Numbers

Device numbers are freed with

● void unregister_chrdev_region(dev_t first,


unsigned int count)

Allocation of device number is done in the init


function of the module and freeing of the device
number is done in the cleanup function of the
module
Character device registration

The kernel uses structures of type cdev to


represent character devices internally.

There are two ways of allocating and initializing


one of these structures
● struct cdev *my_cdev = cdev_alloc();
● my_cdev->ops = &my_fops;

● void cdev_init(struct cdev *cdev, struct


file_operations *fops);
Character device registration Contd.

Once the structure has been set up, the kernel is


notified with the call to
● int cdev_add(struct cdev *dev, dev_t num,
unsigned int count);
To remove the structure
● void cdev_del(struct cdev *dev);

Registration of device is done in the init function


of the module and unregistration of the device is
done in the cleanup function of the module
Linking a char module to the kernel
Open Method
●int (*open) (struct inode *, struct file *);

●The open method is provided for a driver to do


any initialization in preparation for later
operations.
●In addition, open usually increments the usage
count for the device so that the module won’t be
unloaded before the file is closed. The count, is
then decremented by the release method.
Read Method
ssize_t (*read) (struct file *, char *, size_t, loff_t
*);

Used to retrieve data from the device.

A non-negative return value represents the


number of bytes successfully read (the return
value is a “signed size” type, usually the native
integer type for the target platform).
Read Method Contd.
The Read Method
The return value from read is interpreted by the
calling program as follows:
• When return value == count, the transfer
succeeded.
• If return value > 0 && return value < count, only
a partial transfer occurred.
• If return value == 0, end-of-file is reached.
• If return value < 0, an error occurred.
The caller can use this value to look up the error
in <linux/errno.h>
Write Method

ssize_t (*write) (struct file *, const char *,


size_t, loff_t *);

Sends data to the device.


The return value, if non-negative, represents the
number of bytes successfully written.
Write Method contd.
The following semantics are implemented for the
write method:
If return value == count, the transfer succeeded.
If return value > 0 && return value < count, only a
partial transfer occurred.
The caller is free to retry, which is what calling
program will do for you.
If return value == 0, nothing was written.
This is not an error and the standard library should
retry the write.
If return value < 0, an error occurred.
The caller can use this value to look up the error in
<linux/errno.h>
Release Method
int (*release) (struct inode *, struct file *);

This operation is invoked when the file structure is


being released.

The role of the release method is the reverse of


open.
Device File Creation
In order to access the device using system calls, a special file
is created. The driver files are normally stored in the /dev
directory of the system.

The following commands create the special device file:

mknod /dev/MyCharDevice c 22 0

Creates a special character file named xxx and gives it major


number 22 and minor number 0.
Simple Character Driver Interrupt Example
Fig. summarizes the flow of control between a user program, the
kernel, the device driver, and the hardware. The figure shows the
following sequence of events:
● A read request is made to the device driver (C-1 to C-3).

● The character is captured by the hardware (I-4 and I-5).

● The interrupt is generated (I-6).

● The interrupt handler services the interrupt (I-7 to I-9).

● The character is returned (C-10 to C-13).


Block Device Drivers
Resistering and unregistering block device driver:

Block 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.
register_blkdev function takes care of this and is invoked with the
following syntax:
#include <linux/fs.h>
int register_blkdev(unsigned int major,const char *name,struct
block_device_operations *bdops);
The return value is negative in case of failure and is non-
negative(either a positive integer or zero) on success. Calling the
function with zero as major provides dynamic major number
assignment.
For unregistering block driver the function call is:
int unregister_blkdev(unsigned int major, const char *name);

The arguments have the same general meaning as for char


devices, and major numbers can be assigned dynamically in the
same way.

Difference : register_chrdev took a pointer to a file_operations


structure, but register_blkdev uses a structure of type
block_device_operations instead.
Resitering a block device driver: Fig
In Linux, the method used for these I/O operations is
called request method. The request method handles both
read and write operations.
This method is not kept in the block_device_operations
structure for performance reasons, instead it is
associated with the queue of pending I/O operations for
the device.
By default, there is one such queue for each major
number. A block driver must initialize that queue with
blk_init_queue.
Queue initialization and cleanup is defined as follows:
#include <linux/blkdev.h>
blk_init_queue(request_queue_t *queue,request_fn_proc *request);
blk_cleanup_queue(request_queue_t *queue);

The init function sets up the queue, and associates the driver's
request function with the queue.

It is necessary to call bkl_cleanup_queue at module cleanup time.


The driver initializes its queue with this line of code:
blk_init_queue(BLK_DEFAULT_QUEUE(major),my_request_fn);
Each device has a request queue that it uses by default and the
macro BLK_DEFAULT_QUEUE(major) is used to indicate that
queue when needed.
This macro looks into a global array of blk_dev_struct structures
called blk_dev, which maintained by the kernel and indexed by
major number.
The structure looks like this:
struct blk_dev_struct {
request_queue_t request_queue;
queue_proc *queue;
void *data;
};
Thank You