Linux内核的Nand驱动流程分析

发布者:科技奇才最新更新时间:2015-08-20 来源: eefocus关键字:Linux内核  Nand驱动  流程分析 手机看文章 扫描二维码
随时随地手机看文章
最近在做Linux内核移植,总体的感觉是这样的,想要彻底的阅读Linux内核代码几乎是不可能的,至少这还不是嵌入式学期初期的重要任务。内核代码解压后有250M左右,据统计,有400多万行,而且涉及到了软件和硬件两方面的诸多知识,凭一人之力在短时间内阅读Linux内核代码是根本不可能的,强行阅读可能会打消我们嵌入式学习的积极性,最后甚至可能放弃嵌入式学习,如果真的想阅读内核代码来提高自己水平的话可以等熟练掌握嵌入式以后再回过头来阅读,这样理解也会更深刻,更透彻。

我认为Linux内核移植的初期阶段应该将重点放在分析内核设备驱动上。实际上,Linux内核的移植就是设备驱动的移植,内核本身不会直接访问硬件,是通过驱动程序来间接控制硬件的,而其他的高级功能如内存管理,进程管理等是通用的,无需做其他配置,所以我们只需要配置相关的驱动即可实现Linux内核移植。驱动移植的关键在于了解在驱动的结构,本文将以Nand驱动为例,分析Linux内核的驱动结构。

在分析驱动结构之前,还需要了解下内核识别设备的方式,内核通过驱动程序识别设备的方法有两种,一种是驱动程序本身带有设备信息,比如开始地址、中断号等,加载驱动时就可以根据驱动中的信息来识别设备;另一种是驱动程序本身没有设备信息,但是内核中已经根据其他方式确定了很多设备信息,加载驱动时将驱动程序与这些设备逐个比较,确定两者是否匹配,如果匹配就可以使用该驱动来识别设备了。内核常采用的是第二种方式,这样方式可将各种设备集中在一个文件中管理,当开发板的配置改变时便于修改代码。对应的,内核文件include/linux/platform_device.h中定义了两个结构,一个是platform_device,用来描述设备信息,一个是platform_driver,用来描述驱动信息,内核启动后首先构造链表将plartfrom_device结构组织起来得到一个设备链表,当加载某个驱动时根据platform_driver提供的信息与设备链表一一进行匹配,这就是内核设备识别的大体过程,具体的过程比这复杂很多,这里不做过多研究。下面我们开始分析Linux内核的Nand驱动。

这里以Linux内核的3.5.3中默认的mini2440开发板为例,首先定位到arm/arm/mach-s3c24xx/mach-mini2440.c,然后找到如下结构:

  1. static struct platform_device *mini2440_devices[] __initdata = {   
  2.     &s3c_device_ohci,  
  3.     &s3c_device_wdt,  
  4.     &s3c_device_i2c0,  
  5.     &s3c_device_rtc,  
  6.     &s3c_device_usbgadget,  
  7.     &mini2440_device_eth,  
  8.     &mini2440_led1,  
  9.     &mini2440_led2,  
  10.     &mini2440_led3,  
  11.     &mini2440_led4,  
  12.     &mini2440_button_device,  
  13.     &s3c_device_nand,  
  14.     &s3c_device_sdi,  
  15.     &s3c_device_iis,  
  16.     &uda1340_codec,  
  17.     &mini2440_audio,  
  18.     &samsung_asoc_dma,  
  19. };  
显然,这里就是内核需要的设备列表,通过后面的mini2440_init函数中的
  1. platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));  
注册到内核,然后由内核进行管理,显然,跟我们分析的Nand相关的就是s3c_device_nand,这就代表我们开发版上的Nand flash,我们先定位到它的定义,在arch/arm/plat-samsung/devs.c中有如下代码
  1. static struct resource s3c_nand_resource[] = {  
  2.     [0] = DEFINE_RES_MEM(S3C_PA_NAND, SZ_1M),  
  3. };  
  4.   
  5. struct platform_device s3c_device_nand = {  
  6.     .name       = "s3c2410-nand",  
  7.     .id     = -1,  
  8.     .num_resources  = ARRAY_SIZE(s3c_nand_resource),  
  9.     .resource   = s3c_nand_resource,  
  10. };  
第二个 结构就是s3c_device_nand的定义,之所以带上第一个结构是因为定义s3c_device_nand时用到了s3c_nand_resource,我们先看一下s3c_device_nand的定义,s3c_device_nand只明确定义了Nand设备的名称和设备ID,并没有给出具体的寄存器信息,加上s3c_nand_resource的名字带有资源的意思,因此我们断定,寄存器信息应该在s3c_nand_resource中,从s3c_nand_resource的定义中我们只能看到很少的信息,要想了解具体信息需要看一下struct resource和宏DEFINE_RES_MEM的定义及
  1. struct resource {  
  2.     resource_size_t start;  
  3.     resource_size_t end;  
  4.     const char *name;  
  5.     unsigned long flags;  
  6.     struct resource *parent, *sibling, *child;  
  7. };  
这里 可以看到,struct resource中定义了起始,结束,名字等信息,我们再来看一下DEFINE_RES_MEM的定义
  1. #define DEFINE_RES_NAMED(_start, _size, _name, _flags)            
  2.     {                                 
  3.         .start = (_start),                    
  4.         .end = (_start) + (_size) - 1,                
  5.         .name = (_name),                      
  6.         .flags = (_flags),                    
  7.     }  
  8. #define DEFINE_RES_MEM_NAMED(_start, _size, _name)            
  9.     DEFINE_RES_NAMED((_start), (_size), (_name), IORESOURCE_MEM)  
  10. #define DEFINE_RES_MEM(_start, _size)                     
  11.     DEFINE_RES_MEM_NAMED((_start), (_size), NULL)  
我这里整合了一下上面的信息,将相关的宏都做了一下追踪,因此,s3c_nand_resource的实际定义为
  1. {                                
  2.     .start = (S3C_PA_NAND),                   
  3.     .end = (S3C_PA_NAND) + (SZ_1M) - 1,              
  4.     .name = (NULL),                      
  5.     .flags = (IORESOURCE_MEM),                    
  6. }  
追踪可知,S3C_PA_NAND定义如下
  1. #define S3C2410_PA_NAND    (0x4E000000)  
  2. #define S3C24XX_PA_NAND     S3C2410_PA_NAND  
  3. #define S3C_PA_NAND     S3C24XX_PA_NAND  

也就是说,S3C_PA_NAND是Nand flash寄存器首地址,而SZ_1M明显是个长度,因此,这里的resource实际上是Nand flash寄存器首地址跟接下来的1M空间,可是,Nand的寄存器并没有那么多,这又是为什么呢?这些信息有什么用又在哪里用到了呢?答案很简单,这肯定是给驱动程序使用的了,带着这个疑问我们继续分析代码。定位到/drivers/mtd/nand/s3c2410.c,浏览代码可以看到驱动结构定义

  1. static struct platform_driver s3c24xx_nand_driver = {  
  2.     .probe      = s3c24xx_nand_probe,  
  3.     .remove     = s3c24xx_nand_remove,  
  4.     .suspend    = s3c24xx_nand_suspend,  
  5.     .resume     = s3c24xx_nand_resume,  
  6.     .id_table   = s3c24xx_driver_ids,  
  7.     .driver     = {  
  8.         .name   = "s3c24xx-nand",  
  9.         .owner  = THIS_MODULE,  
  10.     },     
  11. };  
可以看到,这里指定了结构中的各种操作的函数指针,从名字上可以看出probe是加载驱动程序后执行的第一个函数,remove是移除驱动前最后执行的函数,suspend是挂起操作,等等。先不着急分析这些函数,先来看看内核是如何加载驱动的,s3c24xx_nand_driver又是如何注册到内核的。往下浏览代码可以看到
  1. static int __init s3c2410_nand_init(void)  
  2. {  
  3.     printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics ");  
  4.   
  5.     return platform_driver_register(&s3c24xx_nand_driver);  
  6. }  
  7.   
  8. static void __exit s3c2410_nand_exit(void)  
  9. {  
  10.     platform_driver_unregister(&s3c24xx_nand_driver);  
  11. }  
  12.   
  13. module_init(s3c2410_nand_init);  
  14. module_exit(s3c2410_nand_exit);  
  15.   
  16. MODULE_LICENSE("GPL");  
  17. MODULE_AUTHOR("Ben Dooks ");  
  18. MODULE_DESCRIPTION("S3C24XX MTD NAND driver");  
显然,加载该驱动时s3c2410_nand_init函数将s3c24xx_nand_driver注册到了内核,卸载该驱动时s3c2410_nand_exit将s3c24xx_nand_driver注销,但是这两个函数也不过是两个普通函数,内核如何知道加载驱动时运行s3c2410_nand_init,卸载驱动时运行s3c2410_nand_exit呢?下面的module_init和module_exit解决了这个问题,它们分别告诉内核驱动程序的入口和出口。至于下面的MODULE_LICENSE指定了内核的权限协议,这里指定内核为GPL协议的,只有符合这个协议才能调用这个协议内的函数,因此是驱动程序必须的部分,剩下的两行是驱动的作者和描述,无关紧要,可以没有。现在我们明白了内核如何加载驱动了,我们再去分析probe函数,往上浏览代码可以找到[page]
  1. static int s3c24xx_nand_probe(struct platform_device *pdev)  
  2. {  
  3.     struct s3c2410_platform_nand *plat = to_nand_plat(pdev);  
  4.     enum s3c_cpu_type cpu_type;  
  5.     struct s3c2410_nand_info *info;  
  6.     struct s3c2410_nand_mtd *nmtd;  
  7.     struct s3c2410_nand_set *sets;  
  8.     struct resource *res;  
  9.     int err = 0;  
  10.     int size;  
  11.     int nr_sets;  
  12.     int setno;  
  13.   
  14.     cpu_type = platform_get_device_id(pdev)->driver_data;  
  15.   
  16.     pr_debug("s3c2410_nand_probe(%p) ", pdev);  
  17.   
  18.     info = kzalloc(sizeof(*info), GFP_KERNEL);  
  19.     if (info == NULL) {  
  20.         dev_err(&pdev->dev, "no memory for flash info ");  
  21.         err = -ENOMEM;  
  22.         goto exit_error;  
  23.     }  
  24.   
  25.     platform_set_drvdata(pdev, info);  
  26.   
  27.     spin_lock_init(&info->controller.lock);  
  28.     init_waitqueue_head(&info->controller.wq);  
  29.   
  30.     /* get the clock source and enable it */  
  31.   
  32.     info->clk = clk_get(&pdev->dev, "nand");  
  33.     if (IS_ERR(info->clk)) {  
  34.         dev_err(&pdev->dev, "failed to get clock ");  
  35.         err = -ENOENT;  
  36.         goto exit_error;  
  37.     }  
  38.   
  39.     s3c2410_nand_clk_set_state(info, CLOCK_ENABLE);  
  40.   
  41.     /* allocate and map the resource */  
  42.   
  43.     /* currently we assume we have the one resource */  
  44.     res  = pdev->resource;  
  45.     size = resource_size(res);  
  46.   
  47.     info->area = request_mem_region(res->start, size, pdev->name);  
  48.   
  49.     if (info->area == NULL) {  
  50.         dev_err(&pdev->dev, "cannot reserve register region ");  
  51.         err = -ENOENT;  
  52.         goto exit_error;  
  53.     }  
  54.   
  55.     info->device     = &pdev->dev;  
  56.     info->platform   = plat;  
  57.     info->regs       = ioremap(res->start, size);  
  58.     info->cpu_type   = cpu_type;  
  59.   
  60.     if (info->regs == NULL) {  
  61.         dev_err(&pdev->dev, "cannot reserve register region ");  
  62.         err = -EIO;  
  63.         goto exit_error;  
  64.     }  
  65.   
  66.     dev_dbg(&pdev->dev, "mapped registers at %p ", info->regs);  
  67.   
  68.     /* initialise the hardware */  
  69.   
  70.     err = s3c2410_nand_inithw(info);  
  71.     if (err != 0)  
  72.         goto exit_error;  
  73.   
  74.     sets = (plat != NULL) ? plat->sets : NULL;  
  75.     nr_sets = (plat != NULL) ? plat->nr_sets : 1;  
  76.   
  77.     info->mtd_count = nr_sets;  
  78.   
  79.     /* allocate our information */  
  80.   
  81.     size = nr_sets * sizeof(*info->mtds);  
  82.     info->mtds = kzalloc(size, GFP_KERNEL);  
  83.     if (info->mtds == NULL) {  
  84.         dev_err(&pdev->dev, "failed to allocate mtd storage ");  
  85.         err = -ENOMEM;  
  86.         goto exit_error;  
  87.     }  
  88.   
  89.     /* initialise all possible chips */  
  90.   
  91.     nmtd = info->mtds;  
  92.   
  93.     for (setno = 0; setno < nr_sets; setno++, nmtd++) {  
  94.         pr_debug("initialising set %d (%p, info %p) ", setno, nmtd, info);  
  95.   
  96.         s3c2410_nand_init_chip(info, nmtd, sets);  
  97.   
  98.         nmtd->scan_res = nand_scan_ident(&nmtd->mtd,  
  99.                          (sets) ? sets->nr_chips : 1,  
  100.                          NULL);  
  101.   
  102.         if (nmtd->scan_res == 0) {  
  103.             s3c2410_nand_update_chip(info, nmtd);  
  104.             nand_scan_tail(&nmtd->mtd);  
  105.             s3c2410_nand_add_partition(info, nmtd, sets);  
  106.         }  
  107.   
  108.         if (sets != NULL)  
  109.             sets++;  
  110.     }  
  111.   
  112.     err = s3c2410_nand_cpufreq_register(info);  
  113.     if (err < 0) {  
  114.         dev_err(&pdev->dev, "failed to init cpufreq support ");  
  115.         goto exit_error;  
  116.     }  
  117.   
  118.     if (allow_clk_suspend(info)) {  
  119.         dev_info(&pdev->dev, "clock idle support enabled ");  
  120.         s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND);  
  121.     }  
  122.   
  123.     pr_debug("initialised ok ");  
  124.     return 0;  
  125.   
  126.  exit_error:  
  127.     s3c24xx_nand_remove(pdev);  
  128.   
  129.     if (err == 0)  
  130.         err = -EINVAL;  
  131.         return err;  
  132. }  
  133.      
对于我们的Nand驱动来说,调用这个函数的参数当然是s3c_device_nand,阅读代码就可以知道前面定义每个变量的原理了。我看到函数开头定义的res就想到了我们前面定义的s3c_nand_resource,往下看能看到
  1. res  = pdev->resource;  
  2. size = resource_size(res);  
也就是说,这里引用了我们前面定义的s3c_device_nand,我们看下他如何使用的(如果前面的已经忘记了,没关系,退回去看一下),紧接着下面几行代码
  1. info->area = request_mem_region(res->start, size, pdev->name);  
  2.   
  3. if (info->area == NULL) {  
  4.     dev_err(&pdev->dev, "cannot reserve register region ");  
  5.     err = -ENOENT;  
  6.     goto exit_error;  
  7. }  
显然,这里的request_mem_region用到的参数实际上就是我们前面定义的s3c_device_nand中的start,size当然就是end-start得到的,还有就是设备的名字,我们前面定义的是"s3c2410-nand",从函数名称可以看出,这里是通过res来申请mem资源,具体的可以自己阅读下代码,实际上request_mem_region是个宏,它调用了另外一个函数,这里我就不作分析了。继续往下看,又看到一行
  1. info->regs       = ioremap(res->start, size);  
ioremap函数的作用是将物理地址影射到虚拟地址,这里就是将s3c_device_nand中记录的Nand寄存器首地址开始的1M空间作了映射,这也就理解为什么是1M空间了,因为内核的一级页表是以1M为单位的,现在就清楚为什么要定义这个s3c_nand_resource了,因为Linux内核使用的地址空间是启动MMU后的虚拟地址空间,而我们给出的寄存器地址是物理地址,内核需要将寄存器的物理地址映射到虚拟地址才能正确访问寄存器,到这里我们知道驱动程序已经可以正确访问Nand寄存器了,前面的疑惑解开了。

 

继续往下看代码,到for循环处停下来,我们需要注意一下这部分代码,因为我们看到了s3c2410_nand_init_chip,从函数名称上很容易可以看出,这就是Nand的初始化代码,但是这里为什么要使用一个for循环呢?我们看到循环控制变量是nr_sets,往上可以找到

  1. sets = (plat != NULL) ? plat->sets : NULL;  
  2. nr_sets = (plat != NULL) ? plat->nr_sets : 1;  
也就是说sets和nr_sets是从plat中获取的,再往上找plat
  1. struct s3c2410_platform_nand *plat = to_nand_plat(pdev);  
在函数的开头部分我们找到了plat的定义,看来plat是pdev中获取到的,我们跟踪进入这个to_nand_plat函数看个究竟
  1. static struct s3c2410_platform_nand *to_nand_plat(struct platform_device *dev)  
  2. {  
  3.     return dev->dev.platform_data;  
  4. }  
这个函数很简单,就是直接返回了s3c_nand_device中的dev成员的platform_data,而前面我们看到的代码中没有出现这个变量,从plat定义处指出的类型可知,这个platform_data的类型是s3c2410_platform_nand,这时,我们可以回到最开始的文件,arch/arm/mach-s3c24xx/mach-mini2440.h,可以找到mini2440_init函数中有这样一行代码[page]
  1. s3c_nand_set_platdata(&mini2440_nand_info);  
这就是上面platform_data的来源,找到mini2440_nand_info的定义也就找到了上面用到的platform_data
  1. /* NAND Flash on MINI2440 board */  
  2.   
  3. static struct mtd_partition mini2440_default_nand_part[] __initdata = {   
  4.     [0] = {   
  5.         .name   = "u-boot",  
  6.         .size   = SZ_256K,  
  7.         .offset = 0,  
  8.     },    
  9.     [1] = {   
  10.         .name   = "u-boot-env",  
  11.         .size   = SZ_128K,  
  12.         .offset = SZ_256K,  
  13.     },    
  14.     [2] = {   
  15.         .name   = "kernel",  
  16.         /* 5 megabytes, for a kernel with no modules 
  17.          * or a uImage with a ramdisk attached */  
  18.         .size   = 0x00500000,  
  19.         .offset = SZ_256K + SZ_128K,  
  20.     },    
  21.     [3] = {   
  22.         .name   = "root",  
  23.         .offset = SZ_256K + SZ_128K + 0x00500000,  
  24.         .size   = MTDPART_SIZ_FULL,  
  25.     },    
  26. };  
  27.   
  28. static struct s3c2410_nand_set mini2440_nand_sets[] __initdata = {   
  29.     [0] = {   
  30.         .name       = "nand",  
  31.         .nr_chips   = 1,  
  32.         .nr_partitions  = ARRAY_SIZE(mini2440_default_nand_part),  
  33.         .partitions = mini2440_default_nand_part,  
  34.         .flash_bbt  = 1, /* we use u-boot to create a BBT */  
  35.     },  
  36. };  
  37.   
  38. static struct s3c2410_platform_nand mini2440_nand_info __initdata = {  
  39.     .tacls      = 0,  
  40.     .twrph0     = 25,  
  41.     .twrph1     = 15,  
  42.     .nr_sets    = ARRAY_SIZE(mini2440_nand_sets),  
  43.     .sets       = mini2440_nand_sets,  
  44.     .ignore_unset_ecc = 1,  
  45. };  
在该文件中,我们找到了三个相互嵌套的结构,最下面一个就是最终赋值给platform_data的变量mini2440_nand_info,mini2440_nand_info除了指定了Nand的时序外还有两个成员,明显是匹配出现的,就是nr_sets和sets,分别指定了sets数组指针和sets数组长度,而上面的结构体就是sets的定以,从结构体名字可知,这时nand_flash芯片,也就是内核可以同时支持多个Nand,mini2440开发板中只有一块Nand,所以这个数组只有一个元素,mini2440_nand_sets指定了芯片的名称,chip数目,另外还有两个变量,也是成对出现的,就是nr_partitions和partitions,这两个就是上面的mtd_partition结构,也就是Nand分区表,这样就清楚了plartform_data中的数据,然后我们继续阅读s3c24xx_nand_probe函数(drivers/mtd/nand/s3c2410.c中),还是循环处,我们追踪进入s3c2410_nand_init_chip,浏览代码可以知道,这个函数实际上完成了下面几件事情

 

(1)初始化了chip中的各种操作函数指针并赋值给了nmtd->mtd.priv。

(2)初始化了info的sel_*成员,显然是Nand片选所用

(3)初始化了nmtd的几个成员

nmtd,info,set是该函数的三个参数,理解了这几个参数也就理解了这个函数的作用。info显然就是s3c24xx_nand_init中的s3c2410_nand_info,nmtd是info->mtds,而info->mtds是kzmalloc开辟的大小为size的内核空间,kzmalloc是kernel zero malloc,也就是开辟了size大小的空间清全部设置为0,也就是nmtds就是空的mtd数组,sets来就前面我定义的mini2440_nand_sets,这样三个参数都知道什么意思了,再去看代码就很简单了。(刚才去打了半小时电话,思路有点乱,不过大体上看了下,这个函数里面没有复杂的操作,相信大家很容易看懂)。

执行完s3c2410_nand_init之后就执行了nand_scan_ident,这是内核函数我就不做分析了,大家自己跟一下就可以知道,这个函数完成了nand_chip其他未指定函数指针的初始化,并获取了Nand的ID信息等,接下来又s3c2410_nand_update_chip,nand_scan_tail,s3c2410_nand_add_partitions,其中nand_scan_tail是通用的内核函数,而s3c2410_nand_update_chip是ecc相关的操作,我们只分析s3c2410_nand_add_partitions,从名字上讲,s3c2410开头的函数肯定不是内核通用函数,也就是说,这实际上是我们需要自行完成的函数,当然,也是可以借鉴的函数,追踪进入s3c2410_nand_add_partitions,看看内核是如何知道分区信息的。

  1. static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info,  
  2.                       struct s3c2410_nand_mtd *mtd,  
  3.                       struct s3c2410_nand_set *set)  
  4. {  
  5.     if (set)       
  6.         mtd->mtd.name = set->name;  
  7.   
  8.     return mtd_device_parse_register(&mtd->mtd, NULL, NULL,  
  9.                      set->partitions, set->nr_partitions);  
  10. }  

这个函数也很简单,仅设置了下mtd的nand然后就调用和mtd_core.c中的mtd_device_parse_register函数,从参数可以知道,该函数向内核注册了Nand分区信息。这样我们就基本上看完了Linux内核Nand驱动部分的结构。

在结尾之前我还要提到一个问题,就是内核驱动的匹配问题,在platform_device定义时内核指定的名称是s3c2410-nand

  1. struct platform_device s3c_device_nand = {  
  2.     .name       = "s3c2410-nand",  
  3.     .id     = -1,  
  4.     .num_resources  = ARRAY_SIZE(s3c_nand_resource),  
  5.     .resource   = s3c_nand_resource,  
  6. };  
但是我们的开发版是s3c2440的核,两者的Nand控制器是不相同的,内核又怎么正确加载到s3c2440-nand的呢?答案在arch/arm/mach-s3c24xx/s3c2440.c中
  1. void __init s3c244x_map_io(void)  
  2. {  
  3.     /* register our io-tables */  
  4.   
  5.     iotable_init(s3c244x_iodesc, ARRAY_SIZE(s3c244x_iodesc));  
  6.   
  7.     /* rename any peripherals used differing from the s3c2410 */  
  8.   
  9.     s3c_device_sdi.name  = "s3c2440-sdi";  
  10.     s3c_device_i2c0.name  = "s3c2440-i2c";  
  11.     s3c_nand_setname("s3c2440-nand");  
  12.     s3c_device_ts.name = "s3c2440-ts";  
  13.     s3c_device_usbgadget.name = "s3c2440-usbgadget";  
  14. }  
从这里就可以看到答案了,原来s3c_nand_setname("s3c2440-nand")将platform_device的name更改了,具体的调用关系是这样的,mini2440_map_io()----->s3c24xx_init_io()---->s3c_init_cpu()---->cpu->map_io()----->s3c2440_map_io()---->s3c_device_nand.name="s3c2440-nand",这样就不难理解了,至少内核加载驱动时是要查找与s3c2440-nand重名的驱动,但是我们的s3c24xx_nand_driver中指定的驱动名称为s3c24xx-nand,难道内核这么智能,能将s3c24xx-nand自动匹配到s3c2440-nand?当然这不可能,自己查看s3c24xx_nand_driver的各个变量就可以知道答案了,答案在drivers/mtd/nand/s3c2410.c中,原来s3c24xx_nand_driver有个成员id_table,其具体定义为
  1. static struct platform_device_id s3c24xx_driver_ids[] = {  
  2.     {      
  3.         .name       = "s3c2410-nand",  
  4.         .driver_data    = TYPE_S3C2410,  
  5.     }, {   
  6.         .name       = "s3c2440-nand",  
  7.         .driver_data    = TYPE_S3C2440,  
  8.     }, {   
  9.         .name       = "s3c2412-nand",  
  10.         .driver_data    = TYPE_S3C2412,  
  11.     }, {   
  12.         .name       = "s3c6400-nand",  
  13.         .driver_data    = TYPE_S3C2412, /* compatible with 2412 */  
  14.     },     
  15.     { }    
  16. };  
这就可以猜到了,原来内核设备匹配驱动时并不是或者说不仅仅是匹配s3c24xx_nand_driver.driver.name与设备的name,也会跟s3c24xx_nand_driver中id_table的各个元素的name进行匹配,只要匹配了其中任何一个则认为驱动匹配成功,继而执行驱动的probe函数对设备进行初始化。如果还不确定这里的关系可以追踪一下s3c24xx_nand_init中的注册函数,原来platform_driver_register调用driver_register之前对s3c24xx_nand_driver.driver.bus指针进行的初始化
  1. struct bus_type platform_bus_type = {  
  2.     .name       = "platform",  
  3.     .dev_attrs  = platform_dev_attrs,  
  4.     .match      = platform_match,  
  5.     .uevent     = platform_uevent,  
  6.     .pm     = &platform_dev_pm_ops,  
  7. };  
  8. int platform_driver_register(struct platform_driver *drv)  
  9. {  
  10.     drv->driver.bus = &platform_bus_type;  
  11.     if (drv->probe)  
  12.         drv->driver.probe = platform_drv_probe;  
  13.     if (drv->remove)  
  14.         drv->driver.remove = platform_drv_remove;  
  15.     if (drv->shutdown)  
  16.         drv->driver.shutdown = platform_drv_shutdown;  
  17.   
  18.     return driver_register(&drv->driver);  
  19. }  
从platform_bus_type的成员可知,match函数就是进行驱动匹配的,我们来看一下这个函数的定义,追踪platfrom_match
  1. static int platform_match(struct device *dev, struct device_driver *drv)  
  2. {  
  3.     struct platform_device *pdev = to_platform_device(dev);  
  4.     struct platform_driver *pdrv = to_platform_driver(drv);  
  5.   
  6.     /* Attempt an OF style match first */  
  7.     if (of_driver_match_device(dev, drv))  
  8.         return 1;  
  9.   
  10.     /* Then try to match against the id table */  
  11.     if (pdrv->id_table)  
  12.         return platform_match_id(pdrv->id_table, pdev) != NULL;  
  13.   
  14.     /* fall-back to driver name match */  
  15.     return (strcmp(pdev->name, drv->name) == 0);  
  16. }  
显然,第二个if就是判断的id_table,下面的这个函数肯定是循环比较的id_table中每个元素的.name跟device.name
  1. static const struct platform_device_id *platform_match_id(  
  2.             const struct platform_device_id *id,  
  3.             struct platform_device *pdev)  
  4. {  
  5.     while (id->name[0]) {  
  6.         if (strcmp(pdev->name, id->name) == 0) {  
  7.             pdev->id_entry = id;  
  8.             return id;  
  9.         }  
  10.         id++;  
  11.     }  
  12.     return NULL;  
  13. }  
果然,这里的确是比较的设备名称跟id_table中的名称,我们的设备名称是s3c2440-nand,我们的id_table中定义了与之对应的元素,所以驱动匹配成功继而运行了改驱动的probe函数。好了,到这里我遇到的绝大多数问题已经解决了,最后,我们需要总结一下添加Nand驱动的步骤:
  1. (1)定义resource,保证可以以物理地址方式正确访问Nand寄存器。(默认有)  
  2. (2)定义platform_device,这是内核记录的硬件信息,要注册到内核设备列表。  
  3. (3)定义mtd_partition,设置Nand分区。  
  4. (4)定义s3c2410_nand_set,枚举所有Nand芯片信息。  
  5. (5)定义s3c2410_platform_nand,这是驱动程序初始化Nand是需要的数据,包括分区信息的和芯片时序等必要信息。  
  6. (6)将s3c2410_platform_nand赋值给mtd_device->dev.platform_data。  
  7. (7)将Nand设备结构注册到设备列表。  
这样,就可以实现Nand驱动的安装,到这里,我们就理解了Nand驱动结构,也就知道了移植教程中的各个步骤,为什么设置分区表,为什么要定义s3c2410开头的几个结构。其实这种移植是基于s3c2410的移植,从软件结构的角度来讲这种移植破坏了Linux内核本身的结构,跟Linux内核本身的结构有所背离,但是这里我们做的是Nand驱动原理的分析,就不讨论这个问题了,如果以后有机会会将我认为合理的代码贴出来请大家指点。
关键字:Linux内核  Nand驱动  流程分析 引用地址:Linux内核的Nand驱动流程分析

上一篇:Eclipse在线调试ARM11——Tiny6410+OpenJTAG
下一篇:Arm2440——Nand flash启动模式详解(LED程序为例)

推荐阅读最新更新时间:2024-03-16 14:29

泰克实时示波器PAM4光分析解决方案简化验证流程
电子网消息,全球领先的测量解决方案提供商——泰克科技公司日前最新推出用于实时示波器的DPO7OE1校准后光探头和分析软件。它面向28-GBaud PAM4应用,满足光基准接收机(ORR)标准,支持IEEE/OIF-CEI特定标准测量。这一最新解决方案补充了泰克为采样示波器提供的光PAM4分析工具,设计项目组可以在光发射机工作流程的所有阶段实现高效的测试解决方案。 这一新产品基于DPO70000SX系列的实时示波器,允许研发工程师和系统工程师通过增加强大的调试功能,更简便地调试光器件,这些功能包括:PAM4和NRZ软件时钟恢复、触发、错误检测、相关捕获时间或信号相邻记录。 Maxim Integrated公司是一家为数据中心
[半导体设计/制造]
基于APIC时钟的嵌入式Linux内核实时化研究
引言      嵌入式Linux是指对Linux进行剪裁后,将其固化在单片机或者存储器中,应用于特定场合的专用Linux系统。嵌入式系统要求实时性能高,但Linux为分时系统设计的操作系统,尽管最新的内核在实时性能方面有所提高,但它仍然不是一个实时系统,在很多场合不能满足实时性要求。一般地,通过改造Linux的内核以提高其实时性能有2种策略:一种是采用底层编程的方法对Linux内核进行修改(如调度算法、时钟修改等),典型的系统有Kansas大学开发的KURT。文献提出了抢占式内核调度算法,容易引起内核优先级翻转,文献针对非抢占式内核,增加抢占点,该方法需要优秀的调度算法。另一种途径是Linux的外部实时性扩展,在原有Linux基
[嵌入式]
u-boot-2014.04启动流程分析
最近开始接触uboot,现在需要将2014.4版本uboot移植到公司armv7开发板。 在网上搜索讲uboot启动过程的文章,大多都是比较老版本的uboot,于是决定将新版uboot启动过程记录下来,和大家共享。 对于uboot,我写了一个专栏来记录我的一些理解,感兴趣的朋友可以点击以下链接: u-boot学习笔记 辛苦之作,大家共享,转载还请注明出处! Author : kerneler Email :karse0104@163.com ------------------------------------------------------------------------- # (C) Copyright
[单片机]
关于Linux内核学习的误区以及相关书籍介绍
写给Linux内核新手-关于Linux内核学习的误区   先说句正经的:其实我没资格写这篇文章,因为自己也就一两个月以来才开始有所领悟的。因此,这里与其说是关于Linux内核学习的经验,不如说是自己的教训吧,希望不要扔鸡蛋砸我^_^    常常有人问:我想学习内核,需要什么基础吗?Linus Torvalds本人是这样回答的:你必须使用过Linux。 这个……还是有点太泛了吧,我想下面几个基础可能还是需要的,尽管不一定必需:   1, 关于操作系统理论的最初级的知识。不需要通读并理解《操作系统概念》《现代操作系统》等巨著,但总要知道分时(time-shared)和实时(real-time)的区别是什么,进程是个什么东西,C
[嵌入式]
arm linux 下中断流程简要分析中断处理流程
三 响应中断 首先在分析源码之前,让我们了解一些原理性的东西, 我们都知道在处理中断要保存当前现场状态,然后才能处理中断,处理完之后还要把现场状态恢复过来才能返回到被中断的地方继续执行,这里要说明的是在指令跳转到中断向量的地方开始执行之前,CPU帮我们做了哪些事情: R14_irq = 要执行的下条指令地址 + 4 //这里的下条指令是相对于被中断指令的下条。即返回地址 SPSR_irq = CPSR //保存的现场状态,r0到r12要由我们软件来保存(如果需要的话)。 CPSR = 0b10010 //进入中断模式 CPSR = 0 //在ARM模式下执行(不是Thumb下) CPSR
[单片机]
arm linux 下中断<font color='red'>流程</font>简要<font color='red'>分析</font>中断处理<font color='red'>流程</font>
(mini2440)建立交叉编译环境+配置linux内核
系统ubuntu12.04(非虚拟机下) mini2440 CPU型号:S3C2440AL-40 Nandflash型号:K9F1G08 Norflash型号:SST39VF1601 LCD:统宝 240 x 320 $:普通账户 #:root账户 *当shell下输入路径时可使用tab键自动补全 (一)建立交叉编译环境 1.将mini2440光盘中的linux文件夹拷贝到 /home/lianghuiyong 并改名为Linux_share (其中两个文档为我后面添加进去的) 2.Ctrl+Alt+T打开shell 3.$ su - root (切换root权限) 4.# cd /home/lianghuiyong/
[单片机]
(mini2440)建立交叉编译环境+配置<font color='red'>linux内核</font>
基于Linux内核模式的PPPoE优化与实现
  PPPoE(Point.to.Point Protoeol over Ethernet)是将PPP协议封装在以太网帧上进行传输,它的通信过程分为探测(Discovery)和PPP会话(Session)2个阶段。PPPoE Discovery阶段主要是客户机确定AC(Access Concentrator)以及客户机与AC协商Session ID。而PPPoE Sess-ion用于完成数据包的接收与发送,同时它也用于完成PPP链路的协商(LCP),以及网络层的控制协商(IPCP)等。   传统的PPPoE先会用Raw socket读取数据,然后采用用户态程序对其封包解包,然后再发送给内核。但是这种方法会引起大量的内核空间与用
[嵌入式]
谷歌新操作系统Fuchsia加速推进
       Android虽然已经是使用人数最多的手机平台,但谷歌也被甲骨文Java、Linux内核效率、欧盟乐此不疲的反垄断审查折腾得够呛,可能是为了让自己完全抽身,Fuchsia OS应运而生。   Fuchsia操作系统不再使用Linux内核,而是基于Zircon微核,采用Flutter引擎+Dart语言编写。更惊人的是,它定位在跨平台(手机、PC、IoT等),支持ARM/x86体系,甚至有望兼容Android、ChromeOS平台的程序。   在AOSP的存储库中,Fuchsia OS的代码增加了760MB,涉及对977个文件的更改以及新增用于调试开发的SDK。   有趣的是,目前的测试设备是“Walleye
[手机便携]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved