dma.c源代码分析
由于在传输大块数据的过程中无须CPU干预(当然在开始、出错和结束时仍然需要),所以与轮询和中断相比,DMA传输效率要高得多。另外,Marvell平台上提供了所谓的memory switch,总线有更高的利用率,DMA就更能显出它的优势了。
下面我们看看mach-pxa/dma.c中的代码:
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的调用上下文,当中断处理函数被调用时,其作为第二个参数传入。
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去清除它。
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置空。
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
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等。
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的抽象如下:
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的使用方法,这里我们看一段串口中代码,以补其不足。
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 启动传输。
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传输的内存。