在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;
- }
用户通过该函数,可以配置指定通道的参数,比如目的和源地址,位宽,传输方向等。
点击(此处)折叠或打开
- 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;
- }
这个预处理函数会比较多,因为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);
- }
主要是将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);
- }
点击(此处)折叠或打开
- 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);
- }
点击(此处)折叠或打开
- 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 功能
点击(此处)折叠或打开
- 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;
- ......
- }
- 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;
- }
点击(此处)折叠或打开
- 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;
- }
点击(此处)折叠或打开
- 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);
- }
点击(此处)折叠或打开
- main_uart4: serial@2840000 {
- compatible = "ti,j721e-uart", "ti,am654-uart";
- reg = <0x00 0x02840000 0x00 0x100>;
- reg-shift = <2>;
- reg-io-width = <4>;
- interrupts = <GIC_SPI 196 IRQ_TYPE_LEVEL_HIGH>;
- clock-frequency = <48000000>;
- current-speed = <115200>;
- power-domains = <&k3_pds 281 TI_SCI_PD_EXCLUSIVE>;
- clocks = <&k3_clks 281 0>;
- clock-names = "fclk";
- dmas = <&main_udmap &pdma_main_usart_g2 0 UDMA_DIR_TX>,
- <&main_udmap &pdma_main_usart_g2 0 UDMA_DIR_RX>;
- dma-names = "tx", "rx";
- }