Drivers and device control


A device driver is written to control a very specific piece of hardware. Usually if you change to a different device or install a new one on your system, you will also have to add or update the software that handles it ” the driver. If the device comes with a circuit card you plug into the machine (of course, while the power is turned off), you'll most likely be installing software, too. Because every piece of hardware is designed with different chips, organized differently, and has different command sequences to make it do its job, it needs specific driver software written to understand that particular hardware. There are some standardized interfaces, like SCSI, the Small Computer Systems Interface, that will often allow you to plug in a compatible peripheral device and communicate without problems, but even these sometimes require new or special drivers.

There are generally two major parts to a device driver: interpreting user requests passed through the operating system and attending to service demands from the device itself. The first part is concerned with data transmission between the user and the device via the operating system and must conform to whatever conventions have been established by the kernel. The second part, interrupt service, is more concerned with the state of the hardware (recognizing error conditions or unexpected events) and handling them. Generally, interrupt routines are very short and have limited interaction with the main kernel. They are also high priority and may interrupt normal kernel processing, so they have to be careful about what they manipulate.

Once a system call has been issued by a user program, the kernel has some basic steps it goes through. The first actions are validation and verification: Check the request to make sure it's valid; check the various parameters passed in by the user to be certain they are in range, or refer to valid addresses, or contain proper commands. Once this part is done, more specific code is invoked to handle the actual request. If it involves I/O or some other action that requires access to a device, the kernel must invoke the necessary function in the driver. The driver code itself will finish the work, passing a result code or some data back to the kernel, which hands it back to the user.

For example, let's say you want to read from a tape device. The kernel will process the read system call in general terms, then call a read function, which makes sure the file descriptor is correct and you have permission to do the operation. It will finally get to the point where it has to access the data, and to do this it has to find the right device and tell it what to do. The driver read() routine is called. Normally, for tape drives , the driver will not have the data already available, so it will have to allocate a buffer, and set up and issue a command to the tape drive hardware to initiate a read. This may take some time, and it's not worth waiting around for the device to finish when the system could be doing real work somewhere else. The driver therefore does a sleep() , which essentially freezes the current program and the request it was making, saves the whole thing, and allows the system to switch over to another process.

Eventually the tape device finishes the I/O transfer. The buffer is full of data, and the kernel should be notified that the device is ready for the next command. An interrupt is issued, the driver interrupt service code is invoked, and a wakeup () is sent to the suspended (sleeping) program. This notifies the kernel that this program can be scheduled to run again. When the kernel switches back to this process, the data is passed out to the user program and the system call returns.

This is the general flow of a system request that uses hardware and indirectly calls the driver functions. We'll take a look at some of the specific routines that may appear in device driver code and what they're used for.

SunOS 4.x drivers

Device drivers in the Solaris 2 kernel are quite a bit different from the older BSD-style 4.x versions. Part of this difference stems from some major changes to the internal structure of the kernel itself. There have also been changes in hardware that make revisions in the driver/kernel interface necessary. But let's look at the structure of the older drivers first.

The 4.x kernel is basically static. You must build a customized kernel that incorporates all the software you anticipate needing and configure it for the expected environment. If any of this changes, you must create a new kernel. If you add new hardware, you will also have to rebuild the system to add the new driver(s), unless you had the foresight to include them earlier. The drivers must be resident in the kernel for the device to function. You may include drivers for devices you don't need or don't have, if you anticipate adding them or you don't care about the wasted space for the code.

Autoconfiguration

Since there may be drivers for hardware that doesn't exist when the system is booted , the kernel must have a way to identify devices that are present. One of the requirements for drivers is that they provide a function the kernel can call to look for the device. This function, called a probe() routine, returns a true/false indication. If the device is present and functional, the kernel will print a message indicating that it has located the device and enabled the driver. This procedure is done as a part of the boot sequence and is the cause of all the device identification messages you see on the console.

Device switches

If you want to add a device to the system, you must include the driver in the kernel and tell the kernel how to get to it. You do so through the device switch tables. In the file conf.c (in /usr/kvm/sys/sun ), you will find two arrays of structures, bdevsw and cdevsw . These structures define the functions provided in the device driver that are available to the kernel. There are two different types of devices, corresponding to the two tables. The bdevsw table is the block device switch ; it handles things like disk drives, which normally perform I/O transfers in fixed- size pieces and are capable of handling a file system on that device. The cdevsw table is the character device switch ; it takes care of everything else. Many of the entries are the same, but block devices have some different requirements and usually are more difficult to set up. In general, the entries in the switch tables are driver functions that the kernel can call. They will take care of the specific hardware details for various system calls. You will see an entry in the table identifying driver open () , close() , read() , write() , and ioctl() functions for every device.

From the point of view of a user, a device is identified by a device file, which is almost always in the /dev directory. The long listing of a directory entry for a device file (with the ls -l command) contains much of the basic information about the type of device behind the file. The file type field, which is the first character on the line in the ' ls -l ' output, will show either b or c for a "special" (device) file. This indicates whether the device is block- or character-based, in other words, which device switch table will be used to find the driver functions. The numbers you see instead of the file size are known as the device major and minor numbers. The major number is used as an index into the appropriate switch table (it selects the driver); the minor number is passed to the driver code as a possible logical unit number. Some devices, like tapes, will encode various extra pieces of information in that minor number, like the density, whether or not to rewind afterwards, and the actual unit number of the tape drive.

In the SunOS 4.x kernel, there is a limited ability to create and use loadable device drivers. These are drivers that are not configured into the system at boot time but can be loaded later. They are not often used and are difficult to debug because the symbol tables for the drivers themselves are not loaded with them. About all you can do is identify the fact that you are looking at code in a loaded driver and tell which one it is. Loaded drivers will appear in the data space of the kernel, so if you cannot locate the instruction codes you want to examine in a vmunix. X file, try looking for it in the vmcore. X file. If it appears there, you can see if it's in a driver by looking at the list of vddrv structures (defined in the /usr/include/sun/vddrv.h file) pointed to by the kernel variable vddrv_list . These "virtual device driver" structures, one per loaded module, will contain information about the driver, such as the start address and size of the code. This will allow you to find out if the instructions you are interested in do appear in the address range that corresponds to code for a particular driver. The name of the module can be found in the vdlinkage structure, which is pointed to by the very first word of vddrv .

Driver code

The code in a SunOS 4.x device driver can be divided into several sections, dealing with different requirements. The part that deals with user system calls is known as the top half of the driver. These are the functions that are listed in the device switch table and are directly called by the kernel. The bottom half of the driver is the interrupt service code, which is invoked directly by the hardware. Finally, the probe and attach routines are responsible for verifying the existence of the device and setting it up at boot time.

Numerous support functions are available in the kernel for the drivers to call when necessary. These are documented in the Writing Device Drivers manual for SunOS 4.x systems. Some of these are:

  • copyin() and copyout() ” Move data to and from user space

  • iowait() ” Sleep, waiting for I/O to complete

  • mballoc() ” Allocate Main Bus resources for I/O transfers

  • physio() , ” Lock down user memory in preparation for data transfers

  • splx() ” Set a processor priority level and possibly block out interrupts

  • uiomove() ” Move data around, using copyin() and copyout()

The 4.x BSD driver structure is complex, but it has been around for a long time and is well understood by many people. However, it is not particularly consistent in many ways, and the Solaris 2 interface definitions establish some needed standards for driver writers.

Solaris 2 drivers

Device drivers in Solaris 2 are all loadable kernel modules. Drivers are no longer configured into the system and are not always resident. Instead, they are loaded only when needed, and perform all the initialization necessary at that time. Some new requirements are imposed on drivers because of this, so the format has changed slightly; new routines must be written, new structures must be maintained . In addition, the interface between the kernel and all device drivers is more firmly established. There is better documentation, and a more rigorous standard helps drivers to communicate.

The Device Driver Interface/Driver Kernel Interface (DDI/DKI) specification provides a list of the required functions a driver must provide, a definition of structures that both drivers and interface functions use, and a set of internal support routines that drivers can call. Along with this goes the requirement that device drivers use only those documented routines and structures: Anything else is subject to change without notice.

There are specific parts to a driver in Solaris 2, similar to those in the SunOS 4.x kernel. One section must deal with various aspects of loading and unloading modules in the kernel. One part handles initialization of the device: probe and attach are still present, and an additional routine, called the identify function, is needed for newer , smarter hardware known as "self-identifying devices." The functions that deal with system calls are known as the user level, and the interrupt level contains service routines as before.

Various structures define the available driver functions. The user-level functions are contained in a cb_ops structure (similar in concept to the cdevsw / bdevsw setup for SunOS 4.x kernels ). A dev_ops structure holds the addresses of the configuration functions, along with a pointer to cb_ops . A modldrv structure holds loadable driver information with a pointer to dev_ops , and a modlinkage structure tops everything off by pointing to the modldrv information. During initialization and loading, a call to a function mod_install() will pass the head of this chain of structures to the system, which will allow it to find out all the necessary information to hook the driver into the kernel.



PANIC. UNIX System Crash Dump Analysis Handbook
PANIC! UNIX System Crash Dump Analysis Handbook (Bk/CD-ROM)
ISBN: 0131493868
EAN: 2147483647
Year: 1994
Pages: 289
Authors: Chris Drake

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net