iJoe's Blog
Published on

linux驱动学习-资源管理02-DMA

Authors

DMA

DMA与CPU对数据的搬运的区别在于,CPU直接搬运,由于设备存储到存储空间是很慢的,所以非常浪费cpu资源。而DMA相当于代替CPU的搬运工作。

而DMA不一定是内部物理模块,可能是外接的。它实际上也是在总线上调配数据。

DMA的地址

对于 DMA buffer 来说,CPU 和 DMA 最终操作的是同一份内存数据,但 CPU 可能通过 Cache 访问,而 DMA 通常直接从内存侧访问,因此可能产生一致性问题。

DMA Mapping 首先解决的是把 CPU 使用的内存转换成设备能够访问的 DMA 地址;在非硬件一致的平台上,它同时还要处理 CPU Cache 与设备访问内存之间的一致性问题。

一致性DMA和流式DMA

一致性 DMA 通常用于需要长期由 CPU 和设备共同访问的 DMA 内存。DMA API 保证 CPU 和设备能看到一致的数据视图,不需要像 streaming mapping 一样在每次所有权切换时显式做同步。dma_pool 主要用于大量较小、固定大小的 coherent DMA buffer,并不是“有多个 buffer 就必须用”。所以不一定是nocache。

流式 DMA 通常围绕一次或一段 DMA 使用周期进行 mapping,在 CPU 与设备之间切换访问权时需要进行相应的同步;使用结束后通常会 unmap。而它在CPU cache上的虚拟地址可能是连续的,但物理地址不一定是连续的。这就涉及到scatterlists。

scatterlists

scatterlist 用多个 entry 描述一组可能不连续的内存区域。经过 DMA mapping 后,某些相邻 entry 在设备看到的 DMA 地址空间中可能被合并,所以 mapping 后的 segment 数量可能少于原来的 entry 数量。

DMA Controlller

实际上DMA Controller的大致结构和其他模块差不多,但中间多了一个关键部分channel。

channel

DMA controller 有实际的硬件执行 channel。Linux provider driver 可以直接把 dma_chan 对应到硬件 channel,也可以额外实现 virtual channel,在软件中保存更多逻辑 channel,再调度到有限的硬件 channel 上。是否存在 virtual channel 取决于具体 controller driver。

DMA transfar request list

consumer 每次产生一个新的 DMA 传输任务时,通常需要准备新的 descriptor,descriptor 中保存这次传输的信息。一个 channel 可以维护多个待执行的 descriptor,并依次执行。但具体是虚拟channel还是物理channel这就看controller driver的调度了。

DMA Request Line

DMA Request Line 是外设向 DMA Controller 发出的硬件请求,例如 SPI TX FIFO 缺数据时,通过对应 request line 告诉 DMA Controller 可以继续搬数据。Request Line、DMA Channel 和 Descriptor 是三个不同概念。

DMAEngine

同样的DMAEngine 类似 Regulator Core,是位于 consumer driver 与 DMA controller driver 之间的统一框架。consumer 通过 DMAEngine 的统一接口使用 DMA,而 controller driver 作为 provider 提供具体硬件实现。

从设备树到注册或注销

  1. 设备树
spi1: spi@2ad00000 {
    dmas = <&dmac0 16>, <&dmac0 17>;
    dma-names = "tx", "rx";
};

dmac0: dma-controller@2ab90000 {
    compatible = "arm,pl330", "arm,primecell";
    #dma-cells = <1>;
};
  1. 初始化流程

从dmac0作为一个controller device和driver去probe后,就产生了这个pl330,然后开始配置channel等。

spi1则会向这个dmac0去申请两个通道,具体什么时候用,就看实际consumer了。

一次DMA传输

前面叽里呱啦说半天也看不明白,可能还有错的,下面用实际代码来说明前面的spi实际一次传输。

channel

static int rockchip_spi_probe(struct platform_device *pdev)
{
    // ...
	master->dma_tx = dma_request_chan(rs->dev, "tx");
	master->dma_rx = dma_request_chan(rs->dev, "rx");

	if (master->dma_tx && master->dma_rx) {
		rs->dma_addr_tx = mem->start + ROCKCHIP_SPI_TXDR;
		rs->dma_addr_rx = mem->start + ROCKCHIP_SPI_RXDR;
		master->can_dma = rockchip_spi_can_dma;
	}
    // ...
}

对于这个spi在probe时就请求它的channel并整个生命周期都持有,但此时还没有占有这个内存块。

transfer

static int rockchip_spi_prepare_dma(struct rockchip_spi *rs,
		struct spi_master *master, struct spi_transfer *xfer)
{
	struct dma_async_tx_descriptor *rxdesc, *txdesc;

	atomic_set(&rs->state, 0);

	rxdesc = NULL;
	if (xfer->rx_buf) {
		struct dma_slave_config rxconf = {
			.direction = DMA_DEV_TO_MEM,
			.src_addr = rs->dma_addr_rx,
			.src_addr_width = rs->n_bytes,
			.src_maxburst = 1,
		};

		dmaengine_slave_config(master->dma_rx, &rxconf);

		rxdesc = dmaengine_prep_slave_sg(
				master->dma_rx,
				xfer->rx_sg.sgl, xfer->rx_sg.nents,
				DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
		if (!rxdesc)
			return -EINVAL;

		rxdesc->callback = rockchip_spi_dma_rxcb;
		rxdesc->callback_param = master;
	}

    // tx...

	if (rxdesc) {
		atomic_or(RXDMA, &rs->state);
		dmaengine_submit(rxdesc);
		dma_async_issue_pending(master->dma_rx);
	}

	spi_enable_chip(rs, true);

	// tx...

	return 1;
}

static int rockchip_spi_transfer_one(
		struct spi_master *master,
		struct spi_device *spi,
		struct spi_transfer *xfer)
{
	bool use_dma;

    // ...

	rs->n_bytes = xfer->bits_per_word <= 8 ? 1 : 2;

	use_dma = master->can_dma ? master->can_dma(master, spi, xfer) : false;

	rockchip_spi_config(rs, spi, xfer, use_dma);

	if (use_dma)
		return rockchip_spi_prepare_dma(rs, master, xfer);

	return rockchip_spi_prepare_irq(rs, xfer);
}

dma_slave_config 配置的是 slave DMA 的设备端信息,例如传输方向、SPI FIFO 地址、访问宽度以及 burst。xfer->rx_sg/tx_sg 则描述这次传输的内存端;驱动再根据这些信息准备本次 DMA descriptor,设置 callback,最后提交并启动。

参考文档

[1]一致性DMA映射与流式DMA映射
[2]使用通用设备的动态 DMA 映射
[3]动态 DMA 映射指南
[4]DMAengine controller documentation
[5]DMA Test Guide
[6]Linux内核scatterlist API介绍
[7]Linux DMA Engine framework(1)(2)(3)
[8]DMA子系统
[9]Linux之DMA驱动详解(2)-sun6i-dma.c驱动为例