Linux Mtd子系统3(基于Linux6.6)---MTD层相关接口介绍
在 Linux MTD(Memory Technology Device)层,涉及到设备管理和数据结构关联的接口主要用于实现 MTD 设备与内核中其他部分(如块设备、文件系统等)之间的关联与解绑。以下是一些关键的接口和数据结构,它们在 MTD 子系统中用于管理 MTD 设备的生命周期,尤其是设备的注册、解绑以及和其他内核子系统(如 UBI 和文件系统)之间的关联。
一、mtd设备相关的注册与注销接口
针对mtd设备相关的注册,主要包括如下几个方面:
- 提供mtd设备注册的接口,完成mtd info设备注册至设备驱动模型子系统中,并完成mtd字符设备与mtd块设备的创建(从而完成mtd info与设备驱动子系统、vfs子系统的关联);
- 若闪存设备支持分区,则提供分区注册接口,主要完成每一个分区对应的mtd_info设备注册至设备驱动模型子系统中;并将每一个分区对应的mtd_part类型的变量注册至链表mtd_partitions中;
基本上也就以上两个功能,而在mtd子系统中,针对注册的接口即为mtd_device_parse_register,而在该接口中,则根据是否进行分区,分别调用add_mtd_partitions、add_mtd_device的注册。
1.1、mtd_device_parse_register接口分析
该接口主要实现将mtd_info设备注册到mtd子系统中。由于闪存设备可以选择分区,也可以选择不分区,因此该接口包括这两个功能。该接口的实现如下,还包括parse_mtd_partitions接口,该接口主要是解析分区信息的配置。
drivers/mtd/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)
{
int ret;
mtd_set_dev_defaults(mtd);
ret = mtd_otp_nvmem_add(mtd);
if (ret)
goto out;
if (IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER)) {
ret = add_mtd_device(mtd);
if (ret)
goto out;
}
/* Prefer parsed partitions over driver-provided fallback */
ret = parse_mtd_partitions(mtd, types, parser_data);
if (ret == -EPROBE_DEFER)
goto out;
if (ret > 0)
ret = 0;
else if (nr_parts)
ret = add_mtd_partitions(mtd, parts, nr_parts);
else if (!device_is_registered(&mtd->dev))
ret = add_mtd_device(mtd);
else
ret = 0;
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:
if (ret) {
nvmem_unregister(mtd->otp_user_nvmem);
nvmem_unregister(mtd->otp_factory_nvmem);
}
if (ret && device_is_registered(&mtd->dev))
del_mtd_device(mtd);
return ret;
}
EXPORT_SYMBOL_GPL(mtd_device_parse_register);
若一个闪存芯片,在注册至mtd子系统中时,不需要进行分区,则只需要使用接口add_mtd_device即可。
1.2、add_mtd_device接口
drivers/mtd/mtdcore.c
int add_mtd_device(struct mtd_info *mtd)
{
struct device_node *np = mtd_get_of_node(mtd);
struct mtd_info *master = mtd_get_master(mtd);
struct mtd_notifier *not;
int i, error, ofidx;
/*
* May occur, for instance, on buggy drivers which call
* mtd_device_parse_register() multiple times on the same master MTD,
* especially with CONFIG_MTD_PARTITIONED_MASTER=y.
*/
if (WARN_ONCE(mtd->dev.type, "MTD already registered\n"))
return -EEXIST;
BUG_ON(mtd->writesize == 0);
/*
* MTD drivers should implement ->_{write,read}() or
* ->_{write,read}_oob(), but not both.
*/
if (WARN_ON((mtd->_write && mtd->_write_oob) ||
(mtd->_read && mtd->_read_oob)))
return -EINVAL;
if (WARN_ON((!mtd->erasesize || !master->_erase) &&
!(mtd->flags & MTD_NO_ERASE)))
return -EINVAL;
/*
* MTD_SLC_ON_MLC_EMULATION can only be set on partitions, when the
* master is an MLC NAND and has a proper pairing scheme defined.
* We also reject masters that implement ->_writev() for now, because
* NAND controller drivers don't implement this hook, and adding the
* SLC -> MLC address/length conversion to this path is useless if we
* don't have a user.
*/
if (mtd->flags & MTD_SLC_ON_MLC_EMULATION &&
(!mtd_is_partition(mtd) || master->type != MTD_MLCNANDFLASH ||
!master->pairing || master->_writev))
return -EINVAL;
mutex_lock(&mtd_table_mutex);
ofidx = -1;
if (np)
ofidx = of_alias_get_id(np, "mtd");
if (ofidx >= 0)
i = idr_alloc(&mtd_idr, mtd, ofidx, ofidx + 1, GFP_KERNEL);
else
i = idr_alloc(&mtd_idr, mtd, 0, 0, GFP_KERNEL);
if (i < 0) {
error = i;
goto fail_locked;
}
mtd->index = i;
kref_init(&mtd->refcnt);
/* default value if not set by driver */
if (mtd->bitflip_threshold == 0)
mtd->bitflip_threshold = mtd->ecc_strength;
if (mtd->flags & MTD_SLC_ON_MLC_EMULATION) {
int ngroups = mtd_pairing_groups(master);
mtd->erasesize /= ngroups;
mtd->size = (u64)mtd_div_by_eb(mtd->size, master) *
mtd->erasesize;
}
if (is_power_of_2(mtd->erasesize))
mtd->erasesize_shift = ffs(mtd->erasesize) - 1;
else
mtd->erasesize_shift = 0;
if (is_power_of_2(mtd->writesize))
mtd->writesize_shift = ffs(mtd->writesize) - 1;
else
mtd->writesize_shift = 0;
mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1;
mtd->writesize_mask = (1 << mtd->writesize_shift) - 1;
/* Some chips always power up locked. Unlock them now */
if ((mtd->flags & MTD_WRITEABLE) && (mtd->flags & MTD_POWERUP_LOCK)) {
error = mtd_unlock(mtd, 0, mtd->size);
if (error && error != -EOPNOTSUPP)
printk(KERN_WARNING
"%s: unlock failed, writes may not work\n",
mtd->name);
/* Ignore unlock failures? */
error = 0;
}
/* Caller should have set dev.parent to match the
* physical device, if appropriate.
*/
mtd->dev.type = &mtd_devtype;
mtd->dev.class = &mtd_class;
mtd->dev.devt = MTD_DEVT(i);
dev_set_name(&mtd->dev, "mtd%d", i);
dev_set_drvdata(&mtd->dev, mtd);
mtd_check_of_node(mtd);
of_node_get(mtd_get_of_node(mtd));
error = device_register(&mtd->dev);
if (error) {
put_device(&mtd->dev);
goto fail_added;
}
/* Add the nvmem provider */
error = mtd_nvmem_add(mtd);
if (error)
goto fail_nvmem_add;
mtd_debugfs_populate(mtd);
device_create(&mtd_class, mtd->dev.parent, MTD_DEVT(i) + 1, NULL,
"mtd%dro", i);
pr_debug("mtd: Giving out device %d to %s\n", i, mtd->name);
/* No need to get a refcount on the module containing
the notifier, since we hold the mtd_table_mutex */
list_for_each_entry(not, &mtd_notifiers, list)
not->add(mtd);
mutex_unlock(&mtd_table_mutex);
if (of_property_read_bool(mtd_get_of_node(mtd), "linux,rootfs")) {
if (IS_BUILTIN(CONFIG_MTD)) {
pr_info("mtd: setting mtd%d (%s) as root device\n", mtd->index, mtd->name);
ROOT_DEV = MKDEV(MTD_BLOCK_MAJOR, mtd->index);
} else {
pr_warn("mtd: can't set mtd%d (%s) as root device - mtd must be builtin\n",
mtd->index, mtd->name);
}
}
/* We _know_ we aren't being removed, because
our caller is still holding us here. So none
of this try_ nonsense, and no bitching about it
either. :) */
__module_get(THIS_MODULE);
return 0;
fail_nvmem_add:
device_unregister(&mtd->dev);
fail_added:
of_node_put(mtd_get_of_node(mtd));
idr_remove(&mtd_idr, i);
fail_locked:
mutex_unlock(&mtd_table_mutex);
return error;
}
该接口的处理流程如下图所示,其主要实现如下功能:
- 设置mtd_info相关的信息,包括writesize_shift等;另外针对设置了可写信息且上电时锁定芯片的mtd设备,需要执行unlock操作(在某些场景下,我们做flash的设计时,可以让硬件设计人员将wp引脚默认为有效,用于保证系统上电时可能导致的写异常。然后在驱动中再将wp引脚关闭即可,而mtd子系统支持该功能,只需要实现mtd_info中_unlock成员函数指针即可);
- 设置该mtd_info所包含的device类型成员的信息,主要设置其所属的class以及其device_type信息
关于device、class相关的关联我们在之前的设备驱动模型系列文章中已经说明;而在mtd_devtype中则主要定义了mtd_info设备的注销信息。如下为device_ktype中的release和device_type的release接口的关系,当mtd_info的引用计数为0时,则会调用device_ktype的release接口,最终则调用mtd_release进行释放操作。
调用device_register,进行mtd_info对应device类型变量的设备注册,而在注册中会向应用层发送uevent信息(借助netlink),然后应用层的mdev/udev程序接收到uevent信息,根据设备号会进行字符设备与块设备对应inode节点的创建(调用mknod),至此即完成该mtd_info对应的字符设备与块设备文件的创建;
1.3、add_mtd_partitions
该接口主要针对需要对注册设备进行分区的情况,该接口主要实现两个功能:
- 针对每一个分区,均创建一个mtd_part类型的变量,该变量包括master mtd_info以及该分区对应的mtd_info,并初始化该分区对应slave mtd_info,而针对slave mtd_info,其上层接口主要为分区相关的接口,该接口主要会调用master mtd_info对应的接口。针对master mtd_info、slave mtd_info的关联,在下面分析。
- 针对每一个分区,将其mtd_part中的slave mtd_info调用add_mtd_device,将其注册至mtd子系统中。
drivers/mtd/mtdpart.c
int add_mtd_partitions(struct mtd_info *parent,
const struct mtd_partition *parts,
int nbparts)
{
struct mtd_info *child, *master = mtd_get_master(parent);
uint64_t cur_offset = 0;
int i, ret;
printk(KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n",
nbparts, parent->name);
for (i = 0; i < nbparts; i++) {
child = allocate_partition(parent, parts + i, i, cur_offset);
if (IS_ERR(child)) {
ret = PTR_ERR(child);
goto err_del_partitions;
}
mutex_lock(&master->master.partitions_lock);
list_add_tail(&child->part.node, &parent->partitions);
mutex_unlock(&master->master.partitions_lock);
ret = add_mtd_device(child);
if (ret) {
mutex_lock(&master->master.partitions_lock);
list_del(&child->part.node);
mutex_unlock(&master->master.partitions_lock);
free_partition(child);
goto err_del_partitions;
}
mtd_add_partition_attrs(child);
/* Look for subpartitions */
parse_mtd_partitions(child, parts[i].types, NULL);
cur_offset = child->part.offset + child->part.size;
}
return 0;
err_del_partitions:
del_mtd_partitions(master);
return ret;
}
Mtd_info的注销接口为mtd_device_unregister,该接口主要是实现mtd_info的注销操作,其实主要就是调用device_unregister进行mtd_info对应device类型设备的注销,同时若为分区设备,则将其从mtd_partitions链表上删除等。
drivers/mtd/mtdcore.c
int mtd_device_unregister(struct mtd_info *master)
{
int err;
if (master->_reboot) {
unregister_reboot_notifier(&master->reboot_notifier);
memset(&master->reboot_notifier, 0, sizeof(master->reboot_notifier));
}
nvmem_unregister(master->otp_user_nvmem);
nvmem_unregister(master->otp_factory_nvmem);
err = del_mtd_partitions(master);
if (err)
return err;
if (!device_is_registered(&master->dev))
return 0;
return del_mtd_device(master);
}
EXPORT_SYMBOL_GPL(mtd_device_unregister);
二、Mtd info master与slaver的区别
在 Linux MTD(Memory Technology Device)子系统中,"master" 和 "slave" 之间的关联和区别通常与多设备管理、总线结构或设备的工作模式相关。虽然 MTD 本身并不直接定义“master”和“slave”的术语,但这些术语通常用于描述多设备架构中的主设备和从设备关系,尤其是在涉及 SPI Flash、NAND Flash 等场景下。
2.1、MTD 设备的主从结构(Master/Slave)
在多设备管理中,"master" 和 "slave" 关系指的是主设备(master)控制从设备(slave)或从设备依赖于主设备进行操作。这个概念在 MTD 中并没有严格的定义,但可以在以下几个方面找到类似的工作模式:
SPI Flash 中的 Master 和 Slave
在 SPI 总线通信中,通常有一个主设备(master)和多个从设备(slave)。主设备通过 SPI 总线控制与多个从设备进行通信。每个 SPI Flash 存储芯片可能是一个 MTD 设备,主设备通过 SPI 总线发起操作并控制从设备。
- Master:主设备通常是控制通信和发起操作的设备,可能是嵌入式处理器(如 ARM 或其他微控制器)。
- Slave:从设备是被主设备控制并响应命令的设备,通常是存储设备如 SPI Flash。
在这种情况下,mtd_info
结构中的设备会包含与 SPI 相关的操作和参数,但它本身并不标识为“master”或“slave”,而是通过硬件总线的通信模式来区分。
NAND Flash 中的 Master 和 Slave
在 NAND Flash 存储器中,也可能存在主从设备的概念,尤其是当一个控制器管理多个 NAND Flash 芯片时。在这种情况下,主设备控制多个 NAND Flash 设备(每个 Flash 芯片可能是一个从设备)。虽然每个 NAND Flash 设备在 MTD 层注册时都有一个独立的 mtd_info
结构,但它们的操作可能由一个主控制器进行协调。
2.2、MTD 设备的信息管理:Master 和 Slave 关联
在 MTD 子系统中,mtd_info
结构是描述每个 MTD 设备的核心数据结构。它包含设备的基本信息(如设备大小、擦除大小、操作函数等)。当涉及多个设备时(如多颗 NAND Flash 或多个 SPI Flash),它们可能通过以下几种方式进行关联:
多设备管理(例如多颗 NAND Flash)
一个主设备(通常是控制器)可能控制多个从设备。在 MTD 中,这种结构可以通过:
- 设备驱动的绑定关系:例如,一个 NAND 控制器驱动程序可能会控制多个 NAND Flash 芯片,每个 NAND Flash 芯片都有自己的
mtd_info
结构,控制器管理这些设备的访问。 - 设备树配置:通过设备树(Device Tree)来描述主设备和从设备之间的关系,特别是在多设备系统中。
设备注册和层次结构
在多设备情境下,主设备(Master)通常负责注册和管理多个从设备(Slaves)。这些从设备可以是多个 MTD 设备实例,每个实例对应一个 Flash 芯片或存储设备。通过设备树或硬件配置,主设备知道如何与每个从设备进行通信。
例如,对于一个多芯片的 NAND Flash 设备,主设备(控制器)会分别初始化多个 mtd_info
结构,并为每个从设备分配操作函数。每个从设备的 mtd_info
会通过主设备的控制器进行访问。
2.3、主从设备的区别
从概念上来说,"master" 和 "slave" 的区别可以从以下几个方面理解:
- 主设备(Master):通常是负责控制、发起操作并管理从设备的设备。在 SPI 总线中,主设备是发起通信的设备,在 NAND 控制器中,主设备管理多个 Flash 芯片。
- 从设备(Slave):从设备是响应主设备操作的设备。在 SPI 总线中,从设备响应主设备的读取和写入请求,在 NAND 控制器中,每个 NAND Flash 芯片作为从设备响应主控制器的命令。
2.4、MTD 驱动中如何实现 Master 和 Slave 的关联
在 Linux 内核的 MTD 驱动中,多个设备的管理通常是通过以下几种方式实现关联的:
- SPI 驱动:在 SPI 驱动中,主设备通过 SPI 总线发起命令与多个从设备进行交互。每个 Flash 存储设备(例如 SPI Flash)会有一个独立的
mtd_info
结构,主设备通过 SPI 总线协议进行控制。 - NAND 驱动:在 NAND 驱动中,主设备通常是 NAND 控制器,它会管理多个 NAND Flash 芯片。每个 NAND Flash 芯片都可以看作一个从设备,它们会各自注册自己的
mtd_info
结构,但它们的操作和访问都是通过主控制器来管理的。 - 设备树(Device Tree):在多设备系统中,设备树可以用于描述主设备与从设备之间的层次关系,帮助内核了解如何初始化和关联多个设备。
如下图是逻辑关联图,当没有逻辑分区时,则mtd接口层通过黄色箭头调用master mtd_info中的接口完成对下层闪存芯片的访问;当存在逻辑分区时,则mtd接口层通过蓝色接口借助slaver mtd_info,间接调用master mtd_info的接口完成对下层闪存芯片的访问。
三、如何进行分区设置操作
在 Linux 中,针对闪存芯片的逻辑分区(Logical Partitioning)设置通常是指如何对 NAND Flash 或 SPI Flash 等闪存设备进行分区,以便更有效地管理存储、提高性能或实现特定的功能。Linux 提供了多种方法来进行闪存芯片的逻辑分区设置,通常包括通过 MTD(Memory Technology Device) 子系统来管理这些设备的分区。以下是常见的闪存芯片逻辑分区设置方法:
3.1、MTD 子系统中的逻辑分区
Linux 的 MTD 子系统专门设计用于处理闪存设备,它允许对闪存设备进行分区和管理。通过 mtd
设备接口,用户可以将闪存设备划分为多个逻辑分区,每个分区对应一个设备的部分存储区域。
MTD 分区(Partition)
在 MTD 子系统中,分区通常指的是将一个大容量的闪存设备(如 NAND 或 SPI Flash)切分成多个逻辑区域。每个分区对应一个独立的 MTD 设备实例,具有自己的操作(读取、写入、擦除)接口。
- MTD 分区通常有两种类型:
- 固定大小的分区:每个分区有固定的大小(例如,16MB、32MB等),在闪存设备初始化时设置。
- 按需求创建的分区:根据设备树(Device Tree)或用户配置动态创建的分区。
分区表
分区信息通常会通过 设备树(Device Tree) 或 内核配置文件 来设置。设备树中可以定义闪存设备的分区表,指定每个分区的起始地址、大小和类型。
例子:设备树中分区表配置
mtd@0 {
compatible = "generic-mtd";
reg = <0x0 0x10000000>; // Start address and size of the MTD device
partitions {
compatible = "linux,mtd-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "boot";
reg = <0x0 0x8000000>; // 128MB
};
partition@1 {
label = "rootfs";
reg = <0x8000000 0x7000000>; // 112MB
};
partition@2 {
label = "data";
reg = <0xf000000 0x1000000>; // 16MB
};
};
};
在这个例子中,设备树定义了一个大闪存设备,并为其创建了三个分区:boot
、rootfs
和 data
,每个分区都有一个标签和大小。
3.2、U-Boot 和闪存分区
在嵌入式设备中,U-Boot 通常用于引导加载和管理设备分区。在 U-Boot 中,用户可以设置和查看闪存的分区。U-Boot 提供了一个 mtdparts
环境变量来配置闪存的逻辑分区。
使用 mtdparts
配置分区
通过 U-Boot 提供的 mtdparts
命令,用户可以为 NAND Flash 或 SPI Flash 设置分区表。分区表可以通过 U-Boot 环境变量进行设置,示例如下:
setenv mtdparts 'mtdparts=nand0:128k(boot),256k(env),1024k(kernel),-(rootfs)'
saveenv
这段配置表示将 NAND Flash 划分为几个分区,其中:
boot
分区大小为 128KBenv
分区大小为 256KBkernel
分区大小为 1MBrootfs
占用剩余的所有空间
U-Boot 中加载和保存配置
U-Boot 在启动时会加载这些分区信息,并根据这些信息来启动操作系统或执行其他操作(如烧写固件)。U-Boot 的 mtdparts
设置也可以与设备树中的分区表进行配合,以确保内核能够正确识别和使用这些分区。
3.3、使用 ubifs
或 jffs2
文件系统
针对闪存设备的逻辑分区设置通常还会涉及文件系统的选择。对于 NAND Flash,常用的文件系统有 UBIFS(UBI File System)和 JFFS2(Journaling Flash File System v2)。这些文件系统专门为闪存设备设计,提供了对擦写周期的管理和容错能力。
UBIFS
UBIFS 是一种适用于大容量闪存(特别是 NAND Flash)的文件系统,通常与 UBI(Unsorted Block Images)层结合使用。UBIFS 支持动态的逻辑分区配置,并能够通过 UBI 层来管理闪存设备上的空间。
- UBI 逻辑卷:UBI(Unsorted Block Images)是 Linux 中的一种虚拟化闪存层,它允许闪存设备的逻辑分区可以在运行时动态管理。
- 分区方式:通过
mtd
子系统对 UBIFS 文件系统进行挂载时,用户可以指定分区设备和挂载点。
mount -t ubifs ubi0:rootfs /mnt
JFFS2
JFFS2 是另一种针对闪存设备的文件系统,适用于小容量的闪存设备。它通过直接在闪存上管理数据的擦除和回写来提供持久存储。在 JFFS2 上,也可以通过 mtd
设备来设置逻辑分区。
mount -t jffs2 /dev/mtdblock0 /mnt
3.4、闪存的磨损均衡和分区
闪存设备的一个关键特性是它们的有限擦写次数,因此在分区设计时要考虑如何管理闪存的磨损(wear leveling)。常见的方法有:
- UBI 层提供的磨损均衡:UBI 层会动态调整擦写操作的区域,以平衡不同区域的擦写次数。
- 合理分配分区:通过合理地分配各个分区的大小,可以减少闪存的磨损集中在某些区域。
3.5、分区管理工具
Linux 提供了一些工具来帮助管理闪存设备的分区。常见的工具包括:
flash_erase
:擦除闪存的一部分内容。mtd-utils
:包含一组命令行工具,用于管理 MTD 设备和分区,例如nandwrite
、nanddump
等。
四、如何实现将一个闪存芯片注册至mtd子系统中
将闪存芯片注册到 MTD 子系统通常涉及以下几个步骤:
4.1、编写或修改设备驱动
闪存设备需要一个设备驱动来让内核识别和管理它。驱动程序负责与硬件交互、初始化设备并将其注册到 MTD 子系统中。不同类型的闪存设备(如 NAND、SPI Flash、NOR Flash 等)有不同的驱动方式。
NAND Flash 驱动示例
假设我们要将一个 NAND Flash 芯片注册到 MTD 子系统中,驱动程序通常需要完成以下任务:
- 初始化硬件并获取相关的硬件信息(如设备容量、页大小等)。
- 使用 MTD 子系统的 API 注册 NAND 设备。
- 为闪存设备设置合适的接口函数(如读、写、擦除等)。
以下是一个基本的 NAND Flash 驱动示例,演示了如何注册一个 NAND 设备。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/platform_device.h>
static struct nand_chip my_nand_chip;
static struct mtd_info *mtd;
static int my_nand_probe(struct platform_device *pdev)
{
int ret;
/* 初始化 NAND 芯片 */
nand_get_device(&my_nand_chip);
/* 创建 mtd_info 结构体 */
mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
if (!mtd) {
pr_err("Failed to allocate memory for mtd_info\n");
return -ENOMEM;
}
/* 填充 mtd_info 结构体 */
mtd->name = "my_nand_flash";
mtd->type = MTD_NANDFLASH;
mtd->size = 256 * 1024 * 1024; // 假设闪存大小为 256MB
mtd->erasesize = 128 * 1024; // 擦除块大小为 128KB
mtd->writesize = 2048; // 页大小为 2048 字节
mtd->read_oob = my_nand_read_oob; // 设置 OOB 读取函数
mtd->write_oob = my_nand_write_oob; // 设置 OOB 写入函数
mtd->erase = my_nand_erase; // 设置擦除函数
mtd->read = my_nand_read; // 设置读函数
mtd->write = my_nand_write; // 设置写函数
/* 注册 MTD 设备 */
ret = add_mtd_device(mtd);
if (ret) {
pr_err("Failed to register MTD device\n");
kfree(mtd);
return ret;
}
pr_info("NAND flash device registered successfully\n");
return 0;
}
static int my_nand_remove(struct platform_device *pdev)
{
/* 注销 MTD 设备 */
del_mtd_device(mtd);
kfree(mtd);
pr_info("NAND flash device removed successfully\n");
return 0;
}
static const struct of_device_id my_nand_of_match[] = {
{ .compatible = "my_nand_chip", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, my_nand_of_match);
static struct platform_driver my_nand_driver = {
.probe = my_nand_probe,
.remove = my_nand_remove,
.driver = {
.name = "my_nand_driver",
.of_match_table = my_nand_of_match,
},
};
module_platform_driver(my_nand_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("MTD NAND Flash Driver");
4.2、编写设备树或硬件描述
闪存设备通常通过 设备树(Device Tree) 或 ACPI(在某些平台上) 来进行描述。设备树用于描述硬件信息和配置,内核可以通过解析设备树来识别和注册闪存设备。
在设备树中,我们需要描述闪存设备的硬件信息,如类型、大小、起始地址等。例如,如果你有一个 NAND Flash 设备,你可能会在设备树中添加如下条目:
nand_flash: nand@0 {
compatible = "my,nand-chip"; // 与驱动程序中的 compatible 字段匹配
reg = <0x0 0x10000000>; // 芯片的起始地址和大小
nand-ecc = "hw"; // 使用硬件 ECC 校验
nand-bus-width = <8>; // 8 位总线宽度
#address-cells = <1>;
#size-cells = <1>;
partitions {
compatible = "linux,mtd-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "boot";
reg = <0x0 0x10000000>; // 分区起始地址和大小
};
partition@1 {
label = "rootfs";
reg = <0x10000000 0x30000000>;
};
};
};
4.3、注册到 MTD 子系统
在驱动程序中,我们通过 add_mtd_device()
函数将闪存设备注册到 MTD 子系统。当 mtd_info
结构体和相关接口函数设置好之后,调用 add_mtd_device()
即可将设备注册到内核,并使其可供其他系统部分使用。
4.4、使用 MTD 子系统的 API
一旦闪存芯片被注册到 MTD 子系统后,您可以通过 MTD 提供的 API 来进行读写操作,例如:
mtd->read()
:从闪存读取数据。mtd->write()
:向闪存写入数据。mtd->erase()
:擦除闪存的某个区域。
例如,读取操作的实现可以如下:
int my_nand_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, uint8_t *buf)
{
// 使用硬件相关的操作读取数据
// 填充 retlen 和 buf
return 0;
}
4.5、加载和卸载模块
在开发完成后,编译并加载驱动程序模块:
insmod my_nand_driver.ko
卸载时:
rmmod my_nand_driver
4.6、总结
将闪存芯片注册到 Linux MTD 子系统的关键步骤包括:
- 编写适当的硬件驱动程序,将设备的读写接口函数注册到
mtd_info
结构体中。 - 配置设备树(如果使用设备树)来描述闪存硬件信息。
- 使用
add_mtd_device()
函数将设备注册到 MTD 子系统中。 - 通过 MTD API 执行闪存的读写操作。