You are on page 1of 26

WHITE PAPER

Linux Device Drivers

Praveen B P
Team Leader

THIS DOCUMENT AND THE DATA DISCLOSED HEREIN IS PROPRIETARY AND IS NOT TO BE REPRODUCED,
USED OR DISCLOSED IN WHOLE OR IN PART TO ANYONE WITHOUT WRITTEN AUTHORIZATION OF MISTRAL
Linux Device Drivers

Table of Contents

Introduction ............................................................................. 3
Define Device Driver.................................................................. 3
Device Driver Classification........................................................ 3
Character devices................................................................................... 4
Block devices ........................................................................................ 4
Network interface.................................................................................. 4
Accessing the devices in Linux ................................................... 4
Major number ........................................................................... 4
Minor number ........................................................................... 5
Warning to the beginners........................................................... 5
Linking driver with the Linux kernel ........................................... 6
Favorite program for beginners “Hello World” ......................................... 6
Character Device Driver .............................................................. 8
Test application for the Character device driver ..................................... 12
Block Device Driver ..................................................................14
Testing the block device driver ............................................................. 25

CONFIDENTIAL Page 2
Linux Device Drivers

Introduction
Programming is interesting but programming a device is not just interesting but challenging
too. So, how do you program a device? Well, the answer is simple. One can program the device
through “device drivers”. If you are new to the world of Linux Device Drivers, you would like to
write simple “hello world” device driver first and then go for real implementation. Writing such
simple kernel module is very easy. It might hardly take 10 minutes to learn and master the art
of writing such simple device driver module.

There are number of books available on Linux device driver but for beginners (especially for
those who are new to the world of Linux) they show stars. Asking them to read those books is
like telling them to dig the dirt and find out what’s there within. Instead explain them
skeleton of the device driver and then ask them to read the book, you are telling them “Dig
this dirt and you will find the GOLD”. The following section of the document does just that.
The content of this document is not a complete guide on Linux device driver. It’s just a
sample of GOLD that you will get after digging the dirt. To get the entire GOLD
in the mine refer “Linux Device Drivers – by Alessandro Rubini & Jonathan Corbet” and use
http://www.google.com/

Define Device Driver


For a user, it’s a piece of software the controls the behavior of the hardware and from the
programmer’s perspective, device driver is software that makes a particular piece of hardware
respond to well defined internal programming interface. They hide completely the details how
the device works. User can access/use the device by means of standardized calls that are
independent of the specific driver.

In Linux, the device drivers can be built independently of the kernel and plugged in at the
runtime.
This document describes the procedure to build the device driver for Linux Operating System.
The stable kernel version available at the time of writing of this document was version 2.4.
However, the sample code discussed in this document should work on the other kernel versions
with minor/no modifications.

Device Driver Classification


Linux classifies the devices broadly into 3 types.

• Character device

• Block device

• Network interface

CONFIDENTIAL Page 3
Linux Device Drivers

On what basis do we identify the device type? Well, the description given below will help you
in differentiating the devices.

Character devices
A character device is one, from which data can be accessed as a stream of bytes. Text console
(/dev/console) and serial ports (/dev/ttyS0) are the examples of character devices.

Block devices
A block device is something that can host a file system, such as disk. In Linux, block devices
can be accessed only as multiples of block. A block is usually 1kb of data or another power of
2.

Network interface
A network interface is in charge of sending and receiving the data packets driven by the
network subsystem of the kernel without knowing how individual transaction map to the actual
packets being transmitted.

There are other classes of driver modules in Linux. The modules in each class exploit public
services the kernel offers to deal with specific type of device. Examples of such modules are
universal serial bus (USB), serial modules, SCSI drivers and so on.

Accessing the devices in Linux


The devices can be accessed through the file system interface provided by the Linux. Each
device in Linux has a file representing it. Such files are called device nodes and are generally
stored in /dev folder. The device nodes are created using the mknod command. The syntax of
the command is given below.

mknod /dev/<name of the device node> <type> <major number> <minor number>

The difference between the character device and the block device is transparent to the user.
User communicates with char devices using open, close, read and write system calls and the
block devices are mounted using the file system that they host. However, communication
between kernel and the network interface is completely different from that used with character
and block device drivers. Instead of read and write the kernel calls the functions related to
packet transmission.

Major number
How does the system call such as read and write invoke the driver functions controlling the
device? There could be hundreds of device drivers loaded into the Linux kernel. How does the
Linux determine which driver has to be invoked when some device node is accessed? Here is an
answer. There is an array of file operation structure maintained within the kernel for the

CONFIDENTIAL Page 4
Linux Device Drivers

character and block device drivers. The file operation structure contains the function pointers,
corresponding to each system call the device driver supports. The pseudo code of that is given
below.

struct file_operatons {

int (*open)(struct inode *, struct file *) ;

int (*close)(struct inode *, struct file *) ;

...

};

When the driver is loaded into the Linux Kernel, its file operation structure is copied in to the
array indexed by number. This number is called the major number.

The major number is specified while creating a device node in /dev folder. Thus the file system
knows the major number and hence it can invoke the correct functions for that particular
device through the system call.

Minor number
If there is more than one similar device (for example 2 COM ports) then there is no need to
have one device driver for each device. The same device driver can handle both the devices but
there has to be a mechanism to know which device the user is trying to access or control. This
is achieved using the minor number. All we need to do is create device nodes in /dev folder
with same major number but with different minor numbers. Maximum number of devices the
driver can handle is determined by the device driver and not by the user.

Warning to the beginners


• Be careful with the kernel modules they are as powerful as superuser shell.

• As a device driver writer, you should be aware of situations in which some types of device
access could adversely affect the system as a whole, and should provide adequate controls.
For example, device operations that affect the global resources (such as setting the
interrupt line) or that could affect the other users are usually only available to the
sufficiently privileged users, and check must be made in the driver itself.

• Major cause of errors are buffer overrun. Perform necessary check before writing the data
into buffers.

• Any input received from the user must be treated with suspicion. Always verify the info
given by the user before using it.

• Be careful with unintialized memory. Any memory obtained from the kernel should be
zeroed to avoid leakage of information.

CONFIDENTIAL Page 5
Linux Device Drivers

• An application can call the function, which it doesn’t define. At the linking stage the
external references are resolved using appropriate library functions. A device driver module
on the other hand is linked to the kernel, only functions it can call are the ones exported
by the kernel. There are no libraries to link to.

• Make sure that you have included the necessary header files and do not ignore any
warnings while compiling the code. This is because some part of the code in the driver that
appears as function call might have been defined as a macro in some header files.

Linking driver with the Linux kernel


The Linux device driver module can be inserted into Linux kernel using insmod command and
can be removed using rmmod command. The user with root previlages can perform these
operations. So, it is clear that there has to be one function to do the initialization operation
when the module is inserted and one to do the cleanup operation when the module is being
removed.

Equipped with the knowledge gained so far, lets jump into the programming and write a simple
“hello world” kernel module. Lets call this program a kernel module and not a device driver
module because it is independent of the device types and it provides an entry point into the
Linux kernel.

Favorite program for beginners “Hello World”

#define MODULE

#include <linux/module.h>

int init_module( void )

printk( "<1> Hello, world\n" ) ;

return 0 ;

void cleanup_module( void )

printk( "<1> Good bye\n" ) ;

MODULE_LICENSE( "GPL" ) ;

CONFIDENTIAL Page 6
Linux Device Drivers

The printk function is similar to printf function in the general user programs. It prints the
kernel messages. The printf cannot be used in the device driver because printf is defined in c
library and the device driver can use only those functions exported by the Linux kernel and
there are no libraries to link to.

Now, can you guess, what the above program does (or what it is supposed to do)? Yes, it
should print the message “Hello, world” when the module is inserted and “Good bye” when the
module is removed.

Save the code given above in hello.c file. Execute the following commands to compile and load
the kernel module. If the messages are not displayed on the terminal type dmesg on the
command line and see the driver messages.

# gcc -D__KERNEL__ -I/usr/src/linux/include -c hello.c

# insmod ./hello.o

Hello, world

# rmmod hello

Good bye

The user applications sometimes use the kernel headers, however, many of the declarations in
the kernel header are relevant only to the kernel itself and should not be seen by the user
space applications. These declarations are therefore protected by #ifdef __KERNEL__ blocks.
That’s why kernel code has to be compiled with __KERNEL__ preprocessor symbol defined.

The path to locate the kernel header files has to be specified to the compiler. Usually the Linux
source files are stored under /usr/src/linux. Hence –I/usr/src/linux/include is passed as an
argument to the compiler.

The last line of the code MODULE_LICENSE is given to avoid the “kernel tainted” warning. For
more information on this visit http://www.tux.org/lkml. There are other macros defined in
module.h header file like MODULE_AUTHOR, MODULE_NAME etc. Whether to use those macros
or not, the choice is left to the programmer.

Once you are able to compile and execute the above program lets build the device driver
module for the character device.

The common system calls used for character device are open, close, read, write and ioctl. The
device driver has to implement functions to handle these system calls and pointers to these
functions are stored in file_operations structure. The structure is then registered with Linux
kernel using register_chrdev function.

CONFIDENTIAL Page 7
Linux Device Drivers

Character Device Driver


Open the file mychrdriver.c and type the following code.

#define MODULE

#include <linux/config.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/slab.h>

#include <linux/fs.h>

#include <linux/errno.h>

#include <linux/types.h>

#include <linux/proc_fs.h>

#include <linux/fcntl.h>

#include <asm/system.h>

int mychrdrv_open( struct inode *Inode, struct file *filep )

MOD_INC_USE_COUNT ;

printk( "<1> opening the device\n" ) ;

return 0 ;

int mychrdrv_close( struct inode *Inode, struct file *filep )

MOD_DEC_USE_COUNT ;

printk( "<1> closing the device\n" ) ;

return 0 ;

int mychrdrv_read( struct file *filep, char *buff,

size_t count, loff_t *offp )

CONFIDENTIAL Page 8
Linux Device Drivers

int retVal = -EFAULT ;

if( buff == NULL )

printk( "<1> invalid buff ptr\n" ) ;

return retVal ;

/*

check if the sufficient data is available. If YES then copy the

data to user buffer.

*/

retVal = count ;

printk( "<1> data read from the device\n" ) ;

return (retVal) ;

int mychrdrv_write( struct file *filep, const char *buff,

size_t count, loff_t *offp )

int retVal = -EFAULT ;

if( buff == NULL )

printk( "<1> invalid buff ptr\n" ) ;

return retVal ;

/*

check if the device buffer has sufficient space to store the data.

CONFIDENTIAL Page 9
Linux Device Drivers

If YES then copy the data from user buffer.

*/

retVal = count ;

printk( "<1> data written to the device\n" ) ;

return (retVal) ;

int mychrdrv_ioctl( struct inode *Inode, struct file *filep,

unsigned int cmd, unsigned long arg )

int retVal = 0 ;

/*Implement switch - case for each command */

printk( "<1> Executing the command %d\n", cmd ) ;

return( retVal ) ;

int mydrv_major = 42 ;

#define DEVICE_NAME "mychrdrv"

struct file_operations mydrv_fops = {

open: mychrdrv_open,

release: mychrdrv_close,

read: mychrdrv_read,

write: mychrdrv_write,

ioctl: mychrdrv_ioctl,

};

int init_module( void )

int retVal ;

printk( "<1> init_module() of the char device driver\n" ) ;

CONFIDENTIAL Page 10
Linux Device Drivers

retVal = register_chrdev( mydrv_major, DEVICE_NAME, &mydrv_fops ) ;

if( retVal < 0 )

printk( "<1> couldn't get the major number\n" ) ;

return( retVal ) ;

return retVal ;

void cleanup_module( void )

printk( "<1> cleanup_module() of the char device driver\n" ) ;

unregister_chrdev( mydrv_major, DEVICE_NAME ) ;

MODULE_LICENSE( "GPL" ) ;

Now, compile this file and load the module. Create a node in the /dev folder to access this
device driver.

# gcc -D__KERNEL__ -I/usr/src/linux/include -c mychrdriver.c

# insmod ./mychrdriver.o

Init module of the char device driver

# mknod /dev/mychrdrv c 42 0

# chmod +666 /dev/mychrdrv

Note:

The major number can either be specified by device driver programmer or can be dynamically
assigned by the Linux. The dynamic allocation of major number is not chosen because we may
have to create the node for the device in /dev every time the module is assigned with a new
major number when its reloaded.

If the first parameter to the register_chrdev function is 0, then the value returned by it is the
dynamically allocated major number. If the first parameter is unsigned integer (other than 0)

CONFIDENTIAL Page 11
Linux Device Drivers

then that is used as major number and on success 0 is returned. In either case negative return
values indicate an error.

The device driver example given above can be used for the device driver with only one minor
number. In other words, this driver model cannot handle the more than one similar device. For
more information on this refer “Linux Device Drivers – by Alessandro Rubini & Jonathan
Corbet”.

Test application for the Character device driver

A test application is required to prove that our foundation for the character device driver is
ready and it can be used to support any kind of character devices by filling in device specific
operations in the corresponding functions.

Open the file chrdrvtestapp.c and type the following code.

#include <stdio.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#define CHRDEVICE "/dev/mychrdrv"

int main( void )

int retVal, fd ;

char *buf = "Test String" ;

fd = open( CHRDEVICE, O_RDWR ) ;

if( fd <= 0 )

printf( "Could not open the device\n" ) ;

exit( 0 );

retVal = write( fd, buf, strlen(buf) ) ;

if( retVal != strlen(buf) )

printf( "failed to write\n" ) ;

CONFIDENTIAL Page 12
Linux Device Drivers

retVal = read( fd, buf, strlen(buf) ) ;

if( retVal != strlen(buf) )

printf( "failed to read\n" ) ;

retVal = ioctl( fd, 0, 0 ) ;

if( retVal < 0 )

printf( "ioctl failed\n" ) ;

close( fd ) ;

To compile and run the test application type the following commands on the terminal.

# gcc –o testapp chrdrvtestapp.c

# ./testapp

init_module() of the char device driver

opening the device

data written to the device

data read from the device

Executing the command 0

closing the device

(If messages are not displayed on the terminal use dmesg)

If the above-mentioned outputs are obtained then your skeleton structure for the character
device driver is ready. Each function has to be implemented completely to do the operations
specific to the device. The following section describes what needs to be done in each function.
There is no generic way to do all these stuffs because it is completely device dependent.

init_module

Register the interrupt handlers if any.

Initialize the global variables within the driver and kernel if any.

cleanup_module

Unregister the interrupt handlers if registered during initialization.

Free the kernel memory if allocated during the initialization process.

CONFIDENTIAL Page 13
Linux Device Drivers

mychrdrv_open

Check for device specific errors (such as device not ready or some hardware problems).

Initialize the device if being opened for the first time.

Check for the minor number and see if the driver supports that minor number.

Allocate and fill the memory for the private data to be maintained with in the device driver.

myvhrdrv_close

Release the memory allocated to maintain the private data within the device driver.

Shutdown the device if the usage count drops to zero.

mychrdrv_read & mychrdrv_write

Check for buffer over run.

Make sure that the critical section of the code is well protected.

mychrdrv_ioctl

The read and write functions are used for exchanging the data with the device. The ioctls are
used to exchange the device specific information (such as the current state of the device) or
to send the commands to the device. In this function implement the different commands do
perform the necessary operation.

Other functions

There are set of other functions which can be used in the character device driver. They are
llseek, poll, mmap and fsync. Usage of these functions depends on the type of the device you
are dealing with.
Block Device Driver
This section describes the most commonly implemented functions of the block device driver.
Unlike the char device driver, the data is not accessed as stream of bytes; instead data is
randomly accessed in fixed-size blocks. In block device driver set of global variables of the
kernel have to be initialized with great care.

The block device driver is split into two files. One file acts as a block driver interface with
Linux and the other file implements the device specific functions.

Lets have a look at the code implementing the device specific functions. The device we are
using in this example is RAM. We allocate some memory in kernel and use that as our block
device. So, there are no complexities involved. Open the file myblkdrv_device.c and type the
following code.

CONFIDENTIAL Page 14
Linux Device Drivers

#include <linux/version.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/vmalloc.h>

#define MYBLKDRV_SIZE 2048


#define MYBLKDRV_BLKSIZE 512
static char *DATA ;

char isDeviceChanged (void)


{
return (TRUE) ;
}

int getDeviceSpecificData (void)


{
return SUCCESS ;
}
int open_Device (void)
{
return SUCCESS ;
}

int readBlock (unsigned int Addr, void *pBuff)


{
memcpy( pBuff, &DATA[Addr], MYBLKDRV_BLKSIZE ) ;
return SUCCESS ;
}

int writeBlock (unsigned int Addr, void *pBuff)


{
memcpy( &DATA[Addr], pBuff, MYBLKDRV_BLKSIZE ) ;
return SUCCESS ;
}

int myblkdrv_Init (void)


{
DATA = vmalloc (MYBLKDRV_SIZE * 1024) ;
if( DATA == NULL )
return (-ENOMEM) ;

memset( DATA, 0, (MYBLKDRV_SIZE * 1024) ) ;


return SUCCESS ;
}

CONFIDENTIAL Page 15
Linux Device Drivers

void myblkdrv_Cleanup (void)


{
vfree (DATA) ;
return ;
}

The details about each function are given below.

isDeviceChanged

This function should implement a mechanism to check whether the device is changed. This is
applicable if the block device is a removable media.

getDeviceSpecificData

This function is called from the revalidate function of the block device driver. This function
should implement the mechanism to collect the device specific information.

open_Device

This function is called when the device is opened. All the initializations required for the device
to perform read and/or write should be implemented here.

readBlock

This function should copy the block of data from the device. Since our device is RAM, memcpy
function serves the purpose.

writeBlock

The mechanism to write a block of data to the device should be implemented here.

myblkdrv_Init

The myblkdrv_Init function is used to register the interrupts and to enable the device specific
registers. It is invoked from the init_module function.

myblkdrv_Cleanup

The myblkdrv_Cleanup function should release the interrupts and reset the device registers.
This function is invoked from the cleanup_module function.

The above-mentioned functions are used in the block driver interface module of the driver.
Open the file myblkdrv_block.c and type the following code.

#define MODULE
#include <linux/config.h>
#include <linux/module.h>
#include <linux/sched.h>

CONFIDENTIAL Page 16
Linux Device Drivers

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/hdreg.h>
#include <asm/system.h>
#include <linux/ioctl.h>

static int myblkdrv_major;


#define MAJOR_NR myblkdrv_major

#define DEVICE_NR(device) MINOR(device)


#define DEVICE_NAME "myblkdrv"
#define DEVICE_INTR myblkdrv_intrptr
#define DEVICE_NO_RANDOM
#define DEVICE_REQUEST myblkdrvRequest
#define DEVICE_OFF(d)

#include <linux/blk.h>
#include "sysdep.h"
#ifdef HAVE_BLKPG_H
#include <linux/blkpg.h>
#endif
#include "myblkdrv.h"
#define TRUE 1
#define FALSE 0
#define SUCCESS 0
#define TRANSFER_SUCCESS 1
#define TRANSFER_FAILED 0
#define MYBLKDRV_MAJOR 120
#define MYBLKDRV_DEVS 1
#define MYBLKDRV_RAHEAD 2
#define MYBLKDRV_SIZE 2048
#define MYBLKDRV_BLKSIZE 512
#define MYBLKDRV_HARDSECT 512

typedef struct mmcsd_Dev


{
int size ;
int blkSize ;
int usage ;
spinlock_t lock ;

CONFIDENTIAL Page 17
Linux Device Drivers

} MYBLKDRV_DEV;

int myblkdrv_devs ;
int myblkdrv_rahead ;
int myblkdrv_size ;
int myblkdrv_blksize ;
int myblkdrv_hardsect ;

MYBLKDRV_DEV *myblkdrv_devices = NULL ;


int *myblkdrv_blksizes = NULL ;
int *myblkdrv_sizes = NULL ;
int *myblkdrv_hardsects = NULL ;

int myblkdrvOpen (struct inode *pstrInode, struct file *pstrFile)


{
int retVal ;
MYBLKDRV_DEV *pstrDevInfo ;
int devNum = MINOR (pstrInode->i_rdev) ;

if (devNum >= myblkdrv_devs)


return (-ENODEV) ;

pstrDevInfo = myblkdrv_devices + devNum ;


spin_lock (&pstrDevInfo->lock) ;
if (pstrDevInfo->usage == 0)
{
check_disk_change (pstrInode->i_rdev) ;
}
pstrDevInfo->usage++ ;
spin_unlock (&pstrDevInfo->lock) ;

if ((retVal = init_Device()) == SUCCESS)


{
MOD_INC_USE_COUNT ;
}
return retVal ;
}

int myblkdrvRelease (struct inode *pstrInode, struct file *pstrFile)


{
MYBLKDRV_DEV *pstrDevInfo = myblkdrv_devices + MINOR (pstrInode->i_rdev) ;

spin_lock (&pstrDevInfo->lock) ;
pstrDevInfo->usage-- ;
if (pstrDevInfo->usage == 0)

CONFIDENTIAL Page 18
Linux Device Drivers

{
fsync_dev (pstrInode->i_rdev) ;
invalidate_buffers (pstrInode->i_rdev) ;
}
MOD_DEC_USE_COUNT ;
spin_unlock (&pstrDevInfo->lock) ;
return (SUCCESS) ;
}

int myblkdrvIoctl (struct inode *pstrInode, struct file *pstrFile,


unsigned int cmd, unsigned long arg)
{
int err ;
unsigned int u32Size ;
struct hd_geometry strGeo ;

switch (cmd)
{
case BLKGETSIZE :
if (arg == 0) /* check for invalid pointer */
return (-EINVAL) ;

u32Size = MYBLKDRV_BLKSIZE ;

if (copy_to_user ((long *)arg, &u32Size, sizeof (long)) != SUCCESS)


return (-EFAULT) ;

return SUCCESS ;

case BLKRRPART:
return (-ENOTTY) ;

case HDIO_GETGEO :
err = !access_ok (VERIFY_WRITE, arg, sizeof(strGeo)) ;
if (err)
return (-EFAULT) ;

return (-ENOTTY) ;

default :
return (blk_ioctl (pstrInode->i_rdev, cmd, arg)) ;
}
return (-ENOTTY) ;
}

CONFIDENTIAL Page 19
Linux Device Drivers

int myblkdrvCheckChange (kdev_t i_rdev)


{
return (isDeviceChanged()) ;
}

int myblkdrvRevalidate (kdev_t i_rdev)


{
int retVal ;

if ((retVal = getDeviceSpecificData()) != SUCCESS)


return (retVal) ;

return (retVal) ;
}

struct block_device_operations myblkdrvBlkDevFops = {


open: myblkdrvOpen,
release: myblkdrvRelease,
ioctl: myblkdrvIoctl,
check_media_change: myblkdrvCheckChange,
revalidate: myblkdrvRevalidate,
};

static MYBLKDRV_DEV *myblkdrvLocateDevice (const struct request *pstrReq)


{
MYBLKDRV_DEV *device ;
int devNum = DEVICE_NR (pstrReq->rq_dev) ;

if (devNum >= myblkdrv_devs)


{
return NULL ;
}
device = myblkdrv_devices + devNum ;
return device ;
}
static int myblkdrvTransfer (MYBLKDRV_DEV *device,
const struct request *pstrReq)
{
int i ;
int dataSize ;
unsigned long Addr ;

Addr = pstrReq->sector * myblkdrv_hardsect ;


dataSize = pstrReq->current_nr_sectors * myblkdrv_hardsect ;

if ((Addr + dataSize) > (myblkdrv_blksize * myblkdrv_size))

CONFIDENTIAL Page 20
Linux Device Drivers

return TRANSFER_FAILED ;

switch (pstrReq->cmd)
{
case READ :
for ( i = 0 ; i < pstrReq->current_nr_sectors ; i++ )
{
readBlock (Addr, &pstrReq->buffer[ (i * myblkdrv_blksize) ]) ;
Addr += myblkdrv_blksize ;
}
return TRANSFER_SUCCESS ;

case WRITE :
for ( i = 0 ; i < pstrReq->current_nr_sectors ; i++ )
{
writeBlock (Addr, &pstrReq->buffer[ (i * myblkdrv_blksize) ]) ;
Addr += myblkdrv_blksize ;
}
return TRANSFER_SUCCESS ;

default:
/* can't happen */
return TRANSFER_FAILED ;
}
}

void myblkdrvRequest (request_queue_t *pstrReqQueue)


{
MYBLKDRV_DEV *device ;
int status ;

while (1)
{
INIT_REQUEST ;
device = myblkdrvLocateDevice (CURRENT) ;
if (device == NULL)
{
end_request (0) ;
continue ;
}
spin_lock (&device->lock) ;
status = myblkdrvTransfer (device, CURRENT) ;
spin_unlock (&device->lock) ;
end_request (status) ;
}
}

CONFIDENTIAL Page 21
Linux Device Drivers

int init_module (void)


{
int result ;
int i ;

myblkdrv_major = MYBLKDRV_MAJOR ;
myblkdrv_devs = MYBLKDRV_DEVS ;
myblkdrv_rahead = MYBLKDRV_RAHEAD ;
myblkdrv_size = MYBLKDRV_SIZE ;
myblkdrv_blksize = MYBLKDRV_BLKSIZE ;
myblkdrv_hardsect = MYBLKDRV_HARDSECT ;

result = register_blkdev(myblkdrv_major, DEVICE_NAME, &myblkdrvBlkDevFops);


if (result < 0)
return result ;

printk ( "<1>" "major = %d\n", myblkdrv_major) ;

read_ahead[ myblkdrv_major ] = myblkdrv_rahead ;


result = -ENOMEM ;

myblkdrv_sizes = kmalloc ((myblkdrv_devs * sizeof(int)), GFP_KERNEL) ;


if (!myblkdrv_sizes)
goto fail_malloc ;

for (i = 0 ; i < myblkdrv_devs ; i++)


myblkdrv_sizes[i] = myblkdrv_size ;
blk_size[ myblkdrv_major ] = myblkdrv_sizes ;

myblkdrv_blksizes = kmalloc ((myblkdrv_devs * sizeof(int)), GFP_KERNEL) ;


if (!myblkdrv_blksizes)
goto fail_malloc ;

for (i = 0 ; i < myblkdrv_devs ; i++)


myblkdrv_blksizes[i] = myblkdrv_blksize;
blksize_size[ myblkdrv_major ] = myblkdrv_blksizes ;

myblkdrv_hardsects = kmalloc ((myblkdrv_devs * sizeof(int)), GFP_KERNEL) ;


if (!myblkdrv_hardsects)
goto fail_malloc ;

for (i = 0 ; i < myblkdrv_devs ; i++)


myblkdrv_hardsects[i] = myblkdrv_hardsect ;
hardsect_size[ myblkdrv_major ] = myblkdrv_hardsects ;

CONFIDENTIAL Page 22
Linux Device Drivers

myblkdrv_devices = kmalloc ((myblkdrv_devs * sizeof(MYBLKDRV_DEV)),


GFP_KERNEL) ;
if (!myblkdrv_devices)
goto fail_malloc ;

memset (myblkdrv_devices, 0, (myblkdrv_devs * sizeof(MYBLKDRV_DEV)) ) ;


for (i = 0 ; i < myblkdrv_devs ; i++)
{
myblkdrv_devices[i].size = (1024 * myblkdrv_size) ;
spin_lock_init (&myblkdrv_devices[i].lock) ;
}

blk_init_queue (BLK_DEFAULT_QUEUE (myblkdrv_major), myblkdrvRequest) ;

for (i = 0 ; i < myblkdrv_devs ; i++)


{
register_disk (NULL, MKDEV (myblkdrv_major, i),
1, &myblkdrvBlkDevFops, (myblkdrv_size << 1)) ;
}

/* initialize the device specific registers, if any */


if( myblkdrv_Init ( ) != SUCCESS )
goto fail_malloc ;

return (SUCCESS) ;

fail_malloc:
read_ahead[ myblkdrv_major ] = 0 ;
if (myblkdrv_sizes)
kfree (myblkdrv_sizes) ;
blk_size[ myblkdrv_major ] = NULL ;
if (myblkdrv_blksizes)
kfree (myblkdrv_blksizes) ;
blksize_size[ myblkdrv_major ] = NULL ;

if (myblkdrv_hardsects)
kfree (myblkdrv_hardsects) ;
hardsect_size[ myblkdrv_major ] = NULL ;

if (myblkdrv_devices)
kfree (myblkdrv_devices) ;

unregister_blkdev (myblkdrv_major, DEVICE_NAME) ;


return result ;
}

CONFIDENTIAL Page 23
Linux Device Drivers

void cleanup_module (void)


{
int i ;

for (i = 0 ; i < myblkdrv_devs ; i++)


{
MYBLKDRV_DEV *dev = myblkdrv_devices + i ;
spin_lock (&dev->lock) ;
dev->usage++ ;
spin_unlock (&dev->lock) ;
}
for (i = 0 ; i < myblkdrv_devs ; i++)
{
fsync_dev (MKDEV (myblkdrv_major, i)) ;
}

unregister_blkdev (myblkdrv_major, DEVICE_NAME) ;


blk_cleanup_queue (BLK_DEFAULT_QUEUE (myblkdrv_major)) ;
myblkdrv_cleanup( ) ;

read_ahead[ myblkdrv_major ] = 0 ;

kfree (blk_size[ myblkdrv_major ]) ;


blk_size[ myblkdrv_major ] = NULL ;

kfree (blksize_size[ myblkdrv_major ]) ;


blksize_size[ myblkdrv_major ] = NULL ;

kfree (hardsect_size[ myblkdrv_major ]) ;


hardsect_size[ myblkdrv_major ] = NULL ;
kfree (myblkdrv_devices) ;
printk( "<1>" "Cleanup complete\n") ;
}
MODULE_LICENSE ("GPL");

The function of init_module, cleanup_module, open, close and ioctl are similar to character
device driver. There are some new functions introduced in this driver and description of the
same is given below.

myblkdrvCheckChange

The Linux kernel uses this function to check if the media is changed. It is useful if the block
device is a removable media like floppy disk or CD-ROM etc. This function should return TRUE if
media is changed or it should return FALSE.

myblkdrvRevalidate

CONFIDENTIAL Page 24
Linux Device Drivers

This function is called if the media change is detected and is used to update the private data
related to the device, maintained within the driver.

myblkdrvRequest

One thing is very clear form the above code is that there are no read and write functions
provided in the file operation structure. All input and output to the block device is normally
buffered by the system, user programs do not perform I/O to these devices directly. Generally
the read and write operations on the block devices are slow. Hence the system buffers the data
from such devices. The read and write operations are maintained as request in the queue and
later the queue is emptied and the read and write instructions are executed by the kernel when
it finds the free time or when the device is being unmounted.

The queue is initialized by the blk_init_queue function. The myblkdrvRequest function is called
when the queue has to be emptied.

Other functions

Implementation of the function myblkdrvTransfer is specific to the device. Since its internal to
the device driver, choice is left to the device driver writer how it has to be designed. The
function given here serves as an example for extracting the information from the request
queue and executing the read/write requests.

Testing the block device driver

Compile the two files and generate the kernel loadable module blockdrv.o. Create a device
node in /dev folder and then insert this module into the kernel. Now, create a file system on
the device and then mount this device on some folder. You can use this device and store files
just as you do on other folders in Linux

Conclusion
There are other classes of device drivers in Linux, such as network, USB etc. They are not
covered under this document. This document was just a sample of GOLD. If you are interested
in the entire GOLD mine, you know where to look for.

CONFIDENTIAL Page 25
Linux Device Drivers

Mistral Software – Corporate Profile USA


Mistral Software Inc.,
Tollway Plaza Center - Tower 2
Mistral Software is an ISO 9001:2000 certified and CMMi Level 3 15950 N.Dallas Parkway, Suite 400
appraised premier product realization company providing end-to- Dallas, TX 75248
end services for product design and development in the embedded Tel: +1-972-361- 8069
space. As a single source for both hardware and software Fax: +1-972-361-8070
engineering expertise, Mistral’s expert design and development E-mail: usa@mistralsoftware.com
services have improved the quality and accelerated the time-to-
market for a broad range of embedded systems. Our expertise India
includes Real-Time Embedded Applications, Digital Signal Mistral Software Pvt. Ltd.
Processing, Board Design, FPGA Design and VoIP Services. No.60, 'Adarsh Regent',
100 Ft. Ring Road,
Mistral's services address the niche embedded domain, covering a Domlur Extension, Bangalore - 560 071.
vast spectrum of industries like Digital Consumer Electronics, Tel: +91-80-2535 6400
Telecommunication, Wireless, Defence, Aerospace, Automotive, Fax:+91-80-2535 6444
Email: info@mistralsoftware.com
Office Automation, Semiconductor, Internet Appliances and
Industrial Applications.
Europe
Mistral Software Europe
Metzstrasse 18
D-81667 Munich, Germany
Tel: +49-89-4476-9113
Mobile:+49-172-893-0002
E-mail: europe@mistralsoftware.com

CONFIDENTIAL Page 26