#include "hv/arch.h" #include "hv/config.h" #include "hv/gicv3.h" #include "hv/log.h" #include "hv/monitor.h" #include "hv/panic.h" #include "hv/policy.h" #include "hv/psci.h" #include "hv/stage2.h" #include "hv/string.h" #include "hv/sysreg.h" #include "hv/timer.h" #include "hv/trap.h" #include "hv/uart.h" #define HVC_GET_HV_ID 0u #define HVC_DUMP_LOG 1u #define HVC_GET_STATUS 2u #define HVC_PAUSE 3u #define HVC_GUEST_CONSOLE_WRITE 4u #define HVC_REPORT_EXCEPTION 5u #define HVC_ERR_UNKNOWN UINT64_MAX static uint32_t action_to_log(enum policy_action action) { switch (action) { case POLICY_EMULATE_READ: return LOG_ACT_EMULATE_READ; case POLICY_EMULATE_WRITE: return LOG_ACT_EMULATE_WRITE; case POLICY_WRITE_MASK: return LOG_ACT_WRITE_MASK; case POLICY_DENY_SKIP: return LOG_ACT_DENY_SKIP; case POLICY_DENY_UNDEF: return LOG_ACT_DENY_UNDEF; case POLICY_PAUSE: return LOG_ACT_PAUSE; case POLICY_PANIC: return LOG_ACT_PANIC; case POLICY_ALLOW_NATIVE: default: return LOG_ACT_NONE; } } static void log_trap_event(const struct vcpu *vcpu, const struct trap_frame *frame, const struct sysreg_access *access, const struct policy_decision *decision, uint64_t operand) { struct log_event ev; hv_memset(&ev, 0, sizeof(ev)); ev.kind = LOG_TRAP; ev.vcpu = vcpu->id; ev.ec = esr_ec(frame->esr_el2); ev.iss = frame->esr_el2 & 0x01ffffffu; ev.elr = frame->elr_el2; ev.far = frame->far_el2; ev.hpfar = frame->hpfar_el2; ev.pstate = frame->spsr_el2; if (access != (const struct sysreg_access *)0) { ev.sysreg_key = access->key; ev.is_read = access->is_read ? 1u : 0u; ev.rt = access->rt; ev.name = decision->entry != (const struct policy_entry *)0 ? decision->entry->name : sysreg_key_name(access->key); } if (decision != (const struct policy_decision *)0) { ev.action = action_to_log(decision->action); ev.result = decision->value; ev.reason = decision->reason; if (decision->entry != (const struct policy_entry *)0) { ev.policy_class = (uint32_t)decision->entry->access_class; } } ev.operand = operand; log_event_commit(&ev); } static bool guest_ptr_to_host(uint64_t ipa, uint64_t len, const char **out) { uint64_t end; if (out == (const char **)0 || hv_add_overflow_u64(ipa, len, &end)) { return false; } if (ipa < CONFIG_GUEST_IPA_BASE || end > (CONFIG_GUEST_IPA_BASE + CONFIG_GUEST_RAM_SIZE)) { return false; } *out = (const char *)(uintptr_t)(CONFIG_GUEST_RAM_BASE + (ipa - CONFIG_GUEST_IPA_BASE)); return true; } static void handle_hvc(struct vcpu *vcpu) { uint64_t num = vcpu_read_gpr(vcpu, 0u); vcpu->stats.hvc_calls++; struct log_event ev; hv_memset(&ev, 0, sizeof(ev)); ev.kind = LOG_HVC; ev.vcpu = vcpu->id; ev.ec = ESR_EC_HVC64; ev.iss = vcpu->last_esr & 0x01ffffffu; ev.elr = vcpu->pc; ev.far = vcpu->last_far; ev.hpfar = vcpu->last_hpfar; ev.pstate = vcpu->pstate; ev.operand = num; ev.action = LOG_ACT_NONE; ev.reason = 300u; switch (num) { case HVC_GET_HV_ID: vcpu_write_gpr(vcpu, 0u, 0x415249454cull); ev.result = 0x415249454cull; break; case HVC_DUMP_LOG: log_dump(64u); vcpu_write_gpr(vcpu, 0u, 0u); break; case HVC_GET_STATUS: vcpu_write_gpr(vcpu, 0u, (uint64_t)vcpu->state); ev.result = (uint64_t)vcpu->state; break; case HVC_PAUSE: vcpu->state = VCPU_PAUSED; vcpu_write_gpr(vcpu, 0u, 0u); log_event_commit(&ev); monitor_loop(vcpu, "guest requested pause"); return; case HVC_GUEST_CONSOLE_WRITE: { const char *s = (const char *)0; uint64_t ptr = vcpu_read_gpr(vcpu, 1u); uint64_t len = vcpu_read_gpr(vcpu, 2u); if (len > 256u || !guest_ptr_to_host(ptr, len, &s)) { vcpu_write_gpr(vcpu, 0u, HVC_ERR_UNKNOWN); ev.reason = 301u; break; } uart_puts("guest: "); for (uint64_t i = 0; i < len; i++) { uart_putc(s[i]); } uart_puts("\n"); vcpu_write_gpr(vcpu, 0u, 0u); break; } case HVC_REPORT_EXCEPTION: { uint64_t guest_esr = vcpu_read_gpr(vcpu, 1u); uint64_t guest_elr = vcpu_read_gpr(vcpu, 2u); uint64_t guest_far = vcpu_read_gpr(vcpu, 3u); struct log_event ge; hv_memset(&ge, 0, sizeof(ge)); ge.kind = LOG_ABORT; ge.vcpu = vcpu->id; ge.ec = esr_ec(guest_esr); ge.iss = guest_esr & 0x01ffffffu; ge.elr = guest_elr; ge.far = guest_far; ge.action = LOG_ACT_NONE; ge.reason = 303u; ge.pstate = vcpu->pstate; ge.name = "guest_exception_report"; log_event_commit(&ge); vcpu_write_gpr(vcpu, 0u, 0u); break; } default: if ((num & 0xffff0000ull) == 0x84000000ull || (num & 0xffff0000ull) == 0xc4000000ull) { log_event_commit(&ev); (void)psci_handle_call(vcpu, num); return; } vcpu_write_gpr(vcpu, 0u, HVC_ERR_UNKNOWN); ev.reason = 302u; break; } log_event_commit(&ev); } static void handle_sysreg(struct vcpu *vcpu, const struct trap_frame *frame) { struct sysreg_access access; struct policy_decision decision; uint64_t operand = 0; if (!sysreg_decode_from_esr_iss(frame->esr_el2 & 0x01ffffffu, &access)) { panic("failed to decode sysreg iss"); } if (!access.is_read) { operand = vcpu_read_gpr(vcpu, access.rt); } if (!policy_apply_sysreg(vcpu, &access, operand, &decision)) { panic("policy engine failed"); } if (access.is_read && decision.action == POLICY_EMULATE_READ) { vcpu_write_gpr(vcpu, access.rt, decision.value); } log_trap_event(vcpu, frame, &access, &decision, operand); vcpu->stats.sysreg_traps++; switch (decision.action) { case POLICY_EMULATE_READ: case POLICY_EMULATE_WRITE: case POLICY_WRITE_MASK: case POLICY_DENY_SKIP: arch_advance_guest_pc(vcpu); break; case POLICY_DENY_UNDEF: arch_inject_guest_undef(vcpu); if (vcpu->state == VCPU_PAUSED) { monitor_loop(vcpu, "policy could not inject undefined instruction"); } break; case POLICY_PAUSE: vcpu->state = VCPU_PAUSED; monitor_loop(vcpu, "policy pause"); break; case POLICY_PANIC: panic("policy panic action"); case POLICY_ALLOW_NATIVE: default: panic("unsafe native sysreg policy"); } } void trap_handle_lower_sync(struct trap_frame *frame) { struct vcpu *vcpu = vcpu_current(); if (vcpu == (struct vcpu *)0) { panic("trap without current vcpu"); } vcpu_load_frame(vcpu, frame); gicv3_sync_vcpu_state(vcpu); vcpu->stats.traps++; vcpu->state = VCPU_RUNNING; uint32_t ec = esr_ec(frame->esr_el2); switch (ec) { case ESR_EC_SYSREG: handle_sysreg(vcpu, frame); break; case ESR_EC_HVC64: handle_hvc(vcpu); break; case ESR_EC_SMC64: { uint64_t function_id = vcpu_read_gpr(vcpu, 0u); if ((function_id & 0xffff0000ull) == 0x84000000ull || (function_id & 0xffff0000ull) == 0xc4000000ull) { (void)psci_handle_call(vcpu, function_id); } else { vcpu->stats.smc_denied++; struct policy_decision d; hv_memset(&d, 0, sizeof(d)); d.action = POLICY_DENY_UNDEF; d.reason = 400u; log_trap_event(vcpu, frame, (const struct sysreg_access *)0, &d, 0u); arch_inject_guest_undef(vcpu); if (vcpu->state == VCPU_PAUSED) { monitor_loop(vcpu, "guest smc denied"); } } break; } case ESR_EC_WFI_WFE: vcpu->stats.wfx_traps++; log_simple(LOG_TRAP, 401u, frame->esr_el2, frame->elr_el2); arch_advance_guest_pc(vcpu); break; case ESR_EC_IABT_LOWER: vcpu->stats.instruction_aborts++; if (stage2_handle_abort(vcpu, frame, true)) { break; } monitor_loop(vcpu, "guest instruction abort"); break; case ESR_EC_DABT_LOWER: vcpu->stats.data_aborts++; if (stage2_handle_abort(vcpu, frame, false)) { break; } monitor_loop(vcpu, "guest data abort"); break; case ESR_EC_BRK64: case ESR_EC_UNKNOWN: default: vcpu->stats.unknown_exceptions++; log_simple(LOG_TRAP, 404u, frame->esr_el2, frame->elr_el2); monitor_loop(vcpu, "guest unknown or brk exception"); break; } timer_vcpu_sync(vcpu); gicv3_flush_vcpu_state(vcpu); arch_context_barrier(); vcpu_store_frame(vcpu, frame); } void trap_handle_current_sync(struct trap_frame *frame) { panic_with_code("unexpected EL2 synchronous exception", frame != (struct trap_frame *)0 ? frame->esr_el2 : 0u); } void trap_handle_irq(struct trap_frame *frame) { uint32_t irq = gicv3_ack_irq(); struct log_event ev; hv_memset(&ev, 0, sizeof(ev)); ev.kind = LOG_TRAP; ev.vcpu = vcpu_current() != (struct vcpu *)0 ? vcpu_current()->id : UINT64_MAX; ev.ec = 0xffu; ev.iss = irq; ev.elr = frame != (struct trap_frame *)0 ? frame->elr_el2 : 0u; ev.pstate = frame != (struct trap_frame *)0 ? frame->spsr_el2 : 0u; ev.reason = irq >= 1020u ? 501u : 500u; log_event_commit(&ev); if (irq < 1020u) { gicv3_eoi(irq); } }