Writing Device Drivers

DMA Windows

The system might be unable to allocate resources for a large object. If this occurs, the transfer must be broken into a series of smaller transfers. The driver can either do this itself, or it can let the system allocate resources for only part of the object, thereby creating a series of DMA windows. Allowing the system to allocate resources is the preferred solution, as the system can manage the resources more effectively than the driver.

A DMA window has attributes offset (from the beginning of the object) and length. After a partial allocation, only a range of length bytes starting at offset has resources allocated for it.

A DMA window is requested by specifying the DDI_DMA_PARTIAL flag as a parameter to ddi_dma_buf_bind_handle(9F) or ddi_dma_addr_bind_handle(9F). Both functions return DDI_DMA_PARTIAL_MAP if a window can be established. However, the system might allocate resources for the entire object (less overhead), in which case DDI_DMA_MAPPED is returned. The driver should check the return value (see Example 7-4) to determine whether DMA windows are in use.

State Structure

This section adds the following fields to the state structure. See "Software State Structure" for more information.

	int	partial;				/* DMA object partially mapped, use windows */
 	int nwin;					/* number of DMA windows for this object */
 	int windex;					/* index of the current active window */


Example 7-4 Setting Up DMA Windows

static int
xxstart (caddr_t arg)
{
		struct xxstate *xsp = (struct xxstate *)arg;
		struct device_reg *regp = xsp->reg;
		ddi_dma_cookie_t cookie;
		int	status;
		mutex_enter(&xsp->mu);
		if (xsp->busy) {
			/* transfer in progress */
			mutex_exit(&xsp->mu);
			return (0);
		}
		xsp->busy = 1;
		mutex_exit(&xsp->mu);
		if (transfer is a read) {
			flags = DDI_DMA_READ;
		} else {
			flags = DDI_DMA_WRITE;
		}
		flags |= DDI_DMA_PARTIAL;
		status = ddi_dma_buf_bind_handle(xsp->handle, xsp->bp,
			flags, xxstart, (caddr_t)xsp, &cookie, &ccount);
		if (status != DDI_DMA_MAPPED &&
			status != DDI_DMA_PARTIAL_MAP)
				return (0);
		if (status == DDI_DMA_PARTIAL_MAP) {
			ddi_dma_numwin(xsp->handle, &xsp->nwin);
			xsp->partial = 1;
			xsp->windex = 0;
		} else {
			xsp->partial = 0;
		}
		...
		program the DMA engine
		...
		return (1);
}

There are two functions operating with DMA windows. The first, ddi_dma_numwin(9F), returns the number of DMA windows for a particular DMA object. The other function, ddi_dma_getwin(9F), allows repositioning (reallocation of system resources) within the object. It shifts the current window to a new window within the object. Because ddi_dma_getwin(9F) reallocates system resources to the new window, the previous window becomes invalid.


Caution - Caution -

It is a severe error to call ddi_dma_getwin(9F) before transfers into the current window are complete.


ddi_dma_getwin(9F) is normally called from an interrupt routine; see Example 7-5. The first DMA transfer is initiated as a result of a call to the driver. Subsequent transfers are started from the interrupt routine.

The interrupt routine examines the status of the device to determine if the device completed the transfer successfully. If not, normal error recovery occurs. If the transfer was successful, the routine must determine if the logical transfer is complete (the entire transfer specified by the buf(9S) structure) or if this was only one DMA window. If it was only one window, it moves the window with ddi_dma_getwin(9F), retrieves a new cookie, and starts another DMA transfer.

If the logical request has been completed, the interrupt routine checks for pending requests and starts a transfer, if necessary. Otherwise, it returns without invoking another DMA transfer. Example 7-5 illustrates the usual flow control.


Example 7-5 Interrupt Handler Using DMA Windows

static u_int
xxintr(caddr_t arg)
{
		struct xxstate *xsp = (struct xxstate *)arg;
		uint8_t status, temp;
		mutex_enter(&xsp->mu);
		/* read status */
		status = ddi_get8(xsp->access_hdl, &xsp->regp->csr);
		if (!(status & INTERRUPTING)) {
			mutex_exit(&xsp->mu);
			return (DDI_INTR_UNCLAIMED);
		}
		ddi_put8(xsp->access_hdl,&xsp->regp->csr, CLEAR_INTERRUPT);
		/* for store buffers */
		temp = ddi_get8(xsp->access_hdl, &xsp->regp->csr);
		if (an error occurred during transfer) {
			bioerror(xsp->bp, EIO);
			xsp->partial = 0;
		} else {
			xsp->bp->b_resid -= amount transferred;
		}

		if (xsp->partial && (++xsp->windex < xsp->nwin)) {
			/* device still marked busy to protect state */
			mutex_exit(&xsp->mu);
			(void) ddi_dma_getwin(xsp->handle, xsp->windex,
					&offset, &len, &cookie, &ccount);
	     program the DMA engine with the new cookie(s)
			...
			return (DDI_INTR_CLAIMED);
		}
		ddi_dma_unbind_handle(xsp->handle);
		biodone(xsp->bp);
		xsp->busy = 0;
		xsp->partial = 0;
		mutex_exit(&xsp->mu);
		if (pending transfers) {
			(void) xxstart((caddr_t)xsp);
		}
		return (DDI_INTR_CLAIMED);
}