Linux DMA驱动详解(二)-----DMA的使用者

文章目录

使用者视角的标准四步

任何外设驱动使用 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);
相关推荐
caoerzhong1 小时前
JeeWMS 开源仓库管理系统 GPL-3.0 合规指南:Java WMS 二次开发前必须弄清的授权边界
java·开发语言·开源·vue
Sam_Deep_Thinking1 小时前
单一职责原则:JAVA LocalDate的设计取舍
java·后端·程序员·单一职责原则
杨杨杨大侠1 小时前
Java 不再需要 JVM?一文看懂 GraalVM Native Image、Substrate VM 与 JDK
java·jvm·java ee
Sylvia33.1 小时前
火星数据体育API|一站式接入足球篮球电竞等18+项目实时数据
java·开发语言·python·websocket·游戏
赵民勇1 小时前
systemd-socket-activate命令详解
linux·运维
李少兄1 小时前
解决 MySQL Lock wait timeout exceeded 报错
java
va学弟1 小时前
Java 语言特性:泛型(Generics)
java·泛型
跨境小彭2 小时前
Temu运营避坑:制造地点信息填写规范、后果及批量实操教程
大数据·运维·自动化·跨境电商·temu
Cx330❀2 小时前
【Linux网络】网络层 IP 协议:从网段划分到内核源码解析
大数据·linux·服务器·网络·tcp/ip·性能优化·langchain