概述
在SD卡读写和framebuffer拷贝功能上用DMA实现
- 添加了dma.c和dma.h源码文件,定义了dma接口和实现
- 在sd.c的sd_readblock和sd_writeblock函数中添加了DMA相关函数调用实现数据传输。
树莓派dma简介
概述
BCM2835 中的大多数硬件流水线和外设都是总线主控器,使它们能够有效地满足自己的数据需求。这降低了 DMA 控制器对块到块内存传输的要求,并支持一些更简单的外设。此外,DMA 控制器提供只读预取模式,以允许将数据带入 L2 高速缓存以供以后使用。
注意 DMA 控制器直接连接到外围设备。因此,DMA控制器必须设置为使用外设的物理(硬件)地址。
BCM2835 DMA 控制器总共提供 16 个 DMA 通道。每个通道独立于其他通道运行,并在内部仲裁到 3 条系统总线之一。这意味着 DMA 通道可能消耗的带宽量可以通过仲裁器设置来控制。
每个 DMA 通道通过将控制块 (control block) 数据结构从内存加载到内部寄存器来运行。控制块定义所需的 DMA 操作。一旦当前控制块中描述的操作完成,每个控制块都可以指向另一个要加载和执行的控制块。以这种方式,可以构建控制块的链接列表,以便在没有软件干预的情况下执行一系列 DMA 操作。
外设到 DMA 通道的分配是可编程的。
DMA 可以处理字节对齐的传输,并通过缓冲和打包未对齐的访问来最小化总线流量。
每个 DMA 通道都可以通过顶级电源寄存器完全禁用以节省功耗。
DMA 控制器寄存器
DMA 控制器由几个相同的 DMA 通道组成,具体取决于所需的配置。每个单独的 DMA 通道都有一个相同的寄存器映射(尽管 LITE 通道的功能较少,因此寄存器较少)。
DMA 通道 0 位于地址 0x7E007000,通道 1 位于 0x7E007100,通道 2 位于 0x7E007200,依此类推。
因此相邻的 DMA 通道偏移 0x100。然而,DMA 通道 15 在物理上从其他 DMA 通道中移除,因此具有不同的地址基址 0x7EE05000。
DMA 通道寄存器地址映射
每个 DMA 通道都有一个相同的寄存器映射,只是每个通道的基地址不同。地址映射顶部有一个全局启用寄存器,可以禁用每个 DMA 以实现节能。每个通道寄存器集中只有三个寄存器是可直接写的(CS、CONBLK_AD 和 DEBUG)。其他寄存器(TI、SOURCE_AD、DEST_AD、TXFR_LEN、STRIDE 和 NEXTCONBK)从外部存储器中保存的控制块数据结构自动加载。
控制块数据结构
控制块 (CB) 的长度为 8 个字(256 位),并且必须从 256 位对齐的地址开始。
CB 数据结构在内存中的格式如下图所示。
在 DMA 传输开始时,控制块的每个 32 位字会自动加载到相应的 32 位 DMA 控制块寄存器中。这些寄存器的描述也定义了内存中CB数据结构中对应的位位置。
通过将 CB 结构的地址写入 CONBLK_AD 寄存器然后设置 ACTIVE 位来启动 DMA。 DMA 将从该 reg 的 SCB_ADDR 字段中设置的地址获取 CB,并将其加载到下面描述的只读寄存器中。然后它将根据 CB 中的信息开始 DMA 传输。
当它完成当前的 DMA 传输(长度 => 0)时,DMA 将使用 NEXTCONBK 寄存器的内容更新 CONBLK_AD 寄存器,从该地址获取一个新的 CB,并再次开始整个过程。
当 DMA 完成 DMA 传输并且 NEXTCONBK 寄存器设置为 0x0000_0000 时,DMA 将停止(并清除 ACTIVE 位)。它将将此值加载到 CONBLK_AD reg 中,然后停止。
大多数控制块寄存器不能直接写入,因为它们是从内存中自动加载的。可以读取它们以提供状态信息,并指示当前 DMA 传输的进度。加载到 NEXTCONBK 寄存器中的值可以被覆盖,以便控制块数据结构的链表可以动态更改。然而,只有在 DMA 暂停时才可以安全地执行此操作。
Register Map
dma寄存器映射到内存上的地址详情见下表:
每个channel有一组寄存器,每个通道的寄存器占0x100的内存,channel 0寄存器相对于0x7E007000的偏移是0,channel 1的寄存器相对0x7E007000的偏移是0x100,依次递增
CS寄存器
DMA 控制和状态寄存器包含该 DMA 通道的主要控制和状态位。
最高位是RESET位,设置为1时初始化DMA
第3位是Data request位,
指示所选 DREQ(数据请求)信号的状态,即。由传输信息的 PERMAP 字段选择的 DREQ。 1 = 请求数据。这仅在 DMA 启动并且 PERMAP 字段已从 CB 加载后才有效。它将保持有效,指示选定的 DREQ 信号,直到加载新的 CB。如果 PERMAP 设置为零(无节奏传输),则该位将读回为 1。 0 = 无数据请求。
第0,1,2位分别是传输启动位和当前control block传输结束位,active位还表示所有的control _block传输结束
TI寄存器
保存了DMA 传输信息
PERMAP表示外设编号 (1-31),其就绪信号将用于控制传输速率,其紧急信号将在 DMA AXI 总线上输出。设置为 0 表示连续的无节奏传输。
这三个SRC开头的位,表示了对源数据的设置,SRC_INC表示源地址是否增长,如果为1, 则每次增长,否则不变。
SRC_WITDH表示源数据传输时地址宽度,如果是0则每次32bit,否则128bit
SRC_DREQ
如果该位是1,PER_MAP 选择的 DREQ 将选通源读取
否则DREQ位没有影响
DEST开头的三个位,与SRC含义相同,只是作用于目标数据而已
INTEN表示INT enable,用于允许CS 寄存器的INT位变化
TXFR寄存器
DMA 传输长度。这指定要传输的数据量(以字节为单位)。在正常(非 2D)模式下,这指定了要传输的字节数。在 2D 模式下,它被解释为 X 和 Y 长度,并且 DMA 将执行 Y 传输,每个长度为 X 字节,并在传输的每个 X 支路之后将跨度添加到地址上。随着传输的进行,DMA 引擎会更新长度寄存器,因此它将指示剩余要传输的数据。
ENABLE寄存器
每个通道的全局使能位,每个比特表示第几个dma channel被允许
外设到 DREQ 的映射如下
)
这里是TI寄存器的PERMAP位可选的值,对应SD卡的读写,选择11号外设,也就是EMMC,因为在sd.c中用的是EMMC协议完成的与sd交互
实现
dma.h
peripherals/dma.h
定义control _block数据结构
1 | typedef struct { |
定义dma通道寄存器组对应的数据结构
1 | typedef struct { |
DMA通道寄存器组偏移计算,每个通道的寄存器占0x100字节宽度
其中KERNEL_MMIO_BASE被定义为
1 |
物理地址0x3F000000被映射到线性地址的0xffffffffff000000地址处
enable寄存器和status寄存器的宏定义
dma.h
在dma.h中定义了dma通道结构体,和channel类型。
dma_channel的channel成员表示通道序号,从0~14.
block成员是个指向dma_control_block结构体的指针
status成员是当前通道最近一次数据传输的状态
1 |
|
暴露的dma功能函数接口,打开一个channel,关闭一个channel,dma实现内存拷贝,dma传输启动, dma等待
1 | dma_channel *dma_open_channel(unsigned int channel); |
dma.c
定义控制块变量,分别用于framebuffer和sd卡读写;定义通道数组
dma_open_channel函数,从dma_channel结构体数组选出参数channel对应的channel,并让结构体的block指针指向对应的控制块变量。sd卡用0号channel,framebuffer使用2号channel。
通过REGS_DMA_ENABLE 指向的enable寄存器的值的变化,使能对应的dma_channel设备。
通过REGS_DMA(dma->channel)->control 指向的CS寄存器让对应的dma channel完成重置。
语句REGS_DMA(dma->channel)->control & CS_RESET 检查CS寄存器的RESET位,直至不为1表示reset过程完成。
返回对应的dma_channel结构体指针。
1 | dma_channel *dma_open_channel(unsigned int channel) |
dma_start启动dma数据传输,首先设置控制块的地址,需要将线性地址转换为物理地址。
使用ACTIVE位启动数据传输,END让当前控制块的dma传输结束时中断,INT允许中断,
dma_wait函数,判断当前dma通道的CS寄存器的active位是否为1,当所有的控制块表示的dma传输完成时,CS寄存器的active位变为0
读取CS寄存器的error位,作为本地dma传输的status状态返回
dma_setup_mem_copy的实现在dma.c中
这里与sd卡读写的dma设置有所不同,其源地址和目的地址在数据传输过程中都是要递增的,所以要给transfer_info设置TI_SRC_WIDTH,TI_SRC_INC,TI_DEST_WITDH,TI_DEST_INC属性
sd.c
在sd_init函数的最后使用dma_open_channel打开0号通道,完成初始化。
在sd_readblock函数中发送读取命令后,如果不使用DMA,那么就用循环的方法从EMMC_DATA处读取数据到buf中。
如果使用DMA的话,设置DMA的源地址和目的地址,使用TI_SRC_DREQ表示从外围设备获取数据,TI_DEST_INC, TI_DEST_WITDH表示目的地址递增,TI_PERMAP_EMMC表示外围设备类型是EMMC。
完成控制块的构造后,调用dma_start启动dma数据传输,调用dma_wait等待dma数据传输完成。
如果不用dma,则需要判断emmc的命令是否执行完成
1 | while( c < num ) { |
对于sd_writeblock函数,只需要将目的地址和源地址交换,在transfer_info中设置TI_DEST_DREQ,TI_SRC_INC,TI_SRC_WITDH, TI_PERMAP_EMMC表示是从内存到外设。