Writing Device Drivers

Allocating Private DMA Buffers

Some device drivers might need to allocate memory for DMA transfers in addition to performing transfers requested by user threads and the kernel. Some examples of allocating private DMA buffers are setting up shared memory for communication with the device and allocating intermediate transfer buffers. Use ddi_dma_mem_alloc(9F) to allocate memory for DMA transfers.

int ddi_dma_mem_alloc(ddi_dma_handle_t handle, size_t length,
    ddi_device_acc_attr_t *accattrp, uint_t flags,
    int (*waitfp)(caddr_t), caddr_t arg, caddr_t *kaddrp,
    size_t *real_length, ddi_acc_handle_t *handlep);

where:

handle

DMA handle

length

Length in bytes of the desired allocation

accattrp

Pointer to a device access attribute structure

flags

Data transfer mode flags. Possible values are DDI_DMA_CONSISTENT and DDI_DMA_STREAMING.

waitfp

Address of callback function for handling resource allocation failures. See the ddi_dma_alloc_handle(9F) man page.

arg

Argument to pass to the callback function

kaddrp

Pointer on a successful return that contains the address of the allocated storage

real_length

Length in bytes that was allocated

handlep

Pointer to a data access handle

The flags parameter should be set to DDI_DMA_CONSISTENT if the device accesses in a nonsequential fashion. Synchronization steps that use ddi_dma_sync(9F) should be as lightweight as possible due to frequent application to small objects. This type of access is commonly known as consistent access. Consistent access is particularly useful for I/O parameter blocks that are used for communication between a device and the driver.

On the x86 platform, allocation of DMA memory that is physically contiguous has these requirements:

The following example shows how to allocate IOPB memory and the necessary DMA resources to access this memory. DMA resources must still be allocated, and the DDI_DMA_CONSISTENT flag must be passed to the allocation function.


Example 9–3 Using ddi_dma_mem_alloc(9F)

if (ddi_dma_mem_alloc(xsp->iopb_handle, size, &accattr,
    DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &xsp->iopb_array,
    &real_length, &xsp->acchandle) != DDI_SUCCESS) {
    /* error handling */
    goto failure;
}
if (ddi_dma_addr_bind_handle(xsp->iopb_handle, NULL,
    xsp->iopb_array, real_length,
    DDI_DMA_READ | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP,
    NULL, &cookie, &count) != DDI_DMA_MAPPED) {
    /* error handling */
    ddi_dma_mem_free(&xsp->acchandle);
    goto failure;
}

The flags parameter should be set to DDI_DMA_STREAMING for memory transfers that are sequential, unidirectional, block-sized, and block-aligned. This type of access is commonly known as streaming access.

In some cases, an I/O transfer can be sped up by using an I/O cache. I/O cache transfers one cache line at a minimum. The ddi_dma_mem_alloc(9F) routine rounds size to a multiple of the cache line to avoid data corruption.

The ddi_dma_mem_alloc(9F) function returns the actual size of the allocated memory object. Because of padding and alignment requirements, the actual size might be larger than the requested size. The ddi_dma_addr_bind_handle(9F) function requires the actual length.

Use the ddi_dma_mem_free(9F) function to free the memory allocated by ddi_dma_mem_alloc(9F).


Note –

Drivers must ensure that buffers are aligned appropriately. Drivers for devices that have alignment requirements on down bound DMA buffers might need to copy the data into a driver intermediate buffer that meets the requirements, and then bind that intermediate buffer to the DMA handle for DMA. Use ddi_dma_mem_alloc(9F) to allocate the driver intermediate buffer. Always use ddi_dma_mem_alloc(9F) instead of kmem_alloc(9F) to allocate memory for the device to access.