第6部分:将MemTest86 +移植到RISC-V


大概很少有IT专家需要解释一下Memtest86 +是什么-也许它已经成为或多或少成为测试PC RAM的标准。 在上一部分内容中,我碰到了与主板捆绑在一起的损坏的内存条,它(与支持DDR2的上网本一起)似乎是一个显而易见的解决方案。 另一个问题是,从原则上讲,肉眼可以看到系统的不稳定运行。 在更棘手的情况下,我听说除了可以无限次“窃听”存储单元至无穷大之外,该工具还使用了一些特殊的数据模式,在这些数据模式上更有可能检测到DDR操作中的错误。 总的来说,这是一件奇妙的事情,可惜的是,即使它的名字说的是:86-“仅适用于x86兼容系统”。 还是不行


在剪切下,您将看到我尝试将MemTest86 + v5.1移植到RISC-V以及小计的情况。 剧透:它移动了!


免责声明:我仅在特定板上的特定RocketChip组件上对测试结果进行了最少的测试。 不保证准确性和安全性(尤其是在其他系统上)。 使用风险自负。 特别是,如果当前保留的内存区域属于RAM范围,则不会对其进行任何处理。


正如我已经说过的,不久前我在速卖通上购买了带有Cyclone IV的主板,但其中的内存有问题。 幸运的是,该板卡的重要功能之一是使用了常规的DDR2 SO-DIMM模块-与我的旧上网本相同。 然而,可以说,获得一种用于测试内存模块(实际上也是控制器)的自托管解决方案是很有趣的。 在内存不足的情况下调试我的错误的前景根本不令人满意。 尤其是不希望有一个快速的解决方案,并且不希望在无限长的时间内推迟在另一个汇编器中进行完全重写,我打开了一篇有关Memtest86 +的Wikipedia文章,突然发现卡片中写着“用C和汇编语言编写”。 嗯,也就是说,他虽然是“ ... 86”,但不是完全用汇编语言编写的? 这令人鼓舞。 仍然只有了解关系。


因此,请转到memtest.org并在GPL2下下载版本5.01。 为了便于开发,我在GitHub上重新加载了它。 幸运的是,在源代码档案中,我们受到名为README.background的文件的欢迎。


Memtest86-SMP的解剖与生理

它详细解释了代码的高级操作(甚至使用ASCII形式的图片)。 在文档的开始,我们看到一个Binary布局 ,它由bootsect.osetup.ohead.o和一些memtest_shared 。 显而易见,这三个目标文件是从相应的汇编程序源中获得的。 乍一看,其他所有内容都是用C编写的! 还不错,还不错...


结果,我将Makefile复制到Makefile.arch并开始重写所有内容,并尝试丢弃不对应的内容。 当然,首先,我需要RISC-V的工具链,幸运的是,自从之前的实验以来,它一直在我身边。 起初我想为32位体系结构创建一个端口,但是后来我想起一个64位处理器已上传到板上,并且我的riscv64-带有riscv64-前缀。


抒情离题:当然,首先是研究32位和64位代码的兼容性问题。 结果,在1.3 RISC-V ISA Overview声明的第1.3 RISC-V ISA Overview段中找到了ISA(指令集体系结构)的非特权部分的规范:


显式分离基本ISA的主要优点是,可以针对每个基本ISA进行优化,而无需支持其他基本ISA所需的所有操作。 例如,RV64I可以省略仅用于处理RV32I中较窄寄存器的指令和CSR。 RV32I选项可以使用编码空间,否则仅保留给更宽的地址空间变体所需的指令使用。

我还想指出的是,如果正确选择了目标体系结构,则带有riscv64-前缀的工具链很可能会轻松收集32位代码-稍后会对此进行更多介绍。


移植时,将这些文件放在手边很有意义:



构建设置


让我们从达成一致开始:我想获得一个适合进一步移植到x86和RISC-V以外的体系结构的端口。 我还建议将引导软盘和其他x86细节排除在跨平台构建之外。


我们最终拥有的是:三个汇编器文件: bootsect.Ssetup.Shead.S 仅在启动时才需要前两个,而稍后在重定位到另一个存储区域时则需要第三个。 事实是,为了“自己”测试内存,测试代码必须首先移至新位置。 Sich文件在ELF中收集,然后从中获取代码,数据等部分。 此外,它以PIC(位置独立代码)的形式收集-起初,我什至感到惊讶:尽管代码是独立的(即,没有内核,libc等),但它使用了此类高级功能。


此外,在Makefile中会定期遇到定义体系结构的参数: -march=i486-m32等。 我需要写这样的东西 然后像一个吸盘 。 从体系结构的角度来看,RISC-V的情况是这样的: rv32rv64有很多选项(例如,对于将来的rv128 ,仍然有截断的嵌入式和保留,但我们对它们不是很感兴趣),而ISA名称是通过为该前缀分配字母而形成的扩展: i基本的整数指令集, m整数乘法和除法,...当然,我想做rv64i ,但是如果没有乘法,Memtest86很难移植到体系结构上。 没错,似乎编译器将只生成函数调用而不是“有问题的”指令,但是存在极大降低性能的风险(更不用说这些函数将需要在某个地方编写或使用)。


您还将需要ABI行。 原则上,调用约定的基础已经在“ RISC-V汇编程序员手册”中指定的Volume I进行了介绍,因此我将做类似


 $ riscv64-linux-gnu-gcc-9 -mabi=help riscv64-linux-gnu-gcc-9: error: unrecognized argument in option '-mabi=help' riscv64-linux-gnu-gcc-9: note: valid arguments to '-mabi=' are: ilp32 ilp32d ilp32e ilp32f lp64 lp64d lp64f riscv64-linux-gnu-gcc-9: fatal error: no input files compilation terminated. 

lp64考虑,我就lp64展望未来,我要说的是,使用此ABI,标准库中的头文件不起作用,因此我选择了lp64f ,并将ARCH“升级”为rv64imf 没有恐慌,我不打算在端口中真正使用浮点。


由于我某种程度上不想钻研跨平台的链接描述文件-因此我无法立即找到ld的键 ,因此我决定使用一个汇编器head.S文件,并使用memtest_shared.arch.lds其余功能。 我从中给出了输出格式和体系结构的指示(毕竟,从Makefile中的变量更改输出更容易),并在最后临时注释掉DISCARD ,无法弄清我需要哪些特定的调试信息部分。 (展望:良好的调试信息,但必须添加.rela一般来说,x86版本强调了必须适合64k的能力-我希望这与实模式的功能相关,并且在RISC-V上与我们无关。 结果,将收集与PIC共享的对象,就像在原始对象中一样,将要加载到内存中的代码和数据也被咬掉。


我们收集...,并且编译位于第一个reloc.c文件上-显然,它是从ld-linux.so提取的,并负责支持Global Offset Table等。 根据x86的调用约定。 事实证明,它需要使用汇编程序插入直接处理寄存器。 但是我们在RISC-V上-它最初是为本地支持PIC制作的,因此请随意抛出reloc.c 。 此外,仍然有插入物,有时很长。 幸运的是,它们要么在被注释掉的C代码之后立即进入测试代码,然后对其进行优化(我从中再次编写了由preprocessor指令切换的完整代码),要么依赖于平台,在某些情况下,如果没有这些代码,您可以(可能)做(例如打开/关闭缓存,减去CPUID等)。 最后,还有一些类似rdtsc调用,我rdtsc没有将任何大问题放入与平台相关的标头中,并根据RISC-V的文档进行了实现。


结果,我们获得了arch/i386目录,其中移动了大量的PCI支持代码,从芯片组读取信息,特定于平台的内存映射地址定义等。 同样, test_start函数的开头test_start ,它是setup.S到C代码的入口。多长时间,短,但是注释掉所有可能的东西,并实现在RISC-V下不能注释掉的所有东西(例如setup.S和用于工作的代码) SiFive实现中的串行端口),我得到了arch/riscv ,使用该arch/riscv或多或少地进行了所有编译。


在这里,我不得不澄清一下,实验本身是在撰写本文之前部分进行的,因此特定动作序列可能包含一定量的“艺术小说”。 但是,我至少尝试以某种方式进行演示,使其在任何情况下都代表一种可能的方式(我是程序员,我记得那) 。 因此,让我们看看如何开始一切。


在铁上运行


从过去的实验开始,我仍然在Raspberry Pi上有一个尘土飞扬的“架子”,并连接到调试板上。 电线提供UART,JTAG和带有SD卡的适配器。 将带有DDR2控制器的RV64处理器缝入配置存储器。 和以前一样,我打开“树莓”,在它之前打开两个SSH会话,其中一个转发3333 TCP端口,用于将gdb连接到OpenOCD。 在一个会话中,我启动minicom来监视UART,在另一个会话中,我启动opencomd来通过JTAG从主机进行调试。 我打开了板子的电源-控制台中出现了有关如何从SD加载数据的消息。


现在,您可以运行以下命令:


 riscv64-unknown-elf-gdb \ -ex 'target remote 127.0.0.1:3333' \ -ex 'restore /path/to/memtest_shared.bin binary 0x80010000' \ -ex 'add-symbol-file /path/to/memtest_shared 0x80010000' -ex 'set $pc=0x80010000' 

-ex选项-ex gdb假装用户已从控制台输入以下命令:


  • 第一个与OpenOCD建立连接
  • 第二个将指定主机文件的内容复制到指定地址
  • 第三部分向gdb解释说,必须从文件中获取有关源代码的信息,并考虑到它是从地址下载的(而不是其本身指示的内容)
    • 注意:我们从ELF文件中提取字符,并加载“原始”二进制文件
  • 最后,第四个将当前命令指针强制转换为我们的代码

不幸的是,并不是所有的事情都能顺利进行,尽管调试器中的代码行显示正确,但是在所有全局变量中均显示为零。 实际上,如果在gdb中执行形式为p &global_var的命令,可惜我们看到的地址与初始下载地址(我有0x0 )一致,而该地址未使用add-symbol-file指定。 作为拐杖,但非常简单的解决方案,我只是手动将0x80010000添加到指定的地址,并通过x/x 0xADDR内存的内容。 实际上,有可能在链接描述文件中临时指示正确的起始地址, 此刻 地址将与此测试配置中的下载地址一致。


现代建筑搬迁的特征


好了,如何以某种方式找到我们下载的代码-我们开始了。 不起作用。 逐步调试表明,我们陷入了switch_to_main_stack函数的运行过程中-似乎它仍在尝试使用与工作堆栈相对应的符号地址的不相关值。


一样,第一卷文档告诉我们不同的伪指令以及它们与PIC一起使用和关闭的工作:


某些RISC-V伪指令


如您所见,一般的原理是从当前指令开始计算存储器中的地址,第一个加上偏移量的顶部,第二个则add低位。 声明这样的全局变量几乎无济于事


 struct vars * const v = &variables; 

因此,我们使用RISC-V ELF psABI文档以及重定位类型的描述,并为reloc.c编写了特定于平台的部分。 这里应该注意的是,原始文件显然是来自跨平台代码的。 在那里,即使未指定特定的位深度, ElfW(Addr)宏, Elf32_AddrElf32_AddrElf64_Addr 。 但是,并不是到处都存在,这就是为什么我们将它们添加到通用代码(以及arch/riscv/reloc.inc.c中不存在的地方的原因-毕竟,对于RISC-V,没有特殊意义要绑定到特定的位深度,而不是特定的位深度必填)。


结果, switch_to_main_stack开始通过(当然不是没有平台相关的汇编器指令)。 调试器显示全局变量仍然不正确。 好吧,好的:(


硬件定义


当然,对于测试而言,可以使用硬编码的常量来代替抛出的设备定义代码,但是对于每个特定的处理器组件,按照我的应用程序的标准来重建memtest的成本甚至太高。 因此,我们将“作为严肃的成年人”行事。 幸运的是,在RISC-V上(可能在大多数现代体系结构上),引导加载程序习惯将代码传递给Device Tree Blob ,它是DTS描述的编译版本,如下所示:


zeowaa-1gb.dts
 /dts-v1/; / { #address-cells = ^_^lt gt^_^; #size-cells = ^_^lt gt^_^; compatible = "freechips,rocketchip-unknown-dev"; model = "freechips,rocketchip-unknown"; chosen { bootargs = "console=ttySIF0,125200 debug loglevel=7"; }; firmware { sifive,uboot = "YYYY-MM-DD"; }; L16: aliases { serial0 = &L8; }; L15: cpus { #address-cells = ^_^lt gt^_^; #size-cells = ^_^lt&#0;gt^_^; timebase-frequency = ^_^lt󴉀gt^_^; L5: cpu@0 { device_type = "cpu"; clock-frequency = ^_^lt&#0;gt^_^; compatible = "sifive,rocket0", "riscv"; d-cache-block-size = ^_^lt gt^_^; d-cache-sets = ^_^lt@gt^_^; d-cache-size = ^_^ltကgt^_^; d-tlb-sets = ^_^lt gt^_^; d-tlb-size = ^_^lt gt^_^; i-cache-block-size = ^_^lt gt^_^; i-cache-sets = ^_^lt@gt^_^; i-cache-size = ^_^ltကgt^_^; i-tlb-sets = ^_^lt gt^_^; i-tlb-size = ^_^lt gt^_^; mmu-type = "riscv,sv39"; next-level-cache = <&L10>; reg = <0x0>; riscv,isa = "rv64imafdc"; status = "okay"; timebase-frequency = ^_^lt󴉀gt^_^; tlb-split; L3: interrupt-controller { #interrupt-cells = ^_^lt gt^_^; compatible = "riscv,cpu-intc"; interrupt-controller; }; }; }; L10: ram@80000000 { device_type = "memory"; reg = <0x0 0x80000000 0x0 0x40000000>; reg-names = "mem"; }; L14: soc { #address-cells = ^_^lt gt^_^; #size-cells = ^_^lt gt^_^; compatible = "freechips,rocketchip-unknown-soc", "simple-bus"; ranges; L1: clint@2000000 { compatible = "riscv,clint0"; interrupts-extended = <&L3 3 &L3 7>; reg = <0x2000000 0x10000>; reg-names = "control"; }; L2: debug-controller@0 { compatible = "sifive,debug-013", "riscv,debug-013"; interrupts-extended = <&L3 65535>; reg = <0x0 0x1000>; reg-names = "control"; }; L9: gpio@64002000 { #gpio-cells = ^_^lt gt^_^; #interrupt-cells = ^_^lt gt^_^; compatible = "sifive,gpio0"; gpio-controller; interrupt-controller; interrupt-parent = <&L0>; interrupts = <3 4 5 6 7 8>; reg = <0x64002000 0x1000>; reg-names = "control"; }; L0: interrupt-controller@c000000 { #interrupt-cells = ^_^lt gt^_^; compatible = "riscv,plic0"; interrupt-controller; interrupts-extended = <&L3 11 &L3 9>; reg = <0xc000000 0x4000000>; reg-names = "control"; riscv,max-priority = ^_^lt gt^_^; riscv,ndev = ^_^lt gt^_^; }; L6: rom@10000 { compatible = "sifive,maskrom0"; reg = <0x10000 0x2000>; reg-names = "mem"; }; L8: serial@64000000 { compatible = "sifive,uart0"; interrupt-parent = <&L0>; clocks = <&tlclk>; interrupts = ^_^lt gt^_^; reg = <0x64000000 0x1000>; reg-names = "control"; }; L7: spi@64001000 { #address-cells = ^_^lt gt^_^; #size-cells = ^_^lt&#0;gt^_^; compatible = "sifive,spi0"; interrupt-parent = <&L0>; interrupts = ^_^lt gt^_^; reg = <0x64001000 0x1000>; clocks = <&tlclk>; reg-names = "control"; L12: mmc@0 { compatible = "mmc-spi-slot"; disable-wp; reg = <0x0>; spi-max-frequency = ^_^lt gt^_^; voltage-ranges = <3300 3300>; }; }; tlclk: tlclk { #clock-cells = ^_^lt&#0;gt^_^; clock-frequency = ^_^lt gt^_^; clock-output-names = "tlclk"; compatible = "fixed-clock"; }; }; }; 

我曾经解析ELF文件,但现在我再次对FDT(平面设备树)深信不疑:这些规范是有爱心的人编写的 (仍然,他们自己然后解析它!) 解析此类文件(至少直到您需要处理不受信任的输入为止)不会造成任何特殊问题。 所以在这里:在文件的开头,有一个简单的头结构,其中包含魔术数字0xd00dfeed和更多字段。 我们对“扁平树” off_dt_struct和行表off_dt_strings的偏移量感兴趣。 实际上,您还需要处理off_mem_rsvmap ,它枚举了最好避免使用的内存区域。 我仍然不理(它们(它们不在我的木板上),但是不要在家中重复


原则上,处理并不是特别困难:您只需要根据令牌在平整的树上行走即可。 三个主要标记:


  • FDT_BEGIN_NODE在紧随其后的额外数据中,以空终止字符串的形式出现子树元素的名称。 只需将名称添加到堆栈中
  • FDT_END_NODE子树结束,从堆栈中删除元素
  • FDT_PROP这有点棘手:它后面是一个结构,然后是len个字节的额外数据。 “变量”的名称位于字符串表中的偏移nameoff
     struct { uint32_t len; uint32_t nameoff; } 

好吧,总的来说,就是这样:我们仔细阅读本节,不要忘记观察4个字节的对齐情况。 美中不足的是:FDT中的数字采用大端格式,所以我们做一个简单的函数


 static inline uint32_t be32(uint32_t x) { return (x << 24) | (x >> 24) | ((x & 0xff0000) >> 8) | ((x & 0xff00) << 8); } 

结果,在riscv_entry首先要做的是解析FDT,而head.S负责将控制权转移到riscv_entry看起来像这样


  .globl startup_32 #  --    ... startup_32: lla sp, boot_stack_top mv s0, a0 # s0, s1 -- callee-saved mv s1, a1 # ...  .bss #   jal _dl_start #      mv a0, s0 mv a1, s1 j riscv_entry 

在寄存器a0我们得到了一个hart id(hart类似于RISC-V术语中的硬件流)-我还没有使用它,我必须在单线程情况下弄清楚它。 在a1引导加载程序会放置一个指向FDT的指针。 我们将其传递给函数void riscv_entry(ulong hartid, uint8_t *fdt_address)


现在,随着我代码中FDT parsilka的出现,电路板的加载顺序如下所示:


  • 打开电源
  • 等待U-boot控制台
  • 在其中输入命令以准备正确的FDT。 尤其是/chosen/bootargs command /chosen/bootargs存储内核命令行。 我从FDT取得的所有其他信息-RAM范围,UART地址,...-可以并且应该保留原样
     run fdtsetup fdt set /chosen bootargs "console=ttyS0 btrace" 
  • 使用fdt addr命令,查找FDT下载地址(如果您尚未查看)

从gdb端添加命令


  • -ex 'set $a1=0xfdtaddr'

信息输出到屏幕


事实证明,除了汇编插入外,还存在已知的内存地址。 例如SCREEN_ADRSCREEN_ADR类似,带有一个D ),它指向与屏幕上显示的内容相对应的区域。 当我遇到此问题时,我只是用宽大的手势将所有引用它的内容放在#if HAS_SCREEN ,然后盲目调试了很长时间。 我以为已经有一段时间手动将其全部转储到控制台了,但是后来我注意到,相同的代码令人痛苦地将许多转义序列输出到串行端口。 事实证明,所有事情已经写在我们面前,您只需要更准确地放置定义-此处是minicom窗口中熟悉的界面(尽管是黑色和白色)! (目前,根本不使用HAS_SCREEN-我只是启动了dummy_con数组以最少更改原始代码。)


在QEMU上调试


因此,我花了一段时间在一块真正的板上调试了所有东西-甚至不是盲目的。 但是一切都减慢了JTAG的速度-恐怖! 好吧,最后,所有内容都应该在真实的硬件上运行,但是在QEMU上进行调试会很好。 经过一定数量的实验,结果发现这是一个拐杖,但与使用电路板非常相似:


 $ qemu-system-riscv64 -M help Supported machines are: none empty machine sifive_e RISC-V Board compatible with SiFive E SDK sifive_u RISC-V Board compatible with SiFive U SDK spike_v1.10 RISC-V Spike Board (Privileged ISA v1.10) (default) spike_v1.9.1 RISC-V Spike Board (Privileged ISA v1.9.1) virt RISC-V VirtIO Board (Privileged ISA v1.10) 

我们看一下QEMU准备模拟哪些板。 我对sifive_u兼容的硬件感兴趣。


 $ qemu-system-riscv64 -M sifive_u,dumpdtb -m 1g # - QEMU      on --  strace   $ ls -l on -rw-rw-r-- 1 trosinenko trosinenko 1923  19 20:14 on $ dtc -I dtb < on > on.dts #   $ vim on.dts #  bootargs $ dtc < on.dts > on.dtb <stdout>: Warning (clocks_property): /soc/ethernet@100900fc:clocks: cell 0 is not a phandle reference <stdout>: Warning (clocks_property): /soc/ethernet@100900fc:clocks: cell 1 is not a phandle reference <stdout>: Warning (clocks_property): /soc/ethernet@100900fc:clocks: cell 2 is not a phandle reference <stdout>: Warning (interrupts_extended_property): /soc/interrupt-controller@c000000:interrupts-extended: cell 0 is not a phandle reference <stdout>: Warning (interrupts_extended_property): /soc/interrupt-controller@c000000:interrupts-extended: cell 2 is not a phandle reference <stdout>: Warning (interrupts_extended_property): /soc/clint@2000000:interrupts-extended: cell 0 is not a phandle reference <stdout>: Warning (interrupts_extended_property): /soc/clint@2000000:interrupts-extended: cell 2 is not a phandle reference 

现在我们有了一个“固定”的设备树blob。 在不更改虚拟机配置的情况下 ,执行以下操作:


 qemu-system-riscv64 \ -M sifive_u -m 1g \ -serial stdio \ -s -S 

-serial stdio将串行端口重定向到控制台,因为将积极使用转义序列。 -s -S选项分别引发gdbserver和创建要暂停的VM。 您可以使用loader下载代码,但是每次都必须重新启动QEMU。


您可以使用连接


 riscv64-unknown-elf-gdb \ -ex 'target remote 127.0.0.1:1234' \ -ex 'restore /path/to/on.dtb binary 0x80100000' \ -ex 'restore /path/to/memtest_shared.bin binary 0x80020000' \ -ex 'add-symbol-file memtest_shared 0x80100000' \ -ex 'set $a1=0x80020000' \ -ex 'set $pc=0x80100000' 

结果,一切都变得比聪明更有效!


一般工作原理


, , , Memtest86+ btrace , , ( , QEMU):


btrace模式


, , memtest . , (, trap): , , QEMU - ! «» Illegal instruction , . mcause (?), — mepc (?), — mtval ( ?), .


非法指示


, :


head.S:


 #       #   = 0 ---   ,   #  ,    ,     ... lla t1, _trap_entry csrw mtvec, t1 # ... _trap_entry: csrr a0, mcause csrr a1, mepc csrr a2, mtval jal riscv_trap_entry 

, calling convention, . memtest, HiFive_U-Boot, Volume II :


arch.c:


 static const char *errors[] = { "Instruction address misaligned", "Instruction access fault", "Illegal instruction", "Breakpoint", "Load address misaligned", "Load access fault", "Store/AMO address misaligned", "Store/AMO access fault", ^_^quot quot^_^, ^_^quot quot^_^, ^_^quot quot^_^, ^_^quot quot^_^, "Instruction page fault", "Load page fault", ^_^quot quot^_^, "Store/AMO page fault", }; void riscv_trap_entry(ulong cause, ulong epc, ulong tval) { char buf[32]; cprint(12, 0, "EXCP: "); if (cause < sizeof(errors) / sizeof(errors[0])) { cprint(12, 8, errors[cause]); } else { itoa(buf, cause); cprint(12, 8, buf); } cprint(13, 0, "PC: "); hprint3(13, 8, epc, 8); cprint(14, 0, "Addr: "); hprint3(14, 8, tval, 8); HALT(); } 

— « » . , «» , , , .


: . , memtest : : « , , . ». : do_test main.c 2, ( ), — «» , memtest. , run_at , memtest _start _end ( «» ), - spinlock' goto *addr; . , , «» , «».


, bss_dl_start , riscv_entry , trap entry. , : L1I-, . , fence.i .


, Memtest86+ — , barrier_s . , . , , .



, : . : . : , - (Own Address, ) . , , . . - . , x86 , , uint64_t 0x80000002 . , : , load/store x86 , — . , QEMU , « , ».


, , — unaligned access ..


, , RocketChip, — QEMU, , , RocketChip — unaligned access trap, QEMU « ».
«misaligned» ,


Changed description of misaligned load and store behavior. The specification now allows visible misaligned address traps in execution environment interfaces, rather than just mandating invisible handling of misaligned loads and stores in user mode. Also, now allows access exceptions to be reported for misaligned accesses (including atomics) that should not be emulated.

, , — , user-mode code , . . , , . , — - machine mode . , rdtsc (x86) rdtime (rv64), trap, . , , memory-mapped .


: , low_test_addr ( ), , fdt . , , low_test_addr , , 2 high_test_adr … , — : head.S initial_load_addr , riscv_entry move_to_correct_addr :


 static void move_to_correct_addr(void) { uintptr_t cur_start = (uintptr_t)&_start; uintptr_t cur_end = (uintptr_t)&_end; if (cur_start == low_test_addr || cur_start == high_test_adr) { //  ,     return; } if (cur_start == initial_load_addr && (cur_start - low_test_addr) < (cur_end - cur_start) ) { //   " ":   , //           //     ,    ,   //     ... serial_echo_print("FIRST STARTUP RELOCATION...\n"); void *temp_addr = (((uintptr_t)&_end >> 12) + 1) << 12; run_at(temp_addr, 0); } else { // ,    --- ,  . serial_echo_print("FINAL STARTUP RELOCATION...\n"); run_at(low_test_addr, 0); } } 

, — , memtest , RAM - . RISC-V , v->plim_lower .


, «» , -, — test.c ulong ( unsigneg long ), 32- x86 uint32_t , « 64 » uint64_t . «!!! Good: ffffffff Real: ffffffff Bad bits: 00000000». ? - -1, 32 1. , , 0… , : , ulong ( uint32_t ), ( uintptr_t ). , . , uint64_t 4. RISC-V , C, , — UB. memtest UBSan. , , UBSan trap-on-error JTAG.



, memtest - , , U-Boot.


: mkimage U-Boot Linux :


 mkimage -A riscv -O linux -T kernel -C none \ -a 0x80000000 -e 0x80000000 \ -n memtest -d memtest.bin memtest.uboot 

SD-


 run mmcsetup; run fdtsetup; fdt set /chosen bootargs "console=ttyS0"; fatload mmc 0:1 82000000 memtest.uboot; bootm fdt; bootm 82000000 - ${fdtaddr} 

( , run — ).


: FDT: 0xbffb7c80 . , : ffffffff , . , ( ), : HiFive_U-Boot :


  theKernel(machid, (unsigned long)images->ft_addr); 

,


  void (*theKernel)(int arch, uint params); 

, , , , 32 , head.S :


  li t0, 0xffffffffL and a1, a1, t0 


, , - , , , :


  • x86. — review
  • SMP RISC-V
  • arch/ -
  • test.c RISC-V ( -O0 !)

Source: https://habr.com/ru/post/zh-CN484026/


All Articles