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;    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值