分类: LINUX
2012-05-02 17:24:13
2410下DMA驱动源码分析
Author:aaron
关于2410下的DMA操作模式等信息的介绍请参考我的另外一篇文章<< S3C2410:DMA介紹>>, 这里
主要以kernel为2.6.22的源码来对2410DMA的驱动源码的做个分析.
首先我们由kconfig和makefile来获取DMA方面相关文件(即源码):
Arch/arm/plat-s3c24xx/Dma.c
Arch/arm/mach-s3c2410/Dma.c
以上两个就是操作DMA的核心文件. 我们会逐个的来分析.
先看初始化函数, 哪些是初始化函数呢? 就是哪些通过module_init, core_initcall, arch_initcall等声明的函数.
首先在arch/arm/mach-s3c2410/s3c2410.c下有个初始化函数.
arch/arm/mach-s3c2410/s3c2410.c:
static int __init s3c2410_core_init(void)
{
return sysdev_class_register(&s3c2410_sysclass); //注册一个class 类
}
core_initcall(s3c2410_core_init);
我们以后会看到, 后面的DMA设备及DMA驱动都会注册到该类下面.
arch/arm/mach-s3c2410/s3c2410.c:
struct sysdev_class s3c2410_sysclass = {
set_kset_name("s3c2410-core"),
};
很明显, 实际上该类并没有其他什么操作, 只是为了让DMA设备和驱动都注册到这个类下面, 以使对方可以互相找的到.
接着在arch/arm/plat-s3c24xx/Dma.c下也注册了一个类
arch/arm/plat-s3c24xx/Dma.c:
static int __init s3c24xx_dma_sysclass_init(void)
{
int ret = sysdev_class_register(&dma_sysclass); //注册的类
if (ret != 0)
printk(KERN_ERR "dma sysclass registration failed/n");
return ret;
}
struct sysdev_class dma_sysclass = {
set_kset_name("s3c24xx-dma"),
.suspend = s3c2410_dma_suspend,
.resume = s3c2410_dma_resume,
};
后面我们会看到这2个类是如何使用的. 其中的dma_sysclass还有suspend和resume的操作, 这些都是电源管理方面的东西,我们这里就不分析了.
接着看在arch/arm/mach-s3c2410/Dma.c下注册了DMA的驱动程序
arch/arm/mach-s3c2410/Dma.c:
#if defined(CONFIG_CPU_S3C2410) /*我们以2410为例*/
static struct sysdev_driver s3c2410_dma_driver = {
.add = s3c2410_dma_add,
};
static int __init s3c2410_dma_drvinit(void)
{
//注册驱动, 把s3c2410_dma_driver注册到s3c2410_sysclass类下
return sysdev_driver_register(&s3c2410_sysclass, &s3c2410_dma_driver);
}
arch_initcall(s3c2410_dma_drvinit);
#endif
可以看到这个函数就是把DMA的驱动程序注册到s3c2410_sysclass的类下面, 后面我们会看到DMA设备是如何找到整个驱动并调用驱动的add函数的.
Drivers/base/sys.c:
int sysdev_driver_register(struct sysdev_class * cls,
struct sysdev_driver * drv)
{
down(&sysdev_drivers_lock);
if (cls && kset_get(&cls->kset)) {
list_add_tail(&drv->entry, &cls->drivers); //把驱动注册到类下面的drivers list下
/* If devices of this class already exist, tell the driver */
if (drv->add) { //如果驱动有add函数的话
struct sys_device *dev;
list_for_each_entry(dev, &cls->kset.list, kobj.entry)
drv->add(dev); //为该类下的每个设备调用驱动的add函数.
}
} else
list_add_tail(&drv->entry, &sysdev_drivers); //把驱动注册到类下面的drivers list下
up(&sysdev_drivers_lock);
return 0;
}
通过上面这个函数, 我们就看到了s3c2410_dma_driver是如何注册进s3c2410_sysclass类的, 即就是把s3c2410_dma_driver挂到s3c2410_sysclass下的drivers列表下.
接着我们来看DMA设备的注册了.
Arch/arm/mach-s3c2410/s3c2410.c:
int __init s3c2410_init(void)
{
printk("S3C2410: Initialising architecture/n");
return sysdev_register(&s3c2410_sysdev); //注册设备了
}
static struct sys_device s3c2410_sysdev = {
.cls = &s3c2410_sysclass,
};
这个函数注册了一个系统设备, 我们看到, 其实这是个虚拟设备(其实根本就不是个设备), 它仅仅是为了要触发dma驱动的那个add函数, 所有的DMA设备会在那个时候才会真正的注册. 至于这个函数是怎么调用的问题, 就由读者自己去分析吧J, 不过我记得我有文章分析过的哦.
Drivers/base/sys.c:
int sysdev_register(struct sys_device * sysdev)
{
int error;
struct sysdev_class * cls = sysdev->cls;
if (!cls)
return -EINVAL;
/* Make sure the kset is set */
sysdev->kobj.kset = &cls->kset;
/* But make sure we point to the right type for sysfs translation */
sysdev->kobj.ktype = &ktype_sysdev;
error = kobject_set_name(&sysdev->kobj, "%s%d",
kobject_name(&cls->kset.kobj), sysdev->id);
if (error)
return error;
pr_debug("Registering sys device '%s'/n", kobject_name(&sysdev->kobj));
/* Register the object */
error = kobject_register(&sysdev->kobj);
if (!error) {
struct sysdev_driver * drv;
down(&sysdev_drivers_lock);
/* Generic notification is implicit, because it's that
* code that should have called us.
*/
//对于我们分析DMA来讲,更关心的是下面这段代码
/* Notify global drivers */
//调用所有全局的sysdev_drivers
list_for_each_entry(drv, &sysdev_drivers, entry) {
if (drv->add)
drv->add(sysdev);
}
/* Notify class auxillary drivers */
//接着调用具体class下面的驱动
list_for_each_entry(drv, &cls->drivers, entry) {
if (drv->add)
drv->add(sysdev); //驱动的add函数.
}
up(&sysdev_drivers_lock);
}
return error;
}
我们可以看到s3c2410_sysdev的类就是s3c2410_sysclass, 所以这里找到的驱动就是前面我们注册进s3c2410_sysclass的dma驱动, 因此这里的add函数就是s3c2410_dma_add了.
Arch/arm/mach-s3c2410/dma.c:
static int s3c2410_dma_add(struct sys_device *sysdev)
{
s3c2410_dma_init(); //DMA初始化
s3c24xx_dma_order_set(&s3c2410_dma_order);
return s3c24xx_dma_init_map(&s3c2410_dma_sel);
}
真正的DMA方面的操作就从这个函数开始了. 我们一个个函数来看.
Arch/arm/plat-s3c24xx/dma.c:
int s3c2410_dma_init(void)
{
return s3c24xx_dma_init(4, IRQ_DMA0, 0x40);
}
我们来看下参数, 第一个参数代表dma channel数(参考2410 data sheet), 第二个参数是dma的中断号, 第三个参数是每个channel对应的寄存器基地址与前一个channel的寄存器的基地址的偏移, 即如果第一个channel的第一个寄存器的地址是0x4b000000则第二个channel的第一个寄存器的地址是0x4b000040,
接着看
Arch/arm/plat-s3c24xx/dma.c:
int __init s3c24xx_dma_init(unsigned int channels, unsigned int irq,
unsigned int stride)
{
struct s3c2410_dma_chan *cp; //每个channel都由个s3c2410_dma_chan表示
int channel;
int ret;
printk("S3C24XX DMA Driver, (c) 2003-2004,2006 Simtec Electronics/n");
dma_channels = channels; //保存channel的数量
//把所有channel的所有寄存器地址由实地址转换成虚拟地址.
//我们驱动中使用的都是虚拟地址.
dma_base = ioremap(S3C24XX_PA_DMA, stride * channels);
if (dma_base == NULL) {
printk(KERN_ERR "dma failed to remap register block/n");
return -ENOMEM;
}
//创建一个高速缓冲对象, 具体可参考linux设备驱动程序III的第8章
dma_kmem = kmem_cache_create("dma_desc",
sizeof(struct s3c2410_dma_buf), 0,
SLAB_HWCACHE_ALIGN,
s3c2410_dma_cache_ctor, NULL);
if (dma_kmem == NULL) {
printk(KERN_ERR "dma failed to make kmem cache/n");
ret = -ENOMEM;
goto err;
}
//为每个channel初始化.
for (channel = 0; channel < channels; channel++) {
cp = &s3c2410_chans[channel]; //全局变量保存每个channel的信息.
memset(cp, 0, sizeof(struct s3c2410_dma_chan));
/* dma channel irqs are in order.. */
cp->number = channel; //channel号
cp->irq = channel + irq; //该channel的中断号
cp->regs = dma_base + (channel * stride); //该channel的寄存器基地址
/* point current stats somewhere */
cp->stats = &cp->stats_store; //channel状态
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;
}
这个函数就是对每个channel进行初始化, 并把每个channel的相关信息保存起来供以后的操作使用.
接着看下一个函数:
Arch/arm/plat-s3c24xx/dma.c:
int __init s3c24xx_dma_order_set(struct s3c24xx_dma_order *ord)
{
struct s3c24xx_dma_order *nord = dma_order; //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;
}
//保存ord信息
dma_order = nord;
memcpy(nord, ord, sizeof(struct s3c24xx_dma_order));
return 0;
}
这个函数主要是分配了一个内存用来保存order信息, 我们来看传进来的参数
Arch/arm/mach-s3c2410/dma.c:
static struct s3c24xx_dma_order __initdata s3c2410_dma_order = {
.channels = {
[DMACH_SDI] = {
.list = {
[0] = 3 | DMA_CH_VALID,
[1] = 2 | DMA_CH_VALID,
[2] = 0 | DMA_CH_VALID,
},
},
[DMACH_I2S_IN] = {
.list = {
[0] = 1 | DMA_CH_VALID,
[1] = 2 | DMA_CH_VALID,
},
},
},
};
注意这个变量用__initdata定义了, 因此它只在初始化的时候存在, 所以我们有必要分配一块内存来保存它的信息. 这也是上面那个函数的作用, 那这个s3c2410_dma_order到底有什么作用呢, 我们看这个结构的解释
Include/asm-arm/plat-s3c24xx/dma.h::
/* struct s3c24xx_dma_order
*
* information provided by either the core or the board to give the
* dma system a hint on how to allocate channels
*/
//注释说的很明确了吧, 就是用来指导系统如何分配dma channel,因为2410下的4个channel的源跟目的并不是所有的外设都可以使用的.
struct s3c24xx_dma_order {
struct s3c24xx_dma_order_ch channels[DMACH_MAX];
};
看完了s3c24xx_dma_order_set, 我们接着看s3c24xx_dma_init_map
Arch/arm/plat-s3c24xx/dma.c:
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;
}
这个函数和s3c24xx_dma_order_set的作用一样, 也是先分配一块内存然后在保存信息. 我们来看参数:
Arch/arm/mach-s3c2410/dma.c:
static struct s3c24xx_dma_selection __initdata s3c2410_dma_sel = {
.select = s3c2410_dma_select,
.dcon_mask = 7 << 24,
.map = s3c2410_dma_mappings,
.map_size = ARRAY_SIZE(s3c2410_dma_mappings),
};
呵呵也是用__initdata定义的, 难怪要重新分配内存并保存起来, 那这些是什么信息呢, 我们看到主要就是个map, 我们接着来看这个map中到底存了些什么东西.
Arch/arm/mach-s3c2410/dma.c:
static struct s3c24xx_dma_map __initdata s3c2410_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[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[2] = S3C2410_DCON_CH2_I2SSDO | DMA_CH_VALID,
.hw_addr.to = S3C2410_PA_IIS + S3C2410_IISFIFO,
},
[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,
},
};
一大堆东西, 我们还是来看这个结构的注释吧
Include/asm-arm/plat-s3c24xx/dma.h:
/* struct s3c24xx_dma_map
*
* this holds the mapping information for the channel selected
* to be connected to the specified device
*/
//保存了一些被选择使用的channel和规定的设备间的一些map信息.具体到了使用的时候就会明白了
struct s3c24xx_dma_map {
const char *name;
struct s3c24xx_dma_addr hw_addr;
unsigned long channels[S3C2410_DMA_CHANNELS];
};
Ok, 这样就把s3c2410_dma_add函数分析完了, 到这里把每个channel的各种信息包括各channel的寄存器地址, 中断号, 跟设备的关系等信息都保存好了, 但是虽然每个channel都初始化好了, 但是还记得吗, 到目前为址, 我们仅仅是向系统注册了一个虚拟的设备, 真真的DMA设备还没注册进系统呢, 因此接下来就是要注册DMA设备了, 在哪呢?
Arch/arm/plat-s3c24xx/dma.c:
static int __init s3c24xx_dma_sysdev_register(void)
{
struct s3c2410_dma_chan *cp = s3c2410_chans; //这个全局变量里已经保存了channel信息哦
int channel, ret;
//对每个channel操作
for (channel = 0; channel < dma_channels; cp++, channel++) {
cp->dev.cls = &dma_sysclass; //指定class为dma_sysclass
cp->dev.id = channel; //channel号
ret = sysdev_register(&cp->dev); //注册设备
if (ret) {
printk(KERN_ERR "error registering dev for dma %d/n",
channel);
return ret;
}
}
return 0;
}
late_initcall(s3c24xx_dma_sysdev_register); //注意这行, 它会在初始化完毕后被调用,
这个函数把所有的channel注册到dma_sysclass类下, 我们前面看到注册设备时会调用该类的add函数, 还好这里的dma_sysclass类没有add函数, 我们可以轻松下了.
Ok, 到这里DMA设备算是全部准备好了, 可以随时被请求使用了, 到这里我们总结一下:
Arch/arm/mach-s3c2410/dma.c 下的代码主要是跟具体板子相关的代码, 而真正核心的代码都在
Arch/arm/plat-s3c24xx/dma.c下, 因此如果我们有块跟2410类似的板子的话, 主要实现的就是
Arch/arm/mach-s3c2410/dma.c 这个文件了,
同时我们也不难推测, 使用DMA的函数应该都在Arch/arm/plat-s3c24xx/dma.c下. 没错, 说的更具体些就是这个文件下被EXPORT_SYMBOL出来的函数都是提供给外部使用的, 也就是其他部分使用DMA的接口. 知道了这些我们接着来分析这些被EXPORT_SYMBOL的函数吧.