Saturday, October 2, 2010

Linux Device Drivers. SCULL #1

"The Project 2501"

#ifndef CHAR_DRIVER
#define CHAR_DRIVER SCULL
#endif

# The character driver is suitable for most simple harware devices.

# The scull acts on a memory area as it were a device.

# The Internal Representation of Device Numbers :

Within the kernel, the dev_t type (defined in ) is used to hold device
numbers—both the major and minor parts. As of Version 2.6.0 of the kernel, dev_t is
a 32-bit quantity with 12 bits set aside for the major number and 20 for the minor number

To obtain the major or minor parts of a dev_t, use:

MAJOR(dev_t dev);
MINOR(dev_t dev);
If, instead, you have the major and minor numbers and need to turn them into a dev_t,
use:
MKDEV(int major, int minor);

# Allocating and Freeing Device Numbers

One of the first things your driver will need to do when setting up a char device is to
obtain one or more device numbers to work with. The necessary function for this
task is register_chrdev_region, which is declared in :

int register_chrdev_region(dev_t first, unsigned int count,char *name);

Here, first is the beginning device number of the range you would like to allocate.
The minor number portion of first is often 0, but there is no requirement to that
effect.

The count is the total number of contiguous device numbers you are requesting.
*** Note that, if count is large, the range your request could spill over to the next major
number.

The name is the name of the device that should be associated
with this number range; it will appear in /proc/devices and sysfs.

As with most kernel functions, the return value from register_chrdev_region will be 0
if the allocation was successfully performed. In case of error, a negative error code
will be returned, and you will not have access to the requested region.

register_chrdev_region works well if you know ahead of time exactly which device
numbers you want. Often, however, you will not know which major numbers your
device will use; there is a constant effort within the Linux kernel development com-
munity to move over to the use of dynamicly-allocated device numbers

The kernelwill happily allocate a major number for you on the fly, but you must request this
allocation by using a different function:

int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,unsigned int count, char *name);

With this function, dev is an output-only parameter that will, on successful comple-
tion, hold the first number in your allocated range.

The firstminor should be the requested first minor number to use, it is usually 0.

The count and name parameters work like those given to register_chrdev_region

Device numbers are freed with:

void unregister_chrdev_region(dev_t first, unsigned int count);

The above functions allocate device numbers for your driver’s use, but they do not
tell the kernel anything about what you will actually do with those numbers. Before a
user-space program can access one of those device numbers, your driver needs to
connect them to its internal functions that implement the device’s operations

# Dynamic Allocation of Major Numbers

The chances of a static number having already been assigned for the use
of your new driver are small, however, and new numbers are not being assigned. So,
as a driver writer, you have a choice: you can simply pick a number that appears to
be unused, or you can allocate major numbers in a dynamic manner.

Picking a number may work as long as the only user of your driver is you; once your driver is more
widely deployed, a randomly picked major number will lead to conflicts and trouble.

Thus, for new drivers, we strongly suggest that you use dynamic allocation to obtain
your major device number, rather than choosing a number randomly from the ones
that are currently free. In other words, your drivers should almost certainly be using
alloc_chrdev_region rather than register_chrdev_region.

The disadvantage of dynamic assignment is that you can’t create the device nodes in
advance, because the major number assigned to your module will vary. For normal
use of the driver, this is hardly a problem, because once the number has been
assigned, you can read it from /proc/devices.*

if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "scull");
} else {
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,

This can give us an option of both satic and dynamic allocation of major and minor numbers.
we can supply major no through command line.

# Some Important Data Structures

# 1 struct file_operations.
# 2 struct inode.
# 3 struct file.

# 1 File Operations

So far, we have reserved some device numbers for our use, but we have not yet con-
nected any of our driver’s operations to those numbers. The file_operations struc-
ture is how a char driver sets up this connection. The structure, defined in ,
is a collection of function pointers.

As you read through the list of file_operations methods, you will note that a number
of parameters include the string __user(_ _). This annotation is a form of documentation,
noting that a pointer is a user-space address that cannot be directly
dereferenced. For normal compilation, __user has no effect, but it can be used by
external checking software to find misuse of user-space addresses.

struct module *owner

The first file_operations field is not an operation at all; it is a pointer to the
module that “owns” the structure. This field is used to prevent the module from
being unloaded while its operations are in use. Almost all the time, it is simply
initialized to THIS_MODULE, a macro defined in .

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

Used to retrieve data from the device. A null pointer in this position causes the
read system call to fail with -EINVAL (“Invalid argument”). A nonnegative return
value represents the number of bytes successfully read (the return value is a
“signed size” type, usually the native integer type for the target platform).

ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t);

Initiates an asynchronous read—a read operation that might not complete
before the function returns. If this method is NULL, all operations will be pro-
cessed (synchronously) by read instead.

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

Sends data to the device. If NULL, -EINVAL is returned to the program calling the
write system call. The return value, if nonnegative, represents the number of
bytes successfully written.

ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *);

Initiates an asynchronous write operation on the device.

# The File Structure

struct file, defined in , is the second most important data structure
used in device drivers. Note that a file has nothing to do with the FILE pointers of
user-space programs. A FILE is defined in the C library and never appears in kernel
code. A struct file, on the other hand, is a kernel structure that never appears in
user programs.

The file structure represents an open file. (It is not specific to device drivers; every
open file in the system has an associated struct file in kernel space.) It is created by
the kernel on open and is passed to any function that operates on the file, until
the last close. After all instances of the file are closed, the kernel releases the data
structure.

mode_t f_mode;

The file mode identifies the file as either readable or writable (or both), by means
of the bits FMODE_READ and FMODE_WRITE. You might want to check this field for
read/write permission in your open or ioctl function, but you don’t need to check
permissions for read and write, because the kernel checks before invoking your
method. An attempt to read or write when the file has not been opened for that
type of access is rejected without the driver even knowing about it.

loff_t f_pos;

The current reading or writing position. loff_t is a 64-bit value on all platforms
(long long in gcc terminology). The driver can read this value if it needs to know
the current position in the file but should not normally change it; read and write
should update a position using the pointer they receive as the last argument
instead of acting on filp->f_pos directly. The one exception to this rule is in the
llseek method, the purpose of which is to change the file position.

unsigned int f_flags;

These are the file flags, such as O_RDONLY, O_NONBLOCK, and O_SYNC. A driver should
check the O_NONBLOCK flag to see if nonblocking operation has been requested (we
discuss nonblocking I/O in the section “Blocking and Nonblocking Operations”
in Chapter 1); the other flags are seldom used. In particular, read/write permis-
sion should be checked using f_mode rather than f_flags. All the flags are
defined in the header .

struct file_operations *f_op;

The operations associated with the file. The kernel assigns the pointer as part of
its implementation of open and then reads it when it needs to dispatch any oper-
ations. The value in filp->f_op is never saved by the kernel for later reference;
this means that you can change the file operations associated with your file, and
the new methods will be effective after you return to the caller. For example, the
code for open associated with major number 1 (/dev/null, /dev/zero, and so on)
substitutes the operations in filp->f_op depending on the minor number being
opened. This practice allows the implementation of several behaviors under thesame major
number without introducing overhead at each system call. The abil-
ity to replace the file operations is the kernel equivalent of “method overriding”
in object-oriented programming.

void *private_data;

The open system call sets this pointer to NULL before calling the open method for
the driver. You are free to make its own use of the field or to ignore it; you can
use the field to point to allocated data, but then you must remember to free that
memory in the release method before the file structure is destroyed by the ker-
nel. private_data is a useful resource for preserving state information across sys-
tem calls and is used by most of our sample modules.

struct dentry *f_dentry;

The directory entry (dentry) structure associated with the file. Device driver writ-
ers normally need not concern themselves with dentry structures, other than to
access the inode structure as filp->f_dentry->d_inode.

# The inode Structure

The inode structure is used by the kernel internally to represent files. Therefore, it is
different from the file structure that represents an open file descriptor. There can be
numerous file structures representing multiple open descriptors on a single file, but
they all point to a single inode structure.
The inode structure contains a great deal of information about the file. As a general
rule, only two fields of this structure are of interest for writing driver code:
dev_t i_rdev;

For inodes that represent device files, this field contains the actual device number.
struct cdev *i_cdev;
struct cdev is the kernel’s internal structure that represents char devices; this
field contains a pointer to that structure when the inode refers to a char device
file.

The type of i_rdev changed over the course of the 2.5 development series, breaking a
lot of drivers. As a way of encouraging more portable programming, the kernel devel-
opers have added two macros that can be used to obtain the major and minor num-
ber from an inode:

unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);

In the interest of not being caught by the next change, these macros should be used
instead of manipulating i_rdev directly.

# Char Device Registration

the kernel uses structures of type struct cdev to represent char
devices internally. Before the kernel invokes your device’s operations, you must allocate
and register one or more of these structures

void cdev_init(struct cdev *cdev, struct file_operations *fops);

Either way, there is one other struct cdev field that you need to initialize. Like the
file_operations structure, struct cdev has an owner field that should be set to
THIS_MODULE.

Once the cdev structure is set up, the final step is to tell the kernel about it with a call to:

int cdev_add(struct cdev *dev, dev_t num, unsigned int count);

Here, dev is the cdev structure, num is the first device number to which this device
responds, and count is the number of device numbers that should be associated with
the device. Often count is one, but there are situations where it makes sense to have
more than one device number correspond to a specific device. Consider, for exam-
ple, the SCSI tape driver, which allows user space to select operating modes (such as
density) by assigning multiple minor numbers to each physical device.
There are a couple of important things to keep in mind when using cdev_add. The
first is that this call can fail. If it returns a negative error code, your device has not
been added to the system. It almost always succeed, however, and that brings up
the other point: as soon as cdev_add returns, your device is “live” and its operations
can be called by the kernel. You should not call cdev_add until your driver is com-
pletely ready to handle operations on the device.

To remove a char device from the system, call:

void cdev_del(struct cdev *dev);

Clearly, you should not access the cdev structure after passing it to cdev_del.

# Device Registration in scull

Internally, scull represents each device with a structure of type struct scull_dev. This
structure is defined as:

struct scull_dev
{
struct scull_qset *data; /* Pointer to first quantum set */
int quantum;
/* the current quantum size */
int qset;
/* the current array size */
unsigned long size;
/* amount of data stored here */
unsigned int access_key; /* used by sculluid and scullpriv */
struct semaphore sem;
/* mutual exclusion semaphore*/
struct cdev cdev;
/* Char device structure */
};

the struct cdev interfaces our device to the kernel.

static void scull_setup_cdev(struct scull_dev *dev, int index)
{
int err, devno = MKDEV(scull_major, scull_minor + index);
cdev_init(&dev->cdev, &scull_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_fops;
err = cdev_add (&dev->cdev, devno, 1);
/* Fail gracefully if need be */
if (err)
printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}

cdev_init must be called to perform the initialization of that structure

No comments:

Post a Comment