| 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 |
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.
| Home |
|---|
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:
| 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 |
|---|
#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
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.
| 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 */
};
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:
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 |
|---|
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;
}
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
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 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.
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.
| 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. |
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.
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);
}
}
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 |
|---|
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:
int stringlen; /* string length */ char *string; struct other_struct *other1;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:
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.
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).
| 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. |
| 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 |
|---|
ioctl(fd, SET_ADDR, &bufadd)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) */
}
| 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.
| 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. |
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) */
}
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.
ioctl(fd, GET_OPTIONS, &optadd)or, alternately, by use of a streamio call
ioctl(fd, I_STR, &opts_strioctl)
| 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) */
}
| 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:
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) */
}
| 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) */
}
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.
The strchg command does the following:
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 */
};
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.
| 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 |
|---|
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;
}
}
| 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
}
}
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.

| Home |
|---|
In Figure 6-13, the following is taking place:
unless the strhold feature is enabled. If the strhold
feature is enabled, messages can be queued on the write side of the Stream
head.

| Home |
|---|
The events taking place in Figure 6-14 are as follows:
ioctl(fd, I_FLUSHBAND, bandp);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;
| 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.
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
where
| Home |
|---|
When designing modules and drivers that make use of priority bands one should keep in mind that priority bands merely provide a way to impose an ordering of messages on a queue. The priority band is not used to determine the service primitive. Instead, the service interface should rely on the data contained in the message to determine the service primitive.
A multithreaded driver or module must call qprocson to enable
its put and service procedures, and qprocsoff to disable
them.
Only the following fields of the u_area (user.h): u_procp, u_ttyp, u_uid, u_gid, u_ruid, and u_rgid. The fields u_uid, u_gid, u_ruid, and u_rgid are for backward compatibility with previously designed device drivers. The actual user credentials are passed directly to the driver and need not be accessed in the u_area. These fields may not support valid uids or gids when the system is configured with large user ids.
Only the following fields can be accessed in the process table (proc.h): p_pid, p_pgrp.
Multithreaded drivers must protect all global driver data with DDI/DKI-defined
locks or synchronization utility functions.
| Home |
|---|
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.
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.
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.
The service procedure should use the
or
routines before doing putnext to honor flow control.
If a service procedure exits for other reasons, it must take explicit steps to assure it will be reenabled.
Continue to Step C. Otherwise, continue to Step D.
| Home |
|---|
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.
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 |
|---|
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 |
|---|
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,
} ;
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.
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.
| Home |
|---|
| 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 |