全部博文(584)
分类: LINUX
2010-08-19 16:17:36
首 先我们由 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 这个文件了 ,