Page: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19 20 21 22 23 24 25 26 27

Chapter 6


Overview of STREAMS Modules and Drivers

6.1 INTRODUCTION

Modules and drivers are processing elements in STREAMS. A Stream device driver is similar to a conventional UNIX system driver. It is opened like a conventional driver and is responsible for the system interface to the device.

STREAMS modules and drivers are structurally similar. The call interfaces to driver routines are identical to interfaces used for modules. Drivers and modules must declare streamtab, qinit, and module_info structures. Within the STREAMS mechanism, drivers are required elements, but modules are optional.

One consequence of the flexibility and modularity of STREAMS is the tendency to split up the processing formerly done by drivers and distribute it among a number of STREAMS modules and a driver. For example, where a TTY driver might directly call a line discipline routine, a STREAMS configuration would isolate the line discipline processing in a module. While STREAMS drivers may be cleaner and less complicated to write, the driver writer may have the additional responsibility of writing modules as well.

Furthermore, the user-level program establishing access to a STREAMS device has the option of building a stream with whatever modules are available. This flexibility implies that a module's functionality must be well documented by the developer so that an applications programmer can be confident of correctly including it in a stream.

6.1.1 Differences Between Modules and Drivers

The following list summarizes the major differences between STREAMS modules and drivers:
Home

 

6.1.2 Similarities Between Modules and Drivers

While the differences are significant, the similarities between modules and drivers are also important. User context is not generally available to STREAMS module procedures and drivers. The exception is during execution of the open and close routines. Driver and module open and close routines have user context and may access the u_area structure, although this is discouraged.

open and close routines are allowed to sleep, but they must always return to the caller. If they sleep, it must be at priority numerically <= PZERO, or with PCATCH set in the sleep priority. Priorities are higher as they decrease in numerical value.

The process never returns from the sleep call and the system call is aborted if one of the following occurs:

NOTES

STREAMS driver and module put procedures and service procedures have no user context. They cannot access the u_area structure of a process and must not sleep. 

The module and driver open/close interface has been modified for UNIX System V Release 4. Examples and descriptions in this chapter reflect the Release 4 interface. 

This release of the system does not support code that does not conform to the DDI/DKI standard.

Home

 

6.1.3 Module and Driver Declarations

A module and driver contains, at a minimum, declarations of the form as shown in Figure 6-1

     #include <sys/types.h>     /* required in all modules and drivers */

#include <sys/stream.h> /* required in all modules and drivers */

#include <sys/param.h>

#ifdef MT

#include <sys/mplock.h>

#endif

static struct module_info rminfo = { 0x08, "mod", 0, INFPSZ, 0, 0 };

static struct module_info wminfo = { 0x08, "mod", 0, INFPSZ, 0, 0 };

static int modopen(), modput(), modclose();

static struct qinit rinit = {

modput, NULL, modopen, modclose, NULL, &rminfo, NULL };

static struct qinit winit = {

modput, NULL, NULL, NULL, NULL, &wminfo, NULL };

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

#ifdef MT

int moddevflag = D_MP;

#else

int moddevflag = 0;

#endif

Figure 6-1 Module and Driver Declarations

The contents of these declarations are constructed for the null module example in this section. This module does no processing. Its only purpose is to show linkage of a module into the system. The descriptions in this section are general to all STREAMS modules and drivers unless they specifically reference the example.

The declarations shown are the header set; the read and write queue (rminfo and wminfo) module_info structures; the module open, read/write-put, and close procedures; the read and write (rinit and winit) qinit structures; and the streamtab structure.

The header files types.h and stream.h are always required for modules and drivers. The header file param.h contains definitions for NULL and other values for STREAMS modules and drivers.

NOTE

When configuring a STREAMS module or driver, the streamtab structure must be externally accessible. The streamtab structure name must be the prefix appended with info. Also, the driver flag must be externally accessible. The flag name must be the prefix appended with devflag.
Home

 

The streamtab contains qinit values for the read and write queues. The qinit structures in turn point to a module_info and an optional module_stat structure. The two required structures are shown in Figure 6-2.

struct qinit {

int (*qi_putp)(); /* put procedure */

int (*qi_srvp)(); /* service procedure */

int (*qi_qopen)(); /* called on each open or a push */

int (*qi_qclose)(); /* called on last close or a pop */

int (*qi_qadmin)(); /* reserved for future use */

struct module_info *qi_minfo; /* information structure */

struct module_stat *qi_mstat; /* statistics structure - optional */

};

struct module_info {

ushort_t mi_idnum; /* module ID number */

char *mi_idname; /* module name */

long mi_minpsz; /* min packet size, for developer use */

long mi_maxpsz; /* max packet size, for developer use */

ulong_t mi_hiwat; /* hi-water mark */

ulong_t mi_lowat; /* lo-water mark */

};

Figure 6-2 Required Structures

The qinit contains the queue procedures: put, service, open, and close. All modules and drivers with the same streamtab (that is, the same fmodsw or cdevsw entry) point to the same upstream and downstream qinit structure(s). The structure is meant to be software read-only, as any changes to it affect all instantiations of that module in all Streams. Pointers to the open and close procedures must be contained in the read qinit structure. These fields are ignored on the write-side. Our example has no service procedure on the read-side or write-side.

The module_info contains identification and limit values. All queues associated with a certain driver/module share the same module_info structures. The module_info structures define the characteristics of that driver/module's queues. As with the qinit, this structure is intended to be software read-only. However, the four limit values (q_minpsz, q_maxpsz, q_hiwat, q_lowat) are copied to a queue structure where they are modifiable. In the example, the flow control high- and low-water marks are zero since there is no service procedure and messages are not queued in the module.

Two names are associated with a module:

Each module ID number and module name should be unique in the system. The module ID number is currently used only in logging and tracing. It is 0x08 in the example.

Minimum and maximum packet sizes are intended to limit the total number of characters contained in M_DATA messages passed to this queue. These limits are advisory except for the Stream head. For certain system calls that write to a Stream, the Stream head observes the packet sizes set in the write queue of the module immediately below it. Otherwise, the use of packet size is developer-dependent. In the example, INFPSZ indicates unlimited size on the read-side.

The module_stat is optional. Currently, there is no STREAMS support for statistical information gathering.

Home

 
6.1.3.1 NULL MODULE EXAMPLE
The null module procedures are shown in Figure 6-3.

static int modopen(queue_t *q, dev_t *devp, int flag,

int sflag, void *credp)

{

#ifdef MT

qprocson(q); /* enables put and srv routines */

#endif

/* return success */

return 0;

}

static int modput(queue_t *q, mblk_t *mp)

{

putnext(q, mp); /* pass message through */

}

/ * Note: we only need one put procedure that can be used for both

* read-side and write-side.

*/

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

{

#ifdef MT

qprocsoff(q); /* disables put and srv routines */

#endif

return 0;

}

Figure 6-3 Null Module Procedure

The form and arguments of these procedures are the same in all modules and all drivers. Modules and drivers can be used in multiple Streams and their procedures must be reentrant.

modopen illustrates the open call arguments and return value. The arguments are the read queue pointer (q), the pointer (devp) to the major/minor device number, the file flags (flag, defined in sys/file.h), the Stream open flag (sflag). The Stream open flag can take on the following values:
MODOPEN Normal module open
0 Normal driver open
CLONEOPEN Clone driver open

Home

 

The return value from open is 0 for success and an error number for failure. If a driver is called with the CLONEOPEN flag, the device number pointed to by the devp should be set by the driver to an unused device number accessible to that driver. This should be an entire device number (major and minor device number). The open procedure for a module is called on the first I_PUSH and on all later open calls to the same Stream. During a push, a nonzero return value causes the I_PUSH to fail and the module to be removed from the Stream. If an error is returned by a module during an open call, the open fails, but the Stream remains intact. In the null module example, modopen simply

modput illustrates the common interface to put procedures. The arguments are the read or write queue pointer, as appropriate, and the message pointer. The put procedure in the appropriate side of the queue is called when a message is passed from upstream or downstream. The put procedure has no return value, but it is defined as int(). In the example, no message processing is done. All messages are forwarded using putnext. putnext calls the put procedure of the next queue in the proper direction.

The close routine is only called on an I_POP ioctl for modules, or on the last close call of the Stream for drivers. The arguments are the read queue pointer, the file flags as in modopen, and a pointer to a credentials structure.

The return value is 0 on success. A failure of close is ignored by the system.

6.2 MODULE AND DRIVER IOCTLS

STREAMS is an addition to the UNIX system traditional character input/output (I/O) mechanism. In this section, the phrases "character I/O mechanism'' and "I/O mechanism'' refer only to that part of the mechanism that preexisted STREAMS.

The character I/O mechanism handles all ioctl(2) system calls in a transparent manner. The kernel expects all ioctls to be handled by the device driver associated with the character special file on which the call is sent. All ioctl calls are sent to the driver, which is expected to do all validation and processing other than file descriptor validity checking. The operation of any specific ioctl is dependent on the device driver. If the driver requires data to be transferred in from user space, it uses the kernel copyin function. It may also use copyout to transfer out any data results back to user space.

With STREAMS, there are a number of differences from the character I/O mechanism that affect ioctl processing.

First, there are a set of generic STREAMS ioctl command values (see ioctl(2)) recognized and processed by the Stream head. These are described in streamio(7). The operation of the generic STREAMS ioctls are generally independent of the presence of any specific module or driver on the Stream.

Home

 

The second difference is the absence of user context in a module and driver when the information associated with the ioctl is received. This prevents use of copyin or copyout by the module. This also prevents the module and driver from associating any kernel data with the currently running process. (It is likely that by the time the module or driver receives the ioctl, the process generating it may no longer be running.)

A third difference is that for the character I/O mechanism, all ioctls are handled by the single driver associated with the file. In STREAMS, there can be multiple modules on a Stream and each one can have its own set of ioctls. The ioctls that can be used on a Stream can change as modules are pushed and popped.

STREAMS provides the capability for user processes to perform control functions on specific modules and drivers in a Stream with ioctl calls. Most streamio(7) ioctl commands go no further than the Stream head. They are fully processed there and no related messages are sent downstream. However, certain commands and all unrecognized commands cause the Stream head to create an M_IOCTL message that includes the ioctl arguments, and send the message downstream to be received and processed by a specific module or driver. The M_IOCTL message is the initial message type that carries ioctl information to modules. Other message types are used to complete the ioctl processing in the Stream. In general, each module must uniquely recognize and take action on specific M_IOCTL messages.

STREAMS ioctl handling is equivalent to the transparent processing of the character I/O mechanism. STREAMS modules and drivers can process ioctls generated by applications implemented for a non- STREAMS environment.

6.2.1 General ioctl Processing

STREAMS blocks a user process that issues an ioctl and causes the Stream head to generate an M_IOCTL message. The process remains blocked until: For the ioctl I_STR, the timeout period can be a user-specified interval or a default. For the other M_IOCTL ioctls, the default value (infinite) is used.

For an I_STR, the STREAMS module or driver that generates a positive acknowledgment message can also return data to the process in that message. An alternate means to return data is provided with transparent ioctls. If the Stream head does not receive a positive or negative acknowledgment message in the specified time, the ioctl call fails.

A module that receives an unrecognized M_IOCTL message should pass it on unchanged. A driver that receives an unrecognized M_IOCTL should produce a negative acknowledgment.

The form of an M_IOCTL message is a single M_IOCTL message block followed by zero or more M_DATA blocks. The M_IOCTL message block contains an iocblk structure, defined in sys/stream.h. For details, see iocblk(D4).

For an I_STR ioctl, ioc_cmd (in the iocblk structure) contains the command supplied by the user in the strioctl structure defined in streamio(7).

Home

 

If a module or driver determines an M_IOCTL message is in error for any reason, it must produce the negative acknowledgment message by setting the message type to M_IOCNAK and sending the message upstream. No data or a return value can be sent to a user in this case. If ioc_error (in iocblk) is set to 0, the Stream head causes the ioctl call to fail with EINVAL. The driver has the option of setting ioc_error to an alternate error number if desired.

NOTE

ioc_error can be set to a nonzero value in both M_IOCACK and M_IOCNAK. This causes that value to be returned as an error number to the process that sent the ioctl.
If a module wants to look at what ioctls of other modules are doing, the module should not look for a specific M_IOCTL on the write-side but look for M_IOCACK or M_IOCNAK on the read-side. For example, the module sees TCSETS (see termios(7)) going down and wants to know what is being set. The module should look at it and save the data, but not use it. The read-side processing knows that the module is waiting for an answer for the ioctl. When the read-side processing sees an ack or nak next time, it checks if it is the same ioctl (here TCSETS) and if it is, the module may use the data previously saved.

The two STREAMS ioctl mechanisms, I_STR and transparent, are described next. [Here, I_STR means the streamio(7) I_STR command and implies the related STREAMS processing unless noted otherwise.] I_STR has a restricted format and restricted addressing for transferring ioctl-related data between user and kernel space. It requires only a single pair of messages to complete ioctl processing. The transparent mechanism is more general and has almost no restrictions on ioctl data format and addressing. The transparent mechanism generally requires that multiple pairs of messages be exchanged between the Stream head and module to complete the processing.

6.2.2 I_STR ioctl Processing

The I_STR ioctl provides a capability for user applications to do module and driver control functions on STREAMS files. I_STR allows an application to specify the ioctl timeout. It requires that all user ioctl data (to be received by the destination module) be placed in a single block that is pointed to from the user strioctl structure. The module can also return data to this block.

If the module is looking at, for example, the TCSETS/TCGETS group of ioctl calls as they pass up or down a Stream, it must never assume that because TCSETS comes down that it actually has a data buffer attached to it. The user may have formed TCSETS as an I_STR call and accidentally given a null data buffer pointer. You should always check b_cont to see if it is NULL before using it as an index to the data block that goes with M_IOCTL messages.

The TCGETA call, if formed as an I_STR call with a data buffer pointer set to a value by the user, always has a data buffer attached to b_cont from the main message block. Check to see that the ioctl message does not have a buffer attached before allocating a new buffer and assigning b_cont to point at it. If you do not, the original buffer will be lost.

Home

 

Figure 6-4 illustrates processing associated with an I_STR ioctl. lpdoioctl is called to process trapped M_IOCTL messages.

TYPE

lpdoioctl(struct lp *lp, mblk_t *mp)

{

struct iocblk *iocp;

queue_t *q;

q = lp->qptr; /*its own write queue*/

/* 1st block contains iocblk structure */

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

switch (iocp->ioc_cmd) {

case SET_OPTIONS:

/* Count should be exactly one short's worth

(for this example) */

if (iocp->ioc_count != sizeof(short))

goto iocnak;

if (mp->b_cont == NULL)

goto lognak; /* not shown in this example */

/* Actual data is in 2nd message block */

lpsetopt(lp, *(short *)mp->b_cont->b_rptr);

/*hypothetical routine*/

/* ACK the ioctl */

mp->b_datap->db_type = M_IOCACK;

iocp->ioc_count = 0;

qreply(q, mp);

break;

default:

iocnak:

/* NAK the ioctl */

mp->b_datap->db_type = M_IOCNAK;

qreply(q, mp);

}

}

Figure 6-4 I_STR ioctl Processing

lpdoioctl illustrates driver M_IOCTL processing, which also applies to modules. However, at case default, a module would not nak an unrecognized command, but would pass the message on. In this example, SET_OPTIONS is the only command recognized. ioc_count contains the number of user-supplied data bytes. For this example, it must equal the size of a short. The user data is sent directly to the printer interface using lpsetopt. Next, the M_IOCTL message is changed to type M_IOCACK and the ioc_count field is set to zero to show that no data is to be returned to the user. Finally, the message is sent upstream using qreply. If ioc_count was left nonzero, the Stream head copies that many bytes from the second through Nth message blocks into the user buffer.

Home

 

6.2.3 Transparent ioctl Processing

The transparent STREAMS ioctl mechanism allows application programs to perform module and driver control functions with ioctls other than I_STR. It is intended to transparently support applications developed before the introduction of STREAMS, and alleviates the need to recode and recompile the user level software to run over STREAMS files.

The mechanism extends the data transfer capability for STREAMS ioctl calls beyond that provided in the I_STR form. Modules and drivers can transfer data between their kernel space and user space in any ioctl that has a value of the command argument not defined in streamio(7). These ioctls are known as transparent ioctls to differentiate them from the I_STR form. Transparent processing support is necessary when existing user level applications perform ioctls on a non-STREAMS character device and the device driver is converted to STREAMS. The ioctl data can be in any format mutually understood by the user application and module.

The transparent mechanism also supports STREAMS applications that want to send ioctl data to a driver or module in a single call, where the data may not be in a form readily embedded in a single user block. For example, the data may be contained in nested structures, different user space buffers, and so forth. This mechanism is needed because user context does not exist in modules and drivers when ioctl processing occurs. This prevents them from using the kernel copyin and copyout functions. For example, consider the following ioctl call:

where ioctl_struct is a structure containing the members: To read (or write) the elements of ioctl_struct, a module would have to do a series of copyin/copyout calls using pointer information from a prior copyin to transfer additional data. A non-STREAMS character driver could directly execute these copy functions because user context exists during all UNIX system calls to the driver. However, in STREAMS, user context is only available to modules and drivers in their open and close routines.

The transparent mechanism enables modules and drivers to request that the Stream head do a copyin or copyout on their behalf to transfer ioctl data between their kernel space and various user space locations. The related data is sent in message pairs exchanged between the Stream head and the module. A pair of messages is required so that each transfer can be acknowledged. In addition to M_IOCTL, M_IOCACK and M_IOCNAK messages, the transparent mechanism also uses M_COPYIN, M_COPYOUT, and M_IOCDATA messages.

The general processing by which a module or a driver reads data from user space for the transparent case involves pairs of request/response messages, as follows:

  1. The Stream head does not recognize the command argument of an ioctl call and creates a transparent M_IOCTL message. The iocblk structure has a TRANSPARENT indicator containing the value of the arg argument in the call. It sends the M_IOCTL message downstream. See Section 6.2.4 for more details.
  2. A module receives the M_IOCTL message, recognizes the ioc_cmd, and determines that it is TRANSPARENT.
  3. If the module requires user data, it creates an M_COPYIN message to request a copyin of user data. The message contains the address of user da1ta to copyin and how much data to transfer. It sends the message upstream.
  4. The Stream head receives the M_COPYIN message and uses the contents to copyin the data from user space into an M_IOCDATA response message that it sends downstream. The message also contains an indicator of whether the data transfer succeeded. The copyin might fail, for instance, because of an EFAULT condition. See intro(2).
  5. The module receives the M_IOCDATA message and processes its contents.
  6. The module may use the message contents to generate another M_COPYIN. Steps 3 through 5 may be repeated until the module has requested and received all the user data to be transferred.

  7. When the module completes its data transfer, it does the ioctl processing and sends an M_IOCACK message upstream to notify the Stream head that ioctl processing has successfully completed.
Writing data from a module to user space is similar except that the module uses an M_COPYOUT message to request the Stream head to write data into user space. In addition to length and user address, the message includes the data to be copied out. In this case, the M_IOCDATA response will not contain user data, only show success or failure.

The module may intermix M_COPYIN and M_COPYOUT messages in any order. However, each message must be sent one at a time; the module must receive the associated M_IOCDATA response before any subsequent M_COPYIN/M_COPYOUT request or ack/nak message is sent upstream. After the last M_COPYIN/M_COPYOUT message, the module must send an M_IOCACK message (or M_IOCNAK for a detected error condition).

NOTE

For a transparent M_IOCTL, user data cannot be returned with an M_IOCACK message. The data must have been sent with a preceding M_COPYOUT message.

6.2.4 Transparent ioctl Messages

The form of the M_IOCTL message generated by the Stream head for a transparent ioctl is a single M_IOCTL message block followed by one M_DATA block. The form of the iocblk structure in the M_IOCTL block is the same as described in Section 6.2.1. However, ioc_cmd is set to the value of the command argument in the ioctl system call and ioc_count is set to TRANSPARENT, defined in sys/stream.h. TRANSPARENT distinguishes the case where an I_STR ioctl may specify a value of ioc_cmd equivalent to the command argument of a transparent ioctl. The M_DATA block of the message contains the value of the arg parameter in the call.

NOTE

Modules that process a specific ioc_cmd that did not validate the ioc_count field of the M_IOCTL message will break if transparent ioctls with the same command are done from user space.
 
Home

 
 

6.2.5 Transparent ioctl Examples

The following are three examples of transparent ioctl processing. Figure 6-5 and Figure 6-6 illustrate M_COPYIN. Figure 6-7 illustrates M_COPYOUT. Figure 6-8 and Figure 6-9 show a more complex example with state transitions combining both M_COPYIN and M_COPYOUT.
6.2.5.1 M_COPYIN EXAMPLE
In this example, the contents of a user buffer are transferred into the kernel as part of an ioctl call of the form where bufadd is a structure declared as

struct address {

int ad_len; /* buffer length in bytes */

caddr_t ad_addr; /* buffer address */

};

This requires two pairs of messages (request/response) following receipt of the M_IOCTL message. The first will copyin the structure and the second will copyin the buffer. Figure 6-5 illustrates processing that supports only the transparent form of ioctl. xxxwput is the write-side put procedure for the module or driver xxx:

struct address { /* same members as in user space */

int ad_len; /* length in bytes */

caddr_t ad_addr; /* buffer address */

};

/* state values (overloaded in private field) */

#define GETSTRUCT 0 /* address structure */

#define GETADDR 1 /* byte string from ad_addr */

xxxwput(queue_t *q, mblk_t *mp)

{

struct iocblk *iocbp;

struct copyreq *cqp;

switch (mp->b_datap->db_type) {

.

.

.

case M_IOCTL:

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

switch (iocbp->ioc_cmd) {

case SET_ADDR:

if (iocbp->ioc_count != TRANSPARENT) { /* fail if I_STR */

if (mp->b_cont) { /* return buffer to pool ASAP */

freemsg(mp->b_cont);

mp->b_cont = NULL;

}

mp->b_datap->db_type = M_IOCNAK; /* EINVAL */

qreply(q, mp);

break;

}

/* Reuse M_IOCTL block for M_COPYIN request */

cqp = (struct copyreq *)mp->b_rptr;

/* Get user space structure address from linked M_DATA block */

cqp->cq_addr = (caddr_t) *(long long *)mp->b_cont->b_rptr;

freemsg(mp->b_cont); /* MUST free linked blocks */

mp->b_cont = NULL;

cqp->cq_private = (mblk_t *)GETSTRUCT; /* to identify response */

/* Finish describing M_COPYIN message */

cqp->cq_size = sizeof(struct address);

cqp->cq_flag = 0;

mp->b_datap->db_type = M_COPYIN;

mp->b_wptr = mp->b_rptr + sizeof(struct copyreq);

qreply(q, mp);

break;

default: /* M_IOCTL not for us */

/* if module, pass on */

/* if driver, nak ioctl */

break;

} /* switch (iocbp->ioc_cmd) */

break;

case M_IOCDATA:

xxxioc(q, mp); /* all M_IOCDATA processing done here */

break;

.

.

.

} /* switch (mp->b_datap->db_type) */

}

Figure 6-5 Request/Response Messages

Home

 

xxxwput verifies that the SET_ADDR is TRANSPARENT to avoid confusion with an I_STR ioctl, which uses a value of ioc_cmd equivalent to the command argument of a transparent ioctl. When sending an M_IOCNAK, freeing the linked M_DATA block is not mandatory as the Stream head frees it. However, this returns the block to the buffer pool more quickly.

In this and all the following examples in this section, the message blocks are reused to avoid the overhead of deallocating and allocating.

NOTE

The Stream head guarantees that the size of the message block containing an iocblk structure is large enough also to hold the copyreq and copyresp structures.
cq_private is set to contain state information for ioctl processing (tells us what the subsequent M_IOCDATA response message contains). Keeping the state in the message makes the message self-describing and simplifies the ioctl processing. M_IOCDATA processing is done in xxxioc. Two M_IOCDATA types are processed, GETSTRUCT and GETADDR. See Figure 6-6.

xxxioc(queue_t *q, mblk_t *mp) /* M_IOCDATA processing */

{

struct iocblk *iocbp;

struct copyreq *cqp;

struct copyresp *csp;

struct address *ap;

csp = (struct copyresp *)mp->b_rptr;

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

switch (csp->cp_cmd) { /* validate M_IOCDATA for this module */

case SET_ADDR:

if (csp->cp_rval) { /* GETSTRUCT or GETADDR failed */

freemsg(mp);

return;

}

switch ((int)csp->cp_private) { /* determine state */

case GETSTRUCT: /* user structure has arrived */

mp->b_datap->db_type = M_COPYIN; /* reuse M_IOCDATA block */

cqp = (struct copyreq *)mp->b_rptr;

ap = (struct address *)mp->b_cont->b_rptr; /* user structure */

cqp->cq_size = ap->ad_len; /* buffer length */

cqp->cq_addr = ap->ad_addr; /* user space buffer address */

freemsg(mp->b_cont);

mp->b_cont = NULL;

cqp->cq_flag = 0;

csp->cp_private = (mblk_t *)GETADDR; /* next state */

qreply(q, mp);

break;

case GETADDR: /* user address is here */

if (xxx_set_addr(mp->b_cont) == FAILURE){/*hypothetical routine*/

mp->b_datap->db_type = M_IOCNAK;

iocbp->ioc_error = EIO;

} else {

mp->b_datap->db_type = M_IOCACK; /* success */

iocbp->ioc_error = 0; /* may have been overwritten */

iocbp->ioc_count = 0; /* may have been overwritten */

iocbp->ioc_rval = 0; /* may have been overwritten */

}

mp->b_wptr = mp->b_rptr + sizeof(struct iocblk);

freemsg(mp->b_cont);

mp->b_cont = NULL;

qreply(q, mp);

break;

default: /* invalid state: can't happen */

freemsg(mp->b_cont);

mp->b_cont = NULL;

mp->b_datap->db_type = M_IOCNAK;

mp->b_wptr = mp->rptr + sizeof(struct iocblk);

iocbp->ioc_error = EINVAL; /* may have been overwritten */

qreply(q, mp);

ASSERT (0); /* panic if debugging mode */

break;

}

break; /* switch (cp_private) */

default: /* M_IOCDATA not for us */

/* if module, pass message on */

/* if driver, free message */

break;

} /* switch (cp_cmd) */

}

Figure 6-6 GETSTRUCT and GETADDR

xxx_set_addr is a routine (not shown in the example) that processes the user address from the ioctl. Because the message block has been reused, the fields that the Stream head examines (denoted by may have been overwritten) must be cleared before sending an M_IOCNAK.

6.2.5.2 M_COPYOUT EXAMPLE
In this example, the user wants option values for this Stream device to be placed into the user's options structure (see beginning of example code). This can be done by use of a transparent ioctl call of the form or, alternately, by use of a streamio call
Home

 

In the first case, optadd is declared struct options. In the I_STR case, opts_strioctl is declared struct strioctl, where opts_strioctl.ic_dp points to the user options structure.

Figure 6-7 illustrates support of both the I_STR and transparent forms of an ioctl. The transparent form requires a single M_COPYOUT message following receipt of the M_IOCTL to copyout the contents of the structure. xxxwput is the write-side put procedure for module or driver xxx. See Figure 6-7.

struct options { /* same members as in user space */

int op_one;

int op_two;

short op_three;

long op_four;

};

xxxwput(queue_t *q, mblk_t *mp)

{

struct iocblk *iocbp;

struct copyreq *cqp;

struct copyresp *csp;

int transparent = 0;

switch (mp->b_datap->db_type) {

.

.

.

case M_IOCTL:

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

switch (iocbp->ioc_cmd) {

case GET_OPTIONS:

if (iocbp->ioc_count == TRANSPARENT) {

transparent = 1;

cqp = (struct copyreq *)mp->b_rptr;

cqp->cq_size = sizeof(struct options);

/* Get structure address from linked M_DATA block */

cqp->cq_addr = (caddr_t) *(long long *)mp->b_cont->b_rptr;

cqp->cq_flag = 0;

/* No state necessary - we will only ever get one

* M_IOCDATA from the Stream head indicating success

* or failure for the copyout */

}

if (mp->b_cont)

freemsg(mp->b_cont); /* overwritten */

if ((mp->b_cont = allocb(sizeof(struct options),

BPRI_MED)) == NULL) {

mp->b_datap->db_type = M_IOCNAK;

iocbp->ioc_error = EAGAIN;

qreply(q, mp);

break;

}

xxx_get_options(mp->b_cont); /* hypothetical routine */

if (transparent) {

mp->b_datap->db_type = M_COPYOUT;

mp->b_wptr = mp->b_rptr + sizeof(struct copyreq);

} else {

mp->b_datap->db_type = M_IOCACK;

iocbp->ioc_count = sizeof(struct options);

}

qreply(q, mp);

break;

default: /* M_IOCTL not for us */

/* if module, pass on; if driver, nak ioctl */

break;

} /* switch (iocbp->ioc_cmd) */

break;

case M_IOCDATA:

csp = (struct copyresp *)mp->b_rptr;

if (csp->cmd != GET_OPTIONS) { /* M_IOCDATA not for us */

/* if module, pass on; if driver, free message */

break;

}

if (csp->cp_rval) {

freemsg(mp); /* failure */

return;

}

/* Data successfully copied out, ack */

mp->b_datap->db_type = M_IOCACK; /* reuse M_IOCDATA for ack */

mp->b_wptr = mp->b_rptr + sizeof(struct iocblk);

iocbp->ioc_error = 0; /* may have been overwritten */

iocbp->ioc_count = 0; /* may have been overwritten */

iocbp->ioc_rval = 0; /* may have been overwritten */

qreply(q, mp);

break;

.

.

.

} /* switch (mp->b_datap->db_type) */

}

Figure 6-7 I_STR and Transparent ioctl

Home

 

6.2.5.3 BIDIRECTIONAL TRANSFER EXAMPLE

Figure 6-8 and Figure 6-9 illustrate bidirectional data transfer between the kernel and user space during transparent ioctl processing. It also shows how more complex state information can be used.

The user wants to send and receive data from user buffers as part of a transparent ioctl call of the form

ioctl(fd, XXX_IOCTL, &addr_xxxdata)

The user addr_xxxdata structure defining the buffers is declared as struct xxxdata, as shown. This requires three pairs of messages following receipt of the M_IOCTL message:

  1.    to copyin the structure
  2.    to copyin one user buffer
  3.    to copyout the second user buffer.
xxxwput is the write-side put procedure for the module or driver xxx. See Figure 6-8.

struct xxxdata { /* same members in user space */

int x_inlen; /* number of bytes copied in */

caddr_t x_inaddr; /* buffer address of data copied in */

int x_outlen; /* number of bytes copied out */

caddr_t x_outaddr; /* buffer address of data copied out */

};

/* State information for ioctl processing */

struct state {

int st_state; /* see below */

struct xxxdata st_data; /* see above */

};

/* state values */

#define GETSTRUCT 0 /* get xxxdata structure */

#define GETINDATA 1 /* get data from x_inaddr */

#define PUTOUTDATA 2 /* get response from M_COPYOUT */

static int

xxxwput(queue_t *q, mblk_t *mp)

{

struct iocblk *iocbp;

struct copyreq *cqp;

struct state *stp;

mblk_t *tmp;

switch (mp->b_datap->db_type) {

.

.

.

case M_IOCTL:

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

switch (iocbp->ioc_cmd) {

case XXX_IOCTL:

if (iocbp->ioc_count != TRANSPARENT) { /* fail if I_STR */

if (mp->b_cont) { /* return buffer to pool ASAP */

freemsg(mp->b_cont);

mp->b_cont = NULL;

}

mp->b_datap->db_type = M_IOCNAK; /* EINVAL */

qreply(q, mp);

break;

}

/* Reuse M_IOCTL block for M_COPYIN request */

cqp = (struct copyreq *)mp->b_rptr;

/* Get structure's user address from linked M_DATA block */

cqp->cq_addr = (caddr_t) *(long long *)mp->b_cont->b_rptr;

freemsg(mp->b_cont);

mp->b_cont = NULL;

/* Allocate state buffer */

if ((tmp = allocb(sizeof(struct state), BPRI_MED)) == NULL) {

mp->b_datap->db_type = M_IOCNAK;

iocbp->ioc_error = EAGAIN;

qreply(q, mp);

break;

}

tmp->b_wptr += sizeof(struct state);

stp = (struct state *)tmp->b_rptr;

stp->st_state = GETSTRUCT;

cqp->cq_private = tmp;

/* Finish describing M_COPYIN message */

cqp->cq_size = sizeof(struct xxxdata);

cqp->cq_flag = 0;

mp->b_datap->db_type = M_COPYIN;

mp->b_wptr = mp->b_rptr + sizeof(struct copyreq);

qreply(q, mp);

break;

default: /* M_IOCTL not for us */

/* if module, pass on */

/* if driver, nak ioctl */

break;

} /* switch (iocbp->ioc_cmd) */

break;

case M_IOCDATA:

xxxioc(q, mp); /* all M_IOCDATA processing done here */

break;

.

.

.

} /* switch (mp->b_datap->db_type) */

}

Figure 6-8 Write-Side put Procedure

Home

 

xxxwput allocates a message block to contain the state structure and reuses the M_IOCTL to create an M_COPYIN message to read in the xxxdata structure. M_IOCDATA processing is done in xxxioc. See Figure 6-9.

xxxioc(queue_t *q, mblk_t *mp) /* M_IOCDATA processing */

{

struct iocblk *iocbp;

struct copyreq *cqp;

struct copyresp *csp;

struct state *stp;

mblk_t *xxx_indata();

csp = (struct copyresp *)mp->b_rptr;

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

switch (csp->cp_cmd) {

case XXX_IOCTL:

if (csp->cp_rval) { /* failure */

if (csp->cp_private) /* state structure */

freemsg(csp->cp_private);

freemsg(mp);

return;

}

stp = (struct state *)csp->cp_private->b_rptr;

switch (stp->st_state) {

case GETSTRUCT: /* xxxdata structure copied in */

/* save structure */

stp->st_data = *(struct xxxdata *)mp->b_cont->b_rptr;

freemsg(mp->b_cont);

mp->b_cont = NULL;

/* Reuse M_IOCDATA to copyin data */

mp->b_datap->db_type = M_COPYIN;

cqp = (struct copyreq *)mp->b_rptr;

cqp->cq_size = stp->st_data.x_inlen;

cqp->cq_addr = stp->st_data.x_inaddr;

cqp->cq_flag = 0;

stp->st_state = GETINDATA; /* next state */

qreply(q, mp);

break;

case GETINDATA: /* data successfully copied in */

/* Process input, return output */

if ((mp->b_cont = xxx_indata(mp->b_cont)) == NULL) {

/* hypothetical */

mp->b_datap->db_type = M_IOCNAK; /* fail xxx_indata */

mp->b_wptr = mp->b_rptr + sizeof(struct iocblk);

iocbp->ioc_error = EIO;

qreply(q, mp);

break;

}

mp->b_datap->db_type = M_COPYOUT;

cqp = (struct copyreq *)mp->b_rptr;

cqp->cq_size = min(msgdsize(mp->b_cont),

stp->st_data.x_outlen);

cqp->cq_addr = stp->st_data.x_outaddr;

cqp->cq_flag = 0;

stp->st_state = PUTOUTDATA;

/* next state */

qreply(q, mp);

break;

case PUTOUTDATA: /* data successfully copied out, ack ioctl */

freemsg(csp->cp_private); /* state structure */

mp->b_datap->db_type = M_IOCACK;

mp->b_wtpr = mp->b_rptr + sizeof(struct iocblk);

iocbp->ioc_error = 0; /* may have been overwritten */

iocbp->ioc_count = 0; /* may have been overwritten */

iocbp->ioc_rval = 0; /* may have been overwritten */

qreply(q, mp);

break;

default: /* invalid state: can't happen */

freemsg(mp->b_cont);

mp->b_cont = NULL;

mp->b_datap->db_type = M_IOCNAK;

mp->b_wptr = mp->b_rptr + sizeof(struct iocblk);

iocbp->ioc_error = EINVAL;

qreply(q, mp);

ASSERT (0); /* panic if debugging mode */

break;

} /* switch (stp->st_state) */

break;

default: /* M_IOCDATA not for us */

/* if module, pass message on */

/* if driver, free message */

break;

} /* switch (csp->cp_cmd) */

}

Figure 6-9 Message Block Allocation

At case GETSTRUCT, the user xxxdata structure is copied into the module's state structure (pointed at by cp_private in the message) and the M_IOCDATA message is reused to create a second M_COPYIN message to read in the user data. At case GETINDATA, the input user data is processed by the xxx_indata routine (not supplied in the example), which frees the linked M_DATA block and returns the output data message block. The M_IOCDATA message is reused to create an M_COPYOUT message to write the user data. At case PUTOUTDATA, the message block containing the state structure is freed and an acknowledgment is sent upstream.

Care must be taken at the "can't happen'' default case since the message block containing the state structure (cp_private) is not returned to the pool because it might not be valid. This might result in a lost block. The ASSERT helps find errors in the module if a "can't happen'' condition occurs.

6.2.6 I_LIST ioctl

The ioctl I_LIST supports the strconf and strchg commands that are used to query or change the configuration of a Stream. Only the super-user or an owner of a STREAMS device may alter the configuration of that Stream. See strchg(1) for more information.

The strchg command does the following:

The strconf command does the following: If the Stream contains a multiplexing driver, the strchg and strconf commands will not recognize any modules below that driver.

The ioctl I_LIST performs two functions. When the third argument of the ioctl call is set to NULL, the return value of the call shows the number of modules, including the driver, present on the Stream. For example, if there are two modules above the driver, 3 is returned. On failure, errno may be set to a value specified in streamio(7). The second function of the I_LIST ioctl is to copy the module names found on the Stream to the user-supplied buffer. The address of the buffer in user space and the size of the buffer are passed to the ioctl through a structure str_list, which is defined in Figure 6-10.

struct str_mlist {

char l_name[FMNAMESZ+1]; /* space for holding a module name */

};

struct str_list {

int sl_nmods; /* # of modules for which space is allocated */

struct str_mlist *sl_modlist;/* address of buffer for names */

};

Figure 6-10 str_list Structure

sl_nmods is the number of modules in the sl_modlist array that the user has allocated. Each element in the array must be at least FMNAMESZ+1 bytes long. FMNAMESZ is defined by sys/conf.h.

Home

 

The user can find out how much space to allocate by first invoking the ioctlI_LIST with arg set to NULL. The I_LIST call with arg pointing to the str_list structure returns, in the sl_nmods member, the number of entries that have been filled into the st array. Note that the number of entries includes the driver. If there is not enough space in the sl_modlist array (see NOTES) or sl_nmods is less than 1, the I_LIST call fails and errno is set to EINVAL. If arg or the sl_modlist array points outside the allocated address space, EFAULT is returned.

NOTES

It is possible, but unlikely, that another module was pushed on the Stream after the user invoked the I_LIST ioctl with the NULL argument and before the I_LIST ioctl with the structure argument was invoked. 

In this Release, SX does not support non-multithreaded modules and drivers.

Home

 

6.3 FLUSH HANDLING

All modules and drivers are expected to handle M_FLUSH messages. An M_FLUSH message can originate at the Stream head or from a module or a driver. The first byte of the M_FLUSH message is an option flag that can have the following values: Figure 6-11 shows line discipline module flush handling.

static int

ld_put(queue_t *q, mblk_t *mp)

{

int qflag;

#ifdef MT

int pl;

#endif

switch (mp->b_datap->db_type) {

default:

/*

* queue everything except flush

*/

putq(q, mp);

return;

case M_FLUSH:

#ifdef MT

pl = freezestr(q);

#endif

(void)strqget(q, QFLAG, 0, &qflag);/* get q_flag */

#ifdef MT

unfreezestr(q, pl);

#endif

if (*mp->b_rptr & FLUSHW) /* flush write queue */

flushq(qflag & QREADR ? WR(q) : q, FLUSHDATA);

if (*mp->b_rptr & FLUSHR) /* flush read queue */

flushq(qflag & QREADR ? q : RD(q), FLUSHDATA);

putnext(q, mp);

return;

}

}

Figure 6-11 Line Discipline Flush Handling

Home

 

The Stream head turns around the M_FLUSH message if FLUSHW is set (FLUSHR will be cleared).

A driver turns around M_FLUSH if FLUSHR is set (should mask off FLUSHW). The Stream head turns around the M_FLUSH message if FLUSHW is set (FLUSHR will be cleared).

A driver turns around M_FLUSH if FLUSHR is set (should mask off FLUSHW).

Figure 6-12 shows the line discipline module flushing because of break.

static int

ld_put(queue_t *q, mblk_t *mp)

{

int qflag;

#ifdef MT

int pl;

#endif

switch (mp->b_datap->db_type) {

default:

/*

* queue everything except flush, break

*/

putq(q, mp);

return;

case M_FLUSH:

#ifdef MT

pl = freezestr(q);

#endif

(void)strqget(q, QFLAG, 0, &qflag);/* get q_flag */

#ifdef MT

unfreezestr(q, pl);

#endif

if (*mp->b_rptr & FLUSHW) /* flush write queue */

flushq(qflag & QREADR ? WR(q) : q, FLUSHDATA);

if (*mp->b_rptr & FLUSHR) /* flush read queue */

flushq(qflag & QREADR ? q : RD(q), FLUSHDATA);

putnext(q, mp);

return;

#ifdef MT

case M_BREAK:

pl=freezestr(q);

(void)strqget(q, QFLAG, 0, &qflag); /* get q_flag */

unfreezestr(q,pl);

/*

* read side only;

* doesn't make sense for write side

*/

if (qflag & QREADR) {

putnextctl1(q, M_PCSIG, SIGINT);

putnextctl1(q, M_FLUSH, FLUSHW);

putnextctl1(WR(q), M_FLUSH, FLUSHR);

} else

freemsg(mp);

return;

#else

case M_BREAK:

(void)strqget(q, QFLAG, 0, &qflag); /* get q_flag */

/*

* read side only;

* doesn't make sense for write side

*/

if (qflag & QREADR) {

putctl1(q->q_next, M_PCSIG, SIGINT);

putctl1(q->q_next, M_FLUSH, FLUSHW);

putctl1(WR(q)->q_next, M_FLUSH, FLUSHR);

} else

freemsg(mp);

return;

#endif

}

}

Figure 6-12 Line Discipline Break Flushing
 
 

The next two figures further show flushing the entire Stream due to a line break. Figure 6-13 shows the flushing of the write-side of a Stream.

Figure 6-13 Flushing the Write-Side of a Stream

Home

 

In Figure 6-13, the following is taking place:

  1. A break is detected by a driver.
  2. The driver generates an M_BREAK message and sends it upstream.
  3. The module translates the M_BREAK into an M_FLUSH message with FLUSHW set and sends it upstream.
  4. The Stream head does not flush the write queue (no messages are ever queued there)

  5. unless the strhold feature is enabled. If the strhold feature is enabled, messages can be queued on the write side of the Stream head.

  6. The Stream head turns the message around (sends it down the write-side).
  7. The module flushes its write queue.
  8. The message is passed downstream.
  9. The driver flushes its write queue and frees the message.
Figure 6-14 shows flushing the read-side of a Stream.The dotted boxes depict flushed queues.

Figure 6-14 Flushing the Read-Side of a Stream

Home

 

The events taking place in Figure 6-14 are as follows:

  1. After generating the first M_FLUSH message, the module generates an M_FLUSH with FLUSHR set and sends it downstream.
  2. The driver flushes its read queue.
  3. The driver turns the message around (sends it up the read-side).
  4. The module flushes its read queue.
  5. The message is passed upstream.
  6. The Stream head flushes the read queue and frees the message.
The flushband routine provides the module and driver with the capability to flush messages associated with a given priority band. A user can flush a particular band of messages by issuing: where bandp is a pointer to a structure bandinfo that has the format:

struct bandinfo {

uchar_t bi_pri;

int bi_flag;

};

The bi_flag field may be one of FLUSHR, FLUSHW, or FLUSHRW.

Figure 6-15 shows flushing according to the priority band.

queue_t *rdq; /* read queue */

queue_t *wrq; /* write queue */

case M_FLUSH:

if (*bp->b_rptr & FLUSHBAND) {

if (*bp->b_rptr & FLUSHW)

flushband(wrq, FLUSHDATA, *(bp->b_rptr + 1));

if (*bp->b_rptr & FLUSHR)

flushband(rdq, FLUSHDATA, *(bp->b_rptr + 1));

} else {

if (*bp->b_rptr & FLUSHW)

flushq(wrq, FLUSHDATA);

if (*bp->b_rptr & FLUSHR)

flushq(rdq, FLUSHDATA);

}

/*

* modules pass the message on;

* drivers shut off FLUSHW and loop the message

* up the read-side if FLUSHR is set; otherwise,

* drivers free the message.

*/

break;

Figure 6-15 Priority Band Flush Handling

Home

 

Note that modules and drivers are not required to treat messages as flowing in separate bands. Modules and drivers can view the queue having only two bands of flow, normal and high priority. However, the latter alternative flushes the entire queue whenever an M_FLUSH message is received.

One use of the field b_flag of the msgb structure is provided to give the Stream head a way to stop M_FLUSH messages from being reflected forever when the Stream is being used as a pipe. When the Stream head receives an M_FLUSH message, it sets the MSGNOLOOP flag in the b_flag field before reflecting the message down the write-side of the Stream. If the Stream head receives an M_FLUSH message with this flag set, the message is freed rather than reflected.

6.4 DRIVER-KERNEL INTERFACE

6.4.1 STREAMS Interface

The entry points from the kernel into STREAMS drivers and modules are through the qinit structures pointed to by the streamtab structure, prefixinfo.

If the STREAMS module has prefix mod, then the declaration is of the form shown in Figure 6-16.

static int modrput(queue_t *, mblk_t *);

static int modrsrv(queue_t *);

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

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

static int modwput(queue_t *, mblk_t *);

static int modwsrv(queue_t *);

static struct mod_minfo = {};

static struct qinit rdinit =

{modrput, modrsrv, modopen, modclose, NULL, & m_info, NULL};

static struct qinit wrinit =

{modwput, modwsrv, NULL, NULL, NULL, & m_info, NULL};

struct streamtab modinfo = { &rdinit, &wrinit, NULL, NULL };

#ifdef MT

int moddevflag = D_MP;

#else

int moddevflag = 0;

#endif

Figure 6-16 mod Declaration Form

where

Each qinit structure can point to four entry points. (An additional function pointer has been reserved for future use and must not be used by drivers or modules.) These four function pointer fields in the qinit structure are qi_putp, qi_srvp, qi_qopen, and qi_close.

Home

 

6.5 CONFIGURING THE SYSTEM FOR STREAMS DRIVERS AND MODULES

This section summarizes guidelines common to the design of STREAMS modules and drivers. Additional rules about modules and drivers can be found in Chapter 7, STREAMS Modules, and Chapter 8, STREAMS Drivers.

6.5.1 Modules and Drivers

6.5.1.1 RULES FOR OPEN/CLOSE ROUTINES
6.5.1.2 RULES FOR IOCTLS
 
6.5.1.3 RULES FOR put AND service PROCEDURES
To ensure proper data flow between modules and drivers, the following rules should be observed in put and service procedures:
Home

 

NOTE

References to drivers apply to modules as well.

put Procedures
  1. Generally, each queue defines a put procedure in its qinit structure for passing messages between modules.

  2. In some instances, drivers do not need put procedures; for example, messages are only passed upstream by the driver's interrupt routine, and therefore a read-side put procedure is not needed.

  3. A put procedure must use the putq utility to enqueue a message on its own queue. This is necessary to ensure that the various fields of the queue structure are maintained consistently.
  4. When passing messages to a neighboring module, a module may not call putq directly, but must call its neighbor module's put procedure using the appropriate DDI/DKI STREAMS utility.
  5. However, the q_qinfo structure that points to a module's put procedure may point to putq (that is, putq is used as the put procedure for that module). When a module calls a neighbor module's put procedure defined in this way, it will be calling putq indirectly. If any module uses putq as its put procedure in this way, the module must define a service procedure. Otherwise, no messages will ever be processed by the next module. Also, because putq does not process M_FLUSH messages, any module that uses putq as its put procedure must define a service procedure to process M_FLUSH messages.

  6. The put procedure of a queue with no service call its neighbor module's put procedure using the appropriate DDI/DKI STREAMS utility. See putnext.
  7. The put procedure of a queue with no service procedure must call the put procedure of the next queue using putnext if a message is to be passed to that queue.
  8. Processing many function calls with the put procedure could lead to interrupt stack overflow. In that case, switch to service procedure processing whenever appropriate to switch to a different stack.

  9. Although most drivers do not have a read-side put procedure, those that do must be called (for example, from the interrupt handler) with the multiprocessor DDI/DKI function put.

Service Procedures
  1. If flow control is desired, a service procedure is required.
  2. The service procedure should use theor  routines before doing putnext to honor flow control.

  3. The service procedure must use getq to remove a message from its message queue, so that the flow control mechanism is maintained.
  4. The service procedure should process all messages in its queue. The only exception is if the queue ahead is blocked [that is, fails] or some other failure like buffer allocation failure. Adherence to this rule is the only guarantee that STREAMS will enable (schedule for execution) the service procedure when necessary, and that the flow control mechanism will not fail.
  5. If a service procedure exits for other reasons, it must take explicit steps to assure it will be reenabled.

  6. The service procedure should not put a high-priority message back on the queue, because of the possibility of getting into an infinite loop.
  7. The service procedure must follow the steps listed next for each message that it processes. STREAMS flow control relies on strict adherence to these steps.
    1. Remove the next message from the queue using getq. It is possible that the service procedure could be called when no messages exist on the queue, so the service procedure should never assume that there is a message in its queue. If there is no message, RETURN.
    2. If all the following conditions are met:
    3. The message must be replaced on the head of the queue from which it was removed using putbq. Following this, the service procedure is exited. The service procedure should not be reenabled at this point. It will be automatically be enabled by flow control.
    4. If all the conditions of Step B are not met, the message should not be returned to the queue, but should be processed as necessary. Then, return to Step A.
 

Home

 

6.5.2 Data Structures

Only the contents of q_ptr, q_minpsz, q_maxpsz, q_hiwat, and q_lowat in the queue structure may be altered. q_minpsz, q_maxpsz, q_hiwat, and q_lowat are set when the module or driver is opened, but they may be modified later only by using the DDI/DKI utility strqset.

Drivers and modules are allowed to change the qb_hiwat and qb_lowat fields of the qband structure. They may only read the qb_count, qb_first, qb_last, and qb_flag fields.

The routines strqget and strqset must be used to get and set the fields associated with the queue. They insulate modules and drivers from changes in the queue structure and from multiprocessor STREAMS implementation details, and also enforce the previous rules.

6.5.3 Header Files

The following header files are generally required in modules and drivers:
types.h  
Contains type definitions used in the STREAMS header files.
stream.h 
Contains required structure and constant definitions.
stropts.h 
Primarily for users, but contains definitions of the arguments to the M_FLUSH message type also required by modules.
One or more of the header files described next may also be included. No standard UNIX system header files should be included except as described in the following section. The intent is to prevent attempts to access data that cannot or should not be accessed.
errno.h   
Defines various system error conditions, and is needed if errors are to be returned upstream to the user.
sysmacros.h  
Contains miscellaneous system macro definitions (subject to DDI/DKI restrictions).
param.h 
Defines various system parameters.
signal.h 
Defines system signal values, and should be used if signals are to be processed or sent upstream.
file.h 
Defines file open flags, and is needed if O_NDELAY (or O_NONBLOCK) is inter- preted.
Home

 

6.5.4 Configuring STREAMS Modules and Drivers

Each character device configured into a UNIX system results in an entry being placed in the kernel cdevsw table. Entries for STREAMS drivers are also placed in this table. However, because system calls to STREAMS drivers must be processed by the STREAMS routines, the configuration mechanism distinguishes between STREAMS drivers and character device drivers in their associated cdevsw entries.

The distinction is contained in the d_str field that was added to the cdevsw structure for this purpose. d_str provides the appropriate single entry point for all system calls on STREAMS files.

extern struct cdevsw {...

.

.

.

struct streamtab *d_str;

int *d_flag;

}

cdevsw[ ];

The configuration mechanism forms the d_str entry name by appending the string info to the STREAMS driver prefix. The info entry is a pointer to a streamtab structure (see Appendix A) that contains pointers to the qinit structures for the read and write QUEUEs of the driver. The driver must contain the following external definition.

     struct streamtab prefixinfo = {...
     int prefixdevflag = D_MP;
If the d_str entry contains a non-NULL pointer, the operating system recognizes the device as a STREAMS driver and calls the appropriate STREAMS routine. If the entry is NULL, a character I/O device cdevsw interface is used. streamtab and devflag must be externally defined in STREAMS drivers and modules. streaintab is used to identify the appropriate open, close, put, service, and administration routines. These driver/module routines should generally be declared static. d_flag is used to specify the driver information and must be set to D_MP.

fmodsw contains the following:

#define FMNAMESZ 8

extern struct fmodsw {

char f_name[FMNAMESZ+l];

struct streamtab *f_str;

int *f_flag;

int f_drvflag;

}

fmodsw[ ];

f_name is the name of the module used in STREAMS-related ioctl calls. f_str is similar to the d_str entry in the cdevsw table. It is a pointer to a streamtab structure that contains pointers to the qinit structures for the read and write queues of this STREAMS module (as in STREAMS drivers). f_flag is similar to the d_flag. The module must contain the external definition:

struct streamtab prefixinfo = { ...

int prefixdevflag = D_MP;

Usually, f_drvflag must be defined 0. If the module is used as a driver, f_drvflag must be set to M_STRDRV.

Home

 

6.5.5 Configuration Example

This section contains examples of configuring the following STREAMS driver and module. To configure the STREAMS driver and module, the following are required.
  1. The driver/module definition in cdevsw (or bdevsw) and fmodsw tables.
  2. The driver/module information definition in a /etc/master.d file (if necessary).
  3. Archive file and makefile modification:
    1. The driver/module definition in cdevsw (or bdevsw) and fmodsw tables:
    2. To link STREAM loop driver to kernel, add loop entry to cdevsw table in /etc/master.d/conf.c as follows.

      extern int loopdevflag;

      extern struct streamtab loopinfo;

      struct cdevsw cdevsw[] = {

      .................

      .................

      nodev, nodev, nodev, nodev, nodev, nodev, seltrue, nodev, 0, &loopinfo, &loopdevflag,

      };

      If a free entry in cdevsw table is not used and new entry is generated for loopinfo, l should be added to cdevcnt.

      To link STREAMS module, mod, to kernel, mod entry must be added to fmodsw table in /etc/ master.d/conf1.c as follows.

      extern int moddevflag;

      extern struct streamtab modinfo;

      struct fmodsw fmodsw[] = {

      ..................

      "mod",&modinfo, &moddevflag, 0,

      } ;

    3. The driver/module information definition in a /etc/master.d file.
    4. To configure the STREAMS software (pseudo-device) driver, loop, and assign values to the driver extem variables, the following must appear in the file m_loop.c.

      /*

      * LOOP-STREAMS loop around software driver

      */

      #include "sys/types.h"

      #include "sys/stream.h"

      #define NLP 2

      struct loop {

      queue_t *qptr;

      queue_t *oqptr;

      };

      struct loop loop_loop[NLP];

      int loop_cnt = NLP;

      mod does not have private structure. Therefore, /etc/master.d file is not required.

    5. Archive file and makefile modification.
    6. To link loop and mod to kernel, archive files for users must be created. First, loop.c, and mod.c are compiled to generate loop.o, and mod.o. Then, these object files are added to an archive file.
      compile loop.c,m_loop.c and mod.c

      (Refer to /etc/master.d/*.rule and compile them.)

      $ls -CF

      mod.c loop.c m_loop.c

      mod.o loop.o m_loop.o

      $su

      password:

      #ar r /etc/master.d/lib6 mod.o loop.o

      /etc/master.d file is not added to archive file. Information about m_loop.c should be described in makefile. (To modify options in makefile, refer to /etc/master.d/*.rule.)

      Finally, config is executed in /etc/master.d, and kernel is generated.

      NOTE

      Kernel source code must be compiled in the float1 mode.
Home

 

6.5.6 Accessible Functions

The following lists the only symbols and functions that modules or drivers may reference (in addition to those defined by STREAMS), if hardware and UNIX system release independence is to be maintained.
bcopy(from, to, nbytes)  copy data quickly
bzero(buffer, nbytes)  zero data quickly
t = max(a, b)  return max of args
t = min(a, b)  return min of args
mapinit(mp, mapsize)  initialize map structure
addr = vtop(vaddr, NULL) translate from virtual to physical address
printf(format,...)  print message
cmn_err(level,...)  print message and optional panic
id = timeout(func, arg, ticks)  schedule event
untimeout(id)  cancel event
t = major(dev)  return major device
t = minor(dev)  return minor device
time_t lbolt() clock ticks since boot in HZ
time_t time()  seconds since epoch
cond_init (cond_t *svp, simple_t *lockp)  initialize cond structure
cond_wait (cond_t, *svp, int pri)  sleep until wakeup
cond_signal (cond_t *svp) wakeup sleeper
mask_init (mask_t *newl, mask_t tpl[])  initialize lock structure
mask_lock (mask_t *lock)  lock
mask_unlock (mask_t *lock)  unlock
Home

Contents Previous Chapter Next Chapter Index Glossary