linux內核之dmaengine

原文鏈接:https://blog.csdn.net/heliangbin87/article/details/81530448

dmaengine framwork主要分爲兩部分:DMA controller 和DMA engine API。涉及內核相關文檔:Documentation/damengine目錄、Documentation/devicetree/bindings/dma/、Documentation/DAM-API-HOWTO.txt\DMA-API.txt\DMA-attributes.txt

1、dma controller(provider的角度)

     基於DMA的硬件地址使用的是總線地址而不是物理地址,總線地址是從設備角度看到的內存地址,物理地址是從CPU mmu控制器外圍角度上看到的內存地址。在pc上,對於ISA和PCI而言,總系地址即爲物理地址,但並不是每個平臺都是如此。接口總線通過橋接電路連接,橋接電路會將I/O地址映射爲不同的物理地址,例如在PREP(PowerPC Reference Platform)系統中,物理地址0在設備端看起來是0x80000000,而0通常又被映射爲虛擬地址0xC0000000,所以同一個地址就具備了三重身份:物理地址0,總線地址0x80000000及虛擬地址0xC0000000。

(1)重要數據結構之struct dma_device

    該結構體抽象了dma controller,部分成員說明如下:

channels:一個鏈表頭,保存該controller支持的所有dma channel

cap_mask:一個bitmap,只是controller所具備的能力

            DMA_MEMCPY:內存到內存的拷貝

            DMA_SG:設備支持內存到內存的分散/聚合傳輸

            DMA_XOR:設備在內存區域執行XOR操作,如raid5等

            DMA_PQ:內存到內存的P+Q計算

           DMA_SLAVE:設備能處理設備到內存傳輸,包括分散/聚合傳輸

           DMA_CYCLIC:設備能處理循環傳輸,如音頻傳輸

src_addr_widths:一個bitmap表示該controller支持哪些寬度的src類型,包括1、2、3、4、8、16、32、64等

dst_addr_widths:一個bitmap表示該controller支持哪些寬的的dst類型,包括1、2、3、4、8、16、32、64等

directions:一個bitmap表示該controller支持哪些傳輸方向,包括DMA_MEM_TO_MEM、DMA_MEM_TO_DEV、DMA_DEV_TO_MEM、DMA_DEV_TO_DEV。

(2)重要數據結構之struct dma_chan

    用於抽象dam channel,部分成員說明如下:

device:指向該channel所在的dma controller

(3)重要數據結構之struct virt_dma_chan

    用於抽象一個虛擬的dma channel,多個虛擬channel可以共用一個物理channel,並由軟件調度多個傳輸請求,將多個虛擬channel的傳輸串行的在物理channel完成,部分成員說明如下:

chan:一個struct dma_chan類型的變量

task:一個tasklet,用於等待該虛擬channel上傳輸的完成

(4)controller需要實現的操作函數集

device_alloc_chan_resources:當client驅動調用dma_request_channel時會調用該函數,負責分配通道需要的資源

device_free_chan_resources:當client驅動釋放dam_release_channel時將會調用該函數,負責釋放通道需要的資源

device_prep_dma_xxx:爲DMA傳輸準備描述符,xxx:controller具有啥cap_mask,就要實現相應的接口

device_issue_pending:從pending queue中取走第一個傳輸描述符並啓動傳輸,當傳輸完成後將會移到列表中下一個傳輸描述符,可以在中斷上下文中使用。

device_tx_status:獲取傳輸狀態

device_config:使用給定參數重新配置通道

device_pause:暫停通道的傳輸

device_resume:恢復通道的傳輸

device_terminate_all:停止通道中所有的傳輸(包括pending和正在進行的傳輸)

dma_async_device_register:把填充好的dma_device結構實體註冊到內核中。

2、dma engine api(consumer的角度)

    DMA傳輸可以分爲4類:mem2mem、mem2dev、dev2mem、dev2dev。mem2mem傳輸內核稱之爲Async TX,後三者統稱爲slave-DMA傳輸。

    linux內核在dma engine之上專門提供了一層針對mem2mem的簡潔API,稱之爲async tx api(例如:async_memcpy, async_memset, async_xor等)。

(1)重要數據結構之struct dma_slave_config

    該結構體包含了完成一次DMA傳輸所需要的所有可能參數,部分成員如下:

direction:如同controller的directions

src_addr:傳輸方向dev2mem或dev2dev時,讀取數據的位置(通常是固定的FIFO地址)

dst_addr:傳輸方向是mem2dev或dev2dev時,寫入數據的位置(通常是固定的FIFO地址)

(2)重要數據結構之struct dam_async_tx_descriptor

    傳輸描述符用於描述一次DMA傳輸(類似一個文件句柄),部分成員說明如下:

cookie:一個整型數,用於追蹤本次傳輸

flags:DMA_CTRL_XXX,例如:DMA_CTRL_REUSE:表明這個描述符可以被重複使用;DMA_CTRL_ACK:表明暫時不能被重複使用

tx_submit:controller driver提供的回調函數,用於把該描述符提交到待傳輸列表

callback、callback_param:傳輸完成的回調函數(及參數)

(3)操作API接口

struct dma_chan *dma_request_channel(dma_cap_mask_t mask, dma_filter_fn filter_fn, void *filter_param)

申請一個DMA channel,當filter_fn爲NULL時函數只是簡單的返回第一個滿足mask參數的通道,對於slave和cyclic通道強烈推薦使用以獲取一個特定的DMA通道

void dma_release_channel(struct dma_chan *chan)

釋放通道

int dmaengine_slave_config(struct dma_chan *chan, struct dam_slave_config *config)

申請到一個dma channel之後,根據實際情況,對該channel進行參數配置

struct dma_async_tx_descriptor *dmaengine_prep_slave_sg( 
        struct dma_chan *chan, struct scatterlist *sgl, 
        unsigned int sg_len, enum dma_data_direction direction, 
        unsigned long flags);

獲取傳輸描述符,用於在“scatter gather buffers”列表和總線設備之間進行DMA傳輸

struct dma_async_tx_descriptor *dmaengine_prep_dma_cyclic( 
        struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len, 
        size_t period_len, enum dma_data_direction direction);

獲取傳輸描述符,常用於音頻等場景中,在進行一定長度的dma傳輸(buf_addr&buf_len)的過程中,每傳輸一定的byte(period_len),就會調用一次傳輸完成的回調函數

struct dma_async_tx_descriptor *dmaengine_prep_interleaved_dma( 
        struct dma_chan *chan, struct dma_interleaved_template *xt, 
        unsigned long flags);

獲取傳輸描述符,可進行不連續的、交叉的DMA傳輸,通常用在圖像處理、顯示等場景中。

dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc)

一旦傳輸描述符準備好並且回調函數也加入後,該函數把傳輸描述符加入到DMA engine驅動的等待隊列,但不會啓動DMA操作

void dma_async_issue_pending(struct dma_chan *chan)

該函數啓動DMA傳輸,此時如果通道是空閒的,等待隊列中的第一個傳輸描述符將會啓動DMA操作。

static inline enum dma_status dma_async_is_tx_complete(struct dma_chan *chan,                                                                     
    dma_cookie_t cookie, dma_cookie_t *last, dma_cookie_t *used)

通過該接口測試傳輸是否完成,也可以通過回調函數獲取傳輸完成的消息

3、測試之驅動

/* 
* dw axi dmac
* author: helb
* date: 2018-08-08
*/
 
 
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#include <linux/dmaengine.h>
 
#define DRIVER_NAME         "axidma"
#define AXIDMA_IOC_MAGIC             'A'
#define AXIDMA_IOCGETCHN            _IO(AXIDMA_IOC_MAGIC, 0)
#define AXIDMA_IOCCFGANDSTART         _IO(AXIDMA_IOC_MAGIC, 1)
#define AXIDMA_IOCGETSTATUS         _IO(AXIDMA_IOC_MAGIC, 2)
#define AXIDMA_IOCRELEASECHN         _IO(AXIDMA_IOC_MAGIC, 3)
 
#define AXI_DMA_MAX_CHANS             8
 
#define DMA_CHN_UNUSED         0
#define DMA_CHN_USED         1
 
struct axidma_chncfg {
    unsigned int src_addr;
    unsigned int dst_addr;
    unsigned int len;
    unsigned char chn_num;
    unsigned char status;
    unsigned char reserve[2];
    unsigned int reserve2;
};
 
struct axidma_chns {
    struct dma_chan *dma_chan;
    unsigned char used;
#define DMA_STATUS_UNFINISHED    0
#define DMA_STATUS_FINISHED        1
    unsigned char status;
    unsigned char reserve[2];
};
 
struct axidma_chns channels[AXI_DMA_MAX_CHANS];
 
static int axidma_open(struct inode *inode, struct file *file)
{
    printk("Open: do nothing\n");
    return 0;
}
 
static int axidma_release(struct inode *inode, struct file *file)
{
    printk("Release: do nothing\n");
    return 0;
}
 
static ssize_t axidma_write(struct file *file, const char __user *data, size_t len, loff_t *ppos)
{
    printk("Write: do nothing\n");
    return 0;
}
 
static void dma_complete_func(void *status)
{
    *(char *)status = DMA_STATUS_FINISHED;
    printk("dma_complete!\n");
}
 
static long axidma_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    struct dma_device *dma_dev;
    struct dma_async_tx_descriptor *tx = NULL;
    dma_cap_mask_t mask;
    dma_cookie_t cookie;
    enum dma_ctrl_flags flags;
 
    struct axidma_chncfg chncfg;
    int ret = -1;
    int i;
 
    memset(&chncfg, 0, sizeof(struct axidma_chncfg));
    switch(cmd)
    {
        case AXIDMA_IOCGETCHN:
        {
            for(i=0; i<AXI_DMA_MAX_CHANS; i++) {
                if(DMA_CHN_UNUSED == channels[i].used)
                    break;                
            }
            if(AXI_DMA_MAX_CHANS == i){
                printk("Get dma chn failed, because no idle channel\n");
                goto error;
            }
            else{
                channels[i].used = DMA_CHN_USED;
                channels[i].status = DMA_STATUS_UNFINISHED;
                chncfg.chn_num = i;
                chncfg.status = DMA_STATUS_UNFINISHED;
            }
 
            dma_cap_zero(mask);
            dma_cap_set(DMA_MEMCPY, mask); 
            channels[i].dma_chan = dma_request_channel(mask, NULL, NULL);
            if(!channels[i].dma_chan){
                printk("dma request channel failed\n");
                channels[i].used = DMA_CHN_UNUSED;
                goto error;
            }
 
            ret = copy_to_user((void __user *)arg, &chncfg, sizeof(struct axidma_chncfg));
            if(ret){
                printk("Copy to user failed\n");
                goto error;
            }
        }
        break;
        case AXIDMA_IOCCFGANDSTART:
        {
            ret = copy_from_user(&chncfg, (void __user *)arg, sizeof(struct axidma_chncfg));
            if(ret){
                printk("Copy from user failed\n");
                goto error;
            }
 
            if((chncfg.chn_num >= AXI_DMA_MAX_CHANS) || (!channels[chncfg.chn_num].dma_chan)){
                printk("chn_num[%d] is invalid\n", chncfg.chn_num);
                goto error;
            }
 
            dma_dev = channels[chncfg.chn_num].dma_chan->device;
            flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
            tx = dma_dev->device_prep_dma_memcpy(channels[chncfg.chn_num].dma_chan, chncfg.dst_addr, chncfg.src_addr, chncfg.len, flags);
            if(!tx){
                printk("Failed to prepare DMA memcpy\n");
                goto error;
            }
            tx->callback = dma_complete_func;
            channels[chncfg.chn_num].status = DMA_STATUS_UNFINISHED;
            tx->callback_param = &channels[chncfg.chn_num].status;
            cookie =  tx->tx_submit(tx);
            if(dma_submit_error(cookie)){
                printk("Failed to dma tx_submit\n");
                goto error;
            }
            dma_async_issue_pending(channels[chncfg.chn_num].dma_chan);
        }
        break;
        case AXIDMA_IOCGETSTATUS:
        {
            ret = copy_from_user(&chncfg, (void __user *)arg, sizeof(struct axidma_chncfg));
            if(ret){
                printk("Copy from user failed\n");
                goto error;
            }
 
            if(chncfg.chn_num >= AXI_DMA_MAX_CHANS){
                printk("chn_num[%d] is invalid\n", chncfg.chn_num);
                goto error;
            }
            chncfg.status = channels[chncfg.chn_num].status;
            ret = copy_to_user((void __user *)arg, &chncfg, sizeof(struct axidma_chncfg));
            if(ret){
                printk("Copy to user failed\n");
                goto error;
            }
        }
        break;
        case AXIDMA_IOCRELEASECHN:
        {
            ret = copy_from_user(&chncfg, (void __user *)arg, sizeof(struct axidma_chncfg));
            if(ret){
                printk("Copy from user failed\n");
                goto error;
            }
            if((chncfg.chn_num >= AXI_DMA_MAX_CHANS) || (!channels[chncfg.chn_num].dma_chan)){
                printk("chn_num[%d] is invalid\n", chncfg.chn_num);
                goto error;
            }
 
            dma_release_channel(channels[chncfg.chn_num].dma_chan);
            channels[chncfg.chn_num].used = DMA_CHN_UNUSED;
            channels[chncfg.chn_num].status = DMA_STATUS_UNFINISHED;
        }
        break;
        default:
            printk("Don't support cmd [%d]\n", cmd);
            break;
    }
    return 0;
error:
    return -EFAULT;
}
 
/*
 *    Kernel Interfaces
 */
 
static struct file_operations axidma_fops = {
    .owner        = THIS_MODULE,
    .llseek        = no_llseek,
    .write        = axidma_write,
    .unlocked_ioctl = axidma_unlocked_ioctl,
    .open        = axidma_open,
    .release    = axidma_release,
};
 
static struct miscdevice axidma_miscdev = {
    .minor        = MISC_DYNAMIC_MINOR,
    .name        = DRIVER_NAME,
    .fops        = &axidma_fops,
};
 
static int __init axidma_init(void)
{
    int ret = 0;
 
    ret = misc_register(&axidma_miscdev);
    if(ret) {
        printk (KERN_ERR "cannot register miscdev (err=%d)\n", ret);
        return ret;
    }
    memset(&channels, 0, sizeof(channels));
 
    return 0;
}
 
static void __exit axidma_exit(void)
{    
    misc_deregister(&axidma_miscdev);
}
 
module_init(axidma_init);
module_exit(axidma_exit);
 
MODULE_AUTHOR("hlb");
MODULE_DESCRIPTION("Axi Dmac Driver");
MODULE_LICENSE("GPL");
 
4、測試之用戶程序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/mman.h>
 
#define DRIVER_NAME         "/dev/axidma"
 
#define AXIDMA_IOC_MAGIC             'A'
#define AXIDMA_IOCGETCHN            _IO(AXIDMA_IOC_MAGIC, 0)
#define AXIDMA_IOCCFGANDSTART         _IO(AXIDMA_IOC_MAGIC, 1)
#define AXIDMA_IOCGETSTATUS         _IO(AXIDMA_IOC_MAGIC, 2)
#define AXIDMA_IOCRELEASECHN         _IO(AXIDMA_IOC_MAGIC, 3)
 
#define DMA_STATUS_UNFINISHED    0
#define DMA_STATUS_FINISHED        1
struct axidma_chncfg {
    unsigned int src_addr;
    unsigned int dst_addr;
    unsigned int len;
    unsigned char chn_num;
    unsigned char status;
    unsigned char reserve[2];
    unsigned int reserve2;
};
 
 
#define SRC_ADDR         0x60000000
#define DST_ADDR         0x70000000
#define DMA_MEMCPY_LEN     0x300000
int main(void)
{
    struct axidma_chncfg chncfg;
    int fd = -1;
    int ret;
    
    printf("AXI dma test, only support mem to mem: copy 0x60000000 to 0x70000000, size:3M\n");
    /* for test */
    read_org_data();
 
    /* open dev */
    fd = open(DRIVER_NAME, O_RDWR);
    if(fd < 0){
        printf("open %s failed\n", DRIVER_NAME);
        return -1;
    }
    
    /* get channel */
    ret = ioctl(fd, AXIDMA_IOCGETCHN, &chncfg);
    if(ret){
        printf("ioctl: get channel failed\n");
        goto error;
    }
    printf("channel: %d\n", chncfg.chn_num);
 
    /* config addr */
    chncfg.src_addr = SRC_ADDR;
    chncfg.dst_addr = DST_ADDR;
    chncfg.len = DMA_MEMCPY_LEN;
    ret = ioctl(fd, AXIDMA_IOCCFGANDSTART, &chncfg);
    if(ret){
        printf("ioctl: config and start dma failed\n");
        goto error;
    }
 
    /* wait finish */
    while(1){
        ret = ioctl(fd, AXIDMA_IOCGETSTATUS, &chncfg);
        if(ret){
            printf("ioctl: get status failed\n");
            goto error;
        }
        if (DMA_STATUS_FINISHED == chncfg.status){
            break;
        }
        printf("status:%d\n", chncfg.status);
        sleep(1);
    }
 
    /* release channel */
    ret = ioctl(fd, AXIDMA_IOCRELEASECHN, &chncfg);
    if(ret){
        printf("ioctl: release channel failed\n");
        goto error;
    }
 
    close(fd);
 
    /* for test */
    read_new_data();
    return 0;
error:
    close(fd);
    return -1;
}
 
 ———————————————— 
版權聲明:本文爲CSDN博主「coolice87」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/heliangbin87/article/details/81530448

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章