Linux DMA驱动构架分析
以linux2.6.32中的S3C2440驱动为例进行分析,DMA驱动所对应的源码为linux-2.6.32.2\arch
\arm\mach-s3c2440\dma.c,代码入口为:
arch_initcall(s3c2440_dma_init);
205 static int __init s3c2440_dma_init(void)
206 {
207 return sysdev_driver_register(&s3c2440_sysclass, &s3c2440_dma_driver);
208 }
DMA驱动作为系统驱动由sysdev_driver_register来向内核注册,这里只关注s3c2440_dma_driver相关的
内容,即调用drive中的add方法,其他的kobject对象略过。
201 static struct sysdev_driver s3c2440_dma_driver = {
202 .add = s3c2440_dma_add,
203 };
s3c2440_dma_add做了一系列的初始化工作,相应的代码如下:
194 static int __init s3c2440_dma_add(struct sys_device *sysdev)
195 {
196 s3c2410_dma_init();
197 s3c24xx_dma_order_set(&s3c2440_dma_order);
198 return s3c24xx_dma_init_map(&s3c2440_dma_sel);
199 }
下面就其中出的三个函数一一进行分析。
一、 s3c2410_dma_init
首先s3c2410_dma_init()调用了plat-s3c24xx平台公共的函数s3c24xx_dma_init(4, IRQ_DMA0, 0x40);
1306 int __init s3c24xx_dma_init(unsigned int channels, unsigned int irq,
1307 unsigned int stride)
1308 {
1309 struct s3c2410_dma_chan *cp;
1310 int channel;
1311 int ret;
1312
1313 printk("S3C24XX DMA Driver, (c) 2003-2004,2006 Simtec Electronics\n");
1314
1315 dma_channels = channels;
1316
1317 dma_base = ioremap(S3C24XX_PA_DMA, stride * channels);
1318 if (dma_base == NULL) {
1319 printk(KERN_ERR "dma failed to remap register block\n");
1320 return -ENOMEM;
1321 }
1322
1323 dma_kmem = kmem_cache_create("dma_desc",
1324 sizeof(struct s3c2410_dma_buf), 0,
1325 SLAB_HWCACHE_ALIGN,
1326 s3c2410_dma_cache_ctor);
1327
1328 if (dma_kmem == NULL) {
1329 printk(KERN_ERR "dma failed to make kmem cache\n");
1330 ret = -ENOMEM;
1331 goto err;
1332 }
1333
1334 for (channel = 0; channel < channels; channel++) {
1335 cp = &s3c2410_chans[channel];
1336
1337 memset(cp, 0, sizeof(struct s3c2410_dma_chan));
1338
1339 /* dma channel irqs are in order.. */
1340 cp->number = channel;
1341 cp->irq = channel + irq;
1342 cp->regs = dma_base + (channel * stride);
1343
1344 /* point current stats somewhere */
1345 cp->stats = &cp->stats_store;
1346 cp->stats_store.timeout_shortest = LONG_MAX;
1347
1348 /* basic channel configuration */
1349
1350 cp->load_timeout = 1<<18;
1351
1352 printk("DMA channel %d at %p, irq %d\n",
1353 cp->number, cp->regs, cp->irq);
1354 }
1355
1356 return 0;
1357
1358 err:
1359 kmem_cache_destroy(dma_kmem);
1360 iounmap(dma_base);
1361 dma_base = NULL;
1362 return ret;
1363 }
1364
首先来关注一下函数传递的参数:
unsigned int channels:s3c2440平台对应的DMA通道总数,为4
unsigned int irq:起始DMA中断的中断号
unsigned int stride:每通道DMA所占寄存器资源数
1309行struct s3c2410_dma_chan记录dma通道信息,内容如下:
151 struct s3c2410_dma_chan {
152 /* channel state flags and information */
153 unsigned char number; //dma通道号,
154 unsigned char in_use; //当前通道是否已经使用
155 unsigned char irq_claimed; // 有无dma中断
156 unsigned char irq_enabled; //是否使能了dma中断
157 unsigned char xfer_unit; //传输块大小
158
159 /* channel state */
160
161 enum s3c2410_dma_state state;
162 enum s3c2410_dma_loadst load_state;
163 struct s3c2410_dma_client *client;
164
165 /* channel configuration */
166 enum s3c2410_dmasrc source;
167 enum dma_ch req_ch;
168 unsigned long dev_addr;
169 unsigned long load_timeout;
170 unsigned int flags; /* channel flags */
171
172 struct s3c24xx_dma_map *map; /* channel hw maps */
173
174 /* channel's hardware position and configuration */
175 void __iomem *regs; /* channels registers */
176 void __iomem *addr_reg; /* data address register */
177 unsigned int irq; 中断号
178 unsigned long dcon; /默认控制寄存器的值
179
180 /* driver handles */
181 s3c2410_dma_cbfn_t callback_fn; 传输完成回调函数
182 s3c2410_dma_opfn_t op_fn; 操作完成回调函数*/
183
184 /* stats gathering */
185 struct s3c2410_dma_stats *stats;
186 struct s3c2410_dma_stats stats_store;
187
188 /* buffer list and information */
189 struct s3c2410_dma_buf *curr; /* current dma buffer */
190 struct s3c2410_dma_buf *next; /* next buffer to load */
191 struct s3c2410_dma_buf *end; /* end of queue */dma缓冲区链表
192
193 /* system device */
194 struct sys_device dev;
195 };
1315行dma_channels是全局变量记录了当前系统dma通道总数
1317-1321行映射dma控制寄存器
1323-1332行为struct s3c2410_dma_buf分配cache,并利用函数s3c2410_dma_cache_ctor初始化为0。
1334-1354行的循环就是初始化全局数组struct s3c2410_dma_chan s3c2410_chans[S3C_DMA_CHANNELS];
,其中包括通道编号、中断号以及寄存器基地址等信息。这个数组是针对实际的硬件信息建立的,每个
硬件的dma通道唯一对应一个struct s3c2410_dma_chan的数据结构。与之对应的还有一个虚拟的dma通道
,其实质是将不同dma请求源区分开来,然后用一个虚拟的通道号与之一一对应,然后与实际的dma通道
通过一张map表关联起来。关于map的相关内容后面将会分析。
二、 s3c24xx_dma_order_set
首先这个函数的意义是预定一些目标板要用的dma通道,使用的是上文提到的虚拟的dma通道号。
1475 int __init s3c24xx_dma_order_set(struct s3c24xx_dma_order *ord)
1476 {
1477 struct s3c24xx_dma_order *nord = dma_order;
1478
1479 if (nord == NULL)
1480 nord = kmalloc(sizeof(struct s3c24xx_dma_order), GFP_KERNEL);
1481
1482 if (nord == NULL) {
1483 printk(KERN_ERR "no memory to store dma channel order\n");
1484 return -ENOMEM;
1485 }
1486
1487 dma_order = nord;
1488 memcpy(nord, ord, sizeof(struct s3c24xx_dma_order));
1489 return 0;
1490 }
1477行dma_order是个全局变量,其作用是记录下目标板的dma预定信息。这里使用的是
s3c2440_dma_order为其赋值,数据如下:
51 static struct s3c24xx_dma_order __initdata s3c2440_dma_order = {
52 .channels = {
53 [DMACH_SDI] = {
54 .list = {
55 [0] = 3 | DMA_CH_VALID,
56 [1] = 2 | DMA_CH_VALID,
57 [2] = 1 | DMA_CH_VALID,
58 [3] = 0 | DMA_CH_VALID,
59 },
60 },
61 [DMACH_I2S_IN] = {
62 .list = {
63 [0] = 1 | DMA_CH_VALID,
64 [1] = 2 | DMA_CH_VALID,
65 },
66 },
67 [DMACH_I2S_OUT] = {
68 .list = {
69 [0] = 2 | DMA_CH_VALID,
70 [1] = 1 | DMA_CH_VALID,
71 },
72 },
73 [DMACH_PCM_IN] = {
74 .list = {
75 [0] = 2 | DMA_CH_VALID,
76 [1] = 1 | DMA_CH_VALID,
77 },
78 },
79 [DMACH_PCM_OUT] = {
80 .list = {
81 [0] = 1 | DMA_CH_VALID,
82 [1] = 3 | DMA_CH_VALID,
83 },
84 },
85 [DMACH_MIC_IN] = {
86 .list = {
87 [0] = 3 | DMA_CH_VALID,
88 [1] = 2 | DMA_CH_VALID,
89 },
90 },
91 },
92 };
[DMACH_SDI]、 [DMACH_I2S_IN]等是系统为dma所分配的虚拟dma通道号,犹如中断子系统为中断分配的
中断号一样,与具体硬件的中断向量号是不一致的。后面我们在系统中使用的dma通道号,都将是内核虚
拟出来的, s3c2410_dma_request函数将为用户找到硬件对应的dma通道号。提取上面[DMACH_SDI] 虚拟
通道来分析一下:
[DMACH_SDI] = {
54 .list = {
55 [0] = 3 | DMA_CH_VALID,
56 [1] = 2 | DMA_CH_VALID,
57 [2] = 1 | DMA_CH_VALID,
58 [3] = 0 | DMA_CH_VALID,
59 },
60 },
List这个结构列出的是实际dma通道的可用信息,这里表面对于sdi所能够使用的dma通道包含通道
3,2,1,0一共四个通道,为什么从大到小排列是因为某些dma请求只能使用dma0,dma1等较小的通道号,
比如外部总线dma只能使用dma0,为了避免sdi占用,这里就采用了这种排列。
三、 s3c24xx_dma_init_map
上面提到过一个map,这里就是为这个map的初始化函数了。他实际是根据硬件情况为一个全局变量赋值
。与前面的初始化一样,这里主要是为了统一管理plat24xx这个平台下的dma资源,所以不同的芯片必须
将自己硬件有关的dma信息初始化到相应的全局变量中。再说函数之前先来关注一下struct
s3c24xx_dma_map这个数据结构,他提供了dma虚拟通道与实际的dma通道直接的关联:
struct s3c24xx_dma_map {
const char *name;//虚拟dma通道名称
struct s3c24xx_dma_addr hw_addr;
unsigned long channels[S3C_DMA_CHANNELS];//实际dma通道信息
unsigned long channels_rx[S3C_DMA_CHANNELS];
};
上面的结构只提供了单个虚拟通道的dma视图,整个芯片的虚拟dma通道的分配情况是靠struct
s3c24xx_dma_map数组完成的。在这里由struct s3c24XX_dma_selection来统一管理。
struct s3c24xx_dma_selection {
struct s3c24xx_dma_map *map;//记录了struct s3c24xx_dma_map数组的首地址
unsigned long map_size;// struct s3c24xx_dma_map数组的成员个数
unsigned long dcon_mask;//dma控制器掩码
void (*select)(struct s3c2410_dma_chan *chan,
struct s3c24xx_dma_map *map);//虚拟通道选择函数
void (*direction)(struct s3c2410_dma_chan *chan,
struct s3c24xx_dma_map *map,
enum s3c2410_dmasrc dir);//dma方向
};
有了上面的背景以后下面函数就是简单的数据拷贝了,函数比较简单不在展开说明。
1454 int __init s3c24xx_dma_init_map(struct s3c24xx_dma_selection *sel)
1455 {
1456 struct s3c24xx_dma_map *nmap;
1457 size_t map_sz = sizeof(*nmap) * sel->map_size;
1458 int ptr;
1459
1460 nmap = kmalloc(map_sz, GFP_KERNEL);
1461 if (nmap == NULL)
1462 return -ENOMEM;
1463
1464 memcpy(nmap, sel->map, map_sz);
1465 memcpy(&dma_sel, sel, sizeof(*sel));
1466
1467 dma_sel.map = nmap;
1468
1469 for (ptr = 0; ptr < sel->map_size; ptr++)
1470 s3c24xx_dma_check_entry(nmap+ptr, ptr);
1471
1472 return 0;
1473 }
初始化的任务比较简单,就是
(1)建立硬件dma通道信息即:
struct s3c2410_dma_chan s3c2410_chans[S3C_DMA_CHANNELS];
(2)建立目标板虚拟dma通道与硬件的dma通道的关联:
static struct s3c24xx_dma_order *dma_order;
(3)建立芯片本身的虚拟dma通道与硬件dma通道的视图:
static struct s3c24xx_dma_selection dma_sel;
完成上述工作以后,基本的dma框架就已经建立起来了。接下分析dma使用过程中相关的函数:
(一) s3c2410_dma_request
715 int s3c2410_dma_request(unsigned int channel,
716 struct s3c2410_dma_client *client,
717 void *dev)
718 {
719 struct s3c2410_dma_chan *chan;
720 unsigned long flags;
721 int err;
722
723 pr_debug("dma%d: s3c2410_request_dma: client=%s, dev=%p\n",
724 channel, client->name, dev);
725
726 local_irq_save(flags);
727
728 chan = s3c2410_dma_map_channel(channel);
729 if (chan == NULL) {
730 local_irq_restore(flags);
731 return -EBUSY;
732 }
733
734 dbg_showchan(chan);
735
736 chan->client = client;
737 chan->in_use = 1;
738
739 if (!chan->irq_claimed) {
740 pr_debug("dma%d: %s : requesting irq %d\n",
741 channel, __func__, chan->irq);
742
743 chan->irq_claimed = 1;
744 local_irq_restore(flags);
745
746 err = request_irq(chan->irq, s3c2410_dma_irq, IRQF_DISABLED,
747 client->name, (void *)chan);
748
749 local_irq_save(flags);
750
751 if (err) {
752 chan->in_use = 0;
753 chan->irq_claimed = 0;
754 local_irq_restore(flags);
755
756 printk(KERN_ERR "%s: cannot get IRQ %d for DMA %d\n",
757 client->name, chan->irq, chan->number);
758 return err;
759 }
760
761 chan->irq_enabled = 1;
762 }
763
764 local_irq_restore(flags);
765
766 /* need to setup */
767
768 pr_debug("%s: channel initialised, %p\n", __func__, chan);
769
770 return chan->number | DMACH_LOW_LEVEL;
771 }
728行s3c2410_dma_map_channel为虚拟的dma通道可用的硬件dma资源,相应的代码如下:
1388 static struct s3c2410_dma_chan *s3c2410_dma_map_channel(int channel)
1389 {
1390 struct s3c24xx_dma_order_ch *ord = NULL;
1391 struct s3c24xx_dma_map *ch_map;
1392 struct s3c2410_dma_chan *dmach;
1393 int ch;
1394
1395 if (dma_sel.map == NULL || channel > dma_sel.map_size)
1396 return NULL;
1397
1398 ch_map = dma_sel.map + channel;
1399
1400 /* first, try the board mapping */
1401
1402 if (dma_order) {
1403 ord = &dma_order->channels[channel];
1404
1405 for (ch = 0; ch < dma_channels; ch++) {
1406 if (!is_channel_valid(ord->list[ch]))
1407 continue;
1408
1409 if (s3c2410_chans[ord->list[ch]].in_use == 0) {
1410 ch = ord->list[ch] & ~DMA_CH_VALID;
1411 goto found;
1412 }
1413 }
1414
1415 if (ord->flags & DMA_CH_NEVER)
1416 return NULL;
1417 }
1418
1419 /* second, search the channel map for first free */
1420
1421 for (ch = 0; ch < dma_channels; ch++) {
1422 if (!is_channel_valid(ch_map->channels[ch]))
1423 continue;
1424
1425 if (s3c2410_chans[ch].in_use == 0) {
1426 printk("mapped channel %d to %d\n", channel, ch);
1427 break;
1428 }
1429 }
1430
1431 if (ch >= dma_channels)
1432 return NULL;
1433
1434 /* update our channel mapping */
1435
1436 found:
1437 dmach = &s3c2410_chans[ch];
1438 dmach->map = ch_map;
1439 dmach->req_ch = channel;
1440 s3c_dma_chan_map[channel] = dmach;
1441
1442 /* select the channel */
1443
1444 (dma_sel.select)(dmach, ch_map);
1445
1446 return dmach;
1447 }
1398行是取出前面初始化的map
1402-1417行是目标板的dma映射情况,这里是优先考虑的。最后得到的是所对应的硬件通道号。
1421-1432行是在所有的map中寻找,这个主要是针对板载没有定义的一些dma通道,比如外设dma等情况
。
1437-1444行就是获取这个找到的dma通道的信息即struct s3c2410_dma_chan结构。
回到request函数,
739-759行如果没有申请dma中断,这里就调用request_irq进行dma中断的安装,相应的中断函数即为
s3c2410_dma_irq。
总结s3c2410_dma_request函数所完成的任务主要是为虚拟的dma通道找到合适的dma硬件资源,并为其申
请中断。使用时传入的参数包括:dma虚拟的通道号(如DMACH_I2S_IN、DMACH_I2S_OUT等)、dma名字。
========
开发DMA驱动
使用DMA的好处就是它不需要CPU的干预而直接服务外设,这样CPU就可以去处理别的事务,从而提高系统
的效率,对于慢速设备,如UART,其作用只是降低CPU的使用率,但对于高速设备,如硬盘,它不只是降
低CPU的使用率,而且能大大提高硬件设备的吞吐量。因为对于这种设备,CPU直接供应数据的速度太低
。
因CPU只能一个总线周期最多存取一次总线,而且对于ARM,它不能把内存中A地址的值直接搬到B地址。
它只能先把A地址的值搬到一个寄存器,然后再从这个寄存器搬到B地址。也就是说,对于ARM,要花费两
个总线周期才能将A地址的值送到B地址。而DMA就不同了,一般系统中的DMA都有突发(Burst)传输的能
力,在这种模式下,DMA能一次传输几个甚至几十个字节的数据,所以使用DMA能使设备的吞吐能力大为
增强。
使用DMA时我们必须要注意如下事实:
DMA使用物理地址,程序是使用虚拟地址的,所以配置DMA时必须将虚拟地址转化成物理地址。
因为程序使用虚拟地址,而且一般使用CACHED地址,所以虚拟地址中的内容与其物理地址上的内容不一
定一致辞,所以在启动DMA传输前一定要将该地址的CACHE刷新,即写入内存。
OS并不能保证每次分配到的内在空间在物理上是连续的。尤其是在系统使用过一段时间而又分配了一块
比较大的内存时。
所以每次都需要判断地址是不是连续的,如果不连续就需要把这段内存分成几段让DMA完成传输
========
ARM-Linux驱动--DMA驱动分析(一)
硬件平台:FL2440 (s3c2440)
内核版本:2.6.35
主机平台:Ubuntu 11.04
内核版本:2.6.39
原创作品,转载请标明出处http://blog.csdn.net/yming0221/article/details/6645821
1、DMA的功能和工作原理这里就不多说了,可以查看s3c2440的手册
2、在正式分析DMA驱动之前,我们先来看一下DMA的注册和初始化过程
系统设备:(翻译自源码注释)
系统设备和系统模型有点不同,它不需要动态绑定驱动,不能被探测(probe),不归结为任何的系统总
线,所以要区分对待。对待系统设备我们仍然要有设备驱动的观念,因为我们需要对设备进行基本的操
作。
定义系统设备,在./arch/arm/mach-s3c2440/s3c244x.c中
[cpp] view plain copy
/* 定义系统设备类 */
struct sysdev_class s3c2440_sysclass = {
.name = "s3c2440-core",
.suspend = s3c244x_suspend,
.resume = s3c244x_resume
};
注册系统设备类,在真正注册设备之前,确保已经注册了初始化了的系统设备类
[cpp] view plain copy
static int __init s3c2440_core_init(void)
{
return sysdev_class_register(&s3c2440_sysclass);
}
下面就是系统设备类的注册函数,在./drivers/base/sys.c中
[cpp] view plain copy
int sysdev_class_register(struct sysdev_class *cls)
{
int retval;
pr_debug("Registering sysdev class '%s'\n", cls->name);
INIT_LIST_HEAD(&cls->drivers);
memset(&cls->kset.kobj, 0x00, sizeof(struct kobject));
cls->kset.kobj.parent = &system_kset->kobj;
cls->kset.kobj.ktype = &ktype_sysdev_class;
cls->kset.kobj.kset = system_kset;
retval = kobject_set_name(&cls->kset.kobj, "%s", cls->name);
if (retval)
return retval;
retval = kset_register(&cls->kset);
if (!retval && cls->attrs)
retval = sysfs_create_files(&cls->kset.kobj,
(const struct attribute **)cls->attrs);
return retval;
}
[cpp] view plain copy
/* 定义DMA系统设备驱动 */
static struct sysdev_driver s3c2440_dma_driver = {
.add = s3c2440_dma_add,/* 添加add函数 */
};
下面是add函数,就是调用三个函数
[cpp] view plain copy
static int __init s3c2440_dma_add(struct sys_device *sysdev)
{
s3c2410_dma_init();
s3c24xx_dma_order_set(&s3c2440_dma_order);
return s3c24xx_dma_init_map(&s3c2440_dma_sel);
}
注册DMA驱动到系统设备
[cpp] view plain copy
static int __init s3c2440_dma_init(void)
{
return sysdev_driver_register(&s3c2440_sysclass, &s3c2440_dma_driver);
}
下面就是系统设备驱动的注册函数
[cpp] view plain copy
/**
* sysdev_driver_register - Register auxillary driver
* @cls: Device class driver belongs to.
* @drv: Driver.
*
* @drv is inserted into @cls->drivers to be
* called on each operation on devices of that class. The refcount
* of @cls is incremented.
*/
int sysdev_driver_register(struct sysdev_class *cls, struct sysdev_driver *drv)
{
int err = 0;
if (!cls) {
WARN(1, KERN_WARNING "sysdev: invalid class passed to "
"sysdev_driver_register!\n");
return -EINVAL;
}
/* Check whether this driver has already been added to a class. */
if (drv->entry.next && !list_empty(&drv->entry))
WARN(1, KERN_WARNING "sysdev: class %s: driver (%p) has already"
" been registered to a class, something is wrong, but "
"will forge on!\n", cls->name, drv);
mutex_lock(&sysdev_drivers_lock);
if (cls && kset_get(&cls->kset)) {
list_add_tail(&drv->entry, &cls->drivers);/* 将设备驱动添加到系统设备类的链表中 */
/* If devices of this class already exist, tell the driver */
if (drv->add) {
struct sys_device *dev;
list_for_each_entry(dev, &cls->kset.list, kobj.entry)
drv->add(dev);
}
} else {
err = -EINVAL;
WARN(1, KERN_ERR "%s: invalid device class\n", __func__);
}
mutex_unlock(&sysdev_drivers_lock);
return err;
}
在./arch/arm/mach-s3c2440/s3c2440.c中定义s3c2440的系统设备和注册
[cpp] view plain copy
static struct sys_device s3c2440_sysdev = {
.cls = &s3c2440_sysclass,/* 定义系统设备的所属系统设备类,用于系统设备注册到指定
设备类 */
};
/* S3C2440初始化 */
int __init s3c2440_init(void)
{
printk("S3C2440: Initialising architecture\n");
s3c24xx_gpiocfg_default.set_pull = s3c_gpio_setpull_1up;
s3c24xx_gpiocfg_default.get_pull = s3c_gpio_getpull_1up;
/* change irq for watchdog */
s3c_device_wdt.resource[1].start = IRQ_S3C2440_WDT;
s3c_device_wdt.resource[1].end = IRQ_S3C2440_WDT;
/* register our system device for everything else */
return sysdev_register(&s3c2440_sysdev);/* 注册s3c2440的系统设备 */
}
接下来是系统设备的注册函数
[cpp] view plain copy
/**
* sysdev_register - add a system device to the tree
* @sysdev: device in question
*
*/
/* 系统设备的注册 */
int sysdev_register(struct sys_device *sysdev)
{
int error;
struct sysdev_class *cls = sysdev->cls;/* 所属的系统设备类 */
if (!cls)
return -EINVAL;
pr_debug("Registering sys device of class '%s'\n",
kobject_name(&cls->kset.kobj));
/* initialize the kobject to 0, in case it had previously been used */
memset(&sysdev->kobj, 0x00, sizeof(struct kobject));
/* Make sure the kset is set */
sysdev->kobj.kset = &cls->kset;
/* Register the object */
error = kobject_init_and_add(&sysdev->kobj, &ktype_sysdev, NULL,
"%s%d", kobject_name(&cls->kset.kobj),
sysdev->id);
if (!error) {
struct sysdev_driver *drv;
pr_debug("Registering sys device '%s'\n",
kobject_name(&sysdev->kobj));
mutex_lock(&sysdev_drivers_lock);
/* Generic notification is implicit, because it's that
* code that should have called us.
*/
/* Notify class auxillary drivers */
list_for_each_entry(drv, &cls->drivers, entry) {
if (drv->add)
drv->add(sysdev);/* 遍历该设备所属同一个设备类的所有设备,并执行相应的add函
数 */
}
mutex_unlock(&sysdev_drivers_lock);
kobject_uevent(&sysdev->kobj, KOBJ_ADD);
}
return error;
}
那DMA系统设备驱动中的add函数中到底是什么呢?
(1)首先看第一个函数int __init s3c2410_dma_init(void),在./arch/arm/plat-s3c24xx/dma.c
[cpp] view plain copy
int __init s3c2410_dma_init(void)
{
return s3c24xx_dma_init(4, IRQ_DMA0, 0x40);
}
实际上就是初始化DMA为4通道,设置中断号,设置寄存器的覆盖范围
下面是该函数的实现
[cpp] view plain copy
int __init s3c24xx_dma_init(unsigned int channels, unsigned int irq,
unsigned int stride)/* 参数分别为通道个数、中断号、寄存器的覆盖范围 */
{
struct s3c2410_dma_chan *cp;/* 通道的结构体表示 */
int channel;
int ret;
printk("S3C24XX DMA Driver, Copyright 2003-2006 Simtec Electronics\n");
dma_channels = channels;
dma_base = ioremap(S3C24XX_PA_DMA, stride * channels);
if (dma_base == NULL) {
printk(KERN_ERR "dma failed to remap register block\n");
return -ENOMEM;
}
/* 分配DMA告诉缓冲区 */
dma_kmem = kmem_cache_create("dma_desc",
sizeof(struct s3c2410_dma_buf), 0,
SLAB_HWCACHE_ALIGN,
s3c2410_dma_cache_ctor);
if (dma_kmem == NULL) {
printk(KERN_ERR "dma failed to make kmem cache\n");
ret = -ENOMEM;
goto err;
}
for (channel = 0; channel < channels; channel++) {
cp = &s3c2410_chans[channel];
memset(cp, 0, sizeof(struct s3c2410_dma_chan));
/* dma channel irqs are in order.. */
cp->number = channel;
cp->irq = channel + irq;
cp->regs = dma_base + (channel * stride);
/* point current stats somewhere */
cp->stats = &cp->stats_store;
cp->stats_store.timeout_shortest = LONG_MAX;
/* basic channel configuration */
cp->load_timeout = 1<<18;
printk("DMA channel %d at %p, irq %d\n",
cp->number, cp->regs, cp->irq);
}
return 0;
/* 异常处理 */
err:
kmem_cache_destroy(dma_kmem);
iounmap(dma_base);
dma_base = NULL;
return ret;
}
(2)然后是函数s3c24xx_dma_order_set(&s3c2440_dma_order);
[cpp] view plain copy
int __init s3c24xx_dma_order_set(struct s3c24xx_dma_order *ord)
{
struct s3c24xx_dma_order *nord = dma_order;
if (nord == NULL)
nord = kmalloc(sizeof(struct s3c24xx_dma_order), GFP_KERNEL);
if (nord == NULL) {
printk(KERN_ERR "no memory to store dma channel order\n");
return -ENOMEM;
}
dma_order = nord;
memcpy(nord, ord, sizeof(struct s3c24xx_dma_order));
return 0;
}
我们注意到函数中使用了kmalloc给结构体重新分配了内存,这是由于__initdata修饰的变量表示初始化
用的变量,初始化完毕后空间自动释放,所以需要将其存储起来。
(3)最后一个函数s3c24xx_dma_init_map(&s3c2440_dma_sel)
该函数功能是建立DMA源与硬件通道的映射图
[cpp] view plain copy
int __init s3c24xx_dma_init_map(struct s3c24xx_dma_selection *sel)
{
struct s3c24xx_dma_map *nmap;
size_t map_sz = sizeof(*nmap) * sel->map_size;
int ptr;
nmap = kmalloc(map_sz, GFP_KERNEL);
if (nmap == NULL)
return -ENOMEM;
memcpy(nmap, sel->map, map_sz);
memcpy(&dma_sel, sel, sizeof(*sel));
dma_sel.map = nmap;
for (ptr = 0; ptr < sel->map_size; ptr++)
s3c24xx_dma_check_entry(nmap+ptr, ptr);
return 0;
}
这里的kmalloc函数的作用同上面的作用一样。
注:由于内核实在是太深了,这里只是表面上按流程大体了解了子同设备的注册和系统设备驱动的注册
以及DMA设备的注册和初始化,函数中有很多细节有待进一步研究。
========
Linux下DMA驱动如何实现
在《深入理解Linux内核》中的第545页介绍了DMA的相关操作。说道DMA,那就不得不提到Cache(高速缓
存)的问题。书中引用了如下一段例子来描述了Cache一致性问题:
“假设设备驱动程序把一些数据填充到内存缓冲区中,然后立刻命令硬件设备利用DMA传送方式读取该数
据。如果DMA访问这些物理RAM内存单元,而相应的硬件高速缓存行的内容还没有写入RAM中,那么硬件设
备所读取的至就是内存缓冲区中的旧值。”
现在有两种方法来处理DMA缓冲区:
一致性DMA映射:
书上讲的比较抽象,通俗地所就是任何对DMA缓冲区的改写都会直接更新到内存中,也称之为“同步的”
或者“一致的”。
流式DMA映射:
根据个人的理解,这里的流即输入输出流,我们需要事先指定DMA缓冲区的方向,比如是”读缓冲区”还
是“写缓冲区”。也称之为“异步的”或“非一致性的”,详细的内容请看下文。
由于x86体系结构中,硬件设备驱动程序本身会“窥探”所访问的硬件告诉缓存,因此x86体系结构中不
存在DMA一致性问题。而对于其他一些架构如MIPS,SPARC以及POWERPC(包括ARM在内)需要在软件上保
证其DMA一致性。
对于以上两者如何选择,书中有一个合适的建议,如果CPU和DMA处理器以不可预知的方式去访问一个缓
冲区,那么必须强制使用一致性DMA映射方式(这里我对不可预知的理解是,不能确定在何时它们访问缓
冲区),其他情形下,流式DMA映射方式更可取,因为在一些体系结构中处理一致性DMA映射是很麻烦的
,并且可能导致更低的系统性能。
这里详细介绍流式DMA:
需要访问的缓冲区需要在数据传送之前被映射(这里的映射也就是需要调用一些函数告知内核,该缓冲
区进行流式映射),在传送之后被取消映射。
启动一次流式DMA数据传输分为如下步骤:
1. 分配DMA缓冲区。
在DMA设备不采用S/G(分散/聚集)模式的情况下,必须保证缓冲区是物理上连续的,linux内核有两个
函数用来分配连续的内存:kmalloc()和__get_free_pages()。这两个函数都有分配连续内存的最大值,
kmalloc以分配字节为单位,最大约为64KB,__get_free_pages()以分配页为单位,最大能分配2^order
数目的页,order参数的最大值由include/linux/Mmzone.h文件中的MAX_ORDER宏决定(在默认的2.6.18
内核版本中,该宏定义为10。也就是说在理论上__get_free_pages函数一次最多能申请1<<10 * 4KB也就
是4MB的连续物理内存,在Xilinx Zynq Linux内核中,该宏定义为11)。
2. 建立流式映射。
在对DMA冲区进行读写访问之后,且在启动DMA设备传输之前,启用dma_map_single()函数建立流式DMA映
射,这两个函数接受缓冲区的线性地址作为其参数并返回相应的总线地址。
3. 释放流式映射。
当DMA传输结束之后我们需要释放该映射,这时调用dma_unmap_single()函数。
注意:
(1). 为了避免高速缓存一致性问题,驱动程序在开始从RAM到设备的DMA数据传输之前,如果有必要,应
该调用dma_sync_single_for_device()函数刷新与DMA缓冲区对应的高速缓存行。
(2). 从设备到RAM的一次DMA数据传送完成之前设备驱动程序是不可以访问内存缓冲区的,但如果有必要
的话,驱动程序在读缓冲区之前,应该调用dma_sync_single_for_cpu()函数使相应的硬件高速缓存行无
效。
(3). 虽然kmalloc底层也是用__get_free_pages实现的,不过kmalloc对应的释放缓冲区函数为kfree,
而__get_free_pages对应的释放缓冲区函数为free_pages。具体与__get_free_pages有关系的几个申请
与释放函数如下:
申请函数:
alloc_pages(gfp_mask,order)
返回第一个所分配页框描述符的地址,或者如果分配失败则返回NULL。
__get_free_pages(gfp_mask,order)
类似于alloc_pages(),但它返回第一个所分配页的线性地址。如果需要获得线性地址对应的页框号,那
么需要调用virt_to_page(addr)宏产生线性地址。
释放函数:
__free_pages(page,order)
这里主要强调page是要释放缓冲区的线性首地址所在的页框号
free_pages(page,order)
这个函数类似于__free_pages(page,order),但是它接收的参数为要释放的第一个页框的线性地址addr
========
DMA驱动程序编写与测试
1、驱动程序编写
在本驱动程序中,我们打算在内存中开辟两个空间,分别作为源和目的。我们用两个方法将源中的数据写到目的中,一种方法是让cpu去做,另外一种发放是让DMA去做!好的,闲话少说,直接分析代码:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
#include <linux/dma-mapping.h>
#define MEM_CPY_NO_DMA 0
#define MEM_CPY_DMA 1
#define BUF_SIZE (512*1024)
#define DMA0_BASE_ADDR 0x4B000000
#define DMA1_BASE_ADDR 0x4B000040
#define DMA2_BASE_ADDR 0x4B000080
#define DMA3_BASE_ADDR 0x4B0000C0
struct s3c_dma_regs {
unsigned long disrc;
unsigned long disrcc;
unsigned long didst;
unsigned long didstc;
unsigned long dcon;
unsigned long dstat;
unsigned long dcsrc;
unsigned long dcdst;
unsigned long dmasktrig;
};
static int major = 0;
static char *src;
static u32 src_phys;
static char *dst;
static u32 dst_phys;
static struct class *cls;
static volatile struct s3c_dma_regs *dma_regs;
static DECLARE_WAIT_QUEUE_HEAD(dma_waitq);
/* 中断事件标志, 中断服务程序将它置1,ioctl将它清0 */
static volatile int ev_dma = 0;
static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int i;
memset(src, 0xAA, BUF_SIZE);
memset(dst, 0x55, BUF_SIZE);
switch (cmd)
{
//这是非DMA模式
case MEM_CPY_NO_DMA :
{
for (i = 0; i < BUF_SIZE; i++)
dst[i] = src[i]; //CPU直接将源拷贝到目的
if (memcmp(src, dst, BUF_SIZE) == 0)//这个函数见注释2
{
printk("MEM_CPY_NO_DMA OK\n");
}
else
{
printk("MEM_CPY_DMA ERROR\n");
}
break;
}
//这是DMA模式
case MEM_CPY_DMA :
{
ev_dma = 0;
/* 把源,目的,长度告诉DMA */
/* 关于下面寄存器的具体情况,我们在注释3里面来详细讲一下 */
dma_regs->disrc = src_phys; /* 源的物理地址 */
dma_regs->disrcc = (0<<1) | (0<<0); /* 源位于AHB总线, 源地址递增 */
dma_regs->didst = dst_phys; /* 目的的物理地址 */
dma_regs->didstc = (0<<2) | (0<<1) | (0<<0); /* 目的位于AHB总线, 目的地址递增 */
dma_regs->dcon = (1<<30)|(1<<29)|(0<<28)|(1<<27)|(0<<23)|(0<<20)|(BUF_SIZE<<0); /* 使能中断,单个传输,软件触发, */
/* 启动DMA */
dma_regs->dmasktrig = (1<<1) | (1<<0);
/* 如何知道DMA什么时候完成? */
/* 休眠 */
wait_event_interruptible(dma_waitq, ev_dma);
if (memcmp(src, dst, BUF_SIZE) == 0)
{
printk("MEM_CPY_DMA OK\n");
}
else
{
printk("MEM_CPY_DMA ERROR\n");
}
break;
}
}
return 0;
}
static struct file_operations dma_fops = {
.owner = THIS_MODULE,
.ioctl = s3c_dma_ioctl,
};
static irqreturn_t s3c_dma_irq(int irq, void *devid)
{
/* 唤醒 */
ev_dma = 1;
wake_up_interruptible(&dma_waitq); /* 唤醒休眠的进程 */
return IRQ_HANDLED;
}
static int s3c_dma_init(void)
{
/* 这里注册一个中断,当DMA数据传输完毕之后会发生此中断 */
if (request_irq(IRQ_DMA3, s3c_dma_irq, 0, "s3c_dma", 1))
{
printk("can't request_irq for DMA\n");
return -EBUSY;
}
/* 分配SRC, DST对应的缓冲区,关于此函数详见注释1 */
src = dma_alloc_writecombine(NULL, BUF_SIZE, &src_phys, GFP_KERNEL);//源
if (NULL == src)
{
printk("can't alloc buffer for src\n");
free_irq(IRQ_DMA3, 1);
return -ENOMEM;
}
dst = dma_alloc_writecombine(NULL, BUF_SIZE, &dst_phys, GFP_KERNEL);//目的
if (NULL == dst)
{
free_irq(IRQ_DMA3, 1);
dma_free_writecombine(NULL, BUF_SIZE, src, src_phys);
printk("can't alloc buffer for dst\n");
return -ENOMEM;
}
major = register_chrdev(0, "s3c_dma", &dma_fops);//注册字符设备
/* 为了自动创建设备节点 */
cls = class_create(THIS_MODULE, "s3c_dma");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "dma"); /* /dev/dma */
dma_regs = ioremap(DMA3_BASE_ADDR, sizeof(struct s3c_dma_regs));//这边是将DMA控制寄存器映射到内核空间
return 0;
}
static void s3c_dma_exit(void)
{
iounmap(dma_regs);
class_device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, "s3c_dma");
dma_free_writecombine(NULL, BUF_SIZE, src, src_phys);
dma_free_writecombine(NULL, BUF_SIZE, dst, dst_phys);
free_irq(IRQ_DMA3, 1);
}
module_init(s3c_dma_init);
module_exit(s3c_dma_exit);
MODULE_LICENSE("GPL");
注释1:
之前我们知道在内核中开辟空间可以用kmalloc函数,这里却用了dma_alloc_writecombine,这是为什么呢?这是因为kmalloc开辟的空间其逻辑地址虽然是连续的,但是其实际的物理地址可能不是连续的。而DMA传输数据时,要求物理地址是连续的,dma_alloc_writecombine就满足这一点,这个函数的原型是:
dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp)
其中size代表开辟的空间的大小,handle代表开辟的空间的物理地址,返回值是开辟的空间的逻辑地址。
注释2:
int memcmp(const void *cs, const void *ct, size_t count)
{
const unsigned char *su1, *su2;
int res = 0;
for (su1 = cs, su2 = ct; 0 < count; ++su1, ++su2, count--)
if ((res = *su1 - *su2) != 0)
break;
return res;
}
我们看到这个函数的作用就是将第一个参数和第二个参数一位一位地比较,一旦不相等就返回,此时返回值为非零。比较的位数为第三个参数,如果前两个参数的前count为都是相等的,那么就会返回0
注释3:
我们先来解析一下上面几个寄存器:
DISRCn
bit
Description
Initial State
S_ADDR
[30:0]
源起始地址
0x00000000
DISRCCn
bit
Description
Initial State
LOC
[1]
用于选择源的位置
0:源在系统总线上
1:源在外设总线上
0
INC
[0]
用于选择地址是否自动增加
0:地址自动增加
1:地址固定不变(此时即便是burst 模式下,传输过程中地址自动增加, 但是一旦传输完这一次数据,地址又变为初值)
0
DIDSTn
bit
Description
Initial State
D_ADDR
[30:0]
目的起始地址
0x00000000
DIDSTCn
Bit
Description
Initial State
CHK_INT
[2]
当设置为自动加载时,用来选择中断发生的时间
0:TC为0是产生中断
1:自动加载完成的时候产生中断
0
LOC
[1]
用于选择目的设备的位置
0:目的设备在系统总线上
1:目的设备在外设总线上
0
INC
[0]
用于选择地址是否自动增加
0:地址自动增加
1:地址固定不变(此时即便是burst模式下,传输过程中地址自动增加,但是一旦传输完这一次数据,地址又重新变为初值)
0
DCONn
Bit
Description
Initial State
DMD_HS
[31]
选择为Demand模式或者是握手模式
0:选择为Demand模式
1:选择为握手模式
这两种模式下都是当发生请求时,DMA控制器开始传输数据并且发出 应 答信号,不同点是握手模式下,当DMA控制器收到请求撤销信号,并且自 身发出应答撤销信号之后才能接收下一次请求。而在Demand模式下,并 不需要等待请求撤销信号,他只需要撤销自身的应答信号,然后等待下一 次的请求。
0
SYNC
[30]
选择DREQ/DACK的同步
0:DREQ and DACK 与PCLK同步
1:DREQ and DACK 与HCLK同步
因此当设备在AHB系统总线时,这一位必须为1,而当设备在APB系统 时,它应该被设为0。当设备位于外部系统时,应根据具体情况而定。
0
INT
[29]
是否使能中断
0:禁止中断,用户需要查看状态寄存器来判断传输是否完成
1:使能中断,所有的传输完成之后产生中断信号
0
TSZ
[28]
选择一个原子传输的大小
0:单元传输(一次传输一个单元)
1:突发传输(一次传输四个单元)
0
SERVMODE
[27]
选择是单服务模式还是整体服务模式
0:单服务模式,当一次原子传输完成后需要等待下一个DMA请求
1:整体服务模式,进行多次原子传输,知道传输计数值到达0
0
HWSRCSEL
[26:24]
为每一个DMA选择DMA请求源
具体参见芯片手册
000
SWHW_SEL
[23]
选择DMA源为软件请求模式还是硬件请求模式
0:软件请求模式,需要将寄存器DMASKTRIG的SW_TRIG置位
1:硬件请求模式
0
RELOAD
[22]
是否自动重新装载
0:自动重装,当目前的传输计数值变为0时,自动重装
1:不自动重装
RELOAD[1]被设置为0以防无意识地进行新的DMA传输
0
DSZ
[21:20]
要被传输的数据的大小
00 = Byte 01 = Half word
10 = Word 11 = reserved
00
TC
[19:0]
初始化传输计数
0000
这里我们需要注意了,里面有三个东东要分清楚:
DSZ :代表数据的大小
TSZ :一次传输多少个数据
TC :一共传输多少次
所以实际传输的数据的大小为:DSZ * TSZ * TC
我们本程序里面由于设置为数据大小为1个字节,一次传输1个数据,所以传输次数直接就是实际数据的大小了。
DSTATn
Bit
Description
Initial State
STAT
[21:20]
DMA控制器的状态
00:DMA控制器已经准备好接收下一个DMA请求
01:DMA控制器正在处理DMA请求
00
CURR_TC
[19:0]
传输计数的当前值
每个原子传输减1
DCSRCn
Bit
Description
Initial State
CURR_SRC
[30:0]
当前的源地址
0x00000000
DCDSTn
Bit
Description
Initial State
CURR_DST
[30:0]
当前的目的地址
0x00000000
DMASKTRIGn
Bit
Description
Initial State
STOP
[2]
停止DMA操作
1:当前的原子传输完成之后,就停止DMA操作。如果当前没有原子 传输正在进行,就立即结束。
ON_OFF
[1]
DMA通道的开/闭
0:DMA通道关闭
1:DMA通道打开,并且处理DMA请求
SW_TRIG
[0]
1:在软件请求模式时触发DMA通道
OK!寄存器分析完毕,具体设置就不在写出来了!
在此我们在来总结一下DMA的操作流程:
我们首先设置DMA的工作方式,然后打开DMA通道,紧接着我们使CPU休眠,停止执行代码。与此同时,在DMA控制器的作用下,从源向目的拷贝数据。一旦数据拷贝完成,就会触发中断,在中断函数里面,唤醒进程,从而程序继续运行,打印相关信息。
2、应用程序编写
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
/* ./dma_test nodma
* ./dma_test dma
*/
#define MEM_CPY_NO_DMA 0
#define MEM_CPY_DMA 1
void print_usage(char *name)
{
printf("Usage:\n");
printf("%s <nodma | dma>\n", name);
}
int main(int argc, char **argv)
{
int fd;
if (argc != 2)
{
print_usage(argv[0]);
return -1;
}
fd = open("/dev/dma", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/dma\n");
return -1;
}
if (strcmp(argv[1], "nodma") == 0)
{
while (1)
{
ioctl(fd, MEM_CPY_NO_DMA);
}
}
else if (strcmp(argv[1], "dma") == 0)
{
while (1)
{
ioctl(fd, MEM_CPY_DMA);
}
}
else
{
print_usage(argv[0]);
return -1;
}
return 0;
}
应用程序过于简单,不在分析!
3、测试
# insmod dma.ko //加载驱动
# cat /proc/interrupts //查看中断
CPU0
30: 52318 s3c S3C2410 Timer Tick
33: 0 s3c s3c-mci
34: 0 s3c I2SSDI
35: 0 s3c I2SSDO
36: 0 s3c s3c_dma
37: 12 s3c s3c-mci
42: 0 s3c ohci_hcd:usb1
43: 0 s3c s3c2440-i2c
51: 2725 s3c-ext eth0
60: 0 s3c-ext s3c-mci
70: 97 s3c-uart0 s3c2440-uart
71: 100 s3c-uart0 s3c2440-uart
83: 0 - s3c2410-wdt
Err: 0
# ls /dev/dma //查看设备
/dev/dma
# ./dmatest //如此就会打印用法
Usage:
./dmatest <nodma | dma>
# ./dmatest dma //以DMA方式拷贝,CPU可以做其他事情
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK
# ./dmatest dma //CPU拷贝,各种竞争CPU
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK
========