在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;
}
2、dma通道配置, 对应Linux API,dmaengine_slave_config (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;
}
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步处理。点击(此处)折叠或打开
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);
}
4、dma数据提交,对应Linux API,dmaengine_submit()。
主要是将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);
}
5、dma数据处理,用于启动一次事务处理,对应Linux API,dma_async_issue_pending()。
这个函数和其他几个函数一样,就是调用dma_device的对应回调,这里给出原型
点击(此处)折叠或打开
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);
}
通过对结构体的观察,因此,实现一个dma控制器驱动的probe函数,大概步骤如下:
点击(此处)折叠或打开
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 功能
注:在Linux dma当中,一个物理channel可以对应多个虚拟的channel。
dma_async_device_register注册函数,实现如下:
点击(此处)折叠或打开
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;
......
}
of_dma_controller_register注册函数,实现如下:
点击(此处)折叠或打开
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;
}
至此,一个dma控制器驱动分析就已经完成了。怎样通过,dts和dma控制器关联呢?一般的
在代码里面调用dma_request_slave_channel()函数申请channel,他会通过dts关联的dma来查找对应的dma控制器。
dma_request_slave_channel()函数实现如下:
点击(此处)折叠或打开
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;
}
其中的dma_request_chan函数最后会调用of_dma_request_slave_channel来申请,而
of_dma_request_slave_channel函数则是使用如下方向查找:
点击(此处)折叠或打开
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);
}
如上,of_dma_list是dma控制器驱动调用of_dma_controller_register函数添加上去。而of_dma_xlate函数则是dma控制器驱动调用of_dma_controller_register函数传入的一个函数参数。
DTS参考结点配置如下:
点击(此处)折叠或打开main_uart4: serial@2840000 {
compatible = "ti,j721e-uart", "ti,am654-uart";
reg = <0x00 0x02840000 0x00 0x100>;
reg-shift = <2>;
reg-io-width = <4>;
interrupts = ;
clock-frequency = <48000000>;
current-speed = <115200>;
power-domains = ;
clocks = ;
clock-names = "fclk";
dmas= ,
;
dma-names= "tx", "rx";
}
其中,黄色部分就是从设备DTS配置方式。