title: DMA設計
tags: linux
date: 2019年1月5日 17:27:08
toc: true
DMA設計
DMA框架
一個簡單的DMA框圖如下DREQ→HOLD→HLDA→DACK
DMAC的一些必備特性:
- 能發出地址信息,對存儲器尋址,並修改地址指針,DMAC內部必須有能自動加1或減1的地址寄存
- 能決定傳送的字節數,並能判斷DMA傳送是否結束。DMA內部必須有能自動減1的字計數寄存器,計數結束產生終止計數信號;
- 能發出DMA結束信號,釋放總線,使CPU恢復總線控制權;
- 能發出讀、寫控制信號,包括存儲器訪問信號和I/O訪問信號。DMAC內部必須有時序和讀寫控制邏輯。
信號線如下
- DRQ:DMA請求信號。是外設向DMA控制器提出要求DMA操作的申請信號。
- DACK:DMA響應信號。是DMA控制器向提出DMA請求的外設表示已收到請求和正進行處理的信號。
- HOLD:總線請求信號。是DMA控制器向CPU要求讓出總線的請求信號。
- HLDA:總線響應信號,是CPU向DMA控制器表示允許總線請求的應答信號。
流程順序
- 當外設有DMA需求,並且准備就緒,就向DMAC控制器發出DMA請求信號DREQ
- DMAC接到DMA請求信號后向CPU發出總線請求信號HRQ。該信號連接到CPU的HOLD信號。
- CPU接到總線請求信號以后,如果允許DMA傳輸,則會在當前總線周期結束后,發出DMA響應信號HLDA。一方面CPU將控制總線、數據總線和地址總線置高阻態,即放棄對總線的控制權;另一方面CPU將有效的HLDA信號送給DMAC,通知DMAC,CPU已經放棄了對總線的控制權。
- DMAC獲得對總線的控制權,並且向外設送出DMAC的應答信號DACK,通知外設可以開始進行DMA傳輸了。
- DMAC向存儲器發送地址信號和向存儲器及外設發出讀/寫控制信號,控制數據按初始化設定的方向傳送,實現外設與內存的數據傳輸。
- 數據全部傳輸結束后,DMAC向CPU發HOLD信號,要求撤銷總線請求信號。CPU收到該信號以后,使HLDA無效,同時收回對總線的控制權。
DMA控制器的基本組成
- 內存地址計數器:用於存放內存中要交換的數據的地址。
- 字計數器:用於記錄傳送數據塊的長度(多少字數)。
- 數據緩沖寄存器:用於暫存每次傳送的數據(一個字)。
- "DMA請求"標志:每當設備准備好一個數據字后給出一個控制信號,使"DMA請求"標志置"1"。該標志置位后向"控制/狀態"邏輯發出DMA請求,后者又向CPU發出總線使用權的請求(HOLD),CPU響應此請求后發回響應信號HLDA,"控制/狀態"邏輯接收此信號后發出DMA響應信號,使"DMA 請求"標志復位,為交換下一個字做好准備。
- "控制/狀態"邏輯:由控制和時序電路以及狀態標志等組成,用於修改內存地址計數器和字計數器,指定傳送類型(輸入或輸出),並對"DMA請求"信號和CPU響應信號進行協調和同步。
- 中斷機構:當字計數器溢出時,意味着一組數據交換完畢,由溢出信號觸發中斷機構,向CPU提出中斷報告。
手冊請看英文手冊
芯片特性
請求來源
-
軟件觸發
-
外設觸發
-
外部引腳觸發,這個是STM32所沒有的,這個是有具體的時序的,STM32應該是可以用中斷引腳觸發
2440通道傳輸類型
- 源和目標都在系統總線上(比如:兩個物理內存地址)
- 目標在外設總線上時,源在系統總線上(外設指:串口,定時器,I2C,I2S等)
- 目標在系統總線上時,源在外設總線上
- 源和目標都在外設總線上----------這個ST的也沒有
外部引腳的DMA協議
這個貌似有點復雜,暫時也沒用過,暫時不做深入分析了
協議簡述
2440里面的DMA傳輸分為兩個層次,一個是REQ/ACK協議,還一個是單模式和全模式,所謂單模式個全模式是指的在一次DMA請求中的傳輸數量
基本時序
時序參數
-
信號的有效性: 高電平無效,低電平有效,這里稱為assert
-
REQ有效
REQ的只能在ACK釋放(high)的時候才能被asserted(high),也就是說 請求信號只能在ACK為高的時候才能被MCU的DMA識別到
-
信號生效識別
nXDREQ請求生效並經過2CLK周期同步后,nXDACK響應並開始生效,但至少還要經過3CLK的周期延遲,DMA控制器才可獲得總線的控制權,並開始數據傳輸。
模式
-
Single service : 當沒有原子傳輸(unit/burst)后,停止傳輸,等待下一次請求
-
Whole service : 重復原子傳輸,直到計數器到0.這個模式下,不需要另外的請求.這個是重點
在全模式下,DMA也會在每個原子傳輸后釋放總線然后去嘗試獲得總線,以防止總線被占據
也就是說,全模式是我們一般使用的模式,使用計數器,一次請求會傳輸所有數據.單模式一次傳輸一個原子操作.
ACK清零:
- 單服務是完成一個原子操作
- 全服務是完成所有傳輸
中斷發生:
- 都在計數器為0的時候
協議
這里的協議,指的是請求應答協議,分為兩種.
-
Demand Mode 請求/查詢模式
如果REQ信號有效,則一直保持傳輸,這個時候的ACK只是告訴你這一次傳輸完成
這個模式會霸占總線的,不像全服務中完成一個原子操作釋放一下總線
-
Handshake Mode 握手模式
如果REQ信號釋放,這個時候DMA控制器釋放ACK兩個周期,否則DMA會一直等到REQ的釋放
也就是啟動下一次傳輸前,需要請求端先釋放,然后MCU完成后會無效ACK兩個周期告訴請求端,請求端再來請求,否則一直等待
在Demond模式下,如果DMA完成一次請求后如果Request仍然有效,那么DMA就認為這是下一次DMA請求,並立即開始下一次的傳輸;
在Handshake模式下,DMA完成一次請求后等待Request信號無效,如果Request無效,DMA會無效ACK兩個時鍾周期,再等待下一次Request。
數據大小的描述
數據傳輸的大小=數據傳輸次數 * 每次傳輸的讀寫次數 * 一次讀或者寫的大小
- 每次傳輸的讀寫次數可以是1個或者4個 unit/burst
- 一次讀或者寫的大小可以是1字節,2字節,4字節
具體完整的實例時序
單服務查詢請求模式
單服務握手模式
全服務握手模式
在這里其實無所謂hand了,因為在全模式下只需要一次請求就能完成后續的所有操作
代碼設計
這里的代碼就是驅動實現一個內存的拷貝,不涉及到上面長篇大論的時序分析,只是需要設置好相關的寄存器配置配置DMA的模式,然后啟動DMA后進入休眠,完成后DMA中斷喚醒后退出.
測試程序調用字符驅動程序的接口ioctl
來測試即可
寫程序前需要查看用到的DMA
cat /proc/interrupts
驅動程序
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/dma-mapping.h>
#define S3C_DMA_SIZE 512*1024 //DMA傳輸長度 512KB
#define NORMAL_COPY 0 //兩個地址之間的正常拷貝
#define DMA_COPY 1 //兩個地址之間的DMA拷貝
/*函數聲明*/
static DECLARE_WAIT_QUEUE_HEAD(s3c_dma_queue); //聲明等待隊列
static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long flags);
/*
* 定義中斷事件標志
* 0:進入等待隊列 1:退出等待隊列
*/
static int s3c_dma_even=0;
static unsigned char *source_virt; //源虛擬地址
static unsigned int source_phys; //源物理地址
static unsigned char *dest_virt; //目的虛擬地址
static unsigned int dest_phys; //目的虛擬地址
/*DMA3寄存器*/
struct S3c_dma3_regs{
unsigned int disrc3 ; //0x4b0000c0
unsigned int disrcc3 ;
unsigned int didst3 ;
unsigned int didstc3 ;
unsigned int dcon3 ;
unsigned int dstat3 ;
unsigned int dcsrc3 ;
unsigned int dcdst3 ;
unsigned int dmasktrig3; //0x4b0000e0
};
static volatile struct S3c_dma3_regs *s3c_dma3_regs;
/*字符設備操作*/
static struct file_operations s3c_dma_fops={
.owner = THIS_MODULE,
.ioctl = s3c_dma_ioctl,
};
/*中斷服務函數*/
static irqreturn_t s3c_dma_irq (int irq, void *dev_id)
{
s3c_dma_even=1; //退出等待隊列
wake_up_interruptible(&s3c_dma_queue); //喚醒 中斷
return IRQ_HANDLED;
}
/*ioctl函數*/
static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long flags)
{
int i;
memset(source_virt, 0xAA, S3C_DMA_SIZE);
memset(dest_virt, 0x55, S3C_DMA_SIZE);
switch(cmd)
{
case NORMAL_COPY: //正常拷貝
for(i=0;i<S3C_DMA_SIZE;i++)
dest_virt[i] = source_virt[i];
if(memcmp(dest_virt, source_virt, S3C_DMA_SIZE)==0)
{
printk("NORMAL_COPY OK\n");
return 0;
}
else
{
printk("NORMAL_COPY ERROR\n");
return -EAGAIN;
}
case DMA_COPY: //DMA拷貝
s3c_dma_even=0; //進入等待隊列
/*設置DMA寄存器,啟動一次DMA傳輸 */
/* 源的物理地址 */
s3c_dma3_regs->disrc3 = source_phys;
/* 源位於AHB總線, 源地址遞增 */
s3c_dma3_regs->disrcc3 = (0<<1) | (0<<0);
/* 目的的物理地址 */
s3c_dma3_regs->didst3 = dest_phys;
/* 目的位於AHB總線, 目的地址遞增 */
s3c_dma3_regs->didstc3 = (0<<2) | (0<<1) | (0<<0);
/* 使能中斷,單個傳輸,軟件觸發, */
s3c_dma3_regs->dcon3=(1<<30)|(1<<29)|(0<<28)|(1<<27)|(0<<23)|(0<<20)|(S3C_DMA_SIZE<<0);
//啟動一次DMA傳輸
s3c_dma3_regs->dmasktrig3 = (1<<1) | (1<<0);
wait_event_interruptible(s3c_dma_queue, s3c_dma_even); //進入睡眠,等待DMA傳輸中斷到來才退出
if(memcmp(dest_virt, source_virt, S3C_DMA_SIZE)==0)
{
printk("DMA_COPY OK\n");
return 0;
}
else
{
printk("DMA_COPY ERROR\n");
return -EAGAIN;
}
break;
}
return 0;
}
static unsigned int major;
static struct class *cls;
static int s3c_dma_init(void)
{
/*1.1 注冊DMA3 中斷 */
if(request_irq(IRQ_DMA3, s3c_dma_irq,NULL, "s3c_dma",1))
{
printk("Can't request_irq \"IRQ_DMA3\"!!!\n ");
return -EBUSY;
}
/*1.2 分配兩個DMA緩沖區(源、目的)*/
source_virt=dma_alloc_writecombine(NULL,S3C_DMA_SIZE, &source_phys, GFP_KERNEL);
if(source_virt==NULL)
{
printk("Can't dma_alloc \n ");
return -ENOMEM;
}
dest_virt=dma_alloc_writecombine(NULL,S3C_DMA_SIZE, &dest_phys, GFP_KERNEL);
if(dest_virt==NULL)
{
printk("Can't dma_alloc \n ");
return -ENOMEM;
}
/*2.注冊字符設備,並提供文件操作集合fops*/
major=register_chrdev(0, "s3c_dma",&s3c_dma_fops);
cls= class_create(THIS_MODULE, "s3c_dma");
class_device_create(cls, NULL,MKDEV(major,0), NULL, "s3c_dma");
s3c_dma3_regs=ioremap(0x4b0000c0, sizeof(struct S3c_dma3_regs));
return 0;
}
static void s3c_dma_exit(void)
{
iounmap(s3c_dma3_regs);
class_device_destroy(cls, MKDEV(major,0));
class_destroy(cls);
dma_free_writecombine(NULL, S3C_DMA_SIZE, dest_virt, dest_phys);
dma_free_writecombine(NULL, S3C_DMA_SIZE, source_virt, source_phys);
free_irq(IRQ_DMA3, 1);
}
module_init(s3c_dma_init);
module_exit(s3c_dma_exit);
MODULE_LICENSE("GPL");
測試程序
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
/* ./dma_test NORMAL
* ./dma_test DMA
*/
#define NORMAL_COPY 0 //兩個地址之間的正常拷貝
#define DMA_COPY 1 //兩個地址之間的DMA拷貝
void print_usage(char *name)
{
printf("Usage:\n");
printf("%s <NORMAL | DMA>\n", name);
}
int main(int argc, char **argv)
{
int fd,i=30;
if (argc != 2)
{
print_usage(argv[0]);
return -1;
}
fd = open("/dev/s3c_dma", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/s3c_dma\n");
return -1;
}
if (strcmp(argv[1], "NORMAL") == 0)
{
while (i--) //調用驅動的ioctl(),30次
{
ioctl(fd, NORMAL_COPY);
}
}
else if (strcmp(argv[1], "DMA") == 0)
{
while (i--) //調用驅動的ioctl(),30次
{
ioctl(fd, DMA_COPY);
}
}
else
{
print_usage(argv[0]);
return -1;
}
return 0;
}
測試
./dma_test NORMAL &
卡住./dma_test DMA &
,輸入命令有反應