Page: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19 20

Chapter 8


STREAMS Drivers

8.1 INTRODUCTION

A driver is software that provides an interface between the operating system and a device. The driver controls the device in response to kernel commands, and user-level programs access the device through system calls. The system calls an interface with the file system and process control system, which in turn access the drivers. The driver provides and manages a path for the data to and from the hardware device, and services interrupts issued by the device controller.

Unlike a module, a device driver may have an interrupt routine so that it is accessible from a hardware interrupt as well as from the Stream. A driver can have multiple Streams connected to it. Multiple connections occur when more than one minor device of the same driver is in use and for multiplexors. However, these particular differences are not recognized by the STREAMS mechanism. They are handled by developer-provided code included in the driver procedures. This chapter describes the operation of a STREAMS driver, and discusses some of the processing typically required in drivers. 

8.1.1 Driver Classification

In general, drivers are grouped according to the type of the device they control, the access method (the way data is transferred), and the interface between the driver and the device.

The type can be hardware or software. A hardware driver controls a physical device such as a disk. A software driver, also called a pseudo-device, controls software, which in turn may interface with a hardware device. The software driver may also support pseudo-devices that have no associated physical device.

Drivers can be character-type or block-type, but many support both access methods. In character-type transfer, data is read a character at a time or as a variable length stream of bytes, the size of which is determined by the device. In block-type access, data transfer is performed on fixed-length blocks of data. Devices that support both block- and character-type access must have a separate special device file for each access method. Character access devices can also use raw (also called unbuffered) data transfer that takes place directly between user address space and the device. Unbuffered data transfer is used mainly for administrative functions where the speed of the specific operation is more important than overall system performance.

The driver interface refers to the system structures and kernel interfaces used by the driver. For example, STREAMS is an interface.

Home

 

8.1.2 Writing a Driver

All drivers are identified by a string of up to four characters, called the prefix. The prefix is defined in the master file for the driver and is added to the name of the driver routines. For example, the open routine for the driver with the xyz prefix is xyzopen.

Writing a driver differs from writing other C programs in the following ways:

Driver code, like other system software, uses the advanced C language capabilities. These include bit-manipulation capabilities, casting of data types, and use of header files for defining and declaring global data structures.

Driver code includes a set of entry point routines:

The following lists rules of driver development:

8.1.3 Major and Minor Device Numbers

A device appears to the UNIX system as a special device file. The system accesses a device by opening, reading, writing, and closing the device's special device file.

The system identifies and accesses the special device file using the file's major and minor device numbers. The major number identifies a driver for a controller. The minor number identifies a specific device.

Major numbers are assigned by the installation and configuration software. Minor numbers are designated by the driver developer.

Minor numbers are determined differently for different types of devices. Typically, minor numbers are an encoding of information needed by the controller board.

The switch tables will have only as many entries as required to support the drivers installed on the system. Switch table entry points are activated by system calls that reference a special device file that supplies the external major number and instructions on whether to use bdevsw or cdevsw. The routines getmajor and getminor return an internal major and minor number for the device.

Home

 

8.1.4 STREAMS Drivers

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, write, and ioctl system calls.

STREAMS device drivers have interrupt entry points at the hardware device interface and have direct entry points only for the open and close system calls. These entry points are accessed by STREAMS, and the call formats differ from traditional character device drivers. (STREAMS drivers are character drivers, too. We call the non-STREAMS character drivers traditional character drivers or non-STREAMS character drivers.) The put procedure is a driver's third entry point, but it is a message (not system) interface. The Stream head translates write and ioctl calls into messages and sends them downstream to be processed by the driver's write queue put procedure. read is seen directly only by the Stream head, which contains the functions required to process system calls. A driver does not know about 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 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 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. Later output interrupts can remove messages from this queue.

Figure 8-1 shows multiple Streams (corresponding to minor devices) 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.

The configuration mechanism distinguishes between STREAMS devices and traditional character devices, because system calls to STREAMS drivers are processed by STREAMS routines, not by the UNIX system driver routines. In the cdevsw file, the field d_str provides this distinction.

Multiple instantiations (minor devices) of the same driver are handled during the initial open for each device. Typically, the queue address is stored in a driver-private structure array indexed by the minor device number. This is for use by the interrupt routine that needs to translate from device number to a particular Stream. 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, allowing the procedures to determine the associated device.

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 simply be able to pass to the next component.

STREAMS guarantees that only one open or close routine will be active at any time for any given major/minor pair.

Home

 

 

Figure 8-1 Device Driver Streams
8.1.4.1 PRINTER DRIVER EXAMPLE
Figure 8-2 through Figure 8-6 show how a simple interrupt-per-character line printer driver could be written. The driver is unidirectional and has no read-side processing. It shows some differences between module and driver programming, including the following:

Open handlingA driver is passed a device number or is asked to select one.
Flush handlingA driver must loop M_FLUSH messages back upstream.
ioctl handlingA driver must send a negative acknowledgment for ioctl messagees it does not understand. This is discussed in Section 6.2.

Home

 

Declarations

The driver declarations are shown in Figure 8-2. For more information, see Section 6.1.3.

/* Simple line printer driver */

#include <sys/types.h>

#ifdef MT

#include "sys/mplock.h"

#endif

#include <sys/param.h>

#include <sys/sysmacros.h>

#include <sys/stream.h>

#include <sys/stropts.h>

#include <sys/errno.h>

static struct module_info minfo = {

0xaabb, "lp", 0, INFPSZ, 150, 50 };

static int lpopen(queue_t *, dev_t *, int, int, void *);

static int lpclose(queue_t *, int, void *);

static int lpwput(queue_t *, mblk_t *);

static struct qinit rinit = {

NULL, NULL, lpopen, lpclose, NULL, &minfo, NULL };

static struct qinit winit = {

lpwput, NULL, NULL, NULL, NULL, &minfo, NULL };

struct streamtab lpinfo = { &rinit, &winit, NULL, NULL };

#define SET_OPTIONS ((`l'<<8)|1) /* should be in a .h file */

/* This is a private data structure, one per minor device number. */

#ifdef MT

/* Access to struct lp must be protected by DDI/DKI locks or */

/* synchronization primitives. */

#endif

struct lp {

short flags; /* flags see below */

mblk_t *msg; /* current message being output */

queue_t *qptr; /* back pointer to write queue */

#ifdef MT

mask_t *lck;

#endif

};

/* Flags bits */

#define BUSY 1 /* device is running and interrupt is pending */

extern struct lp lp_lp[]; /* per device lp structure array */

#ifdef MT

extern mask_t lplck[];

#endif

extern int lp_cnt; /* number of valid minor devices */

#ifdef MT

int lpdevflag = D_MP;

#else

int lpdevflag = 0;

#endif

Figure 8-2 Line Printer Driver

Home

 

Configuring a STREAMS driver requires only the streamtab structure to be externally accessible. For hardware drivers, the interrupt handler must also be externally accessible. All other STREAMS driver procedures would typically be declared static.

The streamtab structure must be defined as prefix info, where prefix is the value of the prefix field in the master file for this driver. The values in the module name and ID fields in the module_info structure should be unique in the system. Note that, as in character I/O drivers, extern variables are assigned values in the master file when configuring drivers or modules.

The private lp structure is indexed by the minor device number and contains these elements:
flags  A set of flags. Only one bit is used: BUSY indicates that output is active and a device interrupt is pending.
msg A pointer to the current message being output.
qptr  A back pointer to the write queue. This is needed to find the write queue during interrupt processing.

   lck         A DDI/DKI driver lock to prevent race conditions on the structure.

There is no read-side put or service procedure. The flow control limits for use on the write-side are 50 bytes for the low water mark and 150 bytes for the high water mark.

Home

 

Driver Open

The STREAMS mechanism allows only one Stream per minor device. The driver open routine is called whenever a STREAMS device is opened. Opening also allocates a private data structure. The driver open, lpopen in Figure 8-3, has the same interface as the module open.

#ifdef MT

void lpinit()

{

register struct lp *lp;

mask_t *lcp;

extern mask_t tmplck[];

/*

* allocate multiprocessor lock for each minor device

*/

for (lp = lp_lp, lcp = lplck; lp<&lp_lp[lp_int]; lp++,lcp++){

mask_init (lcp, tmplck);

lp->lck = lcp;

}

}

#endif

int lpopen(queue_t *q, dev_t *devp, int flag, int sflag, void *credp)

{

struct lp *lp;

dev_t device;

if (sflag) /* check if non-driver open */

return ENXIO;

/* check if already open */

device = getminor(*devp);

if (device > lp_cnt)

return ENXIO;

if (q->q_ptr)

return EBUSY;

/* point q_ptr at driver structure */

lp = &lp_lp[device];

lp->qptr = WR(q);

q->q_ptr = (char *)lp;

WR(q)->q_ptr = (char *)lp;

#ifdef MT

/* enable put and srv routines for queue pair */

qprocson(q);

#endif

return 0;

}

Figure 8-3 Driver Open

The Stream flag, sflag, must have the value 0, indicating a normal driver open. devp is a pointer to the major/minor device number for this port. After checking sflag, the STREAMS open flag, lpopen, extracts the minor device pointed to by devp, using the getminor function.

The minor device number selects a printer. The device number pointed to by devp must be less than lp_cnt, the number of configured printers. Otherwise, failure occurs.

The next check, if (q->q_ptr) . . . , determines if this printer is already open. If it is, EBUSY is returned to avoid merging printouts from multiple users. q_ptr is a driver/module private data pointer. It can be used by the driver for any purpose and is initialized to zero by STREAMS. In this example, the driver sets the value of q_ptr, in both the read and write queue structures, to point to a private data structure for the minor device, lp_lp[device].

Home

 

There are no physical pointers between queues. WR(q) generates the write pointer from the read pointer. RD(q) generates the read pointer from the write pointer, and OTHERQ(q) generates the mate pointer from either.

Driver Flush Handling

The following write put procedure, lpwput, illustrates driver M_FLUSH handling. Note that all drivers are expected to incorporate flush handling.

If FLUSHW is set, the write message queue is flushed, and (in this example) the leading message (lp->msg) is also flushed.

Normally, if FLUSHR is set, the read queue would be flushed. However, in this example, no messages are ever placed on the read queue, so it is not necessary to flush it. The FLUSHW bit is cleared and the message is sent upstream using qreply. If FLUSHR is not set, the message is discarded.

The Stream head always performs the following actions on flush requests received on the read-side from downstream. If FLUSHR is set, messages waiting to be sent to user space are flushed. If FLUSHW is set, the Stream head clears the FLUSHR bit and sends the M_FLUSH message downstream. In this way, a single M_FLUSH message sent from the driver can reach all queues in a Stream. A module must send two M_FLUSH messages to have the same affect.

lpwput enqueues M_DATA and M_IOCTL messages and, if the device is not busy, starts output by calling lpout. Message types that are not recognized are discarded.

Home

 

int lpwput(queue_t *q, mblk_t *mp)

{

register struct lp *lp;

#ifdef MT

int oldpri;

#else

int s;

#endif

lp = (struct lp *)q->q_ptr;

switch(mp->b_datap->db_type) {

default:

freemsg(mp);

break;

case M_FLUSH:

if (*mp->b_rptr & FLUSHW) {

/*

* flush the queue;

* also flush lp->msg since it is logically

* at the head of the write queue.

#ifdef MT

* access to lp must be locked to protect against

* potential multiprocessor race.

#endif

*/

flushq(q, FLUSHDATA);

#ifdef MT

mask_lock(lp->lck);

#else

s = sp16();

#endif

if (lp->msg) {

freemsg(lp->msg);

lp->msg = NULL;

}

#ifdef MT

mask_unlock(lp->lck);

#else

splx(s);

#endif

}

if (*mp->b_rptr & FLUSHR) {

*mp->b_rptr &= ~FLUSHW;

qreply(q, mp);

} else

freemsg(mp);

break;

case M_IOCTL:

case M_DATA:

putq(q, mp);

#ifdef MT

mask_lock(lp->lck);

#else

s = sp16();

#endif

if (!(lp->flags & BUSY))

lpout(lp);

#ifdef MT

mask_unlock(lp->lck);

#else

splx(s);

#endif

}

}

Figure 8-4 Flush Handling
 

Home

 
Driver Interrupt

lpint is the driver interrupt handler routine. lpout simply takes a character from the queue and sends it to the printer. For convenience, the message currently being output is stored in lp->msg. lpoutchar sends a character to the printer and interrupts when complete. Printer interface options need to be set before being able to print.

Figure 8-5 shows the interrupt routine in the printer driver.

/*

* Device interrupt routine

*/

lpint(int device)

{

register struct lp *lp;

#ifdef MT

int oldpri;

#endif

lp = &lp_lp[device];

#ifdef MT

mask_lock(lp->lck);

#endif

if (!(lp->flags & BUSY)) {

#ifdef MT

mask_unlock(lp->lck);

#endif

cmn_err(CE_WARN, "lp: unexpected interrupt n");

return;

lp->flags &= ~BUSY;

lpout(lp);

#ifdef MT

mask_unlock(lp->lck);

#endif

}

/* Start output to device called by put and interrupt routines */

#ifdef MT

/* argument lp is locked on entry */

#endif

lpout(struct lp *lp)

{

register mblk_t *bp;

queue_t *q;

q = lp->qptr;

loop:

if ((bp = lp->msg) == NULL) { /* no current message */

if ((bp = getq(q)) == NULL) {

lp->flags &= ~BUSY;

return;

}

if (bp->b_datap->db_type == M_IOCTL) {

lpioctl(lp, bp);

goto loop;

}

lp->msg = bp; /* new message */

}

if (bp->b_rptr >= bp->b_wptr) { /* validate message */

bp = lp->msg->b_cont;

lp->msg->b_cont = null;

freeb(lp->msg);

lp->msg = bp;

goto loop;

}

lpoutchar(lp, *bp->b_rptr++); /* output one character */

lp->flags |= BUSY;

}

Figure 8-5 Device Interrupt

Driver Close Routine

The driver close routine is called by the Stream head. Any messages left on the queue are automatically removed by STREAMS. The Stream is dismantled and the data structures are deallocated.

static int lpclose(queue_t *q, int flag, void *credp)

{

struct lp *lp;

#ifdef MT

int oldpri;

/*

* disable put and srv routines for q pair

*/

qprocsoff(q);

#else

int s;

#endif

lp = (struct lp *) q->q_ptr;

/* Free message, queue is automatically flushed by STREAMS */

#ifdef MT

mask_lock(lp->lck);

#else

s = sp16();

#endif

if (lp->msg) {

freemsg(lp->msg);

lp->msg = NULL;

}

lp->flags = 0;

#ifdef MT

mask_unlock(lp->lck);

#else

splx(s);

#endif

}

Figure 8-6 Driver Close Routine

Home

 

8.1.4.2 DRIVER FLOW CONTROL

The same utilities and mechanisms used for module flow control are used by drivers.

When the message is queued, putq increments the value of q_count by the size of the message and compares the result against the driver's write high water limit (q_hiwat) value. If the count exceeds q_hiwat, the putq utility routine sets the internal FULL indicator for the driver write queue. This causes messages from upstream to be halted [ returns FALSE] until the write queue count reaches q_lowat. The driver messages waiting to be output are dequeued by the driver output interrupt routine with getq, which decrements the count. If the resulting count is below q_lowat, the getq routine back-enables any upstream queue that had been blocked.

For priority band data, qb_count, qb_hiwat and qb_lowat are used.

Device drivers typically discard input when unable to send it to a user process. However, STREAMS allows flow control to be used on the driver read-side to handle temporary upstream blocks.

To some extent, a driver or a module can control when its upstream transmission will become blocked. Control is available through the M_SETOPTS message to modify the Stream head read-side flow control limits.

Home

 

8.2 CLONING

In many earlier examples, each user process connected a Stream to a driver by opening a particular minor device of that driver. Often, however, a user process had to connect a new Stream to a driver regardless of which minor device is used to access the driver. In the past, this typically forced the user process to poll the various minor device nodes of the driver for an available minor device. To alleviate this task, a facility called "clone open'' is supported for STREAMS drivers. If a STREAMS driver is implemented as a clonable device, a single node in the file system may be opened to access any unused device that the driver controls. This special node guarantees that the user is allocated a separate Stream to the driver on every open call. Each Stream is associated with an unused major/minor device, so the total number of Streams that may be connected to a particular clonable driver is limited by the number of minor devices configured for that driver.

The clone device may be useful, for example, in a networking environment where a protocol pseudo-device driver requires each user to open a separate Stream over which it establishes communication.

Note, however, that a race can occur when simultaneous cloning opens and noncloning opens are in progress. A clone driver must detect this race and return error for the noncloning open.

NOTE

The decision to implement a STREAMS driver as a clonable device is made by the designers of the device driver. Knowledge of clone driver implementation is not required. A description is presented here for completeness and to assist developers who must implement their own clone driver.

There are two ways to create a clone device node in the file system. The first is to have a node with the major number of the clone driver and with a minor number equal to the major number of the real device one wants to open. For example, /dev/net00 might be major 50, minor 0 (normal open), and /dev/net might be major 40 (the major number of the clone driver), minor 50 (the major number of the real device).

The second way to create a clone device node is for the driver to designate a special minor device as its clone entry point. Here, /dev/net might be major 50, minor 0 (clone open).

The former example causes sflag to be set to CLONEOPEN in the open routine when /dev/net is opened. The latter will not. Instead, in the latter case, the driver has decided to designate a special minor device as its clone interface. When the clone is opened, the driver knows that it should look for an unused minor device. This implies that the reserved minor for the clone entry point will never be given out.

In either case, the driver returns the new device number as

Home

 

8.3 LOOP-AROUND DRIVER

The loop-around driver is a pseudo-driver that loops data from one open Stream to another open Stream. The user processes see the associated files almost like a full-duplex pipe. The Streams are not physically linked. The driver is a simple multiplexor that passes messages from one Stream's write queue to the other Stream's read queue.

To create a connection, a process opens two Streams, obtains the minor device number associated with one of the returned file descriptors, and sends the device number in an I_STR ioctl(2) to the other Stream. For each open, the driver open places the passed queue pointer in a driver interconnection table, indexed by the device number. When the driver later receives the I_STR as an M_IOCTL message, it uses the device number to locate the other Stream's interconnection table entry, and stores the appropriate queue pointers in both of the Streams' interconnection table entries.

Subsequently, when messages other than M_IOCTL or M_FLUSH are received by the driver on either Stream's write-side, the messages are switched to the read queue following the driver on the other Stream's read-side. The resultant logical connection is shown in Figure 8-7. In Figure 8-7, the abbreviation QP represents a queue pair. Flow control between the two Streams must be handled by special code since STREAMS does not automatically propagate flow control information between two Streams that are not physically interconnected.

Figure 8-7 Loop-Around Streams

The next example shows the loop-around driver code. The loop structure contains the interconnection information for a pair of Streams. loop_loop is indexed by the minor device number. When a Stream is opened to the driver, the address of the corresponding loop_loop element is placed in q_ptr (private data structure pointer) of the read-side and write-side queues. Because STREAMS clears q_ptr when the queue is allocated, a NULL value of q_ptr indicates an initial open. loop_loop verifies that this Stream is connected to another open Stream.

     This example driver uses coarse-grained locking for simplicity.

Home

 

The declarations for the driver are shown in Figure 8-8.

/* Loop-around driver */

#include <sys/types.h>

#ifdef MT

#include "sys/mplock.h"

#endif

#include <sys/param.h>

#include <sys/sysmacros.h>

#include <sys/stream.h>

#include <sys/stropts.h>

#include <sys/errno.h>

static struct module_info minfo = {

0xee12, "loop", 0, INFPSZ, 512, 128};

static int loopopen(queue_t *, dev_t *, int, int, void *);

static int loopclose(quque_t *, int, void *);

static int loopwput(queue_t *, mblk_t *);

static int loopwsrv(queue_t *);

static int looprsrv(queue_t *);

static struct qinit rinit = {

NULL, looprsrv, loopopen, loopclose, NULL, &minfo, NULL};

static struct qinit winit = {

loopwput, loopwsrv, NULL, NULL, NULL, &minfo, NULL};

struct streamtab loopinfo = {&rinit, &winit, NULL, NULL};

#ifdef MT

extern mask_t loop_lck[];

#endif

struct loop {

queue_t *qptr; /* back pointer to write queue */

queue_t *oqptr; /* pointer to connected read queue */

}

#define LOOP_SET ((`l'<<8)|1) /* should be in a .h file */

extern struct loop loop_loop[];

extern int loop_cnt;

#ifdef MT

int loopdevflag = D_MP;

#else

int loopdevflag = 0;

#endif

Figure 8-8 Driver Declarations

Home

 

The open procedure includes canonical clone processing that enables a single file system node to yield a new minor device/inode each time the driver is opened as shown in Figure 8-9.

int loopopen(queue_t *q, dev_t *devp, int flag, int sflag, void *credp)

{

struct loop *loop;

dev_t newminor;

#ifdef MT

int pl;

#endif

if (q->q_ptr) /* already open */

return(0);

/*

* If CLONEOPEN, pick a minor device number to use.

* Otherwise, check the minor device range.

*/

#ifdef MT

mask_lock(loop_lock);

#endif

if (sflag == CLONEOPEN) {

for (newminor = 0; newminor < loop_cnt; newminor++) {

if (loop_loop[newminor].qptr == NULL)

break;

}

} else

newminor = geteminor(*devp);

if (newminor >= loop_cnt) {

#ifdef MT

mask_unlock(loop_lock);

#endif

return(ENXIO);

}

/* build new device number and reset devp */

/* getmajor gets the external major number, if (sflag == CLONEOPEN) */

*devp = makedev(getemajor(*devp), newminor);

loop = &loop_loop[newminor];

WR(q)->q_ptr = (char *) loop;

q->q_ptr = (char *) loop;

loop->qptr = WR(q);

loop->oqptr = NULL;

#ifdef MT

mask_unlock(loop_lock);

/* enable put and srv routines for this queue pair */

qprocson(q);

#endif

return(0);

}

Figure 8-9 Open Procedure

Home

 

In loopopen, sflag can be CLONEOPEN, indicating that the driver should pick an unused minor device (that is, the user does not care which minor device is used). In this example, the driver scans its private loop_loop data structure to find an unused minor device number. If sflag has not been set to CLONEOPEN, the passed-in minor device specified by geteminor(*devp) is used.

Because the messages are switched to the read queue following the other Stream's read-side, the driver needs a put procedure only on its write-side.

loopwput shows another use of an I_STR ioctl call (see Section 6.2). The driver supports a LOOP_SET value of ioc_cmd in the iocblk of the M_IOCTL message. LOOP_SET instructs the driver to connect the current open Stream to the Stream identified in the message. The second block of the M_IOCTL message holds an integer that specifies the minor device number of the Stream to connect to.

The driver performs the following sanity checks:

If everything checks out, the read queue pointers for the two Streams are stored in the respective oqptr fields. This cross-connects the two Streams indirectly, using loop_loop.

Canonical flush handling is incorporated in the put procedure.

Home

 

Finally, loopwput enqueues all other messages (for example, M_DATA or M_PROTO) for processing by its service procedure. A check is made to see if the Stream is connected. If not, an M_ERROR is sent upstream to the Stream head. See Figure 8-10.

int loopwput(queue_t *q, mblk_t *mp)

{

register struct loop *loop;

#ifdef MT

int pl;

#endif

loop = (struct loop *) q->q_ptr;

switch (mp->b_datap->db_type) {

case M_IOCTL: {

struct iocblk *iocp;

int error;

iocp = (struct iocblk *) mp->b_rptr;

switch (iocp->ioc_cmd) {

case LOOP_SET: {

int to; /* other minor device */

/*

* Sanity check. ioc_count contains the amount of

* user supplied data that must equal the size of

* an int.

*/

if (iocp->ioc_count != sizeof(int)) {

error = EINVAL;

goto iocnak;

}

/* fetch other dev from 2nd message block */

to = *(int *)mp->b_cont->b_rptr;

/*

* More sanity checks. The minor must be in range, open

* already. Also, this device and the other one must be

* disconnected.

*/

#ifdef MT

mask_lock(loop_lock);

#endif

if (to >= loop_cnt || to < 0 || !loop_loop[to].qptr) {

error = ENXIO;

#ifdef MT

mask_unlock(loop_lock);

#endif

goto iocnak;

}

if (loop->oqptr || loop_loop[to].oqptr) {

error = EBUSY;

#ifdef MT

mask_unlock(loop_lock);

#endif

goto iocnak;

}

/* Cross connect streams using the loop structures */

loop->oqptr = RD(loop-loop[to].qptr);

loop_loop[to].oqptr = RD(q);

#ifdef MT

mask_unlock(loop_lock);

#endif

/*

* Return successful ioctl. Set ioc_count to zero,

* since no data is returned.

*/

mp->b_datap->db_type = M_IOCACK;

iocp->ioc_count = 0;

qreply(q, mp);

break;

}

default:

error = EINVAL;

iocnak:

/*

* Bad ioctl. Setting ioc_error causes the ioctl

* call to return that particular errno. By default,

* ioctl will return EINVAL on failure.

*/

mp->b_datap->db_type = M_IOCNAK;

iocp->ioc_error = error;/* set returned errno */

qreply(q, mp);

}

break;

}

case M_FLUSH:

#ifdef MT

mask_lock(loop_lock);

#endif

if (*mp->b_rptr & FLUSHW) {

flushq(q, FLUSHALL); /* write */

if (loop->oqptr != NULL)

flushq(loop->oqptr, FLUSHALL);

/* read on other side equals write on this side */

}

if (*mp->b_rptr & FLUSHR) {

flushq(RD(q), FLUSHALL);

if (loop->oqptr != NULL)

flushq(WR(loop->oqptr), FLUSHALL);

}

#ifdef MT

mask_lock(loop_lock);

#endif

switch(*mp->b_rptr) {

case FLUSHW:

*mp->b_rptr = FLUSHR;

break;

case FLUSHR:

*mp->b_rptr = FLUSHW;

break;

}

#ifdef MT

mask_lock(loop_lock);

#endif

if (loop->oqptr != NULL) {

#ifdef MT

mask_lock(loop_lock);

/*

* loop->oqptr can only be cleared in loopclose, which

* can not be called while the put procedure is executing

*/

putnext(loop->oqptr, mp);

}

else

mask_unlock(loop_lock);

break;

#else

putnext(loop->oqptr, mp);

}

break;

#endif

default: /* If this Stream isn't connected, send M_ERROR upstream */

#ifdef MT

mask_lock(loop_lock);

if (loop->oqptr == NULL) {

mask_unlock(loop_lock);

freemsg(mp);

putnextctl1(RD(q), M_ERROR, ENXIO);

break;

}

mask_unlock(loop_lock);

#else

if (loop->oqptr == NULL) {

freemsg(mp);

putctl1(RD(q)->q_next, M_ERROR, ENXIO);

break;

}

#endif

putq(q, mp);

}

}

Figure 8-10 Driver Sanity Checks

Certain message types can be sent upstream by drivers and modules to the Stream head where they are translated into actions detectable by user process(es). The messages may also change the state of the Stream head.
M_ERROR Causes the Stream head to lock up. Message transmission between Stream and user processes is terminated. All subsequent system calls except close(2) and poll(2) will fail. Also causes an M_FLUSH clearing all message queues to be sent downstream by the Stream head.
M_HANGUP  Terminates input from a user process to the Stream. All subsequent system calls that would send messages downstream will fail. Once the Stream head read message queue is empty, EOF is returned on reads. Can also result in the SIGHUP signal being sent to the process group.
M_SIG/M_PCSIG  Causes a specified signal to be sent to a process. 

Home

 

and are utilities that allocate a nondata (that is, not M_DATA, M_DELAY, M_PROTO, or M_PCPROTO) type message, place one byte in the message (for ), and call the put procedure of the queue next to the specified queue.

service procedures are required in Figure 8-11 on both the write-side and read-side for flow control.

static int loopwsrv(queue_t *q)

{

mblk_t *mp;

register struct loop *loop;

#ifdef MT

int pl;

#endif

loop = (struct loop *) q->q_ptr;

while ((mp = getq(q)) != NULL) {

/*

* Check if we can put the message up the other Stream read

* queue.

*/

#ifdef MT

mask_lock(loop_lock);

if (pcmsg(mp->b_datap->db_type) && !canputnext(loop->oqptr)) {

mask_unlock(loop_lock);

#else

if (mp->b_datap->db_type <= QPCTL && !canput(loop->oqptr->q_next)) {

#endif

putbq(q, mp); /* read-side is blocked */

break;

}

/* send message */

/*

* loopwput verified that loop->oqptr was set and it can only

* be cleared in the close routine, which can not be called

* while this queue was enabled.

*/

putnext(loop->oqptr, mp); /* To queue following other

Stream read queue */

}

}

/*

* read service routine

* Enter only when "back enabled" by flow control

*/

static int looprsrv(queue_t *q)

{

struct loop *loop;

#ifdef MT

int pl;

#endif

loop = (struct loop *) q->q_ptr;

#ifdef MT

mask_lock(loop_lock);

#endif

if (loop->oqptr != NULL)

#ifdef ST

return;

#endif

/* manually enable write service procedure */

#ifdef MT

{

mask_unlock(loop_lock);

qenable(WR(loop->oqptr));

} else

mask_unlock(loop_lock);

#else

qenable(WR(loop->oqptr));

#endif

}


Figure 8-11 Write- and Read-Side Flow Control

Home

 

The write service procedure, loopwsrv, takes on the canonical form. The queue being written to is not downstream, but upstream (found by using oqptr) on the other Stream.

In Figure 8-12, 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 becomes unstuck from flow control blockage. The purpose of the procedure is to reenable 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 ever gets queued to the read service procedure. Messages are kept on the write-side so that flow control can propagate up to the Stream head. The qenable routine schedules the write-side service procedure of the other Stream.

loopclose breaks the connection between the Streams.

int loopclose(queue_t *q, int flag, void *credp)

{

register struct loop *loop;

#ifdef MT

int pl;

/* disable put and srv routines for queue pair. */

qprocsoff(q);

#endif

mask_lock(loop_lock);

loop = (struct loop *) q->q_ptr;

loop->qptr = NULL;

/*

* If we are connected to another stream, break the linkage, and send

* a hangup message. The hangup message causes the stream head to fail

* writes, allow the queued data to be read completely, and then

* return EOF on subsequent reads.

*/

if (loop->oqptr) {

((struct loop *)loop->oqptr->q_ptr)->oqptr = NULL;

#ifdef MT

mask_unlock(loop_lock);

putnextctl(loop->oqptr, M_HANGUP);

mask_unlock(loop_lock);

#else

putctl(loop->oqptr->q_next, M_HANGUP);

#endif

loop->oqptr = NULL;

}

#ifdef MT

mask_unlock(loop_lock);

#endif

}

Figure 8-12 Reenabling the Writer

loopclose sends an M_HANGUP message up the connected Stream to the Stream head.

NOTE

A loop-around driver must never directly link the q_next pointers of the queue pairs of the two Streams.

Home

 

8.4 DESIGN GUIDELINES

Driver developers should follow these guidelines:
Home

Contents Previous Chapter Next Chapter Index Glossary