diff --git a/articles/20230917-licheepi4a-rt-test.md b/articles/20230917-licheepi4a-rt-test.md
new file mode 100755
index 0000000000000000000000000000000000000000..6bfa8f3b109becebb4aa6b03e567c04baef244af
--- /dev/null
+++ b/articles/20230917-licheepi4a-rt-test.md
@@ -0,0 +1,202 @@
+> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2 - [tounix spaces header tables]
+> Author: 王杰迅
+> Date: 2023/09/17
+> Revisor: falcon
+> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux)
+> Sponsor: PLCT Lab, ISCAS
+
+# LicheePi 4A 实时性测试实践
+
+## 前言
+
+在 [之前的文章][1] 中介绍了 LicheePi 4A 开发板构建并运行 Linux v6.5-rc1 的过程,当时实现的效果为:内核可以成功启动并进入 initramfs 命令行界面。
+
+在本文中,将介绍为 LicheePi 4A 开发板添加 eMMC 支持的过程,使得内核可以从 eMMC 上的 rootfs 启动,同时将内核版本更新到 v6.5,打入对应的 PREEMPT_RT 补丁。
+
+除此之外,本文还将介绍使用 cyclictest 和 ftrace 进行延迟追踪的小技巧。
+
+## 软件版本
+
+| Software | Version |
+|-----------|---------------|
+| Linux | 6.5 |
+| U-Boot | 2020.01 |
+| OpenSBI | thead-opensbi |
+| Buildroot | 2023.02.2 |
+
+## 添加 eMMC 支持
+
+LicheePi 4A 开发板提供了 eMMC 作为外部存储,eMMC 是 Flash 的一种,在嵌入式系统中使用广泛。
+
+官方 Linux 内核从 v6.5 版本开始添加了对 LicheePi 4A 开发板的设备树支持,具体见 `arch/riscv/boot/dts/thead` 文件夹下的各文件。但其中的设备树目前不够完善,主要的外设只实现了串口,没有办法使用外部存储。因此想要使用 LicheePi 4A 的 eMMC,必须借助社区中其他开发者提供的补丁。
+
+[Drew Fustini 的补丁][2] 为开发板 Beaglev-Ahead 添加了 eMMC 支持。由于 Beaglev-Ahead 和 LicheePi 4A 都是使用 TH1520 作为主控芯片,因此该补丁也可适用于 LicheePi 4A。
+
+该补丁添加了对 eMMC 的设备树支持,并修改了驱动文件 `drivers/mmc/host/sdhci-of-dwcmshc.c`,在开启内核配置项 `CONFIG_MMC_SDHCI_OF_DWCMSHC=y` 之后,可以实现对 eMMC 外设的基本读写。但目前该驱动还没有支持 DMA,效率较低,仍在迭代开发中。
+
+将 eMMC 驱动和设备树替换完毕后,仍出现了多个小问题,最终通过选择合适的 OpenSBI,才实现了稳定地使用 eMMC 进行读写。
+
+### load access fault
+
+最初时,由于使用的 OpenSBI 版本过低,没有支持设备树中的 "thead,c900-clint",在进行初始化时会出现如下报错:
+
+```
+[ 0.000000] Oops - load access fault [#1]
+...
+[ 0.000000] epc : __plic_toggle+0x6a/0x72
+[ 0.000000] ra : __plic_init.constprop.0+0x31e/0x4ac
+```
+
+更换为较新版本的 OpenSBI(v1.2 及以上)后,报错消失,可以正常启动。
+
+### illegal instruction
+
+当尝试给 eMMC 施加压力测试时,如使用如下命令:
+
+```
+$ while true; do /bin/dd if=/dev/zero of=bigfile bs=1024000 count=1024; done &
+```
+
+会出现如下报错导致系统崩溃:
+
+```
+sbi_trap_error: hart1: illegal instruction handler failed (error -2)
+```
+
+经 Jisheng Zhang 老师提醒,使用 [最新的 thead-opensbi][3] 后,eMMC 在压力测试下也可以稳定使用,不再报错。猜测该报错产生的原因为:TH1520 芯片的部分指令没有被官方 OpenSBI 实现。
+
+总之,目前应使用 [最新的 thead-opensbi][3],以保证系统正常运行。
+
+### rootfs
+
+当 eMMC 可以正常使用后,就可以不再使用 initramfs。将 rootfs 烧写到 eMMC 后,系统就可以从 eMMC 的 rootfs 启动。使用 Buildroot 的 menuconfig,选择构建的 rootfs 文件系统类型为 ext4:
+
+```
+Filesystem images --->
+ [*] ext2/3/4 root filesystem
+ ext2/3/4 variant (ext4) --->
+ (rootfs) filesystem label
+ (60M) exact size
+```
+
+LicheePi 4A 提供的烧录工具最大可以将 4GB 的 rootfs 烧录到 eMMC 中,但实际的 rootfs 可能并没有那么大,在制作 rootfs 时可以选择较小的大小,如 60MB。在系统启动后,可以使用 resize2fs 命令对 rootfs 进行扩容:
+
+```
+$ resize2fs /dev/mmcblk0p3
+```
+
+使用该命令会自动将 rootfs 扩容,将 eMMC 的未使用存储空间全部并入其中。
+
+## 编译 PREEMPT_RT v6.5 内核
+
+为了提高系统实时性,首先打入和 Linux 内核版本对应的 [官方 PREEMPT_RT 补丁][4]。同时,由于 LicheePi 4A 开发板基于 RISC-V 架构,因此还需要打入 [针对 RISC-V 架构的 PREEMPT_RT 补丁][5]。
+
+### 解决 redefinition 错误
+
+在将 Linux 内核更新到 v6.5,打入 PREEMPT_RT 补丁并配置抢占模式为 PREEMPT_RT 后,尝试编译时出现报错:
+
+```
+arch/riscv/kernel/irq.c:64:6: error: redefinition of 'do_softirq_own_stack'
+ 64 | void do_softirq_own_stack(void)
+ | ^~~~~~~~~~~~~~~~~~~~
+In file included from ./arch/riscv/include/generated/asm/softirq_stack.h:1,
+ from arch/riscv/kernel/irq.c:15:
+./include/asm-generic/softirq_stack.h:8:20: note: previous definition of 'do_softirq_own_stack' was here
+ 8 | static inline void do_softirq_own_stack(void)
+ | ^~~~~~~~~~~~~~~~~~~~
+```
+
+经过查看后发现,在 `arch/riscv/kernel/irq.c` 和 `include/asm-generic/softirq_stack.h` 文件中出现了对 `do_softirq_own_stack` 函数的重复定义。
+
+该错误与以下两个配置项有关:
+
+```
+CONFIG_HAVE_SOFTIRQ_ON_OWN_STACK
+CONFIG_SOFTIRQ_ON_OWN_STACK
+```
+
+正常的逻辑是,如果内核配置 `CONFIG_SOFTIRQ_ON_OWN_STACK=y`,则该函数由 `include/asm-generic/softirq_stack.h` 文件负责实现。
+
+如果内核配置 `CONFIG_SOFTIRQ_ON_OWN_STACK=n`,则该函数在 `include/asm-generic/softirq_stack.h` 文件中只负责声明,而由特定架构负责具体实现,如 RISC-V 架构就在 `arch/riscv/kernel/irq.c` 文件中实现。
+
+但在 `arch/riscv/kernel/irq.c` 文件中,错把实现 `do_softirq_own_stack` 函数的条件 `SOFTIRQ_ON_OWN_STACK` 写成了 `HAVE_SOFTIRQ_ON_OWN_STACK` 选项,当内核配置项如下时:
+
+```
+CONFIG_HAVE_SOFTIRQ_ON_OWN_STACK=y
+CONFIG_SOFTIRQ_ON_OWN_STACK=n
+```
+
+两个文件都会实现 `do_softirq_own_stack` 函数,出现 redefinition 错误。
+
+值得一提的是,当没有开启 PREEMPT_RT 时,开启 `HAVE_SOFTIRQ_ON_OWN_STACK` 时默认会开启 `SOFTIRQ_ON_OWN_STACK`,因此不会暴露出错误,只有当开启 PREEMPT_RT 时,才会导致编译失败。
+
+目前,该补丁已发送至 [邮件列表][6]。
+
+## 延迟追踪技巧
+
+在优化系统实时性能时,首先需要分析产生最大延迟的原因。常用的延迟测试及追踪工具为 cyclictest 和 ftrace。这两个软件的基本用法,可以参考泰晓科技的往期直播分享 [RISC-V 实时抢占优化实践][7] 及 [对应 PPT][8]。
+
+实际测试时,容易遇到的一个问题是:测试得到的最大延迟并不是 cyclictest 产生的,这对优化实时性能没有任何帮助。例如:
+
+```
+# wakeup_rt latency trace v1.1.5 on 6.5.0-rt6-r1208-00003-g999d221864bf-dirty
+# --------------------------------------------------------------------
+# latency: 12086 us, #6/6, CPU#0 | (M:preempt_rt VP:0, KP:0, SP:0 HP:0 #P:4)
+# -----------------
+# | task: irq/12-ttyS0-74 (uid:0 nice:0 policy:1 rt_prio:50)
+# -----------------
+……
+```
+
+而实际希望得到的最大延迟和调用栈应该是由 cyclictest 产生的,例如:
+
+```
+# wakeup_rt latency trace v1.1.5 on 6.5.0-rt6-r1208-00003-g999d221864bf-dirty
+# --------------------------------------------------------------------
+# latency: 879 us, #6/6, CPU#2 | (M:preempt_rt VP:0, KP:0, SP:0 HP:0 #P:4)
+# -----------------
+# | task: cyclictest-212 (uid:0 nice:0 policy:1 rt_prio:99)
+# -----------------
+……
+```
+
+如果进行了较长时间的测试之后,查看 ftrace 的结果发现没有任何帮助,难免会令人大失所望。因此需要使用一些技巧保证最大延迟和调用栈是由 cyclictest 产生的。
+
+cyclictest 提供了参数 `--breaktrace=` 和 `--tracemark` 以更好地锁定最大延迟的产生原因。`--breaktrace` 表示,如果测试时 cyclictest 的最大延迟超过给定的数值,就中断测试。`--tracemark` 表示保存产生最大延迟的函数调用栈。在 cyclictest 超过指定延迟后立即中断测试并记录,基本可以保证最大延迟是由 cyclictest 产生的。
+
+在使用 `--tracemark` 参数前首先要保证 ftrace 挂载好,并在 current_tracer 文件中选择合适的 tracer。测试前无需手动将 tracing_on 文件置 1,cyclictest 在测试时会自动将 tracing_on 文件置 1,测试结束会自动置 0。测试因超过设定数值而中断后,不会自动打印调用栈,需要自己手动打印 ftrace 中的 trace 文件查看。
+
+下面是一个简单的示例脚本,在 cyclictest 产生的最大延迟大于 100μs 时,会自动中断测试,并打印产生的最大延迟和函数调用栈:
+
+```sh
+#!/bin/sh
+mount -t tracefs none /sys/kernel/tracing
+cd /sys/kernel/tracing
+echo wakeup_rt > current_tracer
+cyclictest --breaktrace=100 --tracemark
+cat trace
+```
+
+## 总结
+
+本文介绍了为 LicheePi 4A 开发板添加 eMMC 支持的过程,使得内核可以从 eMMC 上的 rootfs 启动,还为 v6.5 版本的内核打入了 PREEMPT_RT 补丁,并解决了 redefinition 编译错误。除此之外,本文还介绍了使用 cyclictest 和 ftrace 追踪延迟的小技巧。
+
+## 参考资料
+
+- [为 LicheePi 4A 开发板构建运行 Linux v6.5-rc1][1]
+- [Drew Fustini 开发的 eMMC 补丁][2]
+- [thead-opensbi][3]
+- [适用于 Linux v6.5 的 官方 PREEMPT_RT 补丁][4]
+- [针对 RISC-V 架构的 PREEMPT_RT 补丁][5]
+- [[PATCH v3] RISC-V: Fix wrong use of CONFIG_HAVE_SOFTIRQ_ON_OWN_STACK][6]
+- [RISC-V 实时抢占优化实践 直播分享视频回放][7]
+- [RISC-V 实时抢占优化实践 直播分享 PPT][8]
+
+[1]: https://gitee.com/tinylab/riscv-linux/blob/master/articles/20230726-licheepi4a-linux.md
+[2]: https://lore.kernel.org/all/20230724-th1520-emmc-v2-0-132ed2e2171e@baylibre.com/
+[3]: https://github.com/xhackerustc/thead-opensbi
+[4]: https://cdn.kernel.org/pub/linux/kernel/projects/rt/6.5/
+[5]: https://lore.kernel.org/linux-riscv/20230510162406.1955-1-jszhang@kernel.org/
+[6]: https://lore.kernel.org/linux-riscv/20230913052940.374686-1-wangjiexun@tinylab.org/
+[7]: https://www.bilibili.com/video/BV1Xz4y1u7pp/?spm_id_from=333.999.0.0
+[8]: https://gitee.com/tinylab/riscv-linux/blob/master/ppt/riscv-licheepi4a-rt-intro.pdf