Chapter 12. Direct Memory Access

image with no caption

Direct Memory Access (DMA) is a feature of modern processors that lets a device transfer data to and from main memory independently of the CPU. With DMA, the CPU merely initiates the data transfer (that is to say, it does not complete it), and then the device (or a separate DMA controller) actually moves the data. Because of this, DMA tends to provide higher system performance as the CPU is free to perform other tasks during the data transfer.

Note

There is some overhead in performing DMA. Accordingly, only devices that move large amounts of data (for example, storage devices) use DMA. You wouldn’t use DMA just to transfer one or two bytes of data.

Implementing DMA

Unlike with previous topics, I’m going to take a holistic approach here. Namely, I’m going to show an example first, and then I’ll describe the DMA family of functions.

The following pseudocode is a device_attach routine for a fictitious device that uses DMA.

static int
foo_attach(device_t dev)
{
        struct foo_softc *sc = device_get_softc(dev);
        int error;

        bzero(sc, sizeof(*sc));

        if (bus_dma_tag_create(bus_get_dma_tag(dev),  /* parent       */
                               1,                       /* alignment    */
                               0,                       /* boundary     */
                               BUS_SPACE_MAXADDR,       /* lowaddr      */
                               BUS_SPACE_MAXADDR,       /* highaddr     */
                               NULL,                    /* filter       */
                               NULL,                    /* filterarg    */
                               BUS_SPACE_MAXSIZE_32BIT, /* maxsize      */
                               BUS_SPACE_UNRESTRICTED,  /* nsegments    */
                               BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize   */
                               0,                       /* flags        */
                               NULL,                    /* lockfunc     */
                               NULL,                    /* lockfuncarg  */
                             &sc->foo_parent_dma_tag)) {
                device_printf(dev, "Cannot allocate parent DMA tag!
");
                return (ENOMEM);
        }

        if (bus_dma_tag_create(sc->foo_parent_dma_tag,/* parent       */
                               1,                       /* alignment    */
                               0,                       /* boundary     */
                               BUS_SPACE_MAXADDR,       /* lowaddr      */
                               BUS_SPACE_MAXADDR,       /* highaddr     */
                               NULL,                    /* filter       */
                               NULL,                    /* filterarg    */
                               MAX_BAZ_SIZE,            /* maxsize      */
                               MAX_BAZ_SCATTER,         /* nsegments    */
                               BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize   */
                               0,                       /* flags        */
                               NULL,                    /* lockfunc     */
                               NULL,                    /* lockfuncarg  */
                             &sc->foo_baz_dma_tag)) {
                device_printf(dev, "Cannot allocate baz DMA tag!
");
                return (ENOMEM);
        }

        if (bus_dmamap_create(sc->foo_baz_dma_tag,      /* DMA tag      */
                              0,                        /* flags        */
                              &sc->foo_baz_dma_map)) {
                device_printf(dev, "Cannot allocate baz DMA map!
");
                return (ENOMEM);
        }

        bzero(sc->foo_baz_buf, BAZ_BUF_SIZE);

        error = bus_dmamap_load(sc->foo_baz_dma_tag,  /* DMA tag      */
                              sc->foo_baz_dma_map,    /* DMA map      */
                              sc->foo_baz_buf,        /* buffer       */
                                BAZ_BUF_SIZE,           /* buffersize   */
                              foo_callback,           /* callback     */
                                &sc->foo_baz_busaddr,   /* callbackarg  */
                                BUS_DMA_NOWAIT);        /* flags        */
        if (error || sc->foo_baz_busaddr == 0) {
                device_printf(dev, "Cannot map baz DMA memory!
");
                return (ENOMEM);
        }

...
}

This pseudocode begins by calling bus_dma_tag_create to create a DMA tag named foo_parent_dma_tag. At heart, DMA tags describe the characteristics and restrictions of DMA transactions.

Next, bus_dma_tag_create is called again. Notice that foo_parent_dma_tag is this call’s first argument. See, DMA tags can inherit the characteristics and restrictions of other tags. Of course, child tags cannot loosen the restrictions set up by their parents. Consequently, the DMA tag foo_baz_dma_tag is a “draconian” version of foo_parent_dma_tag.

The next statement, bus_dmamap_create, creates a DMA map named foo_baz_dma_map. Loosely speaking, DMA maps represent memory areas that have been allocated according to the properties of a DMA tag and are within device visible address space.

Finally, bus_dmamap_load loads the buffer foo_baz_buf into the device visible address associated with the DMA map foo_baz_dma_map.

Note

Any arbitrary buffer can be used for DMA. However, buffers are inaccessible to devices until they’ve been loaded (or mapped) into a device visible address (that is, a DMA map).

Note that bus_dmamap_load requires a callback function, which typically looks something like this:

static void
 foo_callback(void *arg, bus_dma_segment_t *segs, int nseg, int error)
{
        if (error)
               return;

        *(bus_addr_t *)arg = segs[0].ds_addr;
}

Here, arg dereferences to the sixth argument passed to bus_dmamap_load, which was foo_baz_busaddr.

This callback function executes after the buffer-load operation completes. If successful, the address where the buffer was loaded is returned in arg. If unsuccessful, foo_callback does nothing.

Initiating a DMA Data Transfer

Assuming the buffer-load operation completed successfully, one can initiate a DMA data transfer with something like this:

Note

Most devices just require the device visible address of a buffer to be written to a specific register to start a DMA data transfer.

bus_write_4(sc->foo_io_resource, FOO_BAZ, sc->foo_baz_busaddr);

Here, the device visible address of a buffer is written to a device register. Recall that the foo_callback function described in the previous section returns in foo_baz_busaddr the device visible address of foo_baz_buf.

Dismantling DMA

Now that you know how to implement DMA, I’ll demonstrate how to dismantle it.

static int
foo_detach(device_t dev)
{
        struct foo_softc *sc = device_get_softc(dev);

        if (sc->foo_baz_busaddr != 0)
                bus_dmamap_unload(sc->foo_baz_dma_tag, sc->foo_baz_dma_map);

        if (sc->foo_baz_dma_map != NULL)
                bus_dmamap_destroy(sc->foo_baz_dma_tag, sc->foo_baz_dma_map);

        if (sc->foo_baz_dma_tag != NULL)
                bus_dma_tag_destroy(sc->foo_baz_dma_tag);

        if (sc->foo_parent_dma_tag != NULL)
                bus_dma_tag_destroy(sc->foo_parent_dma_tag);

...
}

As you can see, this pseudocode simply tears down everything in the opposite order that it was built up.

Now, let’s discuss in detail the different functions encountered here and in the previous two sections.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.17.91