0% found this document useful (0 votes)
15 views21 pages

Unit 1 Notes

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
15 views21 pages

Unit 1 Notes

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 21

Introduction To Device Drivers

Unit I
Introduction
• A device driver is a software that allows a computer’s operating
system to communicate with a hardware device (such as a printer
and keyboard).
• The device driver helps the operating system to control a hardware
device.
• Device driver is an integration of two pieces of code:
• How driver services are made available to the user space
• The part that actually communicates with the hardware & performs the
required operation.
• Device drivers are hardware dependent and specific to the OS.
• Device drivers communicate with computer hardware through a
computer bus or a communications subsystem that's connected to
the hardware.
• Different hardware devices are built by different manufacturers and
might work in different ways. Device drivers help to ensure that the
hardware device works smoothly with the specific operating system.
• Sometimes, multiple hardware devices need to work together. Device
drivers coordinate this interaction, and ensure that everything
functions harmoniously.
• 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.
• Kernel device drivers are the mechanism through which the
underlying hardware is exposed to the rest of the system.
• The device drivers 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.
• As a developer of embedded systems, you need to know how device
drivers fit into the overall architecture and how to access them from
user space programs.
• Your system will probably have some novel pieces of hardware and
you will have to work out a way of accessing them.
• In many cases, you will find that there are device drivers provided for
you and you can achieve everything you want without writing any
kernel code.
For example, you can manipulate GPIO pins and LEDs using files in
sysfs, and there are libraries to access serial buses, including SPI and
I2C.
• There are a number of reasons to be interested in the writing of Linux
device drivers.
• The rate at which new hardware becomes available (and obsolete!) alone
guarantees that driver writers will be busy for the foreseeable future.
• 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 a Linux driver available for their products, can
add the large and growing Linux user base to their potential markets.
• And the open source nature of the Linux system means that if the driver
writer wishes, the source to a driver can be quickly disseminated to millions
of users.
Role of the Device Driver
• A driver should be flexible ie the role of a device driver is providing
mechanism, and not policy.
• The distinction between mechanism and policy is one of the best
ideas behind the Unix design.
• Most programming problems can indeed be split into two parts:
“what capabilities are to be provided” (the mechanism) and “how
those capabilities can be used” (the policy).
• If the two issues are addressed by different parts of the program, or
even by different programs altogether, the software package is much
easier to develop and to adapt to particular needs.
• Where drivers are concerned, the same separation of mechanism and
policy applies.
• Since different environments usually need to use hardware in
different ways, it’s important to be as policy free as possible.
• When writing drivers, a programmer should pay particular attention
to this fundamental concept: write kernel code to access the
hardware, but don’t force particular policies on the user, since
different users have different needs.
• The driver should deal with making the hardware available, leaving all
the issues about how to use the hardware to the applications. A
driver, then, is flexible if it offers access to the hardware capabilities
without adding constraints.
• Sometimes, however, some policy decisions must be made. For
example, a digital I/O driver may only offer byte-wide access to the
hardware in order to avoid the extra code needed to handle
individual bits.
• You can also look at your driver from a different perspective: it is a
software layer that lies between the applications and the actual
device.
• This privileged role of the driver allows the driver programmer to
choose exactly how the device should appear: different drivers can
offer different capabilities, even for the same device.
• The actual driver design should be a balance between many different
considerations.
• For instance, a single device may be used concurrently by different
programs, and the driver programmer has complete freedom to
determine how to handle concurrency.
• One major consideration is the trade-off between the desire to
present the user with as many options as possible and the time you
have to write the driver, as well as the need to keep things simple so
that errors don’t creep in.
• Policy-free drivers have a number of typical characteristics. These
include
• support for both synchronous and asynchronous operation,
• the ability to be opened multiple times,
• the ability to exploit the full capabilities of the hardware,
• and the lack of software layers to “simplify things” or provide policy-related
operations.
• Drivers of this sort not only work better for their end users, but also
turn out to be easier to write and maintain as well.
• Being policy-free is actually a common target for software designers.
Kernel Modules Versus Applications
• There are various differences between a kernel module and an
application.
• While most small and medium-sized applications perform a single
task from beginning to end, every kernel module just registers itself in
order to serve future requests, and its initialization function
terminates immediately.
• In other words, the task of the module’s initialization function is to prepare
for later invocation of the module’s functions; it’s as though the module were
saying, “Here I am, and this is what I can do.”
• The module’s exit function (hello_exit in the example)gets invoked just before
the module is unloaded. It should tell the kernel, “I’m not there anymore;
don’t ask me to do anything else.”
• This kind of approach to programming is similar to event-driven
programming, but while not all applications are event-driven, each
and every kernel module is.
• Another major difference between event-driven applications and
kernel code is in the exit function: whereas an application that
terminates can be lazy in releasing resources or avoids clean up
altogether, the exit function of a kernel module must carefully undo
everything the init function built up, or the pieces remain around
until the system is rebooted.
• As a programmer, you know that an application can call functions it
doesn’t define: the linking stage resolves external references using
the appropriate library of functions.
For ex. printf is one of those callable functions and is defined in libc.
A module, on the other hand, 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.
The printk function used in hello.c earlier, for example, is the version
of printf defined within the kernel and exported to modules. It
behaves similarly to the original function, with a few minor
differences, the main one being lack of floating-point support.
• Because no library is linked to kernel modules, source files should never
include the usual header files, <stdarg.h> and very special situations being
the only exceptions. Only functions that are actually part of the kernel
itself may be used in kernel modules.
• Anything related to the kernel is declared in headers found in the kernel
source tree you have set up and configured; most of the relevant headers
live in include/linux and include/asm, but other subdirectories of include
have been added to host material associated to specific kernel
subsystems.
• Another important difference between kernel programming and
application programming is in how each environment handles faults. 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 kills the current process at least, if not the
whole system.
User Space and Kernel Space
• A module runs in kernel space, whereas applications run in user
space. This concept is at the base of operating systems theory.
• The role of the operating system, in practice, is to provide programs
with a consistent view of the computer’s hardware.
• In addition, the operating system must account for independent
operation of programs and protection against unauthorized access to
resources.
• This nontrivial task is possible only if the CPU enforces protection of
system software from the applications.
• Every modern processor is able to enforce this behavior.
• The chosen approach is to implement different operating modalities
(or levels)in the CPU itself.
• The levels have different roles, and some operations are disallowed
at the lower levels; program code can switch from one level to
another only through a limited number of gates.
• Unix systems are designed to take advantage of this hardware
feature, using two such levels.
• All current processors have at least two protection levels, and some,
like the x86 family, have more levels; when several levels exist, the
highest and lowest levels are used.
• Under Unix, the kernel executes in the highest level (also called
supervisor mode), where everything is allowed, whereas applications
execute in the lowest level (the so-called user mode), where the
processor regulates direct access to hardware and unauthorized
access to memory.
• We usually refer to the execution modes as kernel space and user
space.
• These terms encompass not only the different privilege levels
inherent in the two modes, but also the fact that each mode can have
its own memory mapping—its own address space—as well.
• Unix transfers execution from user space to kernel space whenever
an application issues a system call or is suspended by a hardware
interrupt.
• Kernel code executing a system call is working in the context of a
process—it operates on behalf of the calling process and is able to
access data in the process’s address space.
• Code that handles interrupts, on the other hand, is asynchronous
with respect to processes and is not related to any particular process.
• The role of a module is to extend kernel functionality; modularized
code runs in kernel space.
• Usually a driver performs both the tasks outlined previously: some
functions in the module are executed as part of system calls, and
some are in charge of interrupt handling.
Concurrency in the Kernel
• One way in which kernel programming differs greatly from
conventional application programming is the issue of concurrency.
• Most applications, with the notable exception of multithreading
applications, typically run sequentially, from the beginning to the
end, without any need to worry about what else might be happening
to change their environment.
• Kernel code does not run in such a simple world, and even the
simplest kernel modules must be written with the idea that many
things can be happening at once.
• There are a few sources of concurrency in kernel programming.
• Naturally, Linux systems run multiple processes, more than one of
which can be trying to use your driver at the same time.
• Most devices are capable of interrupting the processor; interrupt
handlers run asynchronously and can be invoked at the same time
that your driver is trying to do something else.
• Several software abstractions (such as kernel timers)run
asynchronously as well.
• As a result, Linux kernel code, including driver code, must be
reentrant—it must be capable of running in more than one context at
the same time.
• Data structures must be carefully designed to keep multiple threads
of execution separate, and the code must take care to access shared
data in ways that prevent corruption of the data.
• Writing code that handles concurrency and avoids race conditions
(situations in which an unfortunate order of execution causes
undesirable behavior)requires thought and can be tricky.
• Proper management of concurrency is required to write correct
kernel code; for that reason, every sample driver has to be written
with concurrency in mind.
• There are certain kernel primitives available for concurrency
management.
• A common mistake made by driver programmers is to assume that
concurrency is not a problem as long as a particular segment of code
does not go to sleep (or “block”).
• Even in previous kernels (which were not preemptive), this
assumption was not valid on multiprocessor systems.
• In 2.6, kernel code can (almost)never assume that it can hold the
processor over a given stretch of code.
• If you do not write your code with concurrency in mind, it will be
subject to catastrophic failures that can be exceedingly difficult to
debug.

You might also like