32.Linux-2440下的DMA驅動(詳解)

来源:http://www.cnblogs.com/lifexy/archive/2017/11/22/7880737.html
-Advertisement-
Play Games

DMA(Direct Memory Access) 即直接存儲器訪問, DMA 傳輸方式無需 CPU 直接控制傳輸,通過硬體為 RAM 、I/O 設備開闢一條直接傳送數據的通路,能使 CPU 的效率大為提高。 學了這麼多驅動,不難推出DMA的編寫套路: 1)註冊DMA中斷,分配緩衝區 2)註冊字元設 ...


DMA(Direct Memory Access)

即直接存儲器訪問, DMA 傳輸方式無需 CPU 直接控制傳輸,通過硬體為 RAM 、I/O 設備開闢一條直接傳送數據的通路,能使 CPU 的效率大為提高。


 

學了這麼多驅動,不難推出DMA的編寫套路:

  • 1)註冊DMA中斷,分配緩衝區
  • 2)註冊字元設備,並提供文件操作集合fops
  •   -> 2.1)file_operations里設置DMA硬體相關操作,來啟動DMA

由於我們是用字元設備的測試方法測試的,而本例子只是用兩個地址之間的拷貝來演示DMA的作用,所以採用字元設備方式編寫

 

1.驅動編寫之前,先來講如何分配釋放緩衝區DMA相關寄存器介紹使用DMA中斷

1.1在linux中,分配釋放DMA緩衝區,只能使用以下幾個函數

1) 

/*該函數只禁止cache緩衝,保持寫緩衝區,也就是對註冊的物理區寫入數據,也會更新到對應的虛擬緩存區上*/
void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp); 
//分配DMA緩存區
//返回值為:申請到的DMA緩衝區的虛擬地址,若為NULL,表示分配失敗,需要釋放,避免記憶體泄漏
//參數如下:
  //*dev:指針,這裡填0,表示這個申請的緩衝區里沒有內容
  //size:分配的地址大小(位元組單位)
  //*handle:申請到的物理起始地址
  //gfp:分配出來的記憶體參數,標誌定義在<linux/gfp.h>,常用標誌如下:
        //GFP_ATOMIC    用來從中斷處理和進程上下文之外的其他代碼中分配記憶體. 從不睡眠.
        //GFP_KERNEL    內核記憶體的正常分配. 可能睡眠.
      //GFP_USER      用來為用戶空間頁來分配記憶體; 它可能睡眠. 

 

2)

/*該函數禁止cache緩存以及禁止寫入緩衝區*/
void * dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);         
//分配DMA緩存區,返回值和參數和上面的函數一直

 

 3)

dma_free_writecombine(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle);   //釋放DMA緩存,與dma_alloc_writecombine()對應
//size:釋放長度
//cpu_addr:虛擬地址,
//handle:物理地址

 

 4)

dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle)    //釋放DMA緩存,與dma_alloc_coherent ()對應
//size:釋放長度
//cpu_addr:虛擬地址, //handle:物理地址

 

 (PS: dma_free_writecombine()其實就是dma_free_conherent(),只不過是用了#define重命名而已。)

而我們之前用的記憶體分配kmalloc()函數,是不能用在DMA上,因為分配出來的記憶體可能在物理地址上是不連續的.

 

1.2 那麼2440開發板如何來啟動DMA,先來看2440的DMA寄存器

(PS:實際這些DMA相關的寄存器,在linux內核中三星已封裝好了,可以直接調用,不過非常麻煩,還不如直接設置寄存器,可以參考: http://blog.csdn.net/mirkerson/article/details/6632273)

1.2.1 2440支持4個通道的DMA控制器

其中4個通道的DMA外設請求源,如下圖所示(通過DCONn寄存器的[26:24]來設置)

(PS:如果請求源是系統匯流排上的,就只需要設置DCONn寄存器的[23]=0即可)

 

1.2.2 且每個通道都可以處理以下4種情況:

1) 源和目標都在系統匯流排上(比如:兩個物理記憶體地址)
2) 當目標在外設匯流排上時,源在系統匯流排上(外設指:串口,定時器,I2C,I2S等)
3) 當目標在系統匯流排上時,源在外設匯流排上
4) 源和目標都在外設匯流排上

 

1.2.3 DMA有兩種工作模式(通過DCONn寄存器的[28]來設置)

查詢模式:

當DMA請求XnXDREQ為低電平時,則DMA會一直傳輸數據,直到DMA請求拉高,才停止

握手模式:

當DMA請求XnXDREQ有下降沿觸發時,則DMA會傳輸一次數據

 

 

1.2.4 DMA有兩種傳輸模式(通過DCONn寄存器的[31]來設置)

單元傳輸:

指傳輸過程中,每執行一次,則讀1次,寫1次.(如上圖所示)

突發4傳輸:

指傳輸過程中,每執行一次,則讀4次,然後寫4次(如下圖所示)

 

 

1.2.5 2440中的DMA寄存器如下圖所示:

 

 

共有4個通道的寄存器,且每個通道的寄存器內容都一致,所以我們以DMA通道0為例:

1)DISRC0初始源寄存器 

[30:0] : 存放DMA源的基地址

2)DISRCC0初始源控制寄存器

[1] : 源位置選擇,0:源在系統匯流排上,                       1:源在外設匯流排上

[0] : 源地址選擇,0:傳輸時源地址自動增加,            1:源地址固定

3)DIDST0初始目標寄存器

[30:0] : 設置DMA目的的基地址

 

4)DIDSTC0初始目標控制寄存器

[2]  : 中斷時間選擇,       0:當DMA傳輸計數=0,立即發生中斷       1:執行完自動載入後再發送中斷(也就是計數為0,然後重新載入計數值)

[1] : 目的位置選擇,         0:目的在系統匯流排上,                         1:目的在外設匯流排上

[0] : 目的地址選擇,         0:傳輸時目的地址自動增加,            1:目的地址固定

 

5)DCON0控制寄存器

[31] : 工作模式選擇,   0:查詢模式                  1:握手模式      (當源處於外設時,儘量選擇握手模式)

[30] : 中斷請求(DREQ)/中斷回應(DACK)的同步時鐘選擇,        0:PCLK同步     1:HCLK同步

(PS:如果有設備在HCLK上,該位應當設為1,比如:(SDRAM)記憶體數組, 反之當這些設備在PCLK上,應當設為0,比如:ADC,IIS,I2C,UART)

[29] : DMA傳輸計數中斷使能/禁止      0:禁止中斷                                1:當傳輸完成後,產生中斷

[28] : 傳輸模式選擇,         0:單元傳輸                            1:突發4傳輸

[27] : 傳輸服務模式  

0:單服務模式,比如:有2個DMA請求,它們會被順序執行一次(單元傳輸/突發4傳輸)後停止,然後直到有下一次DMA請求,再重新開始另一次迴圈。

1:全服務模式,指該DMA若有請求,則會占用DMA匯流排,一直傳輸,期間若有其它DMA請求,只有等待傳輸計數TC為0,才會執行其它DMA請求

[26:24] : DMA外設請求源選擇

[23]     : 軟體/硬體請求源選擇      0:軟體請求            1:硬體請求(還需要設置[26:24]來選擇外設源)

[22]     : 重新載入開關選項             為0即可

[21:20] : 傳輸數據大小    為00(8位)即可

[19:0]   : 設置DMA傳輸的計數TC               

6)DSTAT0狀態寄存器

[21:20] :      DMA狀態             00:空閑           01:忙

[19:0]   : 傳輸計數當前值CURR_TC            為0表示傳輸結束

7)DCSRC0當前源寄存器

[30:0]  : 存放DMA當前的源基地址

 

8)DCDST0當前目標寄存器

[30:0]  : 存放DMA當前的目的基地址

 

9)DMASKTRIG0觸發屏蔽寄存器

[2]   : 停止STOP            該位寫1,立刻停止DMA當前的傳輸

[1]   : DMA通道使能        0:關閉DMA的通道0(禁止DMA請求)            1:開啟DMA的通道0(開啟DMA請求)

[0]   : 軟體請求觸發      1:表示啟動一次軟體請求DMA,只有DCONn[23]=0和DMASKTRIGn[1]=1才有效,DMA傳輸時,該位自動清0

1.3接下來就開始講linux註冊DMA中斷

首先,DMA的每個通道只能有一個源- >目的,所以輸入命令 cat /proc/interrupts ,找到DMA3中斷未被使用

所以在linux中使用:

request_irq(IRQ_DMA3, s3c_dma_irq, NULL, "s3c_dma", 1);// s3c_dma_irq:中斷服務函數,這裡註冊DMA3中斷服務函數
//NULL:中斷產生類型, 不需要,所以填NULL
//1:表示中斷時,傳入中斷函數的參數,本節不需要所以填1,切記不能填0,否則註冊失敗

 

 

2.接下來,我們便來寫一個DMA的字元設備驅動

步驟如下:

  • 1) 註冊DMA中斷,分配兩個DMA緩衝區(源、目的)
  • 2) 註冊字元設備,並提供文件操作集合fops
  • -> 2.1) 通過ioctl的cmd來判斷是使用DMA啟動兩個地址之間的拷貝,還是直接兩個地址之間的拷貝
  • -> 2.2)若是DMA啟動,則設置DMA的相關硬體,並啟動DMA傳輸

2.1 所以,驅動代碼如下所示:

#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");

2.2 應用測試程式如下所示:

#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;     
}

3.測試運行

輸入 ./dma_test NORMAL & ,使用CPU正常拷貝,可以發現占用了大部分資源,輸入 ls 無反應:

 

 

輸入./dma_test DMA & ,使用DMA拷貝,輸入 ls 立馬有反應,從而釋放了CPU的壓力:

 

 

 

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 若發現sqlsrver所有帳號不小心被禁用了,這個時候怎麼辦?用重裝嗎?不用,仔細看小白是怎麼一步一步解開這個謎題的。首先需要Windows帳號設置里重新添加一個新帳號。並將其添加到管理員組裡面,然後按照以下步驟操作(註意/m是切換到單用戶的登錄模式下進行,修改完成後,建議切換到多用戶模式下):(1 ...
  • 1、一個文件的基本框架為:文件名、文件地址、文件大小、文件最大的大小、文件的增量(Filegrowth)。 2、文件有mdf、ndf、ldf 三種文件的區別。 3、文件組可以進行文件的管理 FileGroup 預設的文件組是 Primary 日誌文件無文件組的概念,存在Log文件中。 4、 alte ...
  • .tar 解包:tar xvf FileName.tar打包:tar cvf FileName.tar DirName(註:tar是打包,不是壓縮!)———————————————.gz解壓1:gunzip FileName.gz解壓2:gzip -d FileName.gz壓縮:gzip File ...
  • 一、下載安裝包 (1). 下載MySQL源碼 (進入/usr/local/src目錄,使用wget下載) cd /usr/local/src wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.20.tar.gz (2). 下載 ...
  • 內部命令和外部命令 內部命令:系統集成的命令,集成到bash程式中的命令,可以用enable和help查看內部命令,直接和系統一樣運行在記憶體中,執行速度快。內部命令中的:相當於TRUE,即什麼都不幹。 enable cmd啟用內部命令 enable –n cmd 禁用內部命令 enable –n 查 ...
  • 1》交換空間概述: 交換空間(Swap Space)是Linux用於暫時補充物理記憶體,以提供更多記憶體空間的一種機制,交換空間對記憶體有限的電腦有所幫助,但不能取代物理記憶體,因為它位於硬碟 上,硬碟的存取速度比記憶體要慢幾個數量級,當同時運行很多程式,而他們不能同時都裝載進記憶體時,使用交換空間是一種很有 ...
  • 操作系統鏡像文件:CentOS-7-x86_64-DVD-1708 第一步:下載操作系統鏡像文件,地址如下: https://www.centos.org/download/ 第二步:載入進虛擬光碟機,單擊上圖中的“自動檢測”,使用ISO鏡像文件,瀏覽找到下載的鏡像文件即可。 第三步:單擊“開啟此虛擬 ...
  • 本文針對Hi3518EV200平臺處理器,通過ADC單次採樣方式,實現對多通道(1~4通道)ADC進行採樣控制。本文僅僅是對Hi3518EV200晶元ADC的用法的介紹,不涉及ADC具體的工作原理、轉換原理等細節內容。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...