#include "hv/allocator.h" #include "hv/gicv3.h" #include "hv/el2_mmu.h" #include "hv/log.h" #include "hv/monitor.h" #include "hv/mmio_policy.h" #include "hv/platform.h" #include "hv/policy.h" #include "hv/psci.h" #include "hv/stage2.h" #include "hv/string.h" #include "hv/timer.h" #include "hv/uart.h" static char g_line[96]; static uint32_t g_pos; static bool g_paused; static struct vcpu g_snapshot; static bool g_snapshot_valid; static void help(void) { #if CONFIG_MONITOR_MUTATION uart_puts("commands: help status vcpu regs sysregs snapshot restore log log clear policy profile psci mappings mmio el2mmu fdt translate read64 write64 dump unmap irq irq clear resume pause reboot shutdown\n"); #else uart_puts("commands: help status vcpu regs sysregs snapshot snapshot show log policy psci mappings mmio el2mmu fdt translate read64 dump resume pause\n"); #endif } static bool is_space(char c) { return c == ' ' || c == '\t'; } static const char *skip_spaces(const char *s) { while (is_space(*s)) { s++; } return s; } static bool starts_with_command(const char *line, const char *cmd) { size_t n = hv_strlen(cmd); return hv_strncmp(line, cmd, n) == 0 && (line[n] == '\0' || is_space(line[n])); } static bool digit_value(char c, uint64_t *out) { if (c >= '0' && c <= '9') { *out = (uint64_t)(c - '0'); return true; } if (c >= 'a' && c <= 'f') { *out = 10u + (uint64_t)(c - 'a'); return true; } if (c >= 'A' && c <= 'F') { *out = 10u + (uint64_t)(c - 'A'); return true; } return false; } static bool parse_u64(const char **cursor, uint64_t *out) { const char *s = skip_spaces(*cursor); uint64_t base = 10u; uint64_t value = 0u; uint64_t digit; bool any = false; if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { base = 16u; s += 2; } while (digit_value(*s, &digit)) { if (digit >= base) { return false; } if (value > (UINT64_MAX - digit) / base) { return false; } value = (value * base) + digit; any = true; s++; } if (!any) { return false; } *cursor = s; *out = value; return true; } static bool ipa_to_host(uint64_t ipa, uint64_t len, uintptr_t *host) { struct stage2_translation tr; uint64_t end; uint64_t tr_end; uint64_t off; if (host == (uintptr_t *)0 || len == 0u || hv_add_overflow_u64(ipa, len, &end)) { return false; } if (!stage2_translate(ipa, &tr) || hv_add_overflow_u64(tr.ipa_base, tr.size, &tr_end) || !hv_range_contains(tr.ipa_base, tr.size, ipa, len)) { return false; } off = ipa - tr.ipa_base; *host = (uintptr_t)(tr.pa_base + off); return true; } static void monitor_translate(const char *args) { uint64_t ipa; struct stage2_translation tr; if (!parse_u64(&args, &ipa)) { uart_puts("usage translate \n"); return; } if (!stage2_translate(ipa, &tr)) { uart_puts("translate unmapped ipa="); uart_put_hex64(ipa); uart_puts("\n"); return; } uart_puts("translate ipa="); uart_put_hex64(ipa); uart_puts(" block="); uart_put_hex64(tr.ipa_base); uart_puts(" pa="); uart_put_hex64(tr.pa_base + (ipa - tr.ipa_base)); uart_puts(" perms="); uart_put_hex64(tr.perms); uart_puts(" desc="); uart_put_hex64(tr.desc); uart_puts("\n"); } static void monitor_read64(const char *args) { uint64_t ipa; uintptr_t host; if (!parse_u64(&args, &ipa) || !hv_is_aligned_u64(ipa, 8u)) { uart_puts("usage read64 \n"); return; } if (!ipa_to_host(ipa, 8u, &host)) { uart_puts("read64 unmapped\n"); return; } uart_puts("read64 "); uart_put_hex64(ipa); uart_puts("="); uart_put_hex64(*(volatile uint64_t *)host); uart_puts("\n"); } static void monitor_write64(const char *args) { #if CONFIG_MONITOR_MUTATION uint64_t ipa; uint64_t value; uintptr_t host; if (!parse_u64(&args, &ipa) || !parse_u64(&args, &value) || !hv_is_aligned_u64(ipa, 8u)) { uart_puts("usage write64 \n"); return; } if (!ipa_to_host(ipa, 8u, &host)) { uart_puts("write64 unmapped\n"); return; } *(volatile uint64_t *)host = value; uart_puts("write64 ok\n"); #else (void)args; uart_puts("write64 disabled\n"); #endif } static void monitor_dump(const char *args) { uint64_t ipa; uint64_t len; uintptr_t host; if (!parse_u64(&args, &ipa) || !parse_u64(&args, &len) || len > 256u) { uart_puts("usage dump \n"); return; } if (!ipa_to_host(ipa, len, &host)) { uart_puts("dump unmapped\n"); return; } for (uint64_t i = 0; i < len; i++) { if ((i & 0x0full) == 0u) { uart_puts("\n"); uart_put_hex64(ipa + i); uart_puts(":"); } uart_puts(" "); uint8_t b = *(volatile uint8_t *)(host + i); static const char hex[] = "0123456789abcdef"; uart_putc(hex[b >> 4u]); uart_putc(hex[b & 0x0fu]); } uart_puts("\n"); } static void monitor_unmap(const char *args) { #if CONFIG_MONITOR_MUTATION uint64_t ipa; if (!parse_u64(&args, &ipa)) { uart_puts("usage unmap <2MiB-aligned-ipa>\n"); return; } if (!stage2_unmap_range(HV_ALIGN_DOWN(ipa, 0x200000ull), 0x200000ull)) { uart_puts("unmap failed\n"); return; } uart_puts("unmap ok\n"); #else (void)args; uart_puts("unmap disabled\n"); #endif } static void monitor_irq(const char *args, struct vcpu *vcpu) { #if CONFIG_MONITOR_MUTATION uint64_t intid; args = skip_spaces(args); if (hv_strncmp(args, "clear", 5u) == 0 && is_space(args[5])) { args = skip_spaces(args + 5u); if (vcpu == (struct vcpu *)0 || !parse_u64(&args, &intid) || intid >= 64u) { uart_puts("usage irq clear \n"); return; } vcpu_clear_pending_irq(vcpu, (uint32_t)intid); gicv3_flush_vcpu_state(vcpu); uart_puts("irq cleared intid="); uart_put_dec64(intid); uart_puts("\n"); return; } if (vcpu == (struct vcpu *)0 || !parse_u64(&args, &intid) || intid >= 64u) { uart_puts("usage irq | irq clear \n"); return; } vcpu_set_pending_irq(vcpu, (uint32_t)intid); gicv3_flush_vcpu_state(vcpu); uart_puts("irq pending intid="); uart_put_dec64(intid); uart_puts("\n"); #else (void)args; (void)vcpu; uart_puts("irq disabled\n"); #endif } static void monitor_profile(const char *args) { #if CONFIG_MONITOR_MUTATION args = skip_spaces(args); if (*args == '\0') { uart_puts("policy profile="); uart_puts(policy_profile_name(policy_get_profile())); uart_puts("\n"); return; } if (!policy_set_profile_name(args)) { uart_puts("usage profile strict|diagnostic|linux-boot|debug\n"); return; } uart_puts("policy profile="); uart_puts(policy_profile_name(policy_get_profile())); uart_puts("\n"); #else (void)args; uart_puts("profile disabled\n"); #endif } static void monitor_snapshot(struct vcpu *vcpu) { if (vcpu == (struct vcpu *)0) { uart_puts("snapshot no-vcpu\n"); return; } if (vcpu->state != VCPU_PAUSED) { uart_puts("snapshot requires-paused-vcpu\n"); return; } g_snapshot = *vcpu; g_snapshot_valid = true; log_simple(LOG_MONITOR, 610u, vcpu->pc, vcpu->pstate); uart_puts("snapshot saved pc="); uart_put_hex64(g_snapshot.pc); uart_puts(" pstate="); uart_put_hex64(g_snapshot.pstate); uart_puts(" id="); uart_put_dec64(g_snapshot.id); uart_puts("\n"); } static void monitor_snapshot_show(void) { if (!g_snapshot_valid) { uart_puts("snapshot empty\n"); return; } uart_puts("snapshot pc="); uart_put_hex64(g_snapshot.pc); uart_puts(" pstate="); uart_put_hex64(g_snapshot.pstate); uart_puts(" sp_el1="); uart_put_hex64(g_snapshot.sp_el1); uart_puts(" traps="); uart_put_dec64(g_snapshot.stats.traps); uart_puts(" state="); uart_put_dec64((uint64_t)g_snapshot.state); uart_puts("\n"); } static void monitor_restore(struct vcpu *vcpu) { #if CONFIG_MONITOR_MUTATION if (vcpu == (struct vcpu *)0 || !g_snapshot_valid || vcpu->id != g_snapshot.id) { uart_puts("restore no-snapshot\n"); return; } *vcpu = g_snapshot; vcpu_set_current(vcpu); log_simple(LOG_MONITOR, 611u, vcpu->pc, vcpu->pstate); uart_puts("restore ok pc="); uart_put_hex64(vcpu->pc); uart_puts("\n"); #else (void)vcpu; uart_puts("restore disabled\n"); #endif } static void show_sysregs(const struct vcpu *vcpu) { if (vcpu == (const struct vcpu *)0) { return; } uart_puts("sysregs sctlr="); uart_put_hex64(vcpu->sysregs.sctlr_el1); uart_puts(" ttbr0="); uart_put_hex64(vcpu->sysregs.ttbr0_el1); uart_puts(" ttbr1="); uart_put_hex64(vcpu->sysregs.ttbr1_el1); uart_puts(" tcr="); uart_put_hex64(vcpu->sysregs.tcr_el1); uart_puts(" mair="); uart_put_hex64(vcpu->sysregs.mair_el1); uart_puts(" vbar="); uart_put_hex64(vcpu->sysregs.vbar_el1); uart_puts("\n"); } static bool command(const char *line, struct vcpu *vcpu) { if (hv_strcmp(line, "help") == 0) { help(); } else if (hv_strcmp(line, "status") == 0) { vcpu_dump(vcpu); timer_dump(); gicv3_dump(); } else if (hv_strcmp(line, "vcpu") == 0 || hv_strcmp(line, "regs") == 0) { vcpu_dump(vcpu); } else if (hv_strcmp(line, "snapshot") == 0) { monitor_snapshot(vcpu); } else if (hv_strcmp(line, "snapshot show") == 0) { monitor_snapshot_show(); } else if (hv_strcmp(line, "restore") == 0) { monitor_restore(vcpu); } else if (hv_strcmp(line, "sysregs") == 0) { show_sysregs(vcpu); } else if (hv_strcmp(line, "log") == 0) { log_dump(128u); } else if (hv_strcmp(line, "log clear") == 0) { #if CONFIG_MONITOR_MUTATION log_clear(); #else uart_puts("log clear disabled\n"); #endif } else if (hv_strcmp(line, "policy") == 0) { policy_dump(); } else if (starts_with_command(line, "profile")) { monitor_profile(line + hv_strlen("profile")); } else if (hv_strcmp(line, "psci") == 0) { psci_dump(); } else if (hv_strcmp(line, "mappings") == 0) { stage2_dump(); allocator_dump(); } else if (hv_strcmp(line, "mmio") == 0) { mmio_policy_dump(); } else if (hv_strcmp(line, "el2mmu") == 0) { el2_mmu_dump(); } else if (hv_strcmp(line, "fdt") == 0) { fdt_dump(platform_info()); } else if (starts_with_command(line, "translate")) { monitor_translate(line + hv_strlen("translate")); } else if (starts_with_command(line, "read64")) { monitor_read64(line + hv_strlen("read64")); } else if (starts_with_command(line, "write64")) { monitor_write64(line + hv_strlen("write64")); } else if (starts_with_command(line, "dump")) { monitor_dump(line + hv_strlen("dump")); } else if (starts_with_command(line, "unmap")) { monitor_unmap(line + hv_strlen("unmap")); } else if (starts_with_command(line, "irq")) { monitor_irq(line + hv_strlen("irq"), vcpu); } else if (hv_strcmp(line, "resume") == 0) { if (vcpu != (struct vcpu *)0) { vcpu->state = VCPU_RUNNING; } g_paused = false; return true; } else if (hv_strcmp(line, "pause") == 0) { g_paused = true; } else if (hv_strcmp(line, "reboot") == 0 || hv_strcmp(line, "shutdown") == 0) { #if CONFIG_MONITOR_MUTATION uart_puts("platform power control is not implemented; halting\n"); for (;;) { __asm__ volatile("wfe"); } #else uart_puts("power control disabled\n"); #endif } else if (line[0] != '\0') { uart_puts("error unknown-command\n"); } return false; } void monitor_init(void) { g_pos = 0; g_paused = false; g_snapshot_valid = false; } void monitor_poll(void) { char c; while (uart_getc_nonblocking(&c)) { if (c == '\r' || c == '\n') { g_line[g_pos] = '\0'; (void)command(g_line, vcpu_current()); g_pos = 0; uart_puts("hv> "); } else if ((c == '\b' || c == 0x7f) && g_pos != 0u) { g_pos--; } else if (g_pos + 1u < sizeof(g_line) && c >= ' ' && c <= '~') { g_line[g_pos++] = c; } else { uart_puts("error input-too-long\n"); g_pos = 0; } } } void monitor_loop(struct vcpu *vcpu, const char *reason) { g_paused = true; if (vcpu != (struct vcpu *)0) { vcpu->state = VCPU_PAUSED; } uart_puts("monitor entered reason="); uart_puts(reason); uart_puts("\nhv> "); while (g_paused) { monitor_poll(); __asm__ volatile("wfe"); } }