JavaScript is required to for searching.
Skip Navigation Links
Exit Print View
STREAMS Programming Guide     Oracle Solaris 11.1 Information Library
search filter icon
search icon

Document Information

Preface

Part I Application Programming Interface

1.  Overview of STREAMS

2.  STREAMS Application-Level Components

3.  STREAMS Application-Level Mechanisms

4.  Application Access to the STREAMS Driver and Module Interfaces

5.  STREAMS Administration

6.  Pipes and Queues

Part II Kernel Interface

7.  STREAMS Framework - Kernel Level

8.  STREAMS Kernel-Level Mechanisms

ioctl Processing

Message Allocation and Freeing

Recovering From No Buffers

Read Device Interrupt Handler

Write Service Procedure

Releasing Callback Requests

Extended STREAMS Buffers

esballoc(9F) Example

General ioctl Processing

STREAMS ioctl Issues

I_STR ioctl Processing

Transparent ioctl

Transparent ioctl Messages

Transparent ioctl Examples

M_COPYIN Example

M_COPYOUT Example

Bidirectional Data Transfer Example

I_LIST ioctl(2)Example

M_FLUSH Message Handling

Flushing According to Priority Bands

Flushing Priority Band

Driver and Module Service Interfaces

Service Interface Library Example

Module Service Interface Example

Message Type Change Rules

Common ioctl Interfaces

FIORDCHK

FIONREAD

I_NREAD

signal Message

9.  STREAMS Drivers

10.  STREAMS Modules

11.  Configuring STREAMS Drivers and Modules

12.  Multithreaded STREAMS

13.  STREAMS Multiplex Drivers

Part III Advanced Topics

14.  Debugging STREAMS-based Applications

Part IV Appendixes

A.  Message Types

B.  Kernel Utility Interface Summary

C.  STREAMS-Based Terminal Subsystem

D.  STREAMS FAQ

Glossary

Index

General ioctl Processing


Note - Please see the ioctl() section in the Writing Device Drivers for information on the 64–bit data structure macros.


When the stream head is called to process an ioctl(2) that it does not recognize, it creates an M_IOCTL message and sends it down the stream. An M_IOCTL message is a single M_IOCTL message block followed by zero or more M_DATA blocks. The M_IOCTL message block has the form of an iocblk(9S) structure. This structure contains the following elements.

int        ioc_cmd;              /* ioctls command type */
cred_t     *ioc_cr;              /* full credentials */
uint       ioc_id;               /* ioctl id */
uint       ioc_count;            /* byte cnt in data field */
int        ioc_error;            /* error code */
int        ioc_rval;             /* return value */

For an I_STR ioctl(2), ioc_cmd contains the command supplied by the user in the ic_cmd member of the strioctl structure defined in streamio(7I). For others, ioc_cmd contains the value of the cmd argument in the call to ioctl(2). The ioc_cr field contains the credentials of the user process.

The ioc_id field is a unique identifier used by the stream head to identify the ioctl and its response messages.

The ioc_count field indicates the number of bytes of data associated with this ioctl request. If the value is greater than zero, there will be one or more M_DATA mblks linked to the M_IOCTL mblkb_cont field. If the value of the ioc_count field is zero, there will be no M_DATA mblk associated with the M_IOCTL mblk. If the value of ioc_count is equal to the special value TRANSPARENT, then there is one M_DATA mblk linked to this mblk and its contents will be the value of the argument passed to ioctl(2). This can be a user address or numeric value. (see Transparent ioctl Processing).

An M_IOCTL message is processed by the first module or driver that recognizes it. If a module does not recognize the command, it should pass it down. If a driver does not recognize the command, it should send a negative acknowledgement or M_IOCNAK message upstream. In all circumstances, a module or driver processing an M_IOCTL message must acknowledge it.

Modules must always pass unrecognized messages on. Drivers should negatively acknowledge unrecognized ioctl(2) messages and free any other unrecognized message.

If a module or driver finds an error in an M_IOCTL message for any reason, it must produce a negative acknowledgement message. To do this, set the message type to M_IOCNAK and send the message upstream. No data or return value can be sent. If ioc_error is set to 0, the stream head causes the ioctl(2) to fail with EINVAL. Optionally, the module can set ioc_error to an alternate error number.

ioc_error can be set to a nonzero value in both M_IOCACK and M_IOCNAK. This causes the value to be returned as an error number to the process that sent the ioctl(2).

If a module checks what the ioctl(2) of other modules below it are doing, the module should not just search for a specific M_IOCTL on the write side, but also look for M_IOCACK or M_IOCNAK on the read side. For example, suppose the module's write side sees TCSETA (see termio(7I)) and records what is being set. The read-side processing knows that the module is waiting for an answer for the ioctl(2). When the read-side processing sees an ack or nak, it checks for the same ioctl(2) by checking the command (here TCSETA) and the ioc_id. If these match, the module can use the information previously saved.

If you have the module check, for example, the TCSETA/TCGETA group of ioctl(2) calls as they pass up or down a stream, you must never assume that because TCSETA comes down it actually has a data buffer attached to it. The user can form TCSETA as an I_STR call and accidentally give a NULL data buffer pointer. 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. Do not assume that the data block is missing and allocate a new buffer, then assign b_cont to point to it, because the original buffer will be lost.

STREAMS ioctl Issues

Regular device drivers have user context in the ioctl(9E) call. However, in a STREAMS driver or module, the only guarantee of user context is in the open(9E) and close(9E) routines. Some indication of the calling context where data is used is therefore necessary.


Note - The notion of data models as well as new macros for handling data structure access are discussed in Writing Device Drivers. A STREAMS driver or module writer should use these flags and macros when dealing with structures that change size between data models.


A flag value that represents the data model of the entity invoking the operation has been added to the ioc_flag field of the iocblk(9S) structure, the cq_flag of the copyreq(9S) structure, and the cp_flag of the copyresp(9S) structure.

The data model flag is one of these possibilities:

In addition, IOC_NATIVE is conditionally defined to match the data model of the kernel implementation.

By looking at the data model flag field of the relevant iocblk(9S), copyreq(9S), or copyresp(9S) structures, the STREAMS module can determine the best method of handling the data.


Caution

Caution - The layout of the iocblk, copyreq, and copyresp structures is different between the 32-bit and 64-bit kernels. Be cautious of any data structure overloading in the cp_private, cq_private, or cq_filler fields because alignment has changed.


I_STR ioctl Processing

The transparent and nontransparent methods implement ioctl(2) in the STREAMS driver or module itself, rather than in the stream head. I_STR ioctl(2) (also referred to as nontransparent ioctl(2)) is created when a user requests an I_STR ioctl(2) and specifies a pointer to a strioctl structure as the argument. For example, assuming that fd is an open lp STREAMS device and LP_CRLF is a valid option, the user could make a request by issuing the struct in the following example:

Example 8-6 Struct for Nontransparent ioctl

struct strioctl *str;
short lp_opt = LP_CRLF;

str.ic_cmd = SET_OPTIONS;
str.ic_timout = -1;
str.ic_dp = (char *)&lp_opt;
str.ic_len = sizeof (lp_opt)

ioctl(fd, I_STR, &str);

On receipt of the I_STR ioctl(2) request, the stream head creates an M_IOCTL message. ioc_cmd is set to SET_OPTIONS, ioc_count is set to the value contained in ic_len (in this example sizeof (short)). An M_DATA mblk is linked to the M_IOCTL mblk and the data pointed to by ic_dp is copied into it (in this case LP_CRLF).

Example 8-7 illustrates processing associated with an I_STR ioctl(2). lpdoioctl illustrates driver M_IOCTL processing, which also applies to modules. lpdoioctl is called by lp write-side put or service procedure to process M_IOCTL messages. This example is for a driver.

Example 8-7 I_STR ioctl(2) Driver

static void
lpdoioctl (queue_t *q, mblk_t     *mp)
{
        struct iocblk *iocp;
        struct lp *lp;

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

        /* 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 */
              iocp->ioc_error = lpsetopt (lp, *(short *)mp->b_cont->b_rptr)

              /* 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);
        }
    }

This example recognizes only one command, SET_OPTIONS. The ioc_count contains the number of user-supplied data bytes. ioc_count must equal the size of a short.

    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 */

Once the command has been verified, lpsetopt is called to process the request. lpsetopt returns 0 if the request is satisfied, otherwise an error number is returned. If ioc_error is nonzero, on receipt of the acknowledgement the stream head returns -1 to the application's ioctl(2) request and sets errno to the value of ioc_error.

        /* Actual data is in 2nd message block */
            iocp->ioc_error = lpsetopt (lp, *(short *)mp->b_cont->b_rptr)

The ioctl(2) is acknowledged. This includes changing the M_IOCTL message type to M_IOCACK and setting the ioc_count field to zero to indicate that no data is to be returned to the user. Finally, the message is sent upstream using qreply(9F).

If ioc_count was left nonzero, the stream head would copy that many bytes from the second through the nth message blocks into the user buffer. You must set ioc_count if you want to pass any data back to the user.

           /* ACK the ioctl */
                mp->b_datap->db_type = M_IOCACK;
                iocp->ioc_count = 0;
                qreply(q, mp);
                break;

In the default case for unrecognized commands or malformed requests, a nak is generated. This is done by changing the message type to an M_IOCNAK and sending it back upstream.

          default:
                iocnak:
                /* NAK the ioctl */
                mp->b_datap->db_type = M_IOCNAK;
                qreply(q, mp);

A module does not acknowledge (nak) an unrecognized command, but passes the message on. A module does not acknowledge (nak) a malformed request.

Transparent ioctl

Transparent ioctls are used from within a module to tell the stream head to perform a copyin or copyout on behalf of the module. The stream head must have knowledge of the data model of the caller in order to process the copyin and copyout properly. The user should use the ioctl macros as described in Writing Device Drivers when coding a STREAMS module that uses Transparent ioctls.

Transparent ioctl Messages

The transparent STREAMS ioctl(2) mechanism is needed because user context does not exist in modules and drivers when an ioctl(2) is processed. This prevents them from using the kernel ddi_copyin/ddi_copyout functions.

Transparent ioctl(2) enable you to write an application using conventional ioctl(2) semantics instead of the I_STR ioctl(2) and an strioctl structure. The difference between transparent and nontransparent ioctl(2) processing in a STREAMS driver and module is the way data is transferred from user to kernel space.

The transparent ioctl(2) mechanism allows backward compatibility for older programs. This transparency only works for modules and drivers that support transparent ioctl(2). Trying to use transparent ioctl(2) on a stream that does not support them makes the driver send an error message upstream, causing the ioctl to fail.

The following example illustrates the semantic difference between a nontransparent and transparent ioctl(2). A module that translates arbitrary character is pushed on the stream The ioctl(2) specifies the translation to do (in this case all uppercase vowels are changed to lowercase). A transparent ioctl(2) uses XCASE instead of I_STR to inform the module directly.

Assume that fd points to a STREAMS device and that the conversion module has been pushed onto it. The semantics of a nontransparent I_STR command to inform the module to change the case of AEIOU are:

strioctl.ic_cmd = XCASE;
strioctl.ic_timout = 0;
strioctl.ic_dp = "AEIOU"
strioctl.ic_len = strlen(strioctl.ic_dp);
ioctl(fd,I_STR, &strioctl);

When the stream head receives the I_STR ioctl(2) it creates an M_IOCTL message with the ioc_cmd set to XCASE and the data specified by ic_dp. AEIOU is copied into the first mblk following the M_IOCTL mblk.

The same ioctl(2) specified as a transparent ioctl(2) is called as follows:

ioctl(fd, XCASE, "AEIOU");

The stream head creates an M_IOCTL message with the ioc_cmd set to XCASE, but the data is not copied in. Instead, ioc_count is set to TRANSPARENT and the address of the user data is placed in the first mblk following the M_IOCTL mblk. The module then requests the stream head to copy in the data ("AEIOU") from user space.

Unlike the nontransparent ioctl(2), which can specify a timeout parameter, transparent ioctl(2)s block until processing is complete.


Caution

Caution - Incorrectly written drivers can cause applications using transparent ioctl(2) to block indefinitely.


Even though this process is simpler in the application, transparent ioctl adds considerable complexity to modules and drivers, and additional overhead to the time required to process the request.

The form of the M_IOCTL message generated by the stream head for a transparent ioctl(2) is a single M_IOCTL message block followed by one M_DATA block. The form of the iocblk(9S) structure in the M_IOCTL block is the same as described under general ioctl(2) processing. However, ioc_cmd is set to the value of the command argument in ioctl(2) and ioc_count is set to the special value of TRANSPARENT. The value TRANSPARENT distinguishes when an I_STR ioctl(2) can specify a value of ioc_cmd that is equivalent to the command argument of a transparent ioctl(2). The b_cont block of the message contains the value of the arg parameter in the call.


Caution

Caution - If a module processes a specific ioc_cmd and does not validate the ioc_count field of the M_IOCTL message, the module breaks when transparent ioctl(2) is performed with the same command.



Note - Write modules and drivers to support both transparent and I_STR ioctl(2).


All M_IOCTL message types (M_COPYIN, M_COPYOUT, M_IOCDATA,M_IOCACK and M_IOCNACK) have some similar data structures and sizes. You can reuse these structures instead of reallocating them. Note the similarities in the command type, credentials, and id.

The iocblk(9S) structure is contained in M_IOCTL, M_IOCACK and M_IOCNAK message types. For the transparent case, M_IOCTL has one M_DATA message linked to it. This message contains a copy of the argument passed to ioctl(2). Transparent processing of M_IOCACK and M_IONAK does not allow any messages to be linked to them.

The copyreq(9S) structure is contained in M_COPYIN and M_COPYOUT message types. The M_COPYIN message type must not have any other message linked to it (that is, b_cont == NULL). The M_COPYOUT message type must have one or more M_DATA messages linked to it. These messages contain the data to be copied into user space.

The copyresp(9S) structure is contained in M_IOCDATA response message types. These messages are generated by the stream head in response to an M_COPYIN or M_COPYOUT request. If the message is in response to an M_COPYOUT request, the message has no messages attached to it (b_cont is NULL). If the response is to an M_COPYIN, then zero or more M_DATA message types are attached to the M_IOCDATA message. These attached messages contain a copy of the user data requested by the M_COPYIN message.

The iocblk(9S), copyreq(9S), and copyresp(9S) structures contain a field indicating the type of ioctl(2) command, a pointer to the user's credentials, and a unique identifier for this ioctl(2). These fields must be preserved.

The structure member cq_private is reserved for use by the module. M_COPYIN and M_COPYOUT request messages contain a cq_private field that can be set to contain state information for ioctl(2) processing (which identifies what the subsequent M_IOCDATA response message contains). This state is returned in cp_private in the M_IOCDATA message. This state information determines the next step in processing the message. Keeping the state in the message makes the message self-describing and simplifies the ioctl(2) processing.

For each piece of data that the module copies from user space, an M_COPYIN message is sent to the stream head. The M_COPYIN message specifies the user address (cq_addr) and number of bytes (cq_size) to copy from user space. The stream head responds to the M_COPYIN request with a M_IOCDATA message. The b_cont field of the M_IOCDATA mblk contains the contents pointed to by the M_COPYIN request. Likewise, for each piece of data that the module copies to user space, an M_COPYOUT message is sent to the stream head. Specify the user address (cq_addr) and number of bytes to copy (cq_size). The data to be copied is linked to the M_COPYOUT message as one or more M_DATA messages. The stream head responds to M_COPYOUT requests with an M_IOCDATA message, but b_cont is null.

After the module has finished processing the ioctl (that is, all M_COPYIN and M_COPYOUT requests have been processed), the ioctl(2) must be acknowledged with an M_IOCACK to indicate successful completion of the command or an M_IOCNAK to indicate failure.

If an error occurs when attempting to copy data to or from user address space, the stream head will set cp_rval in the M_IOCDATA message to the error number. In the event of such an error, the M_IOCDATA message should be freed by the module or driver. No acknowledgement of the ioctl(2) is sent in this case.

Transparent ioctl Examples

Following are three examples of transparent ioctl(2) processing. Example 8-8 and Example 8-9 illustrate how to use M_COPYIN to copy data from user space. Example 8-10 illustrates how to use M_COPYOUT to copy data to user space. Example 8-11 is a more complex example showing state transitions that combine M_COPYIN and M_COPYOUT.

In these examples the message blocks are reused to avoid the overhead of allocating, copying, and releasing messages. This is standard practice.

The stream head guarantees that the size of the message block containing an iocblk(9S) structure is large enough to also hold the copyreq(9S) and copyresp(9S) structures.

M_COPYIN Example


Note - Please see the copyin section in Writing Device Drivers for information on the 64–bit data structure macros.


Example 8-8 illustrates the processing of a transparent ioctl(2) request only (nontransparent request processing is not shown). In this example, the contents of a user buffer are to be transferred into the kernel as part of an ioctl call of the form

ioctl(fd, SET_ADDR, (caddr_t) &bufadd);

where bufadd is a struct address whose elements are:

struct address {    
     int                ad_len;;        /* buffer length in bytes */
     caddr_t        ad_addr;        /* buffer address */
};

This requires two pairs of messages (request and response) following receipt of the M_IOCTL message: the first copyin(9F), shown in Example 8-8, copies the structure (address) , and the second copyin(9F), shown in Example 8-9, copies the buffer (address.ad.addr). Two states are maintained and processed in this example: GETSTRUCT is for copying the address structure and GETADDR for copying the ad_addr of the structure.

The transparent part of the SET_ADDR M_IOCTL message processing requires that the address structure be copied from user address space. To accomplish this, the M_IOCTL message processing issues an M_COPYIN request to the stream head.

Example 8-8 M_COPYIN: Copy the address Structure

    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 */

    static void xxioc(queue_t *q, mblk_t *mp);

    static int
    xxwput(q, mp)
        queue_t *q;        /* write queue */
        mblk_t *mp;
    {
        struct iocblk *iocbp;
        struct copyreq *cqp;

        switch (mp->b_datap->db_type) {
            .
            .
            .
            case M_IOCTL:
                /* Process ioctl commands */
                iocbp = (struct iocblk *)mp->b_rptr;
                switch (iocbp->ioc_cmd) {
                    case SET_ADDR;
                        if (iocbp->ioc_count != TRANSPARENT) {
                          /* do non-transparent processing here
                           *       (not shown here) */
                        } else {
                         /* ioctl command is transparent 
                          * Reuse M_IOCTL block for first M_COPYIN 
                          * request of address structure */
                         cqp = (struct copyreq *)mp->b_rptr;
                         /* Get user space structure address from linked 
                          * M_DATA block */
                         cqp->cq_addr = *(caddr_t *) mp->b_cont->b_rptr;
                         cqp->cq_size = sizeof(struct address);
                         /* MUST free linked blks */
                         freemsg(mp->b_cont);
                         mp->b_cont = NULL;

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

                         /* Finish describing M_COPYIN message */
                         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:
                /* all M_IOCDATA processing done here */
                xxioc(q, mp);
                break;
        }
        return (0);
    }

xxwput() verifies that the SET_ADDR is TRANSPARENT to avoid confusion with an I_STR ioctl(2), which uses a value of ioc_cmd equivalent to the command argument of a transparent ioctl(2).

The if else statement checks whether the size count is equal to TRANSPARENT. If it is equal, the message was not generated from an I_STR ioctl(2) and the else clause of the if else executes.

    if (iocbp->ioc_count != TRANSPARENT) {
            /* do non-transparent processing here (not shown here) */
    } else {

The mblk is reused and mapped into a copyreq(9S) structure. The user space address of bufadd is contained in the b_cont of the M_IOCTL mblk. This address and its size are copied into the copyreq(9S) message. The b_cont of the copy request mblk is not needed, so it is freed and then filled with NULL.

    cqp = (struct copyreq *)mp->b_rptr;
            /* Get user space structure address from linked M_DATA block */
            cqp->cq_addr = *(caddr_t *) mp->b_cont->b_rptr;
            cqp->cq_size = sizeof(struct address);
            /* MUST free linked blks */
            freemsg(mp->b_cont);
            mp->b_cont = NULL;

Caution

Caution - The layout of the iocblk, copyreq, and copyresp structures is different between 32–bit and 64–bit kernels. Be careful not to overload any data structure in the cp_private or the cq_filler fields because alignment has changed.


Example 8-9 M_COPYIN: Copy the Buffer Address

    xxioc(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;

        /* validate this M_IOCDATA is for this module */
        switch (csp->cp_cmd) {
            case SET_ADDR:
                if (csp->cp_rval){ /* GETSTRUCT or GETADDR fail */
                    freemsg(mp);
                    return;
                }
                switch ((int)csp->cp_private){ /* determine state */
                    case GETSTRUCT:        /* user structure has arrived */
                      /* reuse M_IOCDATA block */
                      mp->b_datap->db_type = M_COPYIN;
                      mp->b_wptr = mp->b_rptr + sizeof (struct copyreq);
                      cqp = (struct copyreq *)mp->b_rptr;
                      /* user structure */
                      ap = (struct address *)mp->b_cont->b_rptr;
                      /* buffer length */
                      cqp->cq_size = ap->ad_len;
                      /* user space buffer address */
                      cqp->cq_addr = ap->ad_addr;
                      freemsg(mp->b_cont);
                      mp->b_cont = NULL;
                      cqp->cq_flag = 0;
                      cqp->cp_private=(mblk_t *)GETADDR;  /*nxt st*/
                      qreply(q, mp);
                      break;

                    case GETADDR:                /* user address is here */
                      /* hypothetical routine */
                      if (xx_set_addr(mp->b_cont) == FAILURE) {
                          mp->b_datap->db_type = M_IOCNAK;
                          iocbp->ioc_error = EIO;
                      } else {
                          mp->b_datap->db_type=M_IOCACK;/*success*/
                          /* can have been overwritten */
                          iocbp->ioc_error = 0;
                          iocbp->ioc_count = 0;
                          iocbp->ioc_rval = 0;
                      }
                      mp->b_wptr=mp->b_rptr + sizeof (struct ioclk);
                      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);
                      /* can have been overwritten */
                      iocbp->ioc_error = EINVAL;
                      qreply(q, mp);
                      break;
                }
                break;                        /* switch (cp_private) */

            default: /* M_IOCDATA not for us */
                /* if module, pass message on */
                /* if driver, free message */
                break;

cq_private of the copy request is returned in cp_private of the copy response when the M_IOCDATA message is returned. This value is set to GETSTRUCT to indicate that the address structure is contained in the b_cont of the M_IOCDATA message mblk. The copy request message is then sent back to the stream head. xxwput then returns and is called again when the stream head responds with an M_IOCDATA message, which is processed by the xxioc routine

On receipt of the M_IOCDATA message for the SET_ADDR command, xxioc() checks cp_rval. If an error occurred during the copyin operation, cp_rval is set. The mblk is freed and, if necessary, xxioc() cleans up from previous M_IOCTL requests, freeing memory, resetting state variables, and so on. The stream head returns the appropriate error to the user.

if (csp->cp_rval){ /* GETSTRUCT or GETADDR fail */
        freemsg(mp);
        return;

If no error occurred during the copyin operation, the switch statement determines whether to process the user structure, GETSTRUCT, or user address, GETADDR.

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

The cp_private field set to GETSTRUCT indicates that the linked b_cont mblk contains a copy of the user's address structure. The example then copies the actual address specified in address.ad_addr. The program issues another M_COPYIN request to the stream head, but this time cq_private contains GETADDR to indicate that the M_IOCDATA response will contain a copy of address.ad_addr. The stream head copies the information at the requested user address and sends it downstream in another, final M_IOCDATA message.

case GETSTRUCT:        /* user structure has arrived */
            /* reuse M_IOCDATA block */
            mp->b_datap->db_type = M_COPYIN;
            mp->b_wptr = mp->b_rptr + sizeof (struct copyreq);
            cqp = (struct copyreq *)mp->b_rptr;
            /* user structure */
            ap = (struct address *)mp->b_cont->b_rptr;
            /* buffer length */
            cqp->cq_size = ap->ad_len;
            /* user space buffer address */
            cqp->cq_addr = ap->ad_addr;
            freemsg(mp->b_cont);
            mp->b_cont = NULL;
            cqp->cq_flag = 0;
            cqp->cp_private=(mblk_t *)GETADDR;  /*nxt st*/
            qreply(q, mp);
            break;

The final M_IOCDATA message arrives from the stream head. cp_private contains GETADDR. The ad_addr data is contained in the b_cont link of the mblk. If the address is successfully processed by xx_set_addr() (not shown here), the message is acknowledged with an M_IOCACK message. If xx_set_addr() fails, the message is rejected with an M_IOCNAK message,xx_set_addr() processes the user address from the ioctl(2).

After the final M_IOCDATA message is processed, the module acknowledges the ioctl(2) to let the stream head know that processing is complete. This is done by sending an M_IOCACK message upstream if the request was successfully processed. Always set ioc_error to zero, otherwise an error code could be passed to the user application. Set ioc_rval and ioc_count to zero to reflect that a return value of 0 and no data is to be passed upstream. If the request cannot be processed, either an M_IOCNAK or M_IOCACK can be sent upstream with an appropriate error number. When sending an M_IOCNAK or M_IOCACK, freeing the linked M_DATA block is not mandatory. It is more efficient to use the stream head handle to free the linked M_DATA block.

If ioc_error is set in an M_IOCNAK or M_IOCNACK message, this error code will be returned to the user. If no error code is set in an M_IOCNAK message, EINVAL will be returned to the user.

                case GETADDR:            /* user address is here */
                        /* hypothetical routine */
                        if (xx_set_addr(mp->b_cont) == FAILURE) {
                            mp->b_datap->db_type = M_IOCNAK;
                            iocbp->ioc_error = EIO;
                        } else {
                            mp->b_datap->db_type=M_IOCACK;/*success*/
                            /* can have been overwritten */
                            iocbp->ioc_error = 0;
                            iocbp->ioc_count = 0;
                            iocbp->ioc_rval = 0;
                        }
                        mp->b_wptr=mp->b_rptr + sizeof (struct ioclk);
                        freemsg(mp->b_cont);
                        mp->b_cont = NULL;
                        qreply(q, mp);
                        break;

M_COPYOUT Example


Note - Please see the copyout section in Writing Device Drivers for information on the 64–bit data structure macros.


The following code excerpts return option values for the STREAMS device by placing them in the user's options structure. This is done by a transparent ioctl(2) call of the form

struct options optadd;

ioctl(fd, GET_OPTIONS,(caddr_t) &optadd) 

or by a nontransparent I_STR call

    struct strioctl opts_strioctl;
    structure options optadd;

    opts_strioctl.ic_cmd = GET_OPTIONS;
    opts_strioctl.ic_timeout = -1
    opts_strioctl.ic_len = sizeof (struct options);
    opts_strioctl.ic_dp = (char *)&optadd;
    ioctl(fd, I_STR, (caddr_t) &opts_strioctl) 

In the nontransparent I_STR case, opts_strioctl.ic_dp points to the options structure optadd.

Example 8-7 illustrates support of both the I_STR and transparent forms of ioctl(2). The transparent form requires a single M_COPYOUT message following receipt of the M_IOCTL to copy out the contents of the structure. xxwput() is the write-side put procedure of module or driver xx.

Example 8-10 M_COPYOUT

    struct options {            /* same members as in user space */
        int            op_one;
        int            op_two;
        short            op_three;
        long            op_four;
    };

    static int
    xxwput (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 struct address from
                                linked M_DATA block */
                          cqp->cq_addr = (caddr_t) 
                                *(caddr_t *)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);
                        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;
                        }
                        /* hypothetical routine */
                        xx_get_options(mp->b_cont);
                        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;
                /* M_IOCDATA not for us */
                if (csp->cmd != GET_OPTIONS) {
                    /*if module/pass on, if driver/free message*/
                    break;
                }
                if ( csp->cp_rval ) {
                    freemsg(mp);    /* failure */
                    return (0);
                }
                /* Data successfully copied out, ack */

                /* reuse M_IOCDATA for ack */
                mp->b_datap->db_type = M_IOCACK;
                mp->b_wptr = mp->b_rptr + sizeof(struct iocblk);
                /* can have been overwritten */
                iocbp->ioc_error = 0;
                iocbp->ioc_count = 0;
                iocbp->ioc_rval = 0;
                qreply(q, mp);
                break;
                .
                .
                .
            } /* switch (mp->b_datap->db_type) */
            return (0);

xxwput() first checks whether the ioctl(2) command is transparent. If it is, the message is reused as an M_COPYOUT copy request message. The pointer to the receiving buffer is in the linked message and is copied into cq_addr. Because only a single copy out is being done, no state information needs to be stored in cq_private. The original linked message is freed, in case it isn't big enough to hold the request.

if (iocbp->ioc_count == TRANSPARENT) {
        transparent = 1;
        cqp = (struct copyreq *)mp->b_rptr;
        cqp->cq_size = sizeof(struct options);
        /* Get struct address from linked M_DATA block */
        cqp->cq_addr = (caddr_t) 
                                    *(caddr_t *)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);

As an optimization, the following code checks the size of the message for reuse:

mp->b_cont->b_datap->db_lim 
- mp->b_cont->b_datap->db_base >= sizeof (struct options)

Note - Hardening Information. After message reuse, make sure to retain the relation:

db_base <= b_rptr <= b_wptr <= db_lim

A new linked message is allocated to hold the option request. When using the transparent ioctl(2) M_COPYOUT command, data contained in the linked message is passed to the stream head. The stream head will copy the data to the user's address space and issue an M_IOCDATA in response to the M_COPYOUT message, which the module must acknowledge in an M_IOCACK message.

            /* hypothetical routine */
                xx_get_options(mp->b_cont);
                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);
                }

If the message is not transparent (is issued through an I_STR ioctl(2)), the data is sent with the M_IOCACK acknowledgement message and copied into the buffer specified by the strioctl data structure. ioc_error, ioc_count, and ioc_rval are cleared to prevent any stale data from being passed back to the stream head.

           /* reuse M_IOCDATA for ack */
                mp->b_datap->db_type = M_IOCACK;
                mp->b_wptr = mp->b_rptr + sizeof(struct iocblk);
                /* can have been overwritten */
                iocbp->ioc_error = 0;
                iocbp->ioc_count = 0;
                iocbp->ioc_rval = 0;
                qreply(q, mp);
                break;

Bidirectional Data Transfer Example

Example 8-11 illustrates bidirectional data transfer between the kernel and application during transparent ioctl(2) processing. It also shows how to use more complex state information.

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

    ioctl(fd, XX_IOCTL, (caddr_t) &addr_xxdata) 

Example 8-11 Bidirectional Data Transfer

struct xxdata {             /* same members in user space */
   int         x_inlen;     /* number of bytes copied in */
   caddr_t     x_inaddr;    /* buf addr of data copied in */
   int         x_outlen;    /* number of bytes copied out */
   caddr_t     x_outaddr;   /* buf addr of data copied out */
};
/* State information for ioctl processing */
struct state {
        int         st_state;    /* see below */
        struct xxdata        st_data;        /* see above */
};
/* state values */

#define GETSTRUC     0   /* get xxdata structure */
#define GETINDATA    1   /* get data from x_inaddr */
#define PUTOUTDATA   2   /* get response from M_COPYOUT */

static void xxioc(queue_t *q, mblk_t *mp);

static int
xxwput (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 XX_IOCTL:
                /* do non-transparent processing. (See I_STR ioctl
                 * processing discussed in previous section.)
                 */
                /*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 *)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 xxdata);
                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:
            xxioc(q, mp);   /*all M_IOCDATA processing here*/
            break;
            .
            .
            .
    } /* switch (mp->b_datap->db_type) */
}

Three pairs of messages are required following the M_IOCTL message:

  1. case GETSTRUCT copies the structure into the message buffer.

  2. case GETINDATA copies the user buffer into the message buffer.

  3. case PUTOUTDATA copies the second message buffer into the user buffer.

xxwput() is the write-side put procedure for module or driver xx. xxwput allocates a message block to contain the state structure and reuses the M_IOCTL to create an M_COPYIN message to read in the xxdata structure.

M_IOCDATA processing is done in xxioc() as shown in the following example:

Example 8-12 M_IOCDATA Processing

xxioc(                    /* M_IOCDATA processing */
    queue_t *q,
    mblk_t *mp)
{
    struct iocblk *iocbp;
    struct copyreq *cqp;
    struct copyresp *csp;
    struct state *stp;
    mblk_t *xx_indata();

    csp = (struct copyresp *)mp->b_rptr;
    iocbp = (struct iocblk *)mp->b_rptr;
    switch (csp->cp_cmd) {

    case XX_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:            /* xxdata structure copied in */
                                        /* save structure */

                stp->st_data =
                 *(struct xxdata *)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 = xx_indata(mp->b_cont))
                 == NULL) { /* hypothetical */
                            /* fail xx_indata */
                            mp->b_datap->db_type = M_IOCNAK;
                            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 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);
            /* can have been overwritten */
                iocbp->ioc_error = 0;
                iocbp->ioc_count = 0;
                iocbp->ioc_rval = 0;
                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);
                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 xxdata structure is copied into the module's state structure (pointed to by cp_private in the message) and the M_IOCDATA message is reused to create a second M_COPYIN message to read the user data.

At case GETINDATA, the input user data is processed by xx_indata (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 acknowledgement 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.

I_LIST ioctl(2)Example

The I_LIST ioctl(2) lists the drivers and module in a stream.

Example 8-13 List a Stream's Drivers and Modules

#include <stdio.h>
#include <string.h>
#include <stropts.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/socket.h>

main(int argc, const char **argv)
{
        int               s, i;
        int               mods;
        struct str_list   mod_list;
        struct str_mlist *mlist;

        /* Get a socket... */
        if((s = socket(AF_INET, SOCK_STREAM, 0)) <= 0) {
            perror("socket: ");
            exit(1);
        }

        /* Determine the number of modules in the stream. */
        if((mods = ioctl(s, I_LIST, 0)) < 0){
            perror("I_LIST ioctl");
        }
        if(mods == 0) {
            printf("No modules\n");
            exit(1);
        } else {
            printf("%d modules\n", mods);
        }
        /* Allocate memory for all of the module names... */
        mlist = (struct str_mlist *) calloc(mods, sizeof(struct str_mlist));
        if(mlist == 0){
            perror("malloc failure");
            exit(1);
        }
        mod_list.sl_modlist = mlist;
        mod_list.sl_nmods = mods;

        /* Do the ioctl and get the module names. */
        if(ioctl(s, I_LIST, &mod_list) < 0){
            perror("I_LIST ioctl fetch");
            exit(1);
        }

        /* Print out the name of the modules */
        for(i = 0; i < mods; i++) {
            printf("s: %s\n", mod_list.sl_modlist[i].l_name);
        }

        free(mlist);

        exit(0);
}