Direct Memory Access (DMA) Implementation in Linux Device Drivers

DMA enables peripherals to transfer data directly between memory and hardware registers without CPU intervention. In Linux, DMA operations replace CPU-driven data writes to device registers, such as SPI_DATA for SPI communication, shifting from writel(data, SPI_DATA) to automated buffer transfers.

Key considerations include address handling: CPUs use virtual addresses, while DMA operates on physical addresses. Essentially, DMA controllers move data between specified physical memory locations.

Implementing DMA in Linux involves these primary steps:

DMA Initialization

dma_cap_mask_t capabilities;
dma_cap_zero(capabilities);
dma_cap_set(DMA_SLAVE, capabilities);

struct dma_chan *rx_channel = dma_request_channel(capabilities, filter_func, &params);

The dma_request_channel() function allocates a DMA channel, using a filter callback to match the requesting device's DMA signals based on bus registrasion data.

void *buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
struct scatterlist sg;
sg_init_one(&sg, buffer, BUFFER_SIZE);

Memory allocation for DMA data buffers is followed by scatterlist initialization to manage virtual-to-physical address mappings.

DMA Trensfer Execution

struct dma_async_tx_descriptor *descriptor;
struct dma_chan *channel;
struct dma_slave_config configuration;

channel = controller->tx_channel;

configuration.direction = DMA_MEM_TO_DEV;
configuration.dst_addr = controller->hw_addr;
configuration.dst_maxburst = MAX_BURST_16;
configuration.dst_addr_width = DMA_SLAVE_WIDTH_16BIT;

channel->device->device_control(channel, DMA_SLAVE_CONFIG, (unsigned long)&configuration);

controller->transfer_length = data_len;
dma_map_sg(device, &sg, 1, DMA_TO_DEVICE);

descriptor = channel->device->device_prep_slave_sg(channel,
                                                   &sg,
                                                   1,
                                                   DMA_MEM_TO_DEV,
                                                   DMA_INTERRUPT | DMA_NO_DEST_UNMAP);
descriptor->callback = transfer_complete;
descriptor->callback_param = &callback_data;

dmaengine_submit(descriptor);
dma_async_issue_pending(channel);

Configuration specifies transfer direction, destination address, burst size, and data width. Address mapping is estalbished via dma_map_sg(), followed by descriptor preparation with completion callback registration. The transfer initiates with dma_async_issue_pending().

Tags: linux-kernel device-drivers DMA embedded-systems kernel-modules

Posted on Sun, 12 Jul 2026 16:47:00 +0000 by chet23