This section compares operational features of character I/O device drivers with STREAMS drivers and modules. It is intended for experienced developers of system character device drivers. The Drivers section includes a discussion of clone device drivers and the log device driver. The Modules section includes a discussion of the timod and the tirdwr modules. The 64-Bit Support section discusses the impact of 64-bit support on STREAMS drivers and modules.
No user environment is generally available to STREAMS module procedures and drivers. Exceptions are the module and driver open and close routines, both of which have access to the u_area of the calling process and both of which can sleep. Otherwise, a STREAMS driver, module put procedure, and module service procedure have no user context and can neither sleep nor access the u_area.
Multiple streams can use a copy of the same module (that is, the same fmodsw), each containing the same processing procedures. Therefore, modules must be reentrant, and care must be exercised when using global data in a module. Put and service procedures are always passed the address of the QUEUE (for example, in the Stream Detail diagram , Au calls the Bu put procedure with Bu as a parameter). The processing procedure establishes its environment solely from the QUEUE contents, which is typically the private data (for example, state information).
At the interface to hardware devices, character I/O drivers have interrupt entry points; at the system interface, those same drivers generally have direct entry points (routines) to process open, close, read, and write subroutines, and ioctl operations.
STREAMS device drivers have similar interrupt entry points at the hardware device interface and have direct entry points only for the open and close subroutines. These entry points are accessed using STREAMS, and the call formats differ from character device drivers. The put procedure is a driver's third entry point, but it is a message (not system) interface. The stream head translates write subroutines and ioctl operations into messages and sends them downstream to be processed by the driver's write QUEUE put procedure. The read subroutine is seen directly only by the stream head, which contains the functions required to process subroutines. A driver does not know about system interfaces other than the open and close subroutines, but it can detect the absence of a read subroutine indirectly if flow control propagates from the stream head to the driver and affects the driver's ability to send messages upstream.
For input processing, when the driver is ready to send data or other information to a user process, it does not wake up the process. It prepares a message and sends it 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 output processing, the driver receives messages from the stream head instead of processing a write subroutine. If a 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.
Drivers and modules can pass signals, error codes, and return values to processes by using message types provided for that purpose.
There are three special device drivers:
clone | Finds and opens an unused minor device on another STREAMS driver. |
log | Provides an interface for the STREAMS error-logging and event-tracing processes. |
sad | Provides an interface for administrative operations. |
Modules have user context available only during the execution of their open and close routines. Otherwise, the QUEUEs forming the module are not associated with the user process at the end of the stream, nor with any other process. Because of this, QUEUE procedures must not sleep when they cannot proceed; instead, they must explicitly return control to the system. The system saves no state information for the QUEUE. The QUEUE must store this information internally if it is to proceed from the same point on a later entry.
When a module or driver that requires private working storage (for example, for state information) is pushed, the open routine must obtain the storage from external sources. STREAMS copies the module template from the fmodsw table for the I_PUSH operation, so only fixed data can be contained in the module template. STREAMS has no automatic mechanism to allocate working storage to a module when it is opened. The sources for the storage typically include either a module-specific kernel array, installed when the system is configured, or the STREAMS buffer pool. When using an array as a module storage pool, the maximum number of copies of the module that can exist at any one time must be determined. For drivers, this is typically determined from the physical devices connected, such as the number of ports on a multiplexor. However, certain types of modules may not be associated with a particular external physical limit. For example, the CANONICAL module shown in the Module Reusability diagram could be used on different types of streams.
There are two special modules for use with the Transport Interface (TI) functions of the Network Services Library:
timod | Converts a set of ioctl operations into STREAMS messages. |
tirdwr | Provides an alternate interface to a transport provider. |
The STREAMS modules and drivers will set a new flag STR_64BIT in the sc_flags field of the strconf_t structure, to indicate their capability to support 64-bit data types. They will set this flag before calling the str_install subroutine.
At the driver open time, the stream head will set a per-stream 64-bit flag, if all autopushed modules (if any) and the driver support 64-bit data types. The same flag gets updated at the time of module push or pop, based on the module's 64-bit support capability. The system calls that pass data downstream in PSE, putmsg and putpmsg, will check this per-stream flag for that particular stream. Also, certain ioctl subroutines (such as I_STR and I_STRFDINSERT) and transparent ioctls will check this flag too. If the system call is issued by a 64-bit process and this flag is not set, the system call will fail. The 32-bit behavior is not affected by this flag. All of the present AIX Streams modules and drivers will support 64-bit user processes.
At link or unlink operation time, the stream head of upper half of the STREAMS multiplexor updates its per-stream 64-bit flag based on the flag value of the lower half stream head. For example, if the upper half supports 64-bit and lower half does not, then the multiplexor will not support 64-bit processes. This is necessary because all the system calls are processed at the upper half of the multiplexor.
The STREAMS modules and drivers establish the 64-bit or 32-bit user process context by setting the message block flag (the b_flag field of msgb structure), MSG64BIT. This flag is set by the streams head when it allocates a message to process a system call from a 64-bit process. This flag is set for the putmsg, putpmsg, and ioctl system calls; for the I_STR and I_STRFDINSERT commands; and for transparent ioctls.
The third argument of the transparent ioctl is a pointer to the data in user space to be copied in or out. This address is remapped properly by the ioctl system call. The streams driver or module passes M_COPYIN or M_COPYOUT messages to the stream head and the stream head calls the copyin or copyout subroutines.
If the third argument of the ioctl subroutine points to a structure that contains a pointer (for example, ptr64) or long, remapping is solved by a new structure, copyreq64, which contains a 64-bit user space pointer. If the message block flag is set to MSG64BIT, the driver or module will pass M_COPYIN64 or M_COPYOUT64 to copy in or out a pointer within a structure. In this case, the stream head will call copyin64 or copyout64 to move the data into or out of the user address space, respectively.
The copyreq64 structure uses the last unused cq_filler field to store the 64-bit address. The copyreq64 structure looks like the following example:
struct copyreq64 { int cq_cmd; /* command type == ioc_cmd */ cred_t *cq_cr; /* pointer to full credentials*/ int cq_id; /* ioctl id == ioc_id */ ioc_pad cq_ad; /* addr to copy data to/from */ uint cq_size; /* number of bytes to copy */ int cq_flag; /* reserved */ mblk_t *cq_private; /* module's private state info*/ ptr64 cq_addr64; /* 64-bit address */ long cq_filler[2]; /* reserved */ };
The cq_addr64 field is added in the above structure and the size of the cq_filler is reduced, so overall size remains same. The driver or module first determines whether the MSG64BIT flag is set and, if so, stores the user-space 64-bit address in the cq_addr64 field.