Linux Device Drivers

  • May 2020
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View Linux Device Drivers as PDF for free.

More details

  • Words: 5,153
  • Pages: 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/ <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 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 #include #include #include #include #include #include #include #include #include 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 #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 #include #include #include #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 #include #include

CONFIDENTIAL

Page 16

Linux Device Drivers

#include #include #include #include #include #include #include #include #include #include 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 #include "sysdep.h" #ifdef HAVE_BLKPG_H #include #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 Mistral Software is an ISO 9001:2000 certified and CMMi Level 3 appraised premier product realization company providing end-toend services for product design and development in the embedded space. As a single source for both hardware and software engineering expertise, Mistral’s expert design and development services have improved the quality and accelerated the time-tomarket for a broad range of embedded systems. Our expertise includes Real-Time Embedded Applications, Digital Signal Processing, Board Design, FPGA Design and VoIP Services. Mistral's services address the niche embedded domain, covering a vast spectrum of industries like Digital Consumer Electronics, Telecommunication, Wireless, Defence, Aerospace, Automotive, Office Automation, Semiconductor, Internet Appliances and Industrial Applications.

USA Mistral Software Inc., Tollway Plaza Center - Tower 2 15950 N.Dallas Parkway, Suite 400 Dallas, TX 75248 Tel: +1-972-361- 8069 Fax: +1-972-361-8070 E-mail: [email protected] India Mistral Software Pvt. Ltd. No.60, 'Adarsh Regent', 100 Ft. Ring Road, Domlur Extension, Bangalore - 560 071. Tel: +91-80-2535 6400 Fax:+91-80-2535 6444 Email: [email protected]

Europe Mistral Software Europe Metzstrasse 18 D-81667 Munich, Germany Tel: +49-89-4476-9113 Mobile:+49-172-893-0002 E-mail: [email protected]

CONFIDENTIAL

Page 26

Related Documents