DMA 传输是由 CPU 发起的:CPU 会告诉 DMA 控制器,帮忙将 source 地方的数据搬到 dest 地方。CPU 发完指令之后,就不管了。具体怎么搬,何时搬,完全由 DMA 控制器决定。DMA 控制器搬运数据的方向有如下几种:
因为 CPU 发起 DMA 传输的时候,并不知道当前是否具备传输条件,例如 source 设备是否有数据、dest 设备的 FIFO 是否空闲等等。那谁知道是否可以传输呢?设备!因此,需要设备和 DMA 控制器之间,有几条物理的连接线(称作DMA request,DRQ),用于通知 DMA 控制器可以开始传输了。
DMA 控制器可以同时进行的传输个数是有限的,每一个传输都需要使用到 DMA 物理通道。DMA 物理通道的数量决定了 DMA 控制器能够同时传输的任务量。
在软件上,DMA 控制器会为外设分配一个 DMA 虚拟通道,这个虚拟通道是根据 DMA request 信号来区分的。
我们知道,一般情况下 DMA 传输只能处理在物理上连续的 buffer。但在有些场景下,我们需要将一些非连续的 buffer 拷贝到一个连续 buffer 中,这样的操作称作 scatter gather。
对于这种非连续的传输,大多时候都是通过软件,将传输分成多个连续的小块。但为了提高传输效率,有些 DMA controller 从硬件上支持了这种操作。
我们先看下一个 DMA 客户端设备驱动涉及到的数据结构有哪些,然后再看下使用 dmaengine 内核框架的步骤。
完成一次 DMA 传输所需要的参数:
struct dma_slave_config {
/*
指明传输的方向
DMA_MEM_TO_MEM,memory到memory的传输;
DMA_MEM_TO_DEV,memory到设备的传输;
DMA_DEV_TO_MEM,设备到memory的传输;
DMA_DEV_TO_DEV,设备到设备的传输。
*/
enum dma_transfer_direction direction;
/*
传输方向是dev2mem或者dev2dev时,读取数据的位置(通常是固定的FIFO地址)。
对mem2dev类型的channel,不需配置该参数(每次传输的时候会指定);
*/
phys_addr_t src_addr;
/*
传输方向是mem2dev或者dev2dev时,写入数据的位置(通常是固定的FIFO地址)。
对dev2mem类型的channel,不需配置该参数(每次传输的时候会指定);
*/
phys_addr_t dst_addr;
//src地址的宽度
enum dma_slave_buswidth src_addr_width;
//dst地址的宽度
enum dma_slave_buswidth dst_addr_width;
//src最大可传输的burst size
u32 src_maxburst;
//dst最大可传输的burst size
u32 dst_maxburst;
u32 src_port_window_size;
u32 dst_port_window_size;
u32 src_fifo_num;
u32 dst_fifo_num;
bool device_fc;
/*
外部设备通过slave_id告诉dma controller自己是谁(一般和某个request line对应)。
很多dma controller并不区分slave,只要给它src、dst、len等信息,它就可以进行传输,因此slave_id可以忽略。
而有些controller,必须清晰地知道此次传输的对象是哪个外设,就必须要提供slave_id了。
*/
unsigned int slave_id;
};
用于描述一次 DMA 传输:
struct dma_async_tx_descriptor {
dma_cookie_t cookie;
/*
DMA_CTRL_开头的标记,包括:
DMA_CTRL_REUSE,表明这个描述符可以被重复使用,直到它被清除或者释放;
DMA_CTRL_ACK,如果该flag为0,表明暂时不能被重复使用。
*/
enum dma_ctrl_flags flags; /* not a 'long' to pack with cookie */
//该描述符的物理地址
dma_addr_t phys;
//对应的dma channel
struct dma_chan *chan;
/*
controller driver提供的回调函数,用于把改描述符提交到待传输列表。
通常由dma engine调用,client driver不会直接和该接口打交道。
*/
dma_cookie_t (*tx_submit)(struct dma_async_tx_descriptor *tx);
/*
用于释放该描述符的回调函数。
通常由dma engine调用,client driver不会直接和该接口打交道。
*/
int (*desc_free)(struct dma_async_tx_descriptor *tx);
//传输完成的回调函数(及其参数),由client driver提供。
dma_async_tx_callback callback;
dma_async_tx_callback_result callback_result;
//传输完成的回调函数(及其参数),由client driver提供。
void *callback_param;
struct dmaengine_unmap_data *unmap;
#ifdef CONFIG_ASYNC_TX_ENABLE_CHANNEL_SWITCH
struct dma_async_tx_descriptor *next;
struct dma_async_tx_descriptor *parent;
spinlock_t lock;
#endif
};
一个设备驱动程序使用 dmaengine 的话一般步骤如下:
然后,重复3~5步。下面具体看下每一步的实现和相关接口:
任何设备驱动在开始 DMA 传输之前,都要申请一个 DMA channel(由“struct dma_chan”数据结构表示),可以通过如下的 API 申请 DMA channel:
struct dma_chan *dma_request_chan(struct device *dev, const char *name);
可以用如下接口释放申请得到的 DMA channel:
void dma_release_channel(struct dma_chan *chan);
设备驱动申请到一个为自己使用的 DMA channel 之后,需要根据自身的实际情况,以及 DMA controller 的能力,对该 channel 进行一些配置。设备驱动将它们填充到一个 struct dma_slave_config 变量中后,可以调用如下 API 将这些信息告诉给 DMA 控制器:
int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config)
DMA 传输属于异步传输,在启动传输之前,设备驱动需要将此次传输的一些信息(例如src/dst的buffer、传输的方向等)提交给 DMA 控制器,DMA 控制器确认好后,返回一个描述符。此后,设备驱动就可以以该描述符为单位,控制并跟踪此次传输。设备驱动可以使用下面三个 API 获取传输描述符:
用于在“scatter gather buffers”列表和总线设备之间进行 DMA 传输:
struct dma_async_tx_descriptor *dmaengine_prep_slave_sg( struct dma_chan *chan, struct scatterlist *sgl, unsigned int sg_len, enum dma_data_direction direction, unsigned long flags);
用于音频等场景中,在进行一定长度的dma传输(buf_addr&buf_len)的过程中,每传输一定的byte(period_len),就会调用一次传输完成的回调函数:
struct dma_async_tx_descriptor *dmaengine_prep_dma_cyclic( struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len, size_t period_len, enum dma_data_direction direction);
可进行不连续的、交叉的DMA传输,通常用在图像处理、显示等场景中:
struct dma_async_tx_descriptor *dmaengine_prep_interleaved_dma( struct dma_chan *chan, struct dma_interleaved_template *xt, unsigned long flags);
获取传输描述符之后,设备驱动可以通过 dmaengine_submit 接口将该描述符放到传输队列上,然后调用 dma_async_issue_pending 接口,启动传输。
dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc);
void dma_async_issue_pending(struct dma_chan *chan);
由这两个 API 的特征可知,内核 DMA Engine 鼓励设备驱动一次提交多个传输,然后由 DMA 控制器统一完成这些传输。
传输请求被提交之后,设备驱动可以通过回调函数获取传输完成的消息,当然,也可以通过 dma_async_is_tx_complete 等 API,测试传输是否完成。如果等不及了,也可以使用 dmaengine_pause、dmaengine_resume、dmaengine_terminate_xxx 等 API,暂停、终止传输。
DMA 控制器驱动主要负责抽象 eDMA 硬件,管理 DMA channel,以 channel 为操作对象,响应 设备驱动的传输请求,并控制 eDMA 驱动,执行传输。
内核的 dmaengine framework 提供了一套 DMA 控制器的开发框架,如下图所示:
下面我们看下 dma_device,dma_chan,virt_dma_chan 的数据结构。
主要用于抽象 eDMA:
struct dma_device {
//一个链表头,用于保存该controller支持的所有dma channel
//在初始化的时候,dma controller driver首先要调用 INIT_LIST_HEAD 初始化它,然后调用 list_add_tail 将所有的 channel 添加到该链表头中。
unsigned int chancnt;
unsigned int privatecnt;
struct list_head channels;
......
/*
一个bitmap,用于指示该dma controller所具备的能力
DMA_MEMCPY,可进行memory copy;
DMA_MEMSET,可进行memory set;
DMA_SG,可进行 scatter list 传输;
DMA_CYCLIC,可进行cyclic类的传输;
DMA_INTERLEAVE,可进行交叉传输;
*/
dma_cap_mask_t cap_mask;
......
int dev_id;
struct device *dev;
//表示该controller支持哪些宽度的src类型。具体可参考 enum dma_slave_buswidth 的定义
u32 src_addr_widths;
//表示该controller支持哪些宽度的dst类型。具体可参考 enum dma_slave_buswidth 的定义
u32 dst_addr_widths;
/*
表示该controller支持哪些传输方向
包括DMA_MEM_TO_MEM、DMA_MEM_TO_DEV、DMA_DEV_TO_MEM、DMA_DEV_TO_DEV,具体可参考enum dma_transfer_direction的定义
*/
u32 directions;
//支持的最大的burst传输的size
u32 max_burst;
//指示该controller的传输描述可否可重复使用
bool descriptor_reuse;
enum dma_residue_granularity residue_granularity;
//申请/释放 dma channel 的时候,dmaengine会调用dma controller driver相应的alloc/free回调函数,以准备相应的资源。
int (*device_alloc_chan_resources)(struct dma_chan *chan);
void (*device_free_chan_resources)(struct dma_chan *chan);
//设备驱动通过 dmaengine_prep_xxx API 获取传输描述符的时候,damengine则会直接回调 eDMA 驱动相应的 device_prep_dma_xxx 接口。
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_imm_data)(
struct dma_chan *chan, dma_addr_t dst, u64 data,
unsigned long flags);
//设备驱动调用 dmaengine_slave_config 配置 dma channel 的时候,dmaengine 会调用该回调函数,交给 eDMA 驱动处理。
int (*device_config)(struct dma_chan *chan,
struct dma_slave_config *config);
//设备驱动调用 dmaengine_pause、dmaengine_resume、dmaengine_terminate_xxx 等API的时候,dmaengine 会调用相应的回调函数,交给 eDMA 驱动处理。
int (*device_pause)(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);
//设备驱动调用 dma_async_issue_pending 启动传输的时候,会调用调用该回调函数。
void (*device_issue_pending)(struct dma_chan *chan);
};
主要用于抽象物理的 DMA channel:
struct dma_chan {
//指向该channel所在的dma controller
struct dma_device *device;
//设备驱动以该 channel 为操作对象获取传输描述符时,eDMA 驱动返回给设备的最后一个cookie。
dma_cookie_t cookie;
//在这个 channel 上最后一次完成的传输的 cookie。eDMA 驱动可以在传输完成时调用辅助函数 dma_cookie_complete 设置它的 value。
dma_cookie_t completed_cookie;
/* sysfs */
int chan_id;
struct dma_chan_dev *dev;
//链表 node,用于将该 channel 添加到 dma_device 的 channel 列表中
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;
};
主要用于抽象一个虚拟的 DMA channel:
struct virt_dma_chan {
//一个 struct dma_chan 类型的变量,用于和设备驱动打交道(屏蔽物理channel和虚拟channel的差异)。
struct dma_chan chan;
//一个 tasklet,用于等待该虚拟 channel 上传输的完成(由于是虚拟channel,传输完成与否只能由软件判断)。
struct tasklet_struct task;
void (*desc_free)(struct virt_dma_desc *);
spinlock_t lock;
/* protected by vc.lock */
//用于保存不同状态的虚拟 channel 描述符(struct virt_dma_desc,仅仅对 struct dma_async_tx_descriptor 做了一个简单的封装)。
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;
};
damengine 直接向 DMA 控制器驱动提供的API并不多,主要包括:
int dma_async_device_register(struct dma_device *device);
void dma_async_device_unregister(struct dma_device *device);
初始化dma channel中的cookie、completed_cookie字段:
static inline void dma_cookie_init(struct dma_chan *chan)
为指针的传输描述(tx)分配一个cookie:
static inline dma_cookie_t dma_cookie_assign(struct dma_async_tx_descriptor *tx)
当某一个传输(tx)完成的时候,可以调用该接口,更新该传输所对应channel的completed_cookie字段:
static inline void dma_cookie_complete(struct dma_async_tx_descriptor *tx)
获取指定channel(chan)上指定cookie的传输状态:
static inline enum dma_status dma_cookie_status(struct dma_chan *chan, dma_cookie_t cookie, struct dma_tx_state *state)
待续...
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/FrYAlidkm3C8wk3ft_a56g
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。