99 lines
4.1 KiB
ReStructuredText
99 lines
4.1 KiB
ReStructuredText
DMA 链表模式(LLI)深度解析
|
||
===============================
|
||
|
||
博流系列芯片的 DMA 都支持链表模式(LLI)。
|
||
|
||
在进行一次 DMA 读或者写的时候,可以配置多个链表,从而当一个链表的数据传输完成时,会跳到下一个链表的起始地址,并继续传输数据,直到链表的下一个地址为 0。如果 DMA 使能了完成中断,则当 DMA 发送或者接收完成时,会进入完成中断。
|
||
|
||
.. figure:: img/dma_lli.svg
|
||
:alt:
|
||
|
||
那么有了这种 DMA 链表模式,我们就可以实现以下功能:
|
||
|
||
- DMA 发送或者接收长度不限制
|
||
- DMA 发送接收地址可以不连续
|
||
- DMA 实现多种中断模式,半中断、3中断、4中断等等
|
||
- DMA 实现循环功能
|
||
|
||
OK,那么当我们开始研究链表配置之前,我们需要了解一些前提:
|
||
- 每个链表最多传输 4095 ,单位根据位宽决定
|
||
- 每个链表都可以触发中断
|
||
|
||
|
||
支持长度不限制
|
||
--------------------
|
||
|
||
由于每个 dma 链表最多支持 4905,假设位宽用的是字节,那么一个链表最多传输 4095 字节,很显然这个不能满足我们需求,性能太低。那么如何提高传输长度呢?
|
||
|
||
我们可以使用多个链表,串接起来,这样就能够支持更大的传输长度了,并且传输的地址是连续的,dma 链表连接如图所示:
|
||
|
||
.. figure:: img/dma_lli.png
|
||
:alt:
|
||
|
||
|
||
这个时候还有一个问题,当一个链表使用了 4095 字节,下一个链表是从 4095 的偏移开始,这个时候,就会产生非对齐的问题,如果是在 cache 场景下,是会有问题的。
|
||
因此,我们将 4095 减少到 4064,这样保证每个链表的首地址都是 32 字节对齐的。到这就实现了长度不限制的功能,具体实现参考 ``bflb_dma_lli_config`` 函数。
|
||
|
||
.. code-block:: c
|
||
:linenos:
|
||
|
||
void bflb_dma_lli_config(struct bflb_device_s *dev, struct bflb_dma_channel_lli_pool_s *lli_pool, uint32_t lli_count, uint32_t src_addr, uint32_t dst_addr, uint32_t transfer_offset, uint32_t last_transfer_len)
|
||
{
|
||
uint32_t channel_base;
|
||
union bflb_dma_lli_control_s dma_ctrl_cfg;
|
||
|
||
channel_base = dev->reg_base;
|
||
|
||
dma_ctrl_cfg = (union bflb_dma_lli_control_s)getreg32(channel_base + DMA_CxCONTROL_OFFSET);
|
||
|
||
dma_ctrl_cfg.bits.TransferSize = 4064;
|
||
dma_ctrl_cfg.bits.I = 0;
|
||
|
||
/* nbytes will be integer multiple of 4064*n or 4064*2*n or 4064*4*n,(n>0) */
|
||
for (uint32_t i = 0; i < lli_count; i++) {
|
||
lli_pool[i].src_addr = src_addr;
|
||
lli_pool[i].dst_addr = dst_addr;
|
||
lli_pool[i].nextlli = 0;
|
||
|
||
if (dma_ctrl_cfg.bits.SI) {
|
||
src_addr += transfer_offset;
|
||
}
|
||
|
||
if (dma_ctrl_cfg.bits.DI) {
|
||
dst_addr += transfer_offset;
|
||
}
|
||
|
||
if (i == lli_count - 1) {
|
||
dma_ctrl_cfg.bits.TransferSize = last_transfer_len;
|
||
dma_ctrl_cfg.bits.I = 1;
|
||
}
|
||
|
||
if (i) {
|
||
lli_pool[i - 1].nextlli = (uint32_t)(uintptr_t)&lli_pool[i];
|
||
}
|
||
|
||
lli_pool[i].control = dma_ctrl_cfg;
|
||
}
|
||
}
|
||
|
||
支持地址不连续
|
||
--------------------
|
||
|
||
刚刚我们解决了长度限制问题,那么本身 dma 链表是支持地址不连续的,我们只需要把上面使用的多个链表当成一个大链表,然后两个大链表拼接,并且两个链表传输的首地址不连续,就可以实现地址不连续了。
|
||
具体实现参考 ``bflb_dma_channel_lli_reload`` 函数中的 ``bflb_dma_channel_lli_transfer_s``。dma 链表连接如图所示:
|
||
|
||
.. figure:: img/dma_lli2.png
|
||
:alt:
|
||
|
||
|
||
支持多中断
|
||
--------------------
|
||
|
||
完成了上述两步以后,多中断也就完成了。
|
||
当我们支持完长度不限制后,最后一个链表会开启中断,当传输完成最后一个链表时,就会触发中断。
|
||
当我们支持完地址不连续后,多个大链表(也就是长度不限制的链表的最后一个链表)完成都会触发中断,假设设定了三个传输 ``bflb_dma_channel_lli_transfer_s``, 那么会触发三次 DMA 完成中断。
|
||
|
||
|
||
支持循环模式
|
||
--------------------
|