STREAMS Programming Guide

Exit Print View

Updated: July 2014
 
 

Cloning STREAMS Drivers

To eliminate polling, STREAMS drivers can be made clonable. If a STREAMS driver is implemented as a clonable device, a single node in the file system can be opened to access any unused device that the driver controls. This special node guarantees that each user is allocated a separate stream to the driver for each open call. Each stream is associated with an unused minor device, so the total number of streams that may be connected to a particular clonable driver is limited only by the number of minor devices configured for that driver.

In previous examples, each user process connected a stream to a driver by explicitly opening a particular minor device of the driver. Each minor device had its own node in the device tree file system. Often, there is a need for a user process to connect a new stream to a driver regardless of which minor device is used to access the driver. In the past, this forced the user process to poll the various minor device nodes of the driver for an available minor device.

The clone model is useful, for example, in a networking environment where a protocol pseudo-device driver requires each user to open a separate stream over which it establishes communication. (The decision to implement a STREAMS driver as a clonable device is made by the designers of the device driver. Knowledge of the clone driver implementation is not required to use it.)

There are two ways to open as a clone device. The first is to use the STREAMS framework-provided clone device, which arranges to open the device with the CLONEOPEN flag passed in. This method is demonstrated in Example 9–7 , which shows the attach and open routines for the pseudo-terminal master ptm(7D) driver. The second way is to have the driver open itself as a clone device, without intervention from the system clone device. This method is demonstrated in the attach and open routines for the log(7D) device in Example 9–8 .

The ptm(7D) device, which uses the system-provided clone device, sets up two nodes in the device file system. One has a major number of 23 (ptm's assigned major number) and a minor number of 0. The other node has a major number of 11 (the clone device's assigned major number) and a minor number of 23 (ptm's assigned major number). The driver's attach routine (see Example 9–7 ) calls to ddi_create_minor_node(9F) twice. First, to set up the “normal” node (major number 23); second, to specify CLONE_DEV as the last parameter, making the system create the node with major 11.

crw-rw-rw-   1 sys       11, 23 Mar  6 02:05 clone:ptmx
crw-------   1 sys       23,  0 Mar  6 02:05 ptm:ptmajor

When the special file /devices/pseudo/clone@0:ptmx is opened, the clone driver code in the kernel (accessed by major 11) passes the CLONEOPEN flag in the sflag parameter to the ptm(7D) open routine. ptm's open routine checks sflag to make sure it is being called by the clone driver. The open routine next attempts to find an unused minor device for the open by searching its table of minor devices. (PT_ENTER_WRITE and PT_EXIT_WRITE are driver-defined macros for entering and exiting the driver's mutex.) If it succeeds (and following other open processing), the open routine constructs a new dev_t with the new minor number, which it passes back to its caller in the devp parameter. (The new minor number is available to the user program that opened the clonable device through an fstat(2) call.)

Example 9-7  Opening a System Clone Device
static int
ptm_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
	  if (cmd != DDI_ATTACH)
			return (DDI_FAILURE);

	  if (ddi_create_minor_node(devi, "ptmajor", S_IFCHR, 0, NULL, 0) 
				== DDI_FAILURE) {
			ddi_remove_minor_node(devi, NULL);
			return (DDI_FAILURE);
	  }
	  if (ddi_create_minor_node(devi, "ptmx", S_IFCHR, 0, NULL, CLONE_DEV) 
				== DDI_FAILURE) {
			ddi_remove_minor_node(devi, NULL);
			return (DDI_FAILURE);
	  }
	  ptm_dip = devi;
	  return (DDI_SUCCESS);
}

static int
ptmopen(
		queue_t	*rqp,		/* pointer to the read side queue */
		dev_t	*devp,		/* pointer to stream tail's dev */
		int		oflag,		/* the user open(2) supplied flags */
		int		sflag,		/* open state flag */
		cred_t	*credp)	/* credentials */
{
		struct pt_ttys	*ptmp;
		mblk_t	*mop;		/* ptr to a setopts message block */
		minor_t	dev;

		if (sflag != CLONEOPEN) {
			return (EINVAL);
		}

		for (dev = 0; dev < pt_cnt; dev++) {
			ptmp = &ptms_tty[dev];
			PT_ENTER_WRITE(ptmp);
			if (ptmp->pt_state & (PTMOPEN | PTSOPEN | PTLOCK)) {
				PT_EXIT_WRITE(ptmp);
			} else
				break;
		}

		if (dev >= pt_cnt) {
			return (ENODEV);
		}

		... <other open processing> ...

		/*
		 * The input, devp, is a major device number, the output is put into
		 * into the same parm as a major,minor pair.
		 */
		*devp = makedevice(getmajor(*devp), dev);
		return (0);
}

The log(7D) driver uses the second method; it clones itself without intervention from the system clone device. The log(7D) driver's attach routine (in Example 9–8) is similar to the one in ptm(7D). It creates two nodes using ddi_create_minor_node(9F), but neither specifies CLONE_DEV as the last parameter. Instead, one of the devices has minor 0, the other minor CLONEMIN. These two devices provide log(7D) two interfaces: the first write-only, the second read-write (see the man page log(7D) for more information). Users open one node or the other. If they open the CONSWMIN (clonable, read-write) node, the open routine checks its table of minor devices for an unused device. If it is successful, it (like the ptm(7D) open routine) returns the new dev_t to its caller in devp.

Example 9-8  Opening the log Driver
static int
log_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
		if (ddi_create_minor_node(devi, "conslog", S_IFCHR, 0, NULL, NULL)
				 == DDI_FAILURE ||
				 ddi_create_minor_node(devi, "log", S_IFCHR, 5, NULL, NULL) 
				 == DDI_FAILURE) {
			ddi_remove_minor_node(devi, NULL);
			return (-1);
		}
		log_dip = devi;
		return (DDI_SUCCESS);
}

static int
logopen(
		queue_t *q,
		dev_t *devp,
		int flag,
		int sflag,
		cred_t *cr)
{
		int i;
		struct log *lp;

		/*
		 * A MODOPEN is invalid and so is a CLONEOPEN.
		 * This is because a clone open comes in as a CLONEMIN device open!!
		 */
		if (sflag)
			return (ENXIO);

		mutex_enter(&log_lock);
		switch (getminor(*devp)) {

		case CONSWMIN:
			if (flag & FREAD) { /* you can only write to this minor */
				mutex_exit(&log_lock);
				return (EINVAL);
			}
			if (q->q_ptr) { /* already open */
				mutex_exit(&log_lock);
				return (0);
			}
			lp = &log_log[CONSWMIN];
			break;

		case CLONEMIN:
			/*
			 * Find an unused minor > CLONEMIN.
			 */
				i = CLONEMIN + 1;
			for (lp = &log_log[i]; i < log_cnt; i++, lp++) {
				if (!(lp->log_state & LOGOPEN))
					break;
			}
			if (i >= log_cnt) {
				mutex_exit(&log_lock);
				return (ENXIO);
			}
			*devp = makedevice(getmajor(*devp), i); /* clone it */
			break;

		default:
			mutex_exit(&log_lock);
			return (ENXIO);
		}

		/*
		 * Finish device initialization.
		 */
		lp->log_state = LOGOPEN;
		lp->log_rdq = q;
		q->q_ptr = (void *)lp;
		WR(q)->q_ptr = (void *)lp;
		mutex_exit(&log_lock);
		qprocson(q);
		return (0);
}