Posted by : Unknown Monday, July 1, 2013

ABSTRACT

          Programmers make use of libraries while writing application programs. These  libraries allow  them  to  access  hardware  and devices attached to the machine. (Ex :- Network connections, file system, memory,  etc. )

All these libraries allow application program to make demands on the kernel. In order to fulfill those demands, the kernel relies upon "device drivers"  to talk to all different  types of devices  it might encounter. This enables the kernel to present a uniform interface to applications regardless of the underlying technology.

There is a wide variety of hardware available. This means that a wide variety of software is required to operate this hardware. This is the job of device drivers. Device Drivers are the nuts and bolts of any operating system.

Device Drivers take on a special role in the Linux kernel. They are distinct “black boxes” that make a particular piece of hardware respond to a well-defined internal programming interface; they hide completely the details of how the device works. User activities are performed by means of a set of standardized calls that are independent of the specific driver; mapping those calls to device-specific operations that act on real hardware is then the role of the device driver. This programming interface is such that drivers can be built separately from the rest of the kernel, and plugged in at runtime when needed. This modularity makes Linux drivers easy to write, to the point that there are now hundreds of them available.   

The focus here is on various header files and modules for writing device drivers,what functions need to be written, outline the supporting kernel functions that are available, explain how to initialize the driver and how memory is requested and allocated in an efficient manner.

INTRODUCTION

As the popularity of the Linux system continues to grow, the interest in writing Linux device drivers steadily increases. Most of Linux is independent of the hardware it runs on, and most users can be unaware of hardware issues. Without device drivers there is no functioning system.

There are number of reasons to be interested in writing of Linux device drivers.
* The rapid growth of the hardware industry

* Individuals may need to know about drivers in order to gain access                    
   to a particular device that is of interest to them.

* Hardware vendors, by making Linux drivers available to their products,
   can add the large and growing Linux user to base to their potential markets.

   
A Device Driver is software that controls the device and exports a usable interface that allows other programs to interact with this particular device. A device driver doesn't necessarily control a physical hardware peripheral.

Device drivers are a layer between the kernel and the hardware that it controls. As such it is a very useful abstraction, since it greatly simplifies the kernel: instead of having the kernel talks to each device itself it exports a well defined interface and leaves this task to the individual device drivers. This also means that the kernel can be (and is ) written without knowledge of the various different devices that will be developed later on, as long as they can be accommodated within the framework of the defined models.


The Role of the Device Driver
 As a programmer, he will be able to make his own choices about his driver, choosing an acceptable trade off between the programming time required and the flexibility of the result.  

When writing drivers, a programmer should pay particular attention to this fundamental concept: write kernel code to access hardware, but don’t force particular policies on the user, since different users have different needs.

Modules 

Modules consist of object code linkage and removable at runtime, usually comprising a number of functions (at least two). This code is integrated into the already running kernel with equal rights, which means that it runs in system mode. 

Compiling a Kernel Module

gcc –D_KERNEL_ -D_SMP_ -DMODULE –DMODVERSIONS
      –I/usr/src/linux/include –Wall –O2 –o module.o –c module.c          



  _KERNEL_    Code that will be inserted into the kernel has to define
                          _KERNEL_ to see the whole thing.          

  _SMP_           The kernel can be compiled for either SMP (Symmetric Multi
                          Processor ) or UP (Unit Processor ) machines.

  MODULE      Must be defined for code that is compiled as a kernel module.

  MODVERSION A safe guard against kernel and module incompatibilities.
                                        
Setting up a device
Each individual device can be thus be uniquely identified by the device type(block or character), the major number of the device driver and its minor number. Setting up a device therefore simply requires the command :
$mknod /dev/name type major minor
                               type – c( character ) or b ( block )

The focus here will be on character devices and block devices, modularity, debugging techniques, coding issues and portability.

DEVICE DRIVER


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 control from the user process to the driver routine. Control is returned to the user process when the driver routine has completed. A device driver may be shared simultaneously by user applications and must be protected to ensure its own integrity. 
A device driver provides 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.
  • A user-defined section of the kernel that allows a program or a peripheral device to appear as a `` /dev '' device to the rest of the system's software.
                                       
DEVICE CLASSES

Device Drivers can be split up into different classes according to their behavior. They are:-

  • CHARACTER DEVICES  They are read byte by byte sequentially and access to them is not cached by the buffer system.

  • BLOCK DEVICES They allow random access, are read in multiples of their block size, and access to them goes through the buffer cache system.
USER SPACE & KERNEL SPACE

A module runs in the so-called kernel-space, whereas application runs in user-space. This concept is at the base of operating system. Linux operates in two modes, user-mode and supervisor-mode (also called as kernel-mode). Under Linux, the kernel executes in the highest level (i.e. supervisor-mode), where everything is allowed, whereas applications execute in the lowest level (user-mode), where the processor regulates the direct access to hardware and unauthorized access to memory.


APPLICATIONS versus KERNEL MODULES(DEVICE DRIVERS)

  • Applications perform a single task from beginning to end whereas a module registers itself in order to serve future requests.

  • An application can call functions it doesn’t define, the linking stage resolves external reference using the appropriate library of functions whereas a module is linked only to the kernel, and the only functions it can call are the ones exported by the kernel; there are no libraries to link to.

  • Applications include various header files whereas in a module source file should never include the usual header files. Only functions that are actually part of the kernel is declared in headers found in include/linux and include/asm inside the kernel source. 

  • Application run in user-space whereas a module runs in kernel-space.

  • The last difference between kernel programming and application programming is in how each environment handles faults: whereas a segmentation fault is harmless during application development and a debugger can always be used to trace the error to the problem in the source code, a kernel fault is fatal may cause system crash.
Programming Guidelines

  • Don’t use floating-point arithmetic.

  • Keep the code as clean and comprehensive as possible.

  • Avoid encoding security policy in their code.

  • Don’t busy wait in your driver. This may result system hang.
Building Modules

Example - hello.c

#include <linux/module.h>

#if defined(CONFIG_SMP)       /* if the kernel is compiled for SMP */
#define _SMP_                 /* CONFIG_SMP will be defined and we */   #endif                        /* can define SMP appropriately      */

#if defined(CONFIG_MODVERSIONS)/* and also for MODVERSIONS        */
#define MODVERSIONS
#include <linux/modversions.h>
#endif


#include<linux/kernel.h>

int init_module(void)               /* KERN_DEBUG sets the priority*/
{                                   /* of the printed message <7>  */
   printk(KERN_DEBUG “Hello, Kernel!\n”);
   return 0;
}

void cleanup_module(void)
{
  printk(KERN_DEBUG “Good-Bye, Kernel!\n”);
}



1. Compiling the code
$gcc –D_KERNEL_ -I/usr/src/linux/include –DMODULE –Wall –c hello.c –o hello.o
$

2. Insert the module hello.o into the kernel.
    To insert a module or remove a module you must be root.
$insmod hello.o
Hello, Kernel!
$

3. if nothing is displayed (this may happen because KERN_DEBUG has low priority ) check by using dmesg
$dmesg  | tail –n1
Hello, Kernel!
$

4. Remove the module
 $rmmod hello.o
 Good-Bye, Kernel!
$

How it Works
init_module( ) is called at the load time and is responsible for setting up internal data structures, initialize the hardware and perform any other tasks before the device is invoked for the first time.
cleanup_module( ) takes care of shutting down the device and releasing any resources that the device may have occupied.

A little message is printed to the kernel buffer when the module is loaded and unloaded.



CHARACTER DEVICES

Character device 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.
int register_chrdev( unsigned int major, const char *name,
                     struct file_operations *fops );

Argument
Meaning
major


name


fops
Major number of the device, Zero for dynamic assignment

This is used for the registration with proc/devices

File operation structure – defines how the driver communicates with the outside world
  
 Returns – Non-negative (either positive integer or zero) on success
                  Negative on failure

From the above function declaration it’s clear that the devices in Linux are also files (special file).

When the character device is registered with the kernel, its file_operations structure and name is added to the global chr_devs array of device_struct structures where the major number indexes it. This is called the character device switch table.

A sample character device

The character driver implementation will be explained with an example called the Schar.

Schar.c starts out with forwarding declarations of the functions that define our implementation of the file_operations structure.

/* forward declarations of fops */
static ssize_t schar_read(struct file *file, char *buf, size_t count, loff_t *offset);
static ssize_t schar_write(struct file *file, char *buf, size_t count, loff_t *offset);
static unsigned int schar_poll(struct file *file, poll_table *wait);
static int schar_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);
static int schar_mmap(struct file *file, struct vm_area_struct *vma);
static int schar_open(struct inode, struct file *file);
static int schar_release(struct inode *inode, struct file *file);

static struct file_operations schar_fops = {
  NULL,
  schar_read,
  schar_write,
  NULL,
  schar_poll,
  schar_ioctl,
  NULL,
  schar_open,
  NULL,
  schar_release,
  NULL,
  NULL,
  NULL
};


The  MSG Macro
Its an alternative way to print debugging statements that can prove quite handy. To make the code more readable and modular we put the definition of the MSG in the schar.h

#define DEBUG

#ifdef DEBUG
#define MSG(string, args...) printk(KERN_DEBUG “schar:”string,##args)
#else
#define MSG(string, args...)
#endif


Registering the Device
The entry point of Schar is init_module.Here the device is registered.

int init_module(void)
{
  int res;
 
  if(schar_name == NULL)
    schar_name == “schar”;
  /* register device with the kernel */
  res = register_chrdev(SCHAR_MAJOR, schar_name, & schar_fops);
    if(res ) {
      MSG(“Can’t register device with the kernel\n”);
      return res;
    }
}

schar_name is passed as parameter
SCHAR_MAJOR = 42 defined in schar.h


After the successful call to register_chrdev, the device is registered with the kernel and the given file operations structure is added to the character switch table.


Module Usage Count

The kernel needs to keep track of usage information for each module loaded in the system. The two macros that modify usage count are:-
MOD_INC_USE_COUNT  –   increments the count
MOD_DEC_USE_COUNT -  decrements the count
Its up to the device programmer to maintain a usage count that at the same protects the device from being unloaded unexpectedly while making certain that the module is unloaded when it is unused.

OPEN and RELEASE

The init_module only loads the module and the device sits idle in the system, until someone opens the associated device. When the device is opened by a process, schar_open is invoked.  In our example(Schar) the usage count is incremented.

static int schar_open(struct inode *inode, struct file *file)
{
  /* increment the usage count */
  MOD_INC_USE_COUNT;
 
  if(file->f_mode & FMODE_READ) {
    MSG(“Opened for reading”);
  }
}

The file argument passed to schar_open is the in-kernel description of the file descriptor returned to applications.

schar_release decrements the usage count by 1. There is nothing else to be done, Schar does not keep any memory on a per-open basis.

static int schar_release(struct inode *inode, struct file *file)
{
  MOD_DEC_USE_COUNT;
  MSG(“schar_release\n”);
  return 0;
}


Reading and Writing the Device

Before reading data from the device, make sure that data is available. If data is available then return that data to the requested process otherwise reading processes must be made inactive until data is available.

static ssize_t schar_read(struct file *file, char *buf, size_t count, loff_t *offset);
static ssize_t schar_write(struct file *file, char *buf, size_t count, loff_t *offset);

As far as data transfer is concerned, the main issue associated with the two device methods is the need to transfer data between t he kernel address space and the user address space. The operation cannot be carried out through pointers in the usual way, or through memcpy. User-space addresses cannot be used in the kernel space.

The Current Task

current is a macro that represents the currently running process in the form of a task structure. The task structure can found in the linux/sched.h


 Wait Queues

Wait queues are used to let the current task be put to sleep when no data is available and then wake it up when new data is available. This frees up the system and allows it to run other processes.

struct wait_queue {
  struct task_struct *task;
  struct wait_queue *next;
};
task contains relevant state information in the task structure about the process being put to sleep.
next is the pointer to tne next entry in the wait queue.

Putting the processes to sleep
void interruptible_sleep_on (struct wait_queue **p)
long interruptible_sleep_on_timeout ( struct wait_queue **p, long  
                                      timeout )

These macros put the process to sleep with a state, but allow it to wake up on signals. The timeout variant calls schedule_timeout  internally and thus enables to let the process wake on its own when it expires.

void sleep_on (struct wait_queue **p)
long sleep_on_timeout(struct wait_queue **p, long timeout)

The semantics of the two are exactly same as the function above, except that the state is set to TASK_UNINTERRUPTIBLE.

Sooner or later the sleeping process must be brought to life. This is done using the macros :-
wake_up_interruptible(struct wait_queue **p)
wake_up(struct wait_queue **p)


Seeking a Device

llseek implementation
The llseek function implements the lseek and llseek system calls. If the llseek method is missing from the devices operations, the default implementation in the kernel  performs seek from the beginning of the file and from the current position by modifying fops->f_pos, the current reading/writing position within the file.


ioctl

Sometimes it can be useful to change or get the parameters from a running driver, instead of reconfiguring it and runnig a new compile. For some devices this is not even an option if it is constantly in use and thus cannot be removed from the system. ioctl is an entry point in the driver that will let to either set or retrieve settings while it’s still running.

4 types of ioctl functions distinguished by Linux are


  _IO(base, command)             define the selected command. No data is transferred to or from the application issuing the ioctl.

  _IOR(base, command, size)       A reading ioctl, as seen from the application.
                                                                                size -size of the argument to be transferred back.        
  _IOW(base, command, size)      A writing ioctl, ss seen from the application.

  _IORW(base, command, size)   A reading and writing ioctl.
  
In addition, macros are provided to check the validity of the command
sent..

PROC file system interface

The proc file system works much like a real file system, in that it mounted and reading or writing is accomplished by using standard file utilities. The data read ffrom a file is generated on the fly by the module or kernel and can provide run time statics and other relevant information. Writable files can be used to change configuration or behavior of the driver.

sysctl

The best place to register an entry is under the the proc/sys directory. The entries can be retrieved with the sysctl system call.
  
Memory Management

Types of memory locations
  • Physical This is the “real” address.
  • Virtual   Only the CPU and the kernel knows about virtual address
  • Bus         All devices outside the CPU.

The various functions and macros provided by Linux to convert the three types of address back and forth are:-

unsigned long virt_to_phys(void *address)
void *phys_to_virt(unsigned long address)

unsigned long virt_to_bus(void *address)
void *bus_to_virt(unsigned long address)


Getting Memory in Device Drivers

Memory is allocated in chunks of the PAGE_SIZE on the target machine. The Intel platform has a page of 4Kb whereas the Alpha uses 8Kb sized pages and it is not a user configurable option. The programmer should keep in mind that the page size varies depending on the platform.

unsigned long _get_free_pages(int gfp_mask, unsigned long order)
unsigned long _get_free_page(int gfp_mask) /*allocates 1 page */

gfp_mask – describes priority and attributes of the page.
order        -  Pages to be allocated in orders of 2 i.e. 2order       

Freeing Memory

It is extremely important to free memory once done using it.

  void free_page(unsigned long addr)  Free the page(s) at addr

  void free_pages(unsigned long addr, free_pages expect to supply
                         unsigned long order)  it with the order  
kmalloc

Linux provides kmalloc as an alternative to get_free_pages, which lets to allocate memory of any size.

void *kmalloc(size_t size, int flags)


void kfree(const void *addr)
kfree will free the memory previously allocated by kmalloc.

The get_free_pages and kmalloc return memory that is physically contiguous.

vmalloc

vmalloc provides memory that is contiguous in the virtual address space and thus serves a different purpose. It does so by allocating pages separately and manipulating the page tables.

void *vmalloc(unsigned long size)
void vfree(void *addr)

Interrupt Handling

Interrupts are used to signal the availability of data or other hardware conditions to the device driver and let it take appropriate action. Interrupt is a way for a device to get the device drivers attention and tell it that the device needs to be serviced somehow. This could be to signal that data is available for transfer or that a previously queued command has now completed and the device is ready for a new one.

Allocating Interrupts

int request_irg(unsigned int irq, void(*handler)(int, void*, struct pt_regs *), unsigned long irqflags, const char *devname, void *dev_id)


Argument
Meaning
irq

handler(int irq, void *dev_id,
struct pt_regs *regs)


irqflags


devname

dev_id
The actual IRQ that you wish to handle

When the interrupt occurs this is the function that gets called. This is IRQ handler

This controls the behavior of the interrupt.

The name that is listed in /proc/interrupts

Helps supports sharing of interrupts.


Returns – 0 on success and failure is indicated by negative error.

Unregistering an IRQ handler is done with free_irq.
  void free_irq(unsigned int irq, void *dev_id);

Getting appropriate IRQ
Before one can register a handler to use with your driver you have to find out what irq to use. This is highly hardware dependent, both with regards to the type of peripheral device and the host bus.

Linux provides interrupt detection for devices. They are :-
  unsigned long probe_irq_on(void)
  int probe_irq_off(unsigned long  unused)
 probe_irq_on intitiates the probing sequence and probe_irq_off ends it. In between you should put code that will trigger an IRQ from the device and this will then be t he return value from probe_irq_off.
The IRQ Handler
Handler deals with the device interrupts. The job of the handler is to acknowledge the interrupt ans service the device in some way.
BLOCK DEVICES

They allow random access, are read in multiples of their block size, and access to them goes through the buffer cache system.
Size issues

There are two sector sizes associated with a block device, hardware and software sector size. The former is how data is arranged on the physical media controlled by the device while the latter is the arrangement within the device.
Registering a Block Device

The block device is registered much the same way as the character devices.

Register_blkdev(unsigned int major, const char* name, struct file_operations *fops)

ioctl for block devices

Since block devices are used to host file systems, it seems only appropriate that all block devices should accept some standard ioctl commands. The most common and standard ones are

BLKFLSBUF      Block flush buffers.

BLKGETSIZE    Block get size. Returns the size of the device in units of 1024 bytes

BLKSSZGET     Block get sector size. Returns the software sector size of the block device.
The Request function

The request function is the backbone of the block device. In contrast to character devices which receive a stream of data, block devices process requests instead. A request is either a read or a write and it is the job of the request function to either to retrieve or store data sent to it on the media it controls.

Requests are stored in lists of structures, each of which are of the type struct request. The function does not need to traverse the list itself, but instead accesses the request via the CURRENT macro.

The Buffer Cache

Blocks of data written and read from block devices get cached in the buffer cache. This improves system performance, because if a process wants to read a block of data just read or written, it can be served directly from the buffer cache instead of issuing a new read from the media. Internally this cache is a doubly linked list of buffer head structures indexed by a hash table.
DEBUGGING

Kernel level code does not segmentation fault in the ordinary sense and produce core dumps to examine and so debugging device driver is very different from regular debugging. In this case, all we can do is go back to the source code and work it through step by step.
Debugging Modules 

Unfortunately it is not possible to single step kernel code like ordinary applications. The best way to go about debugging your own modules is to strategically add printk statements in troublesome areas and work your way down from there.
The Magic Key

The most unfortunate bugs are the ones that crash the system completely. In these situations the Magic sysRq Key, or Sytem Attention Key(SAK), can be of great attention. This option can be enabled while configuring the kernel. It doesn’t add any overhead to normal system operation and it can be the only way to resolve a complete hang.
Kernel Debugger - KDB 

There is a way to debug a running kernel safely that ha minimal impact on normal system operation. The debugger can be invokerd in different places.
At boot, pass the kdb parameter to lilo and the debugger will be started. 
During system operation, the debugger can be entered manually by pressing the pause key.
Portability 
The device driver created should naturally run on as many as platforms as possible. Luckily the exposed API is very portable, and the problems that arise are mainly due to platform differences. Most of the portability issues are :-

Data Types
Linux defines standard types that have a given size on any platform.
_u8, …………………… _u64  Unsigned variables
_s8,……………………. _s64   Signed variables

Its always a good idea to use these when a specific size of variables is needed, since there is no guarantee that a long is the same size on all platforms.

Endianess
Platforms like Intel or Alpha use little-endian byte orientation, which means that the most and least significant byte values are swapped. PowerPC and Sparc CPU’s are some of the big-endian platforms.
In most cases there is no need to worry about the target endianess, but if you are building or retrieving data with a specific orientation, converting between the two will be needed.
For big-endian _BIG_ENDIAN_BITFIELD is defined and _LITTLE_ENDIAN_BITFIELD  for little-endian.

Conclusion

This report have detailed about device drivers, types of device drivers, how to write a hardware character device driver and a block device driver for the Linux operating system.Also outlined how to access hardware memory. We have also presented the kernel programming environment, as well as the supporting functions available to write a device driver. Debugging issues and portability considerations are also outlined. Though no sample device driver was implemented but with these information its not difficult writing one!

Reference:
·                    Beginning LINUX programming       Richard Stones & Niel Mattew  
                                                               Wrox Publications

·                    Linux Device Drivers 2nd Edition      Alessandro Rubini & Jonathen 
Corbet  O’Reilly & Associates

·                    Linux Kernel Internals 2nd Edition   M Beck, H Bome, M Dziadzka
                                                          U Kuntz, R Magnus, D Verworner Pearson Education

·                    Linux Manual Pages     
  

Leave a Reply

Subscribe to Posts | Subscribe to Comments

Blog Archive

- Copyright © Seminar Sparkz Inc -- Powered by Semianr Sparkz Inc - Designed by Shaik Chand -