以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;
可见上面一段阐明了s3c2440内部的4通道DMA的硬件资源是如何在linux中表示,接下来就是如何使用内核提供的接口使用DMA资源了。DMA对外提供的接口函数定义在/linux/arch/arm/plat-s3c24xx/dma.c文件中。这里根据调用过程的先后次序对其一一说明,并简单介绍其实现原理。
int s3c2410_dma_request(unsigned int channel,
struct s3c2410_dma_client *client,
void *dev)
首先是分配dma通道,因为DMA是一个硬件资源,对2440来说就只有四个通道,如果你申请要用的通道已被别人占用,比如外部设备要想使用dma时只能使用dam0或DMA1,一旦系统硬件通道被占用则分配将面临失败。
参数:Unsigned int channel:这个是系统分配的就如同中断向量号一样与硬件的通道没有什么联系,属于内核抽象的内容。需要使用那个就直接在map中查就行了。比如DMACH_XD0就是对应外设的dma。
static struct s3c24xx_dma_map __initdata s3c2440_dma_mappings[] = {
[DMACH_XD0] = {
.name = "xdreq0",
.channels[0] = S3C2410_DCON_CH0_XDREQ0 | DMA_CH_VALID,
},
[DMACH_XD1] = {
.name = "xdreq1",
.channels[1] = S3C2410_DCON_CH1_XDREQ1 | DMA_CH_VALID,
},
[DMACH_SDI] = {
.name = "sdi",
.channels[0] = S3C2410_DCON_CH0_SDI | DMA_CH_VALID,
.channels[1] = S3C2440_DCON_CH1_SDI | DMA_CH_VALID,
.channels[2] = S3C2410_DCON_CH2_SDI | DMA_CH_VALID,
.channels[3] = S3C2410_DCON_CH3_SDI | DMA_CH_VALID,
.hw_addr.to = S3C2410_PA_IIS + S3C2410_IISFIFO,
.hw_addr.from = S3C2410_PA_IIS + S3C2410_IISFIFO,
},
[DMACH_SPI0] = {
.name = "spi0",
.channels[1] = S3C2410_DCON_CH1_SPI | DMA_CH_VALID,
.hw_addr.to = S3C2410_PA_SPI + S3C2410_SPTDAT,
.hw_addr.from = S3C2410_PA_SPI + S3C2410_SPRDAT,
},
[DMACH_SPI1] = {
.name = "spi1",
.channels[3] = S3C2410_DCON_CH3_SPI | DMA_CH_VALID,
.hw_addr.to = S3C2410_PA_SPI + 0x20 + S3C2410_SPTDAT,
.hw_addr.from = S3C2410_PA_SPI + 0x20 + S3C2410_SPRDAT,
},
[DMACH_UART0] = {
.name = "uart0",
.channels[0] = S3C2410_DCON_CH0_UART0 | DMA_CH_VALID,
.hw_addr.to = S3C2410_PA_UART0 + S3C2410_UTXH,
.hw_addr.from = S3C2410_PA_UART0 + S3C2410_URXH,
},
[DMACH_UART1] = {
.name = "uart1",
.channels[1] = S3C2410_DCON_CH1_UART1 | DMA_CH_VALID,
.hw_addr.to = S3C2410_PA_UART1 + S3C2410_UTXH,
.hw_addr.from = S3C2410_PA_UART1 + S3C2410_URXH,
},
[DMACH_UART2] = {
.name = "uart2",
.channels[3] = S3C2410_DCON_CH3_UART2 | DMA_CH_VALID,
.hw_addr.to = S3C2410_PA_UART2 + S3C2410_UTXH,
.hw_addr.from = S3C2410_PA_UART2 + S3C2410_URXH,
},
[DMACH_TIMER] = {
.name = "timer",
.channels[0] = S3C2410_DCON_CH0_TIMER | DMA_CH_VALID,
.channels[2] = S3C2410_DCON_CH2_TIMER | DMA_CH_VALID,
.channels[3] = S3C2410_DCON_CH3_TIMER | DMA_CH_VALID,
},
[DMACH_I2S_IN] = {
.name = "i2s-sdi",
.channels[1] = S3C2410_DCON_CH1_I2SSDI | DMA_CH_VALID,
.channels[2] = S3C2410_DCON_CH2_I2SSDI | DMA_CH_VALID,
.hw_addr.from = S3C2410_PA_IIS + S3C2410_IISFIFO,
},
[DMACH_I2S_OUT] = {
.name = "i2s-sdo",
.channels[0] = S3C2440_DCON_CH0_I2SSDO | DMA_CH_VALID,
.channels[2] = S3C2410_DCON_CH2_I2SSDO | DMA_CH_VALID,
.hw_addr.to = S3C2410_PA_IIS + S3C2410_IISFIFO,
},
[DMACH_PCM_IN] = {
.name = "pcm-in",
.channels[0] = S3C2440_DCON_CH0_PCMIN | DMA_CH_VALID,
.channels[2] = S3C2440_DCON_CH2_PCMIN | DMA_CH_VALID,
.hw_addr.from = S3C2440_PA_AC97 + S3C_AC97_PCM_DATA,
},
[DMACH_PCM_OUT] = {
.name = "pcm-out",
.channels[1] = S3C2440_DCON_CH1_PCMOUT | DMA_CH_VALID,
.channels[3] = S3C2440_DCON_CH3_PCMOUT | DMA_CH_VALID,
.hw_addr.to = S3C2440_PA_AC97 + S3C_AC97_PCM_DATA,
},
[DMACH_MIC_IN] = {
.name = "mic-in",
.channels[2] = S3C2440_DCON_CH2_MICIN | DMA_CH_VALID,
.channels[3] = S3C2440_DCON_CH3_MICIN | DMA_CH_VALID,
.hw_addr.from = S3C2440_PA_AC97 + S3C_AC97_MIC_DATA,
},
[DMACH_USB_EP1] = {
.name = "usb-ep1",
.channels[0] = S3C2410_DCON_CH0_USBEP1 | DMA_CH_VALID,
},
[DMACH_USB_EP2] = {
.name = "usb-ep2",
.channels[1] = S3C2410_DCON_CH1_USBEP2 | DMA_CH_VALID,
},
[DMACH_USB_EP3] = {
.name = "usb-ep3",
.channels[2] = S3C2410_DCON_CH2_USBEP3 | DMA_CH_VALID,
},
[DMACH_USB_EP4] = {
.name = "usb-ep4",
.channels[3] = S3C2410_DCON_CH3_USBEP4 | DMA_CH_VALID,
},
};
struct s3c2410_dma_client *client:这个展开以后就是个void *name指针,算是个名字吧
void *dev:指向你自己的数据吧
返回:如果向硬件分配到了资源,那么就会返回分配成功的硬件通道号,否则是个负值表示出错。
这个函数主要就是将你申请的chanle和实际的硬件通道struct s3c2410_dma_chan *chan相关联,以后使用的时候会通过s3c_dma_lookup_channel找出chanle所对应的struct s3c2410_dma_chan。另外回为这个硬件通道申请其中断函数,如果传输完成DMA引擎将以中断方式通知CPU处理。中断处理的函数均为s3c2410_dma_irq,如何使用后文再说。
有了硬件DMA资源,同时还需要内存,这个就要借助内核的dma内存分配函数。比如dma_alloc_writecombine,dma_alloc等函数。Dma传输时使用的是物理地址,所以真正需要的是dma_addr_t类型的地址,而不是内核虚拟地址。
int s3c2410_dma_enqueue(unsigned int channel, void *id,
dma_addr_t data, int size)
这个函数是将要传输的数据放入到dma队列中,实际上在struct s3c2410_dma_chan中为dma的缓冲区维护了一个队列,结构是
struct s3c2410_dma_buf {
struct s3c2410_dma_buf *next;
int magic; /* magic */
int size; /* buffer size in bytes */
dma_addr_t data; /* start of DMA data */
dma_addr_t ptr; /* where the DMA got to [1] */
void *id; /* client's id */
};
内容如下:
/* buffer list and information */
struct s3c2410_dma_buf *curr; /* current dma buffer */
struct s3c2410_dma_buf *next; /* next buffer to load */
struct s3c2410_dma_buf *end; /* end of queue */
所以这个函数的作用就是将新分配的内存缓冲区添加到队列中传输。
Unsigned int channel:是所使用的dma号
Void *id:自由自配
dma_addr_t data:是内存物理地址
int size:传输的内存长度
static int s3c2410_dma_flush(struct s3c2410_dma_chan *chan)
函数是停止当前通道的所有DMA操作,并作相应的清理工作。
static inline void
s3c2410_dma_buffdone(struct s3c2410_dma_chan *chan, struct s3c2410_dma_buf *buf, enum s3c2410_dma_buffresult result)
这个是函数功能很简单就是当完成某个dma_buffer的传输以后,会进入中断处理函数,中断处理函数会调用s3c2410_dma_buffdone来干活,而s3c2410_dma_buffdone又会去调用(chan->callback_fn)(chan, buf->id, buf->size, result);这个回调函数,这个回调函数则是使用int s3c2410_dma_set_buffdone_fn(unsigned int channel, s3c2410_dma_cbfn_t rtn)来安装的。所以如果想在传输完成时做点自己的事情的话,就调用int s3c2410_dma_set_buffdone_fn来安装回调函数。
int s3c2410_dma_config(unsigned int channel,int xferunit)
函数根据通道号的信息来完成DMA硬件的控制寄存器的设置,但是真正写入寄存器的函数则是int s3c2410_dma_devconfig(int channel,enum s3c2410_dmasrc source,unsigned long devaddr)
这两个函数联合起来共同完成DMA寄存器的硬件设置工作。
申请通道、申请内存、将缓冲区放入队列、安装回调函数以及初始化硬件寄存器以后,最后的工作就是使用static int s3c2410_dma_start(struct s3c2410_dma_chan *chan)来开始DMA的传输了。
最后还有一组dma的控制操作3c2410_dma_ctrl(unsigned int channel, enum s3c2410_chan_op op)
实际上就是根据op 的内容来决定开始或停止等一系列的DMA操作。