Chinaunix首页 | 论坛 | 博客
  • 博客访问: 15069
  • 博文数量: 1
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2015-09-27 23:28
文章分类
文章存档

2022年(1)

我的朋友

分类: LINUX

2022-03-16 15:58:38

原文地址:Linux DMA框架简述 作者:BugMan

在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通道。
点击(此处)折叠或打开

  1. struct dma_chan *__dma_request_channel(const dma_cap_mask_t *mask,
  2.                  dma_filter_fn fn, void *fn_param)
  3. {
  4.     struct dma_device *device, *_d;
  5.     struct dma_chan *chan = NULL;

  6.     /* 从dma_device_list上找到一个合适的dma控制器,并从控制器上获取一个dma channel */
  7.     mutex_lock(&dma_list_mutex);
  8.     list_for_each_entry_safe(device, _d, &dma_device_list, global_node) {
  9.         chan = find_candidate(device, mask, fn, fn_param);
  10.         if (!IS_ERR(chan))
  11.             break;

  12.         chan = NULL;
  13.     }
  14.     mutex_unlock(&dma_list_mutex);

  15.     pr_debug("%s: %s (%s)\n",
  16.          __func__,
  17.          chan ? "success" : "fail",
  18.          chan ? dma_chan_name(chan) : NULL);

  19.     return chan;
  20. }
2、dma通道配置, 对应Linux API, dmaengine_slave_config (chan....)

用户通过该函数,可以配置指定通道的参数,比如目的和源地址,位宽,传输方向等。
点击(此处)折叠或打开

  1. static inline int dmaengine_slave_config(struct dma_chan *chan,
  2.                      struct dma_slave_config *config)
  3. {
  4.     // 直接回调dma控制器的device_config函数
  5.     if (chan->device->device_config)
  6.         return chan->device->device_config(chan, config);

  7.     return -ENOSYS;
  8. }
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步处理。
点击(此处)折叠或打开

  1. static inline struct dma_async_tx_descriptor *dmaengine_prep_slave_sg(
  2.     struct dma_chan *chan, struct scatterlist *sgl,    unsigned int sg_len,
  3.     enum dma_transfer_direction dir, unsigned long flags)
  4. {
  5.     if (!chan || !chan->device || !chan->device->device_prep_slave_sg)
  6.         return NULL;
  7.     // 直接回调 dma控制器的device_prep相关函数
  8.     return chan->device->device_prep_slave_sg(chan, sgl, sg_len,
  9.                          dir, flags, NULL);
  10. }
4、dma数据提交,对应Linux API, dmaengine_submit()

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

  1. static inline dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc)
  2. {
  3.     // 直接回调第3步返回的tx_descriptor结构的tx_submit回调函数
  4.     return desc->tx_submit(desc);
  5. }
5、dma数据处理,用于启动一次事务处理,对应Linux API, dma_async_issue_pending()。
这个函数和其他几个函数一样,就是调用dma_device的对应回调,这里给出原型
点击(此处)折叠或打开

  1. static inline void dma_async_issue_pending(struct dma_chan *chan)
  2. {
  3.     // 直接回调dma控制器的devie_issue_pending函数
  4.     chan->device->device_issue_pending(chan);
  5. }

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

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

  1. struct dma_device {
  2.     unsigned int chancnt;    // 通道个数
  3.     struct list_head channels;   // 用于存放channels结构,所有的channel都会链接到这个链表上
  4.     struct list_head global_node; // 用于链接到dma_device_list链表
  5.     .......
  6.     struct device *dev;
  7.     u32 src_addr_widths;    // 源地址位宽
  8.     u32 dst_addr_widths;    // 目的地址位宽
  9.     u32 directions;         // 支持的传输方向
  10.     .........
  11.     /* 申请channel回调,返回一个channel结构体,后面的回调都要用 */
  12.     int (*device_alloc_chan_resources)(struct dma_chan *chan);
  13.     /* 释放channel回调 */
  14.     void (*device_free_chan_resources)(struct dma_chan *chan);
  15.     /* channel预处理回调 */
  16.     struct dma_async_tx_descriptor *(*device_prep_dma_memcpy)(
  17.         struct dma_chan *chan, dma_addr_t dst, dma_addr_t src,
  18.         size_t len, unsigned long flags);
  19.     /*.......省略一堆类似的预处理回调函数...............*/
  20.     struct dma_async_tx_descriptor *(*device_prep_dma_interrupt)(
  21.         struct dma_chan *chan, unsigned long flags);
  22.     /* channel 配置回调 */
  23.     int (*device_config)(struct dma_chan *chan,
  24.              struct dma_slave_config *config);
  25.     /* channel 传输暂停回调 */
  26.     int (*device_pause)(struct dma_chan *chan);
  27.     /* channel 传输恢复回调 */
  28.     int (*device_resume)(struct dma_chan *chan);
  29.     /* channel传输终止回调 */
  30.     int (*device_terminate_all)(struct dma_chan *chan);
  31.     void (*device_synchronize)(struct dma_chan *chan);
  32.     /* 查看传输状态回调 */
  33.     enum dma_status (*device_tx_status)(struct dma_chan *chan,
  34.                      dma_cookie_t cookie,
  35.                      struct dma_tx_state *txstate);
  36.     /* 处理所有事物回调 */
  37.     void (*device_issue_pending)(struct dma_chan *chan);
  38. }
通过对结构体的观察,因此,实现一个dma控制器驱动的probe函数,大概步骤如下:

点击(此处)折叠或打开

  1. struct dma_device *ddev = NULL;

  2. /* 申请一个dma控制器管理结构 */
  3. ddev = kmalloc(sizeof(struct dma_device)...);

  4. /* 初始化DMA控制器的dma_device结构, 主要是设置对应的回调函数和支持的功能 */
  5. ddev->device_alloc_chan_resources = dma_alloc_chan_resources; // 申请channel
  6. ddev->device_config = dma_slave_config; // 配置channel
  7. ddev->device_prep_slave_sg = dma_prep_slave_sg; // 预处理函数
  8. ddev->device_prep_dma_cyclic = dma_prep_dma_cyclic; //预处理函数1
  9. ddev->device_issue_pending = dma_issue_pending;  // 执行函数
  10. ddev->device_tx_status = dma_tx_status;  // 发送状态函数
  11. ddev->device_pause = dma_pause;   // dma暂停函数
  12. ddev->device_resume = dma_resume; // dma恢复函数
  13. ddev->device_terminate_all = dma_terminate_all; // dma传输终止函数
  14. ddev->device_synchronize = dma_synchronize; // 同步
  15. ddev->device_free_chan_resources = dma_free_chan_resources; // channel 释放函数
  16. ddev->src_addr_widths = BUSWIDTHS;  // 源地址宽度
  17. ddev->dst_addr_widths = BUSWIDTHS;  // 目的地址宽度
  18. ddev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); //支持的方向,能力
  19. ddev->residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
  20. ddev->copy_align = DMAENGINE_ALIGN_8_BYTES;
  21. ddev->desc_metadata_modes = DESC_METADATA_CLIENT DESC_METADATA_ENGINE;

  22. //初始化通道,添加到dma控制器的channels链表上
  23. while (...) {
  24.     struct virt_dma_chan *vc;
  25.     
  26.     ........................
  27.     list_add_tail(&vc->chan.device_node, &udev->channels);
  28. }
  29. // 将dma控制器注册到dmaengine框架的dma_device_list链表
  30. dma_async_device_register(ddev);
  31. // 将dma控制器和dts结点关联,方便以后通过dts来配置dma channel
  32. of_dma_controller_register(dev->of_node, dma_of_xlate, ddev); // dma_of_xlate为转换函数,类似于channel allocate 功能
注:在Linux dma当中, 一个物理channel可以对应多个虚拟的channel。
dma_async_device_register注册函数,实现如下:
点击(此处)折叠或打开
  1. int dma_async_device_register(struct dma_device *device)
  2. {
  3.     int chancnt = 0, rc;
  4.     struct dma_chan* chan;
  5.     atomic_t *idr_ref;

  6.     if (!device)
  7.         return -ENODEV;
  8.     /* 省略一堆参数检测 */

  9.     /* 初始化这个dma控制器上的所有通道 */
  10.     list_for_each_entry(chan, &device->channels, device_node) {
  11.         ........
  12.         rc = device_register(&chan->dev->device);
  13.         .......
  14.     }
  15.     mutex_lock(&dma_list_mutex);

  16.     /* 将DMA控制器结构体 加到dma_device_list链表中,供其他驱动申请 */
  17.     list_add_tail_rcu(&device->global_node, &dma_device_list);
  18.     if (dma_has_cap(DMA_PRIVATE, device->cap_mask))
  19.         device->privatecnt++;    /* Always private */
  20.     dma_channel_rebalance();
  21.     mutex_unlock(&dma_list_mutex);

  22.     return 0;
  23.     ......
  24. }
of_dma_controller_register注册函数,实现如下:
点击(此处)折叠或打开
  1. int of_dma_controller_register(struct device_node *np,
  2.                 struct dma_chan *(*of_dma_xlate)
  3.                 (struct of_phandle_args *, struct of_dma *),
  4.                 void *data)
  5. {
  6.     struct of_dma    *ofdma;

  7.     if (!np || !of_dma_xlate) {
  8.         pr_err("%s: not enough information provided\n", __func__);
  9.         return -EINVAL;
  10.     }

  11.     ofdma = kzalloc(sizeof(*ofdma), GFP_KERNEL);
  12.     if (!ofdma)
  13.         return -ENOMEM;

  14.     ofdma->of_node = np;
  15.     ofdma->of_dma_xlate = of_dma_xlate;
  16.     ofdma->of_dma_data = data;

  17.     /* 将dma控制器关联的dts结构 添加到of_dma_list链表上 */
  18.     mutex_lock(&of_dma_lock);
  19.     list_add_tail(&ofdma->of_dma_controllers, &of_dma_list);
  20.     mutex_unlock(&of_dma_lock);

  21.     return 0;
  22. }
至此,一个dma控制器驱动分析就已经完成了。怎样通过,dts和dma控制器关联呢?一般的
在代码里面调用dma_request_slave_channel()函数申请channel,他会通过dts关联的dma来查找对应的dma控制器。
dma_request_slave_channel()函数实现如下:
点击(此处)折叠或打开
  1. struct dma_chan *dma_request_slave_channel(struct device *dev,
  2.                      const char *name)
  3. {
  4.     struct dma_chan *ch = dma_request_chan(dev, name);
  5.     if (IS_ERR(ch))
  6.         return NULL;

  7.     return ch;
  8. }
其中的dma_request_chan函数最后会调用of_dma_request_slave_channel来申请,而
of_dma_request_slave_channel函数则是使用如下方向查找:
点击(此处)折叠或打开
  1. list_for_each_entry(ofdma, &of_dma_list, of_dma_controllers)
  2. {
  3.     if (ofdma->of_node == dma_spec->np)
  4.         continue;
  5.     ofdma->of_dma_xlate(&dma_spec, ofdma);
  6. }
如上,of_dma_list是dma控制器驱动调用of_dma_controller_register函数添加上去。而of_dma_xlate函数则是dma控制器驱动调用of_dma_controller_register函数传入的一个函数参数。
DTS参考结点配置如下:
点击(此处)折叠或打开
  1. main_uart4: serial@2840000 {
  2.         compatible = "ti,j721e-uart", "ti,am654-uart";
  3.         reg = <0x00 0x02840000 0x00 0x100>;
  4.         reg-shift = <2>;
  5.         reg-io-width = <4>;
  6.         interrupts = <GIC_SPI 196 IRQ_TYPE_LEVEL_HIGH>;
  7.         clock-frequency = <48000000>;
  8.         current-speed = <115200>;
  9.         power-domains = <&k3_pds 281 TI_SCI_PD_EXCLUSIVE>;
  10.         clocks = <&k3_clks 281 0>;
  11.         clock-names = "fclk";
  12.         dmas = <&main_udmap &pdma_main_usart_g2 0 UDMA_DIR_TX>,
  13.             <&main_udmap &pdma_main_usart_g2 0 UDMA_DIR_RX>;
  14.         dma-names = "tx", "rx";
  15.     }
其中,黄色部分就是从设备DTS配置方式。




阅读(2578) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:没有了

给主人留下些什么吧!~~