由于在传输大块数据的过程中无须CPU干预(当然在开始、出错和结束时仍然需要),所以与轮询和中断相比,DMA传输效率要高得多。另外,Marvell平台上提供了所谓的memory switch,总线有更高的利用率,DMA就更能显出它的优势了。
下面我们看看mach-pxa/dma.c中的代码:
1
31
static
struct
dma_channel {
2
32
char
*
name;
3
33
void
(
*
irq_handler)(
int
,
void
*
,
struct
pt_regs
*
);
4
34
void
*
data;
5
35
} dma_channels[PXA_DMA_CHANNELS];
6
该结构用于保存已注册的DMA中断处理函数,成员name表示该通道的名称,它只是起说明的作用,没有什么实际用途。成员irq_handler是所注册的中断处理函数,当该通道发生中断时,该函数被调用。成员data是中断处理函数irq_handler的调用上下文,当中断处理函数被调用时,其作为第二个参数传入。
1
38
int
pxa_request_dma (
char
*
name, pxa_dma_prio prio,
2
39
void
(
*
irq_handler)(
int
,
void
*
,
struct
pt_regs
*
),
3
40
void
*
data)
4
41
{
5
42
unsigned
long
flags;
6
43
int
i, found
=
0
;
7
44
8
45
/*
basic sanity checks
*/
9
46
if
(
!
name
||
!
irq_handler)
10
47
return
-
EINVAL;
11
48
12
49
local_irq_save(flags);
13
50
14
51
/*
try grabbing a DMA channel with the requested priority
*/
15
52
for
(i
=
prio; i
<
prio
+
PXA_DMA_NBCH(prio); i
++
) {
16
53
if
(
!
dma_channels[i].name) {
17
54
found
=
1
;
18
55
break
;
19
56
}
20
57
}
21
58
22
59
if
(
!
found) {
23
60
/*
requested prio group is full, try hier priorities
*/
24
61
for
(i
=
prio
-
1
; i
>=
0
; i
--
) {
25
62
if
(
!
dma_channels[i].name) {
26
63
found
=
1
;
27
64
break
;
28
65
}
29
66
}
30
67
}
31
68
32
69
if
(found) {
33
70
DCSR(i)
=
DCSR_STARTINTR
|
DCSR_ENDINTR
|
DCSR_BUSERR;
34
71
dma_channels[i].name
=
name;
35
72
dma_channels[i].irq_handler
=
irq_handler;
36
73
dma_channels[i].data
=
data;
37
74
}
else
{
38
75
printk (KERN_WARNING
"
No more available DMA channels for %s\n
"
, name);
39
76
i
=
-
ENODEV;
40
77
}
41
78
42
79
local_irq_restore(flags);
43
80
return
i;
44
81
}
45
注册一个DMA通道,其参数有名称、优先级、中断处理函数和中断处理函数的调用上下文。
这里的优先级和开发手册中所说的略有差别,在开发手册中(11.3.1.1)里说,从硬件的角度,优先级分为四等,0等优先级最高,3等优先级最低。在代码中,优先级只分为高中低三等,高优先级和中优先级的通道数为8个,低优先级的通道数为16个。
在dma_channels数组中,按优先级从高到低排列,在注册时,先看在所请求的优先级中是否有空位,如果有,就使用该空位,如果没有,就从更高优先级中去找,直到找一个空位,或者发现没有空位可用,则中断循环。
如果找到合适的空位,则重置该通道的状态。和我们前面几次分析中所提到的一样,70行中的代码并非是要设置DCSR_STARTINTR|DCSR_ENDINTR|DCSR_BUSERR几个位域,而是在对应的位域上写1去清除它。
1
83
void
pxa_free_dma (
int
dma_ch)
2
84
{
3
85
unsigned
long
flags;
4
86
5
87
if
(
!
dma_channels[dma_ch].name) {
6
88
printk (KERN_CRIT
7
89
"
%s: trying to free channel %d which is already freed\n
"
,
8
90
__FUNCTION__, dma_ch);
9
91
return
;
10
92
}
11
93
12
94
local_irq_save(flags);
13
95
DCSR(dma_ch)
=
DCSR_STARTINTR
|
DCSR_ENDINTR
|
DCSR_BUSERR;
14
96
dma_channels[dma_ch].name
=
NULL;
15
97
local_irq_restore(flags);
16
98
}
17
该函数用于注销DMA通道,它重置对应的DCSR寄存器,并把name置空。
1
100
static
irqreturn_t dma_irq_handler(
int
irq,
void
*
dev_id,
struct
pt_regs
*
regs)
2
101
{
3
102
int
i, dint
=
DINT;
4
103
5
104
for
(i
=
0
; i
<
PXA_DMA_CHANNELS; i
++
) {
6
105
if
(dint
&
(
1
<<
i)) {
7
106
struct
dma_channel
*
channel
=
&
dma_channels[i];
8
107
if
(channel
->
name
&&
channel
->
irq_handler) {
9
108
channel
->
irq_handler(i, channel
->
data, regs);
10
109
}
else
{
11
110
/*
12
111 * IRQ for an unregistered DMA channel:
13
112 * let's clear the interrupts and disable it.
14
113
*/
15
114
printk (KERN_WARNING
"
spurious IRQ for DMA channel %d\n
"
, i);
16
115
DCSR(i)
=
DCSR_STARTINTR
|
DCSR_ENDINTR
|
DCSR_BUSERR;
17
116
}
18
117
}
19
118
}
20
119
return
IRQ_HANDLED;
21
120
}
22
该函数是DMA中断处理函数的总入口,它在一个循环中检查所有DMA通道,如果对应通道发生中断,而且有人注册了该通道,它则调用注册的中断处理函数。如果没有人注册该通道,它就重置对应的DCSR寄存器。
1
123
void
mhn_init_dmac(
void
)
2
124
{
3
125
int
i;
4
126
5
127
for
(i
=
0
; i
<
PXA_DMA_CHANNELS; i
++
) {
6
128
/*
clear all write-1-to-clear bits
*/
7
129
DCSR(i)
|=
(DCSR_BUSERR
|
DCSR_STARTINTR
|
DCSR_ENDINTR
|
8
130
DCSR_RASINTR
|
DCSR_EORINTR);
9
131
DCSR(i)
=
0x0
;
10
132
}
11
133
12
134
DINT
=
0
;
13
135
14
136
/*
clear DRCMR0 ~ DRCMR63
*/
15
137
for
(i
=
0
; i
<
64
; i
++
)
16
138
DRCMR(i)
=
0x0
;
17
139
18
140
/*
clear DRCMR64 ~ DRCMR99
*/
19
141
for
(i
=
0
; i
<
36
; i
++
)
20
142
*
((
volatile
uint32_t
*
)
&
DRCMR64
+
i)
=
0x0
;
21
143
22
144
/*
clear all the 32 DMA descriptors
*/
23
145
for
(i
=
0
; i
<
32
*
4
; i
++
)
24
146
*
((
volatile
uint32_t
*
)
&
DDADR0
+
i)
=
0x0
;
25
147
}
26
该函数初始化所有通道的DMA寄存器,比如DCSR、DINT、DRCMR和DDADR等。
1
150
static
int
__init pxa_dma_init (
void
)
2
151
{
3
152
int
ret;
4
153
5
154
ret
=
request_irq (IRQ_DMA, dma_irq_handler,
0
,
"
DMA
"
, NULL);
6
155
if
(ret)
7
156
printk (KERN_CRIT
"
Wow! Can't register IRQ for DMA\n
"
);
8
157
return
ret;
9
158
}
10
初始化DMA,向系统注册一个中断处理函数。
补充说明几点:
1. ARM平台对DMA操作做了一次抽象,它让DMA操作可以独立于具体硬件平台,这样驱动程序具有更好的可移植性,但不清楚什么原因,marvell的DMA实现并没有按照这个标准的方式去做。ARM对DMA的抽象如下:
1
struct
dma_ops {
2
int
(
*
request)(dmach_t, dma_t
*
);
/*
optional
*/
3
void
(
*
free)(dmach_t, dma_t
*
);
/*
optional
*/
4
void
(
*
enable)(dmach_t, dma_t
*
);
/*
mandatory
*/
5
void
(
*
disable)(dmach_t, dma_t
*
);
/*
mandatory
*/
6
int
(
*
residue)(dmach_t, dma_t
*
);
/*
optional
*/
7
int
(
*
setspeed)(dmach_t, dma_t
*
,
int
);
/*
optional
*/
8
char
*
type;
9
};
10
11
struct
dma_struct {
12
struct
scatterlist buf;
/*
single DMA
*/
13
int
sgcount;
/*
number of DMA SG
*/
14
struct
scatterlist
*
sg;
/*
DMA Scatter-Gather List
*/
15
16
unsigned
int
active:
1
;
/*
Transfer active
*/
17
unsigned
int
invalid:
1
;
/*
Address/Count changed
*/
18
unsigned
int
using_sg:
1
;
/*
using scatter list?
*/
19
dmamode_t dma_mode;
/*
DMA mode
*/
20
int
speed;
/*
DMA speed
*/
21
22
unsigned
int
lock
;
/*
Device is allocated
*/
23
const
char
*
device_id;
/*
Device name
*/
24
25
unsigned
int
dma_base;
/*
Controller base address
*/
26
int
dma_irq;
/*
Controller IRQ
*/
27
struct
scatterlist cur_sg;
/*
Current controller buffer
*/
28
unsigned
int
state;
29
30
struct
dma_ops
*
d_ops;
31
};
32
2. 前面的代码没有涉及DMA的使用方法,这里我们看一段串口中代码,以补其不足。
1
672
static
void
pxa_uart_receive_dma_start(
struct
uart_pxa_port
*
up)
2
673
{
3
674
dbg(
"
enter
"
);
4
675
DCSR(up
->
rxdma)
=
DCSR_NODESC;
//
| DCSR_EORSTOPEN | DCSR_EORIRQEN;
5
676
DSADR(up
->
rxdma)
=
up
->
port.mapbase;
6
677
DTADR(up
->
rxdma)
=
up
->
rxdma_addr_phys;
7
678
DCMD(up
->
rxdma)
=
DCMD_INCTRGADDR
|
DCMD_FLOWSRC
|
DCMD_ENDIRQEN
|
DCMD_WIDTH1
|
DCMD_BURST16
|
DMA_BLOCK;
8
679
DCSR(up
->
rxdma)
|=
DCSR_RUN;
9
680
dbg(
"
exit
"
);
10
681
}
11
675在marvell平台上,DMA有两种工作方式,一种可以传输多个不连续地址的buffer,称之为描述符方式传输。另外一种一次只能传输一个buffer,称为非描述符方式。这里设置为非描述符方式。
676 设置源地址,其为串口的FIFO。
677 设置目标地址,其为物理内存地址。
678 设置命令寄存器。目标地址是内存,所以要加上DCMD_INCTRGADDR标志要求自动增加目标地址。而源地址是FIFO不需要显式的改变地址,所以不需要设置DCMD_INCSRCADDR标志。目标地址是内存,所以无需要流控。而源地址是FIFO,所以要设置源端流控DCMD_FLOWSRC标志。DCMD_ENDIRQEN标志允许传输完成时发现中断,DCMD_WIDTH1指明一个字节宽度,DCMD_BURST16指明一次传输16个字节,DMA_BLOCK指明传输数据的长度。
679 启动传输。
1
951
if
(
0
==
up
->
rxdma) {
2
952
up
->
rxdma
=
3
953
pxa_request_dma(up
->
name, DMA_PRIO_LOW, pxa_uart_receive_dma, up);
4
954
if
(up
->
rxdma
<
0
)
5
955
goto
out
;
6
956
}
7
8
971
if
(NULL
==
up
->
rxdma_addr) {
9
972
up
->
rxdma_addr
=
dma_alloc_coherent(NULL, DMA_BLOCK,
&
up
->
rxdma_addr_phys, GFP_KERNEL);
10
973
if
(
!
up
->
rxdma_addr)
11
974
goto
rxdma_err_alloc;
12
975
}
13
951-956 注册DMA通道,pxa_uart_receive_dma为中断处理函数。
971-975 分配用于DMA传输的内存。