Linux Device Drivers (3rd Edition) [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Linux Device Drivers (3rd Edition) [Electronic resources] - نسخه متنی

Jonathan Corbet, Greg Kroah-Hartman, Alessandro Rubini

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید








16.1. Registration


Block drivers, like char

drivers, must use a set of
registration interfaces to make their devices available to the
kernel. The concepts are similar, but the details of block device
registration are all different. You have a whole new set of data
structures and device operations to learn.


16.1.1. Block Driver Registration


The first step taken by most block drivers is to register themselves
with the kernel. The function for this task
is

register_blkdev (which is declared in
<linux/fs.h>):

int register_blkdev(unsigned int major, const char *name);

The arguments are the major number that your device will be using and
the associated name (which the kernel will display in
/proc/devices). If major is
passed as 0, the kernel allocates a new major
number and returns it to the caller. As always, a negative return
value from register_blkdev indicates that an
error has occurred.

The corresponding function for canceling a block driver registration
is:

int unregister_blkdev(unsigned int major, const char *name);

Here, the arguments must match those passed to
register_blkdev, or the function returns
-EINVAL and not unregister anything.

In the 2.6 kernel, the call to register_blkdev
is entirely optional. The functions performed by
register_blkdev have been decreasing over time;
the only tasks performed by this call at this point are (1)
allocating a dynamic major number if requested, and (2) creating an
entry in /proc/devices. In future kernels,
register_blkdev may be removed altogether.
Meanwhile, however, most drivers still call it; it's
traditional.


16.1.2. Disk Registration


While register_blkdev can be

used
to obtain a major number, it does not make any disk drives available
to the system. There is a separate registration interface that you
must use to manage individual drives. Using this interface requires
familiarity with a pair of new structures, so that is where we start.


16.1.2.1 Block device operations

Char devices
make their operations available to the
system by way of the file_operations structure. A
similar structure is used with block devices; it is
struct block_device_operations,
which is declared in <linux/fs.h>. The
following is a brief overview of the fields found in this structure;
we revisit them in more detail when we get into the details of the
sbull driver:

int (*open)(struct inode *inode, struct file *filp);

int (*release)(struct inode *inode, struct file *filp);


Functions that work just like their char driver equivalents; they are
called whenever the device is opened and closed. A block driver might
respond to an open call by spinning up the device, locking the door
(for removable media), etc. If you lock media into the device, you
should certainly unlock it in the release
method.


int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd,

unsigned long arg);


Method that implements the ioctl system call.
The block layer first intercepts a large number of standard requests,
however; so most block driver ioctl methods are
fairly short.


int (*media_changed) (struct gendisk *gd);


Method called by the kernel to check whether the user has changed the
media in the drive, returning a nonzero value if so. Obviously, this
method is only applicable to drives that support removable media (and
that are smart enough to make a "media
changed" flag available to the driver); it can be
omitted in other cases.



The struct gendisk argument is how the kernel
represents a single disk; we will be looking at that structure in the
next section.

int (*revalidate_disk) (struct gendisk *gd);


The revalidate_disk method is called in response
to a media change; it gives the driver a chance to perform whatever
work is required to make the new media ready for use. The function
returns an int value, but that value is ignored by
the kernel.


struct module *owner;


A pointer to the module that owns this structure; it should usually
be initialized to THIS_MODULE.



Attentive readers may have noticed an interesting omission from this
list: there are no functions that actually read or write data. In the
block I/O subsystem, these operations are handled by the
request function, which deserves a large section
of its own and is discussed later in the chapter. Before we can talk
about servicing requests, we must complete our discussion of disk
registration.


16.1.2.2 The gendisk structure

struct
gendisk

(declared in <linux/genhd.h>) is the
kernel's representation of an individual disk
device. In fact, the kernel also uses gendisk
structures to represent partitions, but driver authors need not be
aware of that. There are several fields in struct
gendisk
that must be initialized by a block driver:

int major;

int first_minor;

int minors;


Fields that describe the device number(s) used by the disk. At a
minimum, a drive must use at least one minor number. If your drive is
to be partitionable, however (and most should be), you want to
allocate one minor number for each possible partition as well. A
common value for minors is 16, which allows for
the "full disk" device and 15
partitions. Some disk drivers use 64 minor numbers for each device.


char disk_name[32];


Field that should be set to the name of the disk device. It shows up
in /proc/partitions and sysfs.


struct block_device_operations *fops;


Set of device operations from the previous section.


struct request_queue *queue;


Structure used by the kernel to manage I/O requests for this device;
we examine it in Section 16.3.


int flags;


A (little-used) set of flags describing the state of the drive. If
your device has removable media, you should set
GENHD_FL_REMOVABLE. CD-ROM drives can set
GENHD_FL_CD. If, for some reason, you do not want
partition information to show up in
/proc/partitions, set
GENHD_FL_SUPPRESS_PARTITION_INFO.


sector_t capacity;


The capacity of this drive, in 512-byte sectors. The
sector_t type can be 64 bits wide. Drivers should
not set this field directly; instead, pass the number of sectors to
set_capacity.


void *private_data;


Block drivers may use this field for a pointer to their own internal
data.



The kernel provides a small set of functions for working with
gendisk structures. We introduce them here, then
see how sbull uses them to make its disk devices
available to the system.

struct gendisk is a dynamically
allocated structure that requires special kernel manipulation to be
initialized; drivers cannot allocate
the structure on their own. Instead, you must call:

struct gendisk *alloc_disk(int minors);

The minors argument should be the number of minor
numbers this disk uses; note that you cannot change the
minors field later and expect things to work
properly.

When a disk is no longer needed, it should
be

freed with:

void del_gendisk(struct gendisk *gd);

A gendisk is a reference-counted structure (it
contains a kobject). There are get_disk and
put_disk functions available to manipulate the
reference count, but drivers should never need to do that. Normally,
the call to del_gendisk removes the final
reference to a gendisk, but there are no guarantees of that. Thus, it
is possible that the structure could continue to exist (and your
methods could be called) after a call to
del_gendisk. If you delete the structure when
there are no users (that is, after the final
release or in your module
cleanup function), however, you can be sure that
you will not hear from it again.

Allocating a
gendisk structure does not make the disk
available to the system. To do that, you must initialize the
structure and call add_disk:

void add_disk(struct gendisk *gd);

Keep one important thing in mind here: as soon as you call
add_disk, the disk is
"live" and its methods can be
called at any time. In fact, the first such calls will probably
happen even before add_disk returns; the kernel
will read the first few blocks in an attempt to find a partition
table. So you should not call add_disk until
your driver is completely initialized and ready to respond to
requests on that disk.


16.1.3. Initialization in sbull


It is time to get down to





some examples. The
sbull driver (available from
O'Reilly's FTP site with the rest
of the example source) implements a set of in-memory virtual disk
drives. For each drive, sbull allocates (with
vmalloc, for simplicity) an array of memory; it
then makes that array available via block operations. The
sbull driver can be tested by partitioning the
virtual device, building filesystems on it, and mounting it in the
system hierarchy.

Like our other example drivers, sbull allows a
major number to be specified at compile or module load time. If no
number is specified, one is allocated dynamically. Since a call to
register_blkdev is required for dynamic
allocation, sbull does so:

sbull_major = register_blkdev(sbull_major, "sbull");
if (sbull_major <= 0) {
printk(KERN_WARNING "sbull: unable to get major number\n");
return -EBUSY;
}

Also, like the other virtual devices we have presented in this book,
the sbull device is described by an internal
structure:

struct sbull_dev {
int size; /* Device size in sectors */
u8 *data; /* The data array */
short users; /* How many users */
short media_change; /* Flag a media change? */
spinlock_t lock; /* For mutual exclusion */
struct request_queue *queue; /* The device request queue */
struct gendisk *gd; /* The gendisk structure */
struct timer_list timer; /* For simulated media changes */
};

Several steps are required to initialize this structure and make the
associated device available to the system. We start with basic
initialization and allocation of the underlying memory:

memset (dev, 0, sizeof (struct sbull_dev));
dev->size = nsectors*hardsect_size;
dev->data = vmalloc(dev->size);
if (dev->data = = NULL) {
printk (KERN_NOTICE "vmalloc failure.\n");
return;
}
spin_lock_init(&dev->lock);

It's important to allocate and initialize a spinlock
before the next step, which is the allocation of the request queue.
We look at this process in more detail when we get to request
processing; for now, suffice it to say that the necessary call is:

dev->queue = blk_init_queue(sbull_request, &dev->lock);

Here,
sbull_request

is our request functionthe function that
actually performs block read and write requests. When we allocate a
request queue, we must provide a spinlock that controls access to
that queue. The lock is provided by the driver rather than the
general parts of the kernel because, often, the request queue and
other driver data structures fall within the same critical section;
they tend to be accessed together. As with any function that
allocates memory, blk_init_queue can fail, so
you must check the return value before continuing.

Once we have our device memory and request queue in place, we can
allocate, initialize, and install the corresponding
gendisk structure. The code that does this work
is:

dev->gd = alloc_disk(SBULL_MINORS);
if (! dev->gd) {
printk (KERN_NOTICE "alloc_disk failure\n");
goto out_vfree;
}
dev->gd->major = sbull_major;
dev->gd->first_minor = which*SBULL_MINORS;
dev->gd->fops = &sbull_ops;
dev->gd->queue = dev->queue;
dev->gd->private_data = dev;
snprintf (dev->gd->disk_name, 32, "sbull%c", which + 'a');
set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));
add_disk(dev->gd);

Here, SBULL_MINORS is the number of minor numbers
each sbull device supports. When we set the
first minor number for each device, we must take into account all of
the numbers taken by prior devices. The name of the disk is set such
that the first one is sbulla, the second
sbullb, and so on. User space can then add
partition numbers so that the third partition on the second device
might be /dev/sbullb3.

Once everything is set up, we finish with a call to
add_disk. Chances are that several of our
methods will have been called for that disk by the time
add_disk returns, so we take care to make that
call the very last step in the initialization of our device.


16.1.4. A Note on Sector Sizes


As we have mentioned
before, the kernel treats every disk
as a linear array of 512-byte sectors. Not all hardware uses that
sector size, however. Getting a device with a different sector size
to work is not particularly hard; it is just a matter of taking care
of a few details. The sbull device exports a
hardsect_size parameter that can be used to change
the "hardware" sector size of the
device; by looking at its implementation, you can see how to add this
sort of support to your own drivers.

The first of those details is to inform the kernel of the sector size
your device supports. The hardware sector size is a parameter in the
request queue, rather than in the gendisk
structure. This size is set with a call to
blk_queue_hardsect_size

immediately after the queue is allocated:

blk_queue_hardsect_size(dev->queue, hardsect_size);

Once that is done, the kernel adheres to your
device's hardware sector size. All I/O requests are
properly aligned at the beginning of a hardware sector, and the
length of each request is an integral number of sectors. You must
remember, however, that the kernel always expresses itself in
512-byte sectors; thus, it is necessary to translate all sector
numbers accordingly. So, for example, when sbull
sets the capacity of the device in its gendisk
structure, the call looks like:

set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));

KERNEL_SECTOR_SIZE is a locally-defined constant
that we use to scale between the kernel's 512-byte
sectors and whatever size we have been told to use. This sort of
calculation pops up frequently as we look at the sbull
request processing logic.


    / 202