DMA Engine框架(二)
参考资料:
struct dma_device:
struct dma_device{ struct kref ref; unsigned int chancnt; unsigned int privatecnt; struct list_head channels; // 链表头,保存着dmac支持的通道 struct list_head global_node; struct dma_filter filter; dma_cap_mask_t cap_mask; // dmac支持的能力 enum dma_desc_metadata_mode desc_metadata_modes; unsigned short max_xor; unsigned short max_pq; enum dmaengine_alignment copy_align; enum dmaengine_alignment xor_align; enum dmaengine_alignment pq_align; enum dmaengine_alignment fill_align; #define DMA_HAS_PQ_CONTINUE (1 << 15) int dev_id; struct device *dev; struct module *owner; struct ida chan_ida; struct mutex chan_mutex; /* to protect chan_ida */ u32 src_addr_widths; // dmac支持哪些源地址位宽 u32 dst_addr_widths; // dmac支持哪些目的地址位宽 u32 directions; // dmac支持的传输方向,DMA_MEM_TO_MEM、DMA_MEM_TO_DEV、DMA_DEV_TO_MEM、DMA_DEV_TO_DEV四种 u32 min_burst; u32 max_burst; u32 max_sg_burst; bool descriptor_reuse; enum dma_residue_granularity residue_granularity; int (*device_alloc_chan_resources)(struct dma_chan *chan); // client驱动调用dma_request_channel时会调用此函数,分配通道资源 void (*device_free_chan_resources)(struct dma_chan *chan); // client驱动释放dam_release_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); // dmac内存传输拷贝函数 ... ... struct dma_async_tx_descriptor *(*device_prep_slave_sg)( struct dma_chan *chan, struct scatterlist *sgl, unsigned int sg_len, enum dma_transfer_direction direction, unsigned long flags, void *context); // dmac散列表传输的函数 struct dma_async_tx_descriptor *(*device_prep_dma_cyclic)( struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len, size_t period_len, enum dma_transfer_direction direction, unsigned long flags); // cyclic传输 ... ... void (*device_caps)(struct dma_chan *chan, struct dma_slave_caps *caps); int (*device_config)(struct dma_chan *chan, struct dma_slave_config *config); // 配置dmac通道,dmaengine_slave_config时回调 int (*device_pause)(struct dma_chan *chan); // 暂停dmac通道的传输,dmaengine_pause时回调 int (*device_resume)(struct dma_chan *chan); // 恢复dmac通道的传输,dmaengine_resume时回调 int (*device_terminate_all)(struct dma_chan *chan); // 停止dmac通道中所有的传输(包括pending和正在进行的传输),dmaengine_terminate_all时回调 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); // 获取dmac通道传输状态 // 从pending queue中取走第一个传输描述符并启动传输,当传输完成后将会移到列表中下一个传输描述符,可以在中断上下文中使用。dma_async_issue_pending时回调 void (*device_issue_pending)(struct dma_chan *chan); void (*device_release)(struct dma_device *dev); // dmac释放 };
device_prep_dma_xxx,同理,client driver通过dmaengine_prep_xxx API获取传输描述符的时候,damengine则会直接回调dma controller driver相应的device_prep_dma_xxx接口。至于要在这些回调函数中做什么事情,dma controller driver自己决定就是了。
device_config,client driver调用dmaengine_slave_config配置dma channel的时候,dmaengine会调用该回调函数,交给dma controller driver处理。
device_pause/device_resume/device_terminate_all,同理,client driver调用dmaengine_pause、dmaengine_resume、dmaengine_terminate_xxx等API的时候,dmaengine会调用相应的回调函数。
device_issue_pending,client driver调用dma_async_issue_pending启动传输的时候,会调用调用该回调函数。
struct dma_chan:
struct dma_chan { struct dma_device *device; // 指向该channel所在的dmac dma_cookie_t cookie; // client driver以该channel为操作对象获取传输描述符时,dma controller driver返回给client的最后一个cookie dma_cookie_t completed_cookie; // 在这个channel上最后一次完成的传输的cookie。dma controller driver可以在传输完成时调用辅助函数 /* sysfs */ int chan_id; struct dma_chan_dev *dev; struct list_head device_node; // 链表node,用于将该channel添加到dma_device的channel列表中 struct dma_chan_percpu __percpu *local; int client_count; int table_count; /* DMA router */ struct dma_router *router; void *route_data; void *private; }
struct virt_dma_chan:
struct virt_dma_desc { struct dma_async_tx_descriptor tx; // 传输描述符,用于描述一次dma传输 /* protected by vc.lock */ struct list_head node; }; struct virt_dma_chan { struct dma_chan chan; // 一个struct dma_chan类型的变量,用于和client driver打交道(屏蔽物理channel和虚拟channel的差异) struct tasklet_struct task; // tasklet,用于等待该虚拟channel上传输的完成(由于是虚拟channel,传输完成与否只能由软件判断) void (*desc_free)(struct virt_dma_desc *); spinlock_t lock; /* protected by vc.lock */ struct list_head desc_allocated; // 保存不同状态下的虚拟channel描述符 struct list_head desc_submitted; struct list_head desc_issued; struct list_head desc_completed; struct virt_dma_desc *cyclic; };
damengine直接向dma controller driver提供的API并不多(大部分的逻辑交互都位于struct dma_device结构的回调函数中),主要包括:
1、struct dma_device变量的注册和注销接口
/* include/linux/dmaengine.h */ int dma_async_device_register(struct dma_device *device); void dma_async_device_unregister(struct dma_device *device); dma controller driver准备好struct dma_device变量后,可以调用dma_async_device_register将它(controller)注册到kernel中。该接口会对device指针进行一系列的检查, 然后对其做进一步的初始化,最后会放在一个名称为dma_device_list的全局链表上,以便后面使用。 dma_async_device_unregister,注销接口。
2、cookie有关的辅助接口
位于“drivers/dma/dmaengine.h”中,包括
static inline void dma_cookie_init(struct dma_chan *chan) static inline dma_cookie_t dma_cookie_assign(struct dma_async_tx_descriptor *tx) static inline void dma_cookie_complete(struct dma_async_tx_descriptor *tx) static inline enum dma_status dma_cookie_status(struct dma_chan *chan, dma_cookie_t cookie, struct dma_tx_state *state)
由于cookie有关的操作,有很多共性,dmaengine就提供了一些通用实现:
void dma_cookie_init,初始化dma channel中的cookie、completed_cookie字段。 dma_cookie_assign,为指针的传输描述(tx)分配一个cookie。 dma_cookie_complete,当某一个传输(tx)完成的时候,可以调用该接口,更新该传输所对应channel的completed_cookie字段。 dma_cookie_status,获取指定channel(chan)上指定cookie的传输状态。
3、依赖处理接口
void dma_run_dependencies(struct dma_async_tx_descriptor *tx);
由前面的描述可知,client可以同时提交多个具有依赖关系的dma传输。因此当某个传输结束的时候,dma controller driver需要检查是否有依赖该传输的传输,如果有,则传输之。这个检查并传输的过程,可以借助该接口进行(dma controller driver只需调用即可,省很多事)
4、device tree有关的辅助接口
extern struct dma_chan *of_dma_simple_xlate(struct of_phandle_args *dma_spec, struct of_dma *ofdma); extern struct dma_chan *of_dma_xlate_by_chan_id(struct of_phandle_args *dma_spec, struct of_dma *ofdma);
上面两个接口可用于将client device node中有关dma的字段解析出来,并获取对应的dma channel。
编写一个dma controller driver的方法和步骤:
编写一个dma controller driver的基本步骤包括(不考虑虚拟channel的情况):
1、定义一个struct dma_device变量,并根据实际的硬件情况,填充其中的关键字段。
2、根据controller支持的channel个数,为每个channel定义一个struct dma_chan变量,进行必要的初始化后,将每个channel都添加到struct dma_device变量的channels链表中。
3、根据硬件特性,实现struct dma_device变量中必要的回调函数(device_alloc_chan_resources/device_free_chan_resources、device_prep_dma_xxx、device_config、device_issue_pending等等)。
4、调用dma_async_device_register将struct dma_device变量注册到kernel中。
5、当client driver申请dma channel时(例如通过device tree中的dma节点获取),dmaengine core会调用dma controller driver的device_alloc_chan_resources函数,controller driver需要在这个接口中奖该channel的资源准备好。
6、当client driver配置某个dma channel时,dmaengine core会调用dma controller driver的device_config函数,controller driver需要在这个函数中将client想配置的内容准备好,以便进行后续的传输。
7、client driver开始一个传输之前,会把传输的信息通过dmaengine_prep_slave_xxx接口交给controller driver,controller driver需要在对应的device_prep_dma_xxx回调中,将这些要传输的内容准备好,并返回给client driver一个传输描述符。
8、然后,client driver会调用dmaengine_submit将该传输提交给controller driver,此时dmaengine会调用controller driver为每个传输描述符所提供的tx_submit回调函数,controller driver需要在这个函数中将描述符挂到该channel对应的传输队列中。
10、client driver开始传输时,会调用dma_async_issue_pending,controller driver需要在对应的回调函数(device_issue_pending)中,依次将队列上所有的传输请求提交给硬件。