轉載於: http://www.voidcn.com/blog/bcbobo21cn/article/p-5777739.html
以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
========