openwrt spi flash 分區適配過程


openwrt spi flash 分區適配過程

這里基於 openwrt mt7620a 平台來跟蹤,主要是想理清 dts 里的分區描述是如何一步步轉化成內核分區行為。

先來看看 dts 中關於分區的描述:

	palmbus@10000000 {
		spi@b00 {
			status = "okay";

			m25p80@0 {
				#address-cells = <1>;
				#size-cells = <1>;
				compatible = "w25q128";
				reg = <0 0>;
				linux,modalias = "m25p80", "w25q128";
				spi-max-frequency = <10000000>;

				partition@0 {
					label = "u-boot";
					reg = <0x0 0x30000>;
					read-only;
				};

				partition@30000 {
					label = "u-boot-env";
					reg = <0x30000 0x10000>;
					read-only;
				};

				factory: partition@40000 {
					label = "factory";
					reg = <0x40000 0x10000>;
					read-only;
				};

				partition@50000 {
					label = "firmware";
					reg = <0x50000 0xfb0000>;
				};
			};
		};

dts 描述的是一個樹狀結構。spi 控制器掛在 platform 總線上,spi flash (w25q128) 掛在 spi 總線上。 探測到 spi flash 的流程如下:

  1. plat_of_setup() 遍歷 palmbus 上的設備,並為每一個動態創建 platform_device,添加到系統總線上 device_add()。對於 spi 這里會創建一個名為 "ralink,rt2880-spi" 的 platfrom_device 並添加到系統中。
  2. drivers/spi/spi-rt2880.c 中會注冊 spi 的 platform_driver,與上一步的 platfrom_device match 上了之后,觸發調用 rt2880_spi_probe() 。
  3. spi_register_master() 向系統注冊 spi 主控制器,並最后調用 of_register_spi_devices(master) 看看 dts 中在 spi 總線上有哪些設備。
  4. 對 dts 中描述的每一個 spi 總線下的設備,為其創建相應的 spi_device,同時根據 dts 中描述的 reg, spi-cpha, spi-cpol, spi-cs-high, spi-3wire, spi-max-frequency 等屬性來配置該 spi 設備。對於這里,創建了一個名為 “m25p80” 的 spi_device。
  5. drivers/mtd/device/m25p80.c 中有名為 “m25p80" 的 spi_driver,於是 match 上了。觸發執行 m25p_probe()。
  6. m25p_probe() 中讀到了這顆 spi flash 的 id 后,確認了一些基本信息(如頁大小、塊大小), 最后調用 mtd_device_parse_register() 開始真正的分區。

分區解析器

part_parser 用來按照某種規則將分區信息解析出來。這些規則可以有很多,內核里調用 register_mtd_parser() 即可注冊一個新的解析器。

drivers/mtd/mtdpart.c 中維護了一個鏈表 part_parsers,解析器按注冊順序添加到這個鏈表里。

parse_mtd_partitions() 中,如果未指定解析器的話,則默認只允許用 cmdlinepart, ofpart 兩種解析器。對於我們這里,實際上起作用的是 ofpart。

static struct mtd_part_parser ofpart_parser = {
	.owner = THIS_MODULE,
	.parse_fn = parse_ofpart_partitions,
	.name = "ofpart",
};

parse_ofpart_partitions() 遍歷 dts 中 spi flash 設備下的分區描述信息,取出其中的 reg, label, name, read-only, lock 等信息以填充一個 struct mtd_partition 結構體。上面 dts 里描述了 4 個分區, 就有一個大小為 4 的 struct mtd_partition 數組,最后由 add_mtd_partitions() 添加為各 mtd 分區。

分區的情況可以待系統啟動后在 /proc/mtd 文件中查看到。

# cat /proc/mtd 
dev:    size   erasesize  name
mtd0: 00030000 00010000 "u-boot"
mtd1: 00010000 00010000 "u-boot-env"
mtd2: 00010000 00010000 "factory"
mtd3: 00fb0000 00010000 "firmware"
mtd4: 00ea9283 00010000 "rootfs"
mtd5: 00b30000 00010000 "rootfs_data"

根文件系統的解析

上面 /proc/mtd 的內容中相比 dts 中的描述多了兩個分區 rootfs, rootfs_data。這兩個分區是何時添加的呢?

看看添加 mtd 分區的函數:

int add_mtd_partitions(struct mtd_info *master,
		       const struct mtd_partition *parts,
		       int nbparts)
{
	struct mtd_part *slave;
	uint64_t cur_offset = 0;
	int i;

	printk(KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name);

	for (i = 0; i < nbparts; i++) {
		slave = allocate_partition(master, parts + i, i, cur_offset);
		if (IS_ERR(slave))
			return PTR_ERR(slave);

		mutex_lock(&mtd_partitions_mutex);
		list_add(&slave->list, &mtd_partitions);
		mutex_unlock(&mtd_partitions_mutex);

		add_mtd_device(&slave->mtd);
		mtd_partition_split(master, slave);

		cur_offset = slave->offset + slave->mtd.size;
	}

	return 0;
}

最后調用了 mtd_partition_split()。

static void mtd_partition_split(struct mtd_info *master, struct mtd_part *part)
{
	static int rootfs_found = 0;

	if (rootfs_found)
		return;

	if (!strcmp(part->mtd.name, "rootfs")) {
		rootfs_found = 1;

		if (config_enabled(CONFIG_MTD_ROOTFS_SPLIT))
			split_rootfs_data(master, part);
	}

	if (!strcmp(part->mtd.name, SPLIT_FIRMWARE_NAME) &&
	    config_enabled(CONFIG_MTD_SPLIT_FIRMWARE))
		split_firmware(master, part);

	arch_split_mtd_part(master, part->mtd.name, part->offset,
			    part->mtd.size);
}

如果:

  1. rootfs 還沒有被找到
  2. 當前分區名是 "firmware"
  3. 內核配置時開啟了 CONFIG_MTD_SPLIT_FIRMWARE

則調用 split_firmware() 來解析。在該函數中做了以下幾件事:

  1. 找 type 為 MTD_PARSER_TYPE_FIRMWARE 的分區解析器來分析。
  2. "uimage-fw" 解析器讀出 firmware 分區的頭部,成功找到一個 uImage。
  3. 躍過 uImage,緊接着成功找到 squashfs 的頭信息,於是找到了格式為 squashfs 的 rootfs。
  4. 解析器在找到一個分區后,會調用 __mtd_add_partition() 將此分區添加到系統中。
  5. __mtd_add_partition() 最后又調用 mtd_partition_split(),因為此時 rootfs 已經找到,所以會調用 split_rootfs_data() 找 rootfs_data 分區。
  6. rootfs 為 squashfs 分區,該格式的文件系統只讀,且頭信息里有標記分區大小。所以很容易就可以找到 rootfs_data 的起始位置。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM