Writing Device Drivers

DMA Handling

These interfaces allocate, synchronize and release DMA resources for devices capable of directly accessing system memory.

DMA resources are identified by a DMA handle. A DMA handle is created obeying the DMA attributes structure. It allows any constraints that the device's DMA controller may impose on DMA transfers to be specified, such as a limited transfer size and DMA address range.

A family of DMA setup functions are provided that make it easier to allocate DMA resources for use with kernel virtual addresses (ddi_dma_addr_bind_handle(9F), and buf(9S) structures ddi_dma_addr_bind_handle(9F)). The setup functions pass back a pointer to a DMA cookie, which countains I/O address and size information.

The DMA setup functions also provide a callback mechanism where a function can be specified to be called later if the requested mapping can't be set up immediately.

The DMA window functions allow resources to be allocated for a large object. The resources can be moved from one part of the object to another by moving the DMA window.

The DMA engine functions allow drivers to manipulate the system DMA engine, if there is one. These are currently used on x86 systems.

int ddi_dma_burstsizes(ddi_dma_handle_t handle);

ddi_dma_burstsizes(9F) returns an integer that encodes the allowed burst sizes for the DMA resources specified by handle. Allowed power of two burst sizes are bit-encoded in the return value. For a mapping that allows only 2-byte bursts, for example, the return value would be 0x2. For a mapping that allows 1-, 2-, 4-, and 8-byte bursts, the return value would be 0xf.

int ddi_dma_devalign(ddi_dma_handle_t handle, 
			u_int *alignment, u_int *minxfr);

ddi_dma_devalign(9F) passes back, in the location pointed to by alignment, the required alignment for the beginning of a DMA transfer using the resources identified by handle. The alignment will be a power of two. ddi_dma_devalign(9F) also passes back in the location pointed to by minxfr the minimum number of bytes of the mapping that will be read or written in a single transfer.

int ddi_dma_sync(ddi_dma_handle_t handle, off_t off, 
			size_t len, u_int type);

ddi_dma_sync(9F) ensures that any CPU and the device see the same data starting at off bytes into the DMA resources identified by handle and continuing for len bytes. type should be:

int   ddi_dmae_alloc(dev_info_t *dip, int chnl, 
			int (*dmae_waitfp)(), caddr_t arg);

ddi_dmae_alloc(9F) allocates a DMA channel from the system DMA engine. It must be called prior to any operation on a channel.

int ddi_dmae_release(dev_info_t *dip, int chnl);

ddi_dmae_release(9F) releases a previously allocated DMA channel.

int ddi_dmae_prog(dev_info_t *dip, struct ddi_dmae_req *dmaereqp, 
			ddi_dma_cookie_t *cookiep, int chnl);

The ddi_dmae_prog(9F) function programs the DMA channel for an operation. This function allows access to various capabilities of the DMA engine hardware. It disables the channel prior to setup, and enables the channel before returning.

The DMA address and count are specified by passing ddi_dmae_prog(9F) a cookie obtained from ddi_dma_buf_bind_handle(9F). Other DMA engine parameters are specified by the DMA engine request structure passed in through dmaereqp. The fields of that structure are documented in ddi_dmae_req(9S).

int ddi_dmae_disable(dev_info_t *dip, int chnl);

The ddi_dmae_disable(9F) function disables the DMA channel so that it no longer responds to a device's DMA service requests.

int ddi_dmae_enable(dev_info_t *dip, int chnl);

The ddi_dmae_enable(9F) function enables the DMA channel for operation. This may be used to re-enable the channel after a call to ddi_dmae_disable(9F). The channel is automatically enabled after successful programming by ddi_dmae_prog(9F).

int ddi_dmae_stop(dev_info_t *dip, int chnl);

The ddi_dmae_stop(9F) function disables the channel and terminates any active operation.

int ddi_dmae_getcnt(dev_info_t *dip, int chnl, int *countp);

The ddi_dmae_getcnt(9F) function examines the count register of the DMA channel and sets (*countp) to the number of bytes remaining to be transferred. The channel is assumed to be stopped.

int ddi_dmae_1stparty(dev_info_t *dip, int chnl);

The ddi_dmae_1stparty(9F) function is used, by device drivers using first-party DMA, to configure a channel in the system's DMA engine to operate in a ``slave'' mode.

int ddi_dmae_getattr(dev_info_t *dip, ddi_dma_attr_t *attrp);

The ddi_dmae_getattr(9F) function fills in the DMA attribute structure, pointed to by attrp, with the DMA attributes of the system DMA engine. This attribute structure must be passed to ddi_dma_alloc_handle(9F). If the device has any particular restrictions on transfer size or granularity (for example, a disk sector size), the driver should further restrict the values in the structure members before passing them to ddi_dma_alloc_handle(9F). The driver must not relax any of the restrictions embodied in the structure after it is filled in by ddi_dmae_getattr(9F).

int ddi_iomin(dev_info_t *dip, int initial, int streaming);

ddi_iomin(9F) returns an integer that encodes the required alignment and the minimum number of bytes that must be read or written by the DMA controller of the device identified by dip. ddi_iomin(9F) is like ddi_dma_devalign(9F), but the memory object is assumed to be primary memory, and the alignment is assumed to be equal to the minimum possible transfer.

int ddi_dma_alloc_handle(dev_info_t *dip,  	
			ddi_dma_attr_t *attr, int  (*callback)(caddr_t),  	
			caddr_t arg, ddi_dma_handle_t *handlep);

ddi_dma_alloc_handle(9F) allocates a new DMA handle. A DMA handle is an opaque object used as a reference to subsequently allocated DMA resources. ddi_dma_alloc_handle(9F) accepts as parameters the device information referred to by dip and the device's DMA attributes described by a ddi_dma_attr(9S) structure. A successful call to ddi_dma_alloc_handle(9F) fills in the value pointed to by handlep. A DMA handle must only be used by the device for which it was allocated and is valid only for one I/O transaction at a time.

If callback is set to DDI_DMA_DONTWAIT, then the caller does not care if the allocation fails, and can handle an allocation failure appropriately. If callback is set to DDI_DMA_SLEEP, then the caller needs to have the allocation routines wait for resources to become available. If any other value is set, and a DMA resource allocation fails, this value is assumed to be a function to call at a later time when resources may become available. When the specified function is called, it is passed arg as an argument. The specified callback function must return either DDI_DMA_CALLBACK_RUNOUT or DDI_DMA_CALLBACK_DONE.

DDI_DMA_CALLBACK_RUNOUT indicates that the callback routine attempted to allocate DMA resources but failed to do so, in which case the callback function is put back on a list to be called again later. DDI_DMA_CALLBACK_DONE indicates either success at allocating DMA resources or the driver no longer available.

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);

ddi_dma_mem_alloc(9F) allocates memory for DMA transfers to or from a device. The allocation will conform to the alignment, padding constraints, and device granularity as specified by the DMA attributes (see ddi_dma_attr(9S)) passed to ddi_dma_alloc_handle(9F) and the more restrictive attributes imposed by the system.

void ddi_dma_mem_free(ddi_acc_handle_t *handlep);

ddi_dma_mem_free(9F) deallocates the memory acquired by ddi_dma_mem_alloc(9F). In addition, it destroys the data access handle handlep associated with the memory.

void ddi_dma_free_handle(ddi_dma_handle_t *handle);

ddi_dma_free_handle(9F) destroys the DMA handle pointed to by the handle. Any further references to the DMA handle will have undefined results. Note that ddi_dma_unbind_handle(9F) must be called prior to ddi_dma_free_handle(9F) to free any resources the system may be caching on the handle.

int ddi_dma_addr_bind_handle(ddi_dma_handle_t handle,  	
			struct as *as, caddr_t addr, size_t len, uint_t flags, 
			int (*callback)(caddr_t), caddr_t arg, ddi_dma_cookie_t *cookiep,  
			uint_t *ccountp);

ddi_dma_addr_bind_handle(9F) allocates DMA resources for a memory object so that a device can perform DMA to or from the object. DMA resources are allocated with respect to the device's DMA attributes as expressed by ddi_dma_attr(9S) (see ddi_dma_alloc_handle(9F)).

ddi_dma_addr_bind_handle(9F) fills in the first DMA cookie pointed to by cookiep with the appropriate address, length, and bus type. *ccountp is set to the number of DMA cookies representing this DMA object. Subsequent DMA cookies must be retrieved by calling ddi_dma_nextcookie(9F) *countp - 1 times.

When a DMA transfer completes, the driver should free up system DMA resources by calling ddi_dma_unbind_handle(9F).

int ddi_dma_set_sbus64(ddi_dma_handle_t handle,  	
			uint_t burstsizes);

ddi_dma_set_sbus64(9F) informs the system that the device needs to perform 64-bit data transfers on the SBus. The driver must first allocate a DMA handle using ddi_dma_alloc_handle(9F) with a ddi_dma_attr(9S) structure describing the DMA attributes for a 32-bit transfer mode.

int ddi_dma_buf_bind_handle(ddi_dma_handle_t handle,  	
			struct buf *bp, uint_t flags, int (*callback)(caddr_t),caddr_t arg,  	
			ddi_dma_cookie_t *cookiep, uint_t *ccountp);

ddi_dma_buf_bind_handle(9F) allocates DMA resources for a system buffer such that a device can perform DMA to or from the buffer. DMA resources are allocated with respect to the device's DMA attributes as expressed by ddi_dma_attr(9S) (see ddi_dma_alloc_handle(9F)).

int ddi_dma_unbind_handle(ddi_dma_handle_t handle);

ddi_dma_unbind_handle(9F) frees all DMA resources associated with an existing DMA handle. When a DMA transfer is completed, the driver should call ddi_dma_unbind_handle(9F) to free system DMA resources established by a call to ddi_dma_buf_bind_handle(9F) or ddi_dma_addr_bind_handle(9F).

ddi_dma_unbind_handle(9F) does an implicit ddi_dma_sync(9F), making further synchronization steps unnecessary.

int ddi_dma_numwin(ddi_dma_handle_t handle, uint_t *nwinp);

ddi_dma_numwin(9F) returns the number of DMA windows for a DMA object if partial resource allocation was permitted.

int ddi_dma_getwin(ddi_dma_handle_t handle,  	
			uint_t win, off_t *offp, size_t *lenp,
 		ddi_dma_cookie_t *cookiep, uint_t *ccountp);

ddi_dma_getwin(9F) activates a new DMA window. If a DMA resource allocation request returns DDI_DMA_PARTIAL_MAP, indicating that resources for less than the entire object were allocated, the current DMA window can be changed by a call to ddi_dma_getwin(9F).

void ddi_dma_nextcookie(ddi_dma_handle_t handle,  	
			ddi_dma_cookie_t *cookiep);

ddi_dma_nextcookie(9F) retrieves subsequent DMA cookies for a DMA object. ddi_dma_nextcookie(9F) fills in the ddi_dma_cookie(9S) structure pointed to by cookiep. The ddi_dma_cookie(9S) structure must be allocated prior to calling ddi_dma_nextcookie(9F).