Some of the many changes to device drivers in the Solaris 7 operating environment include the new DDI/DKI routines, Solaris SPARC DDI-specific routines, new software properties, and loadable drivers. In addition, many previous device issues have become opaque to the driver including interrupts, DVMA, and memory mapping.
In previous SunOS releases, a driver writer had to cope with changes in the device driver interfaces. Usually, there was a porting effort with each release of the operating system. In addition, the interfaces for each platform varied, so device drivers often required separate releases for each platform. Third-party device driver releases often included complex scripts that would reconfigure and rebuild the operating system in order to integrate a device driver. It was costly to support and maintain device drivers.
Unlike previous releases of SunOS systems (SunOS release 4.1.3 software and earlier), the device driver interfaces in the Solaris 7 operating environment are formalized and are referred to as the Solaris 7 SPARC DDI/DKI. The Solaris 7 SPARC DDI/DKI provides binary compatibility of device drivers across all supported platforms and for all future releases of the Solaris 7 operating environment on those platforms.
The term DDI/DKI is derived from the original specification as supplied in the SVR4 release. It stands for device driver interface/driver kernel interface. The interfaces are divided into three groups:
The DDI/DKI interfaces were standardized in SVR4, and are generic across all implementations of SVR4, regardless of the platform on which they are running.
The DDI-only interfaces are intended to be architecture-specific; for example, methods to access and control-device and system-specific hardware (that is, I/O registers, DMA services, interrupts, and memory mapping). These interfaces are not guaranteed to work in other SVR4 implementations.
This group of features effectively lowers the cost of driver support and maintenance. These features, combined with the large number of SPARC platforms, are helpful to many new third-party hardware developers.
With this level of binary compatibility, third-party hardware developers can now "shrink-wrap" their DDI-compliant device drivers with their driver hardware. Installing a new driver package can now be entirely automated. The self-configuring kernel removes the necessity for recompiling the kernel to add or remove a driver. Thus, a DDI-compliant device driver for Solaris 7 environments can be treated like any other consumer software product.
In the Solaris 7, DDI/DKI the DDI-only interfaces are generic to all systems that support the Solaris 7 DDI/DKI. Note that the interfaces that make up the common SCSI architecture (SCSA), and the locking interfaces used to make the driver behave correctly in a multithreaded kernel, are also considered DDI-only interfaces in the Solaris 7 operating environment.
SCSA shields device drivers from details specific to the platform relating to host adapter implementations. With SCSA, a SCSI driver can run on all supported platforms.
A device driver that restricts itself to using only interfaces in the categories desribed above is said to be Solaris 7 DDI/DKI compliant. A Solaris 7 DDI/DKI compliant device driver is commonly referred to as a DDI-compliant device driver.
The man pages for the driver routines, structures, and support routines that comprise the DDI/DKI can be found in the sections of man Pages(1M): System Administration Commands listed below. See the Intro(9) man page for more information about these sections.
Section 9E - Driver entry points
Section 9F - Driver support functions
A Device Driver Developers Kit (DDK) is available separately.
The Solaris 7 devinfo command performs a different function from the SunOS release 4 version. The new prtconf(1M) command provides the information that the SunOS release 4 devinfo command formerly displayed. The following examples show the output of each command.
4.1system% devinfo Node 'SUNW,Sun 4/50', unit #0 (no driver) Node 'packages', unit #0 (no driver) Node 'openprom', unit #0 (no driver) Node 'zs', unit #0 Node 'zs', unit #1 Node 'audio', unit #0 Node 'eeprom', unit #0 (no driver) Node 'counter-timer', unit #0 (no driver) Node 'memory-error', unit #0 (no driver) Node 'interrupt-enable', unit #0 (no driver) Node 'auxiliary-io', unit #0 (no driver) Node 'sbus', unit #0 Node 'dma', unit #0 Node 'esp', unit #0 Node 'sr', unit #0 Node 'sd', unit #0 Node 'le', unit #0 Node 'cgsix', unit #0 Node 'memory', unit #0 (no driver) Node 'virtual-memory', unit #0 (no driver) Node 'fd', unit #0 Node 'options', unit #0 (no driver)
5.3system% prtconf System Configuration: Sun Microsystems sun4c Memory size: 32 Megabytes System Peripherals (Software Nodes): SUNW,Sun 4_75 packages (driver not attached) disk-label (driver not attached) deblocker (driver not attached) obp-tftp (driver not attached) openprom (driver not attached) zs, instance #0 zs, instance #1 audio (driver not attached) eeprom (driver not attached) counter-timer (driver not attached) memory-error (driver not attached) interrupt-enable (driver not attached) auxiliary-io (driver not attached) sbus, instance #0 dma, instance #0 esp, instance #0 sd (driver not attached) st (driver not attached) sd, instance #0 sd, instance #1 (driver not attached) sd, instance #2 (driver not attached) sd, instance #3 sd, instance #4 (driver not attached) sd, instance #5 (driver not attached) sd, instance #6 le, instance #0 cgsix, instance #0 memory (driver not attached) virtual-memory (driver not attached) fd (driver not attached) options, instance #0 pseudo, instance #0
Under the SunOS release 4 software, only one processor could be in the kernel at any one time. This was accomplished by using a master lock around the entire kernel. When a processor wanted to execute kernel code, it would acquire the lock (excluding other processors from running the code protected by the lock) and it would release the lock when it finished.
The Solaris 7 kernel is multithreaded. Instead of one master lock, there are many smaller locks that protect smaller regions of code. For example, there may be a kernel lock that protects access to a particular vnode, and one that protects an inode. Only one processor can be running code dealing with that vnode at a time, but another could be accessing an inode. This allows a greater amount of concurrency.
The multithreaded kernel will have a major impact on how you design the driver. The old model of using splN/splr pairs no longer works (on a uniprocessor or a multiprocessor system). Instead, you have a choice of MT-style locks. The most common of these for drivers will be mutual exclusion locks, mutexes, and condition variables (which are an approximate equivalent of sleep()/wakeup() synchronization).
The SpIN/pplr pair does block interrupts, but the effect is useless in protecting data structures in a multiprocessor environment.
The old notion that you owned the processor until you explicitly called sleep() is no longer true. Because of kernel pre-emption, the CPU is switched from thread to thread so you must use the appropriate MT lock primitives to guard against concurrent access to device registers, shared data structures, and the like.
A large percentage of the driver code for simple device drivers, which consist primarily of calls to kernel interface routines, will change, but in straightforward ways. For complex device drivers (such as a SCSI driver) which contain large amounts of device-specific handling code, only a small percentage of the driver--the driver interfaces--changes. This driver interface can be a kernel-to-driver interface, a driver-to-kernel interface, or a driver-to-driver interface.
Before you determine how you will support a driver in the Solaris 7 operating environment, refamiliarize yourself with how the driver works. Determine what the SunOS release 4 driver did (not the specific implementation, but general behavior). What interfaces did it export? What ioctl()s did it provide? How did the hardware work and what peculiarities of the hardware did the driver support? Did the driver support multiple open() calls?
The following changes affect drivers and should be considered:
The entry points to drivers are very different
Most structures have become opaque or are no longer needed. For example:
In the SunOS release 4 software, you had to know that a particular driver was a STREAMS driver before making ioctl() requests.
ioctl(fd, DRIVER_IOCTL, arg);
For a STREAMS driver, you had to set up a strioctl structure and then use:
ioctl(fd, I_STR, &strioctl);
There was no easy way to determine whether a driver was STREAMS-based. Now, unrecognized ioctls to the stream head are passed on to the driver, eliminating the need to know whether a driver was STREAMS-based.
Message types added in the Solaris 7 software support transparent ioctls. There are now "copy in" and "copy out" messages to inform the STREAM head to transfer user data to and from the kernel.
For more information on writing STREAMS drivers, see the STREAMS Programming Guide.
In the Solaris 7 operating environment, the system administrator and the autopush(1M) command specify when a STREAMS module is pushed. If required, autopush can be run at driver installation.
See STREAMS Programming Guide for more information about pushing STREAMS modules.
To achieve binary compatibility across all currently supported hardware platforms, the DDI interfaces were carefully designed around architectural abstractions. The underlying abstraction, the device tree, is an extension of the devinfo tree in the original SPARCstationTM design. Each node in the device tree is described by a device information structure or "dev_info node." The bottom-most nodes in the tree are termed leaf nodes. Most devices (such as disks and tape drives, framebuffers, I/O cards, and network interfaces) are examples of leaf devices that would be associated with leaf nodes. The associated device drivers are called leaf drivers.
The intermediate nodes in the tree are generally associated with buses (for example, SBus, SCSI, VME). These nodes are called nexus nodes and the drivers associated with them are called nexus drivers. Bus nexi are intended to encapsulate the architectural details associated with a particular element.
The device tree structure creates a formal parent-child relationship between nodes. This parent-child relationship is the key to platform architecture independence.
When a leaf driver requires a service that is platform dependent (for example, a DMA mapping), the system transparently converts the request into a call to its parent to provide the service. The service providers are always nexus drivers; each nexus driver can in turn pass the request to its parent in order to provide the service. This approach enables leaf drivers to operate regardless of the platform architecture.