Sun Java Solaris Communities My SDN Account Join SDN
 
Documentation

Allocating Private DMA Buffers

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