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

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

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

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

Jonathan Corbet, Greg Kroah-Hartman, Alessandro Rubini

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

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

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








3.2. Major and Minor Numbers



Char devices are accessed
through
names in the filesystem. Those names are
called special files or device files or simply nodes of the
filesystem tree; they are conventionally located in the
/dev
directory. Special files
for char drivers are identified by a
"c" in the first column of the
output of ls -l. Block devices appear in
/dev as well, but they are identified by a
"b." The focus of this chapter is
on char devices, but much of the following information applies to
block devices as well.

If you
issue the ls -l command, you'll
see two numbers (separated by a comma) in the device file entries
before the date of the last modification, where the file length
normally appears. These numbers are the major and minor device number
for the particular device. The following listing shows a few devices
as they appear on a typical system. Their major numbers are 1, 4, 7,
and 10, while the minors are 1, 3, 5, 64, 65, and 129.

 crw-rw-rw-    1 root     root       1,   3 Apr 11  2002 null
crw------- 1 root root 10, 1 Apr 11 2002 psaux
crw------- 1 root root 4, 1 Oct 28 03:04 tty1
crw-rw-rw- 1 root tty 4, 64 Apr 11 2002 ttys0
crw-rw---- 1 root uucp 4, 65 Apr 11 2002 ttyS1
crw--w---- 1 vcsa tty 7, 1 Apr 11 2002 vcs1
crw--w---- 1 vcsa tty 7, 129 Apr 11 2002 vcsa1
crw-rw-rw- 1 root root 1, 5 Apr 11 2002 zero

Traditionally, the major number identifies the driver associated with
the device. For example, /dev/null and
/dev/zero are both managed by driver 1, whereas
virtual consoles and serial terminals are managed by driver 4;
similarly, both vcs1 and
vcsa1 devices are managed by driver 7. Modern
Linux kernels allow multiple drivers to share major numbers, but most
devices that you will see are still organized on the
one-major-one-driver principle.

The minor number is used by the
kernel to determine exactly which device is being referred to.
Depending on how your driver is written (as we will see below), you
can either get a direct pointer to your device from the kernel, or
you can use the minor number yourself as an index into a local array
of devices. Either way, the kernel itself knows almost nothing about
minor numbers beyond the fact that they refer to devices implemented
by your driver.


3.2.1. The Internal Representation of Device Numbers


Within the kernel, the dev_t type (defined in
<linux/types.h>)
is used to hold device numbersboth
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. Your code should, of course, never make any assumptions
about the internal organization of device numbers; it should,
instead, make use of a set of
macros found
in <linux/kdev_t.h>. 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);

Note that the 2.6 kernel can accommodate a vast number of devices,
while previous kernel versions were limited to 255 major and 255
minor numbers. One assumes that the wider range will be sufficient
for quite some time, but the computing field is littered with
erroneous assumptions of that nature. So you should expect that the
format of dev_t could change again in the future;
if you write your drivers carefully, however, these changes will not
be a problem.


3.2.2. 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
<linux/fs.h>:

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. count is the total
number of contiguous device numbers you are requesting. Note that, if
count is large, the range you request could spill
over to the next major number; but everything will still work
properly as long as the number range you request is available.
Finally, 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 community to
move over to the use of dynamicly-allocated device numbers. The
kernel will 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 completion, hold the first number
in your allocated range. firstminor should be the
requested first minor number to use; it is usually
0. The count and
name parameters work like those given to
request_chrdev_region.

Regardless of how you allocate your device numbers, you should free
them when they are no longer in use. Device numbers are freed with:

void unregister_chrdev_region(dev_t first, unsigned int count);

The usual place to call unregister_chrdev_region
would be in your module's cleanup function.

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. We will describe how this
connection is accomplished shortly, but there are a couple of
necessary digressions to take care of first.


3.2.3. Dynamic Allocation of Major Numbers


Some major device numbers are statically

assigned to the most common devices. A
list of those devices can be found in
Documentation/devices.txt within the kernel
source tree. 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.[1]

[1] Even better
device information can usually be obtained from sysfs, generally
mounted on /sys on 2.6-based systems. Getting
scull to export information via sysfs is beyond
the scope of this chapter, however; we'll return to
this topic in Chapter 14.


To load a driver using a
dynamic

major number, therefore, the invocation of
insmod can be replaced by a simple script that,
after calling insmod, reads
/proc/devices in order to create the special
file(s).

A typical /proc/devices file looks like the
following:

Character devices:
1 mem
2 pty
3 ttyp
4 ttyS
6 lp
7 vcs
10 misc
13 input
14 sound
21 sg
180 usb
Block devices:
2 fd
8 sd
11 sr
65 sd
66 sd

The script to load a
module that

has been assigned a dynamic number
can, therefore, be written using a tool such as
awk to retrieve information from
/proc/devices in order to create the files in
/dev.

The following script, scull_load, is part of the
scull distribution. The user of a driver that is
distributed in the form of a module can invoke such a script from the
system's rc.local file or call
it manually whenever the module is needed.

#!/bin/sh
module="scull"
device="scull"
mode="664"
# invoke insmod with all arguments we got
# and use a pathname, as newer modutils don't look in . by default
/sbin/insmod ./$module.ko $* || exit 1
# remove stale nodes
rm -f /dev/${device}[0-3]
major=$(awk "\\$2= =\"$module\" {print \\$1}" /proc/devices)
mknod /dev/${device}0 c $major 0
mknod /dev/${device}1 c $major 1
mknod /dev/${device}2 c $major 2
mknod /dev/${device}3 c $major 3
# give appropriate group/permissions, and change the group.
# Not all distributions have staff, some have "wheel" instead.
group="staff"
grep -q '^staff:' /etc/group || group="wheel"
chgrp $group /dev/${device}[0-3]
chmod $mode /dev/${device}[0-3]

The script can be adapted for another driver by redefining the
variables and adjusting the mknod lines. The
script just shown creates four devices because four is the default in
the scull sources.

The last few lines
of the script may seem obscure: why change the group and mode of a
device? The reason is that the script must be run by the superuser,
so newly created special files are owned by root. The permission bits
default so that only root has write access, while anyone can get read
access. Normally, a device node requires a different access policy,
so in some way or another access rights must be changed. The default
in our script is to give access to a group of users, but your needs
may vary. In Section 6.6 in Chapter 3, the code for
sculluid demonstrates how the driver can enforce
its own kind of authorization for device access.

A scull_unload script is also available to clean
up the /dev directory and remove the module.

As an alternative to using a pair
of scripts for loading and unloading, you could write an init script,
ready to be placed in the directory your distribution uses for these
scripts.[2] As part of the
scull source, we offer a fairly complete and
configurable example of an init script, called
scull.init; it accepts the conventional
argumentsstart, stop,
and restartand performs the role of both
scull_load and
scull_unload.

[2] The Linux Standard Base specifies that init
scripts should be placed in /etc/init.d, but
some distributions still place them elsewhere. In addition, if your
script is to be run at boot time, you need to make a link to it from
the appropriate run-level directory (i.e.,
.../rc3.d).


If repeatedly creating and destroying
/dev nodes sounds like overkill, there is a
useful workaround. If you are loading and unloading only a single
driver, you can just use rmmod and
insmod after the first time you create the
special files with your script: dynamic numbers are not
randomized,[3] and
you can count on the same number being chosen each time if you
don't load any other (dynamic) modules. Avoiding
lengthy scripts is useful during development. But this trick,
clearly, doesn't scale to more than one driver at a
time.

[3] Though certain kernel developers have
threatened to do exactly that in the future.


The best way to assign major numbers, in our opinion, is by
defaulting to dynamic allocation while leaving yourself the option of
specifying the major number at load time, or even at compile time.
The scull implementation works in this way; it
uses a global variable, scull_major, to hold the
chosen number (there is also a scull_minor for the
minor number). The variable is initialized to
SCULL_MAJOR, defined in
scull.h. The default value of
SCULL_MAJOR in the distributed source is
0, which means "use dynamic
assignment." The user can accept the default or
choose a particular major number, either by modifying the macro
before compiling or by specifying a value for
scull_major on the insmod
command line. Finally, by using the scull_load
script, the user can pass arguments to insmod on
scull_load 's command
line.[4]

[4] The init script scull.init
doesn't accept driver options on the command line,
but it supports a configuration file, because it's
designed for automatic use at boot and shutdown time.


Here's the code we use in scull
's source to get a major number:

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,
"scull");
scull_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
return result;
}

Almost all of the sample drivers used in this book use
similar code for their major number assignment.


    / 202