Linux下DMA添加两个channel,Linux DMA框架简述

在Linux当中有一个专门处理DMA的框架,叫做dmaengine,它的代码实现在drivers/dma/dmaengine.c。这个文件主要是提供一套DMA使用的抽象层,但是封装的也比较简单。下面,我主要讲讲做一个Linux的dma驱动,在框架上应该注意的事项。

从使用上来讲,通常我们让DMA工作,大概都是5步,我叫做DMA 5步曲。是哪5步呢?

1、dma通道请求,对应Linux API , chan = dma_request_channel(....)

用户通过该函数,可以向DMA框架申请一个DMA通道。

点击(此处)折叠或打开

struct dma_chan *__dma_request_channel(const dma_cap_mask_t *mask,

dma_filter_fn fn, void *fn_param)

{

struct dma_device *device, *_d;

struct dma_chan *chan = NULL;

/* 从dma_device_list上找到一个合适的dma控制器,并从控制器上获取一个dma channel*/

mutex_lock(&dma_list_mutex);

list_for_each_entry_safe(device, _d, &dma_device_list, global_node) {

chan = find_candidate(device, mask, fn, fn_param);

if (!IS_ERR(chan))

break;

chan = NULL;

}

mutex_unlock(&dma_list_mutex);

pr_debug("%s: %s (%s)\n",

__func__,

chan ? "success" : "fail",

chan ? dma_chan_name(chan) : NULL);

return chan;

}

2、dma通道配置, 对应Linux API,dmaengine_slave_config (chan....)

用户通过该函数,可以配置指定通道的参数,比如目的和源地址,位宽,传输方向等。

点击(此处)折叠或打开

static inline int dmaengine_slave_config(struct dma_chan *chan,

struct dma_slave_config *config)

{

// 直接回调dma控制器的device_config函数

if (chan->device->device_config)

return chan->device->device_config(chan, config);

return -ENOSYS;

}

3、dma通道预处理,对应Linux的API,dmaengine_prep_*。

这个预处理函数会比较多,因为DMA支持很多不同类型的处理,比如DEV TO DEV, MEM TO MEM, MEM TO DEV, DEV TO MEM等。但是都会以dmaengine_prep开头,这个API返回做好预处理的DMA TX结构,用于第4步处理。点击(此处)折叠或打开

static inline struct dma_async_tx_descriptor *dmaengine_prep_slave_sg(

struct dma_chan *chan, struct scatterlist *sgl,    unsigned int sg_len,

enum dma_transfer_direction dir, unsigned long flags)

{

if (!chan || !chan->device || !chan->device->device_prep_slave_sg)

return NULL;

// 直接回调 dma控制器的device_prep相关函数

return chan->device->device_prep_slave_sg(chan, sgl, sg_len,

dir, flags, NULL);

}

4、dma数据提交,对应Linux API,dmaengine_submit()。

主要是将DMA处理事务提交到dma通道处理链上,这个submit用的是第四步得到的DMA TX结构。点击(此处)折叠或打开

static inline dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc)

{

// 直接回调第3步返回的tx_descriptor结构的tx_submit回调函数

return desc->tx_submit(desc);

}

5、dma数据处理,用于启动一次事务处理,对应Linux API,dma_async_issue_pending()。

这个函数和其他几个函数一样,就是调用dma_device的对应回调,这里给出原型

点击(此处)折叠或打开

static inline void dma_async_issue_pending(struct dma_chan *chan)

{

// 直接回调dma控制器的devie_issue_pending函数

chan->device->device_issue_pending(chan);

}

注:以上这些所谓的dmaengine框架提供的DMA函数,其实都是间接调用了封装在struct dma_device里面的回调函数。

通过如上观察,我们在实现一个dma控制器驱动的时候,其实最主要的就是将struct dma_device这个结构体填充好,然后通过函数dma_async_device_register()将其挂接到dma_device_list对应的链表上就可以了,下面是这个结构体的实现:

点击(此处)折叠或打开

struct dma_device {

unsigned int chancnt;    //通道个数

struct list_head channels;   // 用于存放channels结构,所有的channel都会链接到这个链表上

struct list_head global_node; // 用于链接到dma_device_list链表

.......

struct device *dev;

u32 src_addr_widths;    // 源地址位宽

u32 dst_addr_widths;    // 目的地址位宽

u32 directions;         // 支持的传输方向

.........

/* 申请channel回调,返回一个channel结构体,后面的回调都要用 */

int (*device_alloc_chan_resources)(struct dma_chan *chan);

/* 释放channel回调 */

void (*device_free_chan_resources)(struct dma_chan *chan);

/* channel预处理回调 */

struct dma_async_tx_descriptor *(*device_prep_dma_memcpy)(

struct dma_chan *chan, dma_addr_t dst, dma_addr_t src,

size_t len, unsigned long flags);

/*.......省略一堆类似的预处理回调函数...............*/

struct dma_async_tx_descriptor *(*device_prep_dma_interrupt)(

struct dma_chan *chan, unsigned long flags);

/* channel 配置回调 */

int (*device_config)(struct dma_chan *chan,

struct dma_slave_config *config);

/* channel 传输暂停回调 */

int (*device_pause)(struct dma_chan *chan);

/* channel 传输恢复回调 */

int (*device_resume)(struct dma_chan *chan);

/* channel传输终止回调 */

int (*device_terminate_all)(struct dma_chan *chan);

void (*device_synchronize)(struct dma_chan *chan);

/* 查看传输状态回调 */

enum dma_status (*device_tx_status)(struct dma_chan *chan,

dma_cookie_t cookie,

struct dma_tx_state *txstate);

/* 处理所有事物回调 */

void (*device_issue_pending)(struct dma_chan *chan);

}

通过对结构体的观察,因此,实现一个dma控制器驱动的probe函数,大概步骤如下:

点击(此处)折叠或打开

struct dma_device *ddev = NULL;

/* 申请一个dma控制器管理结构 */

ddev = kmalloc(sizeof(struct dma_device)...);

/* 初始化DMA控制器的dma_device结构, 主要是设置对应的回调函数和支持的功能 */

ddev->device_alloc_chan_resources = dma_alloc_chan_resources; // 申请channel

ddev->device_config = dma_slave_config; // 配置channel

ddev->device_prep_slave_sg = dma_prep_slave_sg; // 预处理函数

ddev->device_prep_dma_cyclic = dma_prep_dma_cyclic; //预处理函数1

ddev->device_issue_pending = dma_issue_pending;  // 执行函数

ddev->device_tx_status = dma_tx_status;  // 发送状态函数

ddev->device_pause = dma_pause;   // dma暂停函数

ddev->device_resume = dma_resume; // dma恢复函数

ddev->device_terminate_all = dma_terminate_all; // dma传输终止函数

ddev->device_synchronize = dma_synchronize; // 同步

ddev->device_free_chan_resources = dma_free_chan_resources; // channel 释放函数

ddev->src_addr_widths = BUSWIDTHS;  // 源地址宽度

ddev->dst_addr_widths = BUSWIDTHS;  // 目的地址宽度

ddev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); //支持的方向,能力

ddev->residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;

ddev->copy_align = DMAENGINE_ALIGN_8_BYTES;

ddev->desc_metadata_modes = DESC_METADATA_CLIENT |DESC_METADATA_ENGINE;

//初始化通道,添加到dma控制器的channels链表上

while (...) {

struct virt_dma_chan *vc;

........................

list_add_tail(&vc->chan.device_node, &udev->channels);

}

// 将dma控制器注册到dmaengine框架的dma_device_list链表

dma_async_device_register(ddev);

// 将dma控制器和dts结点关联,方便以后通过dts来配置dma channel

of_dma_controller_register(dev->of_node, dma_of_xlate, ddev); // dma_of_xlate为转换函数,类似于channel allocate 功能

注:在Linux dma当中,一个物理channel可以对应多个虚拟的channel。

dma_async_device_register注册函数,实现如下:

点击(此处)折叠或打开

int dma_async_device_register(struct dma_device *device)

{

int chancnt = 0, rc;

struct dma_chan* chan;

atomic_t *idr_ref;

if (!device)

return -ENODEV;

/* 省略一堆参数检测 */

/* 初始化这个dma控制器上的所有通道 */

list_for_each_entry(chan, &device->channels, device_node) {

........

rc = device_register(&chan->dev->device);

.......

}

mutex_lock(&dma_list_mutex);

/* 将DMA控制器结构体 加到dma_device_list链表中,供其他驱动申请 */

list_add_tail_rcu(&device->global_node, &dma_device_list);

if (dma_has_cap(DMA_PRIVATE, device->cap_mask))

device->privatecnt++;    /* Always private */

dma_channel_rebalance();

mutex_unlock(&dma_list_mutex);

return 0;

......

}

of_dma_controller_register注册函数,实现如下:

点击(此处)折叠或打开

int of_dma_controller_register(struct device_node *np,

struct dma_chan *(*of_dma_xlate)

(struct of_phandle_args *, struct of_dma *),

void *data)

{

struct of_dma    *ofdma;

if (!np || !of_dma_xlate) {

pr_err("%s: not enough information provided\n", __func__);

return -EINVAL;

}

ofdma = kzalloc(sizeof(*ofdma), GFP_KERNEL);

if (!ofdma)

return -ENOMEM;

ofdma->of_node = np;

ofdma->of_dma_xlate= of_dma_xlate;

ofdma->of_dma_data = data;

/* 将dma控制器关联的dts结构 添加到of_dma_list链表上 */

mutex_lock(&of_dma_lock);

list_add_tail(&ofdma->of_dma_controllers, &of_dma_list);

mutex_unlock(&of_dma_lock);

return 0;

}

至此,一个dma控制器驱动分析就已经完成了。怎样通过,dts和dma控制器关联呢?一般的

在代码里面调用dma_request_slave_channel()函数申请channel,他会通过dts关联的dma来查找对应的dma控制器。

dma_request_slave_channel()函数实现如下:

点击(此处)折叠或打开

struct dma_chan *dma_request_slave_channel(struct device *dev,

const char *name)

{

struct dma_chan *ch = dma_request_chan(dev, name);

if (IS_ERR(ch))

return NULL;

return ch;

}

其中的dma_request_chan函数最后会调用of_dma_request_slave_channel来申请,而

of_dma_request_slave_channel函数则是使用如下方向查找:

点击(此处)折叠或打开

list_for_each_entry(ofdma, &of_dma_list, of_dma_controllers)

{

if (ofdma->of_node == dma_spec->np)

continue;

ofdma->of_dma_xlate(&dma_spec, ofdma);

}

如上,of_dma_list是dma控制器驱动调用of_dma_controller_register函数添加上去。而of_dma_xlate函数则是dma控制器驱动调用of_dma_controller_register函数传入的一个函数参数。

DTS参考结点配置如下:

点击(此处)折叠或打开main_uart4: serial@2840000 {

compatible = "ti,j721e-uart", "ti,am654-uart";

reg = <0x00 0x02840000 0x00 0x100>;

reg-shift = <2>;

reg-io-width = <4>;

interrupts = ;

clock-frequency = <48000000>;

current-speed = <115200>;

power-domains = ;

clocks = ;

clock-names = "fclk";

dmas= ,

;

dma-names= "tx", "rx";

}

其中,黄色部分就是从设备DTS配置方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值