diff --git a/src/devices/plic.c b/src/devices/plic.c index b1462cea..a300c3c5 100644 --- a/src/devices/plic.c +++ b/src/devices/plic.c @@ -89,3 +89,8 @@ void plic_delete(plic_t *plic) { free(plic); } + +void plic_reset(plic_t *plic) +{ + memset(plic, 0, sizeof(plic_t)); +} diff --git a/src/devices/plic.h b/src/devices/plic.h index 2385db66..98175403 100644 --- a/src/devices/plic.h +++ b/src/devices/plic.h @@ -39,3 +39,6 @@ plic_t *plic_new(); /* delete a PLIC instance */ void plic_delete(plic_t *plic); + +/* reset a PLIC instance */ +void plic_reset(plic_t *plic); diff --git a/src/devices/uart.c b/src/devices/uart.c index acf27108..eea1e17d 100644 --- a/src/devices/uart.c +++ b/src/devices/uart.c @@ -197,3 +197,8 @@ void u8250_delete(u8250_state_t *uart) { free(uart); } + +void u8250_reset(u8250_state_t *uart) +{ + memset(uart, 0, sizeof(u8250_state_t)); +} diff --git a/src/devices/uart.h b/src/devices/uart.h index 50a477a1..ecbb837c 100644 --- a/src/devices/uart.h +++ b/src/devices/uart.h @@ -49,3 +49,6 @@ u8250_state_t *u8250_new(); /* delete a UART instance */ void u8250_delete(u8250_state_t *uart); + +/* reset a UART instance */ +void u8250_reset(u8250_state_t *uart); diff --git a/src/devices/virtio-blk.c b/src/devices/virtio-blk.c index 47841209..69420e3e 100644 --- a/src/devices/virtio-blk.c +++ b/src/devices/virtio-blk.c @@ -403,8 +403,12 @@ uint32_t *virtio_blk_init(virtio_blk_state_t *vblk, vblk->disk_fd = -1; /* Allocate memory for the private member */ - vblk->priv = calloc(1, sizeof(struct virtio_blk_config)); - assert(vblk->priv); + if (vblk->priv) { /* check for reboot */ + memset(vblk->priv, 0, sizeof(struct virtio_blk_config)); + } else { + vblk->priv = calloc(1, sizeof(struct virtio_blk_config)); + assert(vblk->priv); + } /* No disk image is provided */ if (!disk_file) { diff --git a/src/emulate.c b/src/emulate.c index dafead5c..29a357f2 100644 --- a/src/emulate.c +++ b/src/emulate.c @@ -1098,7 +1098,6 @@ static void rv_check_interrupt(riscv_t *rv) vm_attr_t *attr = PRIV(rv); if (peripheral_update_ctr-- == 0) { peripheral_update_ctr = 64; - #if defined(__EMSCRIPTEN__) escape_seq: #endif @@ -1148,7 +1147,24 @@ void rv_step(void *arg) /* find or translate a block for starting PC */ const uint64_t cycles_target = rv->csr_cycle + cycles; - /* loop until hitting the cycle target */ +#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) + /* Set up the jump point for handling reboots */ + if (setjmp(rv->reboot_jmp) != 0) { + /* longjmp to here after a reboot happens */ +#if !RV32_HAS(JIT) + need_clear_block_map = false; +#endif + is_branch_taken = false; + reloc_enable_mmu_jalr_addr = 0; + reloc_enable_mmu = false; + need_retranslate = false; + need_handle_signal = false; + prev = NULL; + last_pc = 0; + } +#endif + + /* loop until hitting the cycle target or hart is halted */ while (rv->csr_cycle < cycles_target && !rv->halt) { #if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) /* check for any interrupt after every block emulation */ diff --git a/src/riscv.c b/src/riscv.c index b892cf27..66891241 100644 --- a/src/riscv.c +++ b/src/riscv.c @@ -224,7 +224,7 @@ static void *t2c_runloop(void *arg) #endif #if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) -static void map_file(char **ram_loc, const char *name) +void map_file(char **ram_loc, const char *name) { int fd = open(name, O_RDONLY); if (fd < 0) @@ -289,7 +289,7 @@ static char *realloc_property(char *fdt, return fdt; } -static void load_dtb(char **ram_loc, vm_attr_t *attr) +void load_dtb(char **ram_loc, vm_attr_t *attr) { #include "minimal_dtb.h" char *bootargs = attr->data.system.bootargs; @@ -524,283 +524,7 @@ riscv_t *rv_create(riscv_user_t rv_attr) /* copy over the attr */ rv->data = rv_attr; - vm_attr_t *attr = PRIV(rv); - attr->mem = memory_new(attr->mem_size); - assert(attr->mem); - assert(!(((uintptr_t) attr->mem) & 0b11)); - - /* reset */ - rv_reset(rv, 0U); - - /* - * default standard stream. - * rv_remap_stdstream() can be called to overwrite them - * - * The logging stdout stream will be remapped as well - * - */ - attr->fd_map = map_init(int, FILE *, map_cmp_int); - rv_remap_stdstream(rv, - (fd_stream_pair_t[]){ - {STDIN_FILENO, stdin}, - {STDOUT_FILENO, stdout}, - {STDERR_FILENO, stderr}, - }, - 3); - - rv_log_set_level(attr->log_level); - rv_log_info("Log level: %s", rv_log_level_string(attr->log_level)); - -#if !RV32_HAS(SYSTEM) || (RV32_HAS(SYSTEM) && RV32_HAS(ELF_LOADER)) - elf_t *elf = elf_new(); - assert(elf); - - if (!elf_open(elf, attr->data.user.elf_program)) { - rv_log_fatal("elf_open() failed"); - map_delete(attr->fd_map); - memory_delete(attr->mem); - free(rv); - exit(EXIT_FAILURE); - } - rv_log_info("%s ELF loaded", attr->data.user.elf_program); - - const struct Elf32_Sym *end; - if ((end = elf_get_symbol(elf, "_end"))) - attr->break_addr = end->st_value; - -#if !RV32_HAS(SYSTEM) - /* set not exiting */ - attr->on_exit = false; - - const struct Elf32_Sym *exit; - if ((exit = elf_get_symbol(elf, "exit"))) - attr->exit_addr = exit->st_value; -#endif - - assert(elf_load(elf, attr->mem)); - - /* set the entry pc */ - const struct Elf32_Ehdr UNUSED *hdr = get_elf_header(elf); - assert(rv_set_pc(rv, hdr->e_entry)); - - elf_delete(elf); - -/* combine with USE_ELF for system test suite */ -#if RV32_HAS(SYSTEM) - /* this variable has external linkage to mmu_io defined in system.c */ - extern riscv_io_t mmu_io; - /* install the MMU I/O handlers */ - memcpy(&rv->io, &mmu_io, sizeof(riscv_io_t)); -#else - /* install the I/O handlers */ - const riscv_io_t io = { - /* memory read interface */ - .mem_ifetch = MEMIO(ifetch), - .mem_read_w = MEMIO(read_w), - .mem_read_s = MEMIO(read_s), - .mem_read_b = MEMIO(read_b), - - /* memory write interface */ - .mem_write_w = MEMIO(write_w), - .mem_write_s = MEMIO(write_s), - .mem_write_b = MEMIO(write_b), - - /* system services or essential routines */ - .on_ecall = ecall_handler, - .on_ebreak = ebreak_handler, - .on_memcpy = memcpy_handler, - .on_memset = memset_handler, - .on_trap = trap_handler, - }; - memcpy(&rv->io, &io, sizeof(riscv_io_t)); -#endif /* RV32_HAS(SYSTEM) */ - -#else - /* *-----------------------------------------* - * | Memory layout | - * *----------------*----------------*-------* - * | kernel image | initrd image | dtb | - * *----------------*----------------*-------* - */ - - /* load_dtb needs the count to add the virtio block subnode dynamically */ - attr->vblk_cnt = attr->data.system.vblk_device_cnt; - - char *ram_loc = (char *) attr->mem->mem_base; - map_file(&ram_loc, attr->data.system.kernel); - rv_log_info("Kernel loaded"); - - uint32_t dtb_addr = attr->mem->mem_size - DTB_SIZE; - ram_loc = ((char *) attr->mem->mem_base) + dtb_addr; - load_dtb(&ram_loc, attr); - rv_log_info("DTB loaded"); - /* - * Load optional initrd image at last 8 MiB before the dtb region to - * prevent kernel from overwritting it - */ - if (attr->data.system.initrd) { - uint32_t initrd_addr = dtb_addr - INITRD_SIZE; - ram_loc = ((char *) attr->mem->mem_base) + initrd_addr; - map_file(&ram_loc, attr->data.system.initrd); - rv_log_info("Rootfs loaded"); - } - - /* this variable has external linkage to mmu_io defined in system.c */ - extern riscv_io_t mmu_io; - memcpy(&rv->io, &mmu_io, sizeof(riscv_io_t)); - - /* setup RISC-V hart */ - rv_set_reg(rv, rv_reg_a0, 0); - rv_set_reg(rv, rv_reg_a1, dtb_addr); - - /* setup timer */ - attr->timer = 0xFFFFFFFFFFFFFFF; - - /* setup PLIC */ - attr->plic = plic_new(); - assert(attr->plic); - attr->plic->rv = rv; - - /* setup UART */ - attr->uart = u8250_new(); - assert(attr->uart); - attr->uart->in_fd = attr->fd_stdin; - attr->uart->out_fd = attr->fd_stdout; - - /* setup virtio-blk */ - attr->vblk_mmio_base_hi = 0x41; - attr->vblk_mmio_max_hi = attr->vblk_mmio_base_hi + attr->vblk_cnt; - - attr->vblk = malloc(sizeof(virtio_blk_state_t *) * attr->vblk_cnt); - assert(attr->vblk); - attr->disk = malloc(sizeof(uint32_t *) * attr->vblk_cnt); - assert(attr->disk); - - if (attr->vblk_cnt) { - for (int i = 0; i < attr->vblk_cnt; i++) { -/* Currently, only used for block image path and permission */ -#define MAX_OPTS 2 - char *vblk_device_str = attr->data.system.vblk_device[i]; - if (!vblk_device_str[0]) { - rv_log_error("Disk path cannot be empty"); - exit(EXIT_FAILURE); - } - - char *vblk_opts[MAX_OPTS] = {NULL}; - int vblk_opt_idx = 0; - char *opt = strtok(vblk_device_str, ","); - while (opt) { - if (vblk_opt_idx == MAX_OPTS) { - rv_log_error("Too many arguments for vblk"); - break; - } - vblk_opts[vblk_opt_idx++] = opt; - opt = strtok(NULL, ","); - } - - char *vblk_device; - char *vblk_readonly = vblk_opts[1]; - bool readonly = false; - - if (vblk_opts[0][0] == '~') { - /* HOME environment variable should be common in macOS and Linux - * distribution and it is set by the login program - */ - const char *home = getenv("HOME"); - if (!home) { - rv_log_error( - "HOME environment variable is not set, cannot access " - "the disk %s", - vblk_opts[0]); - exit(EXIT_FAILURE); - } - - vblk_device = malloc(strlen(vblk_opts[0]) - 1 /* skip ~ */ + - strlen(home) + 1); - assert(vblk_device); - - strcpy(vblk_device, home); - strcat(vblk_device, vblk_opts[0] + 1 /* skip ~ */); - } else { - vblk_device = vblk_opts[0]; - } - - if (vblk_readonly) { - if (strcmp(vblk_readonly, "readonly") != 0) { - rv_log_error("Unknown vblk option: %s", vblk_readonly); - exit(EXIT_FAILURE); - } - readonly = true; - } - - attr->vblk[i] = vblk_new(); - attr->vblk[i]->ram = (uint32_t *) attr->mem->mem_base; - attr->disk[i] = - virtio_blk_init(attr->vblk[i], vblk_device, readonly); - - if (vblk_opts[0][0] == '~') - free(vblk_device); - } - } - - capture_keyboard_input(); -#endif /* !RV32_HAS(SYSTEM) || (RV32_HAS(SYSTEM) && RV32_HAS(ELF_LOADER)) */ - - /* create block and IRs memory pool */ - rv->block_mp = mpool_create(sizeof(block_t) << BLOCK_MAP_CAPACITY_BITS, - sizeof(block_t)); - rv->block_ir_mp = mpool_create( - sizeof(rv_insn_t) << BLOCK_IR_MAP_CAPACITY_BITS, sizeof(rv_insn_t)); - -#if !RV32_HAS(JIT) - /* initialize the block map */ - block_map_init(&rv->block_map, BLOCK_MAP_CAPACITY_BITS); -#else - INIT_LIST_HEAD(&rv->block_list); - rv->jit_state = jit_state_init(CODE_CACHE_SIZE); - if (!rv->jit_state) { - rv_log_fatal("Failed to initialize JIT state"); - goto fail_jit_state; - } - rv->block_cache = cache_create(BLOCK_MAP_CAPACITY_BITS); - if (!rv->block_cache) { - rv_log_fatal("Failed to create block cache"); - goto fail_block_cache; - } -#if RV32_HAS(T2C) - rv->quit = false; - rv->jit_cache = jit_cache_init(); - if (!rv->jit_cache) { - rv_log_fatal("Failed to initialize JIT cache"); - goto fail_jit_cache; - } - /* prepare wait queue. */ - pthread_mutex_init(&rv->wait_queue_lock, NULL); - pthread_mutex_init(&rv->cache_lock, NULL); - INIT_LIST_HEAD(&rv->wait_queue); - /* activate the background compilation thread. */ - pthread_create(&t2c_thread, NULL, t2c_runloop, rv); -#endif -#endif - - return rv; - -#if RV32_HAS(JIT) -#if RV32_HAS(T2C) -fail_jit_cache: - cache_free(rv->block_cache); -#endif -fail_block_cache: - if (rv->jit_state) - jit_state_exit(rv->jit_state); -fail_jit_state: - mpool_destroy(rv->block_ir_mp); - mpool_destroy(rv->block_mp); - map_delete(attr->fd_map); - memory_delete(attr->mem); - free(rv); - return NULL; -#endif + return rv_cold_reboot(rv, 0U) ? rv : NULL; } #if !RV32_HAS(SYSTEM) || (RV32_HAS(SYSTEM) && RV32_HAS(ELF_LOADER)) @@ -881,66 +605,115 @@ void rv_halt(riscv_t *rv) rv->halt = true; } -bool rv_has_halted(riscv_t *rv) +/* Common hart reset logic shared by cold and warm reboot */ +static void rv_reset_hart(riscv_t *rv, riscv_word_t pc) { - return rv->halt; -} + /* Reset general-purpose registers */ + memset(rv->X, 0, sizeof(uint32_t) * N_RV_REGS); -#if RV32_HAS(ARCH_TEST) -void rv_set_tohost_addr(riscv_t *rv, uint32_t addr) -{ - rv->tohost_addr = addr; -} + /* Reset timer */ + rv->timer = 0; -void rv_set_fromhost_addr(riscv_t *rv, uint32_t addr) -{ - rv->fromhost_addr = addr; -} + /* Reset privilege mode */ +#if RV32_HAS(SYSTEM) + /* + * System simulation defaults to S-mode as + * it does not rely on M-mode software like OpenSBI. + */ + rv->priv_mode = RV_PRIV_S_MODE; +#else + /* ISA simulation defaults to M-mode */ + rv->priv_mode = RV_PRIV_M_MODE; #endif -void rv_delete(riscv_t *rv) -{ - assert(rv); -#if !RV32_HAS(JIT) || (RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)) - vm_attr_t *attr = PRIV(rv); +#if RV32_HAS(SYSTEM) + /* Not being trap */ + rv->is_trapped = false; + + /* Reset CSRs (for full system emulation) */ + rv->csr_cycle = 0; + rv->csr_time[0] = 0; + rv->csr_time[1] = 0; + rv->csr_mstatus = 0; + rv->csr_mtvec = 0; + rv->csr_misa = 0; + rv->csr_mtval = 0; + rv->csr_mcause = 0; + rv->csr_mscratch = 0; + rv->csr_mepc = 0; + rv->csr_mip = 0; + rv->csr_mie = 0; + rv->csr_mideleg = 0; + rv->csr_medeleg = 0; + rv->csr_mvendorid = 0; + rv->csr_marchid = 0; + rv->csr_mimpid = 0; + rv->csr_mbadaddr = 0; + + rv->csr_sstatus = 0; + rv->csr_stvec = 0; + rv->csr_sip = 0; + rv->csr_sie = 0; + rv->csr_scounteren = 0; + rv->csr_sscratch = 0; + rv->csr_sepc = 0; + rv->csr_scause = 0; + rv->csr_stval = 0; + rv->csr_satp = 0; #endif -#if !RV32_HAS(JIT) - map_delete(attr->fd_map); - memory_delete(attr->mem); - block_map_destroy(rv); + + /* Reset CSRs (for bare metal system emulation) */ + rv->csr_mtvec = 0; + rv->csr_cycle = 0; + rv->csr_mstatus = 0; + rv->csr_misa |= MISA_SUPER | MISA_USER; + rv->csr_mvendorid = RV_MVENDORID; + rv->csr_marchid = RV_MARCHID; + rv->csr_mimpid = RV_MIMPID; +#if !RV32_HAS(RV32E) + rv->csr_misa |= MISA_I; #else -#if RV32_HAS(T2C) - rv->quit = true; - pthread_join(t2c_thread, NULL); - pthread_mutex_destroy(&rv->wait_queue_lock); - pthread_mutex_destroy(&rv->cache_lock); - jit_cache_exit(rv->jit_cache); + rv->csr_misa |= MISA_E; #endif - jit_state_exit(rv->jit_state); - cache_free(rv->block_cache); +#if RV32_HAS(EXT_A) + rv->csr_misa |= MISA_A; #endif -#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) - u8250_delete(attr->uart); - plic_delete(attr->plic); - /* sync device, cleanup inside the callee */ - rv_fsync_device(); +#if RV32_HAS(EXT_C) + rv->csr_misa |= MISA_C; +#endif +#if RV32_HAS(EXT_F) + rv->csr_misa |= MISA_F; + /* Reset float registers */ + for (int i = 0; i < N_RV_REGS; i++) + rv->F[i].v = 0; + rv->csr_fcsr = 0; +#endif +#if RV32_HAS(EXT_M) + rv->csr_misa |= MISA_M; #endif - free(rv); -} -void rv_reset(riscv_t *rv, riscv_word_t pc) -{ - assert(rv); - memset(rv->X, 0, sizeof(uint32_t) * N_RV_REGS); + /* Not being halted */ + rv->halt = false; + + /* Clear block cache - translated blocks are stale after reset */ +#if !RV32_HAS(JIT) + block_map_clear(rv); +#else + /* For JIT, we need to clear the cache as well */ + /* TODO: implement cache clearing for JIT mode */ +#endif + + /* Set the reset address */ + rv->PC = pc; +#if !RV32_HAS(SYSTEM) || (RV32_HAS(SYSTEM) && RV32_HAS(ELF_LOADER)) + /* Setup argc/argv for ELF programs */ vm_attr_t *attr = PRIV(rv); + int argc = attr->argc; char **args = attr->argv; memory_t *mem = attr->mem; - /* set the reset address */ - rv->PC = pc; - /* set the default stack pointer */ rv->X[rv_reg_sp] = attr->mem_size - attr->stack_size - attr->args_offset_size; @@ -1033,53 +806,374 @@ void rv_reset(riscv_t *rv, riscv_word_t pc) /* reset sp pointing to argc */ rv->X[rv_reg_sp] = stack_top; +#endif +} + +bool rv_has_halted(riscv_t *rv) +{ + return rv->halt; +} + +void rv_delete(riscv_t *rv) +{ + assert(rv); +#if !RV32_HAS(JIT) || (RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)) + vm_attr_t *attr = PRIV(rv); +#endif +#if !RV32_HAS(JIT) + map_delete(attr->fd_map); + memory_delete(attr->mem); + block_map_destroy(rv); +#else +#if RV32_HAS(T2C) + rv->quit = true; + pthread_join(t2c_thread, NULL); + pthread_mutex_destroy(&rv->wait_queue_lock); + pthread_mutex_destroy(&rv->cache_lock); + jit_cache_exit(rv->jit_cache); +#endif + jit_state_exit(rv->jit_state); + cache_free(rv->block_cache); +#endif +#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) + u8250_delete(attr->uart); + plic_delete(attr->plic); + /* sync device, cleanup inside the callee */ + rv_fsync_device(); +#endif + free(rv); +} + +#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) +/* + * Load kernel image, DTB, and initrd into memory. + * Sets attr->dtb_addr for setting up hart registers. + */ +static void load_boot_images(vm_attr_t *attr) +{ + /* Load kernel at the beginning of memory */ + char *ram_loc = (char *) attr->mem->mem_base; + map_file(&ram_loc, attr->data.system.kernel); + rv_log_info("Kernel loaded"); + + /* Load DTB at the end of memory */ + attr->dtb_addr = attr->mem->mem_size - DTB_SIZE; + ram_loc = ((char *) attr->mem->mem_base) + attr->dtb_addr; + load_dtb(&ram_loc, attr); + rv_log_info("DTB loaded"); + + /* Load optional initrd image before DTB region */ + if (attr->data.system.initrd) { + uint32_t initrd_addr = attr->dtb_addr - INITRD_SIZE; + ram_loc = ((char *) attr->mem->mem_base) + initrd_addr; + map_file(&ram_loc, attr->data.system.initrd); + rv_log_info("Rootfs loaded"); + } +} + +/* Clear memory, load kernel/DTB/initrd and reset the hart. + * + * The "warm" aspect is that skipping peripheral + * reinitialization for faster reboot. + */ +void rv_warm_reboot(riscv_t *rv, riscv_word_t pc) +{ + rv_log_info("Warm reboot: reloading kernel (skipping peripheral reset)"); + assert(rv); + + vm_attr_t *attr = PRIV(rv); + + /* clear memory */ + memory_fill(attr->mem, 0, attr->mem->mem_size, 0); + + /* reload kernel, DTB, and initrd */ + load_boot_images(attr); + + /* Reset hart state (general purpose registers, CSRs, PC, argc/argv, etc.) + */ + rv_reset_hart(rv, pc); + + /* Setup timer */ + attr->timer = 0xFFFFFFFFFFFFFFF; + + /* setup RISC-V hart */ + rv_set_reg(rv, rv_reg_a0, 0); + rv_set_reg(rv, rv_reg_a1, attr->dtb_addr); +} +#endif /* RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) */ + +bool rv_cold_reboot(riscv_t *rv, riscv_word_t pc) +{ + rv_log_info("Resetting..."); + + assert(rv); + vm_attr_t *attr = PRIV(rv); + + /* Initialize memory */ + if (attr->mem) /* check for reboot */ + memory_fill(attr->mem, 0, attr->mem->mem_size, 0); + else { + attr->mem = memory_new(attr->mem_size); + assert(attr->mem); + assert(!(((uintptr_t) attr->mem) & 0b11)); + } + + /* Reset hart state (general purpose registers, CSRs, PC, argc/argv, etc.) + */ + rv_reset_hart(rv, pc); - /* reset privilege mode */ -#if RV32_HAS(SYSTEM) /* - * System simulation defaults to S-mode as - * it does not rely on M-mode software like OpenSBI. + * default standard stream. + * rv_remap_stdstream() can be called to overwrite them + * + * The logging stdout stream will be remapped as well + * */ - rv->priv_mode = RV_PRIV_S_MODE; + if (!attr->fd_map) { /* check for reboot */ + attr->fd_map = map_init(int, FILE *, map_cmp_int); + rv_remap_stdstream(rv, + (fd_stream_pair_t[]){ + {STDIN_FILENO, stdin}, + {STDOUT_FILENO, stdout}, + {STDERR_FILENO, stderr}, + }, + 3); + } - /* not being trapped */ - rv->is_trapped = false; -#else - /* ISA simulation defaults to M-mode */ - rv->priv_mode = RV_PRIV_M_MODE; + /* Log level */ + rv_log_set_level(attr->log_level); + rv_log_info("Log level: %s", rv_log_level_string(attr->log_level)); + +#if !RV32_HAS(SYSTEM) || (RV32_HAS(SYSTEM) && RV32_HAS(ELF_LOADER)) + elf_t *elf = elf_new(); + assert(elf); + + if (!elf_open(elf, attr->data.user.elf_program)) { + rv_log_fatal("elf_open() failed"); + map_delete(attr->fd_map); + memory_delete(attr->mem); + free(rv); + exit(EXIT_FAILURE); + } + rv_log_info("%s ELF loaded", attr->data.user.elf_program); + + const struct Elf32_Sym *end; + if ((end = elf_get_symbol(elf, "_end"))) + attr->break_addr = end->st_value; + +#if !RV32_HAS(SYSTEM) + /* set not exiting */ + attr->on_exit = false; + + const struct Elf32_Sym *exit; + if ((exit = elf_get_symbol(elf, "exit"))) + attr->exit_addr = exit->st_value; #endif - /* reset the csrs */ - rv->csr_mtvec = 0; - rv->csr_cycle = 0; - rv->csr_mstatus = 0; - rv->csr_misa |= MISA_SUPER | MISA_USER; - rv->csr_mvendorid = RV_MVENDORID; - rv->csr_marchid = RV_MARCHID; - rv->csr_mimpid = RV_MIMPID; -#if !RV32_HAS(RV32E) - rv->csr_misa |= MISA_I; + assert(elf_load(elf, attr->mem)); + + /* set the entry pc */ + const struct Elf32_Ehdr UNUSED *hdr = get_elf_header(elf); + assert(rv_set_pc(rv, hdr->e_entry)); + + elf_delete(elf); + +/* combine with USE_ELF for system test suite */ +#if RV32_HAS(SYSTEM) + /* this variable has external linkage to mmu_io defined in system.c */ + extern riscv_io_t mmu_io; + /* install the MMU I/O handlers */ + memcpy(&rv->io, &mmu_io, sizeof(riscv_io_t)); #else - rv->csr_misa |= MISA_E; -#endif -#if RV32_HAS(EXT_A) - rv->csr_misa |= MISA_A; + /* install the I/O handlers */ + const riscv_io_t io = { + /* memory read interface */ + .mem_ifetch = MEMIO(ifetch), + .mem_read_w = MEMIO(read_w), + .mem_read_s = MEMIO(read_s), + .mem_read_b = MEMIO(read_b), + + /* memory write interface */ + .mem_write_w = MEMIO(write_w), + .mem_write_s = MEMIO(write_s), + .mem_write_b = MEMIO(write_b), + + /* system services or essential routines */ + .on_ecall = ecall_handler, + .on_ebreak = ebreak_handler, + .on_memcpy = memcpy_handler, + .on_memset = memset_handler, + .on_trap = trap_handler, + }; + memcpy(&rv->io, &io, sizeof(riscv_io_t)); +#endif /* RV32_HAS(SYSTEM) */ + +#else + /* *-----------------------------------------* + * | Memory layout | + * *----------------*----------------*-------* + * | kernel image | initrd image | dtb | + * *----------------*----------------*-------* + */ + + /* load_dtb needs the count to add the virtio block subnode dynamically */ + attr->vblk_cnt = attr->data.system.vblk_device_cnt; + + /* Load kernel, DTB, and initrd */ + load_boot_images(attr); + + /* this variable has external linkage to mmu_io defined in system.c */ + extern riscv_io_t mmu_io; + memcpy(&rv->io, &mmu_io, sizeof(riscv_io_t)); + + /* setup RISC-V hart */ + rv_set_reg(rv, rv_reg_a0, 0); + rv_set_reg(rv, rv_reg_a1, attr->dtb_addr); + + /* setup timer */ + attr->timer = 0xFFFFFFFFFFFFFFF; + + /* setup PLIC */ + if (attr->plic) /* check for reboot */ + plic_reset(attr->plic); /* reset plic state */ + else { + attr->plic = plic_new(); + assert(attr->plic); + } + attr->plic->rv = rv; + + /* setup UART */ + if (attr->uart) /* check for reboot */ + u8250_reset(attr->uart); /* reset uart state */ + else { + attr->uart = u8250_new(); + assert(attr->uart); + } + attr->uart->in_fd = attr->fd_stdin; + attr->uart->out_fd = attr->fd_stdout; + + /* setup virtio-blk */ + attr->vblk_mmio_base_hi = 0x41; + attr->vblk_mmio_max_hi = attr->vblk_mmio_base_hi + attr->vblk_cnt; + + if (!attr->vblk) { /* check for reboot */ + attr->vblk = calloc(attr->vblk_cnt, sizeof(virtio_blk_state_t *)); + assert(attr->vblk); + attr->disk = calloc(attr->vblk_cnt, sizeof(uint32_t *)); + assert(attr->disk); + } + + if (attr->vblk_cnt) { + for (int i = 0; i < attr->vblk_cnt; i++) { +/* Currently, only used for block image path and permission */ +#define MAX_OPTS 2 + char *vblk_device_str = attr->data.system.vblk_device[i]; + char *vblk_opts[MAX_OPTS] = {NULL}; + int vblk_opt_idx = 0; + char *opt = strtok(vblk_device_str, ","); + while (opt) { + if (vblk_opt_idx == MAX_OPTS) { + rv_log_error("Too many arguments for vblk"); + break; + } + vblk_opts[vblk_opt_idx++] = opt; + opt = strtok(NULL, ","); + } + char *vblk_device = vblk_opts[0]; + char *vblk_readonly = vblk_opts[1]; + + bool readonly = false; + if (vblk_readonly) { + if (strcmp(vblk_readonly, "readonly") != 0) { + rv_log_error("Unknown vblk option: %s", vblk_readonly); + exit(EXIT_FAILURE); + } + readonly = true; + } + + if (attr->vblk[i]) { /* check for reboot */ + /* + * Reset virtio block state. + * + * The attr->vblk[i]->priv could be pre-allocated. + * When rebooting, the pre-allocated pointer could + * be reused. + * + * See virtio_blk_init in src/devices/virtio-blk.c + */ + void *tmp_priv = attr->vblk[i]->priv; + memset(attr->vblk[i], 0, sizeof(virtio_blk_state_t)); + attr->vblk[i]->priv = tmp_priv; + } else { + attr->vblk[i] = vblk_new(); + assert(attr->vblk[i]); + } + attr->vblk[i]->ram = (uint32_t *) attr->mem->mem_base; + attr->disk[i] = + virtio_blk_init(attr->vblk[i], vblk_device, readonly); + } + } + + capture_keyboard_input(); +#endif /* !RV32_HAS(SYSTEM) || (RV32_HAS(SYSTEM) && RV32_HAS(ELF_LOADER)) */ + + /* create block and IRs memory pool */ + rv->block_mp = mpool_create(sizeof(block_t) << BLOCK_MAP_CAPACITY_BITS, + sizeof(block_t)); + rv->block_ir_mp = mpool_create( + sizeof(rv_insn_t) << BLOCK_IR_MAP_CAPACITY_BITS, sizeof(rv_insn_t)); + +#if !RV32_HAS(JIT) + /* initialize the block map */ + if (!rv->block_map.map) { /* check for reboot */ + block_map_init(&rv->block_map, BLOCK_MAP_CAPACITY_BITS); + } +#else + INIT_LIST_HEAD(&rv->block_list); + rv->jit_state = jit_state_init(CODE_CACHE_SIZE); + if (!rv->jit_state) { + rv_log_fatal("Failed to initialize JIT state"); + goto fail_jit_state; + } + rv->block_cache = cache_create(BLOCK_MAP_CAPACITY_BITS); + if (!rv->block_cache) { + rv_log_fatal("Failed to create block cache"); + goto fail_block_cache; + } +#if RV32_HAS(T2C) + rv->quit = false; + rv->jit_cache = jit_cache_init(); + if (!rv->jit_cache) { + rv_log_fatal("Failed to initialize JIT cache"); + goto fail_jit_cache; + } + /* prepare wait queue. */ + pthread_mutex_init(&rv->wait_queue_lock, NULL); + pthread_mutex_init(&rv->cache_lock, NULL); + INIT_LIST_HEAD(&rv->wait_queue); + /* activate the background compilation thread. */ + pthread_create(&t2c_thread, NULL, t2c_runloop, rv); #endif -#if RV32_HAS(EXT_C) - rv->csr_misa |= MISA_C; #endif -#if RV32_HAS(EXT_F) - rv->csr_misa |= MISA_F; - /* reset float registers */ - for (int i = 0; i < N_RV_REGS; i++) - rv->F[i].v = 0; - rv->csr_fcsr = 0; + + return true; + +#if RV32_HAS(JIT) +#if RV32_HAS(T2C) +fail_jit_cache: + cache_free(rv->block_cache); #endif -#if RV32_HAS(EXT_M) - rv->csr_misa |= MISA_M; +fail_block_cache: + if (rv->jit_state) + jit_state_exit(rv->jit_state); +fail_jit_state: + mpool_destroy(rv->block_ir_mp); + mpool_destroy(rv->block_mp); + map_delete(attr->fd_map); + memory_delete(attr->mem); + free(rv); + return false; #endif - - rv->halt = false; } static const char *insn_name_table[] = { diff --git a/src/riscv.h b/src/riscv.h index c8e6cb53..1b10d90f 100644 --- a/src/riscv.h +++ b/src/riscv.h @@ -282,6 +282,11 @@ enum TRAP_CODE { /* Allows the supervisor to request system-level reboot or shutdown. */ #define SBI_EID_RST 0x53525354 #define SBI_RST_SYSTEM_RESET 0 +#define SHUTDOWN 0x0 +#define COLD_REBOOT 0x1 +#define WARM_REBOOT 0x2 +#define NO_REASON 0x0 +#define SYSTEM_FAILURE 0x1 #define BLOCK_MAP_CAPACITY_BITS 10 @@ -368,8 +373,19 @@ riscv_t *rv_create(riscv_user_t attr); /* delete a RISC-V emulator */ void rv_delete(riscv_t *rv); -/* reset the RISC-V processor */ -void rv_reset(riscv_t *rv, riscv_word_t pc); +/* cold reboot the system + * + * the first power on is considered as a cold reboot + * + * Reset the RISC-V processor, memory and peripheral + */ +bool rv_cold_reboot(riscv_t *rv, riscv_word_t pc); + +/* warm reboot the system + * + * Only reset the RISC-V processor and memory + */ +void rv_warm_reboot(riscv_t *rv, riscv_word_t pc); #if RV32_HAS(GDBSTUB) /* Run the RISC-V emulator as gdbstub */ @@ -570,6 +586,11 @@ typedef struct { /* SBI timer */ uint64_t timer; + +#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) + /* DTB address in memory */ + uint32_t dtb_addr; +#endif } vm_attr_t; #ifdef __cplusplus diff --git a/src/riscv_private.h b/src/riscv_private.h index 12a3bfd0..54182272 100644 --- a/src/riscv_private.h +++ b/src/riscv_private.h @@ -4,6 +4,7 @@ */ #pragma once +#include #include #if RV32_HAS(GDBSTUB) @@ -228,6 +229,9 @@ struct riscv_internal { * executing signal handler. */ uint32_t last_csr_sepc; + + /* Jump buffer for restarting the main loop after a reboot */ + jmp_buf reboot_jmp; #endif #if RV32_HAS(ARCH_TEST) diff --git a/src/syscall.c b/src/syscall.c index 3d49c7b6..cf86f3fd 100644 --- a/src/syscall.c +++ b/src/syscall.c @@ -497,6 +497,39 @@ static void syscall_sbi_base(riscv_t *rv) } } +static const char *sbi_rst_type_str(const riscv_word_t type) +{ + switch (type) { + case SHUTDOWN: + return "shutdown"; + case COLD_REBOOT: + return "cold reboot"; + case WARM_REBOOT: + return "warm reboot"; + default: + rv_log_error("Unknown reset type: %u", type); + break; + } + + return NULL; +} + +static const char *sbi_rst_reason_str(const riscv_word_t reason) +{ + switch (reason) { + case NO_REASON: + return "no reason"; + case SYSTEM_FAILURE: + return "system failure"; + default: + rv_log_error("Unknown reset reason: %u", reason); + break; + } + + return NULL; +} + +/* Does not return if it succeeds */ static void syscall_sbi_rst(riscv_t *rv) { const riscv_word_t fid = rv_get_reg(rv, rv_reg_a6); @@ -505,10 +538,31 @@ static void syscall_sbi_rst(riscv_t *rv) switch (fid) { case SBI_RST_SYSTEM_RESET: - rv_log_info("System reset: type=%u, reason=%u", a0, a1); - rv_halt(rv); - rv_set_reg(rv, rv_reg_a0, SBI_SUCCESS); - rv_set_reg(rv, rv_reg_a1, 0); + rv_log_info("System reset: type=%s, reason=%s", sbi_rst_type_str(a0), + sbi_rst_reason_str(a1)); + if (a0 == SHUTDOWN) { + rv_halt(rv); + } else if (a0 == + COLD_REBOOT) { /* default reboot mode, reset whole system */ + rv_cold_reboot(rv, 0U); + /* longjmp to return to the main loop to avoid the complex return + * path and access stale registers (e.g., sp) after rv_cold_reboot() + * has called. + * + * The setjmp point is in rv_step() in src/emulate.c. + */ + longjmp(rv->reboot_jmp, 1); + } else if (a0 == WARM_REBOOT) { /* reset halt only, echo "warm" > + /sys/kernel/reboot/mode to set */ + rv_warm_reboot(rv, 0U); + /* longjmp to return to the main loop to avoid the complex return + * path and access stale registers (e.g., sp) after rv_warm_reboot() + * has called. + * + * The setjmp point is in rv_step() in src/emulate.c. + */ + longjmp(rv->reboot_jmp, 1); + } break; default: rv_set_reg(rv, rv_reg_a0, SBI_ERR_NOT_SUPPORTED);