Considerations in Device Driver Design
A device driver must be compatible with the Solaris OS, both as
a consumer and provider of services. This section discusses the following issues, which should
be considered in device driver design:
DDI/DKI Facilities
The Solaris DDI/DKI interfaces are provided for driver portability. With DDI/DKI, developers can
write driver code in a standard fashion without having to worry about hardware
or platform differences. This section describes aspects of the DDI/DKI interfaces.
Device IDs
The DDI interfaces enable drivers to provide a persistent, unique identifier for a
device. The device ID can be used to identify or locate a
device. The ID is independent of the device's name or number (dev_t). Applications can
use the functions defined in libdevid(3LIB) to read and manipulate the device IDs
registered by the drivers.
Device Properties
The attributes of a device or device driver are specified by properties.
A property is a name-value pair. The name is a string that
identifies the property with an associated value. Properties can be defined by the
FCode of a self-identifying device, by a hardware configuration file (see the driver.conf(4)
man page), or by the driver itself using the ddi_prop_update(9F) family of routines.
Interrupt Handling
The DDI/DKI addresses the following aspects of device interrupt handling:
Registering device interrupts with the system
Removing device interrupts
Dispatching interrupts to interrupt handlers
Device interrupt sources are contained in a property called interrupt, which is either provided
by the PROM of a self-identifying device, in a hardware configuration file, or
by the booting system on the x86 platform.
Callback Functions
Certain DDI mechanisms provide a callback mechanism. DDI functions provide a mechanism for
scheduling a callback when a condition is met. Callback functions can be used
for the following typical conditions:
Callback functions are somewhat similar to entry points, for example, interrupt handlers. DDI
functions that allow callbacks expect the callback function to perform certain tasks. In the
case of DMA routines, a callback function must return a value indicating whether
the callback function needs to be rescheduled in case of a failure.
Callback functions execute as a separate interrupt thread. Callbacks must handle all the
usual multithreading issues.
Note - A driver must cancel all scheduled callback functions before detaching a device.
Software State Management
To assist device driver writers in allocating state structures, the DDI/DKI provides a
set of memory management routines called the software state management routines, also known as the soft-state routines. These
routines dynamically allocate, retrieve, and destroy memory items of a specified size, and
hide the details of list management. An instance number is used to identify
the desired memory item. This number is typically the instance number assigned by
the system.
Routines are provided for the following tasks:
Initialize a driver's soft-state list
Allocate space for an instance of a driver's soft state
Retrieve a pointer to an instance of a driver's soft state
Free the memory for an instance of a driver's soft state
Finish using a driver's soft-state list
See Loadable Driver Interfaces for an example of how to use these routines.
Programmed I/O Device Access
Programmed I/O device access is the act of reading and writing of
device registers or device memory by the host CPU. The Solaris DDI provides
interfaces for mapping a device's registers or memory by the kernel as well
as interfaces for reading and writing to device memory from the driver. These
interfaces enable drivers to be developed that are platform and bus independent, by
automatically managing any difference in device and host endianness as well as by
enforcing any memory-store sequence requirements imposed by the device.
Direct Memory Access (DMA)
The Solaris platform defines a high-level, architecture-independent model for supporting DMA-capable devices.
The Solaris DDI shields drivers from platform-specific details. This concept enables a common
driver to run on multiple platforms and architectures.
Layered Driver Interfaces
The DDI/DKI provides a group of interfaces referred to as layered device interfaces
(LDI). These interfaces enable a device to be accessed from within the Solaris
kernel. This capability enables developers to write applications that observe kernel device usage.
For example, both the prtconf(1M) and fuser(1M) commands use LDI to enable system
administrators to track aspects of device usage. The LDI is covered in more
detail in Chapter 14, Layered Driver Interface (LDI).
Driver Context
The driver context refers to the condition under which a driver is currently
operating. The context limits the operations that a driver can perform. The driver
context depends on the executing code that is invoked. Driver code executes in
four contexts:
User context. A driver entry point has user context when invoked by a user thread in a synchronous fashion. That is, the user thread waits for the system to return from the entry point that was invoked. For example, the read(9E) entry point of the driver has user context when invoked by a read(2) system call. In this case, the driver has access to the user area for copying data into and out of the user thread.
Kernel context. A driver function has kernel context when invoked by some part of the kernel. In a block device driver, the strategy(9E) entry point can be called by the pageout daemon to write pages to the device. Because the page daemon has no relation to the current user thread, strategy(9E) has kernel context in this case.
Interrupt context.Interrupt context is a more restrictive form of kernel context. Interrupt context is invoked as a result of the servicing of an interrupt. Driver interrupt routines operate in interrupt context with an associated interrupt level. Callback routines also operate in an interrupt context. See Chapter 8, Interrupt Handlers for more information.
High-level interrupt context.High-level interrupt context is a more restricted form of interrupt context. If ddi_intr_hilevel(9F) indicates that an interrupt is high level, the driver interrupt handler runs in high-level interrupt context. See Chapter 8, Interrupt Handlers for more information.
The manual pages in section 9F document the allowable contexts for each function.
For example, in kernel context the driver must not call copyin(9F).
Returning Errors
Device drivers do not usually print messages, except for unexpected errors such as
data corruption. Instead, the driver entry points should return error codes so that
the application can determine how to handle the error. Use the cmn_err(9F) function
to write messages to a system log that can then be displayed on
the console.
The format string specifier interpreted by cmn_err(9F) is similar to the printf(3C)
format string specifier, with the addition of the format %b, which prints bit fields.
The first character of the format string can have a special meaning. Calls
to cmn_err(9F) also specify the message level, which indicates the severity label
to be printed. See the cmn_err(9F) man page for more details.
The level CE_PANIC has the side effect of crashing the system. This level
should be used only if the system is in such an unstable state
that to continue would cause more problems. The level can also be
used to get a system core dump when debugging. CE_PANIC should not be used
in production device drivers.
Dynamic Memory Allocation
Device drivers must be prepared to simultaneously handle all attached devices that the
drivers claim to drive. The number of devices that the driver handles should
not be limited. All per-device information must be dynamically allocated.
void *kmem_alloc(size_t size, int flag);
The standard kernel memory allocation routine is kmem_alloc(9F). kmem_alloc() is similar to the C
library routine malloc(3C), with the addition of the flag argument. The flag argument can
be either KM_SLEEP or KM_NOSLEEP, indicating whether the caller is willing to block
if the requested size is not available. If KM_NOSLEEP is set and memory
is not available, kmem_alloc(9F) returns NULL.
kmem_zalloc(9F) is similar to kmem_alloc(9F), but also clears the contents of the allocated
memory.
Note - Kernel memory is a limited resource, not pageable, and competes with user applications
and the rest of the kernel for physical memory. Drivers that allocate a
large amount of kernel memory can cause system performance to degrade.
void kmem_free(void *cp, size_t size);
Memory allocated by kmem_alloc(9F) or by kmem_zalloc(9F) is returned to the system
with kmem_free(9F). kmem_free() is similar to the C library routine free(3C), with
the addition of the size argument. Drivers must keep track of the size
of each allocated object in order to call kmem_free(9F) later.
Hotplugging
This manual does not highlight hotplugging information. If you follow the rules and
suggestions for writing device drivers given in this book, your driver should be
able to handle hotplugging. In particular, make sure that both autoconfiguration (see Chapter 6, Driver Autoconfiguration) and
detach(9E) work correctly in your driver. In addition, if you are designing
a driver that uses power management, you should follow the information given in
Chapter 12, Power Management. SCSI HBA drivers might need to add a cb_ops structure to their dev_ops
structure (see Chapter 18, SCSI Host Bus Adapter Drivers) to take advantage of hotplugging capabilities.
Previous versions of the Solaris OS required hotpluggable drivers to include a DT_HOTPLUG
property, but this property is no longer required. Driver writers are free, however,
to include and use the DT_HOTPLUG property as they see fit.