本文记录一下自己平台上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;