STREAMS Programming Guide

Extended STREAMS Buffers

Some hardware using the STREAMS mechanism supports memory-mapped I/O (see mmap(2)) which allows the sharing of buffers between users, the kernel, and the I/O card. Modules and drivers that need bit-aligned or page-aligned buffers should use extended STREAMS buffers by calling esballoc (also see ddi_umem_alloc).

If the hardware supports memory-mapped I/O, data received from the hardware is placed in the DARAM (dual-access RAM) section of the I/O card. Since DARAM is memory that is shared between the kernel and the I/O card, coordinated data transfer between the kernel and the I/O card is eliminated. Once in kernel space, the data buffer is manipulated as if it were a kernel resident buffer. Similarly, data sent downstream is placed in the DARAM and forwarded to the network.

In a typical network arrangement, data is received from the network by the I/O card. The controller reads the block of data into the card's internal buffer. It interrupts the host computer to notify that data have arrived. The STREAMS driver gives the controller the kernel address where the data block is to go and the number of bytes to transfer. After the controller has read the data into its buffer and verified the checksum, it copies the data into main memory to the address specified by the DMA (direct memory access) memory address. Once in the kernel space, the data is packaged into message blocks and processed in the usual manner.

When data is transmitted from a user process to the network, it is copied from the user space to the kernel space, packaged as a message block, and sent to the downstream driver. The driver interrupts the I/O card, signaling that data is ready to be transmitted to the network. The controller copies the data from the kernel space to the internal buffer on the I/O card, and from there it is placed on the network.

The STREAMS buffer allocation mechanism enables the allocation of message and data blocks to point directly to a client-supplied (non-STREAMS) buffer. Message and data blocks allocated this way are indistinguishable from the normal data blocks. The client-supplied buffers are processed as if they were normal STREAMS data buffers.

Drivers can attach non-STREAMS data buffers and also free them. This is done as follows:

freeb(9F) detects when a buffer is a client supplied, non-STREAMS buffer. If it is, freeb(9F) finds the free_rtn(9S) structure associated with the buffer. After calling the driver-dependent routine (defined in free_rtn(9S)) to free the buffer, freeb(9F) frees the message and data block.

The free routine should not reference any dynamically allocated data structures that are freed when the driver is closed, as messages can exist in a stream after the driver is closed. For example, when a stream is closed, the driver close routine is called and its private data structure can be deallocated. If the driver sends a message created by esballoc upstream, that message can still be on the stream head read queue. When the stream head read queue is flushed, the message is freed and a call is made to the driver's free routine after the driver has been closed.

The format of the free_rtn(9S) structure is as follows:


void (*free_func)();   /*driver dependent free routine*/
char *free_arg;        /* argument for free_rtn */

The structure has two fields: a pointer to a function and a location for any argument passed to the function. Instead of defining a specific number of arguments, free_arg is defined as a char *. This way, drivers can pass pointers to structures if more than one argument is needed.

The method by which free_func is called is implementation-specific. Do not assume that free_func is called directly from STREAMS utility routines like freeb(9F). The free_func function must not call another module's put procedure nor try to acquire a private module lock that can be held by another thread across a call to a STREAMS utility routine that could free a message block. Otherwise, lock recursion and deadlock could occur.

esballoc(9F), provides a common interface for allocating and initializing data blocks. It makes the allocation as transparent to the driver as possible and provides a way to modify the fields of the data block, since modification should only be performed by STREAMS. The driver calls this routine to attach its own data buffer to a newly allocated message and data block. If the routine successfully completes the allocation and assigns the buffer, it returns a pointer to the message block. The driver is responsible for supplying the arguments to esballoc(9F), a pointer to its data buffer, the size of the buffer, the priority of the data block, and a pointer to the free_rtn structure. All arguments should be non-NULL. See Appendix B, Kernel Utility Interface Summary, for a description of esballoc(9F).

esballoc(9F) Example

Example 8–5 (which will not compile) shows how extended buffers are managed in the multithreaded environment. The driver maintains a pool of special memory that is allocated by esballoc(9F). The allocator free routine uses the queue struct assigned to the driver or other queue private data, so the allocator and the close routine need to coordinate to ensure that no outstanding esballoc(9F) memory blocks remain after the close. The special memory blocks are of type ebm_t, the counter is ebm, and the mutex mp and the condition variable cvp are used to implement the coordination.


Example 8–5 esballoc Example

ebm_t *
special_new()
{
		mutex_enter(&mp);
		/*
		 * allocate some special memory
		 */
		esballoc();
		/*
		 * increment counter
		 */
		ebm++;
		mutex_exit(&mp);
}

void
special_free()
{
		mutex_enter(&mp);
		/*
		 * de-allocate some special memory
		 */
		freeb();
	
		/*
		 * decrement counter
		 */
		ebm--;
		if (ebm == 0)
			cv_broadcast(&cvp);
		mutex_exit(&mp);
}

open_close(q, .....)
	....
{
		/*
		 * do some stuff
		 */
		/*
		 * Time to decommission the special allocator.  Are there
		 * any outstanding allocations from it?
		 */
		mutex_enter(&mp);
		while (ebm > 0)
			cv_wait(&cvp, &mp);
	
		mutex_exit(&mp);
}


Caution – Caution –

The close routine must wait for all esballoc(9F) memory to be freed.