新闻中心

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

Linux内核的Nand驱动流程分析

作者: 时间:2016-11-28 来源:网络 收藏
最近在做Linux内核移植,总体的感觉是这样的,想要彻底的阅读Linux内核代码几乎是不可能的,至少这还不是嵌入式学期初期的重要任务。内核代码解压后有250M左右,据统计,有400多万行,而且涉及到了软件和硬件两方面的诸多知识,凭一人之力在短时间内阅读Linux内核代码是根本不可能的,强行阅读可能会打消我们嵌入式学习的积极性,最后甚至可能放弃嵌入式学习,如果真的想阅读内核代码来提高自己水平的话可以等熟练掌握嵌入式以后再回过头来阅读,这样理解也会更深刻,更透彻。

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

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

在分析驱动结构之前,还需要了解下内核识别设备的方式,内核通过驱动程序识别设备的方法有两种,一种是驱动程序本身带有设备信息,比如开始地址、中断号等,加载驱动时就可以根据驱动中的信息来识别设备;另一种是驱动程序本身没有设备信息,但是内核中已经根据其他方式确定了很多设备信息,加载驱动时将驱动程序与这些设备逐个比较,确定两者是否匹配,如果匹配就可以使用该驱动来识别设备了。内核常采用的是第二种方式,这样方式可将各种设备集中在一个文件中管理,当开发板的配置改变时便于修改代码。对应的,内核文件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. staticstructplatform_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. staticstructresources3c_nand_resource[]={
  2. [0]=DEFINE_RES_MEM(S3C_PA_NAND,SZ_1M),
  3. };
  4. structplatform_devices3c_device_nand={
  5. .name="s3c2410-nand",
  6. .id=-1,
  7. .num_resources=ARRAY_SIZE(s3c_nand_resource),
  8. .resource=s3c_nand_resource,
  9. };
第二个 结构就是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. structresource{
  2. resource_size_tstart;
  3. resource_size_tend;
  4. constchar*name;
  5. unsignedlongflags;
  6. structresource*parent,*sibling,*child;
  7. };
这里 可以看到,struct resource中定义了起始,结束,名字等信息,我们再来看一下DEFINE_RES_MEM的定义
  1. #defineDEFINE_RES_NAMED(_start,_size,_name,_flags)
  2. {
  3. .start=(_start),
  4. .end=(_start)+(_size)-1,
  5. .name=(_name),
  6. .flags=(_flags),
  7. }
  8. #defineDEFINE_RES_MEM_NAMED(_start,_size,_name)
  9. DEFINE_RES_NAMED((_start),(_size),(_name),IORESOURCE_MEM)
  10. #defineDEFINE_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. #defineS3C2410_PA_NAND(0x4E000000)
  2. #defineS3C24XX_PA_NANDS3C2410_PA_NAND
  3. #defineS3C_PA_NANDS3C24XX_PA_NAND

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

  1. staticstructplatform_drivers3c24xx_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. staticint__inits3c2410_nand_init(void)
  2. {
  3. printk("S3C24XXNANDDriver,(c)2004SimtecElectronics");
  4. returnplatform_driver_register(&s3c24xx_nand_driver);
  5. }
  6. staticvoid__exits3c2410_nand_exit(void)
  7. {
  8. platform_driver_unregister(&s3c24xx_nand_driver);
  9. }
  10. module_init(s3c2410_nand_init);
  11. module_exit(s3c2410_nand_exit);
  12. MODULE_LICENSE("GPL");
  13. MODULE_AUTHOR("BenDooks ");
  14. MODULE_DESCRIPTION("S3C24XXMTDNANDdriver");
显然,加载该驱动时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函数,往上浏览代码可以找到

上一页 1 2 3 下一页

评论


技术专区

关闭