Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1680885
  • 博文数量: 584
  • 博客积分: 13857
  • 博客等级: 上将
  • 技术积分: 11883
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-16 09:34

分类: 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 这个文件了 ,


阅读(1368) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~