Section 7.5. Device Configuration

   


7.5. Device Configuration

Autoconfiguration is the procedure carried out by the system to recognize and enable the hardware devices present in a system. Autoconfiguration works by systematically probing the possible I/O busses on the machine. For each I/O bus that is found, each type of device attached to it is interpreted, and, depending on this type, the necessary actions are taken to initialize and configure the device.

The first FreeBSD implementation of autoconfiguration was derived from the original 4.2BSD code with the addition of many special-case hacks. The 4.4BSD release introduced a new more machine-independent configuration system that was considered for FreeBSD but was ultimately rejected in favor of the newbus scheme used today. Newbus first appeared in FreeBSD 3.0 to support the Alpha architecture. It was brought over to the PC platform for FreeBSD 4.0. Newbus includes machine-independent routines and data structures for use by machine-dependent layers, and provides a framework for dynamic allocation of data structures for each device.

A key design goal of the newbus system was to expose a stable application binary interface (ABI) to driver writers. A stable ABI is especially important for externally or vendor-maintained loadable kernel modules because their source code is often not available to recompile if the interface is changed.

To help achieve ABI stability, the newbus device and devclass structures are hidden from the rest of the kernel with a simple function-call-based API to access their contents. If the structures were passed to the device driver directly, any change to the structure would require that all the drivers to which it is passed be recompiled. Changes to these newbus data structures do not require a recompilation of all the drivers. Only the access functions to the data structures need to be recompiled.

Unfortunately the goal of building a stable documented kernel ABI for driver writers has not yet been done outside of newbus. For example, the uio structure is still passed in to most drivers. Whenever a field is added, changed, or dropped from the uio structure, all the drivers must be recompiled.

Some hardware devices, such as the interface to the console terminal, are required for system operation. Other devices, however, may not be needed, and their inclusion in the system may needlessly waste system resources. Devices that might be present in different numbers, at different addresses, or in different combinations are difficult to configure in advance. However, the system must support them if they are present and must fail gracefully if they are not present. To address these problems, FreeBSD supports both a static configuration procedure that is done when a bootable system image is created and a dynamic loading capability that allows kernel drivers and modules to be added to a running system as needed. Thus, the statically configured kernel can be small with just enough capability to get the system up and running. Once running, additional functionality can be added as needed.

Allowing code to be loaded dynamically into the kernel raises many security problems. Code running outside the kernel is limited in the damage that it can do because it does not run in privileged mode and cannot directly access the hardware. The kernel runs with full privilege and access to the hardware. Thus, if it loads a module that contains malicious code, it can inflict wide-ranging damage within the system. Kernels can be loaded across the network from a central server. If the kernel allows dynamic loading of modules, they too could come across the network, so there are numerous added points for malfeasance.

An important consideration in deciding whether to enable dynamic loading of kernel modules is to develop a scheme to verify the source of and lack of corruption in any code before that code is permitted to be loaded and used. A group of vendors have formed the Trusted Computing Group (TCG) to specify a hardware module called a Trusted Platform Module (TPM) that keeps a running SHA-1 hash of the software installed on the system to detect the loading of bad programs or modules. It is implemented as a microcontroller-based device similar to a smart card that is attached to the motherboard [TCG, 2003]. Other groups are doing work to limit the potential harm of kernel modules by running them with page protections that limit their access to the rest of the kernel [Chiueh et al., 2004]. The drawback to disabling dynamic loading is that any hardware that is not included in the kernel configuration file will be unavailable for use.

The initial kernel configuration is done by the /usr/sbin/config program. A configuration file is created by the system administrator that contains a list of drivers and kernel options. Historically, the configuration file defined both the set of hardware devices that might be present on a machine and the location where each device might be found. In FreeBSD 5.2 most of the actual hardware devices are discovered dynamically as the various bus drivers probe and attach. The location of legacy devices for non-plug-and-play (non-self-identifying) busses are given in a /boot/device.hints file that is loaded with the kernel. The other use for hints is to hardwire a unit number to a location. Currently only CAM can hardwire unit numbers, although hard wiring could be implemented for any bus. The configuration procedure generates many files that define the initial kernel configuration. These files control the kernel compilation.

The autoconfiguration phase is done first during system initialization to identify the set of devices that are present on a machine. In general, autoconfiguration recurses through a tree of device interconnections, such as busses and controllers to which other devices attach. For example, a system might be configured with two SCSI host adapters (controllers) and four disk drives that are connected in any of the configurations shown in Figure 7.9. Autoconfiguration works in one of two ways at each level in the tree:

  1. Identifying each possible location at which a device might be present and checking to see what type of device (if any) is there.

  2. Probing for devices at each of the possible locations where the device might be attached.

Figure 7.9. Alternative drive configurations.


The first approach of identifying predefined locations for devices is needed for older busses like ISA that were not designed to support autoconfiguration. The second mechanism of probing for devices can be used only when a fixed set of locations are possible and when devices at those locations are self-identifying, such as devices connected to a SCSI or PCI bus.

Devices that are recognized during the autoconfiguration phase are attached and made available for use. The attach function for a device initializes and allocates resources for the device. The attach function for a bus or controller must probe for devices that might be attached at that location. If the attach function fails, the hardware was found but is nonfunctional, which results in a console message being printed. Devices that are present but not recognized may be configured once the system is running and has loaded other kernel modules. The attach function for busses is allowed to reserve resources for devices that are detected on the bus but for which no device driver is currently loaded in the system.

This scheme permits device drivers to allocate system resources for only those devices that are present in a running system. It allows the physical device topology to be changed without requiring the system load image to be regenerated. It also prevents crashes resulting from attempts to access a nonexistent device. In the remainder of this section, we consider the autoconfiguration facilities from the perspective of the device-driver writer. We examine the device-driver support required to identify hardware devices that are present on a machine and the steps needed to attach a device once its presence has been noted.

Device Identification

To participate in autoconfiguration, a device driver must register the set of functions shown in Table 7.1. Devices are an abstract concept in FreeBSD. In addition to the traditional disks, tapes, keyboards, terminal lines, and so on, FreeBSD will have devices that operate all the pieces that make up the I/O infrastructure such as the SCSI bus controller, the bridge controller to the PCI bus, and the bridge controller to the ISA bus. The top-level device is the root of the I/O system and is referred to as root0. On a uniprocessor system, root0 logically resides at the I/O pins of the CPU. On a multiprocessor system, root0 is logically connected to the I/O pins of each of the CPUs. The root0 device is handcrafted at boot time for each architecture supported by FreeBSD.

Table 7.1. Functions defined for autoconfiguration.

Function

Description

device_probe

probe device presence

device_identify

add new device to bus

device_attach

attach a device

device_detach

detach a device

device_shutdown

system about to shut down

device_suspend

suspend requested

device_resume

resume has occurred


Autoconfiguration begins with a request to the root0 bus to configure all its children. When a bus is asked to configure its children, it calls the device_identify( ) routine of each of its possible device drivers. The result is a set of children that have been added to the bus either by the bus itself or by the device_identify( ) routines of one of its drivers. Next the device_probe( ) routine of each of the children is called. The device_probe( ) routine that bids the highest for the device will then have its device_attach( ) routine called. The result is a set of devices corresponding to each of the busses that are directly accessible to root0. Each of these new devices is then given the opportunity to probe for or identify devices below them. The identification process continues until the topology of the I/O system has been determined.

Modern busses can directly identify the things that are connected to them. Older busses such as ISA use the device_identify( ) routine to bring in devices that are found only because of hints.

As an example of the device hierarchy, the device controlling the PCI bus may probe for a SCSI controller, which in turn will probe for possible targets that might be attached, such as disk and tape drives. The newbus autoconfiguration mechanism provides much flexibility, allowing a controller to determine the appropriate way in which to probe for additional devices attached to the controller.

As autoconfiguration proceeds, a device-driver device_probe( ) routine is called for each device that is found. The system passes to the device_probe( ) routine a description of the device's location and possibly other details such as I/O register location, memory location, and interrupt vectors. The device_probe( ) routine usually just checks to see if it recognizes the hardware.

It is possible that there is more than one driver that can operate a device. Here, each matching driver returns a priority that shows how well they match the hardware. Success codes are values less than or equal to zero with the highest value representing the best match. Failure codes are represented by positive values using the usual kernel error codes.

If a driver returns a success code that is less than zero, it must not assume that it will be the same driver whose device_attach( ) routine will be called. In particular, it must not assume that any values stored in the device local-storage area will be available for its device_attach( ) routine. Any resources allocated during the probe must be released and reallocated if its device_attach( ) routine is called. By returning a success code of zero, a driver can assume that it will be the one attached. However, well-written drivers will not have their device_attach( ) routine use the device local-storage area because they may one day have their return value downgraded to a value less than zero. Typically the resources used by a device are identified by the bus (parent device), and it is the bus that prints them out when the devices are probing.

Once the device_probe( ) routine has had the opportunity to identify the device and select the most appropriate driver to operate it, the device_attach( ) routine is called. Attaching a device is separated from probing so that drivers can be bid for devices. Probe and attach are also separate so that drivers can separate out the identification part of the configuration from the attaching part. Most device drivers use the device_attach( ) routine to initialize the hardware device and any software state. The device_attach( ) routine is also responsible for either creating the dev_t entries (for disks and character devices) or for network devices, registering the device with the networking system.

Devices that represent pieces of hardware such as a SCSI controller will respond to verify that the device is present and to set or at least identify the device's interrupt vector. For disk devices, the device_attach( ) routine may make the drive available to higher levels of the kernel such as GEOM. GEOM will let its classes taste the disk drive to identify its geometry and possibly initialize the partition table that defines the placement of filesystems on the drive.

Autoconfiguration Data Structures

The newbus autoconfiguration system in FreeBSD includes machine-independent data structures and support routines. The data structures allow machine- and bus-dependent information to be stored in a general way and allow the autoconfiguration process to be driven by the configuration data, rather than by compiled-in rules. The /usr/sbin/config program constructs many of the tables from information in the kernel-configuration file and from a machine-description file. The /usr/sbin/config program is thus data-driven as well and contains no machine-dependent code.

Figure 7.10 shows the data structures used by autoconfiguration. The basic building block is the device structure. Each piece of the I/O hierarchy will have its own device structure. The name and description fields identify the piece of hardware represented by the device structure. In Figure 7.10, the name of the device is pcil. Device names are globally unique. There can be only one pcil device in the system. Knowing its name is enough to find it unlike filesystems where there can be many files with the same name in different paths. This name space is related by convention to the name space that /dev entries have, but such a relationship is not required.

Figure 7.10. Autoconfiguration data structures for pcil.


Each device is a member of a device class represented by a devclass structure that has two important roles. The first role of the devclass structure is to keep track of a list of drivers for devices in that class. Devices referenced from a devclass do not have to use the same driver. Each device structure references its best matching driver from the list available for the devclass. The list of candidate drivers is traversed, allowing every driver to probe each device that is identified as a member of the class. The best matching driver will be attached to the device. For example, the pci devclass contains a list of drivers suitable for probing against devices that may be plugged into a PCI bus. In Figure 7.10 there are drivers to match pcm (sound cards) and atapci (PCI-based ATA-disk controllers).

The second role of the devclass structure is to manage the mapping from a user-friendly device name such as pcil to its device structure. The name field in the devclass structure contains the root of a family of names in this example, pci. The number following the root of the name 1 in this example indexes into the array of pointers to device structures contained in the devclass. The name in the referenced device structure is the full name, pcil.

When a device structure first comes into existence, it will take the following steps:

1. The parent device determines the existence of a new child device typically by doing a bus scan. The new device is created as a child of the parent. In Figure 7.10, the autoconfiguration code would begin a scan of pcil and discover an ATA disk controller.

2. The parent device starts a probe-and-attach sequence for the new child device. The probe iterates through drivers in the parent device's devclass until a driver is found that claims the device (i.e., the probe succeeds). The device structure sets its driver field to point at the selected driver structure and increments the reference count in the selected driver. In Figure 7.10, the atapci driver matches the ATA disk controller, atadisk.

3. Once a usable driver is found, the new device is registered with the devclass of the same name as the driver. The registration is done by allocating the next available unit number and setting a pointer from the corresponding entry in the devclass's array of device-structure pointers back to the device. In Figure 7.10, the atapci driver was matched, so the device would be bound to the atapci devclass. The resulting device configuration is shown in Figure 7.11 (on page 288). The key observation is that two different devclasses are involved in this three-step process.

Figure 7.11. Autoconfiguration data structures for atapci0.


The hierarchy of device structures is shown in Figure 7.12. Each device structure has a parent pointer and a list of children. In Figure 7.12, the pci device that manages the PCI bus is shown at the top and has as its only child the atapci device that operates ATA disks on the PCI bus. The atapci device has the pci device as its parent and has two children, one for each of the ATA disks that are attached. The devices representing the two drives have the atapci device as their parent. Because they are leaf nodes, they have no children.

Figure 7.12. Sample hierarchy of device structures.


To get a better idea of the I/O hierarchy, an annotated copy of the output of the /usr/sbin/devinfo program from the first author's test machine is shown in Figure 7.13 (on page 290). The output has been trimmed down from its original 250 lines to show just the branch from the root of the tree to the system's two ATA disks. The tree starts at root0, representing the I/O pins on the CPU. That leads to the high-speed bus that connects to the memory and the nexus0 (for example, northbridge) interconnect to the I/O bus. For machines on which ACPI is not available, there is still a legacy0 shim to help identify the I/O busses on the other side of the northbridge. One of these busses is the pcib0 (for example, south-bridge) connection to the PCI bus. The PCI bus is managed by the pci0 device, which as you can see from the figure, has many drivers available for the myriad of devices that may be connected to it. In this example, the one device that we show is the atapci0 device representing the PCI-based ATA disk controller. The final two devices shown in Figure 7.13 are atadisk0 and atadisk1 that manage the operation of the drives themselves.

Resource Management

As part of configuring and operating devices, the autoconfiguration code needs to manage hardware resources such as interrupt-request lines, I/O ports, and device memory. To aid device-driver writers in this task, newbus provides a framework for managing these resources. To participate in newbus bus-resource management, a bus device driver must register the set of functions shown in Table 7.2 (on page 291). Low-level devices such as those that operate individual disk drives do not have the global knowledge of resource utilization needed to allocate scarce systemwide resources such as interrupt-request lines. They may register a generic bypass routine for resources that they do not have the needed information to allocate. When called, the bypass routine simply calls the corresponding routine registered by their parent. The result is that the request will work its way up the device tree until it reaches a high enough level that it can be resolved.

Table 7.2. Functions defined for device resource allocation.

Function

Description

bus_alloc_resource

allocate a bus resource

bus_set_resource

set up a range for a resource

bus_activate_resource

activate allocated resource

bus_deactivate_resource

deactivate allocated resource

bus_release_resource

release ownership of a resource

bus_delete_resource

free a released resource

bus_get_resource

get the range for a resource

bus_get_resource_list

get a resource list

bus_probe_nomatch

call following a failed probe

bus_driver_added

new driver added to devclass

bus_add_child

attach an identified device

bus_child_detached

notice to parent of detached child

bus_setup_intr

initialize an interrupt

bus_config_intr

set interrupt trigger mode and polarity

bus_teardown_intr

disable an interrupt

bus_read_ivar

read an instance variable

bus_write_ivar

write an instance variable

bus_child_present

check for device still present

bus_print_child

print device description


Often a high-level node in the tree will not have enough information to know how much of a resource to allocate. So it will reserve a range of resources, leaving it to the lower-level nodes to allocate and activate the specific resources that they need from the reservation made by the higher-level node.

The actual management of the allocated resources is handled by the kernel resource manager that was described in Section 5.10. The usual allocate and free routines have been expanded to allow different levels in the tree to manage different parts of these two functions. Thus, allocation breaks into three steps:

1. Setting the range for the resource

2. Initial allocation of the resource

3. Activating the resource

Figure 7.13. Sample configuration output.
 root0     description: System root bus     devclass: root, drivers: nexus     children: nexus0   nexus0       devclass: nexus, drivers: acpi, legacy, npx       children: npx0, legacy0     legacy0         description: legacy system         devclass: legacy, drivers: eisa, isa, pcib         children: eisa0, pcib0       pcib0 /* southbridge */           description: Intel 82443BX (440 BX) host to PCI bridge           devclass: pcib, drivers: pci           children: pci0       pcibl           description: PCI-PCI bridge           devclass: pcib, drivers: pci           children: pcil         pci0             description: PCI bus             devclass: pci, drivers: agp, ahc, amr, asr, atapci,                 bfe, bge, ciss, csa, domino, dpt, eisab, emujoy,                 fxp, fixup_pci, hostb, ignore_pci, iir, ips,                 isab, mly, mode0, pcib, pcm, re, sio, uhci, xl             children: agp0, pcibl, isab0, atapci0, ahc0, x10           atapci0               description: Intel 82371AB PIIX4 IDE controller               devclass: atapci, drivers: atadisk, atapicd               children: atadisk0, atadiskl               class: mass storage, subclass: ATA               I/O ports: 0xffa0-0xffaf             atadisk0                 devclass: atadisk, drivers: none                 interrupt request lines: 0xe                 I/O ports: 0xlf0-0xlf7, 0x3f6             atadiskl                 devclass: atadisk, drivers: none                 interrupt request lines: 0xf                 I/O ports: 0x170-0x177, 0x376 

Similarly, resource freeing is done in three steps:

1. Deactivating the resource

2. Releasing ownership of the resource to the parent bus

3. Freeing it

It is common for a high-level part of the tree to allocate a resource and then have a low-level driver activate and use the resource that it was allocated. Some busses reserve space for their children that do not have drivers associated with them. Having the allocation and freeing broken up into three steps gives maximum flexibility in partitioning the allocation and freeing processes.

The bus_driver_added( ), bus_add_child( ), and bus_child_detached( ) functions allow the device to be aware of changes in the I/O hardware so that it can respond appropriately. The bus_driver_added( ) function is called by the system when a new driver is loaded. The driver is added to some devclass, and then all current devices in that devclass have bus_driver_added( called to allow them to possibly match any unclaimed devices using the new driver. The bus_add_child( ) function is used during the identify phase of configuring some busses. It allows a bus device to create and initialize a new child device (for example, setting values for instance variables). The bus_child_detached( ) function is called by a driver when it decides that its hardware is no longer present (for example, a cardbus card is removed). It calls bus_child_detached( on its parent to allow it to do a detach of the child.

The bus_probe_nomatch( ) routine gives the device a last-ditch possibility to take some action after autoconfiguration has failed. It may try to find a generic driver that can run the device in a degraded mode, or it may simply turn the device off. If it is unable to find a driver that can run the device, it notifies the devd daemon, a user-level process started when the system is booted. The devd daemon uses a table to locate and load the proper driver. The loading of kernel modules is described in Section 14.4.

The bus_read_ivar( ) and bus_write_ivar( ) routines manage a bus-specific set of instance variables of a child device. The intention is that each different type of bus defines a set of appropriate instance variables such as ports and interrupt-request lines for the ISA bus.


   
 


The Design and Implementation of the FreeBSD Operating System
The Design and Implementation of the FreeBSD Operating System
ISBN: 0201702452
EAN: 2147483647
Year: 2003
Pages: 183

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