diff --git a/articles/20240922-qemu-drop-ignore_memory_transaction_failures.md b/articles/20240922-qemu-drop-ignore_memory_transaction_failures.md
new file mode 100644
index 0000000000000000000000000000000000000000..6a0c34dc7abd92ee28b219558d7d102e280843fb
--- /dev/null
+++ b/articles/20240922-qemu-drop-ignore_memory_transaction_failures.md
@@ -0,0 +1,426 @@
+> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2 - [spaces]
+> Author: 刘超
+> Date: 2024/09/22
+> Revisor: Bin Meng, falcon
+> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux)
+> Sponsor: PLCT Lab, ISCAS
+
+# 废弃 QEMU xilinx_zynq 板卡的 ignore_memory_transaction_failures
+
+## 前言
+
+早期某些嵌入式系统或者硬件平台,对内存管理不够严苛,当 CPU 访问一个未分配或未映射的内存地址时:
+
+* 进行读操作,则返回零值,这个行为被称为 Read Address Zero(RAZ);
+
+* 进行写操作,则忽略,这个行为被称为 Write Ignore(WI)。
+
+在实际应用中,RAZ/WI 行为通常用于:
+
+* 调试目的:在开发过程中,访问未映射地址,可能会被忽略或返回零值,以避免系统崩溃;
+
+* 硬件兼容性:在某些硬件平台上,这种行为被视为一种标准,以确保软件在不同硬件配置下的兼容性。
+
+在 QEMU 的使用场景中,这种行为通常是为了兼容某些硬件平台的传统行为。在这些平台上,访问未映射的内存区域,可能会被简单地忽略或返回零值,而不是抛出一个错误或异常。
+
+## ignore_memory_transaction_failures
+
+随着QEMU 的发展,更现代的方法是使用 `unimplemented-device` 来模拟那些 QEMU 中尚未实现的硬件设备。这种方法更符合现代操作系统的期望,即当访问未实现的设备时,应该明确地报告错误或异常,而不是简单地返回零或忽略写入。
+
+另外通过 `unimplemented-device` 创建的设备,可以记录所有客户机 CPU 对该设备的访问,并通过 QEMU 的调试日志记录下来。这有助于调试和验证设备模型的行为。
+
+然而,在一些遗留的板卡模型中,可能仍然依赖于 RAZ/WI 行为来处理那些 QEMU 尚未建模的设备。
+
+为了兼容传统的板卡模型(通常是 ARM boards),QEMU 在 v2.10.0-291-ged860129ac 中,为 `MachineClass` 增加了一个新的 `ignore_memory_transaction_failures` 字段,类型为 `bool`。
+
+如果将该字段设置为 `true`,则 vCPU 将忽略那些因访问未分配的物理地址而导致的内存事务失败,通常这些失败会引发异常。
+
+此标志应仅用于 QEMU 中依赖旧的 RAZ/WI 行为的传统板卡访问尚未建模的设备。新板卡模型中未实现的设备应使用 `unimplemented-device`。
+
+### 分析代码实现
+
+这里我们以 qemu v9.0.2 源代码为例,进行分析。
+
+CPU 模型在 realize 阶段,通过 `cpu_common_realizefn` 函数,从`MachineClass` 中获取`ignore_memory_transaction_failures` 的值。
+
+然后更新 `cpu->ignore_memory_transaction_failures`,代码如下:
+
+```c
+// hw/core/cpu-common.c:195
+static void cpu_common_realizefn(DeviceState *dev, Error **errp)
+{
+ CPUState *cpu = CPU(dev);
+ Object *machine = qdev_get_machine();
+
+ /* qdev_get_machine() can return something that's not TYPE_MACHINE
+ * if this is one of the user-only emulators; in that case there's
+ * no need to check the ignore_memory_transaction_failures board flag.
+ */
+ if (object_dynamic_cast(machine, TYPE_MACHINE)) {
+ MachineClass *mc = MACHINE_GET_CLASS(machine);
+ if (mc) {
+ cpu->ignore_memory_transaction_failures =
+ mc->ignore_memory_transaction_failures;
+ }
+ }
+
+ ...
+}
+```
+
+对于全系统仿真,CPU 所有的访存行为,都要经过 SoftMMU。以 TCG 为例:
+
+1. CPU 首先会查询 soft-tlb,如果 TLB Miss,则进入慢速路径访存,调用 helper 函数,示意流程如下;
+
+```
+ tb binary code
+ ---+---
+find tlb ---> |
+ bne -----------------------------+
+direct ld ---> | |
+ -+- | TLB Miss
+ | |
+TB end ---> -+- |
+ | <--- ldst slow path code <---+
+ ...
+```
+
+2. 以读操作为例,helper 函数最终会调用到 `int_ld_mmio_beN` 函数,代码如下:
+
+```c
+// accel/tcg/cputlb.c:1268
+static void io_failed(CPUState *cpu, CPUTLBEntryFull *full, vaddr addr,
+ unsigned size, MMUAccessType access_type, int mmu_idx,
+ MemTxResult response, uintptr_t retaddr)
+{
+ if (!cpu->ignore_memory_transaction_failures // 忽略访存异常
+ && cpu->cc->tcg_ops->do_transaction_failed) {
+ hwaddr physaddr = full->phys_addr | (addr & ~TARGET_PAGE_MASK);
+
+ cpu->cc->tcg_ops->do_transaction_failed(cpu, physaddr, addr, size,
+ access_type, mmu_idx,
+ full->attrs, response, retaddr);
+ }
+}
+
+// accel/tcg/cputlb.c:1928
+static uint64_t int_ld_mmio_beN(CPUState *cpu, CPUTLBEntryFull *full,
+ uint64_t ret_be, vaddr addr, int size,
+ int mmu_idx, MMUAccessType type, uintptr_t ra,
+ MemoryRegion *mr, hwaddr mr_offset)
+{
+ MemTxResult r;
+
+ ...
+
+ r = memory_region_dispatch_read(mr, mr_offset, &val,
+ this_mop, full->attrs);
+ if (unlikely(r != MEMTX_OK)) {
+ io_failed(cpu, full, addr, this_size, type, mmu_idx, r, ra);
+ }
+
+ ...
+
+ return ret_be;
+}
+```
+
+如果在 `memory_region_dispatch_read` 函数中,返回了 `MEMTX_OK`,则表示访问成功,否则,会调用 `io_failed` 函数。
+
+在 `io_failed` 函数中会根据 `ignore_memory_transaction_failures` 的值,判断是否需要抛出异常,如果值为 true,则按照 RAZ/WI 行为处理。
+
+## 移除 Xilinx Zynq 的 ignore_memory_transaction_failures
+
+由于笔者环境中正好有一个 Xilinx Zynq 的 linux kernel 二进制镜像,所以决定尝试移除 Xilinx Zynq 板卡中的 `ignore_memory_transaction_failures`。
+
+首先在 `zynq_machine_class_init` 函数中去除这个字段的初始化代码:
+
+```c
+// hw/arm/xilinx_zynq.c
+@@ -394,7 +437,6 @@ static void zynq_machine_class_init(ObjectClass *oc, void *data)
+ mc->init = zynq_init;
+ mc->max_cpus = ZYNQ_MAX_CPUS;
+ mc->no_sdcard = 1;
+- mc->ignore_memory_transaction_failures = true;
+ mc->valid_cpu_types = valid_cpu_types;
+ mc->default_ram_id = "zynq.ext_ram";
+ prop = object_class_property_add_str(oc, "boot-mode", NULL,
+--
+```
+
+然后重新编译(细节不在这里赘述),并运行QEMU :
+
+```bash
+$ ./qemu/build/qemu-system-arm -M xilinx-zynq-a9 \
+-serial /dev/null \
+-serial mon:stdio \
+-display none \
+-kernel QEMU_CPUFreq_Zynq/Prebuilt_functional/kernel_standard_linux/uImage \
+-dtb QEMU_CPUFreq_Zynq/Prebuilt_functional/my_devicetree.dtb \
+--initrd QEMU_CPUFreq_Zynq/Prebuilt_functional/umy_ramdisk.image.gz
+```
+
+> PS: 获取配套测试的 linux kernel 镜像:`git clone https://github.com/zevorn/QEMU_CPUFreq_Zynq.git`
+
+运行现象是终端没有任何输出,此时我们修改 QEMU 启动命令,尾部加入 `-s -S` 使能 gdb 远程调试功能,然后另起一个终端,进行调试:
+
+```bash
+$ gdb-multiarch QEMU_CPUFreq_Zynq/Prebuilt_functional/kernel_standard_linux/uImage
+(gdb) target remote localhost:1234
+Remote debugging using localhost:1234
+...
+determining executable automatically. Try using the "file" command.
+0x00000000 in ?? ()
+(gdb) c
+Continuing.
+# 这里多等待一会儿,支持 GDB 以后,QEMU 性能较慢
+# 这里键入 Ctrl + C,暂停客户机程序执行
+Program received signal SIGINT, Interrupt.
+0xc0770240 in ?? ()
+(gdb) display /i $pc
+1: x/i $pc
+=> 0xc0770240: subs r0, r0, #1
+(gdb) si
+0xc0770244 in ?? ()
+1: x/i $pc
+=> 0xc0770244: bhi 0xc0770240
+(gdb) si
+0xc0770240 in ?? ()
+1: x/i $pc
+=> 0xc0770240: subs r0, r0, #1
+(gdb)
+0xc0770244 in ?? ()
+1: x/i $pc
+=> 0xc0770244: bhi 0xc0770240
+```
+
+发现此时已经陷入死循环,这里大概率是触发了访存异常,陷入了异常处理函数的死循环当中。我们接着调试,看看是在什么地方触发的访存异常。
+
+```bash
+(gdb) bt
+#0 0xc0770244 in ?? ()
+#1 0xc011cb10 in ?? ()
+Backtrace stopped: previous frame identical to this frame (corrupt stack?)
+(gdb) x /i 0xc011cb10
+ 0xc011cb10: b 0xc011cafc
+(gdb)
+```
+
+简单使用 `bt` 看一下调用栈,没有发现有效信息,我们转变 debug 策略,通过调试 QEMU 源代码来分析客户机访存异常的地址。
+
+上文提到访存异常是在 `io_failed` 函数中设置的,那么我们在这函数中增加打印,将客户机访存的 GVA 和 GPA 分别打印出来:
+
+```c
+static void io_failed(CPUState *cpu, CPUTLBEntryFull *full, vaddr addr,
+ unsigned size, MMUAccessType access_type, int mmu_idx,
+ MemTxResult response, uintptr_t retaddr)
+{
+ if (!cpu->ignore_memory_transaction_failures
+ && cpu->cc->tcg_ops->do_transaction_failed) {
+ hwaddr physaddr = full->phys_addr | (addr & ~TARGET_PAGE_MASK);
+
+ // 增加调试打印信息,输出 GVA、GPA 和 access_type
+ printf("vaddr %lx phyaddr %lx access_type %d\n", addr, physaddr, access_type);
+
+ cpu->cc->tcg_ops->do_transaction_failed(cpu, physaddr, addr, size,
+ access_type, mmu_idx,
+ full->attrs, response, retaddr);
+ }
+}
+```
+
+重新编译运行 QEMU ,输出如下:
+
+```bash
+vaddr c884f080 phyaddr f8007080 access_type 0
+```
+
+访存的客户机物理地址是 0xf8007080,access_type 为 0 ,代表是一个读操作。
+
+查阅 Xilinx Zynq 的设备树,我们知道,0xf8007080 对应的是 devcfg 设备,这个设备在 QEMU 中被模拟为 `xlnx,zynq-devcfg` 类型,其寄存器地址空间为 0xf8007000~0xf8007fff,所以,这个访存异常应该来自 QEMU 模拟的 devcfg 设备。
+
+```c
+devcfg: devcfg@f8007000 {
+ compatible = "xlnx,zynq-devcfg-1.0";
+ interrupt-parent = <&intc>;
+ interrupts = <0 8 4>;
+ reg = <0xf8007000 0x100>;
+ clocks = <&clkc 12>, <&clkc 15>, <&clkc 16>, <&clkc 17>, <&clkc 18>;
+ clock-names = "ref_clk", "fclk0", "fclk1", "fclk2", "fclk3";
+ syscon = <&slcr>;
+};
+```
+
+但是这个设备被 QEMU 模拟了,按道理不应该产生访存异常,除非存在“地址空洞”,即在 devcfg 设备的地址空间范围内,某些地址段没有被实现。
+
+为了验证我们的猜想,先尝试在 Xilinx Zynq 板卡初始化阶段,为 devcfg 添加 `unimplemented-device`,代码如下:
+
+```c
+// hw/arm/xilinx_zynq.c:34
+#include "hw/net/cadence_gem.h"
+#include "hw/cpu/a9mpcore.h"
+#include "hw/qdev-clock.h"
+#include "hw/misc/unimp.h" // 添加头文件
+
+// hw/arm/xilinx_zynq.c:203
+static void zynq_init(MachineState *machine)
+{
+ /* Other */
+ create_unimplemented_device("amba.devcfg", 0xf8007000, 0x100);
+ ...
+}
+```
+
+重新编译 QEMU ,并运行,此时 QEMU 输出:
+
+```bash
+[ 0.000000] Booting Linux on physical CPU 0x0
+[ 0.000000] Linux version 5.6.0-axiom+ (kromes@mcsoc2-Latitude-7480) (gcc version 8.2.1 20180802 (GNU Toolchain for the A-profile Architecture 8.2-2018.11 (arm-rel-8.26))) #10 SMP PREEMPT Fri Jul 3 08:42:52 CEST 2020
+[ 0.000000] CPU: ARMv7 Processor [413fc090] revision 0 (ARMv7), cr=10c5387d
+[ 0.000000] CPU: PIPT / VIPT nonaliasing data cache, VIPT nonaliasing instruction cache
+[ 0.000000] OF: fdt: Machine model: xlnx,zynq-zed
+[ 0.000000] Memory policy: Data cache writeback
+[ 0.000000] cma: Failed to reserve 64 MiB
+[ 0.000000] CPU: All CPU(s) started in SVC mode.
+[ 0.000000] percpu: Embedded 15 pages/cpu s31948 r8192 d21300 u61440
+[ 0.000000] Built 1 zonelists, mobility grouping on. Total pages: 32512
+[ 0.000000] Kernel command line: console=ttyPS0, 115200 root=/dev/ram rw
+[ 0.000000] Dentry cache hash table entries: 16384 (order: 4, 65536 bytes, l
+...
+```
+
+发现可以正常输出了。
+
+但是到这里还没结束,由于我们测试集不够充分,那么 Xilinx Zynq 板卡很可能存在其他没有实现的设备,这里我们开启 QEMU 的命令行界面,查看一下 Xilinx Zynq 板卡已经实现的设备及其地址空间:
+
+```bash
+$ ./qemu/build/qemu-system-arm -M xilinx-zynq-a9 -display none -monitor stdio
+QEMU 9.1.50 monitor - type 'help' for more information
+(qemu) info mtree
+address-space: cpu-memory-0
+address-space: cpu-secure-memory-0
+address-space: dma
+address-space: dma
+address-space: memory
+ 0000000000000000-ffffffffffffffff (prio 0, i/o): system
+ 0000000000000000-0000000007ffffff (prio 0, ram): zynq.ext_ram
+ 00000000e0000000-00000000e0000fff (prio 0, i/o): uart
+ 00000000e0001000-00000000e0001fff (prio 0, i/o): uart
+ 00000000e0002000-00000000e0002fff (prio 0, i/o): ehci
+ 00000000e0002000-00000000e00020ff (prio 0, i/o): usb-chipidea.misc
+ 00000000e0002100-00000000e000210f (prio 0, i/o): capabilities
+ ...
+
+address-space: I/O
+ 0000000000000000-000000000000ffff (prio 0, i/o): io
+
+(qemu) vaddr 8000000 phyaddr 8000000 a
+```
+
+然后再对比 Xilinx Zynq 的设备树,发现确实有很多设备没有实现,由于数量较多,这里直接给出代码实现,不再一一列举:
+
+```c
+// hw/arm/xilinx_zynq.c:203
+static void zynq_init(MachineState *machine)
+{
+ ...
+
+ /* DDR remapped to address zero. */
+ memory_region_add_subregion(address_space_mem, 0, machine->ram);
+
+ /* PMU */
+ create_unimplemented_device("pmu.region0", 0xf8891000, 0x1000);
+ create_unimplemented_device("pmu.region1", 0xf8893000, 0x1000);
+
+ /* CAN */
+ create_unimplemented_device("amba.can0", 0xe0008000, 0x1000);
+ create_unimplemented_device("amba.can1", 0xe0009000, 0x1000);
+
+ /* GPIO */
+ create_unimplemented_device("amba.gpio0", 0xe000a000, 0x1000);
+
+ /* I2C */
+ create_unimplemented_device("amba.i2c0", 0xe0004000, 0x1000);
+ create_unimplemented_device("amba.i2c1", 0xe0005000, 0x1000);
+
+ /* Interrupt-Controller */
+ create_unimplemented_device("amba.intc.region0", 0xf8f00100, 0x100);
+ create_unimplemented_device("amba.intc.region1", 0xf8f01000, 0x1000);
+
+ /* Memory-Controller */
+ create_unimplemented_device("amba.mc", 0xf8006000, 0x1000);
+
+ /* SMCC */
+ create_unimplemented_device("amba.smcc", 0xe000e000, 0x1000);
+ create_unimplemented_device("amba.smcc.nand0", 0xe1000000, 0x1000000);
+
+ /* Timer */
+ create_unimplemented_device("amba.global_timer", 0xf8f00200, 0x20);
+ create_unimplemented_device("amba.scutimer", 0xf8f00600, 0x20);
+
+ /* WatchDog */
+ create_unimplemented_device("amba.watchdog0", 0xf8005000, 0x1000);
+
+ /* Other */
+ create_unimplemented_device("amba.devcfg", 0xf8007000, 0x100);
+ create_unimplemented_device("amba.efuse", 0xf800d000, 0x20);
+ create_unimplemented_device("amba.etb", 0xf8801000, 0x1000);
+ create_unimplemented_device("amba.tpiu", 0xf8803000, 0x1000);
+ create_unimplemented_device("amba.funnel", 0xf8804000, 0x1000);
+ create_unimplemented_device("amba.ptm.region0", 0xf889c000, 0x1000);
+ create_unimplemented_device("amba.ptm.region1", 0xf889d000, 0x1000);
+
+ ...
+}
+```
+
+另外在打印 `info mtree` 时,发现 devcfg 的地址范围不对:
+
+```bash
+(qemu) info mtree
+ ...
+ 00000000f8007000-00000000f800703f (prio 0, i/o): xlnx.ps7-dev-cfg
+ ...
+```
+
+按照上文给出的设备树配置,devcfg 的地址范围应当是 `[0xf8007000-f8007100)`,打开对应代码,定位问题:
+
+```c
+// include/hw/dma/xlnx-zynq-devcfg.h
+#define XLNX_ZYNQ_DEVCFG_R_MAX (0x100 / 4)
+
+...
+
+// hw/dma/xlnx-zynq-devcfg.c:360
+static void xlnx_zynq_devcfg_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ XlnxZynqDevcfg *s = XLNX_ZYNQ_DEVCFG(obj);
+ RegisterInfoArray *reg_array;
+
+ sysbus_init_irq(sbd, &s->irq);
+
+ memory_region_init(&s->iomem, obj, "devcfg", XLNX_ZYNQ_DEVCFG_R_MAX * 4);
+ reg_array =
+ register_init_block32(DEVICE(obj), xlnx_zynq_devcfg_regs_info,
+ ARRAY_SIZE(xlnx_zynq_devcfg_regs_info),
+ s->regs_info, s->regs,
+ &xlnx_zynq_devcfg_reg_ops,
+ XLNX_ZYNQ_DEVCFG_ERR_DEBUG,
+ XLNX_ZYNQ_DEVCFG_R_MAX); // 这里没有 x 4
+}
+```
+
+我们修改 `register_init_block32` 函数最后一个参数为 `XLNX_ZYNQ_DEVCFG_R_MAX * 4` 即可。
+
+## 总结
+
+本文总结了 `ignore_memory_transaction_failures` 代码实现,以及如何废弃某些传统板卡中这个属性,替换成 `unimplemented_device`。
+
+## 参考资料
+
+-[qemu tcg 访存指令模拟][1]
+-[boards.h: Define new flag ignore_memory_transaction_failures][2]
+
+[1]: https://wangzhou.github.io/qemu-tcg%E8%AE%BF%E5%AD%98%E6%8C%87%E4%BB%A4%E6%A8%A1%E6%8B%9F/
+[2]: https://lists.nongnu.org/archive/html/qemu-devel/2017-09/msg01643.html
\ No newline at end of file