前面已经了解过了smc控制器的驱动,如果要想使用nand-flash,光配置好smc控制器还不够;对于Norflash、dram 之类的存储设备,CPU 可以直接通过地址总线对其进行访问,而 Nand Flash 没有这类的总线,只有 IO 接口,只能通过复用的 IO接口发送命令和地址,从而实现对 Nand Flash 内部数据进行访问。因此必然有这样一个文件来实现复杂的flash读写操作,这个文件就是pl35x_nand.c。
-
pl35x_nand驱动程序
按照阅读linux驱动程序的方法,来解读pl35x_nand.c文件。首先也是注册驱动,如果compatible能够与设备树对应上,则调用probe函数,先看下probe函数都干了啥?
static int pl35x_nand_probe(struct platform_device *pdev)
{
struct pl35x_nand_info *xnand;
struct mtd_info *mtd;
struct nand_chip *nand_chip;
struct resource *res;
int ondie_ecc_state;
xnand = devm_kzalloc(&pdev->dev, sizeof(*xnand), GFP_KERNEL); //分配一个pl35x_nand_info结构体
if (!xnand)
return -ENOMEM;
/* Map physical address of NAND flash */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取设备树中reg属性
xnand->nand_base = devm_ioremap_resource(&pdev->dev, res); //nand基地址映射
if (IS_ERR(xnand->nand_base))
return PTR_ERR(xnand->nand_base);
nand_chip = &xnand->chip;
mtd = nand_to_mtd(nand_chip);
nand_set_controller_data(nand_chip, xnand);
mtd->priv = nand_chip;
mtd->owner = THIS_MODULE;
mtd->name = PL35X_NAND_DRIVER_NAME;
nand_set_flash_node(nand_chip, pdev->dev.of_node);
/* Set address of NAND IO lines */
nand_chip->IO_ADDR_R = xnand->nand_base;
nand_chip->IO_ADDR_W = xnand->nand_base;
/* Set the driver entry points for MTD */
nand_chip->cmdfunc = pl35x_nand_cmd_function; //发送命令给nand-flash
nand_chip->dev_ready = pl35x_nand_device_ready; //检查设备是否busy
nand_chip->select_chip = pl35x_nand_select_chip; //片选nand-flash
/* If we don't set this delay driver sets 20us by default */
nand_chip->chip_delay = 30;
/* Buffer read/write routines */
nand_chip->read_buf = pl35x_nand_read_buf; //把chip数据读入缓存
nand_chip->write_buf = pl35x_nand_write_buf; // 把缓存数据写入chip
/* Set the device option and flash width */
nand_chip->options = NAND_BUSWIDTH_AUTO;
nand_chip->bbt_options = NAND_BBT_USE_FLASH;
platform_set_drvdata(pdev, xnand);
ondie_ecc_state = pl35x_nand_detect_ondie_ecc(mtd); //获取flash的ecc状态
/* first scan to find the device and get the page size */
if (nand_scan_ident(mtd, 1, NULL)) { //扫描nand-flash芯片,获取页大小
dev_err(&pdev->dev, "nand_scan_ident for NAND failed\n");
return -ENXIO;
}
xnand->row_addr_cycles = nand_chip->onfi_params.addr_cycles & 0xF;
xnand->col_addr_cycles =
(nand_chip->onfi_params.addr_cycles >> 4) & 0xF;
pl35x_nand_ecc_init(mtd, &nand_chip->ecc, ondie_ecc_state);//根据ecc模式初始化ecc信息
if (nand_chip->options & NAND_BUSWIDTH_16)
pl35x_smc_set_buswidth(PL35X_SMC_MEM_WIDTH_16); //如果带宽是16位的,则初始化SMC控制器位16位,如果在设备树中没有指定带宽,则默认为8位
/* second phase scan */
if (nand_scan_tail(mtd)) { //在这里实现了mtd对nand的读写操作,这是对应于块设备的操作函数,字符型设备的fops在mtdchar.c中实现的
dev_err(&pdev->dev, "nand_scan_tail for NAND failed\n");
return -ENXIO;
}
mtd_device_register(mtd, NULL, 0); //解析分区,并注册mtd设备
return 0;
}
probe函数中调用了nand_scan_tail(关键函数),这个函数实现了对flash的基本操作函数,这里就不分析这些write\read函数的实现了,具体的可自己看代码。但请注意:nand_scan_tail函数中定义的只是底层的flash操作函数,并不是对应VFS的file_operation接口。
probe函数的最后,会调用mtd_device_register来注册mtd设备:
mtd_device_register->mtd_device_parse_register会直接调用到mtdcore.c中的函数了。
int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
struct mtd_part_parser_data *parser_data,
const struct mtd_partition *parts,
int nr_parts)
{
struct mtd_partitions parsed;
int ret;
mtd_set_dev_defaults(mtd);
memset(&parsed, 0, sizeof(parsed));
ret = parse_mtd_partitions(mtd, types, &parsed, parser_data); //解析设备树中的partitions分区
if ((ret < 0 || parsed.nr_parts == 0) && parts && nr_parts) {
/* Fall back to driver-provided partitions */
parsed = (struct mtd_partitions){
.parts = parts,
.nr_parts = nr_parts,
};
} else if (ret < 0) {
/* Didn't come up with parsed OR fallback partitions */
pr_info("mtd: failed to find partitions; one or more parsers reports errors (%d)\n",
ret);
/* Don't abort on errors; we can still use unpartitioned MTD */
memset(&parsed, 0, sizeof(parsed));
}
ret = mtd_add_device_partitions(mtd, &parsed); //向内核注册mtd设备
if (ret)
goto out;
/*
* FIXME: some drivers unfortunately call this function more than once.
* So we have to check if we've already assigned the reboot notifier.
*
* Generally, we can make multiple calls work for most cases, but it
* does cause problems with parse_mtd_partitions() above (e.g.,
* cmdlineparts will register partitions more than once).
*/
WARN_ONCE(mtd->_reboot && mtd->reboot_notifier.notifier_call,
"MTD already registered\n");
if (mtd->_reboot && !mtd->reboot_notifier.notifier_call) {
mtd->reboot_notifier.notifier_call = mtd_reboot_notifier;
register_reboot_notifier(&mtd->reboot_notifier);
}
out:
/* Cleanup any parsed partitions */
mtd_part_parser_cleanup(&parsed);
return ret;
}
最终在mtd_add_device_partitions中会调用到下图中的程序,所以,在/dev/目录下,我们可以看到类似mtd0、mtd1、mtd0ro、mtd1ro这些设备节点。
但看到现在,我们仍然没有看到驱动代码中实现的file_operation结构体。由于flash设备既可以作为字符型设备来访问,也可以作为块设备来访问。不管是作为哪种设备,都必须提供VFS相应的接口。如果是字符型设备,则需要在驱动中实现file_operation结构体,如果是作为块设备,则需要在驱动中实现mtd_blktrans_ops结构体。
对于用户空间的程序,一般对flash的操作,分为两种:一种是将flash作为字符型设备,利用open、write、read等函数来实现对flash的读写;还有一种就是将flash作为块设备,并将块设备上挂载文件系统,直接在文件系统中进行新建文件、删除文件等操作。不管是哪种操作,用户空间调用的都是VFS提供的接口,因此底层驱动必须要向VFS提供驱动接口,这样用户程序才可以正常使用flash设备。
那么字符型设备和块设备都是如何实现驱动对VFS的接口呢?其实,mtd有一套自己的驱动框架,不管是mtd块设备还是mtd字符设备,最终都会调用到对nand-flash的读写操作上。其各模块的调用顺序如下:
- 将mtd作为块设备来使用
首先内核会自动加载mtdblock的驱动文件,在mtdblock.c中会注册块设备对应VFS的接口程序,并通过mtdcore.c路由到nand_base.c,nand_base.c中实现的就是nand-flash具体的读写操作,而nand_base.c中实现的操作是在pl35x_nand.c中调用的。
- 将mtd作为字符设备来使用
首先内核会自动加载mtd_core的驱动文件,在mtd_core.c中会调用到mtd_char.c中的字符驱动注册函数,并在mtd_char.c中实现字符驱动对应于file_operation函数。而file_operation的成员函数又会通过mtd_core路由到nand_base.c中的函数,最终实现用户程序对flash的访问和操作。
现在可以梳理下mtd的驱动框架:
内核会自动加载mtdblock驱动(块设备注册)和mtd_core驱动(字符设备注册),然后根据你的nand-flash型号加载nand-flash驱动(pl35x_nand驱动),在pl35x_nand驱动中调用nand_base.c中的函数,而nand_base.c中定义了NAND 驱动中对NAND 芯片最基本的操作函数和操作流程,如擦除、读写page 、读写oob 等。当然这些函数都只是进行一些default 的操作,若你的系统在对NAND 操作时有一些特殊的动作,则需要在你自己的驱动代码中进行定义,然后Replace 这些default 的函数。
用户程序在将mtd设备作为块设备时,要想对块设备进行写操作,会先调用mtd_blktrans_ops结构体中的.writesect函数,并最终会调用到nand_base.c中的nand_write函数;
用户程序在讲mtd设备作为字符设备时,要想对字符设备进行写操作,会先调用file_operation结构体中的.write函数,并最终会调用到nand_base.c中的nand_write函数;
mtd驱动框架涉及的文件有:pl35x-smc.c、pl35x_nand.c 、nand_base.c 、mtdcore.c、mtdchar.c 、mtdblock.c