1. 问题现象与初步分析
最近在调试AST2600平台的OpenBMC系统时,遇到了一个让人头疼的问题:系统启动过程中rwfs分区挂载失败,导致启动超时。这个问题看似简单,但实际上涉及到了多个技术层面的排查,包括文件系统、分区配置、驱动逻辑等。让我带你一步步走进这个问题的排查过程,分享我是如何从一堆混乱的日志中定位到根本原因的。
首先来看看系统启动时的错误日志。在启动过程中,系统尝试挂载mtd5分区(即rwfs分区)时,出现了一系列jffs2文件系统的错误提示:
[ 2.819160] jffs2: jffs2_scan_eraseblock(): Magic bitmask 0x1985 not found at 0x00000000: 0x001f instead
[ 2.829883] jffs2: jffs2_scan_eraseblock(): Magic bitmask 0x1985 not found at 0x00000004: 0xf03c instead
[ 2.840547] jffs2: jffs2_scan_eraseblock(): Magic bitmask 0x1985 not found at 0x00000008: 0xf03c instead
这些错误信息表明,jffs2文件系统在扫描擦除块时,无法在预期的位置找到正确的魔数(0x1985)。相反,它读取到的数据是0x001f、0xf03c等。这些数据看起来非常眼熟——它们实际上是u-boot分区开头的内容,而不是rwfs分区应该包含的数据。
这里需要简单解释一下jffs2文件系统的基本原理。jffs2是一种日志结构的文件系统,专门为Flash设备设计。它在挂载时会扫描整个分区,寻找有效的节点(node)和擦除块(eraseblock)。每个擦除块的开头应该有一个魔数,用于标识这是一个有效的jffs2节点。如果魔数不匹配,系统就会报错。
在我的板子上,分区配置如下:
[ 1.499195] Creating 5 MTD partitions on "bmc":
[ 1.504287] 0x000000000000-0x000000140000 : "u-boot"
[ 1.511120] 0x000000140000-0x000000160000 : "u-boot-env"
[ 1.518231] 0x000000160000-0x000000960000 : "kernel"
[ 1.525004] 0x000000960000-0x000002e00000 : "rofs"
[ 1.531470] 0x000002e00000-0x000004000000 : "rwfs"
rwfs分区(mtd5)的起始地址是0x2e00000,结束地址是0x4000000。理论上,jffs2应该从这个地址范围读取数据,但日志显示它却在0x00000000处读取,这明显是u-boot分区的起始地址。
2. 分区配置与镜像检查
遇到这种问题,首先怀疑的往往是分区配置或镜像打包是否正确。毕竟,如果分区配置错误或者镜像文件本身有问题,挂载失败是很常见的。因此,我花了些时间仔细检查了这些方面。
首先,确认分区配置是否正确。在OpenBMC中,分区配置通常通过设备树(device tree)或内核配置来定义。我检查了设备树文件,确认mtd5的分区定义与内核日志中的信息一致:
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "u-boot";
reg = <0x0 0x140000>;
};
partition@140000 {
label = "u-boot-env";
reg = <0x140000 0x20000>;
};
partition@160000 {
label = "kernel";
reg = <0x160000 0x800000>;
};
partition@960000 {
label = "rofs";
reg = <0x960000 0x24a0000>;
};
partition@2e00000 {
label = "rwfs";
reg = <0x2e00000 0x1200000>;
};
};
从配置来看,分区定义没有问题。rwfs分区的起始地址确实是0x2e00000,大小是0x1200000(18MB),这与内核日志中的信息吻合。
接下来,检查镜像文件是否正确生成。在OpenBMC构建过程中,通常会生成一个完整的Flash镜像,包含所有分区。我使用hexdump工具查看了镜像文件的内容:
hexdump -C flash-image.bin | head -n 20
输出显示,rwfs分区区域(从0x2e00000开始)确实全是0xffffffff,这意味着该分区是空的,没有被正确格式化。这是正常的,因为rwfs分区是读写分区,通常在第一次启动时由系统格式化。问题不在于镜像内容,而在于挂载时读取的位置错误。
我还尝试手动挂载rwfs分区,以确认问题是否与自动挂载过程有关:
mount -t jffs2 /dev/mtdblock5 /mnt/rwfs
结果同样失败,错误信息与内核日志一致。这说明问题不是出在挂载脚本或启动过程,而是更底层的读取逻辑。
3. 深入jffs2挂载过程
为了更深入地理解问题,我决定跟踪jffs2的挂载过程。jffs2文件系统的挂载从jffs2_scan_eraseblock()函数开始,这个函数负责扫描每个擦除块,识别有效的节点。
从错误日志可以看出,系统在读取偏移0x0、0x4、0x8等位置时,得到的不是预期的魔数0x1985,而是0x001f、0xf03c等。这些数据实际上是u-boot分区开头的内容。例如,u-boot通常以特定的指令序列开头,0x001f可能是一条ARM指令的操作码。
这说明jffs2在读取数据时,偏移量计算错误。它应该从rwfs分区的起始地址(0x2e00000)开始读取,但实际上却从Flash的绝对地址0x0开始读取。
为什么会这样?问题可能出在MTD(Memory Technology Device)层或SPI驱动层。MTD层负责提供统一的Flash访问接口,而SPI驱动则负责具体的读写操作。
我首先检查了MTD分区的映射是否正确。通过/proc/mtd文件,可以查看每个分区的信息:
cat /proc/mtd
输出显示:
dev: size erasesize name
mtd0: 00140000 00010000 "u-boot"
mtd1: 00020000 00010000 "u-boot-env"
mtd2: 00800000 00010000 "kernel"
mtd3: 024a0000 00010000 "rofs"
mtd4: 01200000 00010000 "rwfs"
这里注意到,/proc/mtd中的分区编号与设备树中的略有不同。设备树中rwfs是mtd5,但在这里是mtd4。这是因为MTD分区的编号从0开始,而设备树中的分区索引是从0开始计算的。但这不是问题的关键,因为挂载操作使用的是设备节点(如/dev/mtdblock4),而不是分区编号。
接下来,我使用mtdinfo工具查看rwfs分区的详细信息:
mtdinfo /dev/mtd4
输出显示该分区的类型为jffs2,大小和擦除块大小都符合预期。这说明MTD层对分区的识别是正确的。
那么问题可能出在SPI驱动层。AST2600的SPI控制器支持XIP(eXecute In Place)模式,这意味着CPU可以直接通过内存映射访问Flash内容,而不需要经过SPI控制器进行数据传输。在XIP模式下,Flash的前256MB被映射到系统的内存空间,CPU可以直接读写。
但是,这种模式下,SPI驱动需要正确处理分区的偏移量。当MTD层请求读取rwfs分区的数据时,它应该将分区偏移(0x2e00000)传递给SPI驱动,驱动再将其转换为实际的内存映射地址。
4. SPI驱动问题定位
基于以上分析,我怀疑问题出在ASPEED SPI驱动对分区偏移的处理上。于是,我决定深入驱动代码,跟踪数据读取的过程。
ASPEED SPI驱动的核心函数是aspeed_spi_read_reg()和aspeed_spi_read_from_ahb(),这些函数负责从Flash读取数据。在XIP模式下,读取操作直接通过内存映射完成,而不需要经过SPI传输。
我添加了一些调试打印,跟踪读取操作的偏移量:
static int aspeed_spi_read_from_ahb(void *buf, const void __iomem *addr, size_t len)
{
pr_info("Reading from AHB address: %p, length: %zu\n", addr, len);
memcpy_fromio(buf, addr, len);
return 0;
}
添加调试代码后重新编译内核,启动系统。观察日志输出,发现当jffs2尝试读取rwfs分区时,驱动确实是从基地址(0x0)开始读取,而不是从分区偏移(0x2e00000)开始。
这说明驱动在处理MTD读取请求时,没有正确加上分区的偏移量。根本原因在于驱动代码中的地址计算错误。
在ASPEED SPI驱动中,每个MTD分区都应该有自己的偏移量,当MTD层发起读取请求时,驱动应该将分区偏移加上请求的偏移量,得到实际的Flash地址。但当前的实现中,驱动忽略了分区偏移,直接使用请求的偏移量作为绝对地址。
5. 驱动修复方案
找到问题根源后,修复就比较直接了。需要修改SPI驱动,在处理读取请求时加上分区的偏移量。
首先,需要获取分区的偏移量。在MTD层,每个分区都有其偏移量信息,存储在mtd_info结构体的priv字段中。对于ASPEED SPI驱动,可以通过aspeed_spi_flash结构体获取分区偏移。
修改驱动代码,在读取函数中加入分区偏移:
static int aspeed_spi_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
struct aspeed_spi_flash *flash = mtd->priv;
struct aspeed_spi *aspi = flash->aspi;
u32 addr = flash->base_addr + from; // 加上分区基地址
// 使用addr进行读取操作
aspeed_spi_read_from_ahb(buf, aspi->ahb_base + addr, len);
*retlen = len;
return 0;
}
这里的关键是flash->base_addr,它存储了分区的起始偏移。在驱动初始化时,需要为每个分区正确设置这个值。
此外,还需要确保其他操作(如擦除、写入)也使用正确的偏移量。例如,擦除操作的修改类似:
static int aspeed_spi_erase(struct mtd_info *mtd, struct erase_info *instr)
{
struct aspeed_spi_flash *flash = mtd->priv;
struct aspeed_spi *aspi = flash->aspi;
u32 addr = flash->base_addr + instr->addr; // 加上分区基地址
// 使用addr进行擦除操作
// ...
}
修改完成后,重新编译内核并刷写到设备。启动系统,观察日志输出,发现jffs2不再报魔数错误,rwfs分区成功挂载。
为了验证修复是否彻底,我进行了一些额外测试:
-
读写测试:在rwfs分区创建文件,写入内容,然后读取验证:
echo "test content" > /run/initramfs/rwfs/test.txt cat /run/initramfs/rwfs/test.txt -
重启测试:多次重启系统,确认rwfs分区每次都能正常挂载,数据持久化正确。
-
压力测试:在rwfs分区进行大量文件操作,验证系统的稳定性。
这些测试都通过了,确认驱动修复成功。
6. 经验总结与预防措施
通过这次问题排查,我深刻体会到嵌入式系统中硬件与软件交互的复杂性。一个小小的驱动bug,可能导致整个系统无法正常启动。总结几点经验,供大家参考:
首先,始终怀疑驱动问题。当遇到类似的文件系统挂载失败时,不要轻易认为是分区配置或镜像问题。驱动层的bug往往更加隐蔽,需要深入代码才能发现。
其次,善用调试工具。在这个案例中,内核的调试打印、mtdinfo工具、hexdump等都非常有用。合理添加调试信息,可以大大缩短问题定位的时间。
第三,理解硬件特性。AST2600的XIP模式是一把双刃剑,它提高了读取性能,但也增加了驱动的复杂性。只有深入理解硬件工作原理,才能写出正确的驱动代码。
为了预防类似问题,我建议在驱动开发中采取以下措施:
-
完善的日志系统:在驱动中添加详细的调试信息,包括地址计算、数据传输等关键步骤。这些信息在问题排查时非常有用。
-
单元测试:为驱动编写单元测试,覆盖各种边界情况,如分区偏移、跨擦除块读写等。
-
代码审查:特别关注地址计算和偏移处理,这是驱动中最容易出错的部分。
-
文档记录:详细记录硬件特性和驱动设计思路,方便后续维护和问题排查。
最后,遇到问题不要慌张,耐心分析日志,一步步缩小问题范围。嵌入式调试往往需要多次尝试和验证,但只要方法得当,总能找到解决方案。
149



被折叠的 条评论
为什么被折叠?



