Linux Mtd子系统3

Linux Mtd子系统3(基于Linux6.6)---MTD层相关接口介绍

在 Linux MTD(Memory Technology Device)层,涉及到设备管理和数据结构关联的接口主要用于实现 MTD 设备与内核中其他部分(如块设备、文件系统等)之间的关联与解绑。以下是一些关键的接口和数据结构,它们在 MTD 子系统中用于管理 MTD 设备的生命周期,尤其是设备的注册、解绑以及和其他内核子系统(如 UBI 和文件系统)之间的关联。

一、mtd设备相关的注册与注销接口

针对mtd设备相关的注册,主要包括如下几个方面:

  1. 提供mtd设备注册的接口,完成mtd info设备注册至设备驱动模型子系统中,并完成mtd字符设备与mtd块设备的创建(从而完成mtd info与设备驱动子系统、vfs子系统的关联);
  2. 若闪存设备支持分区,则提供分区注册接口,主要完成每一个分区对应的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;
}

该接口的处理流程如下图所示,其主要实现如下功能:

  1. 设置mtd_info相关的信息,包括writesize_shift等;另外针对设置了可写信息且上电时锁定芯片的mtd设备,需要执行unlock操作(在某些场景下,我们做flash的设计时,可以让硬件设计人员将wp引脚默认为有效,用于保证系统上电时可能导致的写异常。然后在驱动中再将wp引脚关闭即可,而mtd子系统支持该功能,只需要实现mtd_info中_unlock成员函数指针即可);
  2. 设置该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

该接口主要针对需要对注册设备进行分区的情况,该接口主要实现两个功能:

  1. 针对每一个分区,均创建一个mtd_part类型的变量,该变量包括master mtd_info以及该分区对应的mtd_info,并初始化该分区对应slave mtd_info,而针对slave mtd_info,其上层接口主要为分区相关的接口,该接口主要会调用master mtd_info对应的接口。针对master mtd_info、slave mtd_info的关联,在下面分析。
  2. 针对每一个分区,将其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 分区通常有两种类型
    1. 固定大小的分区:每个分区有固定的大小(例如,16MB、32MB等),在闪存设备初始化时设置。
    2. 按需求创建的分区:根据设备树(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
        };
    };
};

在这个例子中,设备树定义了一个大闪存设备,并为其创建了三个分区:bootrootfsdata,每个分区都有一个标签和大小。

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 分区大小为 128KB
  • env 分区大小为 256KB
  • kernel 分区大小为 1MB
  • rootfs 占用剩余的所有空间

U-Boot 中加载和保存配置

U-Boot 在启动时会加载这些分区信息,并根据这些信息来启动操作系统或执行其他操作(如烧写固件)。U-Boot 的 mtdparts 设置也可以与设备树中的分区表进行配合,以确保内核能够正确识别和使用这些分区。

3.3、使用 ubifsjffs2 文件系统

针对闪存设备的逻辑分区设置通常还会涉及文件系统的选择。对于 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 设备和分区,例如 nandwritenanddump 等。

四、如何实现将一个闪存芯片注册至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 子系统的关键步骤包括:

  1. 编写适当的硬件驱动程序,将设备的读写接口函数注册到 mtd_info 结构体中。
  2. 配置设备树(如果使用设备树)来描述闪存硬件信息。
  3. 使用 add_mtd_device() 函数将设备注册到 MTD 子系统中。
  4. 通过 MTD API 执行闪存的读写操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值