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 的流程如下:
- plat_of_setup() 遍歷 palmbus 上的設備,並為每一個動態創建 platform_device,添加到系統總線上 device_add()。對於 spi 這里會創建一個名為 "ralink,rt2880-spi" 的 platfrom_device 並添加到系統中。
- drivers/spi/spi-rt2880.c 中會注冊 spi 的 platform_driver,與上一步的 platfrom_device match 上了之后,觸發調用 rt2880_spi_probe() 。
- spi_register_master() 向系統注冊 spi 主控制器,並最后調用 of_register_spi_devices(master) 看看 dts 中在 spi 總線上有哪些設備。
- 對 dts 中描述的每一個 spi 總線下的設備,為其創建相應的 spi_device,同時根據 dts 中描述的 reg, spi-cpha, spi-cpol, spi-cs-high, spi-3wire, spi-max-frequency 等屬性來配置該 spi 設備。對於這里,創建了一個名為 “m25p80” 的 spi_device。
- drivers/mtd/device/m25p80.c 中有名為 “m25p80" 的 spi_driver,於是 match 上了。觸發執行 m25p_probe()。
- 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);
}
如果:
- rootfs 還沒有被找到
- 當前分區名是 "firmware"
- 內核配置時開啟了 CONFIG_MTD_SPLIT_FIRMWARE
則調用 split_firmware() 來解析。在該函數中做了以下幾件事:
- 找 type 為 MTD_PARSER_TYPE_FIRMWARE 的分區解析器來分析。
- "uimage-fw" 解析器讀出 firmware 分區的頭部,成功找到一個 uImage。
- 躍過 uImage,緊接着成功找到 squashfs 的頭信息,於是找到了格式為 squashfs 的 rootfs。
- 解析器在找到一個分區后,會調用 __mtd_add_partition() 將此分區添加到系統中。
- __mtd_add_partition() 最后又調用 mtd_partition_split(),因為此時 rootfs 已經找到,所以會調用 split_rootfs_data() 找 rootfs_data 分區。
- rootfs 為 squashfs 分區,該格式的文件系統只讀,且頭信息里有標記分區大小。所以很容易就可以找到 rootfs_data 的起始位置。