• Linux下NANDFLASH probe函数分析


    本文记录一下自己平台上NANDFLASH驱动的执行流程。

    驱动入口:

    module_platform_driver(ali_nand_driver);

    module_platform_driver是一个宏,位于kernel根目录下include/linux/platform_device.h,其展开如下:
    #define module_platform_driver(__platform_driver) \
        module_driver(__platform_driver, platform_driver_register, \
                platform_driver_unregister)

    继续展开;

    module_driver(ali_nand_driver, platform_driver_register, platform_driver_unregister)

    module_driver 宏位于kernel根目录下include/linux/device.h,展开后是

    #define module_driver(__driver, __register, __unregister, ...) \
    static int __init __driver##_init(void) \
    { \
        return __register(&(__driver) , ##__VA_ARGS__); \
    } \
    module_init(__driver##_init); \
    static void __exit __driver##_exit(void) \
    { \
        __unregister(&(__driver) , ##__VA_ARGS__); \
    } \
    module_exit(__driver##_exit);

    第二步展开,展开宏module_driver,展开的结果是:
    /*
    ps: 在宏定义里,## 的作用是将连个参数连在一起, # 的作用是加上双引号
    eg:__driver##_init 即为 ali_nand_driver_init 
    #define TEST(a) (#a) -->那么 TEST(123) 展开是 “123”
    */

    static int __init ali_nand_driver_init(void) 
    { 
        return platform_driver_register(&(ali_nand_driver) , ##__VA_ARGS__); \
    }
    module_init(ali_nand_driver_init); 
    static void __exit ali_nand_driver_exit(void) 
    { 
        platform_driver_unregister(&(__driver) , ##__VA_ARGS__); 
    } 
    module_exit(ali_nand_driver_exit);

    //module_init宏展开
    module_init(fn)---> __initcall(fn) ---> device_initcall(fn) ---> __define_initcall(fn, 6)

    __define_initcall(ali_nand_driver_init, 6)

    syscall这里不做深究,可以简单理解为,Kernel在启动过程中会自动调用的函数。
    在kernel启动过程中,会调用do_initcalls函数一次调用我们通过xxx_initcall注册的各种函数,优先级高的先执行。
    所以我们通过module_init注册的函数在kernel启动的时候会被顺序执行。

    可以理解为kernel在启动的时候会自动执行ali_nand_driver_init函数。
    ali_nand_driver_init 
        -> platform_driver_register

    /* platform_driver_register()负责注册平台驱动程序,如果在内核中找到了使用驱动程序的设备,调用probe( )。
    刨去参数检查、错误处理。*/
    platform_driver_register(struct platform_driver *drv)
        -> driver_register(&drv->driver)
            -> bus_add_driver(drv)
                -> driver_attach(drv)
                    -> bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
                    [bus_for_each_dev(drv->bus, NULL, drv, __driver_attach) 
                        -> __driver_attach() 
                            -> driver_match_device(drv, dev) 
                                ->  platform_match(struct device *dev, struct device_driver *drv)
                                    -> of_driver_match_device(dev, drv) //首先比较.of_match_table
                            //匹配成功向下执行driver_probe_device(drv, dev),并将匹配成功的platform_device
                            //设备资源作为参数传递给probe
                    ]
                        -> driver_probe_device(drv, dev); 
                            -> really_probe(dev, drv); 
                                -> drv->probe(dev);

    platform_device由dts数据转换而来,转换过程如下:
    start_kernel() 
        -> setup_arch()
            -> setup_machine_fdt()
                -> of_flat_dt_match_machine()
                [用于获取.arch.info.init段的数据。.arch.info.init由宏DT_MACHINE_START()和宏MACHINE_START()
                来声明。]
                    -> of_flat_dt_get_machine_name()[获取dts文件中”/”node下的model或compatile字符串.]
                -> early_init_dt_scan_nodes()[扫描’/’节点下的‘chosen’子节点,获取它的属性值property]
            -> unflatten_device_tree()
                -> __unflatten_device_tree()
                [unflatten_device_tree()调用__unflatten_device_tree()继续解析dts文件,并将数据保存到
                struct device_node结构中。每个dts节点对应一个device_node,父子关系通过device_node的
                指针来关联。最后将device_node链表赋给of_root,即of_root代表所有的device_node的list的
                root,通过它,可以遍历所有的device_node。]

    解析后的device_node如何变成platform_device,并注册到platform_bus_type的klist_devices链表中?
    arch_initcall_sync(of_platform_default_populate_init)
    of_platform_default_populate_init()
        -> of_platform_bus_create()
            -> of_platform_device_create_pdate()
            [这里设置的platform device的parent,第一个为/sys/devices/platform,子节点的,依次在
            对应的子目录。到这里,dts文件描述的device_node都转换成了platform_device注册到了
            platform_bus_type.klist_devices上了.
            后面,当platform_bus_type.klist_drivers上注册上了驱动,则会调用该驱动的match_table
            去匹配platform_bus_type.klist_devices上的设备,匹配到了,则调用驱动的probe函数进一
            步处理]
            
    从下面实测可以看到nand@18032000已经被转换到/sys/devices/platform/soc下,与dts文件的层次保持一致。
    DTS:    
      nand@18032000 {
       compatible = "alitech,nand";
       reg = <0x18032000 0x60>,
       <0x18082814 0x4>;
       interrupts=<28>;
       clock-names = "nf_gate";
       nand-clk-bit = <8>;
       nand-clk-freq = <1>;
       resets = <&nand_rstc 0>;
       pinctrl-0 = <&pinctrl_nf_sel>;
       pinctrl-names = "default";
       data-scramble = <0>;
      };        
    # pwd
    /sys/devices/platform/soc
    # ls
    .
    18032000.nand              soc:ali_sbm
    .    

    所以先执行 ali_nand_probe

    //ali_nand 18032000.nand: ali_nand_probe, ali_nand_ver_2017_0608    
    【函数devm_kzalloc和kzalloc一样都是内核内存分配函数,但是devm_kzalloc是跟设备(装置)有关的
    ,当设备(装置)被拆卸或者驱动(驱动程序)卸载(空载)时,内存会被自动释放。】
    /* Allocate memory for MTD device structure and private data */
    host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL);
    if (!host)
        return -ENOMEM;

    host->dev = &pdev->dev;
    res = pdev->resource;
    nand = &host->nand;
    mtd = &host->mtd;
    mtd->priv = nand;
    mtd->owner = THIS_MODULE;
    mtd->dev.parent = &pdev->dev;
    mtd->name = "ali_nand";

    /* get nand flash reg */
    【默认外设I/O资源是不在Linux内核空间中的(如sram或硬件接口寄存器等),若需要访问该外设I/O资源,
    必须先将其地址映射到内核空间中来,然后才能在内核空间中访问它。该函数返回映射后的内核虚拟地址
    (3G-4G). 接着便可以通过读写该返回的内核虚拟地址去访问之这段I/O内存资源】
    host->soc = ioremap(SOC_BASE, 0x1040);

    【resource由dts转化而来,查看/proc/iomem,可以看到两个io地址,对应dts中的如下定义:
       reg = <0x18032000 0x60>,
       <0x18082814 0x4>;
       
    # cat /proc/iomem
    01df2000-02df1fff : System RAM
      01ef2000-0262ec7f : Kernel code
      0262ec80-028959e7 : Kernel data
    08be1000-0fffffff : System RAM
    .
    18032000-1803205f : /soc/nand@18032000
    .
    18082814-18082817 : /soc/nand@18032000
    .】    
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    host->regs = devm_ioremap_resource(&pdev->dev, res);
    res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    host->clk_reg = devm_ioremap_resource(&pdev->dev, res);

    //ali_nand 18032000.nand: ali_nand_reg, viture=0xb8032000
    【nand_chip初始化】    
    /* Assign bbt settings */
    nand->bbt_options |= NAND_BBT_USE_FLASH;
    if (!nand->badblock_pattern)
        nand->badblock_pattern = &largepage_flashbased;

    /* Reference hardware control function */
    nand->cmd_ctrl  = ali_nand_cmd_ctrl;
    nand->write_page = ali_nand_write_page;
    【如果这里有指定read_page/write_page函数,则会执行这里指定的函数,否则nand_scan_tail会根据ecc模式指定默认的读写函数】
    nand->ecc.read_page = ali_nand_read_page_hwecc;
    nand->ecc.write_page = ali_nand_write_page_hwecc;

    nand->select_chip = ali_nand_select_chip;
    【如果这里有指定read_oob/write_oob函数,则会执行这里指定的函数,否则nand_scan_tail会根据ecc模式指定默认的oob读写函数】
    nand->ecc.read_oob = ali_nand_read_oob_std;
    nand->ecc.write_oob = ali_nand_write_oob_std;
    nand->scan_bbt = nand_default_bbt;
    nand->cmdfunc = ali_nand_command_lp;
    nand->dev_ready = ali_nand_dev_ready;
    nand->chip_delay = 0;
    nand->options = NAND_NO_SUBPAGE_WRITE | NAND_USE_BOUNCE_BUFFER;
    【如果这里有指定layout,则会使用该layout,否则nand_scan_tail会根据oobsize指定默认的layout】
    nand->ecc.layout = &ali_nand_oob_32;
    nand->ecc.mode = NAND_ECC_HW;
    nand->ecc.size = 1024;
    nand->ecc.bytes = 28;
    nand->ecc.layout->oobavail = 8;
    【如果这里有指定bbt_td/bbt_md,则使用这里指定的函数,否则nand_default_bbt会将默认的函数赋值给这两个函数指针】
    nand->bbt_td = &ali_bbt_main_descr;
    nand->bbt_md = &ali_bbt_mirror_descr;

    【DMA设定】
    /* buffer1 for DMA access */
    host->dma_buf = dma_alloc_coherent(&pdev->dev,
            0x4000, &host->hw_dma_addr, GFP_KERNEL);
    if (!host->dma_buf) {
        err = -ENOMEM;
        goto out_free_host;
    }


    【Linux内核中可使用platform_get_irq()函数获取dts文件中设置的中断号。对应DTS中
    interrupts=<28>;】
    /* request irq */
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        dev_err(&pdev->dev, "failed %s (%d)\n", __func__, __LINE__);
        goto out_free_dma;
    }

    【注册中断服务函数ali_nand_irq,request_irq() 函数来注册中断服务函数,在发生对应
    于第1个参数  irq 的中断时,则调用第 2 个参数 handler 为要注册的中断服务函数(也就
    是把 handler() 中断服务函数注册到内核中)。
    第3个参数 flags指定了快速中断或中断共享等中断处理属性。
    第4个参数 name,通常是 设备驱动程序的名称。改值用在 /proc/interrupt 系统 (虚拟) 文
    件上,或内核发生中断错误时使用。
    第5个参数 dev_id 中断名称 可作为共享中断时的中断区别参数,也可以用来指定中断服务函
    数需要参考的数据地址。建议将 设备结构指针作为 dev_id参数】
    retval = request_irq(irq, ali_nand_irq, 0, DRIVER_NAME, host);
    if (retval != 0) {
        dev_err(&pdev->dev, "failed %s (%d)\n", __func__, __LINE__);
        goto out_free_dma;
    }

            
    //nand: device found, Manufacturer ID: 0xc2, Chip ID: 0xda
    //nand: Macronix MX30LF2G18AC
    //nand: 256 MiB, SLC, erase size: 128 KiB, page size: 2048, OOB size: 64
    【扫描nanflash厂商,型号及page size等信息】
    /* first scan to find the device and get the page size */
    if (nand_scan_ident(mtd, 1, NULL)) {
        err = -ENXIO;
        dev_err(&pdev->dev, "[ERR]nand_scan_ident fail, err %d\n", err);
        goto out_free_irq;
    }

    【填充mtd结构体】
    /* second phase scan */
    if (nand_scan_tail(mtd)) {
        err = -ENXIO;
        goto out_free_irq;
    }    
    nand_scan_tail
        -> chip->scan_bbt(mtd);
            -> nand_default_bbt();

    【根据mtd设备提供的设备信息,结合分区表的信息,建立新分区的mtd_info,添加到mtd_device中,
    回调块设备的注册函数,注册相关的块设备。】        
    ppdata.of_node = of_find_node_by_path("/NAND_flash@0");
    ret = mtd_device_parse_register(mtd, NULL, &ppdata,
            host ? host->parts : NULL,
            host ? host->nr_parts : 0);    
                
    【内核提供了这个方法,使用函数platform_set_drvdata()可以将host保存成平台总线设备的私有
    数据。当需要使用host变量的时候,可以使用platform_get_drvdata来获取】                
    platform_set_drvdata(pdev, host);
    dev_info(&pdev->dev, "%s, ali_nand_probe success\n", __func__);
    return 0;    

  • 相关阅读:
    springboot系列(十六):如何实现发送邮件提醒,附完整源码(完结篇)
    基于华为云云耀云服务器L实例开展性能评测|MySQL性能测评
    Python中Lambda用法
    Jdbi 指南
    大学毕业一年 - 北漂生活
    七、OCR-PaddlePaddle训练源码解析系列-文字识别
    【ESP32_8266_WiFi (十五)】ESP8266 OTA 操作说明
    Android:使用Jetpack Compose画渐变背景
    RK3399应用开发 | 编译安装 mesa 3D 图形库(23.0.0)
    kubectl插件管理工具krew
  • 原文地址:https://blog.csdn.net/man9953211/article/details/127734672