Data messages are scanned and filtered. modwput copies the original message into new blocks, modifying as it copies. nbp points to the current new message block. nmp points to the new message being formed as multiple M_DATA message blocks. The outer for loop goes through each message block of the original message. The inner while loop goes through each byte. bappend is used to add characters to the current or new block. If bappend fails, the current new block is full. If nmp is NULL, nmp is pointed at the new block. If nmp is not NULL, the new block is linked to the end of nmp by use of linkb(9F).
At the end of the loops, the final new block is linked to nmp. The original message (all message blocks) is returned to the pool by freemsg(9F). If a new message exists, it is sent downstream.
Recovering From No Buffers
bufcall(9F) can be used to recover from an allocb(9F) failure. The call syntax is as follows:
bufcall_id_t bufcall(int size, int pri, void(*func)(), long arg); |
Note - qbufcall(9F) and qunbufcall(9F) must be used with perimeters.
bufcall(9F) calls (*func)(arg) when a buffer of size bytes is available. When func is called, it has no user context and must return without blocking. Also, there is no guarantee that when func is called, a buffer will still be available.
On success, bufcall returns a nonzero identifier that can be used as a parameter to unbufcall(9F) to cancel the request later. On failure, 0 is returned and the requested function is never called.
Caution - Care must be taken to avoid deadlock when holding resources while waiting for bufcall to call (*func(arg). bufcall should be used sparingly.
Read Device Interrupt Handler
Example 8-3 is an example of a read device interrupt handler.
Example 8-3 Device Interrupt Handler
See Chapter 12, Multithreaded STREAMS for more information on the uses of unbufcall(9F). These references to unbufcall are protected by MT locks.
Because bufcall(9F) can fail, there is still a chance that the device will hang. A better strategy if bufcall(9F) fails is to discard the current input message and resubmit that buffer to the device. Losing input data is preferable to the device hanging.
Write Service Procedure
Example 8-4 is an example of a write service procedure.
Example 8-4 Write Service Procedure
mod_wsrv prefixes each output message with a header.
In this example, mod_wsrv illustrates a potential deadlock case. If allocb(9F) fails, mod_wsrv tends to recover without loss of data and calls bufcall(9F). In this case, the routine passed to bufcall(9F) is qenable(9F). When a buffer is available, the service procedure is automatically re-enabled. Before exiting, the current message is put back in the queue. Example 8-4 deals with bufcall(9F) failure by calling timeout(9F).
timeout(9F) schedules the given function to be run with the given argument in the given number of clock cycles. In this example, if bufcall(9F) fails, the system runs qenable(9F) after two seconds have passed.
Releasing Callback Requests
When allocb(9F) fails and bufcall(9F) is called, a callback is pending until a buffer is actually returned. Because this callback is asynchronous, it must be released before all processing is complete. To release this queued event, use unbufcall(9F).
Pass the id returned by bufcall(9F) to unbufcall(9F). Then close the driver in the normal way. If this sequence of unbufcall(9F) and xxclose is not followed, the callback can occur when the driver is already closed. This is one of the most difficult types of problems to find and debug.
Caution - All bufcall(9F) and timeouts must be canceled in the close routine.
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:
Allocation - If the drivers use DARAM without using STREAMS resources and without depending on upstream modules or need to use privately allocated buffers, a data and message block can be allocated without an allocated data buffer. Use esballoc(9F). This returns a message block and data block without an associated STREAMS buffer. The buffer used is the one supplied by the caller in the calling sequence.
Freeing - Each driver using non-STREAMS resources in a STREAMS environment must manage those resources completely, including freeing them. To make this as transparent as possible, a driver-dependent routine is executed if freeb(9F) is called to free a message and data block with an attached non-STREAMS buffer.
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).