在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 = <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";
-
}
其中,黄色部分就是从设备DTS配置方式。
阅读(18257) | 评论(0) | 转发(1) |