Hello Worldのプログラムを動かしながら、RISC-V Spikeシミュレータのログを追っていき、RISC-Vのブートシーケンスを追っていく。
riscv-isa-sim/riscv/sim.cc
内のリセットベクタが最初に動作する。
riscv-isa-sim/riscv/sim.cc
void sim_t::make_dtb()
{
...
uint32_t reset_vec[reset_vec_size] = {
0x297,
0x28593 + (reset_vec_size * 4 << 20),
0xf1402573,
get_core(0)->get_xlen() == 32 ?
0x0182a283u :
0x0182b283u,
0x28067,
0,
(uint32_t) (start_pc & 0xffffffff),
(uint32_t) (start_pc >> 32)
};
その直後にコンパイルしたDTB(Device Tree Blob)が挿入される。
riscv-isa-sim/riscv/sim.cc
void sim_t::make_dtb()
{
...
dts = s.str();
std::string dtb = dts_compile(dts);
rom.insert(rom.end(), dtb.begin(), dtb.end());
const int align = 0x1000;
rom.resize((rom.size() + align - 1) / align * align);
ここから先はpk(Proxy Kernel)のエントリポイントに移動する。エントリポイントではまずdo_reset
にジャンプする。
${RISCV}/riscv64-unknown-elf/bin/pk.dmp
0000000080000000 <_ftext>:
80000000: 1e80006f j 800001e8 <do_reset>
${RISCV}/riscv64-unknown-elf/bin/pk.dmp
00000000800001e8 <do_reset>:
800001e8: 00000093 li ra,0
800001ec: 00000113 li sp,0
800001f0: 00000193 li gp,0
800001f4: 00000213 li tp,0
...
800002a8: 00070463 beqz a4,800002b0 <do_reset+0xc8>
800002ac: 6f60206f j 800029a2 <init_first_hart>
800002b0: 00800613 li a2,8
800002b4: 30461073 csrw mie,a2
init_first_hart()
にジャンプする。init_first_hart()
はriscv-pk/machine/minit.c
に定義されている。
void init_first_hart(uintptr_t hartid, uintptr_t dtb)
{
query_uart(dtb);
query_htif(dtb);
hart_init();
hls_init(0);
...
init_first_hart()
での仕事
init_first_hart()
は以下の関数が並んでいる。
struct fdt_cb {
void (*open)(const struct fdt_scan_node *node, void *extra);
void (*prop)(const struct fdt_scan_prop *prop, void *extra);
void (*done)(const struct fdt_scan_node *node, void *extra);
int (*close)(const struct fdt_scan_node *node, void *extra);
void *extra;
};
void query_uart(uintptr_t fdt)
{
struct fdt_cb cb;
struct uart_scan scan;
memset(&cb, 0, sizeof(cb));
cb.open = uart_open;
cb.prop = uart_prop;
cb.done = uart_done;
cb.extra = &scan;
fdt_scan(fdt, &cb);
}
query_htif(dtb)
htif_done()
関数では、グローバル変数htif<0x80015100>
に対して1を設定している。
static void htif_done(const struct fdt_scan_node *node, void *extra)
{
struct htif_scan *scan = (struct htif_scan *)extra;
if (!scan->compat) return;
htif = 1;
}
static void hart_init()
{
mstatus_init();
fp_init();
delegate_traps();
}
static void fp_init()
{
if (!supports_extension('D') && !supports_extension('F'))
return;
assert(read_csr(mstatus) & MSTATUS_FS);
#ifdef __riscv_flen
for (int i = 0; i < 32; i++)
init_fp_reg(i);
write_csr(fcsr, 0);
#else
uintptr_t fd_mask = (1 << ('F' - 'A')) | (1 << ('D' - 'A'));
clear_csr(misa, fd_mask);
assert(!(read_csr(misa) & fd_mask));
#endif
}
static void delegate_traps()
{
if (!supports_extension('S'))
return;
uintptr_t interrupts = MIP_SSIP | MIP_STIP | MIP_SEIP;
uintptr_t exceptions =
(1U << CAUSE_MISALIGNED_FETCH) |
(1U << CAUSE_FETCH_PAGE_FAULT) |
(1U << CAUSE_BREAKPOINT) |
(1U << CAUSE_LOAD_PAGE_FAULT) |
(1U << CAUSE_STORE_PAGE_FAULT) |
(1U << CAUSE_BREAKPOINT) |
(1U << CAUSE_USER_ECALL);
write_csr(mideleg, interrupts);
write_csr(medeleg, exceptions);
assert(read_csr(mideleg) == interrupts);
assert(read_csr(medeleg) == exceptions);
}
hls_t* hls_init(uintptr_t id)
{
hls_t* hls = OTHER_HLS(id);
memset(hls, 0, sizeof(*hls));
return hls;
}
#define OTHER_HLS(id) ((hls_t*)((void*)HLS() + RISCV_PGSIZE * ((id) - read_const_csr(mhartid))))
static void finisher_done(const struct fdt_scan_node *node, void *extra)
{
struct finisher_scan *scan = (struct finisher_scan *)extra;
if (!scan->compat || !scan->reg || finisher) return;
finisher = (uint32_t*)(uintptr_t)scan->reg;
}
query_mem(dtb)
メモリの構成を記述している。
riscv-pk/machine/fdt.c
static void mem_prop(const struct fdt_scan_prop *prop, void *extra)
{
struct mem_scan *scan = (struct mem_scan *)extra;
if (!strcmp(prop->name, "device_type") && !strcmp((const char*)prop->value, "memory")) {
scan->memory = 1;
} else if (!strcmp(prop->name, "reg")) {
scan->reg_value = prop->value;
scan->reg_len = prop->len;
}
}
riscv-pk/machine/fdt.c
別に何かが設定された様子も無いけど、どうなっているんだろう?っていか、下記のSpikeのDTSだと0x0-0x80000000までを2回設定しているんだけど、これはいかに?
static void mem_done(const struct fdt_scan_node *node, void *extra)
{
struct mem_scan *scan = (struct mem_scan *)extra;
const uint32_t *value = scan->reg_value;
const uint32_t *end = value + scan->reg_len/4;
uintptr_t self = (uintptr_t)mem_done;
if (!scan->memory) return;
assert (scan->reg_value && scan->reg_len % 4 == 0);
while (end - value > 0) {
uint64_t base, size;
value = fdt_get_address(node->parent, value, &base);
value = fdt_get_size (node->parent, value, &size);
if (base <= self && self <= base + size) { mem_size = size; }
}
assert (end == value);
}
query_harts(dtb)
CPUのコンフィグレーションについて設定を行っている。"device_type=cpu"の部分のDTSを読み取っている。
static void hart_prop(const struct fdt_scan_prop *prop, void *extra)
{
struct hart_scan *scan = (struct hart_scan *)extra;
if (!strcmp(prop->name, "device_type") && !strcmp((const char*)prop->value, "cpu")) {
assert (!scan->cpu);
scan->cpu = prop->node;
} else if (!strcmp(prop->name, "interrupt-controller")) {
assert (!scan->controller);
scan->controller = prop->node;
} else if (!strcmp(prop->name, "#interrupt-cells")) {
scan->cells = bswap(prop->value[0]);
} else if (!strcmp(prop->name, "phandle")) {
scan->phandle = bswap(prop->value[0]);
} else if (!strcmp(prop->name, "reg")) {
uint64_t reg;
fdt_get_address(prop->node->parent, prop->value, ®);
scan->hart = reg;
}
}
static void hart_done(const struct fdt_scan_node *node, void *extra)
{
struct hart_scan *scan = (struct hart_scan *)extra;
if (scan->cpu == node) {
assert (scan->hart >= 0);
}
if (scan->controller == node && scan->cpu) {
assert (scan->phandle > 0);
assert (scan->cells == 1);
if (scan->hart < MAX_HARTS) {
hart_phandles[scan->hart] = scan->phandle;
hart_mask |= 1 << scan->hart;
hls_init(scan->hart);
}
}
}
static void wake_harts()
{
for (int hart = 0; hart < MAX_HARTS; ++hart)
if ((((~disabled_hart_mask & hart_mask) >> hart) & 1))
*OTHER_HLS(hart)->ipi = 1;
}
static void memory_init()
{
mem_size = mem_size / MEGAPAGE_SIZE * MEGAPAGE_SIZE;
}
void boot_loader(uintptr_t dtb)
{
extern char trap_entry;
write_csr(stvec, &trap_entry);
write_csr(sscratch, 0);
write_csr(sie, 0);
set_csr(sstatus, SSTATUS_SUM);
file_init();
enter_supervisor_mode(rest_of_boot_loader, pk_vm_init(), 0);
}
file_init()
stdin
, stdout
, stderr
を作成する。files_t
という型名でグローバル領域に領域が確保されている。file_t
の型は以下の通り。
typedef struct file
{
int kfd;
uint32_t refcnt;
} file_t;
riscv-pk/pk/file.c
: file_get_free()
参照カウントがまだ0になっている構造体を取得する。dupはインクリメントの回数を増やす?
static file_t* file_get_free()
{
for (file_t* f = files; f < files + MAX_FILES; f++)
if (atomic_read(&f->refcnt) == 0 && atomic_cas(&f->refcnt, 0, 2) == 0)
return f;
return NULL;
}
int file_dup(file_t* f)
{
for (int i = 0; i < MAX_FDS; i++)
{
if (atomic_cas(&fds[i], 0, f) == 0)
{
file_incref(f);
return i;
}
}
return -1;
}
enter_supervisor_mode()
ではプログラムをロードして実行するのだが、mepc
に関数の先頭アドレスをロードして、mret
で実行する。
enter_supervisor_mode(rest_of_boot_loader, pk_vm_init(), 0)
としており、rest_of_boot_loader()
の内容についてみていく。
static void rest_of_boot_loader(uintptr_t kstack_top)
{
arg_buf args;
size_t argc = parse_args(&args);
if (!argc)
panic("tell me what ELF to load!");
long phdrs[128];
current.phdr = (uintptr_t)phdrs;
current.phdr_size = sizeof(phdrs);
load_elf(args.argv[0], ¤t);
run_loaded_program(argc, args.argv, kstack_top);
}
fdt_scan()
の実装
Flatten Device Tree の構造しかParseしない。
まずはFTDのヘッダを見ていくと、構造体としては以下のように定義されている。この辺りは、以下のサイトが非常に詳細に解説しており、役に立った。
http://masahir0y.blogspot.com/2014/05/device-tree.html
struct fdt_header {
uint32_t magic;
uint32_t totalsize;
uint32_t off_dt_struct;
uint32_t off_dt_strings;
uint32_t off_mem_rsvmap;
uint32_t version;
uint32_t last_comp_version;
uint32_t boot_cpuid_phys;
uint32_t size_dt_strings;
uint32_t size_dt_struct;
};
void fdt_scan(uintptr_t fdt, const struct fdt_cb *cb)
{
struct fdt_header *header = (struct fdt_header *)fdt;
if (bswap(header->magic) != FDT_MAGIC ||
bswap(header->last_comp_version) > FDT_VERSION) return;
const char *strings = (const char *)(fdt + bswap(header->off_dt_strings));
uint32_t *lex = (uint32_t *)(fdt + bswap(header->off_dt_struct));
fdt_scan_helper(lex, strings, 0, cb);
}
そこから先はFDTのバイナリを読み取ってひたすらfdt_scan_helper()
を実行する。
lex
にはそのDevice Treeの構造を示す先頭のポインタが入り、string
にはDevice Treeの内容を示す文字列が入る。fdt_scan_helper()
はデータ構造の役割を定義してあり、
#define FDT_BEGIN_NODE 1
#define FDT_END_NODE 2
#define FDT_PROP 3
#define FDT_NOP 4
#define FDT_END 9
となっている。たとえば、FDT_BEGIN_NODE
の場合には、やたらとopen()
、close()
をしているけどこれは何だろう。
static uint32_t *fdt_scan_helper(
uint32_t *lex,
const char *strings,
struct fdt_scan_node *node,
const struct fdt_cb *cb)
{
...
case FDT_BEGIN_NODE: {
uint32_t *lex_next;
if (!last && node && cb->done) cb->done(node, cb->extra);
last = 1;
child.name = (const char *)(lex+1);
if (cb->open) cb->open(&child, cb->extra);
lex_next = fdt_scan_helper(
lex + 2 + strlen(child.name)/4,
strings, &child, cb);
if (cb->close && cb->close(&child, cb->extra) == -1)
while (lex != lex_next) *lex++ = bswap(FDT_NOP);
lex = lex_next;
break;
}
...
}
uartの場合、それぞれ、cb->open()
、cb->prop()
、cb->done()
で使用されている関数は以下となる。
static void uart_open(const struct fdt_scan_node *node, void *extra)
{
struct uart_scan *scan = (struct uart_scan *)extra;
memset(scan, 0, sizeof(*scan));
}
static void uart_prop(const struct fdt_scan_prop *prop, void *extra)
{
struct uart_scan *scan = (struct uart_scan *)extra;
if (!strcmp(prop->name, "compatible") && !strcmp((const char*)prop->value, "sifive,uart0")) {
scan->compat = 1;
} else if (!strcmp(prop->name, "reg")) {
fdt_get_address(prop->node->parent, prop->value, &scan->reg);
}
}
static void uart_done(const struct fdt_scan_node *node, void *extra)
{
struct uart_scan *scan = (struct uart_scan *)extra;
if (!scan->compat || !scan->reg || uart) return;
uart = (void*)(uintptr_t)scan->reg;
uart[UART_REG_TXCTRL] = UART_TXEN;
uart[UART_REG_RXCTRL] = UART_RXEN;
}
prop()
では、Device Tree内にUARTを示す記述が存在するかどうかをチェックしているようだ。最後にuart[...]
で何かの設定をしている。uart
はグローバル変数として定義されている。
volatile uint32_t* uart;
compatible
の意味は良く分からない。reg
が指定してあると、割り当てられているアドレスを探索するようだ。
const uint32_t *fdt_get_address(const struct fdt_scan_node *node, const uint32_t *value, uint64_t *result)
{
*result = 0;
for (int cells = node->address_cells; cells > 0; --cells)
*result = (*result << 32) + bswap(*value++);
return value;
}
おまけ Proxy Kernelのグローバル変数の配置領域
000000008000c000 <fds>:
000000008000c400 <magic_mem.2181>:
000000008000d000 <stacks>:
0000000080015000 <hart_phandles>:
0000000080015020 <lock.2182>:
0000000080015028 <free_pages>:
0000000080015030 <next_free_page>:
0000000080015038 <first_free_page>:
0000000080015040 <vmrs>:
0000000080015048 <vm_lock>:
0000000080015050 <htif_lock>:
0000000080015058 <disabled_hart_mask>:
0000000080015060 <current>:
00000000800150d0 <first_free_paddr>:
00000000800150d8 <plic_ndevs>:
00000000800150e0 <plic_priorities>:
00000000800150e8 <mem_size>:
00000000800150f0 <mtime>:
00000000800150f8 <root_page_table>:
0000000080015100 <htif>:
0000000080015108 <htif_console_buf>:
0000000080015110 <uart>:
0000000080015118 <finisher>:
0000000080015120 <hart_mask>:
おまけ Spikeが生成するDevice Tree Source
$ spike --dump-dts hoge
/dts-v1/;
/ {
#address-cells = <2>;
#size-cells = <2>;
compatible = "ucbbar,spike-bare-dev";
model = "ucbbar,spike-bare";
cpus {
#address-cells = <1>;
#size-cells = <0>;
timebase-frequency = <10000000>;
CPU0: cpu@0 {
device_type = "cpu";
reg = <0>;
status = "okay";
compatible = "riscv";
riscv,isa = "rv64imafdc";
mmu-type = "riscv,sv48";
clock-frequency = <1000000000>;
CPU0_intc: interrupt-controller {
#interrupt-cells = <1>;
interrupt-controller;
compatible = "riscv,cpu-intc";
};
};
};
memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x80000000>;
};
soc {
#address-cells = <2>;
#size-cells = <2>;
compatible = "ucbbar,spike-bare-soc", "simple-bus";
ranges;
clint@2000000 {
compatible = "riscv,clint0";
interrupts-extended = <&CPU0_intc 3 &CPU0_intc 7 >;
reg = <0x0 0x2000000 0x0 0xc0000>;
};
};
htif {
compatible = "ucb,htif0";
};
};