|
Private DMA buffers may be used by device drivers to perform DMA to or
from a device, in addition to doing transfers requested by user threads
and the kernel. Examples of this are the setting up of shared memory for
communication with the device or the allocation of intermediate transfer
buffers. Access can be in non-sequential fashion (consistent access) or
sequential, unidirectional, block-sized / block-aligned access (streamingaccess).
The kernel function ddi_dma_mem_alloc(9F) should be used for
allocating private DMA buffers (Ref: Writing Devices Drivers, "Direct
Memory Access (DMA)" section ). However when using this function
in conjunction with the ddi_dma_alloc_handle(9F) and
ddi_dma_addr_bind_handle(9F)
a number of points should be noted with regard to the values of the ddi_dma_attr(9S)
structure, and the size / alignment of the requested memory.
Requests for physically contiguous memory
On x86 systems it is normally the case that driver private DMA buffers
be physically contiguous. To ensure this the driver writer must specify
the scatter / gather list length ("dma_attr_seg") as 1, in the
ddi_dma_attr(9S)
structure. In addition to this the driver writer must ensure that DDI_DMA_PARTIAL
is not enabled in the flags parameter in calls to ddi_dma_addr_bind_handle(9F).
ddi_dma_addr_bind_handle() fails with a DDI_DMA_TOOBIG
Error
A common reason for a DDI_DMA_TOOBIG error return from ddi_dma_addr_bind_handle(9F),
is because the request for DMA resources for an object is not a multiple
of the granularity specified in the ddi_dma_attr(9S)structure.
The man page for ddi_dma_attr(9S) - "dma_attr_granular"
has the following note:
"Note that all driver requests for DMA resources must be a
multiple of the granularity of the device transfer size"
If this memory is not used for block oriented transfers (streaming access),
then the simplest fix is just to set the granularity to 1. This results
in a ddi_dma_attr(9S) that resembles the following:
static ddi_dma_attr_t XXX_dma_attr_io = {
DMA_ATTR_V0,
/* version number */
(uint64_t) 0ULL,
/* address low */
(uint64_t)0xffffffffULL, /* address high */
(uint64_t) 0x00ffffffULL, /* counter max */
(uint64_t) 0x1ULL,
/* default byte alignment */
0x7f,
/* burst size */
1,
/* minimum xfer */
(uint64_t) 0x00FFFFFEULL, /* max xfer size */
(uint64_t) 0xFFFFFFFFULL, /* max segment size */
1,
/* scatter/gather list length*/
1,
/* granularity */
0
/* flag */
};
If the requested memory is to be used for streaming access then the driver
writer must ensure that the requested length passed to ddi_dma_mem_alloc(9F)is
a multiple of the ddi_dma_attr(9S) granularity.
Back to Top
Passing the wrong length field to ddi_dma_addr_bind_handle(9F)
ddi_dma_mem_alloc(9F) returns the actual size of of the allocated
memory object in the "real_length" parameter. Because of padding and alignment
requirements the actual size might be larger than the requested size.
ddi_dma_addr_bind_handle(9F)
requires that its "len" parameter be the actual length allocated by ddi_dma_mem_alloc(9F).
Requesting more than PAGESIZE with > 16 byte alignment
In Solaris 7 ddi_dma_mem_alloc(9F)fails if requested to allocate
more than 1 page of physically contiguous memory with a more strict alignment
than 16 bytes.
The work around is to do the alignment manually, i.e. make the request
specifying 1 byte alignment, but request mem size + alignment requirement,
then realign the returned buffer to the original alignment requirement
(see the sample code segment).
Sample code
The following sample code segment illustrates how to allocate some
physically contiguous driver private DMA memory used for random access
(consistent access) communication between a device and a driver.
/*
* DMA attribute for data transfer, specifying no scatter gather capability.
Use in conjunction
* with ~DMA_PARTIAL_MAP if you want to allocate 1 cookie of physically
contiguous memory.
*
* Note: Granularity is 1 so all requests for DMA resources will be
a multiple of the granularity.
*/
static ddi_dma_attr_t XXX_dma_attr_io = {
DMA_ATTR_V0,
/* version number */
(uint64_t) 0ULL,
/* address low */
#ifdef PAE
(uint64_t)0xFFFFFFFFFFFFFFFFULL, /* address high */
#else
(uint64_t)0xffffffffULL,
/* address high */
#endif
(uint64_t) 0x00ffffffULL,
/* counter max */
(uint64_t) 0x1ULL,
/* default byte alignment */
0x7f,
/* burst size */
1,
/* minimum xfer */
(uint64_t) 0x00FFFFFEULL,
/* max xfer size */
(uint64_t) 0xFFFFFFFFULL,
/* max segment size */
1,
/* scatter/gather list length*/
1,
/* granularity */
0
/* flag */
};
/* * Device access attribute
*/
static ddi_device_acc_attr_t XXX_dev_attr = {
DDI_DEVICE_ATTR_V0,
DDI_STRUCTURE_LE_ACC,
DDI_STRICTORDER_ACC,
};
XXX_initialize(dev_info_t *dip)
{
ddi_dma_attr_t
dma_attr;
ddi_dma_cookie_t dma_cookie;
ddi_dma_handle_t dma_handle;
ddi_acc_handle_t acc_handle;
void
*virt_memoryp;
dma_attr = XXX_dma_attr_io;
dma_attr.dma_attr_align = 0x20; /* memory alignment
requirement */
if ((virt_memoryp
= XXX_iopb_mem_alloc(dip, memSize, 0, 0,
&dma_cookie, &dma_handle, &acc_handle,
&dma_attr, &XXX_dev_attr)) == NULL) {
goto error_exit;
}
}
/*
* XXX_iopb_mem_alloc()
*
* dip A pointer to the device's dev_info structure.
*
* req_len The length in bytes of the desired allocation.
*
* min_len The minimum number of bytes that are acceptable
for the allocation.
*
* granularity The allocation may be reduced to min_len
in increments of this amount.
*
* dma_cookiep A pointer to a ddi_dma_cookie(9S)
structure. On successful return,
* dma_cookiep points to a dma cookie that contains the physical
address and length of
* the allocated storage.
*
* dma_handlep Pointer to the allocated DMA handle.
*
* acc_handlep Pointer to the allocated data access
handle.
*
* dma_attrp Pointer to a DMA attribute structure for
this device (see ddi_dma_attr(9S)).
* This includes among other things the alignment restrictions for the
requested location.
*
* dev_attrp Pointer to a device access attribute structure
of this device
* (see ddi_device_acc_attr(9S)).
*/
/* This routine will attempt to allocate req_len of private
DMA data, aligned to the requirements
* specified in the ddi_dma_attr(9S) pointed to by dma_attrp.
If successful it will return a
* virtual address pointer to the aligned allocated buffer. dma_cookiep
will point to a
* ddi_dma_cookie(9S)structure that contains the physical address
of the aligned allocation, and
* the actual amount of data allocated. The actual amount maybe larger
than the requested amount due
* to additional system padding and alignment requirements.
* If min_len is non zero the actual amount maybe smaller then
the requested amount (to a minimum
* of min_len) because the system was unable to satisfy
the requested allocation.
*/
static void *XXX_iopb_mem_alloc(dev_info_t
*dip,
uint_t
req_len,
uint_t
min_len,
uint_t
granularity,
ddi_dma_cookie_t
*dma_cookiep,
ddi_dma_handle_t
*dma_handlep,
ddi_acc_handle_t
*acc_handlep,
ddi_dma_attr_t
*dma_attrp,
ddi_device_acc_attr_t *dev_attrp)
{
ddi_dma_attr_t
loc_dma_attr;
void
*virt_memoryp;
uint_t
count;
size_t
real_len;
void
*ret = NULL;
uint_t
size, min;
uint32_t
align_mask;
int
align_workaround = 0;
loc_dma_attr = *dma_attrp;
align_mask = dma_attrp->dma_attr_align - 1;
size = req_len;
min = min_len;
/*
* Solaris 7 bug workaround.
* When we request more than PAGESIZE with > 16 byte alignment
* ddi_dma_mem_alloc(9F) fails on Solaris7.
*/
#ifdef Solaris7
if ((dma_attrp->dma_attr_align > 16) &&
(req_len > PAGESIZE)) {
size += dma_attrp->dma_attr_align;
min += dma_attrp->dma_attr_align;
loc_dma_attr.dma_attr_align
= 1;
}
#endif
if (ddi_dma_alloc_handle(dip, &loc_dma_attr,
DDI_DMA_DONTWAIT,
NULL, dma_handlep) != DDI_SUCCESS) {
cmn_err(CE_WARN, "dma
handle alloc fail");
goto exit;
}
while (ddi_dma_mem_alloc(*dma_handlep, size,
dev_attrp,
DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT,
NULL, (caddr_t *)&virt_memoryp,
&real_len, acc_handlep) != DDI_SUCCESS) {
size -= granularity;
if ((min_len == 0) ||
(min_len == req_len) || (size < min)) {
cmn_err(CE_WARN, "dma memory alloc fail");
ddi_dma_free_handle(dma_handlep);
goto exit;
}
}
if (ddi_dma_addr_bind_handle(*dma_handlep, NULL,
virt_memoryp,
real_len,
DDI_DMA_RDWR | DDI_DMA_CONSISTENT,
DDI_DMA_DONTWAIT, NULL, dma_cookiep,
&count) != DDI_DMA_MAPPED || count != 1) {
cmn_err(CE_WARN, "memory
addr bind fail");
ddi_dma_mem_free(acc_handlep);
ddi_dma_free_handle(dma_handlep);
goto exit;
}
/*
* If we are applying the > PAGESIZE and > 16 byte alignment work
* around, then we need to realign the allocated memory. Align the
* virtual memory address first. Then calculate the matching physical
*/
#ifdef Solaris7
if (align_workaround) {
caddr_t addr = virt_memoryp;
virt_memoryp = (caddr_t)(((uint32_t)(virt_memoryp)
+
align_mask) & ~align_mask);
#ifdef PAE
dma_cookiep->dmac_laddress
= (dma_cookiep->dmac_laddress +
((caddr_t)(virt_memoryp) - addr));
#else
dma_cookiep->dmac_address
= (dma_cookiep->dmac_address +
((caddr_t)(virt_memoryp) - addr));
#endif
dma_cookiep->dmac_size
-= dma_attrp->dma_attr_align;
}
#endif
ret = virt_memoryp;
exit:
return (ret);
}
static void
XXX_iopb_mem_free(ddi_dma_handle_t
*dma_handlep,
ddi_acc_handle_t *acc_handlep)
{
ddi_dma_unbind_handle(*dma_handlep);
ddi_dma_mem_free(acc_handlep);
ddi_dma_free_handle(dma_handlep);
}
Back to Top
|