在 RISC-V 中创建引导程序

Creating A Boot Program in RISC-V

我正在尝试为基于 RISC-V 的主板创建引导程序。 我正在遵循本指南,并将其改编为 riscv。 osdev

我遇到的问题是翻译这条指令。 times 510 -( $ - $$ ) db 0

我能想到的最好的办法是只填满 63 行 .8byte 0 但这似乎不太可行。

这是完整代码。

#################################
########### Boot Init ###########
#################################

.section .text

start:                          # begins the program
    nop                         # the do nothing instruction
    j start                     # loops back to start

# Todo:: figure out the size of the np and j instruction
# The intent of this portion is to fill the remaning bin file with 0's up until the last two bytes

.section .rodata
    .4byte 0                    # size of start instructions + this

    .8byte 0                    # begins the zero's, currently 510 byte
    .8byte 0
     # repeat 60ish times

    .8byte 0x0000000000aa55     # fills the last two bytes with the universal 
                                # 55aa to indicate boot program

编辑

我正在使用 risc 的 gcc 工具链。找到 here。我正在使用 .rept 指令。

Here is the updated code.

#################################
########### Boot Init ###########
#################################

start:                          # begins the program
    nop                         # the do nothing instruction
    j start                     # loops back to start

# Todo:: figure out the size of the np and j instruction
# The intent of this portion is to fill the file with 0's up until the last few bytes

    .rept 63
    .8byte 0
    .endr

    .4byte 0                    # size of start instructions + this

    .8byte 0                    # begins the zero's, currently 510 byte
    .8byte 0

    .8byte 0x0000000000aa55     # fills the last two bytes with the universal 
                                # 55aa to indicate boot program

十六进制转储如下:

00000000  01 00 fd bf 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000210  55 aa 00 00 00 00 00 00                           |U.......|
00000218

在这里我可以看到我显然弄乱了代码的字节顺序。 但是,我有一个新问题。十六进制转储的左列到底发生了什么?我知道 * 表示它填充为 0。但是该行从 0 到 10 然后从 210 到 218 为什么它先增加 10,然后最后增加 8?为什么我有一个空行 (218)?

编辑 无需告诉我行号,我现在意识到它是十六进制的。 所以最后一个问题仍然存在。我如何向这个 board 表明该程序是引导程序。有一个神奇的数字吗?我在他们的任何文档中都找不到任何指示。

times 不是指令。它是一个汇编指令。 $ returns 您当前的地址,$$ 表示您当前扇区的开头。因此,您正在用 0 填充 512 字节扇区的剩余部分(其中 2 个字节是幻数)。是的,64 行代码绝对行不通。对 times 命令的支持将取决于您的汇编器。所以,如果有一个支持 TIMES 和 RISC-V 的汇编器,你应该可以使用时间。 NASM 确实支持 times 指令,并且有一个用于 RISC-V https://github.com/riscv/riscv-nasm 的 NASM 版本。所以,你应该检查一下。

我有一块原装的hifive1板。对于原始板,入门指南是这样说的:

HiFive1 板在 SPI 闪存 (0x20000000) 的开头带有一个可修改的引导加载程序。在该程序执行结束时,核心跳转到代码的主要用户部分 0x20400000。

对于 rev b 板,它是这样说的:

HiFive1 Rev B 板在 SPI 闪存 (0x20000000) 的开头附带一个可修改的引导加载程序。在该程序执行结束时,核心跳转到 0x20010000 处代码的主要用户部分。

两个芯片都显示 0x80000000 用于 ram 和 0x20000000 用于(外部)闪存。假设这是他们将闪光灯放在 rev B 板上的接口。

第一个节目。

novectors.s

.globl _start
_start:
    lui x2,0x80004
    jal notmain
    sbreak
    j .

.globl dummy
dummy:
    ret

notmain.c

void  dummy ( unsigned int );
int notmain ( void )
{
    unsigned int ra;

    for(ra=0;;ra++) dummy(ra);
    return(0);
}

内存映射

MEMORY
{
    ram : ORIGIN = 0x80000000, LENGTH = 0x4000
}
SECTIONS
{
    .text : { *(.text*) } > ram
    .rodata : { *(.rodata*) } > ram
    .bss : { *(.bss*) } > ram
}

建造

riscv32-none-elf-as -march=rv32i -mabi=ilp32 novectors.s -o novectors.o
riscv32-none-elf-gcc -march=rv32i -mabi=ilp32 -Wall -O2 -nostdlib -nostartfiles -ffreestanding  -c notmain.c -o notmain.o
riscv32-none-elf-ld novectors.o notmain.o -T memmap -o notmain.elf
riscv32-none-elf-objdump -D notmain.elf > notmain.list
riscv32-none-elf-objcopy notmain.elf -O binary notmain.bin 

理论上你可以使用 riscv32-whatever-whatever(riscv32-unknown-elf 等)。因为这段代码足够通用。另请注意,我使用的是最小的 rv32i,您可以使用 rv32imac。

检查反汇编:

Disassembly of section .text:

80000000 <_start>:
80000000:   80004137            lui x2,0x80004
80000004:   010000ef            jal x1,80000014 <notmain>
80000008:   00100073            ebreak
8000000c:   0000006f            j   8000000c <_start+0xc>

80000010 <dummy>:
80000010:   00008067            ret

80000014 <notmain>:
80000014:   ff010113            addi    x2,x2,-16 # 80003ff0 <notmain+0x3fdc>
80000018:   00812423            sw  x8,8(x2)
8000001c:   00112623            sw  x1,12(x2)
80000020:   00000413            li  x8,0
80000024:   00040513            mv  x10,x8
80000028:   fe9ff0ef            jal x1,80000010 <dummy>
8000002c:   00140413            addi    x8,x8,1
80000030:   ff5ff06f            j   80000024 <notmain+0x10>

作为 rv32i,它都是 32 位指令,这很好。这个程序打算加载到ram中并且运行那里带有调试器,我使用openocd和telnet in.

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger

然后

halt
load_image notmain.elf
resume 0x80000000

在远程登录中 window。

那你可以再停下来。

80000024:   00040513            mv  x10,x8
80000028:   fe9ff0ef            jal x1,80000010 <dummy>
8000002c:   00140413            addi    x8,x8,1
80000030:   ff5ff06f            j   80000024 <notmain+0x10>

您可以检查 x8 或 x10 以查看它是否计数:

resume
halt

并再次检查寄存器,它们应该已经递增了。第一个节目 运行ning,继续。

第二个程序改用这个 linker 脚本:

内存映射

MEMORY
{
    rom : ORIGIN = 0x20010000, LENGTH = 0x4000
    ram : ORIGIN = 0x80000000, LENGTH = 0x4000
}

SECTIONS
{
    .text : { *(.text*) } > rom
    .rodata : { *(.rodata*) } > rom
    .bss : { *(.bss*) } > ram
}

检查反汇编。

Disassembly of section .text:

20010000 <_start>:
20010000:   80004137            lui x2,0x80004
20010004:   010000ef            jal x1,20010014 <notmain>
20010008:   00100073            ebreak
2001000c:   0000006f            j   2001000c <_start+0xc>

20010010 <dummy>:
20010010:   00008067            ret

20010014 <notmain>:
20010014:   ff010113            addi    x2,x2,-16 # 80003ff0 <notmain+0x5fff3fdc>
20010018:   00812423            sw  x8,8(x2)
2001001c:   00112623            sw  x1,12(x2)
20010020:   00000413            li  x8,0
20010024:   00040513            mv  x10,x8
20010028:   fe9ff0ef            jal x1,20010010 <dummy>
2001002c:   00140413            addi    x8,x8,1
20010030:   ff5ff06f            j   20010024 <notmain+0x10>

它似乎与位置无关,因此它应该与其他 linker 脚本一样工作,但最好使用正确的地址。

我的笔记说:

flash protect 0 64 last off
program notmain.elf verify
resume 0x20010000

现在您应该能够重置或重启开发板,以不重置(或者如果您愿意的话)的方式连接 openocd,然后您不需要加载它应该有的任何东西运行 他们的引导加载程序然后在该地址启动您的引导加载程序(正如他们提到的那样跳转到它)。检查 r8 或 r10(此 abi 的 r10 是传递的第一个参数,因此即使您的 gcc 使用 r8 以外的其他东西构建,r10 仍应反映计数器)resume, halt, reg, resume, halt, reg ...

在覆盖 0x20000000 处的引导加载程序之前,我会转储它并确保您有一份完整的副本,或者他们的网站上可能有一份副本。然后你可以将 linker 脚本更改为 0x20000000。在我亲自这样做之前,我会拆解并检查他们的引导加载程序,看看它是否在做什么,是否值得保留等等。他们的文字说“可修改”

我在 hifive1 板上磨掉了我的 risc-v 牙齿,但已经转向 sim 开源核心,hifive 板非常昂贵。我还制作了一个最小的 pcb 并放下了一些 sifive 部件,只打算 运行 出 ram 等,但是我的板子太小了,我没有回去再试一次,他们论坛上的支持很少对于 pcb 工作和他们的文档还有一些不足之处。

关键是那里有很多核心,你可以用 verilator 或其他东西模拟,看看发生的一切,你不能变砖,也不能放烟,因为它是模拟。

注rv32ic

riscv32-none-elf-as -march=rv32ic -mabi=ilp32 novectors.s -o novectors.o
riscv32-none-elf-gcc -march=rv32ic -mabi=ilp32 -Wall -O2 -nostdlib -nostartfiles -ffreestanding  -c notmain.c -o notmain.o
riscv32-none-elf-ld novectors.o notmain.o -T memmap -o notmain.elf
riscv32-none-elf-objdump -D notmain.elf > notmain.list
riscv32-none-elf-objcopy notmain.elf -O binary notmain.bin 

你可以看到它尽可能使用压缩指令

20010000 <_start>:
20010000:   80004137            lui x2,0x80004
20010004:   00a000ef            jal x1,2001000e <notmain>
20010008:   9002                    ebreak
2001000a:   a001                    j   2001000a <_start+0xa>

2001000c <dummy>:
2001000c:   8082                    ret

2001000e <notmain>:
2001000e:   1141                    addi    x2,x2,-16
20010010:   c422                    sw  x8,8(x2)
20010012:   c606                    sw  x1,12(x2)
20010014:   4401                    li  x8,0
20010016:   8522                    mv  x10,x8
20010018:   3fd5                    jal 2001000c <dummy>
2001001a:   0405                    addi    x8,x8,1
2001001c:   bfed                    j   20010016 <notmain+0x8>

编写自己的模拟器也很容易。取决于你想如何分阶段学习这个平台。掌握指令集与工具链与特定芯片及其外围设备有多少。

您肯定想要来自 riscv.org 的 risc-v 文档,这些文档与核心支持的版本相匹配,大量内部核心寄存器和其他内容以及指令集。以及相关芯片的入门和芯片文档,如果您想做自己的事情。如果你想在他们的沙箱之一中玩并使用一些第三方库,那么你需要学习他们的沙箱并在他们的沙箱中玩而不是做你自己的事情。看来你想做你自己的事了。

注意我使用的是来自 gnu 主线源的 gcc/binutils 的当前版本,手工构建。

riscv32-none-elf-gcc --version
riscv32-none-elf-gcc (GCC) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

riscv32-none-elf-as --version
GNU assembler (GNU Binutils) 2.32
Copyright (C) 2019 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or later.
This program has absolutely no warranty.
This assembler was configured for a target of `riscv32-none-elf'.

上面的代码在几年前针对原始的 hifive1 运行良好,这种风格往往适用于 gnu 的主要版本,我已经将此工具链用于其他 riscv 内核,所以即使你的版本较旧,它仍然可以工作。最重要的是将 arch (-march) 与内核支持的指令集相匹配,或者至少所有内核都应该支持子集 rv32i,压缩和乘法等并不总是支持。

我的第一个开发板的 openocd 配置文件

adapter_khz     10000

interface ftdi
ftdi_device_desc "Dual RS232-HS"
ftdi_vid_pid 0x0403 0x6010

ftdi_layout_init 0x0008 0x001b
ftdi_layout_signal nSRST -oe 0x0020 -data 0x0020

set _CHIPNAME riscv
jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x10e31913

set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME riscv -chain-position $_TARGETNAME
$_TARGETNAME configure -work-area-phys 0x80000000 -work-area-size 10000 -work-area-backup 1

flash bank onboard_spi_flash fespi 0x20000000 0 0 0 $_TARGETNAME
init

openocd -f riscv.cfg 在一个 terminal/window 然后 telnet localhost 4444 在另一个

现在就你所询问的 gnu 汇编程序的细微差别而言,请参阅 gnu 汇编程序,或者最好尽可能少地使用 assembler/toolchain 特定的东西,因为它可能会改变 and/or 你可能会改变某天的工具。 YMMV

gnu 工具无法从墙上的一个洞中识别出这块板。您告诉 gnu 工具有关处理器核心架构的信息,并在 linker 脚本中告诉内存映射。您的代码,直接或间接(如果您使用其他人的 bootstrap 和 linker 脚本)必须匹配处理器核心的启动属性,无论是来自 sifive 的 risc-v 还是某些 arm 核心或 mips 或x86 等

Vector table or not, execute at some address, etc. 在上面的例子中,他们的引导加载程序跳转到 0x20010000 所以你需要把第一条指令放在 0x20010000 上,这是通过让那条指令成为第一条来完成的一个在 bootstrap 源代码中,如果未在 linker 脚本中指定,首先在 ld 命令行上使用该对象,并在尝试 [=201 之前检查反汇编以确认它有效=] 它在硬件上。

我使用的 riscv 核心没有向量 table,对于重置它们只是在某个地址开始执行。因此,如果您没有跳转到您的预引导加载程序,您将使用相同的方法。对于其他非 risc-v 的体系结构,board/platform 的程序结构会有所不同,如果它是跳转到地址事物与向量 table 事物。

现在说,如果您使用的是他们的沙箱,那么这是沙箱问题而不是 gnu 工具链问题。

在他们的文档中,板文档 and/or 网站表明 rev b 板使用 FE310-G002 芯片,在 FE310-G002 文档中您可以找到内存映射。它还表明这是一个 risc-v 架构,从那里你可以转到 riscv.org 基金会并获取该架构的文档,告诉你它是如何启动的。回到 FE310-G002,它告诉您从 MSEL 引脚启动的过程。您需要检查原理图。所以实际上他们的文档确实告诉您如何通过提供您需要提供给 gnu 的信息来表明这是一个引导加载程序。

说...一些实验是 desired/required。 possible/easy 编写一个简单的位置无限循环,为 0x00000000 构建,但根据他们的文档在 0x20010000 处加载,并使用 openocd 检查程序计数器,看它是否真的基于 0x20010000。由此您可以假设,最终在发货时,无论选择何种 MSEL,电路板都会通过他们的引导加载程序进入您的电路板。

嗯嗯:

On power-on, the core’s reset vector is 0x1004 .

它进一步指出了每个 MSEL 带选项的不同第一指令地址。因此,如果您要接管他们的引导加载程序并根据文档将其替换为您自己的引导加载程序,您将为 0x20000000 link 并在那里设置入口点。

编辑

刚拿到我的 rev b 板。

您可以查看入门指南以了解如何指定板 使用他们的沙箱。但这不是必需的,如果你有一个 (gnu) 支持 rv32i 或超过您可以构建的 rv32imac 的工具链 没有其他外部依赖项的程序。

工具链本身不知道一块板与另一块板,一块芯片与另一块芯片。

sifive 文档说:

HiFive1 Rev B 板在 SPI 闪存 (0x20000000) 的开头带有可修改的引导加载程序。在该程序执行结束时,核心跳转到代码的主要用户部分 0x20010000。

这就是我们需要的关键信息,加上内存映射中 0x80000000 0x4000 字节 sram 部分的内存地址 space。

novectors.s

.globl _start
_start:
    lui x2,0x80004
    jal notmain
    j .

.globl dummy
dummy:
    ret

.globl PUT32
PUT32:
    sw x11,(x10)
    ret

.globl GET32
GET32:
    lw x10,(x10)
    ret

notmain.c

void PUT32( unsigned int, unsigned int);
unsigned int GET32 ( unsigned int );
void  dummy ( unsigned int );

#define GPIOBASE 0x10012000
#define GPIO_VALUE          (GPIOBASE+0x00)
#define GPIO_INPUT_EN       (GPIOBASE+0x04)
#define GPIO_OUTPUT_EN      (GPIOBASE+0x08)
#define GPIO_PORT           (GPIOBASE+0x0C)
#define GPIO_PUE            (GPIOBASE+0x10)
#define GPIO_OUT_XOR        (GPIOBASE+0x40)

int notmain ( void )
{
    unsigned int rx;

    PUT32(GPIO_OUTPUT_EN,(1<<19)|(1<<21)|(1<<22));
    PUT32(GPIO_PORT,(1<<19)|(1<<21)|(1<<22));
    PUT32(GPIO_OUT_XOR,0);
    while(1)
    {
        PUT32(GPIO_PORT,(1<<19)|(1<<21)|(1<<22));
        for(rx=0;rx<2000000;rx++) dummy(rx);
        PUT32(GPIO_PORT,0);
        for(rx=0;rx<2000000;rx++) dummy(rx);
    }

    return(0);
}

内存映射

MEMORY
{
    rom : ORIGIN = 0x20010000, LENGTH = 0x1000
    ram : ORIGIN = 0x80000000, LENGTH = 0x4000
}
SECTIONS
{
    .text : { *(.text*) } > rom
    .rodata : { *(.rodata*) } > rom
    .bss : { *(.bss*) } > ram
}

建造

riscv32-none-elf-as -march=rv32imac -mabi=ilp32 novectors.s -o novectors.o
riscv32-none-elf-gcc -march=rv32imac -mabi=ilp32 -Wall -O2 -nostdlib -nostartfiles -ffreestanding  -c notmain.c -o notmain.o
riscv32-none-elf-ld novectors.o notmain.o -T memmap -o notmain.elf
riscv32-none-elf-objdump -D notmain.elf > notmain.list
riscv32-none-elf-objcopy notmain.elf -O ihex notmain.hex
riscv32-none-elf-objcopy notmain.elf -O binary notmain.bin 

现在理论上您可以使用他们谈论的 riscv64-unknown-elf,即使他们想为 rv32 而不是 rv64 构建。我也可以试试

notmain.list

Disassembly of section .text:

20010000 <_start>:
20010000:   80004137            lui x2,0x80004
20010004:   010000ef            jal x1,20010014 <notmain>
20010008:   a001                    j   20010008 <_start+0x8>

2001000a <dummy>:
2001000a:   8082                    ret

2001000c <PUT32>:
2001000c:   c10c                    sw  x11,0(x10)
2001000e:   8082                    ret

20010010 <GET32>:
20010010:   4108                    lw  x10,0(x10)
20010012:   8082                    ret

20010014 <notmain>:
20010014:   1141                    addi    x2,x2,-16
20010016:   c04a                    sw  x18,0(x2)
20010018:   10012937            lui x18,0x10012
2001001c:   00890513            addi    x10,x18,8 # 10012008 <_start-0xfffdff8>
20010020:   006805b7            lui x11,0x680
20010024:   c606                    sw  x1,12(x2)
20010026:   c226                    sw  x9,4(x2)
20010028:   c422                    sw  x8,8(x2)
2001002a:   37cd                    jal 2001000c <PUT32>
2001002c:   00c90513            addi    x10,x18,12
20010030:   006805b7            lui x11,0x680
20010034:   3fe1                    jal 2001000c <PUT32>
20010036:   04090513            addi    x10,x18,64
2001003a:   4581                    li  x11,0
2001003c:   001e84b7            lui x9,0x1e8
20010040:   37f1                    jal 2001000c <PUT32>
20010042:   0931                    addi    x18,x18,12
20010044:   48048493            addi    x9,x9,1152 # 1e8480 <_start-0x1fe27b80>
20010048:   006805b7            lui x11,0x680
2001004c:   854a                    mv  x10,x18
2001004e:   3f7d                    jal 2001000c <PUT32>
20010050:   4401                    li  x8,0
20010052:   8522                    mv  x10,x8
20010054:   0405                    addi    x8,x8,1
20010056:   3f55                    jal 2001000a <dummy>
20010058:   fe941de3            bne x8,x9,20010052 <notmain+0x3e>
2001005c:   4581                    li  x11,0
2001005e:   854a                    mv  x10,x18
20010060:   3775                    jal 2001000c <PUT32>
20010062:   4401                    li  x8,0
20010064:   8522                    mv  x10,x8
20010066:   0405                    addi    x8,x8,1
20010068:   374d                    jal 2001000a <dummy>
2001006a:   fe941de3            bne x8,x9,20010064 <notmain+0x50>
2001006e:   bfe9                    j   20010048 <notmain+0x34>

重要的是在尝试将程序加载到设备上之前检查,我们所需的输入代码,novectors.s 的第一条指令需要在 0x20010000 处,因为 board/chip 出厂时(工厂引导加载程序) .确实如此。

notmain.hex

:020000042001D9
:1000000037410080EF00000101A082800CC1828096
:100010000841828041114AC0372901101305890027
:10002000B705680006C626C222C4CD371305C9002D
:10003000B7056800E13F130509048145B7841E0038
:10004000F137310993840448B70568004A857D3F3C
:10005000014422850504553FE31D94FE81454A85F0
:1000600075370144228505044D37E31D94FEE9BF31
:0400000520010000D6
:00000001FF

复制notmain.hex到挂载的HiFive媒体。现在,这花了我一两个小时来尝试在我开始时找出十六进制文件,在这里,它没有用。下载了他们挖掘的 sdk,发现了一个 elf2hex,但它似乎是用于 fpga 工作的坏切线。想通了,他们所做的就是 riscv...objcopy -O ihex 就像我一样,再试一次。现在它起作用了。我收到一个 fail.txt 说它之前无法连接到 cpu。不知道我做了什么或没有做什么来完成这项工作。

理论上可以剪切粘贴上面的hex文件,保存复制。为什么没有人有示例 hex 文件,您必须正确安装 75 个特殊的东西和 运行 构建而不是还提供这里是带有中间文件的完整示例。我当然会在这个平台的示例中这样做。或者至少是上面那个。

而不是他们的彩虹 LED blinking 模式,上面将使其 blink“白色”以正常速率打开和关闭。

注意 LED 在 rev a 板上的相同 GPIO 线上,引导加载程序位于与 rev b 0x20010000 不同的地址 0x20400000。因此,可以为具有该 memmap 更改的 rev 板构建相同的内容。

如果您或 reader 想要返回一个 rev a,如果他们有一个,那是一个修改过的 openocd,在撰写本文时它位于 github riscv 用户 riscv- openocd项目。正常的 ./bootstrap, ./configure, make 获取工具,在 tcl 目录中有上面显示的 riscv openocd 配置文件

interface ftdi
ftdi_device_desc "Dual RS232-HS"
ftdi_vid_pid 0x0403 0x6010

是关键,rev2 板 lsusb:

Bus 001 Device 018: ID 1366:1051 SEGGER 

并且没有命中 openocd 配置文件中的那些 pid/vid 值。导致阅读更多入门手册。