Add a wrapper API for static DMA allocations to bus_dma(9).
Currently allocating a static DMA allocation (e.g. for a descriptor ring)
requires three steps: creating a tag, allocating memory, and loading the
memory to obtain the bus address. Freeing an allocation requires three
steps as well (map unload, memory free, and tag destroy). This results in
a lot of boilerplate code in many drivers. This wrapper API aims to
simplify the interface by using a single function for each call. Note that
the wrapper API only supports single-segment allocations. This is
sufficient for the majority of drivers. Drivers needing multiple segments
can still use the existing API.
First, a 'struct bus_dmamem' is added which contains all of the information
about a static DMA mapping including the tag, map, virtual address, and
bus (DMA) address.
Second, a 'struct bus_dmamem_args' structure is defined to hold optional
restrictions on a mapping such as address restrictions, alignment, and
boundary. This structure follows the model of the recently added
make_dev_args structure used with make_dev_s() so that it can be
extended in the future to add additional constraints while preserving the
ABI. I think it also makes the code more readable as it allows you to
say:
args.dma_alignment = 64;
instead of having to remember that the Nth argument to bus_dma_tag_create()
is alignment. Also, the argument structure is initialized to specify no
restrictions. The caller is only required to explicitly set fields to
enforce needed restrictions. A NULL args pointer can be passed to
bus_dma_mem_alloc() to indicate that no restrictions are needed beyond those
in the parent tag.
Third, bus_dma_mem_alloc() allocates memory for a static DMA allocation
and saves the relevant information in the caller-supplied
'struct bus_dmamem'.
Fourth, bus_dma_mem_free() frees the memory and resources (map and tag)
used by a static DMA allocation. Note that if the 'struct bus_dmamem' is
initialized to zero before allocation (which happens automatically if it
is embedded in a softc), then this function can be safely called
unconditionally.
Finally, a simple bus_dma_mem_sync() wrapper has been added around
bus_dma_map_sync() that uses the map and tag stored in a 'struct bus_dmamem'.
This diff includes sample conversions of the ndis(4) layer and the xl(4)
driver. Note that the code to allocate a ring is now much shorter and
the ring-specific restrictions are now more obvious in the code:
/*
- Now allocate a chunk of DMA-able memory for the DMA
- descriptor lists. All of our lists are allocated as a
- contiguous block of memory.
*/
bus_dma_mem_args_init(&args);
args.dma_alignment = 8;
args.dma_lowaddr = BUS_SPACE_MAXADDR_32BIT;
error = bus_dma_mem_alloc(bus_get_dma_tag(dev), XL_RX_LIST_SZ, 0, &args,
&sc->xl_ldata.xl_rx_ring);
if (error) {
device_printf(dev, "failed to allocate rx ring\n"); goto fail;
}
error = bus_dma_mem_alloc(bus_get_dma_tag(dev), XL_TX_LIST_SZ, 0, &args,
&sc->xl_ldata.xl_tx_ring);
if (error) {
device_printf(dev, "failed to allocate tx ring\n"); goto fail;
}
If this API is liked, we may want to implement an alternate version of
bus_dma_tag_create() that accepts an args structure of restrictions and
reimplement bus_dma_tag_create() in terms of that. I think it would yield
similar readability improvements in terms of only having to specify
restrictions that are used while also provide room for adding additional
constraints in the future (such as for NUMA). This would be a bit of a
different take than doing setter/getters on tags directly, but if attributes
are only needed by consumers during tag creation the args approach can work
fine. (Note that this is similar to what pthreads does with all of its
attr objects.)