Service procedures are required in this example on both the write side and read side for flow control (see Example 9-13). The write service procedure, loopwsrv, takes on the canonical form. The queue being written to is not downstream, but upstream (found through oqptr) on the other stream.
In this case, there is no read-side put procedure so the read service procedure, looprsrv, is not scheduled by an associated put procedure, as has been done previously. looprsrv is scheduled only by being back-enabled when its upstream flow control blockage is released. The purpose of the procedure is to re-enable the writer (loopwsrv) by using oqptr to find the related queue. loopwsrv cannot be directly back-enabled by STREAMS because there is no direct queue linkage between the two streams. Note that no message is queued to the read service procedure. Messages are kept on the write side so that flow control can propagate up to the stream head. qenable(9F) schedules the write-side service procedure of the other stream.
Example 9-13 Loop-Around Driver Flow Control
loopclose breaks the connection between the streams, as shown in Example 9-14. loopclose sends an M_HANGUP message up the connected stream to the stream head.
Example 9-14 Breaking Stream Connections for Loop-Around Device
An application using this driver would first open the clone device node created in the attach routine (/devices/pseudo/clone@0:loopx) two times to obtain two streams. The application can determine the minor numbers of the devices by using fstat(2). Next, it joins the two streams by using the STREAMS I_STR ioctl(2) (see streamio(7I)) to pass the LOOP_SET ioctl(2) with one of the stream's minor numbers as an argument to the other stream. Once this is completed, the data sent to one stream using write(2) or putmsg(2) can be retrieved from the other stream with read(2) or getmsg(2). The application also can interpose STREAMS modules between the stream heads and the driver using the I_PUSH ioctl(2).
Summarizing STREAMS Device Drivers
STREAMS device drivers are in many ways similar to non-STREAMS device drivers. The following points summarize the differences between STREAMS drivers and other drivers:
Drivers must have attach(9E), probe(9E), and identify(9E) entry points to initialize the driver. The attach routine initializes the driver. Software drivers usually have little to initialize, because there is no hardware involved.
Drivers have open(9E) and close(9E) routines.
Most drivers have an interrupt handler routine. The driver developer is responsible for supplying an interrupt routine for the device's driver. In addition to hardware interrupts, the system also supports software interrupts. A software interrupt is generated by calling ddi_trigger_softintr(9F).
All minor nodes are generated by ddi_create_minor_node(9F).
STREAMS device drivers also are similar to STREAMS modules. The following points summarize some of the differences between STREAMS modules and drivers.
Messages that are not understood by the drivers should be freed.
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 must be sent upstream.
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.
A hardware driver must provide an interrupt routine.
Multithreaded drivers must protect their own data structures.
For more information on global driver issues and non-STREAMS drivers, see Writing Device Drivers.