Linux下NANDFLASH probe函数分析

news/2024/7/7 22:10:48

本文记录一下自己平台上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;
nand->bbt_td = &ali_bbt_main_descr;
nand->bbt_md = &ali_bbt_mirror_descr;
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;
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;
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;
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;
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;    


http://lihuaxi.xjx100.cn/news/141992.html

相关文章

Java中的容器(二) 双例集合

容器(二) 双例集合 1、Map接口介绍 Map中不能包含重复的Key&#xff0c;但可以包含重复的Value。 Map中的常用方法 put方法&#xff1a;key不存在时返回空&#xff0c;key存在时更新value&#xff0c;并返回旧的value。 2、HashMap容器类 通过KeySet获取元素 Set<String…

VectorDraw开发者框架(VDF)

VectorDraw开发者框架(VDF) VectorDraw Developer Framework(VDF)是一个易于创建、管理和打印2D和3D图纸的组件。VectorDraw对象公开了与最常见的矢量格式和其他CAD对象兼容的方法和属性。它支持10多种矢量格式和多种光栅格式。VectorDrawDeveloperFramework(VDF)是完全面向对象…

arraybuffer 转json

场景后端返回的数据是arraybuffer 类型&#xff0c;请求成功下载excel&#xff0c;失败弹出错误原因 <script setup lang"ts"> import { ElMessage} from element-plus; // 下载; const exportData async (fileName: string) > {const res await downloa…

详解Git合并(Merge)错误如何回退。(包括Reset, Revert和页面回滚三种,并说明其优缺点)

文章目录1. 问题场景描述1.1 场景模拟2. 解决方案2.1 利用git reset --hard命令2.2 利用git revert 命令2.3 使用页面进行回滚&#xff08;效果与Revert一致&#xff09;1. 问题场景描述 我的项目包含两个重要git分支&#xff1a;master&#xff08;生产环境&#xff09;&…

在 AWS Marketplace 上订阅 EMQX Cloud 按量计费版

近日&#xff0c;全球领先的开源物联网数据基础设施软件供应商 EMQ 旗下的全托管 MQTT 消息云服务 EMQX Cloud 正式上架云端事业领导者亚马逊 AWS Marketplace&#xff0c;与国际各大云端软件并列销售。 在 AWS Marketplace 中即付即用的 EMQX Cloud 的推出&#xff0c;将为开…

仪器仪表与传感器信号带宽及频响的现场匹配技术

概述:工业现场传感器与PLC/FCS/DCS、仪器仪表之间输入输出的模拟信号隔离放大器(亦称模拟量隔离变送器)属于模拟信号调理的范畴。模拟信号隔离放大器能有效保护各级控制回路,消除或减弱环境噪声对测试电路的影响,抑制公共接地、变频器、电磁阀及浪涌脉冲对设备的干扰,同时…

Java面试大厂名企高频真题--Spring 注解

要求 掌握 Spring 常见注解 提示 注解的详细列表请参考&#xff1a;面试题-spring-注解.xmind 下面列出了视频中重点提及的注解&#xff0c;考虑到大部分注解同学们已经比较熟悉了&#xff0c;仅对个别的作简要说明 原图太大&#xff0c;放不上来&#xff0c;有需要的同学可以…

JAVA家政服务管理系统毕业设计 开题报告

本文给出的java毕业设计开题报告&#xff0c;仅供参考&#xff01;&#xff08;具体模板和要求按照自己学校给的要求修改&#xff09; 选题目的和意义 目的&#xff1a;本课题主要目标是设计并能够实现一个基于web网页的家政服务预约系统&#xff0c;整个网站项目使用了B/S架…