| /* |
| * RISCV machine |
| * |
| * Copyright (c) 2016-2017 Fabrice Bellard |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| * THE SOFTWARE. |
| */ |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include <string.h> |
| #include <inttypes.h> |
| #include <assert.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <time.h> |
| |
| #include "cutils.h" |
| #include "iomem.h" |
| #include "riscv_cpu.h" |
| #include "virtio.h" |
| #include "machine.h" |
| |
| /* RISCV machine */ |
| |
| typedef struct RISCVMachine { |
| VirtMachine common; |
| PhysMemoryMap *mem_map; |
| int max_xlen; |
| RISCVCPUState *cpu_state; |
| uint64_t ram_size; |
| /* RTC */ |
| BOOL rtc_real_time; |
| uint64_t rtc_start_time; |
| uint64_t timecmp; |
| /* PLIC */ |
| uint32_t plic_pending_irq, plic_served_irq; |
| IRQSignal plic_irq[32]; /* IRQ 0 is not used */ |
| /* HTIF */ |
| uint64_t htif_tohost, htif_fromhost; |
| |
| VIRTIODevice *keyboard_dev; |
| VIRTIODevice *mouse_dev; |
| |
| int virtio_count; |
| } RISCVMachine; |
| |
| #define LOW_RAM_SIZE 0x00010000 /* 64KB */ |
| #define RAM_BASE_ADDR 0x80000000 |
| #define CLINT_BASE_ADDR 0x02000000 |
| #define CLINT_SIZE 0x000c0000 |
| #define HTIF_BASE_ADDR 0x40008000 |
| #define IDE_BASE_ADDR 0x40009000 |
| #define VIRTIO_BASE_ADDR 0x40010000 |
| #define VIRTIO_SIZE 0x1000 |
| #define VIRTIO_IRQ 1 |
| #define PLIC_BASE_ADDR 0x40100000 |
| #define PLIC_SIZE 0x00400000 |
| #define FRAMEBUFFER_BASE_ADDR 0x41000000 |
| |
| #define RTC_FREQ 10000000 |
| #define RTC_FREQ_DIV 16 /* arbitrary, relative to CPU freq to have a |
| 10 MHz frequency */ |
| |
| static uint64_t rtc_get_real_time(RISCVMachine *s) |
| { |
| struct timespec ts; |
| clock_gettime(CLOCK_MONOTONIC, &ts); |
| return (uint64_t)ts.tv_sec * RTC_FREQ + |
| (ts.tv_nsec / (1000000000 / RTC_FREQ)); |
| } |
| |
| static uint64_t rtc_get_time(RISCVMachine *m) |
| { |
| uint64_t val; |
| if (m->rtc_real_time) { |
| val = rtc_get_real_time(m) - m->rtc_start_time; |
| } else { |
| val = riscv_cpu_get_cycles(m->cpu_state) / RTC_FREQ_DIV; |
| } |
| // printf("rtc_time=%" PRId64 "\n", val); |
| return val; |
| } |
| |
| static uint32_t htif_read(void *opaque, uint32_t offset, |
| int size_log2) |
| { |
| RISCVMachine *s = opaque; |
| uint32_t val; |
| |
| assert(size_log2 == 2); |
| switch(offset) { |
| case 0: |
| val = s->htif_tohost; |
| break; |
| case 4: |
| val = s->htif_tohost >> 32; |
| break; |
| case 8: |
| val = s->htif_fromhost; |
| break; |
| case 12: |
| val = s->htif_fromhost >> 32; |
| break; |
| default: |
| val = 0; |
| break; |
| } |
| return val; |
| } |
| |
| static void htif_handle_cmd(RISCVMachine *s) |
| { |
| uint32_t device, cmd; |
| |
| device = s->htif_tohost >> 56; |
| cmd = (s->htif_tohost >> 48) & 0xff; |
| if (s->htif_tohost == 1) { |
| /* shuthost */ |
| printf("\nPower off.\n"); |
| exit(0); |
| } else if (device == 1 && cmd == 1) { |
| uint8_t buf[1]; |
| buf[0] = s->htif_tohost & 0xff; |
| s->common.console->write_data(s->common.console->opaque, buf, 1); |
| s->htif_tohost = 0; |
| s->htif_fromhost = ((uint64_t)device << 56) | ((uint64_t)cmd << 48); |
| } else if (device == 1 && cmd == 0) { |
| /* request keyboard interrupt */ |
| s->htif_tohost = 0; |
| } else { |
| printf("HTIF: unsupported tohost=0x%016" PRIx64 "\n", s->htif_tohost); |
| } |
| } |
| |
| static void htif_write(void *opaque, uint32_t offset, uint32_t val, |
| int size_log2) |
| { |
| RISCVMachine *s = opaque; |
| |
| assert(size_log2 == 2); |
| switch(offset) { |
| case 0: |
| s->htif_tohost = (s->htif_tohost & ~0xffffffff) | val; |
| break; |
| case 4: |
| s->htif_tohost = (s->htif_tohost & 0xffffffff) | ((uint64_t)val << 32); |
| htif_handle_cmd(s); |
| break; |
| case 8: |
| s->htif_fromhost = (s->htif_fromhost & ~0xffffffff) | val; |
| break; |
| case 12: |
| s->htif_fromhost = (s->htif_fromhost & 0xffffffff) | |
| (uint64_t)val << 32; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| #if 0 |
| static void htif_poll(RISCVMachine *s) |
| { |
| uint8_t buf[1]; |
| int ret; |
| |
| if (s->htif_fromhost == 0) { |
| ret = s->console->read_data(s->console->opaque, buf, 1); |
| if (ret == 1) { |
| s->htif_fromhost = ((uint64_t)1 << 56) | ((uint64_t)0 << 48) | |
| buf[0]; |
| } |
| } |
| } |
| #endif |
| |
| static uint32_t clint_read(void *opaque, uint32_t offset, int size_log2) |
| { |
| RISCVMachine *m = opaque; |
| uint32_t val; |
| |
| assert(size_log2 == 2); |
| switch(offset) { |
| case 0xbff8: |
| val = rtc_get_time(m); |
| break; |
| case 0xbffc: |
| val = rtc_get_time(m) >> 32; |
| break; |
| case 0x4000: |
| val = m->timecmp; |
| break; |
| case 0x4004: |
| val = m->timecmp >> 32; |
| break; |
| default: |
| val = 0; |
| break; |
| } |
| return val; |
| } |
| |
| static void clint_write(void *opaque, uint32_t offset, uint32_t val, |
| int size_log2) |
| { |
| RISCVMachine *m = opaque; |
| |
| assert(size_log2 == 2); |
| switch(offset) { |
| case 0x4000: |
| m->timecmp = (m->timecmp & ~0xffffffff) | val; |
| riscv_cpu_reset_mip(m->cpu_state, MIP_MTIP); |
| break; |
| case 0x4004: |
| m->timecmp = (m->timecmp & 0xffffffff) | ((uint64_t)val << 32); |
| riscv_cpu_reset_mip(m->cpu_state, MIP_MTIP); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void plic_update_mip(RISCVMachine *s) |
| { |
| RISCVCPUState *cpu = s->cpu_state; |
| uint32_t mask; |
| mask = s->plic_pending_irq & ~s->plic_served_irq; |
| if (mask) { |
| riscv_cpu_set_mip(cpu, MIP_MEIP | MIP_SEIP); |
| } else { |
| riscv_cpu_reset_mip(cpu, MIP_MEIP | MIP_SEIP); |
| } |
| } |
| |
| #define PLIC_HART_BASE 0x200000 |
| #define PLIC_HART_SIZE 0x1000 |
| |
| static uint32_t plic_read(void *opaque, uint32_t offset, int size_log2) |
| { |
| RISCVMachine *s = opaque; |
| uint32_t val, mask; |
| int i; |
| assert(size_log2 == 2); |
| switch(offset) { |
| case PLIC_HART_BASE: |
| val = 0; |
| break; |
| case PLIC_HART_BASE + 4: |
| mask = s->plic_pending_irq & ~s->plic_served_irq; |
| if (mask != 0) { |
| i = ctz32(mask); |
| s->plic_served_irq |= 1 << i; |
| plic_update_mip(s); |
| val = i + 1; |
| } else { |
| val = 0; |
| } |
| break; |
| default: |
| val = 0; |
| break; |
| } |
| return val; |
| } |
| |
| static void plic_write(void *opaque, uint32_t offset, uint32_t val, |
| int size_log2) |
| { |
| RISCVMachine *s = opaque; |
| |
| assert(size_log2 == 2); |
| switch(offset) { |
| case PLIC_HART_BASE + 4: |
| val--; |
| if (val < 32) { |
| s->plic_served_irq &= ~(1 << val); |
| plic_update_mip(s); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void plic_set_irq(void *opaque, int irq_num, int state) |
| { |
| RISCVMachine *s = opaque; |
| uint32_t mask; |
| |
| mask = 1 << (irq_num - 1); |
| if (state) |
| s->plic_pending_irq |= mask; |
| else |
| s->plic_pending_irq &= ~mask; |
| plic_update_mip(s); |
| } |
| |
| static uint8_t *get_ram_ptr(RISCVMachine *s, uint64_t paddr, BOOL is_rw) |
| { |
| return phys_mem_get_ram_ptr(s->mem_map, paddr, is_rw); |
| } |
| |
| /* FDT machine description */ |
| |
| #define FDT_MAGIC 0xd00dfeed |
| #define FDT_VERSION 17 |
| |
| 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; /* <= 17 */ |
| uint32_t boot_cpuid_phys; |
| uint32_t size_dt_strings; |
| uint32_t size_dt_struct; |
| }; |
| |
| struct fdt_reserve_entry { |
| uint64_t address; |
| uint64_t size; |
| }; |
| |
| #define FDT_BEGIN_NODE 1 |
| #define FDT_END_NODE 2 |
| #define FDT_PROP 3 |
| #define FDT_NOP 4 |
| #define FDT_END 9 |
| |
| typedef struct { |
| uint32_t *tab; |
| int tab_len; |
| int tab_size; |
| int open_node_count; |
| |
| char *string_table; |
| int string_table_len; |
| int string_table_size; |
| } FDTState; |
| |
| static FDTState *fdt_init(void) |
| { |
| FDTState *s; |
| s = mallocz(sizeof(*s)); |
| return s; |
| } |
| |
| static void fdt_alloc_len(FDTState *s, int len) |
| { |
| int new_size; |
| if (unlikely(len > s->tab_size)) { |
| new_size = max_int(len, s->tab_size * 3 / 2); |
| s->tab = realloc(s->tab, new_size * sizeof(uint32_t)); |
| s->tab_size = new_size; |
| } |
| } |
| |
| static void fdt_put32(FDTState *s, int v) |
| { |
| fdt_alloc_len(s, s->tab_len + 1); |
| s->tab[s->tab_len++] = cpu_to_be32(v); |
| } |
| |
| /* the data is zero padded */ |
| static void fdt_put_data(FDTState *s, const uint8_t *data, int len) |
| { |
| int len1; |
| |
| len1 = (len + 3) / 4; |
| fdt_alloc_len(s, s->tab_len + len1); |
| memcpy(s->tab + s->tab_len, data, len); |
| memset((uint8_t *)(s->tab + s->tab_len) + len, 0, -len & 3); |
| s->tab_len += len1; |
| } |
| |
| static void fdt_begin_node(FDTState *s, const char *name) |
| { |
| fdt_put32(s, FDT_BEGIN_NODE); |
| fdt_put_data(s, (uint8_t *)name, strlen(name) + 1); |
| s->open_node_count++; |
| } |
| |
| static void fdt_begin_node_num(FDTState *s, const char *name, uint64_t n) |
| { |
| char buf[256]; |
| snprintf(buf, sizeof(buf), "%s@%" PRIx64, name, n); |
| fdt_begin_node(s, buf); |
| } |
| |
| static void fdt_end_node(FDTState *s) |
| { |
| fdt_put32(s, FDT_END_NODE); |
| s->open_node_count--; |
| } |
| |
| static int fdt_get_string_offset(FDTState *s, const char *name) |
| { |
| int pos, new_size, name_size, new_len; |
| |
| pos = 0; |
| while (pos < s->string_table_len) { |
| if (!strcmp(s->string_table + pos, name)) |
| return pos; |
| pos += strlen(s->string_table + pos) + 1; |
| } |
| /* add a new string */ |
| name_size = strlen(name) + 1; |
| new_len = s->string_table_len + name_size; |
| if (new_len > s->string_table_size) { |
| new_size = max_int(new_len, s->string_table_size * 3 / 2); |
| s->string_table = realloc(s->string_table, new_size); |
| s->string_table_size = new_size; |
| } |
| pos = s->string_table_len; |
| memcpy(s->string_table + pos, name, name_size); |
| s->string_table_len = new_len; |
| return pos; |
| } |
| |
| static void fdt_prop(FDTState *s, const char *prop_name, |
| const void *data, int data_len) |
| { |
| fdt_put32(s, FDT_PROP); |
| fdt_put32(s, data_len); |
| fdt_put32(s, fdt_get_string_offset(s, prop_name)); |
| fdt_put_data(s, data, data_len); |
| } |
| |
| static void fdt_prop_tab_u32(FDTState *s, const char *prop_name, |
| uint32_t *tab, int tab_len) |
| { |
| int i; |
| fdt_put32(s, FDT_PROP); |
| fdt_put32(s, tab_len * sizeof(uint32_t)); |
| fdt_put32(s, fdt_get_string_offset(s, prop_name)); |
| for(i = 0; i < tab_len; i++) |
| fdt_put32(s, tab[i]); |
| } |
| |
| static void fdt_prop_u32(FDTState *s, const char *prop_name, uint32_t val) |
| { |
| fdt_prop_tab_u32(s, prop_name, &val, 1); |
| } |
| |
| static void fdt_prop_tab_u64(FDTState *s, const char *prop_name, |
| uint64_t v0) |
| { |
| uint32_t tab[2]; |
| tab[0] = v0 >> 32; |
| tab[1] = v0; |
| fdt_prop_tab_u32(s, prop_name, tab, 2); |
| } |
| |
| static void fdt_prop_tab_u64_2(FDTState *s, const char *prop_name, |
| uint64_t v0, uint64_t v1) |
| { |
| uint32_t tab[4]; |
| tab[0] = v0 >> 32; |
| tab[1] = v0; |
| tab[2] = v1 >> 32; |
| tab[3] = v1; |
| fdt_prop_tab_u32(s, prop_name, tab, 4); |
| } |
| |
| static void fdt_prop_str(FDTState *s, const char *prop_name, |
| const char *str) |
| { |
| fdt_prop(s, prop_name, str, strlen(str) + 1); |
| } |
| |
| /* NULL terminated string list */ |
| static void fdt_prop_tab_str(FDTState *s, const char *prop_name, |
| ...) |
| { |
| va_list ap; |
| int size, str_size; |
| char *ptr, *tab; |
| |
| va_start(ap, prop_name); |
| size = 0; |
| for(;;) { |
| ptr = va_arg(ap, char *); |
| if (!ptr) |
| break; |
| str_size = strlen(ptr) + 1; |
| size += str_size; |
| } |
| va_end(ap); |
| |
| tab = malloc(size); |
| va_start(ap, prop_name); |
| size = 0; |
| for(;;) { |
| ptr = va_arg(ap, char *); |
| if (!ptr) |
| break; |
| str_size = strlen(ptr) + 1; |
| memcpy(tab + size, ptr, str_size); |
| size += str_size; |
| } |
| va_end(ap); |
| |
| fdt_prop(s, prop_name, tab, size); |
| free(tab); |
| } |
| |
| /* write the FDT to 'dst1'. return the FDT size in bytes */ |
| int fdt_output(FDTState *s, uint8_t *dst) |
| { |
| struct fdt_header *h; |
| struct fdt_reserve_entry *re; |
| int dt_struct_size; |
| int dt_strings_size; |
| int pos; |
| |
| assert(s->open_node_count == 0); |
| |
| fdt_put32(s, FDT_END); |
| |
| dt_struct_size = s->tab_len * sizeof(uint32_t); |
| dt_strings_size = s->string_table_len; |
| |
| h = (struct fdt_header *)dst; |
| h->magic = cpu_to_be32(FDT_MAGIC); |
| h->version = cpu_to_be32(FDT_VERSION); |
| h->last_comp_version = cpu_to_be32(16); |
| h->boot_cpuid_phys = cpu_to_be32(0); |
| h->size_dt_strings = cpu_to_be32(dt_strings_size); |
| h->size_dt_struct = cpu_to_be32(dt_struct_size); |
| |
| pos = sizeof(struct fdt_header); |
| |
| h->off_dt_struct = cpu_to_be32(pos); |
| memcpy(dst + pos, s->tab, dt_struct_size); |
| pos += dt_struct_size; |
| |
| /* align to 8 */ |
| while ((pos & 7) != 0) { |
| dst[pos++] = 0; |
| } |
| h->off_mem_rsvmap = cpu_to_be32(pos); |
| re = (struct fdt_reserve_entry *)(dst + pos); |
| re->address = 0; /* no reserved entry */ |
| re->size = 0; |
| pos += sizeof(struct fdt_reserve_entry); |
| |
| h->off_dt_strings = cpu_to_be32(pos); |
| memcpy(dst + pos, s->string_table, dt_strings_size); |
| pos += dt_strings_size; |
| |
| /* align to 8, just in case */ |
| while ((pos & 7) != 0) { |
| dst[pos++] = 0; |
| } |
| |
| h->totalsize = cpu_to_be32(pos); |
| return pos; |
| } |
| |
| void fdt_end(FDTState *s) |
| { |
| free(s->tab); |
| free(s->string_table); |
| free(s); |
| } |
| |
| static int riscv_build_fdt(RISCVMachine *m, uint8_t *dst, |
| uint64_t kernel_start, uint64_t kernel_size, |
| uint64_t initrd_start, uint64_t initrd_size, |
| const char *cmd_line) |
| { |
| FDTState *s; |
| int size, max_xlen, i, cur_phandle, intc_phandle, plic_phandle; |
| char isa_string[128], *q; |
| uint32_t misa; |
| uint32_t tab[4]; |
| FBDevice *fb_dev; |
| |
| s = fdt_init(); |
| |
| cur_phandle = 1; |
| |
| fdt_begin_node(s, ""); |
| fdt_prop_u32(s, "#address-cells", 2); |
| fdt_prop_u32(s, "#size-cells", 2); |
| fdt_prop_str(s, "compatible", "ucbbar,riscvemu-bar_dev"); |
| fdt_prop_str(s, "model", "ucbbar,riscvemu-bare"); |
| |
| /* CPU list */ |
| fdt_begin_node(s, "cpus"); |
| fdt_prop_u32(s, "#address-cells", 1); |
| fdt_prop_u32(s, "#size-cells", 0); |
| fdt_prop_u32(s, "timebase-frequency", RTC_FREQ); |
| |
| /* cpu */ |
| fdt_begin_node_num(s, "cpu", 0); |
| fdt_prop_str(s, "device_type", "cpu"); |
| fdt_prop_u32(s, "reg", 0); |
| fdt_prop_str(s, "status", "okay"); |
| fdt_prop_str(s, "compatible", "riscv"); |
| |
| max_xlen = m->max_xlen; |
| misa = riscv_cpu_get_misa(m->cpu_state); |
| q = isa_string; |
| q += snprintf(isa_string, sizeof(isa_string), "rv%d", max_xlen); |
| for(i = 0; i < 26; i++) { |
| if (misa & (1 << i)) |
| *q++ = 'a' + i; |
| } |
| *q = '\0'; |
| fdt_prop_str(s, "riscv,isa", isa_string); |
| |
| fdt_prop_str(s, "mmu-type", max_xlen <= 32 ? "riscv,sv32" : "riscv,sv48"); |
| fdt_prop_u32(s, "clock-frequency", 2000000000); |
| |
| fdt_begin_node(s, "interrupt-controller"); |
| fdt_prop_u32(s, "#interrupt-cells", 1); |
| fdt_prop(s, "interrupt-controller", NULL, 0); |
| fdt_prop_str(s, "compatible", "riscv,cpu-intc"); |
| intc_phandle = cur_phandle++; |
| fdt_prop_u32(s, "phandle", intc_phandle); |
| fdt_end_node(s); /* interrupt-controller */ |
| |
| fdt_end_node(s); /* cpu */ |
| |
| fdt_end_node(s); /* cpus */ |
| |
| fdt_begin_node_num(s, "memory", RAM_BASE_ADDR); |
| fdt_prop_str(s, "device_type", "memory"); |
| tab[0] = (uint64_t)RAM_BASE_ADDR >> 32; |
| tab[1] = RAM_BASE_ADDR; |
| tab[2] = m->ram_size >> 32; |
| tab[3] = m->ram_size; |
| fdt_prop_tab_u32(s, "reg", tab, 4); |
| |
| fdt_end_node(s); /* memory */ |
| |
| fdt_begin_node(s, "htif"); |
| fdt_prop_str(s, "compatible", "ucb,htif0"); |
| fdt_end_node(s); /* htif */ |
| |
| fdt_begin_node(s, "soc"); |
| fdt_prop_u32(s, "#address-cells", 2); |
| fdt_prop_u32(s, "#size-cells", 2); |
| fdt_prop_tab_str(s, "compatible", |
| "ucbbar,riscvemu-bar-soc", "simple-bus", NULL); |
| fdt_prop(s, "ranges", NULL, 0); |
| |
| fdt_begin_node_num(s, "clint", CLINT_BASE_ADDR); |
| fdt_prop_str(s, "compatible", "riscv,clint0"); |
| |
| tab[0] = intc_phandle; |
| tab[1] = 3; /* M IPI irq */ |
| tab[2] = intc_phandle; |
| tab[3] = 7; /* M timer irq */ |
| fdt_prop_tab_u32(s, "interrupts-extended", tab, 4); |
| |
| fdt_prop_tab_u64_2(s, "reg", CLINT_BASE_ADDR, CLINT_SIZE); |
| |
| fdt_end_node(s); /* clint */ |
| |
| fdt_begin_node_num(s, "plic", PLIC_BASE_ADDR); |
| fdt_prop_u32(s, "#interrupt-cells", 1); |
| fdt_prop(s, "interrupt-controller", NULL, 0); |
| fdt_prop_str(s, "compatible", "riscv,plic0"); |
| fdt_prop_u32(s, "riscv,ndev", 31); |
| fdt_prop_tab_u64_2(s, "reg", PLIC_BASE_ADDR, PLIC_SIZE); |
| |
| tab[0] = intc_phandle; |
| tab[1] = 9; /* S ext irq */ |
| tab[2] = intc_phandle; |
| tab[3] = 11; /* M ext irq */ |
| fdt_prop_tab_u32(s, "interrupts-extended", tab, 4); |
| |
| plic_phandle = cur_phandle++; |
| fdt_prop_u32(s, "phandle", plic_phandle); |
| |
| fdt_end_node(s); /* plic */ |
| |
| for(i = 0; i < m->virtio_count; i++) { |
| fdt_begin_node_num(s, "virtio", VIRTIO_BASE_ADDR + i * VIRTIO_SIZE); |
| fdt_prop_str(s, "compatible", "virtio,mmio"); |
| fdt_prop_tab_u64_2(s, "reg", VIRTIO_BASE_ADDR + i * VIRTIO_SIZE, |
| VIRTIO_SIZE); |
| tab[0] = plic_phandle; |
| tab[1] = VIRTIO_IRQ + i; |
| fdt_prop_tab_u32(s, "interrupts-extended", tab, 2); |
| fdt_end_node(s); /* virtio */ |
| } |
| |
| fb_dev = m->common.fb_dev; |
| if (fb_dev) { |
| fdt_begin_node_num(s, "framebuffer", FRAMEBUFFER_BASE_ADDR); |
| fdt_prop_str(s, "compatible", "simple-framebuffer"); |
| fdt_prop_tab_u64_2(s, "reg", FRAMEBUFFER_BASE_ADDR, fb_dev->fb_size); |
| fdt_prop_u32(s, "width", fb_dev->width); |
| fdt_prop_u32(s, "height", fb_dev->height); |
| fdt_prop_u32(s, "stride", fb_dev->stride); |
| fdt_prop_str(s, "format", "a8r8g8b8"); |
| fdt_end_node(s); /* framebuffer */ |
| } |
| |
| fdt_end_node(s); /* soc */ |
| |
| fdt_begin_node(s, "chosen"); |
| fdt_prop_str(s, "bootargs", cmd_line ? cmd_line : ""); |
| if (kernel_size > 0) { |
| fdt_prop_tab_u64(s, "riscv,kernel-start", kernel_start); |
| fdt_prop_tab_u64(s, "riscv,kernel-end", kernel_start + kernel_size); |
| } |
| if (initrd_size > 0) { |
| fdt_prop_tab_u64(s, "linux,initrd-start", initrd_start); |
| fdt_prop_tab_u64(s, "linux,initrd-end", initrd_start + initrd_size); |
| } |
| |
| |
| fdt_end_node(s); /* chosen */ |
| |
| fdt_end_node(s); /* / */ |
| |
| size = fdt_output(s, dst); |
| #if 0 |
| { |
| FILE *f; |
| f = fopen("/tmp/riscvemu.dtb", "wb"); |
| fwrite(dst, 1, size, f); |
| fclose(f); |
| } |
| #endif |
| fdt_end(s); |
| return size; |
| } |
| |
| static void copy_bios(RISCVMachine *s, const uint8_t *buf, int buf_len, |
| const uint8_t *kernel_buf, int kernel_buf_len, |
| const uint8_t *initrd_buf, int initrd_buf_len, |
| const char *cmd_line) |
| { |
| uint32_t fdt_addr, align, kernel_base, initrd_base; |
| uint8_t *ram_ptr; |
| uint32_t *q; |
| |
| if (buf_len > s->ram_size) { |
| vm_error("BIOS too big\n"); |
| exit(1); |
| } |
| |
| ram_ptr = get_ram_ptr(s, RAM_BASE_ADDR, TRUE); |
| memcpy(ram_ptr, buf, buf_len); |
| |
| kernel_base = 0; |
| if (kernel_buf_len > 0) { |
| /* copy the kernel if present */ |
| if (s->max_xlen == 32) |
| align = 4 << 20; /* 4 MB page align */ |
| else |
| align = 2 << 20; /* 2 MB page align */ |
| kernel_base = (buf_len + align - 1) & ~(align - 1); |
| memcpy(ram_ptr + kernel_base, kernel_buf, kernel_buf_len); |
| if (kernel_buf_len + kernel_base > s->ram_size) { |
| vm_error("kernel too big"); |
| exit(1); |
| } |
| } |
| |
| initrd_base = 0; |
| if (initrd_buf_len > 0) { |
| /* same allocation as QEMU */ |
| initrd_base = s->ram_size / 2; |
| if (initrd_base > (128 << 20)) |
| initrd_base = 128 << 20; |
| memcpy(ram_ptr + initrd_base, initrd_buf, initrd_buf_len); |
| if (initrd_buf_len + initrd_base > s->ram_size) { |
| vm_error("initrd too big"); |
| exit(1); |
| } |
| } |
| |
| ram_ptr = get_ram_ptr(s, 0, TRUE); |
| |
| fdt_addr = 0x1000 + 8 * 8; |
| |
| riscv_build_fdt(s, ram_ptr + fdt_addr, |
| RAM_BASE_ADDR + kernel_base, kernel_buf_len, |
| RAM_BASE_ADDR + initrd_base, initrd_buf_len, |
| cmd_line); |
| |
| /* jump_addr = 0x80000000 */ |
| |
| q = (uint32_t *)(ram_ptr + 0x1000); |
| q[0] = 0x297 + 0x80000000 - 0x1000; /* auipc t0, jump_addr */ |
| q[1] = 0x597; /* auipc a1, dtb */ |
| q[2] = 0x58593 + ((fdt_addr - 4) << 20); /* addi a1, a1, dtb */ |
| q[3] = 0xf1402573; /* csrr a0, mhartid */ |
| q[4] = 0x00028067; /* jalr zero, t0, jump_addr */ |
| } |
| |
| static void riscv_flush_tlb_write_range(void *opaque, uint8_t *ram_addr, |
| size_t ram_size) |
| { |
| RISCVMachine *s = opaque; |
| riscv_cpu_flush_tlb_write_range_ram(s->cpu_state, ram_addr, ram_size); |
| } |
| |
| static void riscv_machine_set_defaults(VirtMachineParams *p) |
| { |
| } |
| |
| static VirtMachine *riscv_machine_init(const VirtMachineParams *p) |
| { |
| RISCVMachine *s; |
| VIRTIODevice *blk_dev; |
| int irq_num, i, max_xlen, ram_flags; |
| VIRTIOBusDef vbus_s, *vbus = &vbus_s; |
| |
| |
| if (!strcmp(p->machine_name, "riscv32")) { |
| max_xlen = 32; |
| } else if (!strcmp(p->machine_name, "riscv64")) { |
| max_xlen = 64; |
| } else if (!strcmp(p->machine_name, "riscv128")) { |
| max_xlen = 128; |
| } else { |
| vm_error("unsupported machine: %s\n", p->machine_name); |
| return NULL; |
| } |
| |
| s = mallocz(sizeof(*s)); |
| s->common.vmc = p->vmc; |
| s->ram_size = p->ram_size; |
| s->max_xlen = max_xlen; |
| s->mem_map = phys_mem_map_init(); |
| /* needed to handle the RAM dirty bits */ |
| s->mem_map->opaque = s; |
| s->mem_map->flush_tlb_write_range = riscv_flush_tlb_write_range; |
| |
| s->cpu_state = riscv_cpu_init(s->mem_map, max_xlen); |
| if (!s->cpu_state) { |
| vm_error("unsupported max_xlen=%d\n", max_xlen); |
| /* XXX: should free resources */ |
| return NULL; |
| } |
| /* RAM */ |
| ram_flags = 0; |
| cpu_register_ram(s->mem_map, RAM_BASE_ADDR, p->ram_size, ram_flags); |
| cpu_register_ram(s->mem_map, 0x00000000, LOW_RAM_SIZE, 0); |
| s->rtc_real_time = p->rtc_real_time; |
| if (p->rtc_real_time) { |
| s->rtc_start_time = rtc_get_real_time(s); |
| } |
| |
| cpu_register_device(s->mem_map, CLINT_BASE_ADDR, CLINT_SIZE, s, |
| clint_read, clint_write, DEVIO_SIZE32); |
| cpu_register_device(s->mem_map, PLIC_BASE_ADDR, PLIC_SIZE, s, |
| plic_read, plic_write, DEVIO_SIZE32); |
| for(i = 1; i < 32; i++) { |
| irq_init(&s->plic_irq[i], plic_set_irq, s, i); |
| } |
| |
| cpu_register_device(s->mem_map, HTIF_BASE_ADDR, 16, |
| s, htif_read, htif_write, DEVIO_SIZE32); |
| s->common.console = p->console; |
| |
| memset(vbus, 0, sizeof(*vbus)); |
| vbus->mem_map = s->mem_map; |
| vbus->addr = VIRTIO_BASE_ADDR; |
| irq_num = VIRTIO_IRQ; |
| |
| /* virtio console */ |
| if (p->console) { |
| vbus->irq = &s->plic_irq[irq_num]; |
| s->common.console_dev = virtio_console_init(vbus, p->console); |
| vbus->addr += VIRTIO_SIZE; |
| irq_num++; |
| s->virtio_count++; |
| } |
| |
| /* virtio net device */ |
| for(i = 0; i < p->eth_count; i++) { |
| vbus->irq = &s->plic_irq[irq_num]; |
| virtio_net_init(vbus, p->tab_eth[i].net); |
| s->common.net = p->tab_eth[i].net; |
| vbus->addr += VIRTIO_SIZE; |
| irq_num++; |
| s->virtio_count++; |
| } |
| |
| /* virtio block device */ |
| for(i = 0; i < p->drive_count; i++) { |
| vbus->irq = &s->plic_irq[irq_num]; |
| blk_dev = virtio_block_init(vbus, p->tab_drive[i].block_dev); |
| (void)blk_dev; |
| vbus->addr += VIRTIO_SIZE; |
| irq_num++; |
| s->virtio_count++; |
| } |
| |
| /* virtio filesystem */ |
| for(i = 0; i < p->fs_count; i++) { |
| VIRTIODevice *fs_dev; |
| vbus->irq = &s->plic_irq[irq_num]; |
| fs_dev = virtio_9p_init(vbus, p->tab_fs[i].fs_dev, |
| p->tab_fs[i].tag); |
| (void)fs_dev; |
| // virtio_set_debug(fs_dev, VIRTIO_DEBUG_9P); |
| vbus->addr += VIRTIO_SIZE; |
| irq_num++; |
| s->virtio_count++; |
| } |
| |
| if (p->display_device) { |
| FBDevice *fb_dev; |
| fb_dev = mallocz(sizeof(*fb_dev)); |
| s->common.fb_dev = fb_dev; |
| if (!strcmp(p->display_device, "simplefb")) { |
| simplefb_init(s->mem_map, |
| FRAMEBUFFER_BASE_ADDR, |
| fb_dev, |
| p->width, p->height); |
| |
| } else { |
| vm_error("unsupported display device: %s\n", p->display_device); |
| exit(1); |
| } |
| } |
| |
| if (p->input_device) { |
| if (!strcmp(p->input_device, "virtio")) { |
| vbus->irq = &s->plic_irq[irq_num]; |
| s->keyboard_dev = virtio_input_init(vbus, |
| VIRTIO_INPUT_TYPE_KEYBOARD); |
| vbus->addr += VIRTIO_SIZE; |
| irq_num++; |
| s->virtio_count++; |
| |
| vbus->irq = &s->plic_irq[irq_num]; |
| s->mouse_dev = virtio_input_init(vbus, |
| VIRTIO_INPUT_TYPE_TABLET); |
| vbus->addr += VIRTIO_SIZE; |
| irq_num++; |
| s->virtio_count++; |
| } else { |
| vm_error("unsupported input device: %s\n", p->input_device); |
| exit(1); |
| } |
| } |
| |
| if (!p->files[VM_FILE_BIOS].buf) { |
| vm_error("No bios found"); |
| } |
| |
| copy_bios(s, p->files[VM_FILE_BIOS].buf, p->files[VM_FILE_BIOS].len, |
| p->files[VM_FILE_KERNEL].buf, p->files[VM_FILE_KERNEL].len, |
| p->files[VM_FILE_INITRD].buf, p->files[VM_FILE_INITRD].len, |
| p->cmdline); |
| |
| return (VirtMachine *)s; |
| } |
| |
| static void riscv_machine_end(VirtMachine *s1) |
| { |
| RISCVMachine *s = (RISCVMachine *)s1; |
| /* XXX: stop all */ |
| riscv_cpu_end(s->cpu_state); |
| phys_mem_map_end(s->mem_map); |
| free(s); |
| } |
| |
| /* in ms */ |
| static int riscv_machine_get_sleep_duration(VirtMachine *s1, int delay) |
| { |
| RISCVMachine *m = (RISCVMachine *)s1; |
| RISCVCPUState *s = m->cpu_state; |
| int64_t delay1; |
| |
| /* wait for an event: the only asynchronous event is the RTC timer */ |
| if (!(riscv_cpu_get_mip(s) & MIP_MTIP)) { |
| delay1 = m->timecmp - rtc_get_time(m); |
| if (delay1 <= 0) { |
| riscv_cpu_set_mip(s, MIP_MTIP); |
| delay = 0; |
| } else { |
| /* convert delay to ms */ |
| delay1 = delay1 / (RTC_FREQ / 1000); |
| if (delay1 < delay) |
| delay = delay1; |
| } |
| } |
| if (!riscv_cpu_get_power_down(s)) |
| delay = 0; |
| return delay; |
| } |
| |
| static void riscv_machine_interp(VirtMachine *s1, int max_exec_cycle) |
| { |
| RISCVMachine *s = (RISCVMachine *)s1; |
| riscv_cpu_interp(s->cpu_state, max_exec_cycle); |
| } |
| |
| static void riscv_vm_send_key_event(VirtMachine *s1, BOOL is_down, |
| uint16_t key_code) |
| { |
| RISCVMachine *s = (RISCVMachine *)s1; |
| if (s->keyboard_dev) { |
| virtio_input_send_key_event(s->keyboard_dev, is_down, key_code); |
| } |
| } |
| |
| static BOOL riscv_vm_mouse_is_absolute(VirtMachine *s) |
| { |
| return TRUE; |
| } |
| |
| static void riscv_vm_send_mouse_event(VirtMachine *s1, int dx, int dy, int dz, |
| unsigned int buttons) |
| { |
| RISCVMachine *s = (RISCVMachine *)s1; |
| if (s->mouse_dev) { |
| virtio_input_send_mouse_event(s->mouse_dev, dx, dy, dz, buttons); |
| } |
| } |
| |
| const VirtMachineClass riscv_machine_class = { |
| "riscv32,riscv64,riscv128", |
| riscv_machine_set_defaults, |
| riscv_machine_init, |
| riscv_machine_end, |
| riscv_machine_get_sleep_duration, |
| riscv_machine_interp, |
| riscv_vm_mouse_is_absolute, |
| riscv_vm_send_mouse_event, |
| riscv_vm_send_key_event, |
| }; |