FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

freedom-u-sdkのLinuxを立ち上げながらLinuxのブートプロセスを学ぶ(3. printmにより文字が画面に表示されるまでの仕組み)

私の開発したRISC-VシミュレータはLinuxを立ち上げることができる。シミュレータのデバッグ時には相当中身を読み込んだのだが、きちんと文章化していない挙句、大昔のプロジェクトなのでもう忘れかけている。

Linuxのブートの方法から各種プロセスの取り扱いまで、思い出しながらRISC-Vシミュレータを動かしていき、ちゃんと文章化しておきたいと思った。

printmにより文字が画面に表示されるまでの仕組み

init_first_hart()内に、画面表示を行うためのprintm()を呼び出す場所がある。

  • freedom-u-sdk/riscv-pk/machine/mtrap.c
void init_first_hart(uintptr_t hartid, uintptr_t dtb)
{
  // Confirm console as early as possible
  query_uart(dtb);
  query_uart16550(dtb);
  query_htif(dtb);
  printm("bbl loader\r\n");
...
  • freedom-u-sdk/riscv-pk/machine/mtrap.c
void printm(const char* s, ...)
{
  va_list vl;

  va_start(vl, s);
  vprintm(s, vl);
  va_end(vl);
}

vprintmではputstring()を呼び出している。どうやらこれが画面に出力する機能のようだ。

  • freedom-u-sdk/riscv-pk/machine/mtrap.c
void vprintm(const char* s, va_list vl)
{
  char buf[256];
  vsnprintf(buf, sizeof buf, s, vl);
  putstring(buf);
}
  • freedom-u-sdk/riscv-pk/machine/mtrap.c
void putstring(const char* s)
{
  while (*s)
    mcall_console_putchar(*s++);
}

文字を1文字ずつmcall_console_putchar(char c)に送り込んでいることが分かる。

static uintptr_t mcall_console_putchar(uint8_t ch)
{
  if (uart) {
    uart_putchar(ch);
  } else if (uart16550) {
    uart16550_putchar(ch);
  } else if (htif) {
    htif_console_putchar(ch);
  }
  return 0;
}

見つかったデバイスに応じて呼び出す関数を変えている。今回の場合はhtifが見つかっているので、htif_console_putchar()を呼び出している。

htifを使うルーチンは、freedom-u-sdk/riscv-pk/machine/htif.cに纏められている。

  • freedom-u-sdk/riscv-pk/machine/htif.c
void htif_console_putchar(uint8_t ch)
{
  spinlock_lock(&htif_lock);
    __set_tohost(1, 1, ch);
  spinlock_unlock(&htif_lock);
}

__set_tohostというのは、TOHOSTアドレスマップに対して値を書き込む。tohostはアドレスマップしてあり0x80007008にマップされている。これは特殊なアドレスマップであり、この領域へのアクセスはシミュレータにより特殊な処理が実施される。

Disassembly of section .htif:

0000000080007000 <fromhost>:
    ...

0000000080007008 <tohost>:
    ...

__set_tohost()を観察すると、tohostアドレスの値が0になるまで待つ(tohost)の値が1である場合、これはまだ何かしらの処理を実施しているという意味である。tohostアドレスがReadyになると、TOHOST_CMDによりコマンドを生成して書き込みを行う。

  • freedom-u-sdk/riscv-pk/machine/htif.c
static void __set_tohost(uintptr_t dev, uintptr_t cmd, uintptr_t data)
{
  while (tohost)
    __check_fromhost();
  tohost = TOHOST_CMD(dev, cmd, data);
}
  • freedom-u-sdk/riscv-pk/machine/htif.h
#if __riscv_xlen == 64
# define TOHOST_CMD(dev, cmd, payload) \
  (((uint64_t)(dev) << 56) | ((uint64_t)(cmd) << 48) | (uint64_t)(payload))
#else
# define TOHOST_CMD(dev, cmd, payload) ({ \
  if ((dev) || (cmd)) __builtin_trap(); \
  (payload); })
#endif

この後はシミュレータにその処理が任される。riscv-isa-simではriscv-fesvr(フロントエンドサーバ)によりtohostへの書き込みが検出され、シグナルに応じて処理が実行される。今回は__set_tohost(1,1,ch)なのでデバイスID=1, コマンド=1となる。

  • freedom-u-sdk/riscv-fesvr/fesvr/htif.cc
int htif_t::run()
{
  start();
...
  if (tohost_addr == 0) {
    while (true)
      idle();
  }

  while (!signal_exit && exitcode == 0)
  {
    if (auto tohost = mem.read_uint64(tohost_addr)) {
      mem.write_uint64(tohost_addr, 0);
      command_t cmd(this, tohost, fromhost_callback);
      device_list.handle_command(cmd);
    } else {
      idle();
    }
...
  • freedom-u-sdk/riscv-fesvr/fesvr/device.cc
void device_t::handle_command(command_t cmd)
{
  command_handlers[cmd.cmd()](cmd);
}

バイスとコマンドはhtifインスタンス時に構成させる。

  • freedom-u-sdk/riscv-fesvr/fesvr/htif.cc
htif_t::htif_t(const std::vector<std::string>& args)
  : mem(this), entry(DRAM_BASE), sig_addr(0), sig_len(0),
    tohost_addr(0), fromhost_addr(0), exitcode(0), stopped(false),
    syscall_proxy(this)
{
  signal(SIGINT, &handle_signal);
...
  device_list.register_device(&syscall_proxy);
  device_list.register_device(&bcd);
  for (auto d : dynamic_devices)
    device_list.register_device(d);
}

bcdが入出力を管理するためのデバイスだ。2番目に登録されたのでデバイスID=1となっている。

  • freedom-u-sdk/riscv-fesvr/fesvr/device.cc
bcd_t::bcd_t()
{
  register_command(0, std::bind(&bcd_t::handle_read, this, _1), "read");
  register_command(1, std::bind(&bcd_t::handle_write, this, _1), "write");
}

書き込みを行った場合はターミナルへの出力が行われる。これで画面出力の完成だ。

  • freedom-u-sdk/riscv-fesvr/fesvr/device.cc
void bcd_t::handle_read(command_t cmd)
{
  pending_reads.push(cmd);
}

void bcd_t::handle_write(command_t cmd)
{
  canonical_terminal_t::write(cmd.payload());
}
  • freedom-u-sdk/riscv-fesvr/fesvr/term.cc
void canonical_terminal_t::write(char ch)
{
  if (::write(1, &ch, 1) != 1)
    abort();
}