/* hypercall.c — UTSM-side VMCALL hypercall handler. * * When the Linux guest executes a VMCALL instruction, it triggers a VM-Exit * with exit reason EXIT_VCALL (18). The guest places: * RAX = UTSM_HCALL_MAGIC | op (identifies this as a UTSM hypercall) * RDI = arg0 * RSI = arg1 * RDX = arg2 * * The VM-Exit assembly stub saves all guest GPRs into g_guest_regs before * calling the C dispatcher. This handler reads arguments from g_guest_regs, * dispatches to the operation handler, writes the return value into * g_guest_regs.rax, and advances GUEST_RIP so the guest resumes after * the VMCALL instruction. * * Protocol details in CODE/utsm-ipc/ipc_proto.h. */ #include #include #include #include #include #include #include #include #include "../arch/x86_64/limine.h" extern volatile struct limine_hhdm_request g_hhdm_request; static u64 g_hypercall_count; /* ===== Guest memory access helpers ===== * * The Linux guest uses identity-mapped page tables (GVA == GPA), so * pointers passed via hypercall arguments are GPAs. We translate GPA→HPA * via EPT walk, then add HHDM offset to get the host virtual address. */ static void *gpa_to_hva(u64 gpa) { u64 hpa = ept_gpa_to_hpa(gpa); if (hpa == 0) return (void *)0; if (!g_hhdm_request.response) return (void *)0; u64 hhdm = g_hhdm_request.response->offset; return (void *)(hhdm + hpa); } /* Write a u64 to guest memory at the given GPA. * Returns 0 on success, -1 on failure. */ static int guest_write_u64(u64 gpa, u64 value) { void *hva = gpa_to_hva(gpa); if (!hva) return -1; *(volatile u64 *)hva = value; return 0; } /* Copy bytes from guest memory (GVA=GPA) to host buffer. * Handles cross-page boundaries by translating each page separately. * Returns 0 on success, -1 on failure. */ static int guest_read_buf(u64 gpa, void *host_dst, u64 len) { u8 *dst = (u8 *)host_dst; u64 copied = 0; while (copied < len) { void *hva = gpa_to_hva(gpa + copied); if (!hva) return -1; u64 page_end = (gpa + copied + 4096) & ~0xFFFULL; u64 chunk = page_end - (gpa + copied); if (chunk > len - copied) chunk = len - copied; const u8 *src = (const u8 *)hva; for (u64 i = 0; i < chunk; i++) { dst[copied + i] = src[i]; } copied += chunk; } return 0; } /* ===== Individual hypercall handlers ===== */ static long hcall_ping(u64 a0, u64 a1, u64 a2) { (void)a0; (void)a1; (void)a2; /* Return a recognizable PONG value */ return 0x504F4E47; /* "PONG" */ } static long hcall_hello(u64 a0, u64 a1, u64 a2) { (void)a0; (void)a1; (void)a2; log_info("[HCALL] HELLO from Linux guest"); /* Linux is announcing itself; the IPC shared memory's linux_ready * flag is set separately when Linux maps the shared memory. */ if (ipc_shm_is_ready()) { /* Send a HELLO_ACK message via the shared memory ring */ const char *msg = "HELLO from UTSM"; ipc_shm_send(UTSM_MSG_HELLO_ACK, msg, 15); } return UTSM_HCALL_OK; } static long hcall_shm_info(u64 a0, u64 a1, u64 a2) { /* a0 = GPA of gpa_out (u64), a1 = GPA of size_out (u64) */ (void)a2; if (!ipc_shm_is_ready()) { return UTSM_HCALL_NOMEM; } u64 shm_gpa = ipc_shm_get_gpa(); u64 shm_size = ipc_shm_get_size(); /* Write results to guest memory */ if (a0 != 0 && guest_write_u64(a0, shm_gpa) != 0) { log_error("[HCALL] SHM_INFO: failed to write gpa to guest"); return UTSM_HCALL_INVAL; } if (a1 != 0 && guest_write_u64(a1, shm_size) != 0) { log_error("[HCALL] SHM_INFO: failed to write size to guest"); return UTSM_HCALL_INVAL; } log_hex64("[HCALL] SHM_INFO: gpa=", shm_gpa); log_hex64("[HCALL] SHM_INFO: size=", shm_size); return UTSM_HCALL_OK; } static long hcall_cap_validate(u64 a0, u64 a1, u64 a2) { /* Phase 1.3: stub — capability validation not yet implemented */ (void)a0; (void)a1; (void)a2; log_info("[HCALL] CAP_VALIDATE (stub)"); return UTSM_HCALL_NOSYS; } static long hcall_utrw_read(u64 a0, u64 a1, u64 a2) { /* Phase 1.3: stub — sealed memory read not yet implemented */ (void)a0; (void)a1; (void)a2; log_info("[HCALL] UTRW_READ (stub)"); return UTSM_HCALL_NOSYS; } static long hcall_utrw_write(u64 a0, u64 a1, u64 a2) { /* Phase 1.3: stub — sealed memory write not yet implemented */ (void)a0; (void)a1; (void)a2; log_info("[HCALL] UTRW_WRITE (stub)"); return UTSM_HCALL_NOSYS; } static long hcall_drr_checkpoint(u64 a0, u64 a1, u64 a2) { /* Phase 1.3: stub — DRR checkpoint trigger not yet implemented */ (void)a0; (void)a1; (void)a2; log_info("[HCALL] DRR_CHECKPOINT (stub)"); return UTSM_HCALL_NOSYS; } static long hcall_console_write(u64 a0, u64 a1, u64 a2) { /* a0 = GPA of string buffer, a1 = length */ (void)a2; if (a0 == 0 || a1 == 0) { return UTSM_HCALL_INVAL; } /* Cap length to prevent runaway reads */ if (a1 > 4096) a1 = 4096; /* Read the string from guest memory */ char buf[4097]; if (guest_read_buf(a0, buf, a1) != 0) { log_error("[HCALL] CONSOLE_WRITE: failed to read guest buffer"); return UTSM_HCALL_INVAL; } buf[a1] = '\0'; /* Output to UTSM serial console with a [LINUX] prefix */ serial_write("[LINUX] "); serial_write(buf); return (long)a1; } static long hcall_console_read(u64 a0, u64 a1, u64 a2) { /* Phase 1.3: stub — console read not yet implemented */ (void)a0; (void)a1; (void)a2; return UTSM_HCALL_NOSYS; } /* ===== Main hypercall dispatcher ===== */ int hypercall_handle(u64 guest_rax, u64 guest_rdi, u64 guest_rsi, u64 guest_rdx, u64 guest_rip, u64 instr_len) { g_hypercall_count++; /* Validate magic: the high bits of RAX must be UTSM_HCALL_MAGIC */ if ((guest_rax & ~0xFFFFULL) != UTSM_HCALL_MAGIC) { log_hex64("[HCALL] bad magic: ", guest_rax); /* Return error in guest RAX */ g_guest_regs.rax = (u64)UTSM_HCALL_INVAL; /* Still advance RIP to avoid infinite loop */ vmx_vmcs_write(VMCS_GUEST_RIP, guest_rip + instr_len); return 1; /* resume guest */ } /* Extract operation from low 16 bits */ u64 op = guest_rax & 0xFFFFULL; long result = UTSM_HCALL_NOSYS; switch (op) { case UTSM_HCALL_PING: result = hcall_ping(guest_rdi, guest_rsi, guest_rdx); break; case UTSM_HCALL_HELLO: result = hcall_hello(guest_rdi, guest_rsi, guest_rdx); break; case UTSM_HCALL_SHM_INFO: result = hcall_shm_info(guest_rdi, guest_rsi, guest_rdx); break; case UTSM_HCALL_CAP_VALIDATE: result = hcall_cap_validate(guest_rdi, guest_rsi, guest_rdx); break; case UTSM_HCALL_UTRW_READ: result = hcall_utrw_read(guest_rdi, guest_rsi, guest_rdx); break; case UTSM_HCALL_UTRW_WRITE: result = hcall_utrw_write(guest_rdi, guest_rsi, guest_rdx); break; case UTSM_HCALL_DRR_CHECKPOINT: result = hcall_drr_checkpoint(guest_rdi, guest_rsi, guest_rdx); break; case UTSM_HCALL_CONSOLE_WRITE: result = hcall_console_write(guest_rdi, guest_rsi, guest_rdx); break; case UTSM_HCALL_CONSOLE_READ: result = hcall_console_read(guest_rdi, guest_rsi, guest_rdx); break; default: log_hex64("[HCALL] unknown op: ", op); result = UTSM_HCALL_NOSYS; break; } /* Write return value into guest RAX (restored by assembly before vmresume) */ g_guest_regs.rax = (u64)result; /* Advance guest RIP past the VMCALL instruction */ vmx_vmcs_write(VMCS_GUEST_RIP, guest_rip + instr_len); return 1; /* resume guest */ } u64 hypercall_get_count(void) { return g_hypercall_count; }