This chapter describes the operation of STREAMS drivers and some of the processing typically required in the drivers.
STREAMS Device Drivers
STREAMS drivers can be considered a subset of device drivers in general and character device drivers in particular. While there are some differences between STREAMS drivers and non-STREAMS drivers, much of the information contained in Writing Device Drivers also applies to STREAMS drivers.
Note - The word module is used differently when talking about drivers. A device driver is a kernel-loadable module that provides the interface between a device and the Device Driver Interface, and is linked to the kernel when it is first invoked.
STREAMS drivers share a basic programming model with STREAMS modules. Information common to both drivers and modules is discussed in Chapter 10, STREAMS Modules. After summarizing some basic device driver concepts, this chapter discusses several topics specific to STREAMS device drivers (and not covered elsewhere) and then presents code samples illustrating basic STREAMS driver processing.
Device drivers can also be software-only, implementing a pseudo-device such as RAM disk or a pseudo-terminal that only exists in software.
In the Solaris operating environment, the interface between the kernel and device drivers is called the Device Driver Interface (DDI/DKI). This interface is specified in the Section 9E manual pages that specify the driver entry points. Section 9 also details the kernel data structures (9S) and utility functions (9F) available to drivers.
The DDI protects the kernel from device specifics. Application programs and the rest of the kernel need little (if any) device-specific code to use the device. The DDI makes the system more portable and easier to maintain.
There are three basic types of device drivers corresponding to the three basic types of devices. Character devices handle data serially and transfer data to and from the processor one character at a time, the same as keyboards and low performance printers. Serial block devices and drivers also handle data serially, but transfer data to and from memory without processor intervention, the same as tape drives. Direct access block devices and drivers also transfer data without processor intervention and blocks of storage on the device can be addressed directly, the same as disk drives.
There are two types of character device drivers: standard character device drivers and STREAMS device drivers. STREAMS is a separate programming model for writing a character driver. Devices that receive data asynchronously (such as terminal and network devices) are suited to a STREAMS implementation.
STREAMS drivers share some kinds of processing with STREAMS modules. Important differences between drivers and modules include how the application manipulates drivers and modules and how interrupts are handled. In STREAMS, drivers are opened and modules are pushed. A device driver has an interrupt routine to process hardware interrupts.
STREAMS Driver Entry Points
STREAMS drivers have five different points of contact with the kernel:
Table 9-1 Kernel Contact Points
STREAMS Configuration Entry Points
As with other SunOS 5 drivers, STREAMS drivers are dynamically linked and loaded when referred to for the first time. For example, when the system is initially booted, the STREAMS pseudo-tty slave pseudo-driver (pts(7D)) is loaded automatically into the kernel when it is first opened.
In STREAMS, the header declarations differ between drivers and modules. The word "module" is used in two different ways when talking about drivers. There are STREAMS modules, which are pushable nondriver entities, and there are kernel-loadable modules, which are components of the kernel. See the appropriate chapters in Writing Device Drivers.
The kernel configuration mechanism must distinguish between STREAMS devices and traditional character devices because system calls to STREAMS drivers are processed by STREAMS routines, not by the system driver routines. The streamtab pointer in the cb_ops(9S) structure provides this distinction. If it is NULL, there are no STREAMS routines to execute; otherwise, STREAMS drivers initialize streamtab with a pointer to a streamtab(9S) structure containing the driver's STREAMS queue processing entry points.
STREAMS Initialization Entry Points
STREAMS Table-Driven Entry Points
In non-STREAMS drivers, most of the driver's work is accomplished through the entry points in the cb_ops(9S) structure. For STREAMS drivers, most of the work is accomplished through the message-based STREAMS queue processing entry points.
Figure 9-1 shows multiple streams (corresponding to minor devices) connecting to a common driver. There are two distinct streams opened from the same major device. Consequently, they have the same streamtab and the same driver procedures.
Figure 9-1 Device Driver Streams
Multiple instances (minor devices) of the same driver are handled during the initial open for each device. Typically, a driver stores the queue address in a driver-private structure that is uniquely identified by the minor device number. (The DDI/DKI provides a mechanism for uniform handling of driver-private structures; see ddi_soft_state(9F)). The q_ptr of the queue points to the private data structure entry. When the messages are received by the queue, the calls to the driver put and service procedures pass the address of the queue, enabling the procedures to determine the associated device through the q_ptr field.
STREAMS guarantees that only one open or close can be active at a time per major/minor device pair.
STREAMS Queue Processing Entry Points
STREAMS device drivers have processing routines that are registered with the framework through the streamtab structure. The put procedure is a driver's entry point, but it is a message (not system) interface. STREAMS drivers and STREAMS modules implement these entry points similarly, as described in "Entry Points".
The stream head translates write(2) and ioctl(2) calls into messages and sends them downstream to be processed by the driver's write queue put(9E) procedure. read is seen directly only by the stream head, which contains the functions required to process system calls. A STREAMS driver does not check system interfaces other than open and close, but it can detect the absence of a read indirectly if flow control propagates from the stream head to the driver and affects the driver's ability to send messages upstream.
For read-side processing, when the driver is ready to send data or other information to a user process, it prepares a message and sends it upstream to the read queue of the appropriate (minor device) stream. The driver's open routine generally stores the queue address corresponding to this stream.
For write-side (or output) processing, the driver receives messages in place of a write call. If the message cannot be sent immediately to the hardware, it may be stored on the driver's write message queue. Subsequent output interrupts can remove messages from this queue.
A driver is at the end of a stream. As a result, drivers must include standard processing for certain message types that a module might be able to pass to the next component. For example, a driver must process all M_IOCTL messages; otherwise, the stream head blocks for an M_IOCNAK, M_IOCACK, or until the timeout (potentially infinite) expires. If a driver does not understand an ioctl(2), an M_IOCNAK message is sent upstream.
Messages that are not understood by the drivers should be freed.
The stream head locks up the stream when it receives an M_ERROR message, so driver developers should be careful when using the M_ERROR message.
STREAMS Interrupt Handlers
Most hardware drivers have an interrupt handler routine. You must supply an interrupt routine for the device's driver. The interrupt handling for STREAMS drivers is not fundamentally different from that for other device drivers. Drivers usually register interrupt handlers in their attach(9E)entry point, using ddi_add_intr(9F). Drivers unregister the interrupt handler at detach time using ddi_remove_intr(9F).
The system also supports software interrupts. The routines ddi_add_softintr(9F) and ddi_remove_softintr(9F) register and unregister (respectively) soft-interrupt handlers. A software interrupt is generated by calling ddi_trigger_softintr(9F).
See Writing Device Drivers for more information.
STREAMS drivers can prevent unloading through the standard driver detach(9E) entry point.
STREAMS Driver Code Samples
The following discussion describes characteristics of a STREAMS driver:
Basic hardware/pseudo drivers
This type of driver communicates with a specific piece of hardware (or simulated hardware). The lp example simulates a simple printer driver.
The STREAMS framework supports a CLONEOPEN facility, which allows multiple streams to be opened from a single special file. If a STREAMS device driver chooses to support CLONEOPEN, it can be referred to as a clonable device. The attach(9E) routines from two Solaris drivers, ptm(7D) and log(7D), illustrate two approaches to cloning.
Multiple instances in drivers
A multiplexer driver is a regular STREAMS driver that can handle multiple streams connected to it instead of just one stream. Multiple connections occur when more than one minor device of the same driver is in use. See "Cloning STREAMS Drivers" for more information.