FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages

RISC-V SpikeシミュレータでC/C++のprintfを実現する仕組み (4. RISC-Vのプログラムロード)

Hello Worldのプログラムを動かしながら、RISC-V Spikeシミュレータのログを追っていき、RISC-Vのブートシーケンスを追っていく、その2。 今回はRISC-Vプログラムのロード部分。

f:id:msyksphinz:20180614003059p:plain

parse_args()により関数をコールを行う。

  • riscv-pk/pk/pk.c
static size_t parse_args(arg_buf* args)
{
  long r = frontend_syscall(SYS_getmainvars, va2pa(args), sizeof(*args), 0, 0, 0, 0, 0);
  kassert(r == 0);
  uint64_t* pk_argv = &args->buf[1];
  // pk_argv[0] is the proxy kernel itself.  skip it and any flags.
  size_t pk_argc = args->buf[0], arg = 1;
  for ( ; arg < pk_argc && *(char*)(uintptr_t)pk_argv[arg] == '-'; arg++)
    handle_option((const char*)(uintptr_t)pk_argv[arg]);

  for (size_t i = 0; arg + i < pk_argc; i++)
    args->argv[i] = (char*)(uintptr_t)pk_argv[arg + i];
  return pk_argc - arg;
}

frontend_syscall()は、フロントエンドに対してシステムコールを要求する。 - riscv-pk/pk/frondend.c

long frontend_syscall(long n, uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6)
{
  static volatile uint64_t magic_mem[8];

  static spinlock_t lock = SPINLOCK_INIT;
  spinlock_lock(&lock);

  magic_mem[0] = n;
  magic_mem[1] = a0;
  magic_mem[2] = a1;
  magic_mem[3] = a2;
  magic_mem[4] = a3;
  magic_mem[5] = a4;
  magic_mem[6] = a5;
  magic_mem[7] = a6;

  htif_syscall((uintptr_t)magic_mem);

  long ret = magic_mem[0];

  spinlock_unlock(&lock);
  return ret;
}

htif_syscallはフロントエンドに対してシステムコールを行う。 - riscv-pk/machine/htif.c

void htif_syscall(uintptr_t arg)
{
  do_tohost_fromhost(0, 0, arg);
}

static void do_tohost_fromhost(uintptr_t dev, uintptr_t cmd, uintptr_t data)
{
  spinlock_lock(&htif_lock);
    __set_tohost(dev, cmd, data);

    while (1) {
      uint64_t fh = fromhost;
      if (fh) {
        if (FROMHOST_DEV(fh) == dev && FROMHOST_CMD(fh) == cmd) {
          fromhost = 0;
          break;
        }
        __check_fromhost();
      }
    }
  spinlock_unlock(&htif_lock);
}

tohostfromhostpk.dmpに以下のアドレスで定義されている。 - pk.dmp

000000008000a000 <fromhost>:
000000008000a008 <tohost>:

SpikeシミュレータにおけるHTIFの扱い

riscv-fesvr/fesvr/htif.cc に、Spikeでインスタンスされるhtifの型が定義されている。 htif.ccにおいて、プログラムをロードする時にtohostfromhostのアドレスを定義している。

  • riscv-fesvr/fesvr/htif.cc
void htif_t::load_program()
{
...
  std::map<std::string, uint64_t> symbols = load_elf(path.c_str(), &mem, &entry);

  if (symbols.count("tohost") && symbols.count("fromhost")) {
    tohost_addr = symbols["tohost"];
    fromhost_addr = symbols["fromhost"];
  } else {
    fprintf(stderr, "warning: tohost and fromhost symbols not in ELF; can't communicate with target\n");
  }

load_elf()

load_elf()の実装はriscv-fesvr/fesvr/elfloader.cc に実装されている。これは特に特筆するものはなく、ひたすらelfをロードしているだけである。

  • riscv-fesvr/fesvr/elfloader.cc
std::map<std::string, uint64_t> load_elf(const char* fn, memif_t* memif, reg_t* entry)
{
  int fd = open(fn, O_RDONLY);
  struct stat s;
  assert(fd != -1);
  if (fstat(fd, &s) < 0)
    abort();
  size_t size = s.st_size;

  char* buf = (char*)mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
  assert(buf != MAP_FAILED);
  close(fd);

  assert(size >= sizeof(Elf64_Ehdr));
  const Elf64_Ehdr* eh64 = (const Elf64_Ehdr*)buf;
  assert(IS_ELF32(*eh64) || IS_ELF64(*eh64));
...

  if (IS_ELF32(*eh64))
    LOAD_ELF(Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr, Elf32_Sym);
  else
    LOAD_ELF(Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr, Elf64_Sym);

  munmap(buf, size);

  return symbols;
}