Bootstrap

Linux DMA子系统(2):DMA控制器驱动(provider)

目录

1. 前言

2. 重要的结构体

2.1 struct dma_device

2.2 struct dma_chan

2.3 struct virt_dma_chan

3. 重要的API

3.1 注册及注销API

3.2 cookie相关API

4. DMA控制器驱动的编写步骤

5. 参考文章


1. 前言

本文将从DMA控制器驱动(provider)的角度来介绍DMA Engine,包括重要的结构体和API接口。

DMA控制器驱动主要作用是管理channel并响应client driver(consumer)的传输请求并控制DMA 控制器执行传输。

2. 重要的结构体

2.1 struct dma_device

就像任何其他内核框架一样,整个DMA Engine注册依赖于驱动程序填充结构体并针对框架进行注册,该结构体就是dma_device。

在控制器驱动中做的第一件事就是分配此结构体,此外还需要在其中初始化一些必要的字段,因为struct dma_device结构体很庞大,所以选了重要的成员介绍下。

struct dma_device {
    ...
	struct list_head channels;
    dma_cap_mask_t  cap_mask;
	u32 src_addr_widths;
	u32 dst_addr_widths;
	u32 directions;
	u32 max_burst;

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

	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_xor)(
		struct dma_chan *chan, dma_addr_t dst, dma_addr_t *src,
		unsigned int src_cnt, size_t len, unsigned long flags);
	struct dma_async_tx_descriptor *(*device_prep_dma_xor_val)(
		struct dma_chan *chan, dma_addr_t *src,	unsigned int src_cnt,
		size_t len, enum sum_check_flags *result, unsigned long flags);
	struct dma_async_tx_descriptor *(*device_prep_dma_pq)(
		struct dma_chan *chan, dma_addr_t *dst, dma_addr_t *src,
		unsigned int src_cnt, const unsigned char *scf,
		size_t len, unsigned long flags);
	struct dma_async_tx_descriptor *(*device_prep_dma_pq_val)(
		struct dma_chan *chan, dma_addr_t *pq, dma_addr_t *src,
		unsigned int src_cnt, const unsigned char *scf, size_t len,
		enum sum_check_flags *pqres, unsigned long flags);
	struct dma_async_tx_descriptor *(*device_prep_dma_memset)(
		struct dma_chan *chan, dma_addr_t dest, int value, size_t len,
		unsigned long flags);
	struct dma_async_tx_descriptor *(*device_prep_dma_memset_sg)(
		struct dma_chan *chan, struct scatterlist *sg,
		unsigned int nents, int value, unsigned long flags);
	struct dma_async_tx_descriptor *(*device_prep_dma_interrupt)(
		struct dma_chan *chan, unsigned long flags);

	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);
	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);
	struct dma_async_tx_descriptor *(*device_prep_interleaved_dma)(
		struct dma_chan *chan, struct dma_interleaved_template *xt,
		unsigned long flags);
	struct dma_async_tx_descriptor *(*device_prep_dma_imm_data)(
		struct dma_chan *chan, dma_addr_t dst, u64 data,
		unsigned long flags);

	int (*device_config)(struct dma_chan *chan,
			     struct dma_slave_config *config);
	int (*device_pause)(struct dma_chan *chan);
	int (*device_resume)(struct dma_chan *chan);
	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);
};

enum dma_transaction_type {
	DMA_MEMCPY,
	DMA_XOR,
	DMA_PQ,
	DMA_XOR_VAL,
	DMA_PQ_VAL,
	DMA_MEMSET,
	DMA_MEMSET_SG,
	DMA_INTERRUPT,
	DMA_PRIVATE,
	DMA_ASYNC_TX,
	DMA_SLAVE,
	DMA_CYCLIC,
	DMA_INTERLEAVE,
/* last transaction type for creation of the capabilities mask */
	DMA_TX_TYPE_END,
};

enum dma_slave_buswidth {
	DMA_SLAVE_BUSWIDTH_UNDEFINED = 0,
	DMA_SLAVE_BUSWIDTH_1_BYTE = 1,
	DMA_SLAVE_BUSWIDTH_2_BYTES = 2,
	DMA_SLAVE_BUSWIDTH_3_BYTES = 3,
	DMA_SLAVE_BUSWIDTH_4_BYTES = 4,
	DMA_SLAVE_BUSWIDTH_8_BYTES = 8,
	DMA_SLAVE_BUSWIDTH_16_BYTES = 16,
	DMA_SLAVE_BUSWIDTH_32_BYTES = 32,
	DMA_SLAVE_BUSWIDTH_64_BYTES = 64,
};

enum dma_transfer_direction {
	DMA_MEM_TO_MEM,
	DMA_MEM_TO_DEV,
	DMA_DEV_TO_MEM,
	DMA_DEV_TO_DEV,
	DMA_TRANS_NONE,
};

1. channels:保存DMA控制器支持的所有channel的链表的链表头,在初始化时应该使用 INIT_LIST_HEAD宏将其初始化为链表,随后然后调用list_add_tail将所有的channel添加到该链表头中。

2. cap_mask:一个bitmap,表示DMA控制器支持的传输类型,支持的传输类型见enum dma_transaction_type中的定义:

        DMA_MEMCPY:该设备能够进行内存到内存的复制。

        DMA_XOR:该设备能够对内存区域执行异或操作。

        DMA_SG:该设备支持内存到内存的分散/聚合传输。

        DMA_SLAVE:该设备可以处理设备到内存的传输,包括分散-聚集转移。

        DMA_CYCLIC:该设备可以处理循环传输。

3. src_addr_widths:一个bitmap,表示该控制器支持哪些宽度的src类型,宽度类型见enum dma_slave_buswidth中的定义。

4. dst_addr_widths:一个bitmap,表示该控制器支持哪些宽度的dst类型,宽度类型见enum dma_slave_buswidth中的定义。

5. directions:一个bitmap,表示该控制器支持哪些传输方向,支持的传输方向见enum dma_transfer_direction中的定义。

6. max_burst:该设备支持的最大的burst传输的大小。

7. device_alloc_chan_resources & device_free_chan_resources:分配DMA channel资源并返回分配的描述符数量 & 释放资源DMA channels资源。

8. device_prep_dma_*:准备各种各样的操作,client driver通过dmaengine_prep_* API获取传输描述符的时候,damengine则会直接回调DMA控制器驱动相应的device_prep_dma_*接口。

9. device_config:将新配置推送到通道。client driver调用dmaengine_slave_config配置dma channel的时候,dmaengine会调用该回调函数,交给DMA控制器驱动处理。

10. device_pause & device_resume & device_terminate_all:暂停通道上的传输 & 恢复通道上的传输 & 中止通道上所有未决和正在进行的传输。client driver调用dmaengine_pause、dmaengine_resume、dmaengine_terminate_*等API的时候,dmaengine会调用相应的回调函数。

11. device_issue_pending:获取待处理队列中的第一个事务描述符,并开始传输。每当传输完成时,它应该移动到列表中的下一个事务。client driver调用dma_async_issue_pending启动传输的时候,会调用该回调函数。

dmaengine对DMA控制器仅封装出来一些回调函数,由DMA控制器驱动实现,被client driver调用。

2.2 struct dma_chan

struct dma_chan {
	struct dma_device *device;
	dma_cookie_t cookie;
	dma_cookie_t completed_cookie;

	/* sysfs */
	int chan_id;
	struct dma_chan_dev *dev;

	struct list_head device_node;
	struct dma_chan_percpu __percpu *local;
	int client_count;
	int table_count;

	/* DMA router */
	struct dma_router *router;
	void *route_data;

	void *private;
};

1. device:指向提供此通道的dma设备的指针。

2. cookie:返回给客户端的最后一个 cookie 值。

3. completed_cookie:此通道最后完成的cookie。

4. device_node:用于将其添加到设备(dma_device)通道列表中。

2.3 struct virt_dma_chan

struct virt_dma_chan {
	struct dma_chan	chan;
	struct tasklet_struct task;
	void (*desc_free)(struct virt_dma_desc *);

	spinlock_t lock;

	/* protected by vc.lock */
	struct list_head desc_allocated;
	struct list_head desc_submitted;
	struct list_head desc_issued;
	struct list_head desc_completed;

	struct virt_dma_desc *cyclic;
};

chan:一个struct dma_chan类型的变量,用于和client driver打交道。

task:一个tasklet,用于等待该虚拟channel上传输的完成(由于是虚拟channel,传输完成与否只能由软件判断)。

desc_allocated & desc_submitted & desc_issued & desc_completed:四个链表头,用于保存不同状态的虚拟channel描述符。

cyclic:描述了一个struct dma_async_tx_descriptor的链表。

3. 重要的API

3.1 注册及注销API

int dma_async_device_register(struct dma_device *device);

void dma_async_device_unregister(struct dma_device *device);

参数:struct dma_device *device

作用:将DMA控制器驱动注册进内核、从内核中注销。

3.2 cookie相关API

/* 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)

dma_cookie_init:初始化DMA通道的cookie。

dma_cookie_assign:将DMA engine cookie分配给描述符。

dma_cookie_complete:完成一个描述符。通过更新通道完成cookie标记来标记此描述符完成。将描述符cookie清零以防止意外重复完成(调用者应持有锁以防止并发)。

dma_cookie_status:报告cookie状态。

4. DMA控制器驱动的编写步骤

编写一个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对应的传输队列中。

(9)client driver开始传输时,会调用dma_async_issue_pending,controller driver需要在对应的回调函数(device_issue_pending)中,依次将队列上所有的传输请求提交给硬件。

(10)其他需要的步骤。

5. 参考文章

\Documentation\dmaengine\provider.txt

Linux内核4.14版本——DMA Engine框架分析(3)_dma controller驱动_风雨兼程8023的博客-CSDN博客

;