文章目录
使用者视角的标准四步
任何外设驱动使用 DMA 都是固定套路,后面读驱动就知道每个回调是被谁、在什么时机调进来的:
c
/* ① 申请通道(通常开机时做一次) */
chan = dma_request_chan(dev, "tx"); /* 根据设备树 dmas/dma-names 找到通道 */
/* ② 配置通道(告诉 DMA 外设 FIFO 地址、位宽等) */
struct dma_slave_config cfg = {
.direction = DMA_MEM_TO_DEV,
.dst_addr = i2s_fifo_phys_addr, /* 外设 FIFO 物理地址 */
.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
};
dmaengine_slave_config(chan, &cfg);
/* ③ 准备描述符(描述"搬什么")并提交 */
desc = dmaengine_prep_slave_sg(chan, sgl, sg_len, DMA_MEM_TO_DEV, flags);
desc->callback = my_done_callback; /* 搬完后通知我 */
cookie = dmaengine_submit(desc); /* 进入"已提交"队列,还没开始搬! */
/* ④ 发令枪:真正启动 */
dma_async_issue_pending(chan);
举个例子
c
static int guc_adc_dma_request(struct device *dev, struct iio_dev *indio_dev)
{
struct guc_adc *info = iio_priv(indio_dev);
struct dma_slave_config config;
int ret;
info->dma_chan = dma_request_chan(dev, "rx");
if (IS_ERR(info->dma_chan)) {
ret = PTR_ERR(info->dma_chan);
if (ret != -ENODEV)
return dev_err_probe(dev, ret, "DMA channel request failed with\n");
/* DMA is optional: fall back to IRQ mode */
info->dma_chan = NULL;
return 0;
}
info->rx_buf = dma_alloc_coherent(info->dma_chan->device->dev, GUC_DMA_BUFFER_SIZE,
&info->rx_dma_buf, GFP_KERNEL);
if (!info->rx_buf) {
ret = -ENOMEM;
goto err_release;
}
/* Configure DMA channel to read data register */
memset(&config, 0, sizeof(config));
config.src_addr = (dma_addr_t)info->phys_addr;
config.src_addr += GUC_CTRL_NOR_STS1;
config.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
config.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
config.src_msize = 64;
config.dst_msize = 64;
ret = dmaengine_slave_config(info->dma_chan, &config);
if (ret)
goto err_free;
return 0;
err_free:
dma_free_coherent(info->dma_chan->device->dev, GUC_DMA_BUFFER_SIZE, info->rx_buf,
info->rx_dma_buf);
err_release:
dma_release_channel(info->dma_chan);
return ret;
}
选择使用DMA的方法
c
① 申请通道 dma_request_chan(dev, "tx") ← 走 DTS dmas/dma-names
dma_request_chan_by_mask(&mask) ← 按能力找空闲通道(memcpy 用)
│
② 配置 dmaengine_slave_config(chan, &cfg) ← 外设 FIFO 地址/位宽/msize
│
③ 描述 dmaengine_prep_slave_single() ┐
dmaengine_prep_slave_sg() ├─ 四选一 ★
dmaengine_prep_dma_cyclic() │
dmaengine_prep_dma_memcpy() ┘
desc->callback = fn; ← 可选,完成通知
│
④ 提交启动 cookie = dmaengine_submit(desc) ← 仅入队,没开始搬
dma_async_issue_pending(chan) ← 发令枪
│
⑤ 收尾 dmaengine_tx_status() / 回调 / 轮询 ← 等完成
dmaengine_pause() / _resume()
dmaengine_terminate_sync() ← 中止
dma_release_channel(chan) ← 释放
mem_to_mem 和 外设的使用是不一样的
c
/* 路径 1:外设专用通道,走设备树 */
chan = dma_request_chan(dev, "rx");
↑ 内核查 DTS 的 dmas = <&axi_dmac 25>; dma-names = "rx";
再调驱动的 of_xlate(:1541 chan->hw_handshake_num = dma_spec->args[0])
→ 拿到的通道被绑定了特定握手号
/* 路径 2:任意空闲通道,按能力匹配(memcpy 专用) */
dma_cap_zero(mask);
dma_cap_set(DMA_MEMCPY, mask);
chan = dma_request_chan_by_mask(&mask);
↑ 不需要 DTS,因为 M2M 不用握手线
(dma_request_slave_channel() 已废弃,新代码别用。)
-
四种 prep 的差异:

客户端侧 4 个入口 驱动侧只实现 3 个回调 ───────────────── ──────────────────── prep_slave_single ─┐ ├──────> device_prep_slave_sg :892 prep_slave_sg ─────┘ prep_dma_cyclic ─────────> device_prep_dma_cyclic :811 prep_dma_memcpy ─────────> device_prep_dma_memcpy :976 -
完成通知的三种方式
c
/* A. 回调(最常用)------在 tasklet 上下文,不能睡眠 */
desc->callback = my_done;
desc->callback_param = ctx;
/* B. 轮询 cookie ------ 不要中断时用 */
status = dmaengine_tx_status(chan, cookie, &state);
/* DMA_COMPLETE / DMA_IN_PROGRESS / DMA_PAUSED;state.residue = 剩余字节 */
/* C. completion 同步等待 ------ 进程上下文 */
desc->callback = complete_fn; /* 回调里 complete(&done) */
dma_async_issue_pending(chan);
wait_for_completion_timeout(&done, HZ);