STREAMS Programming Guide

Module and Driver ioctl(2)

STREAMS is a special type of character device driver that is different from the historical character input/output (I/O) mechanism. In this section, the phrases character I/O mechanism and I/O mechanism refer only to that part of the mechanism that existed before STREAMS.

The character I/O mechanism handles all ioctl(2) calls transparently. That is, the kernel expects all ioctl(2) to be handled by the device driver associated with the character special file on which the call is sent. All ioctl(2) calls are sent to the driver, which is expected to perform all validation and processing other than file descriptor validity checking. The operation of any specific ioctl(2) is dependent on the device driver. If the driver requires data to be transferred in from user space, it will use the kernel ddi_copyin function. It may also use ddi_copyout to transfer any data results to user space.

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

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

The second difference is the absence of user context in a module and driver when the information associated with the ioctl(2) is received. This prevents use of ddi_copyin(9F) or ddi_copyout(9F) 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(2), the process generating it may no longer be running.)

A third difference is that for the character I/O mechanism, all ioctl(2) 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 ioctl(2). That is, the ioctl(2) 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(2) calls. Most streamio(7I) ioctl(2) 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 </function> message, which includes the ioctl(2) arguments, and send the message downstream to be received and processed by a specific module or driver. The M_IOCTL </function> message is the initial message type that carries ioctl(2) information to modules. Other message types are used to complete the ioctl(2) processing in the Stream. In general, each module must uniquely recognize and act on specific M_IOCTL </function> messages.

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

General ioctl(2) Processing

STREAMS blocks a user process that issues an ioctl(2) and causes the Stream head to generate an M_IOCTL </function> message. The process remains blocked until one of the following occurs:

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

A module that receives an unrecognized M_IOCTL </function> message must pass it on unchanged. A driver that receives an unrecognized M_IOCTL must produce a negative acknowledgment.

The two STREAMS ioctl(2) mechanisms, I_STR and transparent, are described next. (Here, I_STR means the streamio(7I) I_STR command and implies the related STREAMS processing unless noted otherwise.) I_STR has a restricted format and restricted addressing for transferring ioctl(2)-related data between user and kernel space. It requires only a single pair of messages to complete ioctl(2) processing. The transparent mechanism is more general and has almost no restrictions on ioctl(2) 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.

This is a rather simplistic view. There is nothing preventing a given ioctl(2) from being issued either directly (transparent) or by means of I_STR. Furthermore, ioctl(2) issued through I_STR potentially can require further processing of the form typically associated with transparent ioctl(2).

I_STR ioctl(2) Processing

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

Transparent ioctl(2) Processing

The transparent STREAMS ioctl(2) mechanism allows application programs to perform module and driver control functions with ioctl(2) other than I_STR. It is intended to transparently support applications developed prior to the introduction of STREAMS. It alleviates the need to recode and recompile the user-level software to run over STREAMS files. More importantly, applications do not have to package their ioctl(2) requests into the form demanded by I_STR.

The mechanism extends the data transfer capability for STREAMS ioctl(2) calls beyond those provided in the I_STR form. Modules and drivers can transfer data between their kernel space and user space in any ioctl(2) that has a value of the command argument not defined in streamio(7I). These ioctl(2) are known as transparent ioctl(2) to differentiate them from the I_STR form. Existing applications that use non-STREAMS character devices require transparent processing for ioctl(2) if the device driver is converted to STREAMS. The ioctl(2) data can be in any format mutually understood by the user application and module.

The transparent mechanism also supports STREAMS applications that send ioctl(2) 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 and different user space buffers.

I_LIST ioctl(2)

The I_LIST ioctl(2) supports the strconf(1) and strchg(1) commands that are used to query or change the configuration of a Stream. Only root or an owner of a STREAMS device can alter the configuration of that Stream.

strchg(1) does the following:

strconf(1) does the following:

The I_LIST ioctl(2) (illustrated in Example 4-1) performs two functions. When the third argument of the ioctl(2) call is NULL


if ((mods = ioctl(s, I_LIST, 0)) < 0) {
, the return value of the call indicates the number of modules, plus 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(7I). The second function of the I_LIST ioctl(2) 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(2) through a structure str_list that is defined as:


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;		/*addr of buf for names*/
};

Here 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. The array is FMNAMESZ+1 so the extra byte can hold the NULL character at the end of the string. FMNAMESZ is defined by <sys/conf.h>.

The amount of space to allocate for module names is indicated by the number of modules in the STREAM. This is not completely reliable because another module might be pushed onto the Stream after the application invokes the I_LIST ioctl(2) with the NULL argument and before it invokes the I_LIST ioctl(2) with the structure argument.

The I_LIST call with arg pointing to the str_list structure returns the number of entries that have been filled into the sl_modlist array (the number represents the number of modules including the driver). If there is not enough space in the sl_modlist array (see note) 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.


Example 4-1 I_LIST ioctl(2)

#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;
		unsigned 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) {
			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 the calloc'd structures... */
		free(mlist);
		return(0);
}

Other ioctl(2) Commands

streamio(7I) details the following ioctl(2) commands.

Table 4-2 Other ioctl(2) Commands

ioctl(2)

Description 

I_FLUSH

Retrieves the name of the module just below the Stream head 

I_FLUSH

Flushes all input or output queues 

I_FLUSHBAND

Flushes a band of messages 

I_FIND

Compares the names of all modules present in the Stream to a specific name 

I_PEEK

Lets the user look at information in the first message on the Stream head read queue without taking the message off the queue  

I_STRDOPT

Sets the read mode using a series of flag options in the argument 

I_GRDOPT

Indicates the read mode in an int

I_NREAD

Counts the data bytes in data blocks in the first message on the Stream head read queue 

I_FDINSERT

Creates a message from a user buffer, adds information about another Stream, and sends the message downstream 

I_SWROPT

Sets the write mode using the value of the argument 

I_GWROPT

Returns the current write mode setting 

I_SENDFD

Requests that the Stream send a message with file pointer to the Stream head at the other end of a Stream pipe 

I_RECVFD

Retrieves the file descriptor of the message sent by an I_SENDFD ioctl(2) over a Stream pipe

I_ATMARK

Lets the application see if the current message on the Stream-head read queue is marked by a module downstream 

I_CKBAND

Checks if the message of a given priority band exists on the Stream-head read queue 

I_GETNBAND

Returns the priority band of the first message on the Stream-head read queue 

I_CANPUT

Checks if a certain band is writable 

I_SETCLTIME

Lets the user set the time the Stream head will delay when a Stream is closing if data exists on the write queues 

I_GETCLTIME

Returns the close time delay 

I_LINK

Connects two Streams 

I_UNLINK

Disconnects two Streams 

I_PLINK

Connects two Streams with a persistent link below a multiplexing driver 

I_PUNLINK

Disconnects two Streams that have been connected with a persistent link 

Message Direction

Various system calls let the user create messages and send them downstream and prioritize the messages.

Table 4-3 Send and Recieve Mesages

putmsg(2)

Creates a message from the caller supplied control and data buffers and sends the message downstream 

putpmsg(2)

Does the same as putmsg(2) and lets the caller specify a priority band for the message

getmsg(2)

Retrieves M_DATA, M_PROTO, or M_PCPROTO or high priority messages from the Stream head, and places the contents into two user buffers

getpmsg(2)

Does the same as getmsg(2) and lets the caller specify a priority band for the message

The Stream head guarantees that the control part of a message generated by putmsg(2) is at least 64 bytes long. This promotes reusability of the buffer. When the buffer is a reasonable size, modules and drivers may reuse the buffer for other headers.

stropts.h contains the specification of strbuf, which describes the control and data buffers.