`

2410下DMA驱动源码分析

 
阅读更多

关于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的函数吧.

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/aaronychen/archive/2009/04/08/4056156.aspx

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics