新闻中心

EEPW首页>嵌入式系统>设计应用> Linux内核的Nand驱动流程分析

Linux内核的Nand驱动流程分析

作者: 时间:2016-11-28 来源:网络 收藏
  1. s3c_nand_set_platdata(&mini2440_nand_info);
这就是上面platform_data的来源,找到mini2440_nand_info的定义也就找到了上面用到的platform_data
  1. /*NANDFlashonMINI2440board*/
  2. staticstructmtd_partitionmini2440_default_nand_part[]__initdata={
  3. [0]={
  4. .name="u-boot",
  5. .size=SZ_256K,
  6. .offset=0,
  7. },
  8. [1]={
  9. .name="u-boot-env",
  10. .size=SZ_128K,
  11. .offset=SZ_256K,
  12. },
  13. [2]={
  14. .name="kernel",
  15. /*5megabytes,forakernelwithnomodules
  16. *orauImagewitharamdiskattached*/
  17. .size=0x00500000,
  18. .offset=SZ_256K+SZ_128K,
  19. },
  20. [3]={
  21. .name="root",
  22. .offset=SZ_256K+SZ_128K+0x00500000,
  23. .size=MTDPART_SIZ_FULL,
  24. },
  25. };
  26. staticstructs3c2410_nand_setmini2440_nand_sets[]__initdata={
  27. [0]={
  28. .name="nand",
  29. .nr_chips=1,
  30. .nr_partitions=ARRAY_SIZE(mini2440_default_nand_part),
  31. .partitions=mini2440_default_nand_part,
  32. .flash_bbt=1,/*weuseu-boottocreateaBBT*/
  33. },
  34. };
  35. staticstructs3c2410_platform_nandmini2440_nand_info__initdata={
  36. .tacls=0,
  37. .twrph0=25,
  38. .twrph1=15,
  39. .nr_sets=ARRAY_SIZE(mini2440_nand_sets),
  40. .sets=mini2440_nand_sets,
  41. .ignore_unset_ecc=1,
  42. };
在该文件中,我们找到了三个相互嵌套的结构,最下面一个就是最终赋值给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,浏览代码可以知道,这个函数实际上完成了下面几件事情

本文引用地址://m.amcfsurvey.com/article/201611/322799.htm

(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. staticints3c2410_nand_add_partition(structs3c2410_nand_info*info,
  2. structs3c2410_nand_mtd*mtd,
  3. structs3c2410_nand_set*set)
  4. {
  5. if(set)
  6. mtd->mtd.name=set->name;
  7. returnmtd_device_parse_register(&mtd->mtd,NULL,NULL,
  8. set->partitions,set->nr_partitions);
  9. }

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

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

  1. structplatform_devices3c_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__inits3c244x_map_io(void)
  2. {
  3. /*registerourio-tables*/
  4. iotable_init(s3c244x_iodesc,ARRAY_SIZE(s3c244x_iodesc));
  5. /*renameanyperipheralsuseddifferingfromthes3c2410*/
  6. s3c_device_sdi.name="s3c2440-sdi";
  7. s3c_device_i2c0.name="s3c2440-i2c";
  8. s3c_nand_setname("s3c2440-nand");
  9. s3c_device_ts.name="s3c2440-ts";
  10. s3c_device_usbgadget.name="s3c2440-usbgadget";
  11. }
从这里就可以看到答案了,原来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. staticstructplatform_device_ids3c24xx_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,/*compatiblewith2412*/
  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. structbus_typeplatform_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. intplatform_driver_register(structplatform_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. returndriver_register(&drv->driver);
  18. }
从platform_bus_type的成员可知,match函数就是进行驱动匹配的,我们来看一下这个函数的定义,追踪platfrom_match
  1. staticintplatform_match(structdevice*dev,structdevice_driver*drv)
  2. {
  3. structplatform_device*pdev=to_platform_device(dev);
  4. structplatform_driver*pdrv=to_platform_driver(drv);
  5. /*AttemptanOFstylematchfirst*/
  6. if(of_driver_match_device(dev,drv))
  7. return1;
  8. /*Thentrytomatchagainsttheidtable*/
  9. if(pdrv->id_table)
  10. returnplatform_match_id(pdrv->id_table,pdev)!=NULL;
  11. /*fall-backtodrivernamematch*/
  12. return(strcmp(pdev->name,drv->name)==0);
  13. }
显然,第二个if就是判断的id_table,下面的这个函数肯定是循环比较的id_table中每个元素的.name跟device.name
  1. staticconststructplatform_device_id*platform_match_id(
  2. conststructplatform_device_id*id,
  3. structplatform_device*pdev)
  4. {
  5. while(id->name[0]){
  6. if(strcmp(pdev->name,id->name)==0){
  7. pdev->id_entry=id;
  8. returnid;
  9. }
  10. id++;
  11. }
  12. returnNULL;
  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驱动原理的分析,就不讨论这个问题了,如果以后有机会会将我认为合理的代码贴出来请大家指点。

上一页 1 2 3 下一页

评论


技术专区

关闭