Skip Navigation Links | |
Exit Print View | |
Writing Device Drivers Oracle Solaris 11.1 Information Library |
Part I Designing Device Drivers for the Oracle Solaris Platform
1. Overview of Oracle Solaris Device Drivers
2. Oracle Solaris Kernel and Device Tree
5. Managing Events and Queueing Tasks
7. Device Access: Programmed I/O
DMA Software Components: Handles, Windows, and Cookies
Performing Bus-Master DMA Transfers
Performing First-Party DMA Transfers
Performing Third-Party DMA Transfers
Determining Maximum Burst Sizes
Allocating Private DMA Buffers
Handling Resource Allocation Failures
10. Mapping Device and Kernel Memory
13. Hardening Oracle Solaris Drivers
14. Layered Driver Interface (LDI)
Part II Designing Specific Kinds of Device Drivers
15. Drivers for Character Devices
18. SCSI Host Bus Adapter Drivers
19. Drivers for Network Devices
Part III Building a Device Driver
22. Compiling, Loading, Packaging, and Testing Drivers
23. Debugging, Testing, and Tuning Device Drivers
24. Recommended Coding Practices
B. Summary of Oracle Solaris DDI/DKI Services
C. Making a Device Driver 64-Bit Ready
If an object does not fit within the limitations of the DMA engine, the transfer must be broken into a series of smaller transfers. The driver can break up the transfer itself. Alternatively, the driver can allow the system to 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, because the system can manage the resources more effectively than the driver can manage the resources.
A DMA window has two attributes. The offset attribute is measured from the beginning of the object. The length attribute is the number of bytes of memory to be allocated. After a partial allocation, only a range of length bytes that starts at offset has allocated resources.
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, in which case DDI_DMA_MAPPED is returned. The driver should check the return value to determine whether DMA windows are in use. See the following example.
Example 9-7 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 (DDI_DMA_CALLBACK_RUNOUT); } 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 (DDI_DMA_CALLBACK_RUNOUT); 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 (DDI_DMA_CALLBACK_DONE); }
Two functions operate 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 within the object, that is, reallocation of system resources. The ddi_dma_getwin() function shifts the current window to a new window within the object. Because ddi_dma_getwin() reallocates system resources to the new window, the previous window becomes invalid.
Caution - Do not move the DMA windows with a call to ddi_dma_getwin() before transfers into the current window are complete. Wait until the transfer to the current window is complete, which is when the interrupt arrives. Then call ddi_dma_getwin() to avoid data corruption. |
The ddi_dma_getwin() function is normally called from an interrupt routine, as shown in Example 9-8. 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 whether the device completes the transfer successfully. If not, normal error recovery occurs. If the transfer is successful, the routine must determine whether the logical transfer is complete. A complete transfer includes the entire object as specified by the buf(9S) structure. In a partial transfer, only one DMA window is moved. In a partial transfer, the interrupt routine 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. If necessary, the interrupt routine starts a transfer. Otherwise, the routine returns without invoking another DMA transfer. The following example illustrates the usual flow control.
Example 9-8 Interrupt Handler Using DMA Windows
static uint_t xxintr(caddr_t arg) { struct xxstate *xsp = (struct xxstate *)arg; uint8_t status; volatile uint8_t 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); }