- Back to Home »
- Device Driver
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