diff --git a/hw/display/reims-vgpu-mmio.c b/hw/display/reims-vgpu-mmio.c index 736bdf62c4d53b665f83b3b127d2d8c422627146..06d37760ed80a415f92de9061e52fff28f0301a5 100644 --- a/hw/display/reims-vgpu-mmio.c +++ b/hw/display/reims-vgpu-mmio.c @@ -30,6 +30,7 @@ #include "hw/core/cpu.h" #include "hw/core/sysbus.h" #include "hw/core/irq.h" +#include "hw/vmapple/vmapple.h" #include "qom/object.h" #include "system/address-spaces.h" #include "system/hw_accel.h" @@ -171,26 +172,8 @@ static int reims_vgpu_mmio_window_main_loop(void) */ static int reims_vgpu_mmio_read_xreg(void *ctx, uint32_t index, uint64_t *out) { -#if defined(CONFIG_DARWIN) - CPUState *cs = current_cpu; - ARMCPU *cpu; - - if (!out || index >= 32) { - return -1; - } - if (!cs) { - return -1; - } - cpu_synchronize_state(cs); - cpu = ARM_CPU(cs); - *out = cpu->env.xregs[index]; - return 0; -#else (void)ctx; - (void)index; - (void)out; - return -1; -#endif + return vmapple_read_current_xreg(index, out) ? 0 : -1; } /* @@ -210,8 +193,11 @@ static int reims_vgpu_mmio_read_xreg(void *ctx, uint32_t index, uint64_t *out) * bought nothing and every fragmented map leaked a VA reservation until * teardown. `map_pages_stable` is 0 accordingly. * - * Non-Darwin hosts: fail closed (no mach_vm); type-11 writeback uses GPA - * copies through HostOps until a Linux aliasing path lands. + * Linux cannot manufacture a packed view for fragmented pages without + * file-backed guest RAM, but QEMU's ordinary RAMBlock mapping is already a + * stable alias. Accept runs that are contiguous in both guest-physical and + * host-virtual space (including every valid one-page request) and fail closed + * only for fragmented runs. */ static int reims_vgpu_mmio_map_pages(void *ctx, const uint64_t *gpas, size_t count, void **out_ptr) @@ -298,10 +284,46 @@ fail: g_free(hvas); return -1; #else - (void)ctx; - (void)gpas; - (void)count; - (void)out_ptr; + const hwaddr page = REIMS_VGPU_GUEST_PAGE_SIZE_ARM64E; + uint8_t *base = NULL; + MemoryRegion *base_mr = NULL; + size_t i; + + if (!ctx || !gpas || count == 0 || !out_ptr || + count > SIZE_MAX / page) { + return -1; + } + + rcu_read_lock(); + for (i = 0; i < count; i++) { + hwaddr xlat, plen = page; + MemoryRegion *mr; + uint8_t *hva; + + mr = address_space_translate(&address_space_memory, gpas[i], + &xlat, &plen, true, + MEMTXATTRS_UNSPECIFIED); + if (!mr || !memory_region_is_ram(mr) || plen < page) { + goto linux_fail; + } + hva = (uint8_t *)memory_region_get_ram_ptr(mr) + xlat; + if (i == 0) { + base = hva; + base_mr = mr; + if (((uintptr_t)base & (page - 1)) != 0) { + goto linux_fail; + } + } else if (mr != base_mr || hva != base + i * page) { + goto linux_fail; + } + } + rcu_read_unlock(); + + *out_ptr = base; + return 0; + +linux_fail: + rcu_read_unlock(); return -1; #endif } @@ -983,16 +1005,19 @@ static void reims_vgpu_mmio_realize(DeviceState *dev, Error **errp) .guest_ram_regions = reims_vgpu_shim_guest_ram_regions, .is_ram_gpa = reims_vgpu_shim_is_ram_gpa, /* - * 0: a fragmented list gets a packed mach_vm_remap view whose lifetime - * the caller owns and ends through unmap_pages. Only a pointer that - * needs no release at all may claim 1, and this shim cannot promise - * that without knowing the run was host-contiguous. + * Darwin can return transient packed mach_vm_remap views. Linux only + * accepts direct RAMBlock aliases, which remain valid for the VM + * lifetime and require no unmap. * * The GPU rail does not read this and must not: it imports the spans * guest_ram_regions names, which are RAMBlock mappings this shim never * built and never releases. */ +#if defined(CONFIG_DARWIN) .map_pages_stable = 0, +#else + .map_pages_stable = 1, +#endif .track_guest_writes = reims_vgpu_mmio_track_guest_writes, .untrack_guest_writes = reims_vgpu_mmio_untrack_guest_writes, .guest_write_gen = reims_vgpu_mmio_guest_write_gen, diff --git a/hw/vmapple/Kconfig b/hw/vmapple/Kconfig index 80dcfad984b581c7a9f85157315523a0d5c3e4b6..42e1f83648e92758ebe8a0acb955a79be54ce70f 100644 --- a/hw/vmapple/Kconfig +++ b/hw/vmapple/Kconfig @@ -14,9 +14,9 @@ config VMAPPLE_VIRTIO_BLK config VMAPPLE bool - depends on ARM - depends on HVF - default y if ARM + depends on AARCH64 + depends on TCG || HVF || KVM + default y imply PCI_DEVICES select ARM_GICV3 select PLATFORM_BUS @@ -30,6 +30,6 @@ config VMAPPLE select VMAPPLE_AES select VMAPPLE_BDIF select VMAPPLE_CFG - select MAC_PVG_MMIO + imply MAC_PVG_MMIO select REIMS_VGPU select VMAPPLE_VIRTIO_BLK diff --git a/hw/vmapple/vmapple.c b/hw/vmapple/vmapple.c index f404ddb0b1e8852bf151beb4bb87ca79e45da643..63714e9bf5e87a9f32c64f031ae536f2d9930886 100644 --- a/hw/vmapple/vmapple.c +++ b/hw/vmapple/vmapple.c @@ -31,6 +31,7 @@ #include "hw/core/qdev-properties.h" #include "hw/core/sysbus.h" #include "hw/usb/usb.h" +#include "hw/arm/bsa.h" #include "hw/arm/boot.h" #include "hw/arm/machines-qom.h" #include "hw/char/pl011.h" @@ -48,11 +49,14 @@ #include "qobject/qlist.h" #include "standard-headers/linux/input.h" #include "system/hvf.h" +#include "system/hw_accel.h" +#include "system/kvm.h" #include "system/reset.h" #include "system/runstate.h" #include "system/system.h" #include "target/arm/gtimer.h" #include "target/arm/cpu.h" +#include "kvm_arm.h" struct VMAppleMachineState { MachineState parent; @@ -75,6 +79,19 @@ struct VMAppleMachineState { #define TYPE_VMAPPLE_MACHINE MACHINE_TYPE_NAME("vmapple") OBJECT_DECLARE_SIMPLE_TYPE(VMAppleMachineState, VMAPPLE_MACHINE) +bool vmapple_read_current_xreg(unsigned int index, uint64_t *value) +{ + CPUState *cs = current_cpu; + + if (!cs || !value || index >= ARRAY_SIZE(ARM_CPU(cs)->env.xregs)) { + return false; + } + + cpu_synchronize_state(cs); + *value = ARM_CPU(cs)->env.xregs[index]; + return true; +} + /* Number of external interrupt lines to configure the GIC with */ #define NUM_IRQS 256 @@ -286,11 +303,33 @@ static void create_gic(VMAppleMachineState *vms, MemoryRegion *mem) */ for (i = 0; i < smp_cpus; i++) { DeviceState *cpudev = DEVICE(qemu_get_cpu(i)); + const int timer_irq[] = { + [GTIMER_PHYS] = ARCH_TIMER_NS_EL1_IRQ, + [GTIMER_VIRT] = ARCH_TIMER_VIRT_IRQ, + [GTIMER_HYP] = ARCH_TIMER_NS_EL2_IRQ, + [GTIMER_SEC] = ARCH_TIMER_S_EL1_IRQ, + }; + int timer; + + for (timer = 0; timer < ARRAY_SIZE(timer_irq); timer++) { + qdev_connect_gpio_out(cpudev, timer, + qdev_get_gpio_in(vms->gic, + arm_gic_ppi_index(i, timer_irq[timer]))); + } - /* Map the virt timer to PPI 27 */ - qdev_connect_gpio_out(cpudev, GTIMER_VIRT, - qdev_get_gpio_in(vms->gic, - arm_gic_ppi_index(i, 27))); + qdev_connect_gpio_out_named(cpudev, "gicv3-maintenance-interrupt", 0, + qdev_get_gpio_in(vms->gic, + arm_gic_ppi_index(i, + ARCH_GIC_MAINT_IRQ))); + qdev_connect_gpio_out_named(cpudev, "pmu-interrupt", 0, + qdev_get_gpio_in(vms->gic, + arm_gic_ppi_index(i, + VIRTUAL_PMU_IRQ))); + + if (kvm_enabled()) { + kvm_arm_pmu_set_irq(ARM_CPU(qemu_get_cpu(i)), VIRTUAL_PMU_IRQ); + kvm_arm_pmu_init(ARM_CPU(qemu_get_cpu(i))); + } /* Map the GIC IRQ and FIQ lines to CPU */ sysbus_connect_irq(gicbusdev, i, qdev_get_gpio_in(cpudev, ARM_CPU_IRQ)); @@ -515,6 +554,17 @@ static void mach_vmapple_init(MachineState *machine) object_property_set_int(cpu, "psci-conduit", QEMU_PSCI_CONDUIT_HVC, &error_fatal); + /* + * Ventura's ApplePSCI driver accepts PSCI through version 1.1 and + * refuses to attach when newer Linux KVM hosts expose PSCI 1.2/1.3. + * HVF and TCG already implement 1.1, so pin the KVM guest ABI to the + * same version before KVM_ARM_VCPU_INIT. + */ + if (kvm_enabled()) { + object_property_set_str(cpu, "kvm-psci-version", "1.1", + &error_fatal); + } + /* Secondary CPUs start in PSCI powered-down state */ if (n > 0) { object_property_set_bool(cpu, "start-powered-off", true, diff --git a/include/hw/vmapple/vmapple.h b/include/hw/vmapple/vmapple.h index 9c1ad1bd8c3c042acbabbaba8691b21381166118..cb49130ba0f7d0128470826dd062900bde3a9521 100644 --- a/include/hw/vmapple/vmapple.h +++ b/include/hw/vmapple/vmapple.h @@ -20,4 +20,6 @@ #define TYPE_VMAPPLE_VIRTIO_BLK_PCI "vmapple-virtio-blk-pci" +bool vmapple_read_current_xreg(unsigned int index, uint64_t *value); + #endif /* HW_VMAPPLE_VMAPPLE_H */ diff --git a/target/arm/kvm.c b/target/arm/kvm.c index a54ef51ec2afd05dd394aa33d26e085f4f53b2e0..bba51865d61dd28adb2d612340988f5c4e9fb4e3 100644 --- a/target/arm/kvm.c +++ b/target/arm/kvm.c @@ -610,6 +610,26 @@ int kvm_arch_init(MachineState *ms, KVMState *s) cap_has_mp_state = kvm_check_extension(s, KVM_CAP_MP_STATE); + if (g_getenv("QEMU_VMAPPLE_KVM_HVC")) { + struct kvm_smccc_filter filter = { + .base = 0xc1000000, + .nr_functions = 0x100, + .action = KVM_SMCCC_FILTER_FWD_TO_USER, + }; + struct kvm_device_attr attr = { + .group = KVM_ARM_VM_SMCCC_CTRL, + .attr = KVM_ARM_VM_SMCCC_FILTER, + .addr = (uintptr_t)&filter, + }; + + ret = kvm_vm_ioctl(s, KVM_SET_DEVICE_ATTR, &attr); + if (ret < 0) { + error_report("failed to forward VMApple CPU HVCs: %s", + strerror(-ret)); + return ret; + } + } + /* Check whether user space can specify guest syndrome value */ cap_has_inject_serror_esr = kvm_check_extension(s, KVM_CAP_ARM_INJECT_SERROR_ESR); @@ -1544,12 +1564,182 @@ static bool kvm_arm_handle_debug(ARMCPU *cpu, return false; } +#define AARCH64_CORE_REG(x) (KVM_REG_ARM64 | KVM_REG_SIZE_U64 | \ + KVM_REG_ARM_CORE | KVM_REG_ARM_CORE_REG(x)) + +#define VMAPPLE_DEFAULT_B_KEY 0xfeedfacefeedfacfULL +#define VMAPPLE_DEFAULT_EL0_KEY (VMAPPLE_DEFAULT_B_KEY + 4) +#define VMAPPLE_DEFAULT_A_KEY (VMAPPLE_DEFAULT_B_KEY + 6) +#define VMAPPLE_DEFAULT_G_KEY (VMAPPLE_DEFAULT_B_KEY + 10) +#define VMAPPLE_APCTL_EL12 ARM64_SYS_REG(3, 6, 15, 15, 0) +#define VMAPPLE_KERNKEYLO_EL12 ARM64_SYS_REG(3, 6, 15, 2, 3) +#define VMAPPLE_KERNKEYHI_EL12 ARM64_SYS_REG(3, 6, 15, 2, 4) + +static int kvm_arm_set_vmapple_key(CPUState *cs, uint64_t input, + const uint64_t *regs, size_t count) +{ + size_t i; + int ret; + + for (i = 0; i < count; i++) { + uint64_t value = input + i; + + ret = kvm_set_one_reg(cs, regs[i], &value); + if (ret) { + return ret; + } + } + + return 0; +} + +static int kvm_arm_set_vmapple_a_keys(CPUState *cs, uint64_t input) +{ + static const uint64_t regs[] = { + ARM64_SYS_REG(3, 0, 2, 1, 0), /* APIAKEYLO_EL1 */ + ARM64_SYS_REG(3, 0, 2, 1, 1), /* APIAKEYHI_EL1 */ + ARM64_SYS_REG(3, 0, 2, 2, 0), /* APDAKEYLO_EL1 */ + ARM64_SYS_REG(3, 0, 2, 2, 1), /* APDAKEYHI_EL1 */ + }; + + return kvm_arm_set_vmapple_key(cs, input, regs, ARRAY_SIZE(regs)); +} + +static int kvm_arm_set_vmapple_b_keys(CPUState *cs, uint64_t input) +{ + static const uint64_t regs[] = { + ARM64_SYS_REG(3, 0, 2, 1, 2), /* APIBKEYLO_EL1 */ + ARM64_SYS_REG(3, 0, 2, 1, 3), /* APIBKEYHI_EL1 */ + ARM64_SYS_REG(3, 0, 2, 2, 2), /* APDBKEYLO_EL1 */ + ARM64_SYS_REG(3, 0, 2, 2, 3), /* APDBKEYHI_EL1 */ + }; + + return kvm_arm_set_vmapple_key(cs, input, regs, ARRAY_SIZE(regs)); +} + +static int kvm_arm_set_vmapple_g_key(CPUState *cs, uint64_t input) +{ + static const uint64_t regs[] = { + ARM64_SYS_REG(3, 0, 2, 3, 0), /* APGAKEYLO_EL1 */ + ARM64_SYS_REG(3, 0, 2, 3, 1), /* APGAKEYHI_EL1 */ + }; + + return kvm_arm_set_vmapple_key(cs, input, regs, ARRAY_SIZE(regs)); +} + +static int kvm_arm_set_vmapple_el0_key(CPUState *cs, uint64_t input) +{ + static const uint64_t regs[] = { + VMAPPLE_KERNKEYLO_EL12, + VMAPPLE_KERNKEYHI_EL12, + }; + + return kvm_arm_set_vmapple_key(cs, input, regs, ARRAY_SIZE(regs)); +} + +static int kvm_arm_set_vmapple_apctl(CPUState *cs, bool el0_at_el1) +{ + uint64_t value = 0x19 | (el0_at_el1 ? 0x2 : 0); + + return kvm_set_one_reg(cs, VMAPPLE_APCTL_EL12, &value); +} + int kvm_arch_handle_exit(CPUState *cs, struct kvm_run *run) { ARMCPU *cpu = ARM_CPU(cs); int ret = 0; switch (run->exit_reason) { + case KVM_EXIT_HYPERCALL: + if (g_getenv("QEMU_VMAPPLE_KVM_HVC") && + run->hypercall.nr >= 0xc1000000 && + run->hypercall.nr < 0xc1000100) { + static unsigned int vmapple_hvc_count[256]; + uint64_t args[4]; + unsigned int function = run->hypercall.nr & 0xff; + int i; + + for (i = 0; i < ARRAY_SIZE(args); i++) { + ret = kvm_get_one_reg( + cs, AARCH64_CORE_REG(regs.regs[i + 1]), &args[i]); + if (ret) { + error_report("failed to read VMApple HVC x%d: %s", + i + 1, strerror(-ret)); + return ret; + } + } + + if (vmapple_hvc_count[function]++ < 20) { + warn_report("VMApple HVC %#" PRIx64 + " x1=%#" PRIx64 " x2=%#" PRIx64 + " x3=%#" PRIx64, + (uint64_t)run->hypercall.nr, + args[0], args[1], args[2]); + } + + if (function == 0) { + ret = kvm_arm_set_vmapple_apctl(cs, false); + if (!ret) { + ret = kvm_arm_set_vmapple_b_keys(cs, + VMAPPLE_DEFAULT_B_KEY); + } + if (!ret) { + ret = kvm_arm_set_vmapple_el0_key( + cs, VMAPPLE_DEFAULT_EL0_KEY); + } + if (!ret) { + ret = kvm_arm_set_vmapple_a_keys(cs, + VMAPPLE_DEFAULT_A_KEY); + } + if (!ret) { + ret = kvm_arm_set_vmapple_g_key(cs, + VMAPPLE_DEFAULT_G_KEY); + } + } + + if (function == 1) { + static const uint64_t defaults[] = { + VMAPPLE_DEFAULT_A_KEY, + VMAPPLE_DEFAULT_B_KEY, + VMAPPLE_DEFAULT_EL0_KEY, + VMAPPLE_DEFAULT_G_KEY, + }; + + for (i = 0; i < ARRAY_SIZE(defaults); i++) { + uint64_t value = defaults[i]; + + ret = kvm_set_one_reg( + cs, AARCH64_CORE_REG(regs.regs[i + 1]), &value); + if (ret) { + break; + } + } + } else if (function == 2) { + ret = kvm_arm_set_vmapple_a_keys(cs, args[0]); + } else if (function == 3) { + ret = kvm_arm_set_vmapple_b_keys(cs, args[0]); + } else if (function == 4) { + ret = kvm_arm_set_vmapple_el0_key(cs, args[0]); + } else if (function == 5) { + ret = kvm_arm_set_vmapple_el0_key(cs, args[1]); + if (!ret) { + ret = kvm_arm_set_vmapple_apctl(cs, args[0] != 0); + } + } else if (function == 6) { + ret = kvm_arm_set_vmapple_g_key(cs, args[0]); + } + + if (ret) { + error_report("failed to service VMApple PAC HVC %#x: %s", + function, strerror(-ret)); + return ret; + } + run->hypercall.ret = 0; + break; + } + qemu_log_mask(LOG_UNIMP, "%s: unhandled hypercall %#" PRIx64 "\n", + __func__, (uint64_t)run->hypercall.nr); + break; case KVM_EXIT_DEBUG: if (kvm_arm_handle_debug(cpu, &run->debug.arch)) { ret = EXCP_DEBUG; @@ -2083,9 +2273,6 @@ static void kvm_inject_arm_sea(CPUState *c) arm_cpu_do_interrupt(c); } -#define AARCH64_CORE_REG(x) (KVM_REG_ARM64 | KVM_REG_SIZE_U64 | \ - KVM_REG_ARM_CORE | KVM_REG_ARM_CORE_REG(x)) - #define AARCH64_SIMD_CORE_REG(x) (KVM_REG_ARM64 | KVM_REG_SIZE_U128 | \ KVM_REG_ARM_CORE | KVM_REG_ARM_CORE_REG(x))