From 35703a784e58e9700f79766637b0af966aee9bfe Mon Sep 17 00:00:00 2001 From: Konstantin Nazarov Date: Fri, 7 May 2021 14:59:38 +0000 Subject: [PATCH] Add 3rd version of qemu patchset: rebased to latest master --- Patches/qemu-v03.diff | 11034 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 11034 insertions(+) create mode 100644 Patches/qemu-v03.diff diff --git a/Patches/qemu-v03.diff b/Patches/qemu-v03.diff new file mode 100644 index 0000000..c55b99c --- /dev/null +++ b/Patches/qemu-v03.diff @@ -0,0 +1,11034 @@ +diff --git a/MAINTAINERS b/MAINTAINERS +index f880f45a59..cdf3f79fd0 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -457,13 +457,26 @@ F: accel/accel-*.c + F: accel/Makefile.objs + F: accel/stubs/Makefile.objs + ++Apple Silicon HVF CPUs ++M: Alexander Graf ++S: Maintained ++F: target/arm/hvf/ ++ + X86 HVF CPUs + M: Cameron Esfahani + M: Roman Bolshakov + W: https://wiki.qemu.org/Features/HVF + S: Maintained + F: target/i386/hvf/ ++ ++HVF ++M: Cameron Esfahani ++M: Roman Bolshakov ++W: https://wiki.qemu.org/Features/HVF ++S: Maintained ++F: accel/hvf/ + F: include/sysemu/hvf.h ++F: include/sysemu/hvf_int.h + + WHPX CPUs + M: Sunil Muthuswamy +diff --git a/accel/hvf/hvf-accel.c b/accel/hvf/hvf-accel.c +new file mode 100644 +index 0000000000..971424b179 +--- /dev/null ++++ b/accel/hvf/hvf-accel.c +@@ -0,0 +1,477 @@ ++/* ++ * Copyright 2008 IBM Corporation ++ * 2008 Red Hat, Inc. ++ * Copyright 2011 Intel Corporation ++ * Copyright 2016 Veertu, Inc. ++ * Copyright 2017 The Android Open Source Project ++ * ++ * QEMU Hypervisor.framework support ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of version 2 of the GNU General Public ++ * License as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ * ++ * This file contain code under public domain from the hvdos project: ++ * https://github.com/mist64/hvdos ++ * ++ * Parts Copyright (c) 2011 NetApp, Inc. ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND ++ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE ++ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ++ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ++ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ++ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ++ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++ * SUCH DAMAGE. ++ */ ++ ++#include "qemu/osdep.h" ++#include "qemu/error-report.h" ++#include "qemu/main-loop.h" ++#include "exec/address-spaces.h" ++#include "exec/exec-all.h" ++#include "sysemu/cpus.h" ++#include "sysemu/hvf.h" ++#include "sysemu/hvf_int.h" ++#include "sysemu/runstate.h" ++#include "qemu/guest-random.h" ++ ++#ifdef __aarch64__ ++#define HV_VM_DEFAULT NULL ++#endif ++ ++/* Memory slots */ ++ ++struct mac_slot { ++ int present; ++ uint64_t size; ++ uint64_t gpa_start; ++ uint64_t gva; ++}; ++ ++hvf_slot *hvf_find_overlap_slot(uint64_t start, uint64_t size) ++{ ++ hvf_slot *slot; ++ int x; ++ for (x = 0; x < hvf_state->num_slots; ++x) { ++ slot = &hvf_state->slots[x]; ++ if (slot->size && start < (slot->start + slot->size) && ++ (start + size) > slot->start) { ++ return slot; ++ } ++ } ++ return NULL; ++} ++ ++struct mac_slot mac_slots[32]; ++ ++static int do_hvf_set_memory(hvf_slot *slot, hv_memory_flags_t flags) ++{ ++ struct mac_slot *macslot; ++ hv_return_t ret; ++ ++ macslot = &mac_slots[slot->slot_id]; ++ ++ if (macslot->present) { ++ if (macslot->size != slot->size) { ++ macslot->present = 0; ++ ret = hv_vm_unmap(macslot->gpa_start, macslot->size); ++ assert_hvf_ok(ret); ++ } ++ } ++ ++ if (!slot->size) { ++ return 0; ++ } ++ ++ macslot->present = 1; ++ macslot->gpa_start = slot->start; ++ macslot->size = slot->size; ++ ret = hv_vm_map(slot->mem, slot->start, slot->size, flags); ++ assert_hvf_ok(ret); ++ return 0; ++} ++ ++static void hvf_set_phys_mem(MemoryRegionSection *section, bool add) ++{ ++ hvf_slot *mem; ++ MemoryRegion *area = section->mr; ++ bool writeable = !area->readonly && !area->rom_device; ++ hv_memory_flags_t flags; ++ ++ if (!memory_region_is_ram(area)) { ++ if (writeable) { ++ return; ++ } else if (!memory_region_is_romd(area)) { ++ /* ++ * If the memory device is not in romd_mode, then we actually want ++ * to remove the hvf memory slot so all accesses will trap. ++ */ ++ add = false; ++ } ++ } ++ ++ mem = hvf_find_overlap_slot( ++ section->offset_within_address_space, ++ int128_get64(section->size)); ++ ++ if (mem && add) { ++ if (mem->size == int128_get64(section->size) && ++ mem->start == section->offset_within_address_space && ++ mem->mem == (memory_region_get_ram_ptr(area) + ++ section->offset_within_region)) { ++ return; /* Same region was attempted to register, go away. */ ++ } ++ } ++ ++ /* Region needs to be reset. set the size to 0 and remap it. */ ++ if (mem) { ++ mem->size = 0; ++ if (do_hvf_set_memory(mem, 0)) { ++ error_report("Failed to reset overlapping slot"); ++ abort(); ++ } ++ } ++ ++ if (!add) { ++ return; ++ } ++ ++ if (area->readonly || ++ (!memory_region_is_ram(area) && memory_region_is_romd(area))) { ++ flags = HV_MEMORY_READ | HV_MEMORY_EXEC; ++ } else { ++ flags = HV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC; ++ } ++ ++ /* Now make a new slot. */ ++ int x; ++ ++ for (x = 0; x < hvf_state->num_slots; ++x) { ++ mem = &hvf_state->slots[x]; ++ if (!mem->size) { ++ break; ++ } ++ } ++ ++ if (x == hvf_state->num_slots) { ++ error_report("No free slots"); ++ abort(); ++ } ++ ++ mem->size = int128_get64(section->size); ++ mem->mem = memory_region_get_ram_ptr(area) + section->offset_within_region; ++ mem->start = section->offset_within_address_space; ++ mem->region = area; ++ ++ if (do_hvf_set_memory(mem, flags)) { ++ error_report("Error registering new memory slot"); ++ abort(); ++ } ++} ++ ++static void hvf_set_dirty_tracking(MemoryRegionSection *section, bool on) ++{ ++ hvf_slot *slot; ++ ++ slot = hvf_find_overlap_slot( ++ section->offset_within_address_space, ++ int128_get64(section->size)); ++ ++ /* protect region against writes; begin tracking it */ ++ if (on) { ++ slot->flags |= HVF_SLOT_LOG; ++ hv_vm_protect((uintptr_t)slot->start, (size_t)slot->size, ++ HV_MEMORY_READ); ++ /* stop tracking region*/ ++ } else { ++ slot->flags &= ~HVF_SLOT_LOG; ++ hv_vm_protect((uintptr_t)slot->start, (size_t)slot->size, ++ HV_MEMORY_READ | HV_MEMORY_WRITE); ++ } ++} ++ ++static void hvf_log_start(MemoryListener *listener, ++ MemoryRegionSection *section, int old, int new) ++{ ++ if (old != 0) { ++ return; ++ } ++ ++ hvf_set_dirty_tracking(section, 1); ++} ++ ++static void hvf_log_stop(MemoryListener *listener, ++ MemoryRegionSection *section, int old, int new) ++{ ++ if (new != 0) { ++ return; ++ } ++ ++ hvf_set_dirty_tracking(section, 0); ++} ++ ++static void hvf_log_sync(MemoryListener *listener, ++ MemoryRegionSection *section) ++{ ++ /* ++ * sync of dirty pages is handled elsewhere; just make sure we keep ++ * tracking the region. ++ */ ++ hvf_set_dirty_tracking(section, 1); ++} ++ ++static void hvf_region_add(MemoryListener *listener, ++ MemoryRegionSection *section) ++{ ++ hvf_set_phys_mem(section, true); ++} ++ ++static void hvf_region_del(MemoryListener *listener, ++ MemoryRegionSection *section) ++{ ++ hvf_set_phys_mem(section, false); ++} ++ ++static MemoryListener hvf_memory_listener = { ++ .priority = 10, ++ .region_add = hvf_region_add, ++ .region_del = hvf_region_del, ++ .log_start = hvf_log_start, ++ .log_stop = hvf_log_stop, ++ .log_sync = hvf_log_sync, ++}; ++ ++static void do_hvf_cpu_synchronize_state(CPUState *cpu, run_on_cpu_data arg) ++{ ++ if (!cpu->vcpu_dirty) { ++ hvf_get_registers(cpu); ++ cpu->vcpu_dirty = true; ++ } ++} ++ ++static void hvf_cpu_synchronize_state(CPUState *cpu) ++{ ++ if (!cpu->vcpu_dirty) { ++ run_on_cpu(cpu, do_hvf_cpu_synchronize_state, RUN_ON_CPU_NULL); ++ } ++} ++ ++static void do_hvf_cpu_synchronize_set_dirty(CPUState *cpu, ++ run_on_cpu_data arg) ++{ ++ /* QEMU state is the reference, push it to HVF now and on next entry */ ++ cpu->vcpu_dirty = true; ++} ++ ++static void hvf_cpu_synchronize_post_reset(CPUState *cpu) ++{ ++ run_on_cpu(cpu, do_hvf_cpu_synchronize_set_dirty, RUN_ON_CPU_NULL); ++} ++ ++static void hvf_cpu_synchronize_post_init(CPUState *cpu) ++{ ++ run_on_cpu(cpu, do_hvf_cpu_synchronize_set_dirty, RUN_ON_CPU_NULL); ++} ++ ++static void hvf_cpu_synchronize_pre_loadvm(CPUState *cpu) ++{ ++ run_on_cpu(cpu, do_hvf_cpu_synchronize_set_dirty, RUN_ON_CPU_NULL); ++} ++ ++static void hvf_vcpu_destroy(CPUState *cpu) ++{ ++ hv_return_t ret = hv_vcpu_destroy(cpu->hvf->fd); ++ assert_hvf_ok(ret); ++ ++ hvf_arch_vcpu_destroy(cpu); ++ g_free(cpu->hvf); ++ cpu->hvf = NULL; ++} ++ ++static void dummy_signal(int sig) ++{ ++} ++ ++static int hvf_init_vcpu(CPUState *cpu) ++{ ++ int r; ++ ++ cpu->hvf = g_malloc0(sizeof(*cpu->hvf)); ++ ++ /* init cpu signals */ ++ struct sigaction sigact; ++ ++ memset(&sigact, 0, sizeof(sigact)); ++ sigact.sa_handler = dummy_signal; ++ sigaction(SIG_IPI, &sigact, NULL); ++ ++ pthread_sigmask(SIG_BLOCK, NULL, &cpu->hvf->unblock_ipi_mask); ++ sigdelset(&cpu->hvf->unblock_ipi_mask, SIG_IPI); ++ ++#ifdef __aarch64__ ++ r = hv_vcpu_create(&cpu->hvf->fd, (hv_vcpu_exit_t **)&cpu->hvf->exit, NULL); ++#else ++ r = hv_vcpu_create((hv_vcpuid_t *)&cpu->hvf->fd, HV_VCPU_DEFAULT); ++#endif ++ cpu->vcpu_dirty = 1; ++ assert_hvf_ok(r); ++ ++ return hvf_arch_init_vcpu(cpu); ++} ++ ++/* ++ * The HVF-specific vCPU thread function. This one should only run when the host ++ * CPU supports the VMX "unrestricted guest" feature. ++ */ ++static void *hvf_cpu_thread_fn(void *arg) ++{ ++ CPUState *cpu = arg; ++ ++ int r; ++ ++ assert(hvf_enabled()); ++ ++ rcu_register_thread(); ++ ++ qemu_mutex_lock_iothread(); ++ qemu_thread_get_self(cpu->thread); ++ ++ cpu->thread_id = qemu_get_thread_id(); ++ cpu->can_do_io = 1; ++ current_cpu = cpu; ++ ++ hvf_init_vcpu(cpu); ++ ++ /* signal CPU creation */ ++ cpu_thread_signal_created(cpu); ++ qemu_guest_random_seed_thread_part2(cpu->random_seed); ++ ++ do { ++ if (cpu_can_run(cpu)) { ++ r = hvf_vcpu_exec(cpu); ++ if (r == EXCP_DEBUG) { ++ cpu_handle_guest_debug(cpu); ++ } ++ } ++ qemu_wait_io_event(cpu); ++ } while (!cpu->unplug || cpu_can_run(cpu)); ++ ++ hvf_vcpu_destroy(cpu); ++ cpu_thread_signal_destroyed(cpu); ++ qemu_mutex_unlock_iothread(); ++ rcu_unregister_thread(); ++ return NULL; ++} ++ ++static void hvf_start_vcpu_thread(CPUState *cpu) ++{ ++ char thread_name[VCPU_THREAD_NAME_SIZE]; ++ ++ /* ++ * HVF currently does not support TCG, and only runs in ++ * unrestricted-guest mode. ++ */ ++ assert(hvf_enabled()); ++ ++ cpu->thread = g_malloc0(sizeof(QemuThread)); ++ cpu->halt_cond = g_malloc0(sizeof(QemuCond)); ++ qemu_cond_init(cpu->halt_cond); ++ ++ snprintf(thread_name, VCPU_THREAD_NAME_SIZE, "CPU %d/HVF", ++ cpu->cpu_index); ++ qemu_thread_create(cpu->thread, thread_name, hvf_cpu_thread_fn, ++ cpu, QEMU_THREAD_JOINABLE); ++} ++ ++__attribute__((weak)) void hvf_kick_vcpu_thread(CPUState *cpu) ++{ ++ cpus_kick_thread(cpu); ++} ++ ++static void hvf_accel_ops_class_init(ObjectClass *oc, void *data) ++{ ++ AccelOpsClass *ops = ACCEL_OPS_CLASS(oc); ++ ++ ops->create_vcpu_thread = hvf_start_vcpu_thread; ++ ops->kick_vcpu_thread = hvf_kick_vcpu_thread; ++ ++ ops->synchronize_post_reset = hvf_cpu_synchronize_post_reset; ++ ops->synchronize_post_init = hvf_cpu_synchronize_post_init; ++ ops->synchronize_state = hvf_cpu_synchronize_state; ++ ops->synchronize_pre_loadvm = hvf_cpu_synchronize_pre_loadvm; ++}; ++ ++static const TypeInfo hvf_accel_ops_type = { ++ .name = ACCEL_OPS_NAME("hvf"), ++ ++ .parent = TYPE_ACCEL_OPS, ++ .class_init = hvf_accel_ops_class_init, ++ .abstract = true, ++}; ++ ++static int hvf_accel_init(MachineState *ms) ++{ ++ int x; ++ hv_return_t ret; ++ HVFState *s; ++ ++ ret = hv_vm_create(HV_VM_DEFAULT); ++ assert_hvf_ok(ret); ++ ++ s = g_new0(HVFState, 1); ++ ++ s->num_slots = 32; ++ for (x = 0; x < s->num_slots; ++x) { ++ s->slots[x].size = 0; ++ s->slots[x].slot_id = x; ++ } ++ ++ hvf_state = s; ++ memory_listener_register(&hvf_memory_listener, &address_space_memory); ++ return 0; ++} ++ ++static void hvf_accel_class_init(ObjectClass *oc, void *data) ++{ ++ AccelClass *ac = ACCEL_CLASS(oc); ++ ac->name = "HVF"; ++ ac->init_machine = hvf_accel_init; ++ ac->allowed = &hvf_allowed; ++} ++ ++static const TypeInfo hvf_accel_type = { ++ .name = TYPE_HVF_ACCEL, ++ .parent = TYPE_ACCEL, ++ .class_init = hvf_accel_class_init, ++}; ++ ++static void hvf_type_init(void) ++{ ++ type_register_static(&hvf_accel_ops_type); ++ type_register_static(&hvf_accel_type); ++} ++ ++type_init(hvf_type_init); +diff --git a/accel/hvf/hvf-all.c b/accel/hvf/hvf-all.c +new file mode 100644 +index 0000000000..5dc79e71cd +--- /dev/null ++++ b/accel/hvf/hvf-all.c +@@ -0,0 +1,54 @@ ++/* ++ * QEMU Hypervisor.framework support ++ * ++ * This work is licensed under the terms of the GNU GPL, version 2. See ++ * the COPYING file in the top-level directory. ++ * ++ * Contributions after 2012-01-13 are licensed under the terms of the ++ * GNU GPL, version 2 or (at your option) any later version. ++ */ ++ ++#include "qemu/osdep.h" ++#include "qemu-common.h" ++#include "qemu/error-report.h" ++#include "sysemu/hvf.h" ++#include "sysemu/hvf_int.h" ++#include "sysemu/runstate.h" ++ ++#include "qemu/main-loop.h" ++#include "qemu/accel.h" ++ ++bool hvf_allowed; ++HVFState *hvf_state; ++ ++void assert_hvf_ok(hv_return_t ret) ++{ ++ if (ret == HV_SUCCESS) { ++ return; ++ } ++ ++ switch (ret) { ++ case HV_ERROR: ++ error_report("Error: HV_ERROR"); ++ break; ++ case HV_BUSY: ++ error_report("Error: HV_BUSY"); ++ break; ++ case HV_BAD_ARGUMENT: ++ error_report("Error: HV_BAD_ARGUMENT"); ++ break; ++ case HV_NO_RESOURCES: ++ error_report("Error: HV_NO_RESOURCES"); ++ break; ++ case HV_NO_DEVICE: ++ error_report("Error: HV_NO_DEVICE"); ++ break; ++ case HV_UNSUPPORTED: ++ error_report("Error: HV_UNSUPPORTED"); ++ break; ++ default: ++ error_report("Unknown Error"); ++ } ++ ++ abort(); ++} +diff --git a/accel/hvf/meson.build b/accel/hvf/meson.build +new file mode 100644 +index 0000000000..93867871b9 +--- /dev/null ++++ b/accel/hvf/meson.build +@@ -0,0 +1,7 @@ ++hvf_ss = ss.source_set() ++hvf_ss.add(files( ++ 'hvf-all.c', ++ 'hvf-accel.c', ++)) ++ ++specific_ss.add_all(when: 'CONFIG_HVF', if_true: hvf_ss) +diff --git a/accel/meson.build b/accel/meson.build +index b44ba30c86..dfd808d2c8 100644 +--- a/accel/meson.build ++++ b/accel/meson.build +@@ -2,6 +2,7 @@ specific_ss.add(files('accel-common.c')) + softmmu_ss.add(files('accel-softmmu.c')) + user_ss.add(files('accel-user.c')) + ++subdir('hvf') + subdir('qtest') + subdir('kvm') + subdir('tcg') +diff --git a/block/file-posix.c b/block/file-posix.c +index 10b71d9a13..8763779658 100644 +--- a/block/file-posix.c ++++ b/block/file-posix.c +@@ -44,6 +44,7 @@ + #if defined(__APPLE__) && (__MACH__) + #include + #include ++#include + #include + #include + #include +@@ -1263,6 +1264,8 @@ static int hdev_probe_blocksizes(BlockDriverState *bs, BlockSizes *bsz) + if (check_for_dasd(s->fd) < 0) { + return -ENOTSUP; + } ++ bsz->opt_io = 0; ++ bsz->discard_granularity = -1; + ret = probe_logical_blocksize(s->fd, &bsz->log); + if (ret < 0) { + return ret; +@@ -1557,6 +1560,7 @@ out: + } + } + ++G_GNUC_UNUSED + static int translate_err(int err) + { + if (err == -ENODEV || err == -ENOSYS || err == -EOPNOTSUPP || +@@ -1766,16 +1770,27 @@ static int handle_aiocb_discard(void *opaque) + } + } while (errno == EINTR); + +- ret = -errno; ++ ret = translate_err(-errno); + #endif + } else { + #ifdef CONFIG_FALLOCATE_PUNCH_HOLE + ret = do_fallocate(s->fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, + aiocb->aio_offset, aiocb->aio_nbytes); ++ ret = translate_err(-errno); ++#elif defined(__APPLE__) && (__MACH__) ++ fpunchhole_t fpunchhole; ++ fpunchhole.fp_flags = 0; ++ fpunchhole.reserved = 0; ++ fpunchhole.fp_offset = aiocb->aio_offset; ++ fpunchhole.fp_length = aiocb->aio_nbytes; ++ if (fcntl(s->fd, F_PUNCHHOLE, &fpunchhole) == -1) { ++ ret = errno == ENODEV ? -ENOTSUP : -errno; ++ } else { ++ ret = 0; ++ } + #endif + } + +- ret = translate_err(ret); + if (ret == -ENOTSUP) { + s->has_discard = false; + } +@@ -2084,6 +2099,26 @@ static int raw_co_flush_to_disk(BlockDriverState *bs) + return raw_thread_pool_submit(bs, handle_aiocb_flush, &acb); + } + ++static int raw_probe_blocksizes(BlockDriverState *bs, BlockSizes *bsz) ++{ ++#if defined(__APPLE__) && (__MACH__) ++ BDRVRawState *s = bs->opaque; ++ struct statfs buf; ++ ++ if (!fstatfs(s->fd, &buf)) { ++ bsz->phys = 0; ++ bsz->log = 0; ++ bsz->opt_io = buf.f_iosize; ++ bsz->discard_granularity = buf.f_bsize; ++ return 0; ++ } ++ ++ return -errno; ++#else ++ return -ENOTSUP; ++#endif ++} ++ + static void raw_aio_attach_aio_context(BlockDriverState *bs, + AioContext *new_context) + { +@@ -3209,6 +3244,7 @@ BlockDriver bdrv_file = { + .bdrv_refresh_limits = raw_refresh_limits, + .bdrv_io_plug = raw_aio_plug, + .bdrv_io_unplug = raw_aio_unplug, ++ .bdrv_probe_blocksizes = raw_probe_blocksizes, + .bdrv_attach_aio_context = raw_aio_attach_aio_context, + + .bdrv_co_truncate = raw_co_truncate, +diff --git a/block/nvme.c b/block/nvme.c +index 2b5421e7aa..1845d07577 100644 +--- a/block/nvme.c ++++ b/block/nvme.c +@@ -989,6 +989,8 @@ static int nvme_probe_blocksizes(BlockDriverState *bs, BlockSizes *bsz) + uint32_t blocksize = nvme_get_blocksize(bs); + bsz->phys = blocksize; + bsz->log = blocksize; ++ bsz->opt_io = 0; ++ bsz->discard_granularity = -1; + return 0; + } + +diff --git a/block/raw-format.c b/block/raw-format.c +index 7717578ed6..847df11f2a 100644 +--- a/block/raw-format.c ++++ b/block/raw-format.c +@@ -507,6 +507,7 @@ static int raw_probe(const uint8_t *buf, int buf_size, const char *filename) + static int raw_probe_blocksizes(BlockDriverState *bs, BlockSizes *bsz) + { + BDRVRawState *s = bs->opaque; ++ uint32_t size; + int ret; + + ret = bdrv_probe_blocksizes(bs->file->bs, bsz); +@@ -514,7 +515,8 @@ static int raw_probe_blocksizes(BlockDriverState *bs, BlockSizes *bsz) + return ret; + } + +- if (!QEMU_IS_ALIGNED(s->offset, MAX(bsz->log, bsz->phys))) { ++ size = MAX(bsz->log, bsz->phys); ++ if (size && !QEMU_IS_ALIGNED(s->offset, size)) { + return -ENOTSUP; + } + +diff --git a/configure b/configure +index 4f374b4889..49fb478ac9 100755 +--- a/configure ++++ b/configure +@@ -394,6 +394,7 @@ u2f="auto" + libusb="$default_feature" + usb_redir="$default_feature" + opengl="$default_feature" ++egl="no" + cpuid_h="no" + avx2_opt="$default_feature" + capstone="auto" +@@ -3623,18 +3624,7 @@ if $pkg_config gbm; then + fi + + if test "$opengl" != "no" ; then +- epoxy=no + if $pkg_config epoxy; then +- cat > $TMPC << EOF +-#include +-int main(void) { return 0; } +-EOF +- if compile_prog "" "" ; then +- epoxy=yes +- fi +- fi +- +- if test "$epoxy" = "yes" ; then + opengl_cflags="$($pkg_config --cflags epoxy)" + opengl_libs="$($pkg_config --libs epoxy)" + opengl=yes +@@ -3648,6 +3638,16 @@ EOF + fi + fi + ++if test "$opengl" = "yes"; then ++ cat > $TMPC << EOF ++#include ++int main(void) { return 0; } ++EOF ++ if compile_prog "" "" ; then ++ egl=yes ++ fi ++fi ++ + ########################################## + # libxml2 probe + if test "$libxml2" != "no" ; then +@@ -5835,6 +5835,9 @@ if test "$opengl" = "yes" ; then + echo "CONFIG_OPENGL=y" >> $config_host_mak + echo "OPENGL_CFLAGS=$opengl_cflags" >> $config_host_mak + echo "OPENGL_LIBS=$opengl_libs" >> $config_host_mak ++ if test "$egl" = "yes" ; then ++ echo "CONFIG_EGL=y" >> $config_host_mak ++ fi + fi + + if test "$gbm" = "yes" ; then +diff --git a/contrib/vhost-user-gpu/virgl.c b/contrib/vhost-user-gpu/virgl.c +index 9e6660c7ab..ae3be063ea 100644 +--- a/contrib/vhost-user-gpu/virgl.c ++++ b/contrib/vhost-user-gpu/virgl.c +@@ -307,7 +307,7 @@ virgl_cmd_set_scanout(VuGpu *g, + struct virtio_gpu_ctrl_command *cmd) + { + struct virtio_gpu_set_scanout ss; +- struct virgl_renderer_resource_info info; ++ struct virgl_renderer_texture_info info; + int ret; + + VUGPU_FILL_CMD(ss); +@@ -322,7 +322,7 @@ virgl_cmd_set_scanout(VuGpu *g, + memset(&info, 0, sizeof(info)); + + if (ss.resource_id && ss.r.width && ss.r.height) { +- ret = virgl_renderer_resource_get_info(ss.resource_id, &info); ++ ret = virgl_renderer_borrow_texture_for_scanout(ss.resource_id, &info); + if (ret == -1) { + g_critical("%s: illegal resource specified %d\n", + __func__, ss.resource_id); +diff --git a/hw/block/block.c b/hw/block/block.c +index 1e34573da7..c907e5a772 100644 +--- a/hw/block/block.c ++++ b/hw/block/block.c +@@ -70,19 +70,27 @@ bool blkconf_blocksizes(BlockConf *conf, Error **errp) + backend_ret = blk_probe_blocksizes(blk, &blocksizes); + /* fill in detected values if they are not defined via qemu command line */ + if (!conf->physical_block_size) { +- if (!backend_ret) { ++ if (!backend_ret && blocksizes.phys) { + conf->physical_block_size = blocksizes.phys; + } else { + conf->physical_block_size = BDRV_SECTOR_SIZE; + } + } + if (!conf->logical_block_size) { +- if (!backend_ret) { ++ if (!backend_ret && blocksizes.log) { + conf->logical_block_size = blocksizes.log; + } else { + conf->logical_block_size = BDRV_SECTOR_SIZE; + } + } ++ if (!backend_ret) { ++ if (!conf->opt_io_size) { ++ conf->opt_io_size = blocksizes.opt_io; ++ } ++ if (conf->discard_granularity == -1) { ++ conf->discard_granularity = blocksizes.discard_granularity; ++ } ++ } + + if (conf->logical_block_size > conf->physical_block_size) { + error_setg(errp, +diff --git a/hw/display/edid-generate.c b/hw/display/edid-generate.c +index a1bea9a3aa..5d8387ab5c 100644 +--- a/hw/display/edid-generate.c ++++ b/hw/display/edid-generate.c +@@ -204,7 +204,7 @@ static void edid_desc_dummy(uint8_t *desc) + edid_desc_type(desc, 0x10); + } + +-static void edid_desc_timing(uint8_t *desc, ++static void edid_desc_timing(uint8_t *desc, uint32_t refresh_rate, + uint32_t xres, uint32_t yres, + uint32_t xmm, uint32_t ymm) + { +@@ -217,9 +217,9 @@ static void edid_desc_timing(uint8_t *desc, + uint32_t ysync = yres * 5 / 1000; + uint32_t yblank = yres * 35 / 1000; + +- uint32_t clock = 75 * (xres + xblank) * (yres + yblank); ++ uint64_t clock = (uint64_t)refresh_rate * (xres + xblank) * (yres + yblank); + +- stl_le_p(desc, clock / 10000); ++ stl_le_p(desc, clock / 10000000); + + desc[2] = xres & 0xff; + desc[3] = xblank & 0xff; +@@ -304,6 +304,7 @@ void qemu_edid_generate(uint8_t *edid, size_t size, + uint8_t *xtra3 = NULL; + uint8_t *dta = NULL; + uint32_t width_mm, height_mm; ++ uint32_t refresh_rate = info->refresh_rate ? info->refresh_rate : 75000; + uint32_t dpi = 100; /* if no width_mm/height_mm */ + + /* =============== set defaults =============== */ +@@ -401,7 +402,7 @@ void qemu_edid_generate(uint8_t *edid, size_t size, + + /* =============== descriptor blocks =============== */ + +- edid_desc_timing(edid + desc, info->prefx, info->prefy, ++ edid_desc_timing(edid + desc, refresh_rate, info->prefx, info->prefy, + width_mm, height_mm); + desc += 18; + +diff --git a/hw/display/vhost-user-gpu.c b/hw/display/vhost-user-gpu.c +index 6cdaa1c73b..dd0db1a869 100644 +--- a/hw/display/vhost-user-gpu.c ++++ b/hw/display/vhost-user-gpu.c +@@ -249,7 +249,7 @@ vhost_user_gpu_handle_display(VhostUserGPU *g, VhostUserGpuMsg *msg) + } + + con = g->parent_obj.scanout[m->scanout_id].con; +- if (!console_has_gl(con)) { ++ if (!console_has_gl()) { + error_report("console doesn't support GL!"); + vhost_user_gpu_unblock(g); + break; +diff --git a/hw/display/virtio-gpu-3d.c b/hw/display/virtio-gpu-3d.c +index d98964858e..6aa27109c8 100644 +--- a/hw/display/virtio-gpu-3d.c ++++ b/hw/display/virtio-gpu-3d.c +@@ -140,12 +140,39 @@ static void virgl_cmd_resource_flush(VirtIOGPU *g, + } + } + ++static GLuint virgl_borrow_texture_for_scanout(uint32_t id, bool *y_0_top, ++ uint32_t *width, ++ uint32_t *height) ++{ ++ struct virgl_renderer_texture_info info; ++ int ret; ++ ++ memset(&info, 0, sizeof(info)); ++ ++ ret = virgl_renderer_borrow_texture_for_scanout(id, &info); ++ if (ret == -1) { ++ return 0; ++ } ++ ++ if (y_0_top) { ++ *y_0_top = info.flags & 1 /* FIXME: Y_0_TOP */; ++ } ++ ++ if (width) { ++ *width = info.width; ++ } ++ ++ if (height) { ++ *height = info.height; ++ } ++ ++ return info.tex_id; ++} ++ + static void virgl_cmd_set_scanout(VirtIOGPU *g, + struct virtio_gpu_ctrl_command *cmd) + { + struct virtio_gpu_set_scanout ss; +- struct virgl_renderer_resource_info info; +- int ret; + + VIRTIO_GPU_FILL_CMD(ss); + trace_virtio_gpu_cmd_set_scanout(ss.scanout_id, ss.resource_id, +@@ -159,24 +186,13 @@ static void virgl_cmd_set_scanout(VirtIOGPU *g, + } + g->parent_obj.enable = 1; + +- memset(&info, 0, sizeof(info)); +- + if (ss.resource_id && ss.r.width && ss.r.height) { +- ret = virgl_renderer_resource_get_info(ss.resource_id, &info); +- if (ret == -1) { +- qemu_log_mask(LOG_GUEST_ERROR, +- "%s: illegal resource specified %d\n", +- __func__, ss.resource_id); +- cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; +- return; +- } + qemu_console_resize(g->parent_obj.scanout[ss.scanout_id].con, + ss.r.width, ss.r.height); + virgl_renderer_force_ctx_0(); + dpy_gl_scanout_texture( +- g->parent_obj.scanout[ss.scanout_id].con, info.tex_id, +- info.flags & 1 /* FIXME: Y_0_TOP */, +- info.width, info.height, ++ g->parent_obj.scanout[ss.scanout_id].con, ss.resource_id, ++ virgl_borrow_texture_for_scanout, + ss.r.x, ss.r.y, ss.r.width, ss.r.height); + } else { + dpy_gfx_replace_surface( +diff --git a/hw/display/virtio-gpu-base.c b/hw/display/virtio-gpu-base.c +index 25f8920fdb..c23fcfd041 100644 +--- a/hw/display/virtio-gpu-base.c ++++ b/hw/display/virtio-gpu-base.c +@@ -80,6 +80,7 @@ static int virtio_gpu_ui_info(void *opaque, uint32_t idx, QemuUIInfo *info) + + g->req_state[idx].x = info->xoff; + g->req_state[idx].y = info->yoff; ++ g->req_state[idx].refresh_rate = info->refresh_rate; + g->req_state[idx].width = info->width; + g->req_state[idx].height = info->height; + g->req_state[idx].width_mm = info->width_mm; +diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c +index c9f5e36fd0..a01ec81d1e 100644 +--- a/hw/display/virtio-gpu.c ++++ b/hw/display/virtio-gpu.c +@@ -216,6 +216,7 @@ virtio_gpu_generate_edid(VirtIOGPU *g, int scanout, + .height_mm = b->req_state[scanout].height_mm, + .prefx = b->req_state[scanout].width, + .prefy = b->req_state[scanout].height, ++ .refresh_rate = b->req_state[scanout].refresh_rate, + }; + + edid->size = cpu_to_le32(sizeof(edid->edid)); +diff --git a/hw/display/xenfb.c b/hw/display/xenfb.c +index 838260b6ad..a53341ef67 100644 +--- a/hw/display/xenfb.c ++++ b/hw/display/xenfb.c +@@ -777,16 +777,24 @@ static void xenfb_update(void *opaque) + xenfb->up_fullscreen = 0; + } + +-static void xenfb_update_interval(void *opaque, uint64_t interval) ++static void xenfb_ui_info(void *opaque, uint32_t idx, QemuUIInfo *info) + { + struct XenFB *xenfb = opaque; ++ uint32_t refresh_rate; + + if (xenfb->feature_update) { + #ifdef XENFB_TYPE_REFRESH_PERIOD + if (xenfb_queue_full(xenfb)) { + return; + } +- xenfb_send_refresh_period(xenfb, interval); ++ ++ refresh_rate = info->refresh_rate; ++ if (!refresh_rate) { ++ refresh_rate = 75; ++ } ++ ++ /* T = 1 / f = 1 [s*Hz] / f = 1000*1000 [ms*mHz] / f */ ++ xenfb_send_refresh_period(xenfb, 1000 * 1000 / refresh_rate); + #endif + } + } +@@ -983,5 +991,5 @@ struct XenDevOps xen_framebuffer_ops = { + static const GraphicHwOps xenfb_ops = { + .invalidate = xenfb_invalidate, + .gfx_update = xenfb_update, +- .update_interval = xenfb_update_interval, ++ .ui_info = xenfb_ui_info, + }; +diff --git a/include/block/block.h b/include/block/block.h +index 82185965ff..9ac297e161 100644 +--- a/include/block/block.h ++++ b/include/block/block.h +@@ -94,6 +94,8 @@ typedef enum { + typedef struct BlockSizes { + uint32_t phys; + uint32_t log; ++ uint32_t discard_granularity; ++ uint32_t opt_io; + } BlockSizes; + + typedef struct HDGeometry { +diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h +index c68bc3ba8a..f93fee5416 100644 +--- a/include/hw/core/cpu.h ++++ b/include/hw/core/cpu.h +@@ -245,6 +245,7 @@ struct KVMState; + struct kvm_run; + + struct hax_vcpu_state; ++struct hvf_vcpu_state; + + #define TB_JMP_CACHE_BITS 12 + #define TB_JMP_CACHE_SIZE (1 << TB_JMP_CACHE_BITS) +@@ -430,7 +431,7 @@ struct CPUState { + + struct hax_vcpu_state *hax_vcpu; + +- int hvf_fd; ++ struct hvf_vcpu_state *hvf; + + /* track IOMMUs whose translations we've cached in the TCG TLB */ + GArray *iommu_notifiers; +diff --git a/include/hw/display/edid.h b/include/hw/display/edid.h +index 1f8fc9b375..520f8ec202 100644 +--- a/include/hw/display/edid.h ++++ b/include/hw/display/edid.h +@@ -11,6 +11,7 @@ typedef struct qemu_edid_info { + uint32_t prefy; + uint32_t maxx; + uint32_t maxy; ++ uint32_t refresh_rate; + } qemu_edid_info; + + void qemu_edid_generate(uint8_t *edid, size_t size, +@@ -21,10 +22,11 @@ void qemu_edid_region_io(MemoryRegion *region, Object *owner, + + uint32_t qemu_edid_dpi_to_mm(uint32_t dpi, uint32_t res); + +-#define DEFINE_EDID_PROPERTIES(_state, _edid_info) \ +- DEFINE_PROP_UINT32("xres", _state, _edid_info.prefx, 0), \ +- DEFINE_PROP_UINT32("yres", _state, _edid_info.prefy, 0), \ +- DEFINE_PROP_UINT32("xmax", _state, _edid_info.maxx, 0), \ +- DEFINE_PROP_UINT32("ymax", _state, _edid_info.maxy, 0) ++#define DEFINE_EDID_PROPERTIES(_state, _edid_info) \ ++ DEFINE_PROP_UINT32("xres", _state, _edid_info.prefx, 0), \ ++ DEFINE_PROP_UINT32("yres", _state, _edid_info.prefy, 0), \ ++ DEFINE_PROP_UINT32("xmax", _state, _edid_info.maxx, 0), \ ++ DEFINE_PROP_UINT32("ymax", _state, _edid_info.maxy, 0), \ ++ DEFINE_PROP_UINT32("refresh_rate", _state, _edid_info.refresh_rate, 0) + + #endif /* EDID_H */ +diff --git a/include/hw/virtio/virtio-gpu.h b/include/hw/virtio/virtio-gpu.h +index fae149235c..876c4a51a6 100644 +--- a/include/hw/virtio/virtio-gpu.h ++++ b/include/hw/virtio/virtio-gpu.h +@@ -64,6 +64,7 @@ struct virtio_gpu_scanout { + struct virtio_gpu_requested_state { + uint16_t width_mm, height_mm; + uint32_t width, height; ++ uint32_t refresh_rate; + int x, y; + }; + +diff --git a/include/sysemu/hvf.h b/include/sysemu/hvf.h +index c98636bc81..98e26990bd 100644 +--- a/include/sysemu/hvf.h ++++ b/include/sysemu/hvf.h +@@ -19,6 +19,8 @@ + #ifdef CONFIG_HVF + uint32_t hvf_get_supported_cpuid(uint32_t func, uint32_t idx, + int reg); ++struct ARMCPU; ++void hvf_arm_set_cpu_features_from_host(struct ARMCPU *cpu); + extern bool hvf_allowed; + #define hvf_enabled() (hvf_allowed) + #else /* !CONFIG_HVF */ +diff --git a/include/sysemu/hvf_int.h b/include/sysemu/hvf_int.h +new file mode 100644 +index 0000000000..7a397fe85a +--- /dev/null ++++ b/include/sysemu/hvf_int.h +@@ -0,0 +1,66 @@ ++/* ++ * QEMU Hypervisor.framework (HVF) support ++ * ++ * This work is licensed under the terms of the GNU GPL, version 2 or later. ++ * See the COPYING file in the top-level directory. ++ * ++ */ ++ ++/* header to be included in HVF-specific code */ ++ ++#ifndef HVF_INT_H ++#define HVF_INT_H ++ ++#include "qemu/osdep.h" ++#ifdef __aarch64__ ++#include ++#else ++#include ++#endif ++ ++/* hvf_slot flags */ ++#define HVF_SLOT_LOG (1 << 0) ++ ++typedef struct hvf_slot { ++ uint64_t start; ++ uint64_t size; ++ uint8_t *mem; ++ int slot_id; ++ uint32_t flags; ++ MemoryRegion *region; ++} hvf_slot; ++ ++typedef struct hvf_vcpu_caps { ++ uint64_t vmx_cap_pinbased; ++ uint64_t vmx_cap_procbased; ++ uint64_t vmx_cap_procbased2; ++ uint64_t vmx_cap_entry; ++ uint64_t vmx_cap_exit; ++ uint64_t vmx_cap_preemption_timer; ++} hvf_vcpu_caps; ++ ++struct HVFState { ++ AccelState parent; ++ hvf_slot slots[32]; ++ int num_slots; ++ ++ hvf_vcpu_caps *hvf_caps; ++}; ++extern HVFState *hvf_state; ++ ++struct hvf_vcpu_state { ++ uint64_t fd; ++ void *exit; ++ sigset_t unblock_ipi_mask; ++}; ++ ++void assert_hvf_ok(hv_return_t ret); ++int hvf_get_registers(CPUState *cpu); ++int hvf_put_registers(CPUState *cpu); ++int hvf_arch_init_vcpu(CPUState *cpu); ++void hvf_arch_vcpu_destroy(CPUState *cpu); ++int hvf_vcpu_exec(CPUState *cpu); ++hvf_slot *hvf_find_overlap_slot(uint64_t, uint64_t); ++void hvf_kick_vcpu_thread(CPUState *cpu); ++ ++#endif +diff --git a/include/ui/cocoa.h b/include/ui/cocoa.h +new file mode 100644 +index 0000000000..5f2b0a5789 +--- /dev/null ++++ b/include/ui/cocoa.h +@@ -0,0 +1,112 @@ ++/* ++ * QEMU Cocoa CG display driver ++ * ++ * Copyright (c) 2008 Mike Kronenberg ++ * ++ * 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. ++ */ ++ ++#ifndef UI_COCOA_H ++#define UI_COCOA_H ++ ++#import ++ ++#include "ui/console.h" ++#include "ui/kbd-state.h" ++#include "qemu/thread.h" ++ ++//#define DEBUG ++ ++#ifdef DEBUG ++#define COCOA_DEBUG(...) { (void) fprintf (stdout, __VA_ARGS__); } ++#else ++#define COCOA_DEBUG(...) ((void) 0) ++#endif ++ ++typedef void (^CodeBlock)(void); ++typedef bool (^BoolCodeBlock)(void); ++ ++typedef struct { ++ DisplayChangeListener dcl; ++ DisplaySurface *surface; ++ QemuMutex draw_mutex; ++ int mouse_x; ++ int mouse_y; ++ int mouse_on; ++ CGImageRef cursor_cgimage; ++ int cursor_show; ++ bool inited; ++} QEMUScreen; ++ ++@interface QemuCocoaView : NSView ++{ ++ NSTextField *pauseLabel; ++ NSTrackingArea *trackingArea; ++ QEMUScreen *screen; ++ int screen_width; ++ int screen_height; ++ QKbdState *kbd; ++ BOOL isMouseGrabbed; ++ BOOL isAbsoluteEnabled; ++} ++- (id)initWithFrame:(NSRect)frameRect ++ screen:(QEMUScreen *)given_screen; ++- (void) frameUpdated; ++- (NSSize) computeUnzoomedSize; ++- (NSSize) fixZoomedFullScreenSize:(NSSize)proposedSize; ++- (void) resizeWindow; ++- (void) updateUIInfo; ++- (void) updateScreenWidth:(int)w height:(int)h; ++- (void) grabMouse; ++- (void) ungrabMouse; ++- (bool) handleEvent:(NSEvent *)event; ++- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled; ++/* The state surrounding mouse grabbing is potentially confusing. ++ * isAbsoluteEnabled tracks qemu_input_is_absolute() [ie "is the emulated ++ * pointing device an absolute-position one?"], but is only updated on ++ * next refresh. ++ * isMouseGrabbed tracks whether GUI events are directed to the guest; ++ * it controls whether special keys like Cmd get sent to the guest, ++ * and whether we capture the mouse when in non-absolute mode. ++ */ ++- (BOOL) isMouseGrabbed; ++- (BOOL) isAbsoluteEnabled; ++- (void) setNeedsDisplayForCursorX:(int)x ++ y:(int)y ++ width:(int)width ++ height:(int)height ++ screenHeight:(int)screen_height; ++- (void)displayPause; ++- (void)removePause; ++@end ++ ++@interface QemuCocoaAppController : NSObject ++ ++{ ++ QemuSemaphore *started_sem; ++ NSWindow *about_window; ++ NSArray * supportedImageFileTypes; ++ QemuCocoaView *cocoaView; ++} ++- (id) initWithStartedSem:(QemuSemaphore *)given_started_sem ++ screen:(QEMUScreen *)screen; ++- (QemuCocoaView *)cocoaView; ++@end ++ ++#endif +diff --git a/include/ui/console.h b/include/ui/console.h +index ca3c7af6a6..2a1683df4c 100644 +--- a/include/ui/console.h ++++ b/include/ui/console.h +@@ -128,6 +128,7 @@ typedef struct QemuUIInfo { + int yoff; + uint32_t width; + uint32_t height; ++ uint32_t refresh_rate; + } QemuUIInfo; + + /* cursor data format is 32bit RGBA */ +@@ -206,41 +207,6 @@ typedef struct DisplayChangeListenerOps { + QEMUCursor *cursor); + + /* required if GL */ +- QEMUGLContext (*dpy_gl_ctx_create)(DisplayChangeListener *dcl, +- QEMUGLParams *params); +- /* required if GL */ +- void (*dpy_gl_ctx_destroy)(DisplayChangeListener *dcl, +- QEMUGLContext ctx); +- /* required if GL */ +- int (*dpy_gl_ctx_make_current)(DisplayChangeListener *dcl, +- QEMUGLContext ctx); +- +- /* required if GL */ +- void (*dpy_gl_scanout_disable)(DisplayChangeListener *dcl); +- /* required if GL */ +- void (*dpy_gl_scanout_texture)(DisplayChangeListener *dcl, +- uint32_t backing_id, +- bool backing_y_0_top, +- uint32_t backing_width, +- uint32_t backing_height, +- uint32_t x, uint32_t y, +- uint32_t w, uint32_t h); +- /* optional (default to true if has dpy_gl_scanout_dmabuf) */ +- bool (*dpy_has_dmabuf)(DisplayChangeListener *dcl); +- /* optional */ +- void (*dpy_gl_scanout_dmabuf)(DisplayChangeListener *dcl, +- QemuDmaBuf *dmabuf); +- /* optional */ +- void (*dpy_gl_cursor_dmabuf)(DisplayChangeListener *dcl, +- QemuDmaBuf *dmabuf, bool have_hot, +- uint32_t hot_x, uint32_t hot_y); +- /* optional */ +- void (*dpy_gl_cursor_position)(DisplayChangeListener *dcl, +- uint32_t pos_x, uint32_t pos_y); +- /* optional */ +- void (*dpy_gl_release_dmabuf)(DisplayChangeListener *dcl, +- QemuDmaBuf *dmabuf); +- /* required if GL */ + void (*dpy_gl_update)(DisplayChangeListener *dcl, + uint32_t x, uint32_t y, uint32_t w, uint32_t h); + +@@ -255,6 +221,36 @@ struct DisplayChangeListener { + QLIST_ENTRY(DisplayChangeListener) next; + }; + ++typedef GLuint (* DisplayGLTextureBorrower)(uint32_t id, bool *y_0_top, ++ uint32_t *width, uint32_t *height); ++ ++typedef struct DisplayGLOps { ++ QEMUGLContext (*dpy_gl_ctx_create)(void *dg, QEMUGLParams *params); ++ void (*dpy_gl_ctx_destroy)(void *dg, QEMUGLContext ctx); ++ int (*dpy_gl_ctx_make_current)(void *dg, QEMUGLContext ctx); ++ ++ bool (*dpy_gl_scanout_get_enabled)(void *dg); ++ void (*dpy_gl_scanout_disable)(void *dg); ++ void (*dpy_gl_scanout_texture)(void *dg, ++ uint32_t backing_id, ++ DisplayGLTextureBorrower backing_borrow, ++ uint32_t x, uint32_t y, ++ uint32_t w, uint32_t h); ++ ++ /* optional (default to true if has dpy_gl_scanout_dmabuf) */ ++ bool (*dpy_has_dmabuf)(void *dg); ++ /* optional */ ++ void (*dpy_gl_scanout_dmabuf)(void *dg, QemuDmaBuf *dmabuf); ++ /* optional */ ++ void (*dpy_gl_cursor_dmabuf)(void *dg, ++ QemuDmaBuf *dmabuf, bool have_hot, ++ uint32_t hot_x, uint32_t hot_y); ++ /* optional */ ++ void (*dpy_gl_cursor_position)(void *dg, uint32_t pos_x, uint32_t pos_y); ++ /* optional */ ++ void (*dpy_gl_release_dmabuf)(void *dg, QemuDmaBuf *dmabuf); ++} DisplayGLOps; ++ + DisplayState *init_displaystate(void); + DisplaySurface *qemu_create_displaysurface_from(int width, int height, + pixman_format_code_t format, +@@ -277,6 +273,7 @@ static inline int is_placeholder(DisplaySurface *surface) + return surface->flags & QEMU_PLACEHOLDER_FLAG; + } + ++void register_displayglops(const DisplayGLOps *dg_ops); + void register_displaychangelistener(DisplayChangeListener *dcl); + void update_displaychangelistener(DisplayChangeListener *dcl, + uint64_t interval); +@@ -300,9 +297,8 @@ bool dpy_gfx_check_format(QemuConsole *con, + pixman_format_code_t format); + + void dpy_gl_scanout_disable(QemuConsole *con); +-void dpy_gl_scanout_texture(QemuConsole *con, +- uint32_t backing_id, bool backing_y_0_top, +- uint32_t backing_width, uint32_t backing_height, ++void dpy_gl_scanout_texture(QemuConsole *con, uint32_t backing_id, ++ DisplayGLTextureBorrower backing_borrow, + uint32_t x, uint32_t y, uint32_t w, uint32_t h); + void dpy_gl_scanout_dmabuf(QemuConsole *con, + QemuDmaBuf *dmabuf); +@@ -320,7 +316,8 @@ QEMUGLContext dpy_gl_ctx_create(QemuConsole *con, + void dpy_gl_ctx_destroy(QemuConsole *con, QEMUGLContext ctx); + int dpy_gl_ctx_make_current(QemuConsole *con, QEMUGLContext ctx); + +-bool console_has_gl(QemuConsole *con); ++void console_set_displayglcontext(QemuConsole *con, void *dg); ++bool console_has_gl(void); + + static inline int surface_stride(DisplaySurface *s) + { +@@ -380,7 +377,6 @@ typedef struct GraphicHwOps { + void (*gfx_update)(void *opaque); + bool gfx_update_async; /* if true, calls graphic_hw_update_done() */ + void (*text_update)(void *opaque, console_ch_t *text); +- void (*update_interval)(void *opaque, uint64_t interval); + int (*ui_info)(void *opaque, uint32_t head, QemuUIInfo *info); + void (*gl_block)(void *opaque, bool block); + void (*gl_flushed)(void *opaque); +diff --git a/include/ui/egl-context.h b/include/ui/egl-context.h +index 9374fe41e3..450ac78ad3 100644 +--- a/include/ui/egl-context.h ++++ b/include/ui/egl-context.h +@@ -4,10 +4,8 @@ + #include "ui/console.h" + #include "ui/egl-helpers.h" + +-QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl, +- QEMUGLParams *params); +-void qemu_egl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx); +-int qemu_egl_make_context_current(DisplayChangeListener *dcl, +- QEMUGLContext ctx); ++QEMUGLContext qemu_egl_create_context(void *dg, QEMUGLParams *params); ++void qemu_egl_destroy_context(void *dg, QEMUGLContext ctx); ++int qemu_egl_make_context_current(void *dg, QEMUGLContext ctx); + + #endif /* EGL_CONTEXT_H */ +diff --git a/include/ui/egl-helpers.h b/include/ui/egl-helpers.h +index f1bf8f97fc..e36cc00605 100644 +--- a/include/ui/egl-helpers.h ++++ b/include/ui/egl-helpers.h +@@ -48,7 +48,9 @@ void egl_dmabuf_release_texture(QemuDmaBuf *dmabuf); + + #endif + +-EGLSurface qemu_egl_init_surface_x11(EGLContext ectx, EGLNativeWindowType win); ++EGLSurface qemu_egl_init_surface(EGLContext ectx, EGLNativeWindowType win); ++ ++int qemu_egl_init_dpy_cocoa(DisplayGLMode mode); + + #if defined(CONFIG_X11) || defined(CONFIG_GBM) + +diff --git a/include/ui/gtk.h b/include/ui/gtk.h +index 5ae0ad60a6..7893c9e82a 100644 +--- a/include/ui/gtk.h ++++ b/include/ui/gtk.h +@@ -19,7 +19,7 @@ + #endif + + #include "ui/kbd-state.h" +-#if defined(CONFIG_OPENGL) ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) + #include "ui/egl-helpers.h" + #include "ui/egl-context.h" + #endif +@@ -35,7 +35,7 @@ typedef struct VirtualGfxConsole { + cairo_surface_t *surface; + double scale_x; + double scale_y; +-#if defined(CONFIG_OPENGL) ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) + QemuGLShader *gls; + EGLContext ectx; + EGLSurface esurface; +@@ -87,7 +87,7 @@ extern bool gtk_use_gl_area; + + /* ui/gtk.c */ + void gd_update_windowsize(VirtualConsole *vc); +-int gd_monitor_update_interval(GtkWidget *widget); ++void gd_update_monitor_refresh_rate(VirtualConsole *vc, GtkWidget *widget); + + /* ui/gtk-egl.c */ + void gd_egl_init(VirtualConsole *vc); +@@ -97,30 +97,24 @@ void gd_egl_update(DisplayChangeListener *dcl, + void gd_egl_refresh(DisplayChangeListener *dcl); + void gd_egl_switch(DisplayChangeListener *dcl, + DisplaySurface *surface); +-QEMUGLContext gd_egl_create_context(DisplayChangeListener *dcl, +- QEMUGLParams *params); +-void gd_egl_scanout_disable(DisplayChangeListener *dcl); +-void gd_egl_scanout_texture(DisplayChangeListener *dcl, ++QEMUGLContext gd_egl_create_context(void *dg, QEMUGLParams *params); ++bool gd_egl_scanout_get_enabled(void *dg); ++void gd_egl_scanout_disable(void *dg); ++void gd_egl_scanout_texture(void *dg, + uint32_t backing_id, +- bool backing_y_0_top, +- uint32_t backing_width, +- uint32_t backing_height, ++ DisplayGLTextureBorrower backing_borrow, + uint32_t x, uint32_t y, + uint32_t w, uint32_t h); +-void gd_egl_scanout_dmabuf(DisplayChangeListener *dcl, +- QemuDmaBuf *dmabuf); +-void gd_egl_cursor_dmabuf(DisplayChangeListener *dcl, ++void gd_egl_scanout_dmabuf(void *dg, QemuDmaBuf *dmabuf); ++void gd_egl_cursor_dmabuf(void *dg, + QemuDmaBuf *dmabuf, bool have_hot, + uint32_t hot_x, uint32_t hot_y); +-void gd_egl_cursor_position(DisplayChangeListener *dcl, +- uint32_t pos_x, uint32_t pos_y); +-void gd_egl_release_dmabuf(DisplayChangeListener *dcl, +- QemuDmaBuf *dmabuf); ++void gd_egl_cursor_position(void *dg, uint32_t pos_x, uint32_t pos_y); ++void gd_egl_release_dmabuf(void *dg, QemuDmaBuf *dmabuf); + void gd_egl_scanout_flush(DisplayChangeListener *dcl, + uint32_t x, uint32_t y, uint32_t w, uint32_t h); + void gtk_egl_init(DisplayGLMode mode); +-int gd_egl_make_current(DisplayChangeListener *dcl, +- QEMUGLContext ctx); ++int gd_egl_make_current(void *dg, QEMUGLContext ctx); + + /* ui/gtk-gl-area.c */ + void gd_gl_area_init(VirtualConsole *vc); +@@ -130,24 +124,19 @@ void gd_gl_area_update(DisplayChangeListener *dcl, + void gd_gl_area_refresh(DisplayChangeListener *dcl); + void gd_gl_area_switch(DisplayChangeListener *dcl, + DisplaySurface *surface); +-QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl, +- QEMUGLParams *params); +-void gd_gl_area_destroy_context(DisplayChangeListener *dcl, +- QEMUGLContext ctx); +-void gd_gl_area_scanout_dmabuf(DisplayChangeListener *dcl, +- QemuDmaBuf *dmabuf); +-void gd_gl_area_scanout_texture(DisplayChangeListener *dcl, ++QEMUGLContext gd_gl_area_create_context(void *dg, QEMUGLParams *params); ++void gd_gl_area_destroy_context(void *dg, QEMUGLContext ctx); ++bool gd_gl_area_scanout_get_enabled(void *dg); ++void gd_gl_area_scanout_dmabuf(void *dg, QemuDmaBuf *dmabuf); ++void gd_gl_area_scanout_texture(void *dg, + uint32_t backing_id, +- bool backing_y_0_top, +- uint32_t backing_width, +- uint32_t backing_height, ++ DisplayGLTextureBorrower backing_borrow, + uint32_t x, uint32_t y, + uint32_t w, uint32_t h); +-void gd_gl_area_scanout_disable(DisplayChangeListener *dcl); ++void gd_gl_area_scanout_disable(void *dg); + void gd_gl_area_scanout_flush(DisplayChangeListener *dcl, + uint32_t x, uint32_t y, uint32_t w, uint32_t h); + void gtk_gl_area_init(void); +-int gd_gl_area_make_current(DisplayChangeListener *dcl, +- QEMUGLContext ctx); ++int gd_gl_area_make_current(void *dg, QEMUGLContext ctx); + + #endif /* UI_GTK_H */ +diff --git a/include/ui/sdl2.h b/include/ui/sdl2.h +index f85c117a78..3001916bbc 100644 +--- a/include/ui/sdl2.h ++++ b/include/ui/sdl2.h +@@ -11,7 +11,7 @@ + #endif + + #include "ui/kbd-state.h" +-#ifdef CONFIG_OPENGL ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) + # include "ui/egl-helpers.h" + #endif + +@@ -32,7 +32,7 @@ struct sdl2_console { + int ignore_hotkeys; + SDL_GLContext winctx; + QKbdState *kbd; +-#ifdef CONFIG_OPENGL ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) + QemuGLShader *gls; + egl_fb guest_fb; + egl_fb win_fb; +@@ -65,18 +65,15 @@ void sdl2_gl_switch(DisplayChangeListener *dcl, + void sdl2_gl_refresh(DisplayChangeListener *dcl); + void sdl2_gl_redraw(struct sdl2_console *scon); + +-QEMUGLContext sdl2_gl_create_context(DisplayChangeListener *dcl, +- QEMUGLParams *params); +-void sdl2_gl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx); +-int sdl2_gl_make_context_current(DisplayChangeListener *dcl, +- QEMUGLContext ctx); ++QEMUGLContext sdl2_gl_create_context(void *dg, QEMUGLParams *params); ++void sdl2_gl_destroy_context(void *dg, QEMUGLContext ctx); ++int sdl2_gl_make_context_current(void *dg, QEMUGLContext ctx); + +-void sdl2_gl_scanout_disable(DisplayChangeListener *dcl); +-void sdl2_gl_scanout_texture(DisplayChangeListener *dcl, ++bool sdl2_gl_scanout_get_enabled(void *dg); ++void sdl2_gl_scanout_disable(void *dg); ++void sdl2_gl_scanout_texture(void *dg, + uint32_t backing_id, +- bool backing_y_0_top, +- uint32_t backing_width, +- uint32_t backing_height, ++ DisplayGLTextureBorrower backing_borrow, + uint32_t x, uint32_t y, + uint32_t w, uint32_t h); + void sdl2_gl_scanout_flush(DisplayChangeListener *dcl, +diff --git a/include/ui/spice-display.h b/include/ui/spice-display.h +index ed298d58f0..894234e874 100644 +--- a/include/ui/spice-display.h ++++ b/include/ui/spice-display.h +@@ -27,7 +27,7 @@ + #include "ui/qemu-pixman.h" + #include "ui/console.h" + +-#if defined(CONFIG_OPENGL) && defined(CONFIG_GBM) ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) && defined(CONFIG_GBM) + # if SPICE_SERVER_VERSION >= 0x000d01 /* release 0.13.1 */ + # define HAVE_SPICE_GL 1 + # include "ui/egl-helpers.h" +diff --git a/meson.build b/meson.build +index 1ffdc9e6c4..b5065005a4 100644 +--- a/meson.build ++++ b/meson.build +@@ -77,16 +77,25 @@ else + endif + + accelerator_targets = { 'CONFIG_KVM': kvm_targets } ++ ++if cpu in ['x86', 'x86_64'] ++ hvf_targets = ['x86_64-softmmu'] ++elif cpu in ['aarch64'] ++ hvf_targets = ['aarch64-softmmu'] ++else ++ hvf_targets = [] ++endif ++ + if cpu in ['x86', 'x86_64', 'arm', 'aarch64'] + # i368 emulator provides xenpv machine type for multiple architectures + accelerator_targets += { + 'CONFIG_XEN': ['i386-softmmu', 'x86_64-softmmu'], ++ 'CONFIG_HVF': hvf_targets, + } + endif + if cpu in ['x86', 'x86_64'] + accelerator_targets += { + 'CONFIG_HAX': ['i386-softmmu', 'x86_64-softmmu'], +- 'CONFIG_HVF': ['x86_64-softmmu'], + 'CONFIG_WHPX': ['i386-softmmu', 'x86_64-softmmu'], + } + endif +@@ -169,6 +178,7 @@ socket = [] + version_res = [] + coref = [] + iokit = [] ++vmnet = not_found + emulator_link_args = [] + hvf = not_found + if targetos == 'windows' +@@ -182,6 +192,7 @@ if targetos == 'windows' + elif targetos == 'darwin' + coref = dependency('appleframeworks', modules: 'CoreFoundation') + iokit = dependency('appleframeworks', modules: 'IOKit') ++ vmnet = dependency('appleframeworks', modules: 'vmnet') + elif targetos == 'sunos' + socket = [cc.find_library('socket'), + cc.find_library('nsl'), +@@ -383,7 +394,8 @@ if not get_option('attr').disabled() + endif + endif + +-cocoa = dependency('appleframeworks', modules: 'Cocoa', required: get_option('cocoa')) ++cocoa = dependency('appleframeworks', modules: ['Cocoa', 'CoreVideo'], ++ required: get_option('cocoa')) + if cocoa.found() and get_option('sdl').enabled() + error('Cocoa and SDL cannot be enabled at the same time') + endif +@@ -1137,6 +1149,7 @@ config_host_data.set('CONFIG_FUSE', fuse.found()) + config_host_data.set('CONFIG_FUSE_LSEEK', fuse_lseek.found()) + config_host_data.set('CONFIG_X11', x11.found()) + config_host_data.set('CONFIG_CFI', get_option('cfi')) ++config_host_data.set('CONFIG_VMNET', vmnet.found()) + config_host_data.set('QEMU_VERSION', '"@0@"'.format(meson.project_version())) + config_host_data.set('QEMU_VERSION_MAJOR', meson.project_version().split('.')[0]) + config_host_data.set('QEMU_VERSION_MINOR', meson.project_version().split('.')[1]) +@@ -2130,6 +2143,7 @@ common_all = static_library('common', + build_by_default: false, + sources: common_all.sources() + genh, + dependencies: common_all.dependencies(), ++ implicit_include_directories: false, + name_suffix: 'fa') + + feature_to_c = find_program('scripts/feature_to_c.sh') +diff --git a/net/clients.h b/net/clients.h +index 92f9b59aed..2c2af67f82 100644 +--- a/net/clients.h ++++ b/net/clients.h +@@ -63,4 +63,9 @@ int net_init_vhost_user(const Netdev *netdev, const char *name, + + int net_init_vhost_vdpa(const Netdev *netdev, const char *name, + NetClientState *peer, Error **errp); ++ ++#ifdef CONFIG_VMNET ++int net_init_vmnet_macos(const Netdev *netdev, const char *name, ++ NetClientState *peer, Error **errp); ++#endif + #endif /* QEMU_NET_CLIENTS_H */ +diff --git a/net/meson.build b/net/meson.build +index 1076b0a7ab..ba6a5b7fa0 100644 +--- a/net/meson.build ++++ b/net/meson.build +@@ -37,5 +37,6 @@ endif + softmmu_ss.add(when: 'CONFIG_POSIX', if_true: files(tap_posix)) + softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('tap-win32.c')) + softmmu_ss.add(when: 'CONFIG_VHOST_NET_VDPA', if_true: files('vhost-vdpa.c')) ++softmmu_ss.add(when: vmnet, if_true: files('vmnet-macos.c')) + + subdir('can') +diff --git a/net/net.c b/net/net.c +index 2a472604ec..ff8665ca79 100644 +--- a/net/net.c ++++ b/net/net.c +@@ -1000,6 +1000,9 @@ static int (* const net_client_init_fun[NET_CLIENT_DRIVER__MAX])( + #ifdef CONFIG_L2TPV3 + [NET_CLIENT_DRIVER_L2TPV3] = net_init_l2tpv3, + #endif ++#ifdef CONFIG_VMNET ++ [NET_CLIENT_DRIVER_VMNET_MACOS] = net_init_vmnet_macos, ++#endif + }; + + +diff --git a/net/vmnet-macos.c b/net/vmnet-macos.c +new file mode 100644 +index 0000000000..062ba2091e +--- /dev/null ++++ b/net/vmnet-macos.c +@@ -0,0 +1,447 @@ ++/* ++ * vmnet.framework backed netdev for macOS 10.15+ hosts ++ * ++ * Copyright (c) 2021 Phillip Tennen ++ * ++ * This work is licensed under the terms of the GNU GPL, version 2 or later. ++ * See the COPYING file in the top-level directory. ++ * ++ */ ++#include "qemu/osdep.h" ++#include "qemu/main-loop.h" ++#include "qemu/error-report.h" ++#include "qapi/qapi-types-net.h" ++#include "net/net.h" ++/* macOS vmnet framework header */ ++#include ++ ++typedef struct vmnet_state { ++ NetClientState nc; ++ interface_ref vmnet_iface_ref; ++ /* Switched on after vmnet informs us that the interface has started */ ++ bool link_up; ++ /* ++ * If qemu_send_packet_async returns 0, this is switched off until our ++ * delivery callback is invoked ++ */ ++ bool qemu_ready_to_receive; ++} vmnet_state_t; ++ ++int net_init_vmnet_macos(const Netdev *netdev, const char *name, ++ NetClientState *peer, Error **errp); ++ ++static const char *_vmnet_status_repr(vmnet_return_t status) ++{ ++ switch (status) { ++ case VMNET_SUCCESS: ++ return "success"; ++ case VMNET_FAILURE: ++ return "generic failure"; ++ case VMNET_MEM_FAILURE: ++ return "out of memory"; ++ case VMNET_INVALID_ARGUMENT: ++ return "invalid argument"; ++ case VMNET_SETUP_INCOMPLETE: ++ return "setup is incomplete"; ++ case VMNET_INVALID_ACCESS: ++ return "insufficient permissions"; ++ case VMNET_PACKET_TOO_BIG: ++ return "packet size exceeds MTU"; ++ case VMNET_BUFFER_EXHAUSTED: ++ return "kernel buffers temporarily exhausted"; ++ case VMNET_TOO_MANY_PACKETS: ++ return "number of packets exceeds system limit"; ++ /* This error code was introduced in macOS 11.0 */ ++#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 ++ case VMNET_SHARING_SERVICE_BUSY: ++ return "sharing service busy"; ++#endif ++ default: ++ return "unknown status code"; ++ } ++} ++ ++static operating_modes_t _vmnet_operating_mode_enum_compat( ++ VmnetOperatingMode mode) ++{ ++ switch (mode) { ++ case VMNET_OPERATING_MODE_HOST: ++ return VMNET_HOST_MODE; ++ case VMNET_OPERATING_MODE_SHARED: ++ return VMNET_SHARED_MODE; ++ case VMNET_OPERATING_MODE_BRIDGED: ++ return VMNET_BRIDGED_MODE; ++ default: ++ /* Should never happen as the modes are parsed before we get here */ ++ assert(false); ++ } ++} ++ ++static bool vmnet_can_receive(NetClientState *nc) ++{ ++ vmnet_state_t *s = DO_UPCAST(vmnet_state_t, nc, nc); ++ return s->link_up; ++} ++ ++static ssize_t vmnet_receive_iov(NetClientState *nc, ++ const struct iovec *iovs, ++ int iovcnt) ++{ ++ vmnet_state_t *s = DO_UPCAST(vmnet_state_t, nc, nc); ++ ++ /* Combine the provided iovs into a single vmnet packet */ ++ struct vmpktdesc *packet = g_new0(struct vmpktdesc, 1); ++ packet->vm_pkt_iov = g_new0(struct iovec, iovcnt); ++ memcpy(packet->vm_pkt_iov, iovs, sizeof(struct iovec) * iovcnt); ++ packet->vm_pkt_iovcnt = iovcnt; ++ packet->vm_flags = 0; ++ ++ /* Figure out the packet size by iterating the iov's */ ++ for (int i = 0; i < iovcnt; i++) { ++ const struct iovec *iov = iovs + i; ++ packet->vm_pkt_size += iov->iov_len; ++ } ++ ++ /* Finally, write the packet to the vmnet interface */ ++ int packet_count = 1; ++ vmnet_return_t result = vmnet_write(s->vmnet_iface_ref, packet, ++ &packet_count); ++ if (result != VMNET_SUCCESS || packet_count != 1) { ++ error_printf("Failed to send packet to host: %s\n", ++ _vmnet_status_repr(result)); ++ } ++ ssize_t wrote_bytes = packet->vm_pkt_size; ++ g_free(packet->vm_pkt_iov); ++ g_free(packet); ++ return wrote_bytes; ++} ++ ++static void vmnet_send_completed(NetClientState *nc, ssize_t len) ++{ ++ vmnet_state_t *vmnet_client_state = DO_UPCAST(vmnet_state_t, nc, nc); ++ /* Ready to receive more packets! */ ++ vmnet_client_state->qemu_ready_to_receive = true; ++} ++ ++static NetClientInfo net_vmnet_macos_info = { ++ .type = NET_CLIENT_DRIVER_VMNET_MACOS, ++ .size = sizeof(vmnet_state_t), ++ .receive_iov = vmnet_receive_iov, ++ .can_receive = vmnet_can_receive, ++}; ++ ++static bool _validate_ifname_is_valid_bridge_target(const char *ifname) ++{ ++ /* Iterate available bridge interfaces, ensure the provided one is valid */ ++ xpc_object_t bridge_interfaces = vmnet_copy_shared_interface_list(); ++ bool failed_to_match_iface_name = xpc_array_apply( ++ bridge_interfaces, ++ ^bool(size_t index, xpc_object_t _Nonnull value) { ++ if (!strcmp(xpc_string_get_string_ptr(value), ifname)) { ++ /* The interface name is valid! Stop iterating */ ++ return false; ++ } ++ return true; ++ }); ++ ++ if (failed_to_match_iface_name) { ++ error_printf("Invalid bridge interface name provided: %s\n", ifname); ++ error_printf("Valid bridge interfaces:\n"); ++ xpc_array_apply( ++ vmnet_copy_shared_interface_list(), ++ ^bool(size_t index, xpc_object_t _Nonnull value) { ++ error_printf("\t%s\n", xpc_string_get_string_ptr(value)); ++ /* Keep iterating */ ++ return true; ++ }); ++ exit(1); ++ return false; ++ } ++ ++ return true; ++} ++ ++static xpc_object_t _construct_vmnet_interface_description( ++ const NetdevVmnetModeOptions *vmnet_opts) ++{ ++ operating_modes_t mode = _vmnet_operating_mode_enum_compat( ++ vmnet_opts->mode); ++ ++ /* Validate options */ ++ if (mode == VMNET_HOST_MODE || mode == VMNET_SHARED_MODE) { ++ NetdevVmnetModeOptionsHostOrShared mode_opts = vmnet_opts->u.host; ++ /* If one DHCP parameter is configured, all 3 are required */ ++ if (mode_opts.has_dhcp_start_address || ++ mode_opts.has_dhcp_end_address || ++ mode_opts.has_dhcp_subnet_mask) { ++ if (!(mode_opts.has_dhcp_start_address && ++ mode_opts.has_dhcp_end_address && ++ mode_opts.has_dhcp_subnet_mask)) { ++ error_printf("Incomplete DHCP configuration provided\n"); ++ exit(1); ++ } ++ } ++ } else if (mode == VMNET_BRIDGED_MODE) { ++ /* Nothing to validate */ ++ } else { ++ error_printf("Unknown vmnet mode %d\n", mode); ++ exit(1); ++ } ++ ++ xpc_object_t interface_desc = xpc_dictionary_create(NULL, NULL, 0); ++ xpc_dictionary_set_uint64( ++ interface_desc, ++ vmnet_operation_mode_key, ++ mode ++ ); ++ ++ if (mode == VMNET_BRIDGED_MODE) { ++ /* ++ * Configure the provided physical interface to act ++ * as a bridge with QEMU ++ */ ++ NetdevVmnetModeOptionsBridged mode_opts = vmnet_opts->u.bridged; ++ /* Bridge with en0 by default */ ++ const char *physical_ifname = mode_opts.has_ifname ? mode_opts.ifname : ++ "en0"; ++ _validate_ifname_is_valid_bridge_target(physical_ifname); ++ xpc_dictionary_set_string(interface_desc, ++ vmnet_shared_interface_name_key, ++ physical_ifname); ++ } else if (mode == VMNET_HOST_MODE || mode == VMNET_SHARED_MODE) { ++ /* Pass the DHCP configuration to vmnet, if the user provided one */ ++ NetdevVmnetModeOptionsHostOrShared mode_opts = vmnet_opts->u.host; ++ if (mode_opts.has_dhcp_start_address) { ++ /* All DHCP arguments are available, as per the checks above */ ++ xpc_dictionary_set_string(interface_desc, ++ vmnet_start_address_key, ++ mode_opts.dhcp_start_address); ++ xpc_dictionary_set_string(interface_desc, ++ vmnet_end_address_key, ++ mode_opts.dhcp_end_address); ++ xpc_dictionary_set_string(interface_desc, ++ vmnet_subnet_mask_key, ++ mode_opts.dhcp_subnet_mask); ++ } ++ } ++ ++ return interface_desc; ++} ++ ++int net_init_vmnet_macos(const Netdev *netdev, const char *name, ++ NetClientState *peer, Error **errp) ++{ ++ assert(netdev->type == NET_CLIENT_DRIVER_VMNET_MACOS); ++ ++ NetdevVmnetModeOptions *vmnet_opts = netdev->u.vmnet_macos.options; ++ xpc_object_t iface_desc = _construct_vmnet_interface_description(vmnet_opts); ++ ++ NetClientState *nc = qemu_new_net_client(&net_vmnet_macos_info, peer, ++ "vmnet", name); ++ vmnet_state_t *vmnet_client_state = DO_UPCAST(vmnet_state_t, nc, nc); ++ ++ dispatch_queue_t vmnet_dispatch_queue = dispatch_queue_create( ++ "org.qemu.vmnet.iface_queue", ++ DISPATCH_QUEUE_SERIAL ++ ); ++ ++ __block vmnet_return_t vmnet_start_status = 0; ++ __block uint64_t vmnet_iface_mtu = 0; ++ __block uint64_t vmnet_max_packet_size = 0; ++ __block const char *vmnet_mac_address = NULL; ++ /* ++ * We can't refer to an array type directly within a block, ++ * so hold a pointer instead. ++ */ ++ uuid_string_t vmnet_iface_uuid = {0}; ++ __block uuid_string_t *vmnet_iface_uuid_ptr = &vmnet_iface_uuid; ++ /* These are only provided in VMNET_HOST_MODE and VMNET_SHARED_MODE */ ++ bool vmnet_provides_dhcp_info = ( ++ vmnet_opts->mode == VMNET_OPERATING_MODE_HOST || ++ vmnet_opts->mode == VMNET_OPERATING_MODE_SHARED); ++ __block const char *vmnet_subnet_mask = NULL; ++ __block const char *vmnet_dhcp_range_start = NULL; ++ __block const char *vmnet_dhcp_range_end = NULL; ++ ++ /* Create the vmnet interface */ ++ dispatch_semaphore_t vmnet_iface_sem = dispatch_semaphore_create(0); ++ interface_ref vmnet_iface_ref = vmnet_start_interface( ++ iface_desc, ++ vmnet_dispatch_queue, ++ ^(vmnet_return_t status, xpc_object_t _Nullable interface_param) { ++ vmnet_start_status = status; ++ if (vmnet_start_status != VMNET_SUCCESS || !interface_param) { ++ /* Early return if the interface couldn't be started */ ++ dispatch_semaphore_signal(vmnet_iface_sem); ++ return; ++ } ++ ++ /* ++ * Read the configuration that vmnet provided us. ++ * The provided dictionary is owned by XPC and may be freed ++ * shortly after this block's execution. ++ * So, copy data buffers now. ++ */ ++ vmnet_iface_mtu = xpc_dictionary_get_uint64( ++ interface_param, ++ vmnet_mtu_key ++ ); ++ vmnet_max_packet_size = xpc_dictionary_get_uint64( ++ interface_param, ++ vmnet_max_packet_size_key ++ ); ++ vmnet_mac_address = strdup(xpc_dictionary_get_string( ++ interface_param, ++ vmnet_mac_address_key ++ )); ++ ++ const uint8_t *iface_uuid = xpc_dictionary_get_uuid( ++ interface_param, ++ vmnet_interface_id_key ++ ); ++ uuid_unparse_upper(iface_uuid, *vmnet_iface_uuid_ptr); ++ ++ /* If we're in a mode that provides DHCP info, read it out now */ ++ if (vmnet_provides_dhcp_info) { ++ vmnet_dhcp_range_start = strdup(xpc_dictionary_get_string( ++ interface_param, ++ vmnet_start_address_key ++ )); ++ vmnet_dhcp_range_end = strdup(xpc_dictionary_get_string( ++ interface_param, ++ vmnet_end_address_key ++ )); ++ vmnet_subnet_mask = strdup(xpc_dictionary_get_string( ++ interface_param, ++ vmnet_subnet_mask_key ++ )); ++ } ++ dispatch_semaphore_signal(vmnet_iface_sem); ++ }); ++ ++ /* And block until we receive a response from vmnet */ ++ dispatch_semaphore_wait(vmnet_iface_sem, DISPATCH_TIME_FOREVER); ++ ++ /* Did we manage to start the interface? */ ++ if (vmnet_start_status != VMNET_SUCCESS || !vmnet_iface_ref) { ++ error_printf("Failed to start interface: %s\n", ++ _vmnet_status_repr(vmnet_start_status)); ++ if (vmnet_start_status == VMNET_FAILURE) { ++ error_printf("Hint: vmnet requires running with root access\n"); ++ } ++ return -1; ++ } ++ ++ info_report("Started vmnet interface with configuration:"); ++ info_report("MTU: %llu", vmnet_iface_mtu); ++ info_report("Max packet size: %llu", vmnet_max_packet_size); ++ info_report("MAC: %s", vmnet_mac_address); ++ if (vmnet_provides_dhcp_info) { ++ info_report("DHCP IPv4 start: %s", vmnet_dhcp_range_start); ++ info_report("DHCP IPv4 end: %s", vmnet_dhcp_range_end); ++ info_report("IPv4 subnet mask: %s", vmnet_subnet_mask); ++ } ++ info_report("UUID: %s", vmnet_iface_uuid); ++ ++ /* The interface is up! Set a block to run when packets are received */ ++ vmnet_client_state->vmnet_iface_ref = vmnet_iface_ref; ++ vmnet_return_t event_cb_stat = vmnet_interface_set_event_callback( ++ vmnet_iface_ref, ++ VMNET_INTERFACE_PACKETS_AVAILABLE, ++ vmnet_dispatch_queue, ++ ^(interface_event_t event_mask, xpc_object_t _Nonnull event) { ++ if (event_mask != VMNET_INTERFACE_PACKETS_AVAILABLE) { ++ error_printf("Unknown vmnet interface event 0x%08x\n", event_mask); ++ return; ++ } ++ ++ /* If we're unable to handle more packets now, drop this packet */ ++ if (!vmnet_client_state->qemu_ready_to_receive) { ++ return; ++ } ++ ++ /* ++ * TODO(Phillip Tennen ): There may be more than ++ * one packet available. ++ * As an optimization, we could read ++ * vmnet_estimated_packets_available_key packets now. ++ */ ++ char *packet_buf = g_malloc0(vmnet_max_packet_size); ++ struct iovec *iov = g_new0(struct iovec, 1); ++ iov->iov_base = packet_buf; ++ iov->iov_len = vmnet_max_packet_size; ++ ++ int pktcnt = 1; ++ struct vmpktdesc *v = g_new0(struct vmpktdesc, pktcnt); ++ v->vm_pkt_size = vmnet_max_packet_size; ++ v->vm_pkt_iov = iov; ++ v->vm_pkt_iovcnt = 1; ++ v->vm_flags = 0; ++ ++ vmnet_return_t result = vmnet_read(vmnet_iface_ref, v, &pktcnt); ++ if (result != VMNET_SUCCESS) { ++ error_printf("Failed to read packet from host: %s\n", ++ _vmnet_status_repr(result)); ++ } ++ ++ /* Ensure we read exactly one packet */ ++ assert(pktcnt == 1); ++ ++ /* Dispatch this block to a global queue instead of the main queue, ++ * which is only created when the program has a Cocoa event loop. ++ * If QEMU is started with -nographic, no Cocoa event loop will be ++ * created and thus the main queue will be unavailable. ++ */ ++ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, ++ 0), ++ ^{ ++ qemu_mutex_lock_iothread(); ++ ++ /* ++ * Deliver the packet to the guest ++ * If the delivery succeeded synchronously, this returns the length ++ * of the sent packet. ++ */ ++ if (qemu_send_packet_async(nc, iov->iov_base, ++ v->vm_pkt_size, ++ vmnet_send_completed) == 0) { ++ vmnet_client_state->qemu_ready_to_receive = false; ++ } ++ ++ /* ++ * It's safe to free the packet buffers. ++ * Even if delivery needs to wait, qemu_net_queue_append copies ++ * the packet buffer. ++ */ ++ g_free(v); ++ g_free(iov); ++ g_free(packet_buf); ++ ++ qemu_mutex_unlock_iothread(); ++ }); ++ }); ++ ++ /* Did we manage to set an event callback? */ ++ if (event_cb_stat != VMNET_SUCCESS) { ++ error_printf("Failed to set up a callback to receive packets: %s\n", ++ _vmnet_status_repr(vmnet_start_status)); ++ exit(1); ++ } ++ ++ /* We're now ready to receive packets */ ++ vmnet_client_state->qemu_ready_to_receive = true; ++ vmnet_client_state->link_up = true; ++ ++ /* Include DHCP info if we're in a relevant mode */ ++ if (vmnet_provides_dhcp_info) { ++ snprintf(nc->info_str, sizeof(nc->info_str), ++ "dhcp_start=%s,dhcp_end=%s,mask=%s", ++ vmnet_dhcp_range_start, vmnet_dhcp_range_end, ++ vmnet_subnet_mask); ++ } else { ++ snprintf(nc->info_str, sizeof(nc->info_str), ++ "mac=%s", vmnet_mac_address); ++ } ++ ++ return 0; ++} +diff --git a/qapi/net.json b/qapi/net.json +index af3f5b0fda..9eea41a113 100644 +--- a/qapi/net.json ++++ b/qapi/net.json +@@ -450,6 +450,115 @@ + '*vhostdev': 'str', + '*queues': 'int' } } + ++## ++# @VmnetOperatingMode: ++# ++# The operating modes in which a vmnet netdev can run ++# Only available on macOS ++# ++# @host: the guest may communicate with the host ++# and other guest network interfaces ++# ++# @shared: the guest may reach the Internet through a NAT, ++# and may communicate with the host and other guest ++# network interfaces ++# ++# @bridged: the guest's traffic is bridged with a ++# physical network interface of the host ++# ++# Since: 6.0 ++## ++{ 'enum': 'VmnetOperatingMode', ++ 'data': [ 'host', 'shared', 'bridged' ], ++ 'if': 'defined(CONFIG_VMNET)' } ++ ++## ++# @NetdevVmnetModeOptionsBridged: ++# ++# Options for the vmnet-macos netdev ++# that are only available in 'bridged' mode ++# Only available on macOS ++# ++# @ifname: the physical network interface to bridge with ++# (defaults to en0 if not specified) ++# ++# Since: 6.0 ++## ++{ 'struct': 'NetdevVmnetModeOptionsBridged', ++ 'data': { '*ifname': 'str' }, ++ 'if': 'defined(CONFIG_VMNET)' } ++ ++## ++# @NetdevVmnetModeOptionsHostOrShared: ++# ++# Options for the vmnet-macos netdev ++# that are only available in 'host' or 'shared' mode ++# Only available on macOS ++# ++# @dhcp-start-address: the gateway address to use for the interface. ++# The range to dhcp_end_address is placed in the DHCP pool. ++# (only valid with mode=host|shared) ++# (must be specified with dhcp-end-address and ++# dhcp-subnet-mask) ++# (allocated automatically if unset) ++# ++# @dhcp-end-address: the DHCP IPv4 range end address to use for the interface. ++# (only valid with mode=host|shared) ++# (must be specified with dhcp-start-address and ++# dhcp-subnet-mask) ++# (allocated automatically if unset) ++# ++# @dhcp-subnet-mask: the IPv4 subnet mask (string) to use on the interface. ++# (only valid with mode=host|shared) ++# (must be specified with dhcp-start-address and ++# dhcp-end-address) ++# (allocated automatically if unset) ++# ++# Since: 6.0 ++## ++{ 'struct': 'NetdevVmnetModeOptionsHostOrShared', ++ 'data': { ++ '*dhcp-start-address': 'str' , ++ '*dhcp-end-address': 'str', ++ '*dhcp-subnet-mask': 'str' }, ++ 'if': 'defined(CONFIG_VMNET)' } ++ ++## ++# @NetdevVmnetModeOptions: ++# ++# Options specific to different operating modes of a vmnet netdev ++# Only available on macOS ++# ++# @mode: the operating mode vmnet should run in ++# ++# Since: 6.0 ++## ++{ 'union': 'NetdevVmnetModeOptions', ++ 'base': { 'mode': 'VmnetOperatingMode' }, ++ 'discriminator': 'mode', ++ 'data': { ++ 'bridged': 'NetdevVmnetModeOptionsBridged', ++ 'host': 'NetdevVmnetModeOptionsHostOrShared', ++ 'shared': 'NetdevVmnetModeOptionsHostOrShared' }, ++ 'if': 'defined(CONFIG_VMNET)' } ++ ++## ++# @NetdevVmnetOptions: ++# ++# vmnet network backend ++# Only available on macOS ++# ++# @options: a structure specifying the mode and mode-specific options ++# (once QAPI supports a union type as a branch to another union type, ++# this structure can be changed to a union, and the contents of ++# NetdevVmnetModeOptions moved here) ++# ++# Since: 6.0 ++## ++{ 'struct': 'NetdevVmnetOptions', ++ 'data': {'options': 'NetdevVmnetModeOptions' }, ++ 'if': 'defined(CONFIG_VMNET)' } ++ + ## + # @NetClientDriver: + # +@@ -458,10 +567,13 @@ + # Since: 2.7 + # + # @vhost-vdpa since 5.1 ++# ++# @vmnet-macos since 6.0 (only available on macOS) + ## + { 'enum': 'NetClientDriver', + 'data': [ 'none', 'nic', 'user', 'tap', 'l2tpv3', 'socket', 'vde', +- 'bridge', 'hubport', 'netmap', 'vhost-user', 'vhost-vdpa' ] } ++ 'bridge', 'hubport', 'netmap', 'vhost-user', 'vhost-vdpa', ++ { 'name': 'vmnet-macos', 'if': 'defined(CONFIG_VMNET)' } ] } + + ## + # @Netdev: +@@ -475,6 +587,8 @@ + # Since: 1.2 + # + # 'l2tpv3' - since 2.1 ++# ++# 'vmnet-macos' since 6.0 (only available on macOS) + ## + { 'union': 'Netdev', + 'base': { 'id': 'str', 'type': 'NetClientDriver' }, +@@ -490,7 +604,9 @@ + 'hubport': 'NetdevHubPortOptions', + 'netmap': 'NetdevNetmapOptions', + 'vhost-user': 'NetdevVhostUserOptions', +- 'vhost-vdpa': 'NetdevVhostVDPAOptions' } } ++ 'vhost-vdpa': 'NetdevVhostVDPAOptions', ++ 'vmnet-macos': { 'type': 'NetdevVmnetOptions', ++ 'if': 'defined(CONFIG_VMNET)' } } } + + ## + # @RxState: +diff --git a/qemu-options.hx b/qemu-options.hx +index 635dc8a624..2abab6a7b9 100644 +--- a/qemu-options.hx ++++ b/qemu-options.hx +@@ -1783,7 +1783,7 @@ DEF("display", HAS_ARG, QEMU_OPTION_display, + #if defined(CONFIG_CURSES) + "-display curses[,charset=]\n" + #endif +-#if defined(CONFIG_OPENGL) ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) + "-display egl-headless[,rendernode=]\n" + #endif + "-display none\n" +@@ -2542,6 +2542,15 @@ DEF("netdev", HAS_ARG, QEMU_OPTION_netdev, + #ifdef __linux__ + "-netdev vhost-vdpa,id=str,vhostdev=/path/to/dev\n" + " configure a vhost-vdpa network,Establish a vhost-vdpa netdev\n" ++#endif ++#ifdef CONFIG_VMNET ++ "-netdev vmnet-macos,id=str,mode=bridged[,ifname=ifname]\n" ++ " configure a macOS-provided vmnet network in \"physical interface bridge\" mode\n" ++ " the physical interface to bridge with defaults to en0 if unspecified\n" ++ "-netdev vmnet-macos,id=str,mode=host|shared\n" ++ " [,dhcp_start_address=addr,dhcp_end_address=addr,dhcp_subnet_mask=mask]\n" ++ " configure a macOS-provided vmnet network in \"host\" or \"shared\" mode\n" ++ " the DHCP configuration will be set automatically if unspecified\n" + #endif + "-netdev hubport,id=str,hubid=n[,netdev=nd]\n" + " configure a hub port on the hub with ID 'n'\n", QEMU_ARCH_ALL) +diff --git a/target/arm/cpu.c b/target/arm/cpu.c +index 4eb0d2f85c..72e0794423 100644 +--- a/target/arm/cpu.c ++++ b/target/arm/cpu.c +@@ -1078,8 +1078,8 @@ static void arm_cpu_initfn(Object *obj) + cpu->psci_version = 1; /* By default assume PSCI v0.1 */ + cpu->kvm_target = QEMU_KVM_ARM_TARGET_NONE; + +- if (tcg_enabled()) { +- cpu->psci_version = 2; /* TCG implements PSCI 0.2 */ ++ if (tcg_enabled() || hvf_enabled()) { ++ cpu->psci_version = 2; /* TCG and HVF implement PSCI 0.2 */ + } + } + +@@ -1998,12 +1998,16 @@ static void arm_cpu_class_init(ObjectClass *oc, void *data) + #endif /* CONFIG_TCG */ + } + +-#ifdef CONFIG_KVM ++#if defined(CONFIG_KVM) || defined(CONFIG_HVF) + static void arm_host_initfn(Object *obj) + { + ARMCPU *cpu = ARM_CPU(obj); + ++#ifdef CONFIG_KVM + kvm_arm_set_cpu_features_from_host(cpu); ++#else ++ hvf_arm_set_cpu_features_from_host(cpu); ++#endif + if (arm_feature(&cpu->env, ARM_FEATURE_AARCH64)) { + aarch64_add_sve_properties(obj); + } +@@ -2015,7 +2019,6 @@ static const TypeInfo host_arm_cpu_type_info = { + .parent = TYPE_AARCH64_CPU, + .instance_init = arm_host_initfn, + }; +- + #endif + + static void arm_cpu_instance_init(Object *obj) +@@ -2066,7 +2069,7 @@ static void arm_cpu_register_types(void) + { + type_register_static(&arm_cpu_type_info); + +-#ifdef CONFIG_KVM ++#if defined(CONFIG_KVM) || defined(CONFIG_HVF) + type_register_static(&host_arm_cpu_type_info); + #endif + } +diff --git a/target/arm/cpu.h b/target/arm/cpu.h +index 616b393253..4360e77183 100644 +--- a/target/arm/cpu.h ++++ b/target/arm/cpu.h +@@ -2977,6 +2977,8 @@ bool write_cpustate_to_list(ARMCPU *cpu, bool kvm_sync); + #define ARM_CPU_TYPE_NAME(name) (name ARM_CPU_TYPE_SUFFIX) + #define CPU_RESOLVING_TYPE TYPE_ARM_CPU + ++#define TYPE_ARM_HOST_CPU "host-" TYPE_ARM_CPU ++ + #define cpu_signal_handler cpu_arm_signal_handler + #define cpu_list arm_cpu_list + +diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c +new file mode 100644 +index 0000000000..27897318c8 +--- /dev/null ++++ b/target/arm/hvf/hvf.c +@@ -0,0 +1,856 @@ ++/* ++ * QEMU Hypervisor.framework support for Apple Silicon ++ ++ * Copyright 2020 Alexander Graf ++ * Copyright 2020 Google LLC ++ * ++ * This work is licensed under the terms of the GNU GPL, version 2 or later. ++ * See the COPYING file in the top-level directory. ++ * ++ */ ++ ++#include "qemu/osdep.h" ++#include "qemu-common.h" ++#include "qemu/error-report.h" ++ ++#include "sysemu/runstate.h" ++#include "sysemu/hvf.h" ++#include "sysemu/hvf_int.h" ++#include "sysemu/hw_accel.h" ++ ++#include ++ ++#include "exec/address-spaces.h" ++#include "hw/irq.h" ++#include "hw/intc/gicv3_internal.h" ++#include "qemu/main-loop.h" ++#include "qemu/accel.h" ++#include "sysemu/cpus.h" ++#include "target/arm/cpu.h" ++#include "target/arm/internals.h" ++ ++#define HVF_DEBUG 0 ++#define DPRINTF(...) \ ++ if (HVF_DEBUG) { \ ++ fprintf(stderr, "HVF %s:%d ", __func__, __LINE__); \ ++ fprintf(stderr, __VA_ARGS__); \ ++ fprintf(stderr, "\n"); \ ++ } ++ ++#define HVF_SYSREG(crn, crm, op0, op1, op2) \ ++ ENCODE_AA64_CP_REG(CP_REG_ARM64_SYSREG_CP, crn, crm, op0, op1, op2) ++#define PL1_WRITE_MASK 0x4 ++ ++#define SYSREG(op0, op1, crn, crm, op2) \ ++ ((op0 << 20) | (op2 << 17) | (op1 << 14) | (crn << 10) | (crm << 1)) ++#define SYSREG_MASK SYSREG(0x3, 0x7, 0xf, 0xf, 0x7) ++#define SYSREG_CNTPCT_EL0 SYSREG(3, 3, 14, 0, 1) ++#define SYSREG_PMCCNTR_EL0 SYSREG(3, 3, 9, 13, 0) ++ ++#define SYSREG_ICC_AP0R0_EL1 SYSREG(3, 0, 12, 8, 4) ++#define SYSREG_ICC_AP0R1_EL1 SYSREG(3, 0, 12, 8, 5) ++#define SYSREG_ICC_AP0R2_EL1 SYSREG(3, 0, 12, 8, 6) ++#define SYSREG_ICC_AP0R3_EL1 SYSREG(3, 0, 12, 8, 7) ++#define SYSREG_ICC_AP1R0_EL1 SYSREG(3, 0, 12, 9, 0) ++#define SYSREG_ICC_AP1R1_EL1 SYSREG(3, 0, 12, 9, 1) ++#define SYSREG_ICC_AP1R2_EL1 SYSREG(3, 0, 12, 9, 2) ++#define SYSREG_ICC_AP1R3_EL1 SYSREG(3, 0, 12, 9, 3) ++#define SYSREG_ICC_ASGI1R_EL1 SYSREG(3, 0, 12, 11, 6) ++#define SYSREG_ICC_BPR0_EL1 SYSREG(3, 0, 12, 8, 3) ++#define SYSREG_ICC_BPR1_EL1 SYSREG(3, 0, 12, 12, 3) ++#define SYSREG_ICC_CTLR_EL1 SYSREG(3, 0, 12, 12, 4) ++#define SYSREG_ICC_DIR_EL1 SYSREG(3, 0, 12, 11, 1) ++#define SYSREG_ICC_EOIR0_EL1 SYSREG(3, 0, 12, 8, 1) ++#define SYSREG_ICC_EOIR1_EL1 SYSREG(3, 0, 12, 12, 1) ++#define SYSREG_ICC_HPPIR0_EL1 SYSREG(3, 0, 12, 8, 2) ++#define SYSREG_ICC_HPPIR1_EL1 SYSREG(3, 0, 12, 12, 2) ++#define SYSREG_ICC_IAR0_EL1 SYSREG(3, 0, 12, 8, 0) ++#define SYSREG_ICC_IAR1_EL1 SYSREG(3, 0, 12, 12, 0) ++#define SYSREG_ICC_IGRPEN0_EL1 SYSREG(3, 0, 12, 12, 6) ++#define SYSREG_ICC_IGRPEN1_EL1 SYSREG(3, 0, 12, 12, 7) ++#define SYSREG_ICC_PMR_EL1 SYSREG(3, 0, 4, 6, 0) ++#define SYSREG_ICC_RPR_EL1 SYSREG(3, 0, 12, 11, 3) ++#define SYSREG_ICC_SGI0R_EL1 SYSREG(3, 0, 12, 11, 7) ++#define SYSREG_ICC_SGI1R_EL1 SYSREG(3, 0, 12, 11, 5) ++#define SYSREG_ICC_SRE_EL1 SYSREG(3, 0, 12, 12, 5) ++ ++#define WFX_IS_WFE (1 << 0) ++ ++struct hvf_reg_match { ++ int reg; ++ uint64_t offset; ++}; ++ ++static const struct hvf_reg_match hvf_reg_match[] = { ++ { HV_REG_X0, offsetof(CPUARMState, xregs[0]) }, ++ { HV_REG_X1, offsetof(CPUARMState, xregs[1]) }, ++ { HV_REG_X2, offsetof(CPUARMState, xregs[2]) }, ++ { HV_REG_X3, offsetof(CPUARMState, xregs[3]) }, ++ { HV_REG_X4, offsetof(CPUARMState, xregs[4]) }, ++ { HV_REG_X5, offsetof(CPUARMState, xregs[5]) }, ++ { HV_REG_X6, offsetof(CPUARMState, xregs[6]) }, ++ { HV_REG_X7, offsetof(CPUARMState, xregs[7]) }, ++ { HV_REG_X8, offsetof(CPUARMState, xregs[8]) }, ++ { HV_REG_X9, offsetof(CPUARMState, xregs[9]) }, ++ { HV_REG_X10, offsetof(CPUARMState, xregs[10]) }, ++ { HV_REG_X11, offsetof(CPUARMState, xregs[11]) }, ++ { HV_REG_X12, offsetof(CPUARMState, xregs[12]) }, ++ { HV_REG_X13, offsetof(CPUARMState, xregs[13]) }, ++ { HV_REG_X14, offsetof(CPUARMState, xregs[14]) }, ++ { HV_REG_X15, offsetof(CPUARMState, xregs[15]) }, ++ { HV_REG_X16, offsetof(CPUARMState, xregs[16]) }, ++ { HV_REG_X17, offsetof(CPUARMState, xregs[17]) }, ++ { HV_REG_X18, offsetof(CPUARMState, xregs[18]) }, ++ { HV_REG_X19, offsetof(CPUARMState, xregs[19]) }, ++ { HV_REG_X20, offsetof(CPUARMState, xregs[20]) }, ++ { HV_REG_X21, offsetof(CPUARMState, xregs[21]) }, ++ { HV_REG_X22, offsetof(CPUARMState, xregs[22]) }, ++ { HV_REG_X23, offsetof(CPUARMState, xregs[23]) }, ++ { HV_REG_X24, offsetof(CPUARMState, xregs[24]) }, ++ { HV_REG_X25, offsetof(CPUARMState, xregs[25]) }, ++ { HV_REG_X26, offsetof(CPUARMState, xregs[26]) }, ++ { HV_REG_X27, offsetof(CPUARMState, xregs[27]) }, ++ { HV_REG_X28, offsetof(CPUARMState, xregs[28]) }, ++ { HV_REG_X29, offsetof(CPUARMState, xregs[29]) }, ++ { HV_REG_X30, offsetof(CPUARMState, xregs[30]) }, ++ { HV_REG_PC, offsetof(CPUARMState, pc) }, ++}; ++ ++struct hvf_sreg_match { ++ int reg; ++ uint32_t key; ++}; ++ ++static const struct hvf_sreg_match hvf_sreg_match[] = { ++ { HV_SYS_REG_DBGBVR0_EL1, HVF_SYSREG(0, 0, 14, 0, 4) }, ++ { HV_SYS_REG_DBGBCR0_EL1, HVF_SYSREG(0, 0, 14, 0, 5) }, ++ { HV_SYS_REG_DBGWVR0_EL1, HVF_SYSREG(0, 0, 14, 0, 6) }, ++ { HV_SYS_REG_DBGWCR0_EL1, HVF_SYSREG(0, 0, 14, 0, 7) }, ++ ++ { HV_SYS_REG_DBGBVR1_EL1, HVF_SYSREG(0, 1, 14, 0, 4) }, ++ { HV_SYS_REG_DBGBCR1_EL1, HVF_SYSREG(0, 1, 14, 0, 5) }, ++ { HV_SYS_REG_DBGWVR1_EL1, HVF_SYSREG(0, 1, 14, 0, 6) }, ++ { HV_SYS_REG_DBGWCR1_EL1, HVF_SYSREG(0, 1, 14, 0, 7) }, ++ ++ { HV_SYS_REG_DBGBVR2_EL1, HVF_SYSREG(0, 2, 14, 0, 4) }, ++ { HV_SYS_REG_DBGBCR2_EL1, HVF_SYSREG(0, 2, 14, 0, 5) }, ++ { HV_SYS_REG_DBGWVR2_EL1, HVF_SYSREG(0, 2, 14, 0, 6) }, ++ { HV_SYS_REG_DBGWCR2_EL1, HVF_SYSREG(0, 2, 14, 0, 7) }, ++ ++ { HV_SYS_REG_DBGBVR3_EL1, HVF_SYSREG(0, 3, 14, 0, 4) }, ++ { HV_SYS_REG_DBGBCR3_EL1, HVF_SYSREG(0, 3, 14, 0, 5) }, ++ { HV_SYS_REG_DBGWVR3_EL1, HVF_SYSREG(0, 3, 14, 0, 6) }, ++ { HV_SYS_REG_DBGWCR3_EL1, HVF_SYSREG(0, 3, 14, 0, 7) }, ++ ++ { HV_SYS_REG_DBGBVR4_EL1, HVF_SYSREG(0, 4, 14, 0, 4) }, ++ { HV_SYS_REG_DBGBCR4_EL1, HVF_SYSREG(0, 4, 14, 0, 5) }, ++ { HV_SYS_REG_DBGWVR4_EL1, HVF_SYSREG(0, 4, 14, 0, 6) }, ++ { HV_SYS_REG_DBGWCR4_EL1, HVF_SYSREG(0, 4, 14, 0, 7) }, ++ ++ { HV_SYS_REG_DBGBVR5_EL1, HVF_SYSREG(0, 5, 14, 0, 4) }, ++ { HV_SYS_REG_DBGBCR5_EL1, HVF_SYSREG(0, 5, 14, 0, 5) }, ++ { HV_SYS_REG_DBGWVR5_EL1, HVF_SYSREG(0, 5, 14, 0, 6) }, ++ { HV_SYS_REG_DBGWCR5_EL1, HVF_SYSREG(0, 5, 14, 0, 7) }, ++ ++ { HV_SYS_REG_DBGBVR6_EL1, HVF_SYSREG(0, 6, 14, 0, 4) }, ++ { HV_SYS_REG_DBGBCR6_EL1, HVF_SYSREG(0, 6, 14, 0, 5) }, ++ { HV_SYS_REG_DBGWVR6_EL1, HVF_SYSREG(0, 6, 14, 0, 6) }, ++ { HV_SYS_REG_DBGWCR6_EL1, HVF_SYSREG(0, 6, 14, 0, 7) }, ++ ++ { HV_SYS_REG_DBGBVR7_EL1, HVF_SYSREG(0, 7, 14, 0, 4) }, ++ { HV_SYS_REG_DBGBCR7_EL1, HVF_SYSREG(0, 7, 14, 0, 5) }, ++ { HV_SYS_REG_DBGWVR7_EL1, HVF_SYSREG(0, 7, 14, 0, 6) }, ++ { HV_SYS_REG_DBGWCR7_EL1, HVF_SYSREG(0, 7, 14, 0, 7) }, ++ ++ { HV_SYS_REG_DBGBVR8_EL1, HVF_SYSREG(0, 8, 14, 0, 4) }, ++ { HV_SYS_REG_DBGBCR8_EL1, HVF_SYSREG(0, 8, 14, 0, 5) }, ++ { HV_SYS_REG_DBGWVR8_EL1, HVF_SYSREG(0, 8, 14, 0, 6) }, ++ { HV_SYS_REG_DBGWCR8_EL1, HVF_SYSREG(0, 8, 14, 0, 7) }, ++ ++ { HV_SYS_REG_DBGBVR9_EL1, HVF_SYSREG(0, 9, 14, 0, 4) }, ++ { HV_SYS_REG_DBGBCR9_EL1, HVF_SYSREG(0, 9, 14, 0, 5) }, ++ { HV_SYS_REG_DBGWVR9_EL1, HVF_SYSREG(0, 9, 14, 0, 6) }, ++ { HV_SYS_REG_DBGWCR9_EL1, HVF_SYSREG(0, 9, 14, 0, 7) }, ++ ++ { HV_SYS_REG_DBGBVR10_EL1, HVF_SYSREG(0, 10, 14, 0, 4) }, ++ { HV_SYS_REG_DBGBCR10_EL1, HVF_SYSREG(0, 10, 14, 0, 5) }, ++ { HV_SYS_REG_DBGWVR10_EL1, HVF_SYSREG(0, 10, 14, 0, 6) }, ++ { HV_SYS_REG_DBGWCR10_EL1, HVF_SYSREG(0, 10, 14, 0, 7) }, ++ ++ { HV_SYS_REG_DBGBVR11_EL1, HVF_SYSREG(0, 11, 14, 0, 4) }, ++ { HV_SYS_REG_DBGBCR11_EL1, HVF_SYSREG(0, 11, 14, 0, 5) }, ++ { HV_SYS_REG_DBGWVR11_EL1, HVF_SYSREG(0, 11, 14, 0, 6) }, ++ { HV_SYS_REG_DBGWCR11_EL1, HVF_SYSREG(0, 11, 14, 0, 7) }, ++ ++ { HV_SYS_REG_DBGBVR12_EL1, HVF_SYSREG(0, 12, 14, 0, 4) }, ++ { HV_SYS_REG_DBGBCR12_EL1, HVF_SYSREG(0, 12, 14, 0, 5) }, ++ { HV_SYS_REG_DBGWVR12_EL1, HVF_SYSREG(0, 12, 14, 0, 6) }, ++ { HV_SYS_REG_DBGWCR12_EL1, HVF_SYSREG(0, 12, 14, 0, 7) }, ++ ++ { HV_SYS_REG_DBGBVR13_EL1, HVF_SYSREG(0, 13, 14, 0, 4) }, ++ { HV_SYS_REG_DBGBCR13_EL1, HVF_SYSREG(0, 13, 14, 0, 5) }, ++ { HV_SYS_REG_DBGWVR13_EL1, HVF_SYSREG(0, 13, 14, 0, 6) }, ++ { HV_SYS_REG_DBGWCR13_EL1, HVF_SYSREG(0, 13, 14, 0, 7) }, ++ ++ { HV_SYS_REG_DBGBVR14_EL1, HVF_SYSREG(0, 14, 14, 0, 4) }, ++ { HV_SYS_REG_DBGBCR14_EL1, HVF_SYSREG(0, 14, 14, 0, 5) }, ++ { HV_SYS_REG_DBGWVR14_EL1, HVF_SYSREG(0, 14, 14, 0, 6) }, ++ { HV_SYS_REG_DBGWCR14_EL1, HVF_SYSREG(0, 14, 14, 0, 7) }, ++ ++ { HV_SYS_REG_DBGBVR15_EL1, HVF_SYSREG(0, 15, 14, 0, 4) }, ++ { HV_SYS_REG_DBGBCR15_EL1, HVF_SYSREG(0, 15, 14, 0, 5) }, ++ { HV_SYS_REG_DBGWVR15_EL1, HVF_SYSREG(0, 15, 14, 0, 6) }, ++ { HV_SYS_REG_DBGWCR15_EL1, HVF_SYSREG(0, 15, 14, 0, 7) }, ++ ++#ifdef SYNC_NO_RAW_REGS ++ /* ++ * The registers below are manually synced on init because they are ++ * marked as NO_RAW. We still list them to make number space sync easier. ++ */ ++ { HV_SYS_REG_MDCCINT_EL1, HVF_SYSREG(0, 2, 2, 0, 0) }, ++ { HV_SYS_REG_MIDR_EL1, HVF_SYSREG(0, 0, 3, 0, 0) }, ++ { HV_SYS_REG_MPIDR_EL1, HVF_SYSREG(0, 0, 3, 0, 5) }, ++ { HV_SYS_REG_ID_AA64PFR0_EL1, HVF_SYSREG(0, 4, 3, 0, 0) }, ++#endif ++ { HV_SYS_REG_ID_AA64PFR1_EL1, HVF_SYSREG(0, 4, 3, 0, 2) }, ++ { HV_SYS_REG_ID_AA64DFR0_EL1, HVF_SYSREG(0, 5, 3, 0, 0) }, ++ { HV_SYS_REG_ID_AA64DFR1_EL1, HVF_SYSREG(0, 5, 3, 0, 1) }, ++ { HV_SYS_REG_ID_AA64ISAR0_EL1, HVF_SYSREG(0, 6, 3, 0, 0) }, ++ { HV_SYS_REG_ID_AA64ISAR1_EL1, HVF_SYSREG(0, 6, 3, 0, 1) }, ++#ifdef SYNC_NO_MMFR0 ++ /* We keep the hardware MMFR0 around. HW limits are there anyway */ ++ { HV_SYS_REG_ID_AA64MMFR0_EL1, HVF_SYSREG(0, 7, 3, 0, 0) }, ++#endif ++ { HV_SYS_REG_ID_AA64MMFR1_EL1, HVF_SYSREG(0, 7, 3, 0, 1) }, ++ { HV_SYS_REG_ID_AA64MMFR2_EL1, HVF_SYSREG(0, 7, 3, 0, 2) }, ++ ++ { HV_SYS_REG_MDSCR_EL1, HVF_SYSREG(0, 2, 2, 0, 2) }, ++ { HV_SYS_REG_SCTLR_EL1, HVF_SYSREG(1, 0, 3, 0, 0) }, ++ { HV_SYS_REG_CPACR_EL1, HVF_SYSREG(1, 0, 3, 0, 2) }, ++ { HV_SYS_REG_TTBR0_EL1, HVF_SYSREG(2, 0, 3, 0, 0) }, ++ { HV_SYS_REG_TTBR1_EL1, HVF_SYSREG(2, 0, 3, 0, 1) }, ++ { HV_SYS_REG_TCR_EL1, HVF_SYSREG(2, 0, 3, 0, 2) }, ++ ++ { HV_SYS_REG_APIAKEYLO_EL1, HVF_SYSREG(2, 1, 3, 0, 0) }, ++ { HV_SYS_REG_APIAKEYHI_EL1, HVF_SYSREG(2, 1, 3, 0, 1) }, ++ { HV_SYS_REG_APIBKEYLO_EL1, HVF_SYSREG(2, 1, 3, 0, 2) }, ++ { HV_SYS_REG_APIBKEYHI_EL1, HVF_SYSREG(2, 1, 3, 0, 3) }, ++ { HV_SYS_REG_APDAKEYLO_EL1, HVF_SYSREG(2, 2, 3, 0, 0) }, ++ { HV_SYS_REG_APDAKEYHI_EL1, HVF_SYSREG(2, 2, 3, 0, 1) }, ++ { HV_SYS_REG_APDBKEYLO_EL1, HVF_SYSREG(2, 2, 3, 0, 2) }, ++ { HV_SYS_REG_APDBKEYHI_EL1, HVF_SYSREG(2, 2, 3, 0, 3) }, ++ { HV_SYS_REG_APGAKEYLO_EL1, HVF_SYSREG(2, 3, 3, 0, 0) }, ++ { HV_SYS_REG_APGAKEYHI_EL1, HVF_SYSREG(2, 3, 3, 0, 1) }, ++ ++ { HV_SYS_REG_SPSR_EL1, HVF_SYSREG(4, 0, 3, 1, 0) }, ++ { HV_SYS_REG_ELR_EL1, HVF_SYSREG(4, 0, 3, 0, 1) }, ++ { HV_SYS_REG_SP_EL0, HVF_SYSREG(4, 1, 3, 0, 0) }, ++ { HV_SYS_REG_AFSR0_EL1, HVF_SYSREG(5, 1, 3, 0, 0) }, ++ { HV_SYS_REG_AFSR1_EL1, HVF_SYSREG(5, 1, 3, 0, 1) }, ++ { HV_SYS_REG_ESR_EL1, HVF_SYSREG(5, 2, 3, 0, 0) }, ++ { HV_SYS_REG_FAR_EL1, HVF_SYSREG(6, 0, 3, 0, 0) }, ++ { HV_SYS_REG_PAR_EL1, HVF_SYSREG(7, 4, 3, 0, 0) }, ++ { HV_SYS_REG_MAIR_EL1, HVF_SYSREG(10, 2, 3, 0, 0) }, ++ { HV_SYS_REG_AMAIR_EL1, HVF_SYSREG(10, 3, 3, 0, 0) }, ++ { HV_SYS_REG_VBAR_EL1, HVF_SYSREG(12, 0, 3, 0, 0) }, ++ { HV_SYS_REG_CONTEXTIDR_EL1, HVF_SYSREG(13, 0, 3, 0, 1) }, ++ { HV_SYS_REG_TPIDR_EL1, HVF_SYSREG(13, 0, 3, 0, 4) }, ++ { HV_SYS_REG_CNTKCTL_EL1, HVF_SYSREG(14, 1, 3, 0, 0) }, ++ { HV_SYS_REG_CSSELR_EL1, HVF_SYSREG(0, 0, 3, 2, 0) }, ++ { HV_SYS_REG_TPIDR_EL0, HVF_SYSREG(13, 0, 3, 3, 2) }, ++ { HV_SYS_REG_TPIDRRO_EL0, HVF_SYSREG(13, 0, 3, 3, 3) }, ++ { HV_SYS_REG_CNTV_CTL_EL0, HVF_SYSREG(14, 3, 3, 3, 1) }, ++ { HV_SYS_REG_CNTV_CVAL_EL0, HVF_SYSREG(14, 3, 3, 3, 2) }, ++ { HV_SYS_REG_SP_EL1, HVF_SYSREG(4, 1, 3, 4, 0) }, ++}; ++ ++int hvf_get_registers(CPUState *cpu) ++{ ++ ARMCPU *arm_cpu = ARM_CPU(cpu); ++ CPUARMState *env = &arm_cpu->env; ++ hv_return_t ret; ++ uint64_t val; ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(hvf_reg_match); i++) { ++ ret = hv_vcpu_get_reg(cpu->hvf->fd, hvf_reg_match[i].reg, &val); ++ *(uint64_t *)((void *)env + hvf_reg_match[i].offset) = val; ++ assert_hvf_ok(ret); ++ } ++ ++ val = 0; ++ ret = hv_vcpu_get_reg(cpu->hvf->fd, HV_REG_FPCR, &val); ++ assert_hvf_ok(ret); ++ vfp_set_fpcr(env, val); ++ ++ val = 0; ++ ret = hv_vcpu_get_reg(cpu->hvf->fd, HV_REG_FPSR, &val); ++ assert_hvf_ok(ret); ++ vfp_set_fpsr(env, val); ++ ++ ret = hv_vcpu_get_reg(cpu->hvf->fd, HV_REG_CPSR, &val); ++ assert_hvf_ok(ret); ++ pstate_write(env, val); ++ ++ for (i = 0; i < ARRAY_SIZE(hvf_sreg_match); i++) { ++ ret = hv_vcpu_get_sys_reg(cpu->hvf->fd, hvf_sreg_match[i].reg, &val); ++ assert_hvf_ok(ret); ++ ++ arm_cpu->cpreg_values[i] = val; ++ } ++ write_list_to_cpustate(arm_cpu); ++ ++ return 0; ++} ++ ++int hvf_put_registers(CPUState *cpu) ++{ ++ ARMCPU *arm_cpu = ARM_CPU(cpu); ++ CPUARMState *env = &arm_cpu->env; ++ hv_return_t ret; ++ uint64_t val; ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(hvf_reg_match); i++) { ++ val = *(uint64_t *)((void *)env + hvf_reg_match[i].offset); ++ ret = hv_vcpu_set_reg(cpu->hvf->fd, hvf_reg_match[i].reg, val); ++ ++ assert_hvf_ok(ret); ++ } ++ ++ ret = hv_vcpu_set_reg(cpu->hvf->fd, HV_REG_FPCR, vfp_get_fpcr(env)); ++ assert_hvf_ok(ret); ++ ++ ret = hv_vcpu_set_reg(cpu->hvf->fd, HV_REG_FPSR, vfp_get_fpsr(env)); ++ assert_hvf_ok(ret); ++ ++ ret = hv_vcpu_set_reg(cpu->hvf->fd, HV_REG_CPSR, pstate_read(env)); ++ assert_hvf_ok(ret); ++ ++ write_cpustate_to_list(arm_cpu, false); ++ for (i = 0; i < ARRAY_SIZE(hvf_sreg_match); i++) { ++ val = arm_cpu->cpreg_values[i]; ++ ret = hv_vcpu_set_sys_reg(cpu->hvf->fd, hvf_sreg_match[i].reg, val); ++ assert_hvf_ok(ret); ++ } ++ ++ return 0; ++} ++ ++static void flush_cpu_state(CPUState *cpu) ++{ ++ if (cpu->vcpu_dirty) { ++ hvf_put_registers(cpu); ++ cpu->vcpu_dirty = false; ++ } ++} ++ ++static void hvf_set_reg(CPUState *cpu, int rt, uint64_t val) ++{ ++ hv_return_t r; ++ ++ flush_cpu_state(cpu); ++ ++ if (rt < 31) { ++ r = hv_vcpu_set_reg(cpu->hvf->fd, HV_REG_X0 + rt, val); ++ assert_hvf_ok(r); ++ } ++} ++ ++static uint64_t hvf_get_reg(CPUState *cpu, int rt) ++{ ++ uint64_t val = 0; ++ hv_return_t r; ++ ++ flush_cpu_state(cpu); ++ ++ if (rt < 31) { ++ r = hv_vcpu_get_reg(cpu->hvf->fd, HV_REG_X0 + rt, &val); ++ assert_hvf_ok(r); ++ } ++ ++ return val; ++} ++ ++void hvf_arm_set_cpu_features_from_host(ARMCPU *cpu) ++{ ++ ARMISARegisters host_isar; ++ const struct isar_regs { ++ int reg; ++ uint64_t *val; ++ } regs[] = { ++ { HV_SYS_REG_ID_AA64PFR0_EL1, &host_isar.id_aa64pfr0 }, ++ { HV_SYS_REG_ID_AA64PFR1_EL1, &host_isar.id_aa64pfr1 }, ++ { HV_SYS_REG_ID_AA64DFR0_EL1, &host_isar.id_aa64dfr0 }, ++ { HV_SYS_REG_ID_AA64DFR1_EL1, &host_isar.id_aa64dfr1 }, ++ { HV_SYS_REG_ID_AA64ISAR0_EL1, &host_isar.id_aa64isar0 }, ++ { HV_SYS_REG_ID_AA64ISAR1_EL1, &host_isar.id_aa64isar1 }, ++ { HV_SYS_REG_ID_AA64MMFR0_EL1, &host_isar.id_aa64mmfr0 }, ++ { HV_SYS_REG_ID_AA64MMFR1_EL1, &host_isar.id_aa64mmfr1 }, ++ { HV_SYS_REG_ID_AA64MMFR2_EL1, &host_isar.id_aa64mmfr2 }, ++ }; ++ hv_vcpu_t fd; ++ hv_vcpu_exit_t *exit; ++ int i; ++ ++ cpu->dtb_compatible = "arm,arm-v8"; ++ cpu->env.features = (1ULL << ARM_FEATURE_V8) | ++ (1ULL << ARM_FEATURE_NEON) | ++ (1ULL << ARM_FEATURE_AARCH64) | ++ (1ULL << ARM_FEATURE_PMU) | ++ (1ULL << ARM_FEATURE_GENERIC_TIMER); ++ ++ /* We set up a small vcpu to extract host registers */ ++ ++ assert_hvf_ok(hv_vcpu_create(&fd, &exit, NULL)); ++ for (i = 0; i < ARRAY_SIZE(regs); i++) { ++ assert_hvf_ok(hv_vcpu_get_sys_reg(fd, regs[i].reg, regs[i].val)); ++ } ++ assert_hvf_ok(hv_vcpu_get_sys_reg(fd, HV_SYS_REG_MIDR_EL1, &cpu->midr)); ++ assert_hvf_ok(hv_vcpu_destroy(fd)); ++ ++ cpu->isar = host_isar; ++ cpu->reset_sctlr = 0x00c50078; ++} ++ ++void hvf_arch_vcpu_destroy(CPUState *cpu) ++{ ++} ++ ++int hvf_arch_init_vcpu(CPUState *cpu) ++{ ++ ARMCPU *arm_cpu = ARM_CPU(cpu); ++ CPUARMState *env = &arm_cpu->env; ++ uint32_t sregs_match_len = ARRAY_SIZE(hvf_sreg_match); ++ uint64_t pfr; ++ hv_return_t ret; ++ int i; ++ ++ env->aarch64 = 1; ++ asm volatile("mrs %0, cntfrq_el0" : "=r"(arm_cpu->gt_cntfrq_hz)); ++ ++ /* Allocate enough space for our sysreg sync */ ++ arm_cpu->cpreg_indexes = g_renew(uint64_t, arm_cpu->cpreg_indexes, ++ sregs_match_len); ++ arm_cpu->cpreg_values = g_renew(uint64_t, arm_cpu->cpreg_values, ++ sregs_match_len); ++ arm_cpu->cpreg_vmstate_indexes = g_renew(uint64_t, ++ arm_cpu->cpreg_vmstate_indexes, ++ sregs_match_len); ++ arm_cpu->cpreg_vmstate_values = g_renew(uint64_t, ++ arm_cpu->cpreg_vmstate_values, ++ sregs_match_len); ++ ++ memset(arm_cpu->cpreg_values, 0, sregs_match_len * sizeof(uint64_t)); ++ arm_cpu->cpreg_array_len = sregs_match_len; ++ arm_cpu->cpreg_vmstate_array_len = sregs_match_len; ++ ++ /* Populate cp list for all known sysregs */ ++ for (i = 0; i < sregs_match_len; i++) { ++ const ARMCPRegInfo *ri; ++ ++ arm_cpu->cpreg_indexes[i] = cpreg_to_kvm_id(hvf_sreg_match[i].key); ++ ++ ri = get_arm_cp_reginfo(arm_cpu->cp_regs, hvf_sreg_match[i].key); ++ if (ri) { ++ assert(!(ri->type & ARM_CP_NO_RAW)); ++ } ++ } ++ write_cpustate_to_list(arm_cpu, false); ++ ++ /* Set CP_NO_RAW system registers on init */ ++ ret = hv_vcpu_set_sys_reg(cpu->hvf->fd, HV_SYS_REG_MIDR_EL1, ++ arm_cpu->midr); ++ assert_hvf_ok(ret); ++ ++ ret = hv_vcpu_set_sys_reg(cpu->hvf->fd, HV_SYS_REG_MPIDR_EL1, ++ arm_cpu->mp_affinity); ++ assert_hvf_ok(ret); ++ ++ ret = hv_vcpu_get_sys_reg(cpu->hvf->fd, HV_SYS_REG_ID_AA64PFR0_EL1, &pfr); ++ assert_hvf_ok(ret); ++ pfr |= env->gicv3state ? (1 << 24) : 0; ++ ret = hv_vcpu_set_sys_reg(cpu->hvf->fd, HV_SYS_REG_ID_AA64PFR0_EL1, pfr); ++ assert_hvf_ok(ret); ++ ++ /* We're limited to underlying hardware caps, override internal versions */ ++ ret = hv_vcpu_get_sys_reg(cpu->hvf->fd, HV_SYS_REG_ID_AA64MMFR0_EL1, ++ &arm_cpu->isar.id_aa64mmfr0); ++ assert_hvf_ok(ret); ++ ++ return 0; ++} ++ ++void hvf_kick_vcpu_thread(CPUState *cpu) ++{ ++ cpus_kick_thread(cpu); ++ hv_vcpus_exit(&cpu->hvf->fd, 1); ++} ++ ++static uint32_t hvf_reg2cp_reg(uint32_t reg) ++{ ++ return ENCODE_AA64_CP_REG(CP_REG_ARM64_SYSREG_CP, ++ (reg >> 10) & 0xf, ++ (reg >> 1) & 0xf, ++ (reg >> 20) & 0x3, ++ (reg >> 14) & 0x7, ++ (reg >> 17) & 0x7); ++} ++ ++static uint64_t hvf_sysreg_read_cp(CPUState *cpu, uint32_t reg) ++{ ++ ARMCPU *arm_cpu = ARM_CPU(cpu); ++ CPUARMState *env = &arm_cpu->env; ++ const ARMCPRegInfo *ri; ++ uint64_t val = 0; ++ ++ ri = get_arm_cp_reginfo(arm_cpu->cp_regs, hvf_reg2cp_reg(reg)); ++ if (ri) { ++ if (ri->type & ARM_CP_CONST) { ++ val = ri->resetvalue; ++ } else if (ri->readfn) { ++ val = ri->readfn(env, ri); ++ } else { ++ val = CPREG_FIELD64(env, ri); ++ } ++ DPRINTF("vgic read from %s [val=%016llx]", ri->name, val); ++ } ++ ++ return val; ++} ++ ++static uint64_t hvf_sysreg_read(CPUState *cpu, uint32_t reg) ++{ ++ ARMCPU *arm_cpu = ARM_CPU(cpu); ++ uint64_t val = 0; ++ ++ switch (reg) { ++ case SYSREG_CNTPCT_EL0: ++ val = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / ++ gt_cntfrq_period_ns(arm_cpu); ++ break; ++ case SYSREG_PMCCNTR_EL0: ++ val = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); ++ break; ++ case SYSREG_ICC_AP0R0_EL1: ++ case SYSREG_ICC_AP0R1_EL1: ++ case SYSREG_ICC_AP0R2_EL1: ++ case SYSREG_ICC_AP0R3_EL1: ++ case SYSREG_ICC_AP1R0_EL1: ++ case SYSREG_ICC_AP1R1_EL1: ++ case SYSREG_ICC_AP1R2_EL1: ++ case SYSREG_ICC_AP1R3_EL1: ++ case SYSREG_ICC_ASGI1R_EL1: ++ case SYSREG_ICC_BPR0_EL1: ++ case SYSREG_ICC_BPR1_EL1: ++ case SYSREG_ICC_DIR_EL1: ++ case SYSREG_ICC_EOIR0_EL1: ++ case SYSREG_ICC_EOIR1_EL1: ++ case SYSREG_ICC_HPPIR0_EL1: ++ case SYSREG_ICC_HPPIR1_EL1: ++ case SYSREG_ICC_IAR0_EL1: ++ case SYSREG_ICC_IAR1_EL1: ++ case SYSREG_ICC_IGRPEN0_EL1: ++ case SYSREG_ICC_IGRPEN1_EL1: ++ case SYSREG_ICC_PMR_EL1: ++ case SYSREG_ICC_SGI0R_EL1: ++ case SYSREG_ICC_SGI1R_EL1: ++ case SYSREG_ICC_SRE_EL1: ++ val = hvf_sysreg_read_cp(cpu, reg); ++ break; ++ case SYSREG_ICC_CTLR_EL1: ++ val = hvf_sysreg_read_cp(cpu, reg); ++ ++ /* AP0R registers above 0 don't trap, expose less PRIs to fit */ ++ val &= ~ICC_CTLR_EL1_PRIBITS_MASK; ++ val |= 4 << ICC_CTLR_EL1_PRIBITS_SHIFT; ++ break; ++ default: ++ DPRINTF("unhandled sysreg read %08x (op0=%d op1=%d op2=%d " ++ "crn=%d crm=%d)", reg, (reg >> 20) & 0x3, ++ (reg >> 14) & 0x7, (reg >> 17) & 0x7, ++ (reg >> 10) & 0xf, (reg >> 1) & 0xf); ++ break; ++ } ++ ++ return val; ++} ++ ++static void hvf_sysreg_write_cp(CPUState *cpu, uint32_t reg, uint64_t val) ++{ ++ ARMCPU *arm_cpu = ARM_CPU(cpu); ++ CPUARMState *env = &arm_cpu->env; ++ const ARMCPRegInfo *ri; ++ ++ ri = get_arm_cp_reginfo(arm_cpu->cp_regs, hvf_reg2cp_reg(reg)); ++ ++ if (ri) { ++ if (ri->writefn) { ++ ri->writefn(env, ri, val); ++ } else { ++ CPREG_FIELD64(env, ri) = val; ++ } ++ DPRINTF("vgic write to %s [val=%016llx]", ri->name, val); ++ } ++} ++ ++static void hvf_sysreg_write(CPUState *cpu, uint32_t reg, uint64_t val) ++{ ++ ARMCPU *arm_cpu = ARM_CPU(cpu); ++ ++ switch (reg) { ++ case SYSREG_CNTPCT_EL0: ++ break; ++ case SYSREG_ICC_AP0R0_EL1: ++ case SYSREG_ICC_AP0R1_EL1: ++ case SYSREG_ICC_AP0R2_EL1: ++ case SYSREG_ICC_AP0R3_EL1: ++ case SYSREG_ICC_AP1R0_EL1: ++ case SYSREG_ICC_AP1R1_EL1: ++ case SYSREG_ICC_AP1R2_EL1: ++ case SYSREG_ICC_AP1R3_EL1: ++ case SYSREG_ICC_ASGI1R_EL1: ++ case SYSREG_ICC_BPR0_EL1: ++ case SYSREG_ICC_BPR1_EL1: ++ case SYSREG_ICC_CTLR_EL1: ++ case SYSREG_ICC_DIR_EL1: ++ case SYSREG_ICC_HPPIR0_EL1: ++ case SYSREG_ICC_HPPIR1_EL1: ++ case SYSREG_ICC_IAR0_EL1: ++ case SYSREG_ICC_IAR1_EL1: ++ case SYSREG_ICC_IGRPEN0_EL1: ++ case SYSREG_ICC_IGRPEN1_EL1: ++ case SYSREG_ICC_PMR_EL1: ++ case SYSREG_ICC_SGI0R_EL1: ++ case SYSREG_ICC_SGI1R_EL1: ++ case SYSREG_ICC_SRE_EL1: ++ hvf_sysreg_write_cp(cpu, reg, val); ++ break; ++ case SYSREG_ICC_EOIR0_EL1: ++ case SYSREG_ICC_EOIR1_EL1: ++ hvf_sysreg_write_cp(cpu, reg, val); ++ qemu_set_irq(arm_cpu->gt_timer_outputs[GTIMER_VIRT], 0); ++ hv_vcpu_set_vtimer_mask(cpu->hvf->fd, false); ++ default: ++ DPRINTF("unhandled sysreg write %08x", reg); ++ break; ++ } ++} ++ ++static int hvf_inject_interrupts(CPUState *cpu) ++{ ++ if (cpu->interrupt_request & CPU_INTERRUPT_FIQ) { ++ DPRINTF("injecting FIQ"); ++ hv_vcpu_set_pending_interrupt(cpu->hvf->fd, HV_INTERRUPT_TYPE_FIQ, true); ++ } ++ ++ if (cpu->interrupt_request & CPU_INTERRUPT_HARD) { ++ DPRINTF("injecting IRQ"); ++ hv_vcpu_set_pending_interrupt(cpu->hvf->fd, HV_INTERRUPT_TYPE_IRQ, true); ++ } ++ ++ return 0; ++} ++ ++static void hvf_wait_for_ipi(CPUState *cpu, struct timespec *ts) ++{ ++ /* ++ * Use pselect to sleep so that other threads can IPI us while we're ++ * sleeping. ++ */ ++ qatomic_mb_set(&cpu->thread_kicked, false); ++ qemu_mutex_unlock_iothread(); ++ pselect(0, 0, 0, 0, ts, &cpu->hvf->unblock_ipi_mask); ++ qemu_mutex_lock_iothread(); ++} ++ ++int hvf_vcpu_exec(CPUState *cpu) ++{ ++ ARMCPU *arm_cpu = ARM_CPU(cpu); ++ CPUARMState *env = &arm_cpu->env; ++ hv_vcpu_exit_t *hvf_exit = cpu->hvf->exit; ++ hv_return_t r; ++ ++ while (1) { ++ bool advance_pc = false; ++ ++ qemu_wait_io_event_common(cpu); ++ flush_cpu_state(cpu); ++ ++ if (hvf_inject_interrupts(cpu)) { ++ return EXCP_INTERRUPT; ++ } ++ ++ if (cpu->halted) { ++ return EXCP_HLT; ++ } ++ ++ qemu_mutex_unlock_iothread(); ++ assert_hvf_ok(hv_vcpu_run(cpu->hvf->fd)); ++ ++ /* handle VMEXIT */ ++ uint64_t exit_reason = hvf_exit->reason; ++ uint64_t syndrome = hvf_exit->exception.syndrome; ++ uint32_t ec = syn_get_ec(syndrome); ++ ++ qemu_mutex_lock_iothread(); ++ switch (exit_reason) { ++ case HV_EXIT_REASON_EXCEPTION: ++ /* This is the main one, handle below. */ ++ break; ++ case HV_EXIT_REASON_VTIMER_ACTIVATED: ++ qemu_set_irq(arm_cpu->gt_timer_outputs[GTIMER_VIRT], 1); ++ continue; ++ case HV_EXIT_REASON_CANCELED: ++ /* we got kicked, no exit to process */ ++ continue; ++ default: ++ assert(0); ++ } ++ ++ switch (ec) { ++ case EC_DATAABORT: { ++ bool isv = syndrome & ARM_EL_ISV; ++ bool iswrite = (syndrome >> 6) & 1; ++ bool s1ptw = (syndrome >> 7) & 1; ++ uint32_t sas = (syndrome >> 22) & 3; ++ uint32_t len = 1 << sas; ++ uint32_t srt = (syndrome >> 16) & 0x1f; ++ uint64_t val = 0; ++ ++ DPRINTF("data abort: [pc=0x%llx va=0x%016llx pa=0x%016llx isv=%x " ++ "iswrite=%x s1ptw=%x len=%d srt=%d]\n", ++ env->pc, hvf_exit->exception.virtual_address, ++ hvf_exit->exception.physical_address, isv, iswrite, ++ s1ptw, len, srt); ++ ++ assert(isv); ++ ++ if (iswrite) { ++ val = hvf_get_reg(cpu, srt); ++ address_space_write(&address_space_memory, ++ hvf_exit->exception.physical_address, ++ MEMTXATTRS_UNSPECIFIED, &val, len); ++ ++ /* ++ * We do not have a callback to see if the timer is out of ++ * pending state. That means every MMIO write could ++ * potentially be an EOI ends the vtimer. Until we get an ++ * actual callback, let's just see if the timer is still ++ * pending on every possible toggle point. ++ */ ++ qemu_set_irq(arm_cpu->gt_timer_outputs[GTIMER_VIRT], 0); ++ hv_vcpu_set_vtimer_mask(cpu->hvf->fd, false); ++ } else { ++ address_space_read(&address_space_memory, ++ hvf_exit->exception.physical_address, ++ MEMTXATTRS_UNSPECIFIED, &val, len); ++ hvf_set_reg(cpu, srt, val); ++ } ++ ++ advance_pc = true; ++ break; ++ } ++ case EC_SYSTEMREGISTERTRAP: { ++ bool isread = (syndrome >> 0) & 1; ++ uint32_t rt = (syndrome >> 5) & 0x1f; ++ uint32_t reg = syndrome & SYSREG_MASK; ++ uint64_t val = 0; ++ ++ DPRINTF("sysreg %s operation reg=%08x (op0=%d op1=%d op2=%d " ++ "crn=%d crm=%d)", (isread) ? "read" : "write", ++ reg, (reg >> 20) & 0x3, ++ (reg >> 14) & 0x7, (reg >> 17) & 0x7, ++ (reg >> 10) & 0xf, (reg >> 1) & 0xf); ++ ++ if (isread) { ++ hvf_set_reg(cpu, rt, hvf_sysreg_read(cpu, reg)); ++ } else { ++ val = hvf_get_reg(cpu, rt); ++ hvf_sysreg_write(cpu, reg, val); ++ } ++ ++ advance_pc = true; ++ break; ++ } ++ case EC_WFX_TRAP: ++ advance_pc = true; ++ if (!(syndrome & WFX_IS_WFE) && !(cpu->interrupt_request & ++ (CPU_INTERRUPT_HARD | CPU_INTERRUPT_FIQ))) { ++ ++ uint64_t ctl; ++ r = hv_vcpu_get_sys_reg(cpu->hvf->fd, HV_SYS_REG_CNTV_CTL_EL0, ++ &ctl); ++ assert_hvf_ok(r); ++ ++ if (!(ctl & 1) || (ctl & 2)) { ++ /* Timer disabled or masked, just wait for an IPI. */ ++ hvf_wait_for_ipi(cpu, NULL); ++ break; ++ } ++ ++ uint64_t cval; ++ r = hv_vcpu_get_sys_reg(cpu->hvf->fd, HV_SYS_REG_CNTV_CVAL_EL0, ++ &cval); ++ assert_hvf_ok(r); ++ ++ int64_t ticks_to_sleep = cval - mach_absolute_time(); ++ if (ticks_to_sleep < 0) { ++ break; ++ } ++ ++ uint64_t seconds = ticks_to_sleep / arm_cpu->gt_cntfrq_hz; ++ uint64_t nanos = ++ (ticks_to_sleep - arm_cpu->gt_cntfrq_hz * seconds) * ++ 1000000000 / arm_cpu->gt_cntfrq_hz; ++ ++ /* ++ * Don't sleep for less than 2ms. This is believed to improve ++ * latency of message passing workloads. ++ */ ++ if (!seconds && nanos < 2000000) { ++ break; ++ } ++ ++ struct timespec ts = { seconds, nanos }; ++ hvf_wait_for_ipi(cpu, &ts); ++ } ++ break; ++ case EC_AA64_HVC: ++ cpu_synchronize_state(cpu); ++ if (arm_is_psci_call(arm_cpu, EXCP_HVC)) { ++ arm_handle_psci_call(arm_cpu); ++ } else { ++ DPRINTF("unknown HVC! %016llx", env->xregs[0]); ++ env->xregs[0] = -1; ++ } ++ break; ++ case EC_AA64_SMC: ++ cpu_synchronize_state(cpu); ++ if (arm_is_psci_call(arm_cpu, EXCP_SMC)) { ++ arm_handle_psci_call(arm_cpu); ++ } else { ++ DPRINTF("unknown SMC! %016llx", env->xregs[0]); ++ env->xregs[0] = -1; ++ } ++ env->pc += 4; ++ break; ++ default: ++ cpu_synchronize_state(cpu); ++ DPRINTF("exit: %llx [ec=0x%x pc=0x%llx]", syndrome, ec, env->pc); ++ error_report("%llx: unhandled exit %llx", env->pc, exit_reason); ++ } ++ ++ if (advance_pc) { ++ uint64_t pc; ++ ++ flush_cpu_state(cpu); ++ ++ r = hv_vcpu_get_reg(cpu->hvf->fd, HV_REG_PC, &pc); ++ assert_hvf_ok(r); ++ pc += 4; ++ r = hv_vcpu_set_reg(cpu->hvf->fd, HV_REG_PC, pc); ++ assert_hvf_ok(r); ++ } ++ } ++} +diff --git a/target/arm/hvf/meson.build b/target/arm/hvf/meson.build +new file mode 100644 +index 0000000000..855e6cce5a +--- /dev/null ++++ b/target/arm/hvf/meson.build +@@ -0,0 +1,3 @@ ++arm_softmmu_ss.add(when: [hvf, 'CONFIG_HVF'], if_true: files( ++ 'hvf.c', ++)) +diff --git a/target/arm/kvm_arm.h b/target/arm/kvm_arm.h +index 34f8daa377..828dca4a4a 100644 +--- a/target/arm/kvm_arm.h ++++ b/target/arm/kvm_arm.h +@@ -214,8 +214,6 @@ bool kvm_arm_create_scratch_host_vcpu(const uint32_t *cpus_to_try, + */ + void kvm_arm_destroy_scratch_host_vcpu(int *fdarray); + +-#define TYPE_ARM_HOST_CPU "host-" TYPE_ARM_CPU +- + /** + * ARMHostCPUFeatures: information about the host CPU (identified + * by asking the host kernel) +diff --git a/target/arm/meson.build b/target/arm/meson.build +index 15b936c101..2efd6e672a 100644 +--- a/target/arm/meson.build ++++ b/target/arm/meson.build +@@ -54,5 +54,7 @@ arm_softmmu_ss.add(files( + 'psci.c', + )) + ++subdir('hvf') ++ + target_arch += {'arm': arm_ss} + target_softmmu_arch += {'arm': arm_softmmu_ss} +diff --git a/target/i386/hvf/hvf-accel-ops.c b/target/i386/hvf/hvf-accel-ops.c +deleted file mode 100644 +index cbaad238e0..0000000000 +--- a/target/i386/hvf/hvf-accel-ops.c ++++ /dev/null +@@ -1,146 +0,0 @@ +-/* +- * Copyright 2008 IBM Corporation +- * 2008 Red Hat, Inc. +- * Copyright 2011 Intel Corporation +- * Copyright 2016 Veertu, Inc. +- * Copyright 2017 The Android Open Source Project +- * +- * QEMU Hypervisor.framework support +- * +- * This program is free software; you can redistribute it and/or +- * modify it under the terms of version 2 of the GNU General Public +- * License as published by the Free Software Foundation. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You should have received a copy of the GNU General Public License +- * along with this program; if not, see . +- * +- * This file contain code under public domain from the hvdos project: +- * https://github.com/mist64/hvdos +- * +- * Parts Copyright (c) 2011 NetApp, Inc. +- * All rights reserved. +- * +- * Redistribution and use in source and binary forms, with or without +- * modification, are permitted provided that the following conditions +- * are met: +- * 1. Redistributions of source code must retain the above copyright +- * notice, this list of conditions and the following disclaimer. +- * 2. Redistributions in binary form must reproduce the above copyright +- * notice, this list of conditions and the following disclaimer in the +- * documentation and/or other materials provided with the distribution. +- * +- * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND +- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +- * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE +- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +- * SUCH DAMAGE. +- */ +- +-#include "qemu/osdep.h" +-#include "qemu/error-report.h" +-#include "qemu/main-loop.h" +-#include "sysemu/hvf.h" +-#include "sysemu/runstate.h" +-#include "target/i386/cpu.h" +-#include "qemu/guest-random.h" +- +-#include "hvf-accel-ops.h" +- +-/* +- * The HVF-specific vCPU thread function. This one should only run when the host +- * CPU supports the VMX "unrestricted guest" feature. +- */ +-static void *hvf_cpu_thread_fn(void *arg) +-{ +- CPUState *cpu = arg; +- +- int r; +- +- assert(hvf_enabled()); +- +- rcu_register_thread(); +- +- qemu_mutex_lock_iothread(); +- qemu_thread_get_self(cpu->thread); +- +- cpu->thread_id = qemu_get_thread_id(); +- cpu->can_do_io = 1; +- current_cpu = cpu; +- +- hvf_init_vcpu(cpu); +- +- /* signal CPU creation */ +- cpu_thread_signal_created(cpu); +- qemu_guest_random_seed_thread_part2(cpu->random_seed); +- +- do { +- if (cpu_can_run(cpu)) { +- r = hvf_vcpu_exec(cpu); +- if (r == EXCP_DEBUG) { +- cpu_handle_guest_debug(cpu); +- } +- } +- qemu_wait_io_event(cpu); +- } while (!cpu->unplug || cpu_can_run(cpu)); +- +- hvf_vcpu_destroy(cpu); +- cpu_thread_signal_destroyed(cpu); +- qemu_mutex_unlock_iothread(); +- rcu_unregister_thread(); +- return NULL; +-} +- +-static void hvf_start_vcpu_thread(CPUState *cpu) +-{ +- char thread_name[VCPU_THREAD_NAME_SIZE]; +- +- /* +- * HVF currently does not support TCG, and only runs in +- * unrestricted-guest mode. +- */ +- assert(hvf_enabled()); +- +- cpu->thread = g_malloc0(sizeof(QemuThread)); +- cpu->halt_cond = g_malloc0(sizeof(QemuCond)); +- qemu_cond_init(cpu->halt_cond); +- +- snprintf(thread_name, VCPU_THREAD_NAME_SIZE, "CPU %d/HVF", +- cpu->cpu_index); +- qemu_thread_create(cpu->thread, thread_name, hvf_cpu_thread_fn, +- cpu, QEMU_THREAD_JOINABLE); +-} +- +-static void hvf_accel_ops_class_init(ObjectClass *oc, void *data) +-{ +- AccelOpsClass *ops = ACCEL_OPS_CLASS(oc); +- +- ops->create_vcpu_thread = hvf_start_vcpu_thread; +- +- ops->synchronize_post_reset = hvf_cpu_synchronize_post_reset; +- ops->synchronize_post_init = hvf_cpu_synchronize_post_init; +- ops->synchronize_state = hvf_cpu_synchronize_state; +- ops->synchronize_pre_loadvm = hvf_cpu_synchronize_pre_loadvm; +-}; +-static const TypeInfo hvf_accel_ops_type = { +- .name = ACCEL_OPS_NAME("hvf"), +- +- .parent = TYPE_ACCEL_OPS, +- .class_init = hvf_accel_ops_class_init, +- .abstract = true, +-}; +-static void hvf_accel_ops_register_types(void) +-{ +- type_register_static(&hvf_accel_ops_type); +-} +-type_init(hvf_accel_ops_register_types); +diff --git a/target/i386/hvf/hvf-accel-ops.h b/target/i386/hvf/hvf-accel-ops.h +deleted file mode 100644 +index 8f992da168..0000000000 +--- a/target/i386/hvf/hvf-accel-ops.h ++++ /dev/null +@@ -1,23 +0,0 @@ +-/* +- * Accelerator CPUS Interface +- * +- * Copyright 2020 SUSE LLC +- * +- * This work is licensed under the terms of the GNU GPL, version 2 or later. +- * See the COPYING file in the top-level directory. +- */ +- +-#ifndef HVF_CPUS_H +-#define HVF_CPUS_H +- +-#include "sysemu/cpus.h" +- +-int hvf_init_vcpu(CPUState *); +-int hvf_vcpu_exec(CPUState *); +-void hvf_cpu_synchronize_state(CPUState *); +-void hvf_cpu_synchronize_post_reset(CPUState *); +-void hvf_cpu_synchronize_post_init(CPUState *); +-void hvf_cpu_synchronize_pre_loadvm(CPUState *); +-void hvf_vcpu_destroy(CPUState *); +- +-#endif /* HVF_CPUS_H */ +diff --git a/target/i386/hvf/hvf-i386.h b/target/i386/hvf/hvf-i386.h +index 59cfca8875..76e9235524 100644 +--- a/target/i386/hvf/hvf-i386.h ++++ b/target/i386/hvf/hvf-i386.h +@@ -18,42 +18,11 @@ + + #include "qemu/accel.h" + #include "sysemu/hvf.h" ++#include "sysemu/hvf_int.h" + #include "cpu.h" + #include "x86.h" + +-/* hvf_slot flags */ +-#define HVF_SLOT_LOG (1 << 0) +- +-typedef struct hvf_slot { +- uint64_t start; +- uint64_t size; +- uint8_t *mem; +- int slot_id; +- uint32_t flags; +- MemoryRegion *region; +-} hvf_slot; +- +-typedef struct hvf_vcpu_caps { +- uint64_t vmx_cap_pinbased; +- uint64_t vmx_cap_procbased; +- uint64_t vmx_cap_procbased2; +- uint64_t vmx_cap_entry; +- uint64_t vmx_cap_exit; +- uint64_t vmx_cap_preemption_timer; +-} hvf_vcpu_caps; +- +-struct HVFState { +- AccelState parent; +- hvf_slot slots[32]; +- int num_slots; +- +- hvf_vcpu_caps *hvf_caps; +-}; +-extern HVFState *hvf_state; +- +-void hvf_set_phys_mem(MemoryRegionSection *, bool); + void hvf_handle_io(CPUArchState *, uint16_t, void *, int, int, int); +-hvf_slot *hvf_find_overlap_slot(uint64_t, uint64_t); + + #ifdef NEED_CPU_H + /* Functions exported to host specific mode */ +diff --git a/target/i386/hvf/hvf.c b/target/i386/hvf/hvf.c +index f044181d06..b34006055c 100644 +--- a/target/i386/hvf/hvf.c ++++ b/target/i386/hvf/hvf.c +@@ -51,6 +51,7 @@ + #include "qemu/error-report.h" + + #include "sysemu/hvf.h" ++#include "sysemu/hvf_int.h" + #include "sysemu/runstate.h" + #include "hvf-i386.h" + #include "vmcs.h" +@@ -72,171 +73,6 @@ + #include "qemu/accel.h" + #include "target/i386/cpu.h" + +-#include "hvf-accel-ops.h" +- +-HVFState *hvf_state; +- +-static void assert_hvf_ok(hv_return_t ret) +-{ +- if (ret == HV_SUCCESS) { +- return; +- } +- +- switch (ret) { +- case HV_ERROR: +- error_report("Error: HV_ERROR"); +- break; +- case HV_BUSY: +- error_report("Error: HV_BUSY"); +- break; +- case HV_BAD_ARGUMENT: +- error_report("Error: HV_BAD_ARGUMENT"); +- break; +- case HV_NO_RESOURCES: +- error_report("Error: HV_NO_RESOURCES"); +- break; +- case HV_NO_DEVICE: +- error_report("Error: HV_NO_DEVICE"); +- break; +- case HV_UNSUPPORTED: +- error_report("Error: HV_UNSUPPORTED"); +- break; +- default: +- error_report("Unknown Error"); +- } +- +- abort(); +-} +- +-/* Memory slots */ +-hvf_slot *hvf_find_overlap_slot(uint64_t start, uint64_t size) +-{ +- hvf_slot *slot; +- int x; +- for (x = 0; x < hvf_state->num_slots; ++x) { +- slot = &hvf_state->slots[x]; +- if (slot->size && start < (slot->start + slot->size) && +- (start + size) > slot->start) { +- return slot; +- } +- } +- return NULL; +-} +- +-struct mac_slot { +- int present; +- uint64_t size; +- uint64_t gpa_start; +- uint64_t gva; +-}; +- +-struct mac_slot mac_slots[32]; +- +-static int do_hvf_set_memory(hvf_slot *slot, hv_memory_flags_t flags) +-{ +- struct mac_slot *macslot; +- hv_return_t ret; +- +- macslot = &mac_slots[slot->slot_id]; +- +- if (macslot->present) { +- if (macslot->size != slot->size) { +- macslot->present = 0; +- ret = hv_vm_unmap(macslot->gpa_start, macslot->size); +- assert_hvf_ok(ret); +- } +- } +- +- if (!slot->size) { +- return 0; +- } +- +- macslot->present = 1; +- macslot->gpa_start = slot->start; +- macslot->size = slot->size; +- ret = hv_vm_map((hv_uvaddr_t)slot->mem, slot->start, slot->size, flags); +- assert_hvf_ok(ret); +- return 0; +-} +- +-void hvf_set_phys_mem(MemoryRegionSection *section, bool add) +-{ +- hvf_slot *mem; +- MemoryRegion *area = section->mr; +- bool writeable = !area->readonly && !area->rom_device; +- hv_memory_flags_t flags; +- +- if (!memory_region_is_ram(area)) { +- if (writeable) { +- return; +- } else if (!memory_region_is_romd(area)) { +- /* +- * If the memory device is not in romd_mode, then we actually want +- * to remove the hvf memory slot so all accesses will trap. +- */ +- add = false; +- } +- } +- +- mem = hvf_find_overlap_slot( +- section->offset_within_address_space, +- int128_get64(section->size)); +- +- if (mem && add) { +- if (mem->size == int128_get64(section->size) && +- mem->start == section->offset_within_address_space && +- mem->mem == (memory_region_get_ram_ptr(area) + +- section->offset_within_region)) { +- return; /* Same region was attempted to register, go away. */ +- } +- } +- +- /* Region needs to be reset. set the size to 0 and remap it. */ +- if (mem) { +- mem->size = 0; +- if (do_hvf_set_memory(mem, 0)) { +- error_report("Failed to reset overlapping slot"); +- abort(); +- } +- } +- +- if (!add) { +- return; +- } +- +- if (area->readonly || +- (!memory_region_is_ram(area) && memory_region_is_romd(area))) { +- flags = HV_MEMORY_READ | HV_MEMORY_EXEC; +- } else { +- flags = HV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC; +- } +- +- /* Now make a new slot. */ +- int x; +- +- for (x = 0; x < hvf_state->num_slots; ++x) { +- mem = &hvf_state->slots[x]; +- if (!mem->size) { +- break; +- } +- } +- +- if (x == hvf_state->num_slots) { +- error_report("No free slots"); +- abort(); +- } +- +- mem->size = int128_get64(section->size); +- mem->mem = memory_region_get_ram_ptr(area) + section->offset_within_region; +- mem->start = section->offset_within_address_space; +- mem->region = area; +- +- if (do_hvf_set_memory(mem, flags)) { +- error_report("Error registering new memory slot"); +- abort(); +- } +-} +- + void vmx_update_tpr(CPUState *cpu) + { + /* TODO: need integrate APIC handling */ +@@ -244,11 +80,11 @@ void vmx_update_tpr(CPUState *cpu) + int tpr = cpu_get_apic_tpr(x86_cpu->apic_state) << 4; + int irr = apic_get_highest_priority_irr(x86_cpu->apic_state); + +- wreg(cpu->hvf_fd, HV_X86_TPR, tpr); ++ wreg(cpu->hvf->fd, HV_X86_TPR, tpr); + if (irr == -1) { +- wvmcs(cpu->hvf_fd, VMCS_TPR_THRESHOLD, 0); ++ wvmcs(cpu->hvf->fd, VMCS_TPR_THRESHOLD, 0); + } else { +- wvmcs(cpu->hvf_fd, VMCS_TPR_THRESHOLD, (irr > tpr) ? tpr >> 4 : ++ wvmcs(cpu->hvf->fd, VMCS_TPR_THRESHOLD, (irr > tpr) ? tpr >> 4 : + irr >> 4); + } + } +@@ -256,7 +92,7 @@ void vmx_update_tpr(CPUState *cpu) + static void update_apic_tpr(CPUState *cpu) + { + X86CPU *x86_cpu = X86_CPU(cpu); +- int tpr = rreg(cpu->hvf_fd, HV_X86_TPR) >> 4; ++ int tpr = rreg(cpu->hvf->fd, HV_X86_TPR) >> 4; + cpu_set_apic_tpr(x86_cpu->apic_state, tpr); + } + +@@ -276,56 +112,6 @@ void hvf_handle_io(CPUArchState *env, uint16_t port, void *buffer, + } + } + +-static void do_hvf_cpu_synchronize_state(CPUState *cpu, run_on_cpu_data arg) +-{ +- if (!cpu->vcpu_dirty) { +- hvf_get_registers(cpu); +- cpu->vcpu_dirty = true; +- } +-} +- +-void hvf_cpu_synchronize_state(CPUState *cpu) +-{ +- if (!cpu->vcpu_dirty) { +- run_on_cpu(cpu, do_hvf_cpu_synchronize_state, RUN_ON_CPU_NULL); +- } +-} +- +-static void do_hvf_cpu_synchronize_post_reset(CPUState *cpu, +- run_on_cpu_data arg) +-{ +- hvf_put_registers(cpu); +- cpu->vcpu_dirty = false; +-} +- +-void hvf_cpu_synchronize_post_reset(CPUState *cpu) +-{ +- run_on_cpu(cpu, do_hvf_cpu_synchronize_post_reset, RUN_ON_CPU_NULL); +-} +- +-static void do_hvf_cpu_synchronize_post_init(CPUState *cpu, +- run_on_cpu_data arg) +-{ +- hvf_put_registers(cpu); +- cpu->vcpu_dirty = false; +-} +- +-void hvf_cpu_synchronize_post_init(CPUState *cpu) +-{ +- run_on_cpu(cpu, do_hvf_cpu_synchronize_post_init, RUN_ON_CPU_NULL); +-} +- +-static void do_hvf_cpu_synchronize_pre_loadvm(CPUState *cpu, +- run_on_cpu_data arg) +-{ +- cpu->vcpu_dirty = true; +-} +- +-void hvf_cpu_synchronize_pre_loadvm(CPUState *cpu) +-{ +- run_on_cpu(cpu, do_hvf_cpu_synchronize_pre_loadvm, RUN_ON_CPU_NULL); +-} +- + static bool ept_emulation_fault(hvf_slot *slot, uint64_t gpa, uint64_t ept_qual) + { + int read, write; +@@ -370,90 +156,12 @@ static bool ept_emulation_fault(hvf_slot *slot, uint64_t gpa, uint64_t ept_qual) + return false; + } + +-static void hvf_set_dirty_tracking(MemoryRegionSection *section, bool on) +-{ +- hvf_slot *slot; +- +- slot = hvf_find_overlap_slot( +- section->offset_within_address_space, +- int128_get64(section->size)); +- +- /* protect region against writes; begin tracking it */ +- if (on) { +- slot->flags |= HVF_SLOT_LOG; +- hv_vm_protect((hv_gpaddr_t)slot->start, (size_t)slot->size, +- HV_MEMORY_READ); +- /* stop tracking region*/ +- } else { +- slot->flags &= ~HVF_SLOT_LOG; +- hv_vm_protect((hv_gpaddr_t)slot->start, (size_t)slot->size, +- HV_MEMORY_READ | HV_MEMORY_WRITE); +- } +-} +- +-static void hvf_log_start(MemoryListener *listener, +- MemoryRegionSection *section, int old, int new) +-{ +- if (old != 0) { +- return; +- } +- +- hvf_set_dirty_tracking(section, 1); +-} +- +-static void hvf_log_stop(MemoryListener *listener, +- MemoryRegionSection *section, int old, int new) +-{ +- if (new != 0) { +- return; +- } +- +- hvf_set_dirty_tracking(section, 0); +-} +- +-static void hvf_log_sync(MemoryListener *listener, +- MemoryRegionSection *section) +-{ +- /* +- * sync of dirty pages is handled elsewhere; just make sure we keep +- * tracking the region. +- */ +- hvf_set_dirty_tracking(section, 1); +-} +- +-static void hvf_region_add(MemoryListener *listener, +- MemoryRegionSection *section) +-{ +- hvf_set_phys_mem(section, true); +-} +- +-static void hvf_region_del(MemoryListener *listener, +- MemoryRegionSection *section) +-{ +- hvf_set_phys_mem(section, false); +-} +- +-static MemoryListener hvf_memory_listener = { +- .priority = 10, +- .region_add = hvf_region_add, +- .region_del = hvf_region_del, +- .log_start = hvf_log_start, +- .log_stop = hvf_log_stop, +- .log_sync = hvf_log_sync, +-}; +- +-void hvf_vcpu_destroy(CPUState *cpu) ++void hvf_arch_vcpu_destroy(CPUState *cpu) + { + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + +- hv_return_t ret = hv_vcpu_destroy((hv_vcpuid_t)cpu->hvf_fd); + g_free(env->hvf_mmio_buf); +- assert_hvf_ok(ret); +-} +- +-static void dummy_signal(int sig) +-{ + } + + static void init_tsc_freq(CPUX86State *env) +@@ -498,23 +206,11 @@ static inline bool apic_bus_freq_is_known(CPUX86State *env) + return env->apic_bus_freq != 0; + } + +-int hvf_init_vcpu(CPUState *cpu) ++int hvf_arch_init_vcpu(CPUState *cpu) + { + + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; +- int r; +- +- /* init cpu signals */ +- sigset_t set; +- struct sigaction sigact; +- +- memset(&sigact, 0, sizeof(sigact)); +- sigact.sa_handler = dummy_signal; +- sigaction(SIG_IPI, &sigact, NULL); +- +- pthread_sigmask(SIG_BLOCK, NULL, &set); +- sigdelset(&set, SIG_IPI); + + init_emu(); + init_decoder(); +@@ -531,10 +227,6 @@ int hvf_init_vcpu(CPUState *cpu) + } + } + +- r = hv_vcpu_create((hv_vcpuid_t *)&cpu->hvf_fd, HV_VCPU_DEFAULT); +- cpu->vcpu_dirty = 1; +- assert_hvf_ok(r); +- + if (hv_vmx_read_capability(HV_VMX_CAP_PINBASED, + &hvf_state->hvf_caps->vmx_cap_pinbased)) { + abort(); +@@ -553,43 +245,43 @@ int hvf_init_vcpu(CPUState *cpu) + } + + /* set VMCS control fields */ +- wvmcs(cpu->hvf_fd, VMCS_PIN_BASED_CTLS, ++ wvmcs(cpu->hvf->fd, VMCS_PIN_BASED_CTLS, + cap2ctrl(hvf_state->hvf_caps->vmx_cap_pinbased, + VMCS_PIN_BASED_CTLS_EXTINT | + VMCS_PIN_BASED_CTLS_NMI | + VMCS_PIN_BASED_CTLS_VNMI)); +- wvmcs(cpu->hvf_fd, VMCS_PRI_PROC_BASED_CTLS, ++ wvmcs(cpu->hvf->fd, VMCS_PRI_PROC_BASED_CTLS, + cap2ctrl(hvf_state->hvf_caps->vmx_cap_procbased, + VMCS_PRI_PROC_BASED_CTLS_HLT | + VMCS_PRI_PROC_BASED_CTLS_MWAIT | + VMCS_PRI_PROC_BASED_CTLS_TSC_OFFSET | + VMCS_PRI_PROC_BASED_CTLS_TPR_SHADOW) | + VMCS_PRI_PROC_BASED_CTLS_SEC_CONTROL); +- wvmcs(cpu->hvf_fd, VMCS_SEC_PROC_BASED_CTLS, ++ wvmcs(cpu->hvf->fd, VMCS_SEC_PROC_BASED_CTLS, + cap2ctrl(hvf_state->hvf_caps->vmx_cap_procbased2, + VMCS_PRI_PROC_BASED2_CTLS_APIC_ACCESSES)); + +- wvmcs(cpu->hvf_fd, VMCS_ENTRY_CTLS, cap2ctrl(hvf_state->hvf_caps->vmx_cap_entry, ++ wvmcs(cpu->hvf->fd, VMCS_ENTRY_CTLS, cap2ctrl(hvf_state->hvf_caps->vmx_cap_entry, + 0)); +- wvmcs(cpu->hvf_fd, VMCS_EXCEPTION_BITMAP, 0); /* Double fault */ ++ wvmcs(cpu->hvf->fd, VMCS_EXCEPTION_BITMAP, 0); /* Double fault */ + +- wvmcs(cpu->hvf_fd, VMCS_TPR_THRESHOLD, 0); ++ wvmcs(cpu->hvf->fd, VMCS_TPR_THRESHOLD, 0); + + x86cpu = X86_CPU(cpu); + x86cpu->env.xsave_buf = qemu_memalign(4096, 4096); + +- hv_vcpu_enable_native_msr(cpu->hvf_fd, MSR_STAR, 1); +- hv_vcpu_enable_native_msr(cpu->hvf_fd, MSR_LSTAR, 1); +- hv_vcpu_enable_native_msr(cpu->hvf_fd, MSR_CSTAR, 1); +- hv_vcpu_enable_native_msr(cpu->hvf_fd, MSR_FMASK, 1); +- hv_vcpu_enable_native_msr(cpu->hvf_fd, MSR_FSBASE, 1); +- hv_vcpu_enable_native_msr(cpu->hvf_fd, MSR_GSBASE, 1); +- hv_vcpu_enable_native_msr(cpu->hvf_fd, MSR_KERNELGSBASE, 1); +- hv_vcpu_enable_native_msr(cpu->hvf_fd, MSR_TSC_AUX, 1); +- hv_vcpu_enable_native_msr(cpu->hvf_fd, MSR_IA32_TSC, 1); +- hv_vcpu_enable_native_msr(cpu->hvf_fd, MSR_IA32_SYSENTER_CS, 1); +- hv_vcpu_enable_native_msr(cpu->hvf_fd, MSR_IA32_SYSENTER_EIP, 1); +- hv_vcpu_enable_native_msr(cpu->hvf_fd, MSR_IA32_SYSENTER_ESP, 1); ++ hv_vcpu_enable_native_msr(cpu->hvf->fd, MSR_STAR, 1); ++ hv_vcpu_enable_native_msr(cpu->hvf->fd, MSR_LSTAR, 1); ++ hv_vcpu_enable_native_msr(cpu->hvf->fd, MSR_CSTAR, 1); ++ hv_vcpu_enable_native_msr(cpu->hvf->fd, MSR_FMASK, 1); ++ hv_vcpu_enable_native_msr(cpu->hvf->fd, MSR_FSBASE, 1); ++ hv_vcpu_enable_native_msr(cpu->hvf->fd, MSR_GSBASE, 1); ++ hv_vcpu_enable_native_msr(cpu->hvf->fd, MSR_KERNELGSBASE, 1); ++ hv_vcpu_enable_native_msr(cpu->hvf->fd, MSR_TSC_AUX, 1); ++ hv_vcpu_enable_native_msr(cpu->hvf->fd, MSR_IA32_TSC, 1); ++ hv_vcpu_enable_native_msr(cpu->hvf->fd, MSR_IA32_SYSENTER_CS, 1); ++ hv_vcpu_enable_native_msr(cpu->hvf->fd, MSR_IA32_SYSENTER_EIP, 1); ++ hv_vcpu_enable_native_msr(cpu->hvf->fd, MSR_IA32_SYSENTER_ESP, 1); + + return 0; + } +@@ -630,16 +322,16 @@ static void hvf_store_events(CPUState *cpu, uint32_t ins_len, uint64_t idtvec_in + } + if (idtvec_info & VMCS_IDT_VEC_ERRCODE_VALID) { + env->has_error_code = true; +- env->error_code = rvmcs(cpu->hvf_fd, VMCS_IDT_VECTORING_ERROR); ++ env->error_code = rvmcs(cpu->hvf->fd, VMCS_IDT_VECTORING_ERROR); + } + } +- if ((rvmcs(cpu->hvf_fd, VMCS_GUEST_INTERRUPTIBILITY) & ++ if ((rvmcs(cpu->hvf->fd, VMCS_GUEST_INTERRUPTIBILITY) & + VMCS_INTERRUPTIBILITY_NMI_BLOCKING)) { + env->hflags2 |= HF2_NMI_MASK; + } else { + env->hflags2 &= ~HF2_NMI_MASK; + } +- if (rvmcs(cpu->hvf_fd, VMCS_GUEST_INTERRUPTIBILITY) & ++ if (rvmcs(cpu->hvf->fd, VMCS_GUEST_INTERRUPTIBILITY) & + (VMCS_INTERRUPTIBILITY_STI_BLOCKING | + VMCS_INTERRUPTIBILITY_MOVSS_BLOCKING)) { + env->hflags |= HF_INHIBIT_IRQ_MASK; +@@ -718,20 +410,20 @@ int hvf_vcpu_exec(CPUState *cpu) + return EXCP_HLT; + } + +- hv_return_t r = hv_vcpu_run(cpu->hvf_fd); ++ hv_return_t r = hv_vcpu_run(cpu->hvf->fd); + assert_hvf_ok(r); + + /* handle VMEXIT */ +- uint64_t exit_reason = rvmcs(cpu->hvf_fd, VMCS_EXIT_REASON); +- uint64_t exit_qual = rvmcs(cpu->hvf_fd, VMCS_EXIT_QUALIFICATION); +- uint32_t ins_len = (uint32_t)rvmcs(cpu->hvf_fd, ++ uint64_t exit_reason = rvmcs(cpu->hvf->fd, VMCS_EXIT_REASON); ++ uint64_t exit_qual = rvmcs(cpu->hvf->fd, VMCS_EXIT_QUALIFICATION); ++ uint32_t ins_len = (uint32_t)rvmcs(cpu->hvf->fd, + VMCS_EXIT_INSTRUCTION_LENGTH); + +- uint64_t idtvec_info = rvmcs(cpu->hvf_fd, VMCS_IDT_VECTORING_INFO); ++ uint64_t idtvec_info = rvmcs(cpu->hvf->fd, VMCS_IDT_VECTORING_INFO); + + hvf_store_events(cpu, ins_len, idtvec_info); +- rip = rreg(cpu->hvf_fd, HV_X86_RIP); +- env->eflags = rreg(cpu->hvf_fd, HV_X86_RFLAGS); ++ rip = rreg(cpu->hvf->fd, HV_X86_RIP); ++ env->eflags = rreg(cpu->hvf->fd, HV_X86_RFLAGS); + + qemu_mutex_lock_iothread(); + +@@ -761,7 +453,7 @@ int hvf_vcpu_exec(CPUState *cpu) + case EXIT_REASON_EPT_FAULT: + { + hvf_slot *slot; +- uint64_t gpa = rvmcs(cpu->hvf_fd, VMCS_GUEST_PHYSICAL_ADDRESS); ++ uint64_t gpa = rvmcs(cpu->hvf->fd, VMCS_GUEST_PHYSICAL_ADDRESS); + + if (((idtvec_info & VMCS_IDT_VEC_VALID) == 0) && + ((exit_qual & EXIT_QUAL_NMIUDTI) != 0)) { +@@ -806,7 +498,7 @@ int hvf_vcpu_exec(CPUState *cpu) + store_regs(cpu); + break; + } else if (!string && !in) { +- RAX(env) = rreg(cpu->hvf_fd, HV_X86_RAX); ++ RAX(env) = rreg(cpu->hvf->fd, HV_X86_RAX); + hvf_handle_io(env, port, &RAX(env), 1, size, 1); + macvm_set_rip(cpu, rip + ins_len); + break; +@@ -822,21 +514,21 @@ int hvf_vcpu_exec(CPUState *cpu) + break; + } + case EXIT_REASON_CPUID: { +- uint32_t rax = (uint32_t)rreg(cpu->hvf_fd, HV_X86_RAX); +- uint32_t rbx = (uint32_t)rreg(cpu->hvf_fd, HV_X86_RBX); +- uint32_t rcx = (uint32_t)rreg(cpu->hvf_fd, HV_X86_RCX); +- uint32_t rdx = (uint32_t)rreg(cpu->hvf_fd, HV_X86_RDX); ++ uint32_t rax = (uint32_t)rreg(cpu->hvf->fd, HV_X86_RAX); ++ uint32_t rbx = (uint32_t)rreg(cpu->hvf->fd, HV_X86_RBX); ++ uint32_t rcx = (uint32_t)rreg(cpu->hvf->fd, HV_X86_RCX); ++ uint32_t rdx = (uint32_t)rreg(cpu->hvf->fd, HV_X86_RDX); + + if (rax == 1) { + /* CPUID1.ecx.OSXSAVE needs to know CR4 */ +- env->cr[4] = rvmcs(cpu->hvf_fd, VMCS_GUEST_CR4); ++ env->cr[4] = rvmcs(cpu->hvf->fd, VMCS_GUEST_CR4); + } + hvf_cpu_x86_cpuid(env, rax, rcx, &rax, &rbx, &rcx, &rdx); + +- wreg(cpu->hvf_fd, HV_X86_RAX, rax); +- wreg(cpu->hvf_fd, HV_X86_RBX, rbx); +- wreg(cpu->hvf_fd, HV_X86_RCX, rcx); +- wreg(cpu->hvf_fd, HV_X86_RDX, rdx); ++ wreg(cpu->hvf->fd, HV_X86_RAX, rax); ++ wreg(cpu->hvf->fd, HV_X86_RBX, rbx); ++ wreg(cpu->hvf->fd, HV_X86_RCX, rcx); ++ wreg(cpu->hvf->fd, HV_X86_RDX, rdx); + + macvm_set_rip(cpu, rip + ins_len); + break; +@@ -844,16 +536,16 @@ int hvf_vcpu_exec(CPUState *cpu) + case EXIT_REASON_XSETBV: { + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; +- uint32_t eax = (uint32_t)rreg(cpu->hvf_fd, HV_X86_RAX); +- uint32_t ecx = (uint32_t)rreg(cpu->hvf_fd, HV_X86_RCX); +- uint32_t edx = (uint32_t)rreg(cpu->hvf_fd, HV_X86_RDX); ++ uint32_t eax = (uint32_t)rreg(cpu->hvf->fd, HV_X86_RAX); ++ uint32_t ecx = (uint32_t)rreg(cpu->hvf->fd, HV_X86_RCX); ++ uint32_t edx = (uint32_t)rreg(cpu->hvf->fd, HV_X86_RDX); + + if (ecx) { + macvm_set_rip(cpu, rip + ins_len); + break; + } + env->xcr0 = ((uint64_t)edx << 32) | eax; +- wreg(cpu->hvf_fd, HV_X86_XCR0, env->xcr0 | 1); ++ wreg(cpu->hvf->fd, HV_X86_XCR0, env->xcr0 | 1); + macvm_set_rip(cpu, rip + ins_len); + break; + } +@@ -892,11 +584,11 @@ int hvf_vcpu_exec(CPUState *cpu) + + switch (cr) { + case 0x0: { +- macvm_set_cr0(cpu->hvf_fd, RRX(env, reg)); ++ macvm_set_cr0(cpu->hvf->fd, RRX(env, reg)); + break; + } + case 4: { +- macvm_set_cr4(cpu->hvf_fd, RRX(env, reg)); ++ macvm_set_cr4(cpu->hvf->fd, RRX(env, reg)); + break; + } + case 8: { +@@ -932,7 +624,7 @@ int hvf_vcpu_exec(CPUState *cpu) + break; + } + case EXIT_REASON_TASK_SWITCH: { +- uint64_t vinfo = rvmcs(cpu->hvf_fd, VMCS_IDT_VECTORING_INFO); ++ uint64_t vinfo = rvmcs(cpu->hvf->fd, VMCS_IDT_VECTORING_INFO); + x68_segment_selector sel = {.sel = exit_qual & 0xffff}; + vmx_handle_task_switch(cpu, sel, (exit_qual >> 30) & 0x3, + vinfo & VMCS_INTR_VALID, vinfo & VECTORING_INFO_VECTOR_MASK, vinfo +@@ -945,8 +637,8 @@ int hvf_vcpu_exec(CPUState *cpu) + break; + } + case EXIT_REASON_RDPMC: +- wreg(cpu->hvf_fd, HV_X86_RAX, 0); +- wreg(cpu->hvf_fd, HV_X86_RDX, 0); ++ wreg(cpu->hvf->fd, HV_X86_RAX, 0); ++ wreg(cpu->hvf->fd, HV_X86_RDX, 0); + macvm_set_rip(cpu, rip + ins_len); + break; + case VMX_REASON_VMCALL: +@@ -962,48 +654,3 @@ int hvf_vcpu_exec(CPUState *cpu) + + return ret; + } +- +-bool hvf_allowed; +- +-static int hvf_accel_init(MachineState *ms) +-{ +- int x; +- hv_return_t ret; +- HVFState *s; +- +- ret = hv_vm_create(HV_VM_DEFAULT); +- assert_hvf_ok(ret); +- +- s = g_new0(HVFState, 1); +- +- s->num_slots = 32; +- for (x = 0; x < s->num_slots; ++x) { +- s->slots[x].size = 0; +- s->slots[x].slot_id = x; +- } +- +- hvf_state = s; +- memory_listener_register(&hvf_memory_listener, &address_space_memory); +- return 0; +-} +- +-static void hvf_accel_class_init(ObjectClass *oc, void *data) +-{ +- AccelClass *ac = ACCEL_CLASS(oc); +- ac->name = "HVF"; +- ac->init_machine = hvf_accel_init; +- ac->allowed = &hvf_allowed; +-} +- +-static const TypeInfo hvf_accel_type = { +- .name = TYPE_HVF_ACCEL, +- .parent = TYPE_ACCEL, +- .class_init = hvf_accel_class_init, +-}; +- +-static void hvf_type_init(void) +-{ +- type_register_static(&hvf_accel_type); +-} +- +-type_init(hvf_type_init); +diff --git a/target/i386/hvf/meson.build b/target/i386/hvf/meson.build +index e9eb5a5da8..c8a43717ee 100644 +--- a/target/i386/hvf/meson.build ++++ b/target/i386/hvf/meson.build +@@ -1,6 +1,5 @@ + i386_softmmu_ss.add(when: [hvf, 'CONFIG_HVF'], if_true: files( + 'hvf.c', +- 'hvf-accel-ops.c', + 'x86.c', + 'x86_cpuid.c', + 'x86_decode.c', +diff --git a/target/i386/hvf/vmx.h b/target/i386/hvf/vmx.h +index 24c4cdf0be..6df87116f6 100644 +--- a/target/i386/hvf/vmx.h ++++ b/target/i386/hvf/vmx.h +@@ -30,6 +30,8 @@ + #include "vmcs.h" + #include "cpu.h" + #include "x86.h" ++#include "sysemu/hvf.h" ++#include "sysemu/hvf_int.h" + + #include "exec/address-spaces.h" + +@@ -179,15 +181,15 @@ static inline void macvm_set_rip(CPUState *cpu, uint64_t rip) + uint64_t val; + + /* BUG, should take considering overlap.. */ +- wreg(cpu->hvf_fd, HV_X86_RIP, rip); ++ wreg(cpu->hvf->fd, HV_X86_RIP, rip); + env->eip = rip; + + /* after moving forward in rip, we need to clean INTERRUPTABILITY */ +- val = rvmcs(cpu->hvf_fd, VMCS_GUEST_INTERRUPTIBILITY); ++ val = rvmcs(cpu->hvf->fd, VMCS_GUEST_INTERRUPTIBILITY); + if (val & (VMCS_INTERRUPTIBILITY_STI_BLOCKING | + VMCS_INTERRUPTIBILITY_MOVSS_BLOCKING)) { + env->hflags &= ~HF_INHIBIT_IRQ_MASK; +- wvmcs(cpu->hvf_fd, VMCS_GUEST_INTERRUPTIBILITY, ++ wvmcs(cpu->hvf->fd, VMCS_GUEST_INTERRUPTIBILITY, + val & ~(VMCS_INTERRUPTIBILITY_STI_BLOCKING | + VMCS_INTERRUPTIBILITY_MOVSS_BLOCKING)); + } +@@ -199,9 +201,9 @@ static inline void vmx_clear_nmi_blocking(CPUState *cpu) + CPUX86State *env = &x86_cpu->env; + + env->hflags2 &= ~HF2_NMI_MASK; +- uint32_t gi = (uint32_t) rvmcs(cpu->hvf_fd, VMCS_GUEST_INTERRUPTIBILITY); ++ uint32_t gi = (uint32_t) rvmcs(cpu->hvf->fd, VMCS_GUEST_INTERRUPTIBILITY); + gi &= ~VMCS_INTERRUPTIBILITY_NMI_BLOCKING; +- wvmcs(cpu->hvf_fd, VMCS_GUEST_INTERRUPTIBILITY, gi); ++ wvmcs(cpu->hvf->fd, VMCS_GUEST_INTERRUPTIBILITY, gi); + } + + static inline void vmx_set_nmi_blocking(CPUState *cpu) +@@ -210,16 +212,16 @@ static inline void vmx_set_nmi_blocking(CPUState *cpu) + CPUX86State *env = &x86_cpu->env; + + env->hflags2 |= HF2_NMI_MASK; +- uint32_t gi = (uint32_t)rvmcs(cpu->hvf_fd, VMCS_GUEST_INTERRUPTIBILITY); ++ uint32_t gi = (uint32_t)rvmcs(cpu->hvf->fd, VMCS_GUEST_INTERRUPTIBILITY); + gi |= VMCS_INTERRUPTIBILITY_NMI_BLOCKING; +- wvmcs(cpu->hvf_fd, VMCS_GUEST_INTERRUPTIBILITY, gi); ++ wvmcs(cpu->hvf->fd, VMCS_GUEST_INTERRUPTIBILITY, gi); + } + + static inline void vmx_set_nmi_window_exiting(CPUState *cpu) + { + uint64_t val; +- val = rvmcs(cpu->hvf_fd, VMCS_PRI_PROC_BASED_CTLS); +- wvmcs(cpu->hvf_fd, VMCS_PRI_PROC_BASED_CTLS, val | ++ val = rvmcs(cpu->hvf->fd, VMCS_PRI_PROC_BASED_CTLS); ++ wvmcs(cpu->hvf->fd, VMCS_PRI_PROC_BASED_CTLS, val | + VMCS_PRI_PROC_BASED_CTLS_NMI_WINDOW_EXITING); + + } +@@ -228,8 +230,8 @@ static inline void vmx_clear_nmi_window_exiting(CPUState *cpu) + { + + uint64_t val; +- val = rvmcs(cpu->hvf_fd, VMCS_PRI_PROC_BASED_CTLS); +- wvmcs(cpu->hvf_fd, VMCS_PRI_PROC_BASED_CTLS, val & ++ val = rvmcs(cpu->hvf->fd, VMCS_PRI_PROC_BASED_CTLS); ++ wvmcs(cpu->hvf->fd, VMCS_PRI_PROC_BASED_CTLS, val & + ~VMCS_PRI_PROC_BASED_CTLS_NMI_WINDOW_EXITING); + } + +diff --git a/target/i386/hvf/x86.c b/target/i386/hvf/x86.c +index cd045183a8..2898bb70a8 100644 +--- a/target/i386/hvf/x86.c ++++ b/target/i386/hvf/x86.c +@@ -62,11 +62,11 @@ bool x86_read_segment_descriptor(struct CPUState *cpu, + } + + if (GDT_SEL == sel.ti) { +- base = rvmcs(cpu->hvf_fd, VMCS_GUEST_GDTR_BASE); +- limit = rvmcs(cpu->hvf_fd, VMCS_GUEST_GDTR_LIMIT); ++ base = rvmcs(cpu->hvf->fd, VMCS_GUEST_GDTR_BASE); ++ limit = rvmcs(cpu->hvf->fd, VMCS_GUEST_GDTR_LIMIT); + } else { +- base = rvmcs(cpu->hvf_fd, VMCS_GUEST_LDTR_BASE); +- limit = rvmcs(cpu->hvf_fd, VMCS_GUEST_LDTR_LIMIT); ++ base = rvmcs(cpu->hvf->fd, VMCS_GUEST_LDTR_BASE); ++ limit = rvmcs(cpu->hvf->fd, VMCS_GUEST_LDTR_LIMIT); + } + + if (sel.index * 8 >= limit) { +@@ -85,11 +85,11 @@ bool x86_write_segment_descriptor(struct CPUState *cpu, + uint32_t limit; + + if (GDT_SEL == sel.ti) { +- base = rvmcs(cpu->hvf_fd, VMCS_GUEST_GDTR_BASE); +- limit = rvmcs(cpu->hvf_fd, VMCS_GUEST_GDTR_LIMIT); ++ base = rvmcs(cpu->hvf->fd, VMCS_GUEST_GDTR_BASE); ++ limit = rvmcs(cpu->hvf->fd, VMCS_GUEST_GDTR_LIMIT); + } else { +- base = rvmcs(cpu->hvf_fd, VMCS_GUEST_LDTR_BASE); +- limit = rvmcs(cpu->hvf_fd, VMCS_GUEST_LDTR_LIMIT); ++ base = rvmcs(cpu->hvf->fd, VMCS_GUEST_LDTR_BASE); ++ limit = rvmcs(cpu->hvf->fd, VMCS_GUEST_LDTR_LIMIT); + } + + if (sel.index * 8 >= limit) { +@@ -103,8 +103,8 @@ bool x86_write_segment_descriptor(struct CPUState *cpu, + bool x86_read_call_gate(struct CPUState *cpu, struct x86_call_gate *idt_desc, + int gate) + { +- target_ulong base = rvmcs(cpu->hvf_fd, VMCS_GUEST_IDTR_BASE); +- uint32_t limit = rvmcs(cpu->hvf_fd, VMCS_GUEST_IDTR_LIMIT); ++ target_ulong base = rvmcs(cpu->hvf->fd, VMCS_GUEST_IDTR_BASE); ++ uint32_t limit = rvmcs(cpu->hvf->fd, VMCS_GUEST_IDTR_LIMIT); + + memset(idt_desc, 0, sizeof(*idt_desc)); + if (gate * 8 >= limit) { +@@ -118,7 +118,7 @@ bool x86_read_call_gate(struct CPUState *cpu, struct x86_call_gate *idt_desc, + + bool x86_is_protected(struct CPUState *cpu) + { +- uint64_t cr0 = rvmcs(cpu->hvf_fd, VMCS_GUEST_CR0); ++ uint64_t cr0 = rvmcs(cpu->hvf->fd, VMCS_GUEST_CR0); + return cr0 & CR0_PE; + } + +@@ -136,7 +136,7 @@ bool x86_is_v8086(struct CPUState *cpu) + + bool x86_is_long_mode(struct CPUState *cpu) + { +- return rvmcs(cpu->hvf_fd, VMCS_GUEST_IA32_EFER) & MSR_EFER_LMA; ++ return rvmcs(cpu->hvf->fd, VMCS_GUEST_IA32_EFER) & MSR_EFER_LMA; + } + + bool x86_is_long64_mode(struct CPUState *cpu) +@@ -149,13 +149,13 @@ bool x86_is_long64_mode(struct CPUState *cpu) + + bool x86_is_paging_mode(struct CPUState *cpu) + { +- uint64_t cr0 = rvmcs(cpu->hvf_fd, VMCS_GUEST_CR0); ++ uint64_t cr0 = rvmcs(cpu->hvf->fd, VMCS_GUEST_CR0); + return cr0 & CR0_PG; + } + + bool x86_is_pae_enabled(struct CPUState *cpu) + { +- uint64_t cr4 = rvmcs(cpu->hvf_fd, VMCS_GUEST_CR4); ++ uint64_t cr4 = rvmcs(cpu->hvf->fd, VMCS_GUEST_CR4); + return cr4 & CR4_PAE; + } + +diff --git a/target/i386/hvf/x86_descr.c b/target/i386/hvf/x86_descr.c +index 9f539e73f6..af15c06ac5 100644 +--- a/target/i386/hvf/x86_descr.c ++++ b/target/i386/hvf/x86_descr.c +@@ -48,47 +48,47 @@ static const struct vmx_segment_field { + + uint32_t vmx_read_segment_limit(CPUState *cpu, X86Seg seg) + { +- return (uint32_t)rvmcs(cpu->hvf_fd, vmx_segment_fields[seg].limit); ++ return (uint32_t)rvmcs(cpu->hvf->fd, vmx_segment_fields[seg].limit); + } + + uint32_t vmx_read_segment_ar(CPUState *cpu, X86Seg seg) + { +- return (uint32_t)rvmcs(cpu->hvf_fd, vmx_segment_fields[seg].ar_bytes); ++ return (uint32_t)rvmcs(cpu->hvf->fd, vmx_segment_fields[seg].ar_bytes); + } + + uint64_t vmx_read_segment_base(CPUState *cpu, X86Seg seg) + { +- return rvmcs(cpu->hvf_fd, vmx_segment_fields[seg].base); ++ return rvmcs(cpu->hvf->fd, vmx_segment_fields[seg].base); + } + + x68_segment_selector vmx_read_segment_selector(CPUState *cpu, X86Seg seg) + { + x68_segment_selector sel; +- sel.sel = rvmcs(cpu->hvf_fd, vmx_segment_fields[seg].selector); ++ sel.sel = rvmcs(cpu->hvf->fd, vmx_segment_fields[seg].selector); + return sel; + } + + void vmx_write_segment_selector(struct CPUState *cpu, x68_segment_selector selector, X86Seg seg) + { +- wvmcs(cpu->hvf_fd, vmx_segment_fields[seg].selector, selector.sel); ++ wvmcs(cpu->hvf->fd, vmx_segment_fields[seg].selector, selector.sel); + } + + void vmx_read_segment_descriptor(struct CPUState *cpu, struct vmx_segment *desc, X86Seg seg) + { +- desc->sel = rvmcs(cpu->hvf_fd, vmx_segment_fields[seg].selector); +- desc->base = rvmcs(cpu->hvf_fd, vmx_segment_fields[seg].base); +- desc->limit = rvmcs(cpu->hvf_fd, vmx_segment_fields[seg].limit); +- desc->ar = rvmcs(cpu->hvf_fd, vmx_segment_fields[seg].ar_bytes); ++ desc->sel = rvmcs(cpu->hvf->fd, vmx_segment_fields[seg].selector); ++ desc->base = rvmcs(cpu->hvf->fd, vmx_segment_fields[seg].base); ++ desc->limit = rvmcs(cpu->hvf->fd, vmx_segment_fields[seg].limit); ++ desc->ar = rvmcs(cpu->hvf->fd, vmx_segment_fields[seg].ar_bytes); + } + + void vmx_write_segment_descriptor(CPUState *cpu, struct vmx_segment *desc, X86Seg seg) + { + const struct vmx_segment_field *sf = &vmx_segment_fields[seg]; + +- wvmcs(cpu->hvf_fd, sf->base, desc->base); +- wvmcs(cpu->hvf_fd, sf->limit, desc->limit); +- wvmcs(cpu->hvf_fd, sf->selector, desc->sel); +- wvmcs(cpu->hvf_fd, sf->ar_bytes, desc->ar); ++ wvmcs(cpu->hvf->fd, sf->base, desc->base); ++ wvmcs(cpu->hvf->fd, sf->limit, desc->limit); ++ wvmcs(cpu->hvf->fd, sf->selector, desc->sel); ++ wvmcs(cpu->hvf->fd, sf->ar_bytes, desc->ar); + } + + void x86_segment_descriptor_to_vmx(struct CPUState *cpu, x68_segment_selector selector, struct x86_segment_descriptor *desc, struct vmx_segment *vmx_desc) +diff --git a/target/i386/hvf/x86_emu.c b/target/i386/hvf/x86_emu.c +index e52c39ddb1..7c8203b21f 100644 +--- a/target/i386/hvf/x86_emu.c ++++ b/target/i386/hvf/x86_emu.c +@@ -674,7 +674,7 @@ void simulate_rdmsr(struct CPUState *cpu) + + switch (msr) { + case MSR_IA32_TSC: +- val = rdtscp() + rvmcs(cpu->hvf_fd, VMCS_TSC_OFFSET); ++ val = rdtscp() + rvmcs(cpu->hvf->fd, VMCS_TSC_OFFSET); + break; + case MSR_IA32_APICBASE: + val = cpu_get_apic_base(X86_CPU(cpu)->apic_state); +@@ -683,16 +683,16 @@ void simulate_rdmsr(struct CPUState *cpu) + val = x86_cpu->ucode_rev; + break; + case MSR_EFER: +- val = rvmcs(cpu->hvf_fd, VMCS_GUEST_IA32_EFER); ++ val = rvmcs(cpu->hvf->fd, VMCS_GUEST_IA32_EFER); + break; + case MSR_FSBASE: +- val = rvmcs(cpu->hvf_fd, VMCS_GUEST_FS_BASE); ++ val = rvmcs(cpu->hvf->fd, VMCS_GUEST_FS_BASE); + break; + case MSR_GSBASE: +- val = rvmcs(cpu->hvf_fd, VMCS_GUEST_GS_BASE); ++ val = rvmcs(cpu->hvf->fd, VMCS_GUEST_GS_BASE); + break; + case MSR_KERNELGSBASE: +- val = rvmcs(cpu->hvf_fd, VMCS_HOST_FS_BASE); ++ val = rvmcs(cpu->hvf->fd, VMCS_HOST_FS_BASE); + break; + case MSR_STAR: + abort(); +@@ -780,13 +780,13 @@ void simulate_wrmsr(struct CPUState *cpu) + cpu_set_apic_base(X86_CPU(cpu)->apic_state, data); + break; + case MSR_FSBASE: +- wvmcs(cpu->hvf_fd, VMCS_GUEST_FS_BASE, data); ++ wvmcs(cpu->hvf->fd, VMCS_GUEST_FS_BASE, data); + break; + case MSR_GSBASE: +- wvmcs(cpu->hvf_fd, VMCS_GUEST_GS_BASE, data); ++ wvmcs(cpu->hvf->fd, VMCS_GUEST_GS_BASE, data); + break; + case MSR_KERNELGSBASE: +- wvmcs(cpu->hvf_fd, VMCS_HOST_FS_BASE, data); ++ wvmcs(cpu->hvf->fd, VMCS_HOST_FS_BASE, data); + break; + case MSR_STAR: + abort(); +@@ -799,9 +799,9 @@ void simulate_wrmsr(struct CPUState *cpu) + break; + case MSR_EFER: + /*printf("new efer %llx\n", EFER(cpu));*/ +- wvmcs(cpu->hvf_fd, VMCS_GUEST_IA32_EFER, data); ++ wvmcs(cpu->hvf->fd, VMCS_GUEST_IA32_EFER, data); + if (data & MSR_EFER_NXE) { +- hv_vcpu_invalidate_tlb(cpu->hvf_fd); ++ hv_vcpu_invalidate_tlb(cpu->hvf->fd); + } + break; + case MSR_MTRRphysBase(0): +@@ -1425,21 +1425,21 @@ void load_regs(struct CPUState *cpu) + CPUX86State *env = &x86_cpu->env; + + int i = 0; +- RRX(env, R_EAX) = rreg(cpu->hvf_fd, HV_X86_RAX); +- RRX(env, R_EBX) = rreg(cpu->hvf_fd, HV_X86_RBX); +- RRX(env, R_ECX) = rreg(cpu->hvf_fd, HV_X86_RCX); +- RRX(env, R_EDX) = rreg(cpu->hvf_fd, HV_X86_RDX); +- RRX(env, R_ESI) = rreg(cpu->hvf_fd, HV_X86_RSI); +- RRX(env, R_EDI) = rreg(cpu->hvf_fd, HV_X86_RDI); +- RRX(env, R_ESP) = rreg(cpu->hvf_fd, HV_X86_RSP); +- RRX(env, R_EBP) = rreg(cpu->hvf_fd, HV_X86_RBP); ++ RRX(env, R_EAX) = rreg(cpu->hvf->fd, HV_X86_RAX); ++ RRX(env, R_EBX) = rreg(cpu->hvf->fd, HV_X86_RBX); ++ RRX(env, R_ECX) = rreg(cpu->hvf->fd, HV_X86_RCX); ++ RRX(env, R_EDX) = rreg(cpu->hvf->fd, HV_X86_RDX); ++ RRX(env, R_ESI) = rreg(cpu->hvf->fd, HV_X86_RSI); ++ RRX(env, R_EDI) = rreg(cpu->hvf->fd, HV_X86_RDI); ++ RRX(env, R_ESP) = rreg(cpu->hvf->fd, HV_X86_RSP); ++ RRX(env, R_EBP) = rreg(cpu->hvf->fd, HV_X86_RBP); + for (i = 8; i < 16; i++) { +- RRX(env, i) = rreg(cpu->hvf_fd, HV_X86_RAX + i); ++ RRX(env, i) = rreg(cpu->hvf->fd, HV_X86_RAX + i); + } + +- env->eflags = rreg(cpu->hvf_fd, HV_X86_RFLAGS); ++ env->eflags = rreg(cpu->hvf->fd, HV_X86_RFLAGS); + rflags_to_lflags(env); +- env->eip = rreg(cpu->hvf_fd, HV_X86_RIP); ++ env->eip = rreg(cpu->hvf->fd, HV_X86_RIP); + } + + void store_regs(struct CPUState *cpu) +@@ -1448,20 +1448,20 @@ void store_regs(struct CPUState *cpu) + CPUX86State *env = &x86_cpu->env; + + int i = 0; +- wreg(cpu->hvf_fd, HV_X86_RAX, RAX(env)); +- wreg(cpu->hvf_fd, HV_X86_RBX, RBX(env)); +- wreg(cpu->hvf_fd, HV_X86_RCX, RCX(env)); +- wreg(cpu->hvf_fd, HV_X86_RDX, RDX(env)); +- wreg(cpu->hvf_fd, HV_X86_RSI, RSI(env)); +- wreg(cpu->hvf_fd, HV_X86_RDI, RDI(env)); +- wreg(cpu->hvf_fd, HV_X86_RBP, RBP(env)); +- wreg(cpu->hvf_fd, HV_X86_RSP, RSP(env)); ++ wreg(cpu->hvf->fd, HV_X86_RAX, RAX(env)); ++ wreg(cpu->hvf->fd, HV_X86_RBX, RBX(env)); ++ wreg(cpu->hvf->fd, HV_X86_RCX, RCX(env)); ++ wreg(cpu->hvf->fd, HV_X86_RDX, RDX(env)); ++ wreg(cpu->hvf->fd, HV_X86_RSI, RSI(env)); ++ wreg(cpu->hvf->fd, HV_X86_RDI, RDI(env)); ++ wreg(cpu->hvf->fd, HV_X86_RBP, RBP(env)); ++ wreg(cpu->hvf->fd, HV_X86_RSP, RSP(env)); + for (i = 8; i < 16; i++) { +- wreg(cpu->hvf_fd, HV_X86_RAX + i, RRX(env, i)); ++ wreg(cpu->hvf->fd, HV_X86_RAX + i, RRX(env, i)); + } + + lflags_to_rflags(env); +- wreg(cpu->hvf_fd, HV_X86_RFLAGS, env->eflags); ++ wreg(cpu->hvf->fd, HV_X86_RFLAGS, env->eflags); + macvm_set_rip(cpu, env->eip); + } + +diff --git a/target/i386/hvf/x86_mmu.c b/target/i386/hvf/x86_mmu.c +index 78fff04684..e9ed0f5aa1 100644 +--- a/target/i386/hvf/x86_mmu.c ++++ b/target/i386/hvf/x86_mmu.c +@@ -127,7 +127,7 @@ static bool test_pt_entry(struct CPUState *cpu, struct gpt_translation *pt, + pt->err_code |= MMU_PAGE_PT; + } + +- uint32_t cr0 = rvmcs(cpu->hvf_fd, VMCS_GUEST_CR0); ++ uint32_t cr0 = rvmcs(cpu->hvf->fd, VMCS_GUEST_CR0); + /* check protection */ + if (cr0 & CR0_WP) { + if (pt->write_access && !pte_write_access(pte)) { +@@ -172,7 +172,7 @@ static bool walk_gpt(struct CPUState *cpu, target_ulong addr, int err_code, + { + int top_level, level; + bool is_large = false; +- target_ulong cr3 = rvmcs(cpu->hvf_fd, VMCS_GUEST_CR3); ++ target_ulong cr3 = rvmcs(cpu->hvf->fd, VMCS_GUEST_CR3); + uint64_t page_mask = pae ? PAE_PTE_PAGE_MASK : LEGACY_PTE_PAGE_MASK; + + memset(pt, 0, sizeof(*pt)); +diff --git a/target/i386/hvf/x86_task.c b/target/i386/hvf/x86_task.c +index d66dfd7669..422156128b 100644 +--- a/target/i386/hvf/x86_task.c ++++ b/target/i386/hvf/x86_task.c +@@ -62,7 +62,7 @@ static void load_state_from_tss32(CPUState *cpu, struct x86_tss_segment32 *tss) + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + +- wvmcs(cpu->hvf_fd, VMCS_GUEST_CR3, tss->cr3); ++ wvmcs(cpu->hvf->fd, VMCS_GUEST_CR3, tss->cr3); + + env->eip = tss->eip; + env->eflags = tss->eflags | 2; +@@ -111,11 +111,11 @@ static int task_switch_32(CPUState *cpu, x68_segment_selector tss_sel, x68_segme + + void vmx_handle_task_switch(CPUState *cpu, x68_segment_selector tss_sel, int reason, bool gate_valid, uint8_t gate, uint64_t gate_type) + { +- uint64_t rip = rreg(cpu->hvf_fd, HV_X86_RIP); ++ uint64_t rip = rreg(cpu->hvf->fd, HV_X86_RIP); + if (!gate_valid || (gate_type != VMCS_INTR_T_HWEXCEPTION && + gate_type != VMCS_INTR_T_HWINTR && + gate_type != VMCS_INTR_T_NMI)) { +- int ins_len = rvmcs(cpu->hvf_fd, VMCS_EXIT_INSTRUCTION_LENGTH); ++ int ins_len = rvmcs(cpu->hvf->fd, VMCS_EXIT_INSTRUCTION_LENGTH); + macvm_set_rip(cpu, rip + ins_len); + return; + } +@@ -174,12 +174,12 @@ void vmx_handle_task_switch(CPUState *cpu, x68_segment_selector tss_sel, int rea + //ret = task_switch_16(cpu, tss_sel, old_tss_sel, old_tss_base, &next_tss_desc); + VM_PANIC("task_switch_16"); + +- macvm_set_cr0(cpu->hvf_fd, rvmcs(cpu->hvf_fd, VMCS_GUEST_CR0) | CR0_TS); ++ macvm_set_cr0(cpu->hvf->fd, rvmcs(cpu->hvf->fd, VMCS_GUEST_CR0) | CR0_TS); + x86_segment_descriptor_to_vmx(cpu, tss_sel, &next_tss_desc, &vmx_seg); + vmx_write_segment_descriptor(cpu, &vmx_seg, R_TR); + + store_regs(cpu); + +- hv_vcpu_invalidate_tlb(cpu->hvf_fd); +- hv_vcpu_flush(cpu->hvf_fd); ++ hv_vcpu_invalidate_tlb(cpu->hvf->fd); ++ hv_vcpu_flush(cpu->hvf->fd); + } +diff --git a/target/i386/hvf/x86hvf.c b/target/i386/hvf/x86hvf.c +index 0d7533742e..3111c0be4c 100644 +--- a/target/i386/hvf/x86hvf.c ++++ b/target/i386/hvf/x86hvf.c +@@ -20,6 +20,9 @@ + #include "qemu/osdep.h" + + #include "qemu-common.h" ++#include "sysemu/hvf.h" ++#include "sysemu/hvf_int.h" ++#include "sysemu/hw_accel.h" + #include "x86hvf.h" + #include "vmx.h" + #include "vmcs.h" +@@ -32,8 +35,6 @@ + #include + #include + +-#include "hvf-accel-ops.h" +- + void hvf_set_segment(struct CPUState *cpu, struct vmx_segment *vmx_seg, + SegmentCache *qseg, bool is_tr) + { +@@ -81,7 +82,7 @@ void hvf_put_xsave(CPUState *cpu_state) + + x86_cpu_xsave_all_areas(X86_CPU(cpu_state), xsave); + +- if (hv_vcpu_write_fpstate(cpu_state->hvf_fd, (void*)xsave, 4096)) { ++ if (hv_vcpu_write_fpstate(cpu_state->hvf->fd, (void*)xsave, 4096)) { + abort(); + } + } +@@ -91,19 +92,19 @@ void hvf_put_segments(CPUState *cpu_state) + CPUX86State *env = &X86_CPU(cpu_state)->env; + struct vmx_segment seg; + +- wvmcs(cpu_state->hvf_fd, VMCS_GUEST_IDTR_LIMIT, env->idt.limit); +- wvmcs(cpu_state->hvf_fd, VMCS_GUEST_IDTR_BASE, env->idt.base); ++ wvmcs(cpu_state->hvf->fd, VMCS_GUEST_IDTR_LIMIT, env->idt.limit); ++ wvmcs(cpu_state->hvf->fd, VMCS_GUEST_IDTR_BASE, env->idt.base); + +- wvmcs(cpu_state->hvf_fd, VMCS_GUEST_GDTR_LIMIT, env->gdt.limit); +- wvmcs(cpu_state->hvf_fd, VMCS_GUEST_GDTR_BASE, env->gdt.base); ++ wvmcs(cpu_state->hvf->fd, VMCS_GUEST_GDTR_LIMIT, env->gdt.limit); ++ wvmcs(cpu_state->hvf->fd, VMCS_GUEST_GDTR_BASE, env->gdt.base); + +- /* wvmcs(cpu_state->hvf_fd, VMCS_GUEST_CR2, env->cr[2]); */ +- wvmcs(cpu_state->hvf_fd, VMCS_GUEST_CR3, env->cr[3]); ++ /* wvmcs(cpu_state->hvf->fd, VMCS_GUEST_CR2, env->cr[2]); */ ++ wvmcs(cpu_state->hvf->fd, VMCS_GUEST_CR3, env->cr[3]); + vmx_update_tpr(cpu_state); +- wvmcs(cpu_state->hvf_fd, VMCS_GUEST_IA32_EFER, env->efer); ++ wvmcs(cpu_state->hvf->fd, VMCS_GUEST_IA32_EFER, env->efer); + +- macvm_set_cr4(cpu_state->hvf_fd, env->cr[4]); +- macvm_set_cr0(cpu_state->hvf_fd, env->cr[0]); ++ macvm_set_cr4(cpu_state->hvf->fd, env->cr[4]); ++ macvm_set_cr0(cpu_state->hvf->fd, env->cr[0]); + + hvf_set_segment(cpu_state, &seg, &env->segs[R_CS], false); + vmx_write_segment_descriptor(cpu_state, &seg, R_CS); +@@ -129,31 +130,31 @@ void hvf_put_segments(CPUState *cpu_state) + hvf_set_segment(cpu_state, &seg, &env->ldt, false); + vmx_write_segment_descriptor(cpu_state, &seg, R_LDTR); + +- hv_vcpu_flush(cpu_state->hvf_fd); ++ hv_vcpu_flush(cpu_state->hvf->fd); + } + + void hvf_put_msrs(CPUState *cpu_state) + { + CPUX86State *env = &X86_CPU(cpu_state)->env; + +- hv_vcpu_write_msr(cpu_state->hvf_fd, MSR_IA32_SYSENTER_CS, ++ hv_vcpu_write_msr(cpu_state->hvf->fd, MSR_IA32_SYSENTER_CS, + env->sysenter_cs); +- hv_vcpu_write_msr(cpu_state->hvf_fd, MSR_IA32_SYSENTER_ESP, ++ hv_vcpu_write_msr(cpu_state->hvf->fd, MSR_IA32_SYSENTER_ESP, + env->sysenter_esp); +- hv_vcpu_write_msr(cpu_state->hvf_fd, MSR_IA32_SYSENTER_EIP, ++ hv_vcpu_write_msr(cpu_state->hvf->fd, MSR_IA32_SYSENTER_EIP, + env->sysenter_eip); + +- hv_vcpu_write_msr(cpu_state->hvf_fd, MSR_STAR, env->star); ++ hv_vcpu_write_msr(cpu_state->hvf->fd, MSR_STAR, env->star); + + #ifdef TARGET_X86_64 +- hv_vcpu_write_msr(cpu_state->hvf_fd, MSR_CSTAR, env->cstar); +- hv_vcpu_write_msr(cpu_state->hvf_fd, MSR_KERNELGSBASE, env->kernelgsbase); +- hv_vcpu_write_msr(cpu_state->hvf_fd, MSR_FMASK, env->fmask); +- hv_vcpu_write_msr(cpu_state->hvf_fd, MSR_LSTAR, env->lstar); ++ hv_vcpu_write_msr(cpu_state->hvf->fd, MSR_CSTAR, env->cstar); ++ hv_vcpu_write_msr(cpu_state->hvf->fd, MSR_KERNELGSBASE, env->kernelgsbase); ++ hv_vcpu_write_msr(cpu_state->hvf->fd, MSR_FMASK, env->fmask); ++ hv_vcpu_write_msr(cpu_state->hvf->fd, MSR_LSTAR, env->lstar); + #endif + +- hv_vcpu_write_msr(cpu_state->hvf_fd, MSR_GSBASE, env->segs[R_GS].base); +- hv_vcpu_write_msr(cpu_state->hvf_fd, MSR_FSBASE, env->segs[R_FS].base); ++ hv_vcpu_write_msr(cpu_state->hvf->fd, MSR_GSBASE, env->segs[R_GS].base); ++ hv_vcpu_write_msr(cpu_state->hvf->fd, MSR_FSBASE, env->segs[R_FS].base); + } + + +@@ -163,7 +164,7 @@ void hvf_get_xsave(CPUState *cpu_state) + + xsave = X86_CPU(cpu_state)->env.xsave_buf; + +- if (hv_vcpu_read_fpstate(cpu_state->hvf_fd, (void*)xsave, 4096)) { ++ if (hv_vcpu_read_fpstate(cpu_state->hvf->fd, (void*)xsave, 4096)) { + abort(); + } + +@@ -202,17 +203,17 @@ void hvf_get_segments(CPUState *cpu_state) + vmx_read_segment_descriptor(cpu_state, &seg, R_LDTR); + hvf_get_segment(&env->ldt, &seg); + +- env->idt.limit = rvmcs(cpu_state->hvf_fd, VMCS_GUEST_IDTR_LIMIT); +- env->idt.base = rvmcs(cpu_state->hvf_fd, VMCS_GUEST_IDTR_BASE); +- env->gdt.limit = rvmcs(cpu_state->hvf_fd, VMCS_GUEST_GDTR_LIMIT); +- env->gdt.base = rvmcs(cpu_state->hvf_fd, VMCS_GUEST_GDTR_BASE); ++ env->idt.limit = rvmcs(cpu_state->hvf->fd, VMCS_GUEST_IDTR_LIMIT); ++ env->idt.base = rvmcs(cpu_state->hvf->fd, VMCS_GUEST_IDTR_BASE); ++ env->gdt.limit = rvmcs(cpu_state->hvf->fd, VMCS_GUEST_GDTR_LIMIT); ++ env->gdt.base = rvmcs(cpu_state->hvf->fd, VMCS_GUEST_GDTR_BASE); + +- env->cr[0] = rvmcs(cpu_state->hvf_fd, VMCS_GUEST_CR0); ++ env->cr[0] = rvmcs(cpu_state->hvf->fd, VMCS_GUEST_CR0); + env->cr[2] = 0; +- env->cr[3] = rvmcs(cpu_state->hvf_fd, VMCS_GUEST_CR3); +- env->cr[4] = rvmcs(cpu_state->hvf_fd, VMCS_GUEST_CR4); ++ env->cr[3] = rvmcs(cpu_state->hvf->fd, VMCS_GUEST_CR3); ++ env->cr[4] = rvmcs(cpu_state->hvf->fd, VMCS_GUEST_CR4); + +- env->efer = rvmcs(cpu_state->hvf_fd, VMCS_GUEST_IA32_EFER); ++ env->efer = rvmcs(cpu_state->hvf->fd, VMCS_GUEST_IA32_EFER); + } + + void hvf_get_msrs(CPUState *cpu_state) +@@ -220,27 +221,27 @@ void hvf_get_msrs(CPUState *cpu_state) + CPUX86State *env = &X86_CPU(cpu_state)->env; + uint64_t tmp; + +- hv_vcpu_read_msr(cpu_state->hvf_fd, MSR_IA32_SYSENTER_CS, &tmp); ++ hv_vcpu_read_msr(cpu_state->hvf->fd, MSR_IA32_SYSENTER_CS, &tmp); + env->sysenter_cs = tmp; + +- hv_vcpu_read_msr(cpu_state->hvf_fd, MSR_IA32_SYSENTER_ESP, &tmp); ++ hv_vcpu_read_msr(cpu_state->hvf->fd, MSR_IA32_SYSENTER_ESP, &tmp); + env->sysenter_esp = tmp; + +- hv_vcpu_read_msr(cpu_state->hvf_fd, MSR_IA32_SYSENTER_EIP, &tmp); ++ hv_vcpu_read_msr(cpu_state->hvf->fd, MSR_IA32_SYSENTER_EIP, &tmp); + env->sysenter_eip = tmp; + +- hv_vcpu_read_msr(cpu_state->hvf_fd, MSR_STAR, &env->star); ++ hv_vcpu_read_msr(cpu_state->hvf->fd, MSR_STAR, &env->star); + + #ifdef TARGET_X86_64 +- hv_vcpu_read_msr(cpu_state->hvf_fd, MSR_CSTAR, &env->cstar); +- hv_vcpu_read_msr(cpu_state->hvf_fd, MSR_KERNELGSBASE, &env->kernelgsbase); +- hv_vcpu_read_msr(cpu_state->hvf_fd, MSR_FMASK, &env->fmask); +- hv_vcpu_read_msr(cpu_state->hvf_fd, MSR_LSTAR, &env->lstar); ++ hv_vcpu_read_msr(cpu_state->hvf->fd, MSR_CSTAR, &env->cstar); ++ hv_vcpu_read_msr(cpu_state->hvf->fd, MSR_KERNELGSBASE, &env->kernelgsbase); ++ hv_vcpu_read_msr(cpu_state->hvf->fd, MSR_FMASK, &env->fmask); ++ hv_vcpu_read_msr(cpu_state->hvf->fd, MSR_LSTAR, &env->lstar); + #endif + +- hv_vcpu_read_msr(cpu_state->hvf_fd, MSR_IA32_APICBASE, &tmp); ++ hv_vcpu_read_msr(cpu_state->hvf->fd, MSR_IA32_APICBASE, &tmp); + +- env->tsc = rdtscp() + rvmcs(cpu_state->hvf_fd, VMCS_TSC_OFFSET); ++ env->tsc = rdtscp() + rvmcs(cpu_state->hvf->fd, VMCS_TSC_OFFSET); + } + + int hvf_put_registers(CPUState *cpu_state) +@@ -248,26 +249,26 @@ int hvf_put_registers(CPUState *cpu_state) + X86CPU *x86cpu = X86_CPU(cpu_state); + CPUX86State *env = &x86cpu->env; + +- wreg(cpu_state->hvf_fd, HV_X86_RAX, env->regs[R_EAX]); +- wreg(cpu_state->hvf_fd, HV_X86_RBX, env->regs[R_EBX]); +- wreg(cpu_state->hvf_fd, HV_X86_RCX, env->regs[R_ECX]); +- wreg(cpu_state->hvf_fd, HV_X86_RDX, env->regs[R_EDX]); +- wreg(cpu_state->hvf_fd, HV_X86_RBP, env->regs[R_EBP]); +- wreg(cpu_state->hvf_fd, HV_X86_RSP, env->regs[R_ESP]); +- wreg(cpu_state->hvf_fd, HV_X86_RSI, env->regs[R_ESI]); +- wreg(cpu_state->hvf_fd, HV_X86_RDI, env->regs[R_EDI]); +- wreg(cpu_state->hvf_fd, HV_X86_R8, env->regs[8]); +- wreg(cpu_state->hvf_fd, HV_X86_R9, env->regs[9]); +- wreg(cpu_state->hvf_fd, HV_X86_R10, env->regs[10]); +- wreg(cpu_state->hvf_fd, HV_X86_R11, env->regs[11]); +- wreg(cpu_state->hvf_fd, HV_X86_R12, env->regs[12]); +- wreg(cpu_state->hvf_fd, HV_X86_R13, env->regs[13]); +- wreg(cpu_state->hvf_fd, HV_X86_R14, env->regs[14]); +- wreg(cpu_state->hvf_fd, HV_X86_R15, env->regs[15]); +- wreg(cpu_state->hvf_fd, HV_X86_RFLAGS, env->eflags); +- wreg(cpu_state->hvf_fd, HV_X86_RIP, env->eip); ++ wreg(cpu_state->hvf->fd, HV_X86_RAX, env->regs[R_EAX]); ++ wreg(cpu_state->hvf->fd, HV_X86_RBX, env->regs[R_EBX]); ++ wreg(cpu_state->hvf->fd, HV_X86_RCX, env->regs[R_ECX]); ++ wreg(cpu_state->hvf->fd, HV_X86_RDX, env->regs[R_EDX]); ++ wreg(cpu_state->hvf->fd, HV_X86_RBP, env->regs[R_EBP]); ++ wreg(cpu_state->hvf->fd, HV_X86_RSP, env->regs[R_ESP]); ++ wreg(cpu_state->hvf->fd, HV_X86_RSI, env->regs[R_ESI]); ++ wreg(cpu_state->hvf->fd, HV_X86_RDI, env->regs[R_EDI]); ++ wreg(cpu_state->hvf->fd, HV_X86_R8, env->regs[8]); ++ wreg(cpu_state->hvf->fd, HV_X86_R9, env->regs[9]); ++ wreg(cpu_state->hvf->fd, HV_X86_R10, env->regs[10]); ++ wreg(cpu_state->hvf->fd, HV_X86_R11, env->regs[11]); ++ wreg(cpu_state->hvf->fd, HV_X86_R12, env->regs[12]); ++ wreg(cpu_state->hvf->fd, HV_X86_R13, env->regs[13]); ++ wreg(cpu_state->hvf->fd, HV_X86_R14, env->regs[14]); ++ wreg(cpu_state->hvf->fd, HV_X86_R15, env->regs[15]); ++ wreg(cpu_state->hvf->fd, HV_X86_RFLAGS, env->eflags); ++ wreg(cpu_state->hvf->fd, HV_X86_RIP, env->eip); + +- wreg(cpu_state->hvf_fd, HV_X86_XCR0, env->xcr0); ++ wreg(cpu_state->hvf->fd, HV_X86_XCR0, env->xcr0); + + hvf_put_xsave(cpu_state); + +@@ -275,14 +276,14 @@ int hvf_put_registers(CPUState *cpu_state) + + hvf_put_msrs(cpu_state); + +- wreg(cpu_state->hvf_fd, HV_X86_DR0, env->dr[0]); +- wreg(cpu_state->hvf_fd, HV_X86_DR1, env->dr[1]); +- wreg(cpu_state->hvf_fd, HV_X86_DR2, env->dr[2]); +- wreg(cpu_state->hvf_fd, HV_X86_DR3, env->dr[3]); +- wreg(cpu_state->hvf_fd, HV_X86_DR4, env->dr[4]); +- wreg(cpu_state->hvf_fd, HV_X86_DR5, env->dr[5]); +- wreg(cpu_state->hvf_fd, HV_X86_DR6, env->dr[6]); +- wreg(cpu_state->hvf_fd, HV_X86_DR7, env->dr[7]); ++ wreg(cpu_state->hvf->fd, HV_X86_DR0, env->dr[0]); ++ wreg(cpu_state->hvf->fd, HV_X86_DR1, env->dr[1]); ++ wreg(cpu_state->hvf->fd, HV_X86_DR2, env->dr[2]); ++ wreg(cpu_state->hvf->fd, HV_X86_DR3, env->dr[3]); ++ wreg(cpu_state->hvf->fd, HV_X86_DR4, env->dr[4]); ++ wreg(cpu_state->hvf->fd, HV_X86_DR5, env->dr[5]); ++ wreg(cpu_state->hvf->fd, HV_X86_DR6, env->dr[6]); ++ wreg(cpu_state->hvf->fd, HV_X86_DR7, env->dr[7]); + + return 0; + } +@@ -292,40 +293,40 @@ int hvf_get_registers(CPUState *cpu_state) + X86CPU *x86cpu = X86_CPU(cpu_state); + CPUX86State *env = &x86cpu->env; + +- env->regs[R_EAX] = rreg(cpu_state->hvf_fd, HV_X86_RAX); +- env->regs[R_EBX] = rreg(cpu_state->hvf_fd, HV_X86_RBX); +- env->regs[R_ECX] = rreg(cpu_state->hvf_fd, HV_X86_RCX); +- env->regs[R_EDX] = rreg(cpu_state->hvf_fd, HV_X86_RDX); +- env->regs[R_EBP] = rreg(cpu_state->hvf_fd, HV_X86_RBP); +- env->regs[R_ESP] = rreg(cpu_state->hvf_fd, HV_X86_RSP); +- env->regs[R_ESI] = rreg(cpu_state->hvf_fd, HV_X86_RSI); +- env->regs[R_EDI] = rreg(cpu_state->hvf_fd, HV_X86_RDI); +- env->regs[8] = rreg(cpu_state->hvf_fd, HV_X86_R8); +- env->regs[9] = rreg(cpu_state->hvf_fd, HV_X86_R9); +- env->regs[10] = rreg(cpu_state->hvf_fd, HV_X86_R10); +- env->regs[11] = rreg(cpu_state->hvf_fd, HV_X86_R11); +- env->regs[12] = rreg(cpu_state->hvf_fd, HV_X86_R12); +- env->regs[13] = rreg(cpu_state->hvf_fd, HV_X86_R13); +- env->regs[14] = rreg(cpu_state->hvf_fd, HV_X86_R14); +- env->regs[15] = rreg(cpu_state->hvf_fd, HV_X86_R15); ++ env->regs[R_EAX] = rreg(cpu_state->hvf->fd, HV_X86_RAX); ++ env->regs[R_EBX] = rreg(cpu_state->hvf->fd, HV_X86_RBX); ++ env->regs[R_ECX] = rreg(cpu_state->hvf->fd, HV_X86_RCX); ++ env->regs[R_EDX] = rreg(cpu_state->hvf->fd, HV_X86_RDX); ++ env->regs[R_EBP] = rreg(cpu_state->hvf->fd, HV_X86_RBP); ++ env->regs[R_ESP] = rreg(cpu_state->hvf->fd, HV_X86_RSP); ++ env->regs[R_ESI] = rreg(cpu_state->hvf->fd, HV_X86_RSI); ++ env->regs[R_EDI] = rreg(cpu_state->hvf->fd, HV_X86_RDI); ++ env->regs[8] = rreg(cpu_state->hvf->fd, HV_X86_R8); ++ env->regs[9] = rreg(cpu_state->hvf->fd, HV_X86_R9); ++ env->regs[10] = rreg(cpu_state->hvf->fd, HV_X86_R10); ++ env->regs[11] = rreg(cpu_state->hvf->fd, HV_X86_R11); ++ env->regs[12] = rreg(cpu_state->hvf->fd, HV_X86_R12); ++ env->regs[13] = rreg(cpu_state->hvf->fd, HV_X86_R13); ++ env->regs[14] = rreg(cpu_state->hvf->fd, HV_X86_R14); ++ env->regs[15] = rreg(cpu_state->hvf->fd, HV_X86_R15); + +- env->eflags = rreg(cpu_state->hvf_fd, HV_X86_RFLAGS); +- env->eip = rreg(cpu_state->hvf_fd, HV_X86_RIP); ++ env->eflags = rreg(cpu_state->hvf->fd, HV_X86_RFLAGS); ++ env->eip = rreg(cpu_state->hvf->fd, HV_X86_RIP); + + hvf_get_xsave(cpu_state); +- env->xcr0 = rreg(cpu_state->hvf_fd, HV_X86_XCR0); ++ env->xcr0 = rreg(cpu_state->hvf->fd, HV_X86_XCR0); + + hvf_get_segments(cpu_state); + hvf_get_msrs(cpu_state); + +- env->dr[0] = rreg(cpu_state->hvf_fd, HV_X86_DR0); +- env->dr[1] = rreg(cpu_state->hvf_fd, HV_X86_DR1); +- env->dr[2] = rreg(cpu_state->hvf_fd, HV_X86_DR2); +- env->dr[3] = rreg(cpu_state->hvf_fd, HV_X86_DR3); +- env->dr[4] = rreg(cpu_state->hvf_fd, HV_X86_DR4); +- env->dr[5] = rreg(cpu_state->hvf_fd, HV_X86_DR5); +- env->dr[6] = rreg(cpu_state->hvf_fd, HV_X86_DR6); +- env->dr[7] = rreg(cpu_state->hvf_fd, HV_X86_DR7); ++ env->dr[0] = rreg(cpu_state->hvf->fd, HV_X86_DR0); ++ env->dr[1] = rreg(cpu_state->hvf->fd, HV_X86_DR1); ++ env->dr[2] = rreg(cpu_state->hvf->fd, HV_X86_DR2); ++ env->dr[3] = rreg(cpu_state->hvf->fd, HV_X86_DR3); ++ env->dr[4] = rreg(cpu_state->hvf->fd, HV_X86_DR4); ++ env->dr[5] = rreg(cpu_state->hvf->fd, HV_X86_DR5); ++ env->dr[6] = rreg(cpu_state->hvf->fd, HV_X86_DR6); ++ env->dr[7] = rreg(cpu_state->hvf->fd, HV_X86_DR7); + + x86_update_hflags(env); + return 0; +@@ -334,16 +335,16 @@ int hvf_get_registers(CPUState *cpu_state) + static void vmx_set_int_window_exiting(CPUState *cpu) + { + uint64_t val; +- val = rvmcs(cpu->hvf_fd, VMCS_PRI_PROC_BASED_CTLS); +- wvmcs(cpu->hvf_fd, VMCS_PRI_PROC_BASED_CTLS, val | ++ val = rvmcs(cpu->hvf->fd, VMCS_PRI_PROC_BASED_CTLS); ++ wvmcs(cpu->hvf->fd, VMCS_PRI_PROC_BASED_CTLS, val | + VMCS_PRI_PROC_BASED_CTLS_INT_WINDOW_EXITING); + } + + void vmx_clear_int_window_exiting(CPUState *cpu) + { + uint64_t val; +- val = rvmcs(cpu->hvf_fd, VMCS_PRI_PROC_BASED_CTLS); +- wvmcs(cpu->hvf_fd, VMCS_PRI_PROC_BASED_CTLS, val & ++ val = rvmcs(cpu->hvf->fd, VMCS_PRI_PROC_BASED_CTLS); ++ wvmcs(cpu->hvf->fd, VMCS_PRI_PROC_BASED_CTLS, val & + ~VMCS_PRI_PROC_BASED_CTLS_INT_WINDOW_EXITING); + } + +@@ -379,7 +380,7 @@ bool hvf_inject_interrupts(CPUState *cpu_state) + uint64_t info = 0; + if (have_event) { + info = vector | intr_type | VMCS_INTR_VALID; +- uint64_t reason = rvmcs(cpu_state->hvf_fd, VMCS_EXIT_REASON); ++ uint64_t reason = rvmcs(cpu_state->hvf->fd, VMCS_EXIT_REASON); + if (env->nmi_injected && reason != EXIT_REASON_TASK_SWITCH) { + vmx_clear_nmi_blocking(cpu_state); + } +@@ -388,17 +389,17 @@ bool hvf_inject_interrupts(CPUState *cpu_state) + info &= ~(1 << 12); /* clear undefined bit */ + if (intr_type == VMCS_INTR_T_SWINTR || + intr_type == VMCS_INTR_T_SWEXCEPTION) { +- wvmcs(cpu_state->hvf_fd, VMCS_ENTRY_INST_LENGTH, env->ins_len); ++ wvmcs(cpu_state->hvf->fd, VMCS_ENTRY_INST_LENGTH, env->ins_len); + } + + if (env->has_error_code) { +- wvmcs(cpu_state->hvf_fd, VMCS_ENTRY_EXCEPTION_ERROR, ++ wvmcs(cpu_state->hvf->fd, VMCS_ENTRY_EXCEPTION_ERROR, + env->error_code); + /* Indicate that VMCS_ENTRY_EXCEPTION_ERROR is valid */ + info |= VMCS_INTR_DEL_ERRCODE; + } + /*printf("reinject %lx err %d\n", info, err);*/ +- wvmcs(cpu_state->hvf_fd, VMCS_ENTRY_INTR_INFO, info); ++ wvmcs(cpu_state->hvf->fd, VMCS_ENTRY_INTR_INFO, info); + }; + } + +@@ -406,7 +407,7 @@ bool hvf_inject_interrupts(CPUState *cpu_state) + if (!(env->hflags2 & HF2_NMI_MASK) && !(info & VMCS_INTR_VALID)) { + cpu_state->interrupt_request &= ~CPU_INTERRUPT_NMI; + info = VMCS_INTR_VALID | VMCS_INTR_T_NMI | EXCP02_NMI; +- wvmcs(cpu_state->hvf_fd, VMCS_ENTRY_INTR_INFO, info); ++ wvmcs(cpu_state->hvf->fd, VMCS_ENTRY_INTR_INFO, info); + } else { + vmx_set_nmi_window_exiting(cpu_state); + } +@@ -418,7 +419,7 @@ bool hvf_inject_interrupts(CPUState *cpu_state) + int line = cpu_get_pic_interrupt(&x86cpu->env); + cpu_state->interrupt_request &= ~CPU_INTERRUPT_HARD; + if (line >= 0) { +- wvmcs(cpu_state->hvf_fd, VMCS_ENTRY_INTR_INFO, line | ++ wvmcs(cpu_state->hvf->fd, VMCS_ENTRY_INTR_INFO, line | + VMCS_INTR_VALID | VMCS_INTR_T_HWINTR); + } + } +@@ -434,10 +435,13 @@ int hvf_process_events(CPUState *cpu_state) + X86CPU *cpu = X86_CPU(cpu_state); + CPUX86State *env = &cpu->env; + +- env->eflags = rreg(cpu_state->hvf_fd, HV_X86_RFLAGS); ++ if (!cpu_state->vcpu_dirty) { ++ /* light weight sync for CPU_INTERRUPT_HARD and IF_MASK */ ++ env->eflags = rreg(cpu_state->hvf->fd, HV_X86_RFLAGS); ++ } + + if (cpu_state->interrupt_request & CPU_INTERRUPT_INIT) { +- hvf_cpu_synchronize_state(cpu_state); ++ cpu_synchronize_state(cpu_state); + do_cpu_init(cpu); + } + +@@ -451,12 +455,12 @@ int hvf_process_events(CPUState *cpu_state) + cpu_state->halted = 0; + } + if (cpu_state->interrupt_request & CPU_INTERRUPT_SIPI) { +- hvf_cpu_synchronize_state(cpu_state); ++ cpu_synchronize_state(cpu_state); + do_cpu_sipi(cpu); + } + if (cpu_state->interrupt_request & CPU_INTERRUPT_TPR) { + cpu_state->interrupt_request &= ~CPU_INTERRUPT_TPR; +- hvf_cpu_synchronize_state(cpu_state); ++ cpu_synchronize_state(cpu_state); + apic_handle_tpr_access_report(cpu->apic_state, env->eip, + env->tpr_access_type); + } +diff --git a/target/i386/hvf/x86hvf.h b/target/i386/hvf/x86hvf.h +index 635ab0f34e..99ed8d608d 100644 +--- a/target/i386/hvf/x86hvf.h ++++ b/target/i386/hvf/x86hvf.h +@@ -21,8 +21,6 @@ + #include "x86_descr.h" + + int hvf_process_events(CPUState *); +-int hvf_put_registers(CPUState *); +-int hvf_get_registers(CPUState *); + bool hvf_inject_interrupts(CPUState *); + void hvf_set_segment(struct CPUState *cpu, struct vmx_segment *vmx_seg, + SegmentCache *qseg, bool is_tr); +diff --git a/ui/cocoa.m b/ui/cocoa.m +deleted file mode 100644 +index 37e1fb52eb..0000000000 +--- a/ui/cocoa.m ++++ /dev/null +@@ -1,1905 +0,0 @@ +-/* +- * QEMU Cocoa CG display driver +- * +- * Copyright (c) 2008 Mike Kronenberg +- * +- * 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 "qemu/osdep.h" +- +-#import +-#include +- +-#include "qemu-common.h" +-#include "ui/console.h" +-#include "ui/input.h" +-#include "ui/kbd-state.h" +-#include "sysemu/sysemu.h" +-#include "sysemu/runstate.h" +-#include "sysemu/cpu-throttle.h" +-#include "qapi/error.h" +-#include "qapi/qapi-commands-block.h" +-#include "qapi/qapi-commands-machine.h" +-#include "qapi/qapi-commands-misc.h" +-#include "sysemu/blockdev.h" +-#include "qemu-version.h" +-#include "qemu/cutils.h" +-#include "qemu/main-loop.h" +-#include "qemu/module.h" +-#include +-#include "hw/core/cpu.h" +- +-#ifndef MAC_OS_X_VERSION_10_13 +-#define MAC_OS_X_VERSION_10_13 101300 +-#endif +- +-/* 10.14 deprecates NSOnState and NSOffState in favor of +- * NSControlStateValueOn/Off, which were introduced in 10.13. +- * Define for older versions +- */ +-#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13 +-#define NSControlStateValueOn NSOnState +-#define NSControlStateValueOff NSOffState +-#endif +- +-//#define DEBUG +- +-#ifdef DEBUG +-#define COCOA_DEBUG(...) { (void) fprintf (stdout, __VA_ARGS__); } +-#else +-#define COCOA_DEBUG(...) ((void) 0) +-#endif +- +-#define cgrect(nsrect) (*(CGRect *)&(nsrect)) +- +-typedef struct { +- int width; +- int height; +-} QEMUScreen; +- +-static void cocoa_update(DisplayChangeListener *dcl, +- int x, int y, int w, int h); +- +-static void cocoa_switch(DisplayChangeListener *dcl, +- DisplaySurface *surface); +- +-static void cocoa_refresh(DisplayChangeListener *dcl); +- +-static NSWindow *normalWindow, *about_window; +-static const DisplayChangeListenerOps dcl_ops = { +- .dpy_name = "cocoa", +- .dpy_gfx_update = cocoa_update, +- .dpy_gfx_switch = cocoa_switch, +- .dpy_refresh = cocoa_refresh, +-}; +-static DisplayChangeListener dcl = { +- .ops = &dcl_ops, +-}; +-static int last_buttons; +-static int cursor_hide = 1; +- +-static int gArgc; +-static char **gArgv; +-static bool stretch_video; +-static NSTextField *pauseLabel; +-static NSArray * supportedImageFileTypes; +- +-static QemuSemaphore display_init_sem; +-static QemuSemaphore app_started_sem; +-static bool allow_events; +- +-// Utility functions to run specified code block with iothread lock held +-typedef void (^CodeBlock)(void); +-typedef bool (^BoolCodeBlock)(void); +- +-static void with_iothread_lock(CodeBlock block) +-{ +- bool locked = qemu_mutex_iothread_locked(); +- if (!locked) { +- qemu_mutex_lock_iothread(); +- } +- block(); +- if (!locked) { +- qemu_mutex_unlock_iothread(); +- } +-} +- +-static bool bool_with_iothread_lock(BoolCodeBlock block) +-{ +- bool locked = qemu_mutex_iothread_locked(); +- bool val; +- +- if (!locked) { +- qemu_mutex_lock_iothread(); +- } +- val = block(); +- if (!locked) { +- qemu_mutex_unlock_iothread(); +- } +- return val; +-} +- +-// Mac to QKeyCode conversion +-static const int mac_to_qkeycode_map[] = { +- [kVK_ANSI_A] = Q_KEY_CODE_A, +- [kVK_ANSI_B] = Q_KEY_CODE_B, +- [kVK_ANSI_C] = Q_KEY_CODE_C, +- [kVK_ANSI_D] = Q_KEY_CODE_D, +- [kVK_ANSI_E] = Q_KEY_CODE_E, +- [kVK_ANSI_F] = Q_KEY_CODE_F, +- [kVK_ANSI_G] = Q_KEY_CODE_G, +- [kVK_ANSI_H] = Q_KEY_CODE_H, +- [kVK_ANSI_I] = Q_KEY_CODE_I, +- [kVK_ANSI_J] = Q_KEY_CODE_J, +- [kVK_ANSI_K] = Q_KEY_CODE_K, +- [kVK_ANSI_L] = Q_KEY_CODE_L, +- [kVK_ANSI_M] = Q_KEY_CODE_M, +- [kVK_ANSI_N] = Q_KEY_CODE_N, +- [kVK_ANSI_O] = Q_KEY_CODE_O, +- [kVK_ANSI_P] = Q_KEY_CODE_P, +- [kVK_ANSI_Q] = Q_KEY_CODE_Q, +- [kVK_ANSI_R] = Q_KEY_CODE_R, +- [kVK_ANSI_S] = Q_KEY_CODE_S, +- [kVK_ANSI_T] = Q_KEY_CODE_T, +- [kVK_ANSI_U] = Q_KEY_CODE_U, +- [kVK_ANSI_V] = Q_KEY_CODE_V, +- [kVK_ANSI_W] = Q_KEY_CODE_W, +- [kVK_ANSI_X] = Q_KEY_CODE_X, +- [kVK_ANSI_Y] = Q_KEY_CODE_Y, +- [kVK_ANSI_Z] = Q_KEY_CODE_Z, +- +- [kVK_ANSI_0] = Q_KEY_CODE_0, +- [kVK_ANSI_1] = Q_KEY_CODE_1, +- [kVK_ANSI_2] = Q_KEY_CODE_2, +- [kVK_ANSI_3] = Q_KEY_CODE_3, +- [kVK_ANSI_4] = Q_KEY_CODE_4, +- [kVK_ANSI_5] = Q_KEY_CODE_5, +- [kVK_ANSI_6] = Q_KEY_CODE_6, +- [kVK_ANSI_7] = Q_KEY_CODE_7, +- [kVK_ANSI_8] = Q_KEY_CODE_8, +- [kVK_ANSI_9] = Q_KEY_CODE_9, +- +- [kVK_ANSI_Grave] = Q_KEY_CODE_GRAVE_ACCENT, +- [kVK_ANSI_Minus] = Q_KEY_CODE_MINUS, +- [kVK_ANSI_Equal] = Q_KEY_CODE_EQUAL, +- [kVK_Delete] = Q_KEY_CODE_BACKSPACE, +- [kVK_CapsLock] = Q_KEY_CODE_CAPS_LOCK, +- [kVK_Tab] = Q_KEY_CODE_TAB, +- [kVK_Return] = Q_KEY_CODE_RET, +- [kVK_ANSI_LeftBracket] = Q_KEY_CODE_BRACKET_LEFT, +- [kVK_ANSI_RightBracket] = Q_KEY_CODE_BRACKET_RIGHT, +- [kVK_ANSI_Backslash] = Q_KEY_CODE_BACKSLASH, +- [kVK_ANSI_Semicolon] = Q_KEY_CODE_SEMICOLON, +- [kVK_ANSI_Quote] = Q_KEY_CODE_APOSTROPHE, +- [kVK_ANSI_Comma] = Q_KEY_CODE_COMMA, +- [kVK_ANSI_Period] = Q_KEY_CODE_DOT, +- [kVK_ANSI_Slash] = Q_KEY_CODE_SLASH, +- [kVK_Space] = Q_KEY_CODE_SPC, +- +- [kVK_ANSI_Keypad0] = Q_KEY_CODE_KP_0, +- [kVK_ANSI_Keypad1] = Q_KEY_CODE_KP_1, +- [kVK_ANSI_Keypad2] = Q_KEY_CODE_KP_2, +- [kVK_ANSI_Keypad3] = Q_KEY_CODE_KP_3, +- [kVK_ANSI_Keypad4] = Q_KEY_CODE_KP_4, +- [kVK_ANSI_Keypad5] = Q_KEY_CODE_KP_5, +- [kVK_ANSI_Keypad6] = Q_KEY_CODE_KP_6, +- [kVK_ANSI_Keypad7] = Q_KEY_CODE_KP_7, +- [kVK_ANSI_Keypad8] = Q_KEY_CODE_KP_8, +- [kVK_ANSI_Keypad9] = Q_KEY_CODE_KP_9, +- [kVK_ANSI_KeypadDecimal] = Q_KEY_CODE_KP_DECIMAL, +- [kVK_ANSI_KeypadEnter] = Q_KEY_CODE_KP_ENTER, +- [kVK_ANSI_KeypadPlus] = Q_KEY_CODE_KP_ADD, +- [kVK_ANSI_KeypadMinus] = Q_KEY_CODE_KP_SUBTRACT, +- [kVK_ANSI_KeypadMultiply] = Q_KEY_CODE_KP_MULTIPLY, +- [kVK_ANSI_KeypadDivide] = Q_KEY_CODE_KP_DIVIDE, +- [kVK_ANSI_KeypadEquals] = Q_KEY_CODE_KP_EQUALS, +- [kVK_ANSI_KeypadClear] = Q_KEY_CODE_NUM_LOCK, +- +- [kVK_UpArrow] = Q_KEY_CODE_UP, +- [kVK_DownArrow] = Q_KEY_CODE_DOWN, +- [kVK_LeftArrow] = Q_KEY_CODE_LEFT, +- [kVK_RightArrow] = Q_KEY_CODE_RIGHT, +- +- [kVK_Help] = Q_KEY_CODE_INSERT, +- [kVK_Home] = Q_KEY_CODE_HOME, +- [kVK_PageUp] = Q_KEY_CODE_PGUP, +- [kVK_PageDown] = Q_KEY_CODE_PGDN, +- [kVK_End] = Q_KEY_CODE_END, +- [kVK_ForwardDelete] = Q_KEY_CODE_DELETE, +- +- [kVK_Escape] = Q_KEY_CODE_ESC, +- +- /* The Power key can't be used directly because the operating system uses +- * it. This key can be emulated by using it in place of another key such as +- * F1. Don't forget to disable the real key binding. +- */ +- /* [kVK_F1] = Q_KEY_CODE_POWER, */ +- +- [kVK_F1] = Q_KEY_CODE_F1, +- [kVK_F2] = Q_KEY_CODE_F2, +- [kVK_F3] = Q_KEY_CODE_F3, +- [kVK_F4] = Q_KEY_CODE_F4, +- [kVK_F5] = Q_KEY_CODE_F5, +- [kVK_F6] = Q_KEY_CODE_F6, +- [kVK_F7] = Q_KEY_CODE_F7, +- [kVK_F8] = Q_KEY_CODE_F8, +- [kVK_F9] = Q_KEY_CODE_F9, +- [kVK_F10] = Q_KEY_CODE_F10, +- [kVK_F11] = Q_KEY_CODE_F11, +- [kVK_F12] = Q_KEY_CODE_F12, +- [kVK_F13] = Q_KEY_CODE_PRINT, +- [kVK_F14] = Q_KEY_CODE_SCROLL_LOCK, +- [kVK_F15] = Q_KEY_CODE_PAUSE, +- +- // JIS keyboards only +- [kVK_JIS_Yen] = Q_KEY_CODE_YEN, +- [kVK_JIS_Underscore] = Q_KEY_CODE_RO, +- [kVK_JIS_KeypadComma] = Q_KEY_CODE_KP_COMMA, +- [kVK_JIS_Eisu] = Q_KEY_CODE_MUHENKAN, +- [kVK_JIS_Kana] = Q_KEY_CODE_HENKAN, +- +- /* +- * The eject and volume keys can't be used here because they are handled at +- * a lower level than what an Application can see. +- */ +-}; +- +-static int cocoa_keycode_to_qemu(int keycode) +-{ +- if (ARRAY_SIZE(mac_to_qkeycode_map) <= keycode) { +- error_report("(cocoa) warning unknown keycode 0x%x", keycode); +- return 0; +- } +- return mac_to_qkeycode_map[keycode]; +-} +- +-/* Displays an alert dialog box with the specified message */ +-static void QEMU_Alert(NSString *message) +-{ +- NSAlert *alert; +- alert = [NSAlert new]; +- [alert setMessageText: message]; +- [alert runModal]; +-} +- +-/* Handles any errors that happen with a device transaction */ +-static void handleAnyDeviceErrors(Error * err) +-{ +- if (err) { +- QEMU_Alert([NSString stringWithCString: error_get_pretty(err) +- encoding: NSASCIIStringEncoding]); +- error_free(err); +- } +-} +- +-/* +- ------------------------------------------------------ +- QemuCocoaView +- ------------------------------------------------------ +-*/ +-@interface QemuCocoaView : NSView +-{ +- QEMUScreen screen; +- NSWindow *fullScreenWindow; +- float cx,cy,cw,ch,cdx,cdy; +- pixman_image_t *pixman_image; +- QKbdState *kbd; +- BOOL isMouseGrabbed; +- BOOL isFullscreen; +- BOOL isAbsoluteEnabled; +-} +-- (void) switchSurface:(pixman_image_t *)image; +-- (void) grabMouse; +-- (void) ungrabMouse; +-- (void) toggleFullScreen:(id)sender; +-- (void) handleMonitorInput:(NSEvent *)event; +-- (bool) handleEvent:(NSEvent *)event; +-- (bool) handleEventLocked:(NSEvent *)event; +-- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled; +-/* The state surrounding mouse grabbing is potentially confusing. +- * isAbsoluteEnabled tracks qemu_input_is_absolute() [ie "is the emulated +- * pointing device an absolute-position one?"], but is only updated on +- * next refresh. +- * isMouseGrabbed tracks whether GUI events are directed to the guest; +- * it controls whether special keys like Cmd get sent to the guest, +- * and whether we capture the mouse when in non-absolute mode. +- */ +-- (BOOL) isMouseGrabbed; +-- (BOOL) isAbsoluteEnabled; +-- (float) cdx; +-- (float) cdy; +-- (QEMUScreen) gscreen; +-- (void) raiseAllKeys; +-@end +- +-QemuCocoaView *cocoaView; +- +-@implementation QemuCocoaView +-- (id)initWithFrame:(NSRect)frameRect +-{ +- COCOA_DEBUG("QemuCocoaView: initWithFrame\n"); +- +- self = [super initWithFrame:frameRect]; +- if (self) { +- +- screen.width = frameRect.size.width; +- screen.height = frameRect.size.height; +- kbd = qkbd_state_init(dcl.con); +- +- } +- return self; +-} +- +-- (void) dealloc +-{ +- COCOA_DEBUG("QemuCocoaView: dealloc\n"); +- +- if (pixman_image) { +- pixman_image_unref(pixman_image); +- } +- +- qkbd_state_free(kbd); +- [super dealloc]; +-} +- +-- (BOOL) isOpaque +-{ +- return YES; +-} +- +-- (BOOL) screenContainsPoint:(NSPoint) p +-{ +- return (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height); +-} +- +-/* Get location of event and convert to virtual screen coordinate */ +-- (CGPoint) screenLocationOfEvent:(NSEvent *)ev +-{ +- NSWindow *eventWindow = [ev window]; +- // XXX: Use CGRect and -convertRectFromScreen: to support macOS 10.10 +- CGRect r = CGRectZero; +- r.origin = [ev locationInWindow]; +- if (!eventWindow) { +- if (!isFullscreen) { +- return [[self window] convertRectFromScreen:r].origin; +- } else { +- CGPoint locationInSelfWindow = [[self window] convertRectFromScreen:r].origin; +- CGPoint loc = [self convertPoint:locationInSelfWindow fromView:nil]; +- if (stretch_video) { +- loc.x /= cdx; +- loc.y /= cdy; +- } +- return loc; +- } +- } else if ([[self window] isEqual:eventWindow]) { +- if (!isFullscreen) { +- return r.origin; +- } else { +- CGPoint loc = [self convertPoint:r.origin fromView:nil]; +- if (stretch_video) { +- loc.x /= cdx; +- loc.y /= cdy; +- } +- return loc; +- } +- } else { +- return [[self window] convertRectFromScreen:[eventWindow convertRectToScreen:r]].origin; +- } +-} +- +-- (void) hideCursor +-{ +- if (!cursor_hide) { +- return; +- } +- [NSCursor hide]; +-} +- +-- (void) unhideCursor +-{ +- if (!cursor_hide) { +- return; +- } +- [NSCursor unhide]; +-} +- +-- (void) drawRect:(NSRect) rect +-{ +- COCOA_DEBUG("QemuCocoaView: drawRect\n"); +- +- // get CoreGraphic context +- CGContextRef viewContextRef = [[NSGraphicsContext currentContext] CGContext]; +- +- CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone); +- CGContextSetShouldAntialias (viewContextRef, NO); +- +- // draw screen bitmap directly to Core Graphics context +- if (!pixman_image) { +- // Draw request before any guest device has set up a framebuffer: +- // just draw an opaque black rectangle +- CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0); +- CGContextFillRect(viewContextRef, NSRectToCGRect(rect)); +- } else { +- int w = pixman_image_get_width(pixman_image); +- int h = pixman_image_get_height(pixman_image); +- int bitsPerPixel = PIXMAN_FORMAT_BPP(pixman_image_get_format(pixman_image)); +- int stride = pixman_image_get_stride(pixman_image); +- CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData( +- NULL, +- pixman_image_get_data(pixman_image), +- stride * h, +- NULL +- ); +- CGImageRef imageRef = CGImageCreate( +- w, //width +- h, //height +- DIV_ROUND_UP(bitsPerPixel, 8) * 2, //bitsPerComponent +- bitsPerPixel, //bitsPerPixel +- stride, //bytesPerRow +- CGColorSpaceCreateWithName(kCGColorSpaceSRGB), //colorspace +- kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, //bitmapInfo +- dataProviderRef, //provider +- NULL, //decode +- 0, //interpolate +- kCGRenderingIntentDefault //intent +- ); +- // selective drawing code (draws only dirty rectangles) (OS X >= 10.4) +- const NSRect *rectList; +- NSInteger rectCount; +- int i; +- CGImageRef clipImageRef; +- CGRect clipRect; +- +- [self getRectsBeingDrawn:&rectList count:&rectCount]; +- for (i = 0; i < rectCount; i++) { +- clipRect.origin.x = rectList[i].origin.x / cdx; +- clipRect.origin.y = (float)h - (rectList[i].origin.y + rectList[i].size.height) / cdy; +- clipRect.size.width = rectList[i].size.width / cdx; +- clipRect.size.height = rectList[i].size.height / cdy; +- clipImageRef = CGImageCreateWithImageInRect( +- imageRef, +- clipRect +- ); +- CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef); +- CGImageRelease (clipImageRef); +- } +- CGImageRelease (imageRef); +- CGDataProviderRelease(dataProviderRef); +- } +-} +- +-- (void) setContentDimensions +-{ +- COCOA_DEBUG("QemuCocoaView: setContentDimensions\n"); +- +- if (isFullscreen) { +- cdx = [[NSScreen mainScreen] frame].size.width / (float)screen.width; +- cdy = [[NSScreen mainScreen] frame].size.height / (float)screen.height; +- +- /* stretches video, but keeps same aspect ratio */ +- if (stretch_video == true) { +- /* use smallest stretch value - prevents clipping on sides */ +- if (MIN(cdx, cdy) == cdx) { +- cdy = cdx; +- } else { +- cdx = cdy; +- } +- } else { /* No stretching */ +- cdx = cdy = 1; +- } +- cw = screen.width * cdx; +- ch = screen.height * cdy; +- cx = ([[NSScreen mainScreen] frame].size.width - cw) / 2.0; +- cy = ([[NSScreen mainScreen] frame].size.height - ch) / 2.0; +- } else { +- cx = 0; +- cy = 0; +- cw = screen.width; +- ch = screen.height; +- cdx = 1.0; +- cdy = 1.0; +- } +-} +- +-- (void) switchSurface:(pixman_image_t *)image +-{ +- COCOA_DEBUG("QemuCocoaView: switchSurface\n"); +- +- int w = pixman_image_get_width(image); +- int h = pixman_image_get_height(image); +- /* cdx == 0 means this is our very first surface, in which case we need +- * to recalculate the content dimensions even if it happens to be the size +- * of the initial empty window. +- */ +- bool isResize = (w != screen.width || h != screen.height || cdx == 0.0); +- +- int oldh = screen.height; +- if (isResize) { +- // Resize before we trigger the redraw, or we'll redraw at the wrong size +- COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h); +- screen.width = w; +- screen.height = h; +- [self setContentDimensions]; +- [self setFrame:NSMakeRect(cx, cy, cw, ch)]; +- } +- +- // update screenBuffer +- if (pixman_image) { +- pixman_image_unref(pixman_image); +- } +- +- pixman_image = image; +- +- // update windows +- if (isFullscreen) { +- [[fullScreenWindow contentView] setFrame:[[NSScreen mainScreen] frame]]; +- [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:NO animate:NO]; +- } else { +- if (qemu_name) +- [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; +- [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:YES animate:NO]; +- } +- +- if (isResize) { +- [normalWindow center]; +- } +-} +- +-- (void) toggleFullScreen:(id)sender +-{ +- COCOA_DEBUG("QemuCocoaView: toggleFullScreen\n"); +- +- if (isFullscreen) { // switch from fullscreen to desktop +- isFullscreen = FALSE; +- [self ungrabMouse]; +- [self setContentDimensions]; +- [fullScreenWindow close]; +- [normalWindow setContentView: self]; +- [normalWindow makeKeyAndOrderFront: self]; +- [NSMenu setMenuBarVisible:YES]; +- } else { // switch from desktop to fullscreen +- isFullscreen = TRUE; +- [normalWindow orderOut: nil]; /* Hide the window */ +- [self grabMouse]; +- [self setContentDimensions]; +- [NSMenu setMenuBarVisible:NO]; +- fullScreenWindow = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame] +- styleMask:NSWindowStyleMaskBorderless +- backing:NSBackingStoreBuffered +- defer:NO]; +- [fullScreenWindow setAcceptsMouseMovedEvents: YES]; +- [fullScreenWindow setHasShadow:NO]; +- [fullScreenWindow setBackgroundColor: [NSColor blackColor]]; +- [self setFrame:NSMakeRect(cx, cy, cw, ch)]; +- [[fullScreenWindow contentView] addSubview: self]; +- [fullScreenWindow makeKeyAndOrderFront:self]; +- } +-} +- +-- (void) toggleKey: (int)keycode { +- qkbd_state_key_event(kbd, keycode, !qkbd_state_key_get(kbd, keycode)); +-} +- +-// Does the work of sending input to the monitor +-- (void) handleMonitorInput:(NSEvent *)event +-{ +- int keysym = 0; +- int control_key = 0; +- +- // if the control key is down +- if ([event modifierFlags] & NSEventModifierFlagControl) { +- control_key = 1; +- } +- +- /* translates Macintosh keycodes to QEMU's keysym */ +- +- int without_control_translation[] = { +- [0 ... 0xff] = 0, // invalid key +- +- [kVK_UpArrow] = QEMU_KEY_UP, +- [kVK_DownArrow] = QEMU_KEY_DOWN, +- [kVK_RightArrow] = QEMU_KEY_RIGHT, +- [kVK_LeftArrow] = QEMU_KEY_LEFT, +- [kVK_Home] = QEMU_KEY_HOME, +- [kVK_End] = QEMU_KEY_END, +- [kVK_PageUp] = QEMU_KEY_PAGEUP, +- [kVK_PageDown] = QEMU_KEY_PAGEDOWN, +- [kVK_ForwardDelete] = QEMU_KEY_DELETE, +- [kVK_Delete] = QEMU_KEY_BACKSPACE, +- }; +- +- int with_control_translation[] = { +- [0 ... 0xff] = 0, // invalid key +- +- [kVK_UpArrow] = QEMU_KEY_CTRL_UP, +- [kVK_DownArrow] = QEMU_KEY_CTRL_DOWN, +- [kVK_RightArrow] = QEMU_KEY_CTRL_RIGHT, +- [kVK_LeftArrow] = QEMU_KEY_CTRL_LEFT, +- [kVK_Home] = QEMU_KEY_CTRL_HOME, +- [kVK_End] = QEMU_KEY_CTRL_END, +- [kVK_PageUp] = QEMU_KEY_CTRL_PAGEUP, +- [kVK_PageDown] = QEMU_KEY_CTRL_PAGEDOWN, +- }; +- +- if (control_key != 0) { /* If the control key is being used */ +- if ([event keyCode] < ARRAY_SIZE(with_control_translation)) { +- keysym = with_control_translation[[event keyCode]]; +- } +- } else { +- if ([event keyCode] < ARRAY_SIZE(without_control_translation)) { +- keysym = without_control_translation[[event keyCode]]; +- } +- } +- +- // if not a key that needs translating +- if (keysym == 0) { +- NSString *ks = [event characters]; +- if ([ks length] > 0) { +- keysym = [ks characterAtIndex:0]; +- } +- } +- +- if (keysym) { +- kbd_put_keysym(keysym); +- } +-} +- +-- (bool) handleEvent:(NSEvent *)event +-{ +- if(!allow_events) { +- /* +- * Just let OSX have all events that arrive before +- * applicationDidFinishLaunching. +- * This avoids a deadlock on the iothread lock, which cocoa_display_init() +- * will not drop until after the app_started_sem is posted. (In theory +- * there should not be any such events, but OSX Catalina now emits some.) +- */ +- return false; +- } +- return bool_with_iothread_lock(^{ +- return [self handleEventLocked:event]; +- }); +-} +- +-- (bool) handleEventLocked:(NSEvent *)event +-{ +- /* Return true if we handled the event, false if it should be given to OSX */ +- COCOA_DEBUG("QemuCocoaView: handleEvent\n"); +- int buttons = 0; +- int keycode = 0; +- bool mouse_event = false; +- static bool switched_to_fullscreen = false; +- // Location of event in virtual screen coordinates +- NSPoint p = [self screenLocationOfEvent:event]; +- NSUInteger modifiers = [event modifierFlags]; +- +- /* +- * Check -[NSEvent modifierFlags] here. +- * +- * There is a NSEventType for an event notifying the change of +- * -[NSEvent modifierFlags], NSEventTypeFlagsChanged but these operations +- * are performed for any events because a modifier state may change while +- * the application is inactive (i.e. no events fire) and we don't want to +- * wait for another modifier state change to detect such a change. +- * +- * NSEventModifierFlagCapsLock requires a special treatment. The other flags +- * are handled in similar manners. +- * +- * NSEventModifierFlagCapsLock +- * --------------------------- +- * +- * If CapsLock state is changed, "up" and "down" events will be fired in +- * sequence, effectively updates CapsLock state on the guest. +- * +- * The other flags +- * --------------- +- * +- * If a flag is not set, fire "up" events for all keys which correspond to +- * the flag. Note that "down" events are not fired here because the flags +- * checked here do not tell what exact keys are down. +- * +- * If one of the keys corresponding to a flag is down, we rely on +- * -[NSEvent keyCode] of an event whose -[NSEvent type] is +- * NSEventTypeFlagsChanged to know the exact key which is down, which has +- * the following two downsides: +- * - It does not work when the application is inactive as described above. +- * - It malfactions *after* the modifier state is changed while the +- * application is inactive. It is because -[NSEvent keyCode] does not tell +- * if the key is up or down, and requires to infer the current state from +- * the previous state. It is still possible to fix such a malfanction by +- * completely leaving your hands from the keyboard, which hopefully makes +- * this implementation usable enough. +- */ +- if (!!(modifiers & NSEventModifierFlagCapsLock) != +- qkbd_state_modifier_get(kbd, QKBD_MOD_CAPSLOCK)) { +- qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, true); +- qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, false); +- } +- +- if (!(modifiers & NSEventModifierFlagShift)) { +- qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT, false); +- qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT_R, false); +- } +- if (!(modifiers & NSEventModifierFlagControl)) { +- qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL, false); +- qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL_R, false); +- } +- if (!(modifiers & NSEventModifierFlagOption)) { +- qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false); +- qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false); +- } +- if (!(modifiers & NSEventModifierFlagCommand)) { +- qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false); +- qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false); +- } +- +- switch ([event type]) { +- case NSEventTypeFlagsChanged: +- switch ([event keyCode]) { +- case kVK_Shift: +- if (!!(modifiers & NSEventModifierFlagShift)) { +- [self toggleKey:Q_KEY_CODE_SHIFT]; +- } +- break; +- +- case kVK_RightShift: +- if (!!(modifiers & NSEventModifierFlagShift)) { +- [self toggleKey:Q_KEY_CODE_SHIFT_R]; +- } +- break; +- +- case kVK_Control: +- if (!!(modifiers & NSEventModifierFlagControl)) { +- [self toggleKey:Q_KEY_CODE_CTRL]; +- } +- break; +- +- case kVK_RightControl: +- if (!!(modifiers & NSEventModifierFlagControl)) { +- [self toggleKey:Q_KEY_CODE_CTRL_R]; +- } +- break; +- +- case kVK_Option: +- if (!!(modifiers & NSEventModifierFlagOption)) { +- [self toggleKey:Q_KEY_CODE_ALT]; +- } +- break; +- +- case kVK_RightOption: +- if (!!(modifiers & NSEventModifierFlagOption)) { +- [self toggleKey:Q_KEY_CODE_ALT_R]; +- } +- break; +- +- /* Don't pass command key changes to guest unless mouse is grabbed */ +- case kVK_Command: +- if (isMouseGrabbed && +- !!(modifiers & NSEventModifierFlagCommand)) { +- [self toggleKey:Q_KEY_CODE_META_L]; +- } +- break; +- +- case kVK_RightCommand: +- if (isMouseGrabbed && +- !!(modifiers & NSEventModifierFlagCommand)) { +- [self toggleKey:Q_KEY_CODE_META_R]; +- } +- break; +- } +- break; +- case NSEventTypeKeyDown: +- keycode = cocoa_keycode_to_qemu([event keyCode]); +- +- // forward command key combos to the host UI unless the mouse is grabbed +- if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) { +- /* +- * Prevent the command key from being stuck down in the guest +- * when using Command-F to switch to full screen mode. +- */ +- if (keycode == Q_KEY_CODE_F) { +- switched_to_fullscreen = true; +- } +- return false; +- } +- +- // default +- +- // handle control + alt Key Combos (ctrl+alt+[1..9,g] is reserved for QEMU) +- if (([event modifierFlags] & NSEventModifierFlagControl) && ([event modifierFlags] & NSEventModifierFlagOption)) { +- NSString *keychar = [event charactersIgnoringModifiers]; +- if ([keychar length] == 1) { +- char key = [keychar characterAtIndex:0]; +- switch (key) { +- +- // enable graphic console +- case '1' ... '9': +- console_select(key - '0' - 1); /* ascii math */ +- return true; +- +- // release the mouse grab +- case 'g': +- [self ungrabMouse]; +- return true; +- } +- } +- } +- +- if (qemu_console_is_graphic(NULL)) { +- qkbd_state_key_event(kbd, keycode, true); +- } else { +- [self handleMonitorInput: event]; +- } +- break; +- case NSEventTypeKeyUp: +- keycode = cocoa_keycode_to_qemu([event keyCode]); +- +- // don't pass the guest a spurious key-up if we treated this +- // command-key combo as a host UI action +- if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) { +- return true; +- } +- +- if (qemu_console_is_graphic(NULL)) { +- qkbd_state_key_event(kbd, keycode, false); +- } +- break; +- case NSEventTypeMouseMoved: +- if (isAbsoluteEnabled) { +- // Cursor re-entered into a window might generate events bound to screen coordinates +- // and `nil` window property, and in full screen mode, current window might not be +- // key window, where event location alone should suffice. +- if (![self screenContainsPoint:p] || !([[self window] isKeyWindow] || isFullscreen)) { +- if (isMouseGrabbed) { +- [self ungrabMouse]; +- } +- } else { +- if (!isMouseGrabbed) { +- [self grabMouse]; +- } +- } +- } +- mouse_event = true; +- break; +- case NSEventTypeLeftMouseDown: +- buttons |= MOUSE_EVENT_LBUTTON; +- mouse_event = true; +- break; +- case NSEventTypeRightMouseDown: +- buttons |= MOUSE_EVENT_RBUTTON; +- mouse_event = true; +- break; +- case NSEventTypeOtherMouseDown: +- buttons |= MOUSE_EVENT_MBUTTON; +- mouse_event = true; +- break; +- case NSEventTypeLeftMouseDragged: +- buttons |= MOUSE_EVENT_LBUTTON; +- mouse_event = true; +- break; +- case NSEventTypeRightMouseDragged: +- buttons |= MOUSE_EVENT_RBUTTON; +- mouse_event = true; +- break; +- case NSEventTypeOtherMouseDragged: +- buttons |= MOUSE_EVENT_MBUTTON; +- mouse_event = true; +- break; +- case NSEventTypeLeftMouseUp: +- mouse_event = true; +- if (!isMouseGrabbed && [self screenContainsPoint:p]) { +- /* +- * In fullscreen mode, the window of cocoaView may not be the +- * key window, therefore the position relative to the virtual +- * screen alone will be sufficient. +- */ +- if(isFullscreen || [[self window] isKeyWindow]) { +- [self grabMouse]; +- } +- } +- break; +- case NSEventTypeRightMouseUp: +- mouse_event = true; +- break; +- case NSEventTypeOtherMouseUp: +- mouse_event = true; +- break; +- case NSEventTypeScrollWheel: +- /* +- * Send wheel events to the guest regardless of window focus. +- * This is in-line with standard Mac OS X UI behaviour. +- */ +- +- /* +- * When deltaY is zero, it means that this scrolling event was +- * either horizontal, or so fine that it only appears in +- * scrollingDeltaY. So we drop the event. +- */ +- if ([event deltaY] != 0) { +- /* Determine if this is a scroll up or scroll down event */ +- buttons = ([event deltaY] > 0) ? +- INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN; +- qemu_input_queue_btn(dcl.con, buttons, true); +- qemu_input_event_sync(); +- qemu_input_queue_btn(dcl.con, buttons, false); +- qemu_input_event_sync(); +- } +- /* +- * Since deltaY also reports scroll wheel events we prevent mouse +- * movement code from executing. +- */ +- mouse_event = false; +- break; +- default: +- return false; +- } +- +- if (mouse_event) { +- /* Don't send button events to the guest unless we've got a +- * mouse grab or window focus. If we have neither then this event +- * is the user clicking on the background window to activate and +- * bring us to the front, which will be done by the sendEvent +- * call below. We definitely don't want to pass that click through +- * to the guest. +- */ +- if ((isMouseGrabbed || [[self window] isKeyWindow]) && +- (last_buttons != buttons)) { +- static uint32_t bmap[INPUT_BUTTON__MAX] = { +- [INPUT_BUTTON_LEFT] = MOUSE_EVENT_LBUTTON, +- [INPUT_BUTTON_MIDDLE] = MOUSE_EVENT_MBUTTON, +- [INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON +- }; +- qemu_input_update_buttons(dcl.con, bmap, last_buttons, buttons); +- last_buttons = buttons; +- } +- if (isMouseGrabbed) { +- if (isAbsoluteEnabled) { +- /* Note that the origin for Cocoa mouse coords is bottom left, not top left. +- * The check on screenContainsPoint is to avoid sending out of range values for +- * clicks in the titlebar. +- */ +- if ([self screenContainsPoint:p]) { +- qemu_input_queue_abs(dcl.con, INPUT_AXIS_X, p.x, 0, screen.width); +- qemu_input_queue_abs(dcl.con, INPUT_AXIS_Y, screen.height - p.y, 0, screen.height); +- } +- } else { +- qemu_input_queue_rel(dcl.con, INPUT_AXIS_X, (int)[event deltaX]); +- qemu_input_queue_rel(dcl.con, INPUT_AXIS_Y, (int)[event deltaY]); +- } +- } else { +- return false; +- } +- qemu_input_event_sync(); +- } +- return true; +-} +- +-- (void) grabMouse +-{ +- COCOA_DEBUG("QemuCocoaView: grabMouse\n"); +- +- if (!isFullscreen) { +- if (qemu_name) +- [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press ctrl + alt + g to release Mouse)", qemu_name]]; +- else +- [normalWindow setTitle:@"QEMU - (Press ctrl + alt + g to release Mouse)"]; +- } +- [self hideCursor]; +- CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled); +- isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:] +-} +- +-- (void) ungrabMouse +-{ +- COCOA_DEBUG("QemuCocoaView: ungrabMouse\n"); +- +- if (!isFullscreen) { +- if (qemu_name) +- [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; +- else +- [normalWindow setTitle:@"QEMU"]; +- } +- [self unhideCursor]; +- CGAssociateMouseAndMouseCursorPosition(TRUE); +- isMouseGrabbed = FALSE; +-} +- +-- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled { +- isAbsoluteEnabled = tIsAbsoluteEnabled; +- if (isMouseGrabbed) { +- CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled); +- } +-} +-- (BOOL) isMouseGrabbed {return isMouseGrabbed;} +-- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;} +-- (float) cdx {return cdx;} +-- (float) cdy {return cdy;} +-- (QEMUScreen) gscreen {return screen;} +- +-/* +- * Makes the target think all down keys are being released. +- * This prevents a stuck key problem, since we will not see +- * key up events for those keys after we have lost focus. +- */ +-- (void) raiseAllKeys +-{ +- with_iothread_lock(^{ +- qkbd_state_lift_all_keys(kbd); +- }); +-} +-@end +- +- +- +-/* +- ------------------------------------------------------ +- QemuCocoaAppController +- ------------------------------------------------------ +-*/ +-@interface QemuCocoaAppController : NSObject +- +-{ +-} +-- (void)doToggleFullScreen:(id)sender; +-- (void)toggleFullScreen:(id)sender; +-- (void)showQEMUDoc:(id)sender; +-- (void)zoomToFit:(id) sender; +-- (void)displayConsole:(id)sender; +-- (void)pauseQEMU:(id)sender; +-- (void)resumeQEMU:(id)sender; +-- (void)displayPause; +-- (void)removePause; +-- (void)restartQEMU:(id)sender; +-- (void)powerDownQEMU:(id)sender; +-- (void)ejectDeviceMedia:(id)sender; +-- (void)changeDeviceMedia:(id)sender; +-- (BOOL)verifyQuit; +-- (void)openDocumentation:(NSString *)filename; +-- (IBAction) do_about_menu_item: (id) sender; +-- (void)make_about_window; +-- (void)adjustSpeed:(id)sender; +-@end +- +-@implementation QemuCocoaAppController +-- (id) init +-{ +- COCOA_DEBUG("QemuCocoaAppController: init\n"); +- +- self = [super init]; +- if (self) { +- +- // create a view and add it to the window +- cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)]; +- if(!cocoaView) { +- error_report("(cocoa) can't create a view"); +- exit(1); +- } +- +- // create a window +- normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame] +- styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable +- backing:NSBackingStoreBuffered defer:NO]; +- if(!normalWindow) { +- error_report("(cocoa) can't create window"); +- exit(1); +- } +- [normalWindow setAcceptsMouseMovedEvents:YES]; +- [normalWindow setTitle:@"QEMU"]; +- [normalWindow setContentView:cocoaView]; +- [normalWindow makeKeyAndOrderFront:self]; +- [normalWindow center]; +- [normalWindow setDelegate: self]; +- stretch_video = false; +- +- /* Used for displaying pause on the screen */ +- pauseLabel = [NSTextField new]; +- [pauseLabel setBezeled:YES]; +- [pauseLabel setDrawsBackground:YES]; +- [pauseLabel setBackgroundColor: [NSColor whiteColor]]; +- [pauseLabel setEditable:NO]; +- [pauseLabel setSelectable:NO]; +- [pauseLabel setStringValue: @"Paused"]; +- [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]]; +- [pauseLabel setTextColor: [NSColor blackColor]]; +- [pauseLabel sizeToFit]; +- +- // set the supported image file types that can be opened +- supportedImageFileTypes = [NSArray arrayWithObjects: @"img", @"iso", @"dmg", +- @"qcow", @"qcow2", @"cloop", @"vmdk", @"cdr", +- @"toast", nil]; +- [self make_about_window]; +- } +- return self; +-} +- +-- (void) dealloc +-{ +- COCOA_DEBUG("QemuCocoaAppController: dealloc\n"); +- +- if (cocoaView) +- [cocoaView release]; +- [super dealloc]; +-} +- +-- (void)applicationDidFinishLaunching: (NSNotification *) note +-{ +- COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n"); +- allow_events = true; +- /* Tell cocoa_display_init to proceed */ +- qemu_sem_post(&app_started_sem); +-} +- +-- (void)applicationWillTerminate:(NSNotification *)aNotification +-{ +- COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n"); +- +- qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI); +- +- /* +- * Sleep here, because returning will cause OSX to kill us +- * immediately; the QEMU main loop will handle the shutdown +- * request and terminate the process. +- */ +- [NSThread sleepForTimeInterval:INFINITY]; +-} +- +-- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication +-{ +- return YES; +-} +- +-- (NSApplicationTerminateReply)applicationShouldTerminate: +- (NSApplication *)sender +-{ +- COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n"); +- return [self verifyQuit]; +-} +- +-/* Called when the user clicks on a window's close button */ +-- (BOOL)windowShouldClose:(id)sender +-{ +- COCOA_DEBUG("QemuCocoaAppController: windowShouldClose\n"); +- [NSApp terminate: sender]; +- /* If the user allows the application to quit then the call to +- * NSApp terminate will never return. If we get here then the user +- * cancelled the quit, so we should return NO to not permit the +- * closing of this window. +- */ +- return NO; +-} +- +-/* Called when QEMU goes into the background */ +-- (void) applicationWillResignActive: (NSNotification *)aNotification +-{ +- COCOA_DEBUG("QemuCocoaAppController: applicationWillResignActive\n"); +- [cocoaView raiseAllKeys]; +-} +- +-/* We abstract the method called by the Enter Fullscreen menu item +- * because Mac OS 10.7 and higher disables it. This is because of the +- * menu item's old selector's name toggleFullScreen: +- */ +-- (void) doToggleFullScreen:(id)sender +-{ +- [self toggleFullScreen:(id)sender]; +-} +- +-- (void)toggleFullScreen:(id)sender +-{ +- COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n"); +- +- [cocoaView toggleFullScreen:sender]; +-} +- +-/* Tries to find then open the specified filename */ +-- (void) openDocumentation: (NSString *) filename +-{ +- /* Where to look for local files */ +- NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"docs/"}; +- NSString *full_file_path; +- NSURL *full_file_url; +- +- /* iterate thru the possible paths until the file is found */ +- int index; +- for (index = 0; index < ARRAY_SIZE(path_array); index++) { +- full_file_path = [[NSBundle mainBundle] executablePath]; +- full_file_path = [full_file_path stringByDeletingLastPathComponent]; +- full_file_path = [NSString stringWithFormat: @"%@/%@%@", full_file_path, +- path_array[index], filename]; +- full_file_url = [NSURL fileURLWithPath: full_file_path +- isDirectory: false]; +- if ([[NSWorkspace sharedWorkspace] openURL: full_file_url] == YES) { +- return; +- } +- } +- +- /* If none of the paths opened a file */ +- NSBeep(); +- QEMU_Alert(@"Failed to open file"); +-} +- +-- (void)showQEMUDoc:(id)sender +-{ +- COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n"); +- +- [self openDocumentation: @"index.html"]; +-} +- +-/* Stretches video to fit host monitor size */ +-- (void)zoomToFit:(id) sender +-{ +- stretch_video = !stretch_video; +- if (stretch_video == true) { +- [sender setState: NSControlStateValueOn]; +- } else { +- [sender setState: NSControlStateValueOff]; +- } +-} +- +-/* Displays the console on the screen */ +-- (void)displayConsole:(id)sender +-{ +- console_select([sender tag]); +-} +- +-/* Pause the guest */ +-- (void)pauseQEMU:(id)sender +-{ +- with_iothread_lock(^{ +- qmp_stop(NULL); +- }); +- [sender setEnabled: NO]; +- [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES]; +- [self displayPause]; +-} +- +-/* Resume running the guest operating system */ +-- (void)resumeQEMU:(id) sender +-{ +- with_iothread_lock(^{ +- qmp_cont(NULL); +- }); +- [sender setEnabled: NO]; +- [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES]; +- [self removePause]; +-} +- +-/* Displays the word pause on the screen */ +-- (void)displayPause +-{ +- /* Coordinates have to be calculated each time because the window can change its size */ +- int xCoord, yCoord, width, height; +- xCoord = ([normalWindow frame].size.width - [pauseLabel frame].size.width)/2; +- yCoord = [normalWindow frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5); +- width = [pauseLabel frame].size.width; +- height = [pauseLabel frame].size.height; +- [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)]; +- [cocoaView addSubview: pauseLabel]; +-} +- +-/* Removes the word pause from the screen */ +-- (void)removePause +-{ +- [pauseLabel removeFromSuperview]; +-} +- +-/* Restarts QEMU */ +-- (void)restartQEMU:(id)sender +-{ +- with_iothread_lock(^{ +- qmp_system_reset(NULL); +- }); +-} +- +-/* Powers down QEMU */ +-- (void)powerDownQEMU:(id)sender +-{ +- with_iothread_lock(^{ +- qmp_system_powerdown(NULL); +- }); +-} +- +-/* Ejects the media. +- * Uses sender's tag to figure out the device to eject. +- */ +-- (void)ejectDeviceMedia:(id)sender +-{ +- NSString * drive; +- drive = [sender representedObject]; +- if(drive == nil) { +- NSBeep(); +- QEMU_Alert(@"Failed to find drive to eject!"); +- return; +- } +- +- __block Error *err = NULL; +- with_iothread_lock(^{ +- qmp_eject(true, [drive cStringUsingEncoding: NSASCIIStringEncoding], +- false, NULL, false, false, &err); +- }); +- handleAnyDeviceErrors(err); +-} +- +-/* Displays a dialog box asking the user to select an image file to load. +- * Uses sender's represented object value to figure out which drive to use. +- */ +-- (void)changeDeviceMedia:(id)sender +-{ +- /* Find the drive name */ +- NSString * drive; +- drive = [sender representedObject]; +- if(drive == nil) { +- NSBeep(); +- QEMU_Alert(@"Could not find drive!"); +- return; +- } +- +- /* Display the file open dialog */ +- NSOpenPanel * openPanel; +- openPanel = [NSOpenPanel openPanel]; +- [openPanel setCanChooseFiles: YES]; +- [openPanel setAllowsMultipleSelection: NO]; +- [openPanel setAllowedFileTypes: supportedImageFileTypes]; +- if([openPanel runModal] == NSModalResponseOK) { +- NSString * file = [[[openPanel URLs] objectAtIndex: 0] path]; +- if(file == nil) { +- NSBeep(); +- QEMU_Alert(@"Failed to convert URL to file path!"); +- return; +- } +- +- __block Error *err = NULL; +- with_iothread_lock(^{ +- qmp_blockdev_change_medium(true, +- [drive cStringUsingEncoding: +- NSASCIIStringEncoding], +- false, NULL, +- [file cStringUsingEncoding: +- NSASCIIStringEncoding], +- true, "raw", +- false, 0, +- &err); +- }); +- handleAnyDeviceErrors(err); +- } +-} +- +-/* Verifies if the user really wants to quit */ +-- (BOOL)verifyQuit +-{ +- NSAlert *alert = [NSAlert new]; +- [alert autorelease]; +- [alert setMessageText: @"Are you sure you want to quit QEMU?"]; +- [alert addButtonWithTitle: @"Cancel"]; +- [alert addButtonWithTitle: @"Quit"]; +- if([alert runModal] == NSAlertSecondButtonReturn) { +- return YES; +- } else { +- return NO; +- } +-} +- +-/* The action method for the About menu item */ +-- (IBAction) do_about_menu_item: (id) sender +-{ +- [about_window makeKeyAndOrderFront: nil]; +-} +- +-/* Create and display the about dialog */ +-- (void)make_about_window +-{ +- /* Make the window */ +- int x = 0, y = 0, about_width = 400, about_height = 200; +- NSRect window_rect = NSMakeRect(x, y, about_width, about_height); +- about_window = [[NSWindow alloc] initWithContentRect:window_rect +- styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | +- NSWindowStyleMaskMiniaturizable +- backing:NSBackingStoreBuffered +- defer:NO]; +- [about_window setTitle: @"About"]; +- [about_window setReleasedWhenClosed: NO]; +- [about_window center]; +- NSView *superView = [about_window contentView]; +- +- /* Create the dimensions of the picture */ +- int picture_width = 80, picture_height = 80; +- x = (about_width - picture_width)/2; +- y = about_height - picture_height - 10; +- NSRect picture_rect = NSMakeRect(x, y, picture_width, picture_height); +- +- /* Make the picture of QEMU */ +- NSImageView *picture_view = [[NSImageView alloc] initWithFrame: +- picture_rect]; +- char *qemu_image_path_c = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/512x512/apps/qemu.png"); +- NSString *qemu_image_path = [NSString stringWithUTF8String:qemu_image_path_c]; +- g_free(qemu_image_path_c); +- NSImage *qemu_image = [[NSImage alloc] initWithContentsOfFile:qemu_image_path]; +- [picture_view setImage: qemu_image]; +- [picture_view setImageScaling: NSImageScaleProportionallyUpOrDown]; +- [superView addSubview: picture_view]; +- +- /* Make the name label */ +- NSBundle *bundle = [NSBundle mainBundle]; +- if (bundle) { +- x = 0; +- y = y - 25; +- int name_width = about_width, name_height = 20; +- NSRect name_rect = NSMakeRect(x, y, name_width, name_height); +- NSTextField *name_label = [[NSTextField alloc] initWithFrame: name_rect]; +- [name_label setEditable: NO]; +- [name_label setBezeled: NO]; +- [name_label setDrawsBackground: NO]; +- [name_label setAlignment: NSTextAlignmentCenter]; +- NSString *qemu_name = [[bundle executablePath] lastPathComponent]; +- [name_label setStringValue: qemu_name]; +- [superView addSubview: name_label]; +- } +- +- /* Set the version label's attributes */ +- x = 0; +- y = 50; +- int version_width = about_width, version_height = 20; +- NSRect version_rect = NSMakeRect(x, y, version_width, version_height); +- NSTextField *version_label = [[NSTextField alloc] initWithFrame: +- version_rect]; +- [version_label setEditable: NO]; +- [version_label setBezeled: NO]; +- [version_label setAlignment: NSTextAlignmentCenter]; +- [version_label setDrawsBackground: NO]; +- +- /* Create the version string*/ +- NSString *version_string; +- version_string = [[NSString alloc] initWithFormat: +- @"QEMU emulator version %s", QEMU_FULL_VERSION]; +- [version_label setStringValue: version_string]; +- [superView addSubview: version_label]; +- +- /* Make copyright label */ +- x = 0; +- y = 35; +- int copyright_width = about_width, copyright_height = 20; +- NSRect copyright_rect = NSMakeRect(x, y, copyright_width, copyright_height); +- NSTextField *copyright_label = [[NSTextField alloc] initWithFrame: +- copyright_rect]; +- [copyright_label setEditable: NO]; +- [copyright_label setBezeled: NO]; +- [copyright_label setDrawsBackground: NO]; +- [copyright_label setAlignment: NSTextAlignmentCenter]; +- [copyright_label setStringValue: [NSString stringWithFormat: @"%s", +- QEMU_COPYRIGHT]]; +- [superView addSubview: copyright_label]; +-} +- +-/* Used by the Speed menu items */ +-- (void)adjustSpeed:(id)sender +-{ +- int throttle_pct; /* throttle percentage */ +- NSMenu *menu; +- +- menu = [sender menu]; +- if (menu != nil) +- { +- /* Unselect the currently selected item */ +- for (NSMenuItem *item in [menu itemArray]) { +- if (item.state == NSControlStateValueOn) { +- [item setState: NSControlStateValueOff]; +- break; +- } +- } +- } +- +- // check the menu item +- [sender setState: NSControlStateValueOn]; +- +- // get the throttle percentage +- throttle_pct = [sender tag]; +- +- with_iothread_lock(^{ +- cpu_throttle_set(throttle_pct); +- }); +- COCOA_DEBUG("cpu throttling at %d%c\n", cpu_throttle_get_percentage(), '%'); +-} +- +-@end +- +-@interface QemuApplication : NSApplication +-@end +- +-@implementation QemuApplication +-- (void)sendEvent:(NSEvent *)event +-{ +- COCOA_DEBUG("QemuApplication: sendEvent\n"); +- if (![cocoaView handleEvent:event]) { +- [super sendEvent: event]; +- } +-} +-@end +- +-static void create_initial_menus(void) +-{ +- // Add menus +- NSMenu *menu; +- NSMenuItem *menuItem; +- +- [NSApp setMainMenu:[[NSMenu alloc] init]]; +- +- // Application menu +- menu = [[NSMenu alloc] initWithTitle:@""]; +- [menu addItemWithTitle:@"About QEMU" action:@selector(do_about_menu_item:) keyEquivalent:@""]; // About QEMU +- [menu addItem:[NSMenuItem separatorItem]]; //Separator +- [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU +- menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others +- [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)]; +- [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All +- [menu addItem:[NSMenuItem separatorItem]]; //Separator +- [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"]; +- menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""]; +- [menuItem setSubmenu:menu]; +- [[NSApp mainMenu] addItem:menuItem]; +- [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+) +- +- // Machine menu +- menu = [[NSMenu alloc] initWithTitle: @"Machine"]; +- [menu setAutoenablesItems: NO]; +- [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]]; +- menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease]; +- [menu addItem: menuItem]; +- [menuItem setEnabled: NO]; +- [menu addItem: [NSMenuItem separatorItem]]; +- [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]]; +- [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]]; +- menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease]; +- [menuItem setSubmenu:menu]; +- [[NSApp mainMenu] addItem:menuItem]; +- +- // View menu +- menu = [[NSMenu alloc] initWithTitle:@"View"]; +- [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen +- [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease]]; +- menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease]; +- [menuItem setSubmenu:menu]; +- [[NSApp mainMenu] addItem:menuItem]; +- +- // Speed menu +- menu = [[NSMenu alloc] initWithTitle:@"Speed"]; +- +- // Add the rest of the Speed menu items +- int p, percentage, throttle_pct; +- for (p = 10; p >= 0; p--) +- { +- percentage = p * 10 > 1 ? p * 10 : 1; // prevent a 0% menu item +- +- menuItem = [[[NSMenuItem alloc] +- initWithTitle: [NSString stringWithFormat: @"%d%%", percentage] action:@selector(adjustSpeed:) keyEquivalent:@""] autorelease]; +- +- if (percentage == 100) { +- [menuItem setState: NSControlStateValueOn]; +- } +- +- /* Calculate the throttle percentage */ +- throttle_pct = -1 * percentage + 100; +- +- [menuItem setTag: throttle_pct]; +- [menu addItem: menuItem]; +- } +- menuItem = [[[NSMenuItem alloc] initWithTitle:@"Speed" action:nil keyEquivalent:@""] autorelease]; +- [menuItem setSubmenu:menu]; +- [[NSApp mainMenu] addItem:menuItem]; +- +- // Window menu +- menu = [[NSMenu alloc] initWithTitle:@"Window"]; +- [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize +- menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; +- [menuItem setSubmenu:menu]; +- [[NSApp mainMenu] addItem:menuItem]; +- [NSApp setWindowsMenu:menu]; +- +- // Help menu +- menu = [[NSMenu alloc] initWithTitle:@"Help"]; +- [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help +- menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; +- [menuItem setSubmenu:menu]; +- [[NSApp mainMenu] addItem:menuItem]; +-} +- +-/* Returns a name for a given console */ +-static NSString * getConsoleName(QemuConsole * console) +-{ +- return [NSString stringWithFormat: @"%s", qemu_console_get_label(console)]; +-} +- +-/* Add an entry to the View menu for each console */ +-static void add_console_menu_entries(void) +-{ +- NSMenu *menu; +- NSMenuItem *menuItem; +- int index = 0; +- +- menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu]; +- +- [menu addItem:[NSMenuItem separatorItem]]; +- +- while (qemu_console_lookup_by_index(index) != NULL) { +- menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(qemu_console_lookup_by_index(index)) +- action: @selector(displayConsole:) keyEquivalent: @""] autorelease]; +- [menuItem setTag: index]; +- [menu addItem: menuItem]; +- index++; +- } +-} +- +-/* Make menu items for all removable devices. +- * Each device is given an 'Eject' and 'Change' menu item. +- */ +-static void addRemovableDevicesMenuItems(void) +-{ +- NSMenu *menu; +- NSMenuItem *menuItem; +- BlockInfoList *currentDevice, *pointerToFree; +- NSString *deviceName; +- +- currentDevice = qmp_query_block(NULL); +- pointerToFree = currentDevice; +- if(currentDevice == NULL) { +- NSBeep(); +- QEMU_Alert(@"Failed to query for block devices!"); +- return; +- } +- +- menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu]; +- +- // Add a separator between related groups of menu items +- [menu addItem:[NSMenuItem separatorItem]]; +- +- // Set the attributes to the "Removable Media" menu item +- NSString *titleString = @"Removable Media"; +- NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:titleString]; +- NSColor *newColor = [NSColor blackColor]; +- NSFontManager *fontManager = [NSFontManager sharedFontManager]; +- NSFont *font = [fontManager fontWithFamily:@"Helvetica" +- traits:NSBoldFontMask|NSItalicFontMask +- weight:0 +- size:14]; +- [attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [titleString length])]; +- [attString addAttribute:NSForegroundColorAttributeName value:newColor range:NSMakeRange(0, [titleString length])]; +- [attString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt: 1] range:NSMakeRange(0, [titleString length])]; +- +- // Add the "Removable Media" menu item +- menuItem = [NSMenuItem new]; +- [menuItem setAttributedTitle: attString]; +- [menuItem setEnabled: NO]; +- [menu addItem: menuItem]; +- +- /* Loop through all the block devices in the emulator */ +- while (currentDevice) { +- deviceName = [[NSString stringWithFormat: @"%s", currentDevice->value->device] retain]; +- +- if(currentDevice->value->removable) { +- menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", currentDevice->value->device] +- action: @selector(changeDeviceMedia:) +- keyEquivalent: @""]; +- [menu addItem: menuItem]; +- [menuItem setRepresentedObject: deviceName]; +- [menuItem autorelease]; +- +- menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", currentDevice->value->device] +- action: @selector(ejectDeviceMedia:) +- keyEquivalent: @""]; +- [menu addItem: menuItem]; +- [menuItem setRepresentedObject: deviceName]; +- [menuItem autorelease]; +- } +- currentDevice = currentDevice->next; +- } +- qapi_free_BlockInfoList(pointerToFree); +-} +- +-/* +- * The startup process for the OSX/Cocoa UI is complicated, because +- * OSX insists that the UI runs on the initial main thread, and so we +- * need to start a second thread which runs the vl.c qemu_main(): +- * +- * Initial thread: 2nd thread: +- * in main(): +- * create qemu-main thread +- * wait on display_init semaphore +- * call qemu_main() +- * ... +- * in cocoa_display_init(): +- * post the display_init semaphore +- * wait on app_started semaphore +- * create application, menus, etc +- * enter OSX run loop +- * in applicationDidFinishLaunching: +- * post app_started semaphore +- * tell main thread to fullscreen if needed +- * [...] +- * run qemu main-loop +- * +- * We do this in two stages so that we don't do the creation of the +- * GUI application menus and so on for command line options like --help +- * where we want to just print text to stdout and exit immediately. +- */ +- +-static void *call_qemu_main(void *opaque) +-{ +- int status; +- +- COCOA_DEBUG("Second thread: calling qemu_main()\n"); +- status = qemu_main(gArgc, gArgv, *_NSGetEnviron()); +- COCOA_DEBUG("Second thread: qemu_main() returned, exiting\n"); +- exit(status); +-} +- +-int main (int argc, const char * argv[]) { +- QemuThread thread; +- +- COCOA_DEBUG("Entered main()\n"); +- gArgc = argc; +- gArgv = (char **)argv; +- +- qemu_sem_init(&display_init_sem, 0); +- qemu_sem_init(&app_started_sem, 0); +- +- qemu_thread_create(&thread, "qemu_main", call_qemu_main, +- NULL, QEMU_THREAD_DETACHED); +- +- COCOA_DEBUG("Main thread: waiting for display_init_sem\n"); +- qemu_sem_wait(&display_init_sem); +- COCOA_DEBUG("Main thread: initializing app\n"); +- +- NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +- +- // Pull this console process up to being a fully-fledged graphical +- // app with a menubar and Dock icon +- ProcessSerialNumber psn = { 0, kCurrentProcess }; +- TransformProcessType(&psn, kProcessTransformToForegroundApplication); +- +- [QemuApplication sharedApplication]; +- +- create_initial_menus(); +- +- /* +- * Create the menu entries which depend on QEMU state (for consoles +- * and removeable devices). These make calls back into QEMU functions, +- * which is OK because at this point we know that the second thread +- * holds the iothread lock and is synchronously waiting for us to +- * finish. +- */ +- add_console_menu_entries(); +- addRemovableDevicesMenuItems(); +- +- // Create an Application controller +- QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init]; +- [NSApp setDelegate:appController]; +- +- // Start the main event loop +- COCOA_DEBUG("Main thread: entering OSX run loop\n"); +- [NSApp run]; +- COCOA_DEBUG("Main thread: left OSX run loop, exiting\n"); +- +- [appController release]; +- [pool release]; +- +- return 0; +-} +- +- +- +-#pragma mark qemu +-static void cocoa_update(DisplayChangeListener *dcl, +- int x, int y, int w, int h) +-{ +- NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +- +- COCOA_DEBUG("qemu_cocoa: cocoa_update\n"); +- +- dispatch_async(dispatch_get_main_queue(), ^{ +- NSRect rect; +- if ([cocoaView cdx] == 1.0) { +- rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h); +- } else { +- rect = NSMakeRect( +- x * [cocoaView cdx], +- ([cocoaView gscreen].height - y - h) * [cocoaView cdy], +- w * [cocoaView cdx], +- h * [cocoaView cdy]); +- } +- [cocoaView setNeedsDisplayInRect:rect]; +- }); +- +- [pool release]; +-} +- +-static void cocoa_switch(DisplayChangeListener *dcl, +- DisplaySurface *surface) +-{ +- NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +- pixman_image_t *image = surface->image; +- +- COCOA_DEBUG("qemu_cocoa: cocoa_switch\n"); +- +- // The DisplaySurface will be freed as soon as this callback returns. +- // We take a reference to the underlying pixman image here so it does +- // not disappear from under our feet; the switchSurface method will +- // deref the old image when it is done with it. +- pixman_image_ref(image); +- +- dispatch_async(dispatch_get_main_queue(), ^{ +- [cocoaView switchSurface:image]; +- }); +- [pool release]; +-} +- +-static void cocoa_refresh(DisplayChangeListener *dcl) +-{ +- NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +- +- COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n"); +- graphic_hw_update(NULL); +- +- if (qemu_input_is_absolute()) { +- dispatch_async(dispatch_get_main_queue(), ^{ +- if (![cocoaView isAbsoluteEnabled]) { +- if ([cocoaView isMouseGrabbed]) { +- [cocoaView ungrabMouse]; +- } +- } +- [cocoaView setAbsoluteEnabled:YES]; +- }); +- } +- [pool release]; +-} +- +-static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts) +-{ +- COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n"); +- +- /* Tell main thread to go ahead and create the app and enter the run loop */ +- qemu_sem_post(&display_init_sem); +- qemu_sem_wait(&app_started_sem); +- COCOA_DEBUG("cocoa_display_init: app start completed\n"); +- +- /* if fullscreen mode is to be used */ +- if (opts->has_full_screen && opts->full_screen) { +- dispatch_async(dispatch_get_main_queue(), ^{ +- [NSApp activateIgnoringOtherApps: YES]; +- [(QemuCocoaAppController *)[[NSApplication sharedApplication] delegate] toggleFullScreen: nil]; +- }); +- } +- if (opts->has_show_cursor && opts->show_cursor) { +- cursor_hide = 0; +- } +- +- // register vga output callbacks +- register_displaychangelistener(&dcl); +-} +- +-static QemuDisplay qemu_display_cocoa = { +- .type = DISPLAY_TYPE_COCOA, +- .init = cocoa_display_init, +-}; +- +-static void register_cocoa(void) +-{ +- qemu_display_register(&qemu_display_cocoa); +-} +- +-type_init(register_cocoa); +diff --git a/ui/cocoa/app_controller.m b/ui/cocoa/app_controller.m +new file mode 100644 +index 0000000000..0aca300700 +--- /dev/null ++++ b/ui/cocoa/app_controller.m +@@ -0,0 +1,727 @@ ++/* ++ * QEMU Cocoa CG display driver ++ * ++ * Copyright (c) 2008 Mike Kronenberg ++ * ++ * 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 "qemu/osdep.h" ++ ++#include "qemu-common.h" ++#include "ui/cocoa.h" ++#include "sysemu/sysemu.h" ++#include "sysemu/runstate.h" ++#include "sysemu/cpu-throttle.h" ++#include "qapi/error.h" ++#include "qapi/qapi-commands-block.h" ++#include "qapi/qapi-commands-machine.h" ++#include "qapi/qapi-commands-misc.h" ++#include "sysemu/blockdev.h" ++#include "qemu-version.h" ++#include "qemu/cutils.h" ++#include "qemu/main-loop.h" ++#include "qemu/module.h" ++#include "hw/core/cpu.h" ++ ++#ifndef MAC_OS_X_VERSION_10_13 ++#define MAC_OS_X_VERSION_10_13 101300 ++#endif ++ ++/* 10.14 deprecates NSOnState and NSOffState in favor of ++ * NSControlStateValueOn/Off, which were introduced in 10.13. ++ * Define for older versions ++ */ ++#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13 ++#define NSControlStateValueOn NSOnState ++#define NSControlStateValueOff NSOffState ++#endif ++ ++// Utility function to run specified code block with iothread lock held ++static void with_iothread_lock(CodeBlock block) ++{ ++ bool locked = qemu_mutex_iothread_locked(); ++ if (!locked) { ++ qemu_mutex_lock_iothread(); ++ } ++ block(); ++ if (!locked) { ++ qemu_mutex_unlock_iothread(); ++ } ++} ++ ++/* Displays an alert dialog box with the specified message */ ++static void QEMU_Alert(NSString *message) ++{ ++ NSAlert *alert; ++ alert = [NSAlert new]; ++ [alert setMessageText: message]; ++ [alert runModal]; ++} ++ ++/* Handles any errors that happen with a device transaction */ ++static void handleAnyDeviceErrors(Error * err) ++{ ++ if (err) { ++ QEMU_Alert([NSString stringWithCString: error_get_pretty(err) ++ encoding: NSASCIIStringEncoding]); ++ error_free(err); ++ } ++} ++static void create_initial_menus(void) ++{ ++ // Add menus ++ NSMenu *menu; ++ NSMenuItem *menuItem; ++ ++ [NSApp setMainMenu:[[NSMenu alloc] init]]; ++ ++ // Application menu ++ menu = [[NSMenu alloc] initWithTitle:@""]; ++ [menu addItemWithTitle:@"About QEMU" action:@selector(do_about_menu_item:) keyEquivalent:@""]; // About QEMU ++ [menu addItem:[NSMenuItem separatorItem]]; //Separator ++ [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU ++ menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others ++ [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)]; ++ [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All ++ [menu addItem:[NSMenuItem separatorItem]]; //Separator ++ [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"]; ++ menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""]; ++ [menuItem setSubmenu:menu]; ++ [[NSApp mainMenu] addItem:menuItem]; ++ [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+) ++ ++ // Machine menu ++ menu = [[NSMenu alloc] initWithTitle: @"Machine"]; ++ [menu setAutoenablesItems: NO]; ++ [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]]; ++ menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease]; ++ [menu addItem: menuItem]; ++ [menuItem setEnabled: NO]; ++ [menu addItem: [NSMenuItem separatorItem]]; ++ [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]]; ++ [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]]; ++ menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease]; ++ [menuItem setSubmenu:menu]; ++ [[NSApp mainMenu] addItem:menuItem]; ++ ++ // View menu ++ menu = [[NSMenu alloc] initWithTitle:@"View"]; ++ [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen ++ [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease]]; ++ menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease]; ++ [menuItem setSubmenu:menu]; ++ [[NSApp mainMenu] addItem:menuItem]; ++ ++ // Speed menu ++ menu = [[NSMenu alloc] initWithTitle:@"Speed"]; ++ ++ // Add the rest of the Speed menu items ++ int p, percentage, throttle_pct; ++ for (p = 10; p >= 0; p--) ++ { ++ percentage = p * 10 > 1 ? p * 10 : 1; // prevent a 0% menu item ++ ++ menuItem = [[[NSMenuItem alloc] ++ initWithTitle: [NSString stringWithFormat: @"%d%%", percentage] action:@selector(adjustSpeed:) keyEquivalent:@""] autorelease]; ++ ++ if (percentage == 100) { ++ [menuItem setState: NSControlStateValueOn]; ++ } ++ ++ /* Calculate the throttle percentage */ ++ throttle_pct = -1 * percentage + 100; ++ ++ [menuItem setTag: throttle_pct]; ++ [menu addItem: menuItem]; ++ } ++ menuItem = [[[NSMenuItem alloc] initWithTitle:@"Speed" action:nil keyEquivalent:@""] autorelease]; ++ [menuItem setSubmenu:menu]; ++ [[NSApp mainMenu] addItem:menuItem]; ++ ++ // Window menu ++ menu = [[NSMenu alloc] initWithTitle:@"Window"]; ++ [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize ++ menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; ++ [menuItem setSubmenu:menu]; ++ [[NSApp mainMenu] addItem:menuItem]; ++ [NSApp setWindowsMenu:menu]; ++ ++ // Help menu ++ menu = [[NSMenu alloc] initWithTitle:@"Help"]; ++ [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help ++ menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; ++ [menuItem setSubmenu:menu]; ++ [[NSApp mainMenu] addItem:menuItem]; ++} ++ ++/* Returns a name for a given console */ ++static NSString * getConsoleName(QemuConsole * console) ++{ ++ return [NSString stringWithFormat: @"%s", qemu_console_get_label(console)]; ++} ++ ++/* Add an entry to the View menu for each console */ ++static void add_console_menu_entries(void) ++{ ++ NSMenu *menu; ++ NSMenuItem *menuItem; ++ int index = 0; ++ ++ menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu]; ++ ++ [menu addItem:[NSMenuItem separatorItem]]; ++ ++ while (qemu_console_lookup_by_index(index) != NULL) { ++ menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(qemu_console_lookup_by_index(index)) ++ action: @selector(displayConsole:) keyEquivalent: @""] autorelease]; ++ [menuItem setTag: index]; ++ [menu addItem: menuItem]; ++ index++; ++ } ++} ++ ++/* Make menu items for all removable devices. ++ * Each device is given an 'Eject' and 'Change' menu item. ++ */ ++static void addRemovableDevicesMenuItems(void) ++{ ++ NSMenu *menu; ++ NSMenuItem *menuItem; ++ BlockInfoList *currentDevice, *pointerToFree; ++ NSString *deviceName; ++ ++ currentDevice = qmp_query_block(NULL); ++ pointerToFree = currentDevice; ++ if(currentDevice == NULL) { ++ NSBeep(); ++ QEMU_Alert(@"Failed to query for block devices!"); ++ return; ++ } ++ ++ menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu]; ++ ++ // Add a separator between related groups of menu items ++ [menu addItem:[NSMenuItem separatorItem]]; ++ ++ // Set the attributes to the "Removable Media" menu item ++ NSString *titleString = @"Removable Media"; ++ NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:titleString]; ++ NSColor *newColor = [NSColor blackColor]; ++ NSFontManager *fontManager = [NSFontManager sharedFontManager]; ++ NSFont *font = [fontManager fontWithFamily:@"Helvetica" ++ traits:NSBoldFontMask|NSItalicFontMask ++ weight:0 ++ size:14]; ++ [attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [titleString length])]; ++ [attString addAttribute:NSForegroundColorAttributeName value:newColor range:NSMakeRange(0, [titleString length])]; ++ [attString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt: 1] range:NSMakeRange(0, [titleString length])]; ++ ++ // Add the "Removable Media" menu item ++ menuItem = [NSMenuItem new]; ++ [menuItem setAttributedTitle: attString]; ++ [menuItem setEnabled: NO]; ++ [menu addItem: menuItem]; ++ ++ /* Loop through all the block devices in the emulator */ ++ while (currentDevice) { ++ deviceName = [[NSString stringWithFormat: @"%s", currentDevice->value->device] retain]; ++ ++ if(currentDevice->value->removable) { ++ menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", currentDevice->value->device] ++ action: @selector(changeDeviceMedia:) ++ keyEquivalent: @""]; ++ [menu addItem: menuItem]; ++ [menuItem setRepresentedObject: deviceName]; ++ [menuItem autorelease]; ++ ++ menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", currentDevice->value->device] ++ action: @selector(ejectDeviceMedia:) ++ keyEquivalent: @""]; ++ [menu addItem: menuItem]; ++ [menuItem setRepresentedObject: deviceName]; ++ [menuItem autorelease]; ++ } ++ currentDevice = currentDevice->next; ++ } ++ qapi_free_BlockInfoList(pointerToFree); ++} ++ ++@implementation QemuCocoaAppController ++- (id) initWithStartedSem:(QemuSemaphore *)given_started_sem ++ screen:(QEMUScreen *)screen ++{ ++ COCOA_DEBUG("%s\n", __func__); ++ ++ self = [super init]; ++ if (self) { ++ ++ started_sem = given_started_sem; ++ ++ create_initial_menus(); ++ ++ /* ++ * Create the menu entries which depend on QEMU state (for consoles ++ * and removeable devices). These make calls back into QEMU functions, ++ * which is OK because at this point we know that the second thread ++ * holds the iothread lock and is synchronously waiting for us to ++ * finish. ++ */ ++ add_console_menu_entries(); ++ addRemovableDevicesMenuItems(); ++ ++ // create a view and add it to the window ++ cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0) ++ screen:screen]; ++ if(!cocoaView) { ++ error_report("(cocoa) can't create a view"); ++ exit(1); ++ } ++ ++ // create a window ++ NSWindow *normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame] ++ styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable ++ backing:NSBackingStoreBuffered defer:NO]; ++ if(!normalWindow) { ++ error_report("(cocoa) can't create window"); ++ exit(1); ++ } ++ [normalWindow setAcceptsMouseMovedEvents:YES]; ++ [normalWindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; ++ [normalWindow setTitle:qemu_name ? [NSString stringWithFormat:@"QEMU %s", qemu_name] : @"QEMU"]; ++ [normalWindow setContentView:cocoaView]; ++ [normalWindow makeKeyAndOrderFront:self]; ++ [normalWindow center]; ++ [normalWindow setDelegate: self]; ++ [normalWindow release]; ++ ++ // set the supported image file types that can be opened ++ supportedImageFileTypes = [NSArray arrayWithObjects: @"img", @"iso", @"dmg", ++ @"qcow", @"qcow2", @"cloop", @"vmdk", @"cdr", ++ @"toast", nil]; ++ [self make_about_window]; ++ } ++ return self; ++} ++ ++- (void) dealloc ++{ ++ COCOA_DEBUG("QemuCocoaAppController: dealloc\n"); ++ ++ if (about_window) { ++ [about_window release]; ++ } ++ if (cocoaView) ++ [cocoaView release]; ++ [super dealloc]; ++} ++ ++- (void)applicationDidFinishLaunching: (NSNotification *) note ++{ ++ COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n"); ++ /* Tell cocoa_display_init to proceed */ ++ qemu_sem_post(started_sem); ++} ++ ++- (void)applicationWillTerminate:(NSNotification *)aNotification ++{ ++ COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n"); ++ ++ qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI); ++ ++ /* ++ * Sleep here, because returning will cause OSX to kill us ++ * immediately; the QEMU main loop will handle the shutdown ++ * request and terminate the process. ++ */ ++ [NSThread sleepForTimeInterval:INFINITY]; ++} ++ ++- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication ++{ ++ return YES; ++} ++ ++- (NSApplicationTerminateReply)applicationShouldTerminate: ++ (NSApplication *)sender ++{ ++ COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n"); ++ return [self verifyQuit]; ++} ++ ++- (void)windowDidChangeScreen:(NSNotification *)notification ++{ ++ [cocoaView updateUIInfo]; ++} ++ ++- (void)windowDidEnterFullScreen:(NSNotification *)notification ++{ ++ [cocoaView grabMouse]; ++} ++ ++- (void)windowDidExitFullScreen:(NSNotification *)notification ++{ ++ [cocoaView resizeWindow]; ++ [cocoaView ungrabMouse]; ++} ++ ++- (void)windowDidResize:(NSNotification *)notification ++{ ++ [cocoaView frameUpdated]; ++} ++ ++/* Called when the user clicks on a window's close button */ ++- (BOOL)windowShouldClose:(id)sender ++{ ++ COCOA_DEBUG("QemuCocoaAppController: windowShouldClose\n"); ++ [NSApp terminate: sender]; ++ /* If the user allows the application to quit then the call to ++ * NSApp terminate will never return. If we get here then the user ++ * cancelled the quit, so we should return NO to not permit the ++ * closing of this window. ++ */ ++ return NO; ++} ++ ++- (NSSize) window:(NSWindow *)window willUseFullScreenContentSize:(NSSize)proposedSize ++{ ++ if (([[cocoaView window] styleMask] & NSWindowStyleMaskResizable) == 0) { ++ return [cocoaView computeUnzoomedSize]; ++ } ++ ++ return [cocoaView fixZoomedFullScreenSize:proposedSize]; ++} ++ ++- (NSApplicationPresentationOptions) window:(NSWindow *)window ++ willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions; ++ ++{ ++ return (proposedOptions & ~(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)) | ++ NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar; ++} ++ ++/* We abstract the method called by the Enter Fullscreen menu item ++ * because Mac OS 10.7 and higher disables it. This is because of the ++ * menu item's old selector's name toggleFullScreen: ++ */ ++- (void) doToggleFullScreen:(id)sender ++{ ++ [[cocoaView window] toggleFullScreen:sender]; ++} ++ ++/* Tries to find then open the specified filename */ ++- (void) openDocumentation: (NSString *) filename ++{ ++ /* Where to look for local files */ ++ NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"docs/"}; ++ NSString *full_file_path; ++ NSURL *full_file_url; ++ ++ /* iterate thru the possible paths until the file is found */ ++ int index; ++ for (index = 0; index < ARRAY_SIZE(path_array); index++) { ++ full_file_path = [[NSBundle mainBundle] executablePath]; ++ full_file_path = [full_file_path stringByDeletingLastPathComponent]; ++ full_file_path = [NSString stringWithFormat: @"%@/%@%@", full_file_path, ++ path_array[index], filename]; ++ full_file_url = [NSURL fileURLWithPath: full_file_path ++ isDirectory: false]; ++ if ([[NSWorkspace sharedWorkspace] openURL: full_file_url] == YES) { ++ return; ++ } ++ } ++ ++ /* If none of the paths opened a file */ ++ NSBeep(); ++ QEMU_Alert(@"Failed to open file"); ++} ++ ++- (void)showQEMUDoc:(id)sender ++{ ++ COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n"); ++ ++ [self openDocumentation: @"index.html"]; ++} ++ ++/* Toggles the flag which stretches video to fit host window size */ ++- (void)zoomToFit:(id) sender ++{ ++ if (([[cocoaView window] styleMask] & NSWindowStyleMaskResizable) == 0) { ++ [[cocoaView window] setStyleMask:[[cocoaView window] styleMask] | NSWindowStyleMaskResizable]; ++ [sender setState: NSControlStateValueOn]; ++ } else { ++ [[cocoaView window] setStyleMask:[[cocoaView window] styleMask] & ~NSWindowStyleMaskResizable]; ++ [cocoaView resizeWindow]; ++ [sender setState: NSControlStateValueOff]; ++ } ++} ++ ++/* Displays the console on the screen */ ++- (void)displayConsole:(id)sender ++{ ++ with_iothread_lock(^{ ++ console_select([sender tag]); ++ }); ++} ++ ++/* Pause the guest */ ++- (void)pauseQEMU:(id)sender ++{ ++ with_iothread_lock(^{ ++ qmp_stop(NULL); ++ }); ++ [sender setEnabled: NO]; ++ [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES]; ++ [cocoaView displayPause]; ++} ++ ++/* Resume running the guest operating system */ ++- (void)resumeQEMU:(id) sender ++{ ++ with_iothread_lock(^{ ++ qmp_cont(NULL); ++ }); ++ [sender setEnabled: NO]; ++ [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES]; ++ [cocoaView removePause]; ++} ++ ++/* Restarts QEMU */ ++- (void)restartQEMU:(id)sender ++{ ++ with_iothread_lock(^{ ++ qmp_system_reset(NULL); ++ }); ++} ++ ++/* Powers down QEMU */ ++- (void)powerDownQEMU:(id)sender ++{ ++ with_iothread_lock(^{ ++ qmp_system_powerdown(NULL); ++ }); ++} ++ ++/* Ejects the media. ++ * Uses sender's tag to figure out the device to eject. ++ */ ++- (void)ejectDeviceMedia:(id)sender ++{ ++ NSString * drive; ++ drive = [sender representedObject]; ++ if(drive == nil) { ++ NSBeep(); ++ QEMU_Alert(@"Failed to find drive to eject!"); ++ return; ++ } ++ ++ __block Error *err = NULL; ++ with_iothread_lock(^{ ++ qmp_eject(true, [drive cStringUsingEncoding: NSASCIIStringEncoding], ++ false, NULL, false, false, &err); ++ }); ++ handleAnyDeviceErrors(err); ++} ++ ++/* Displays a dialog box asking the user to select an image file to load. ++ * Uses sender's represented object value to figure out which drive to use. ++ */ ++- (void)changeDeviceMedia:(id)sender ++{ ++ /* Find the drive name */ ++ NSString * drive; ++ drive = [sender representedObject]; ++ if(drive == nil) { ++ NSBeep(); ++ QEMU_Alert(@"Could not find drive!"); ++ return; ++ } ++ ++ /* Display the file open dialog */ ++ NSOpenPanel * openPanel; ++ openPanel = [NSOpenPanel openPanel]; ++ [openPanel setCanChooseFiles: YES]; ++ [openPanel setAllowsMultipleSelection: NO]; ++ [openPanel setAllowedFileTypes: supportedImageFileTypes]; ++ if([openPanel runModal] == NSModalResponseOK) { ++ NSString * file = [[[openPanel URLs] objectAtIndex: 0] path]; ++ if(file == nil) { ++ NSBeep(); ++ QEMU_Alert(@"Failed to convert URL to file path!"); ++ return; ++ } ++ ++ __block Error *err = NULL; ++ with_iothread_lock(^{ ++ qmp_blockdev_change_medium(true, ++ [drive cStringUsingEncoding: ++ NSASCIIStringEncoding], ++ false, NULL, ++ [file cStringUsingEncoding: ++ NSASCIIStringEncoding], ++ true, "raw", ++ false, 0, ++ &err); ++ }); ++ handleAnyDeviceErrors(err); ++ } ++} ++ ++/* Verifies if the user really wants to quit */ ++- (BOOL)verifyQuit ++{ ++ NSAlert *alert = [NSAlert new]; ++ [alert autorelease]; ++ [alert setMessageText: @"Are you sure you want to quit QEMU?"]; ++ [alert addButtonWithTitle: @"Cancel"]; ++ [alert addButtonWithTitle: @"Quit"]; ++ if([alert runModal] == NSAlertSecondButtonReturn) { ++ return YES; ++ } else { ++ return NO; ++ } ++} ++ ++/* The action method for the About menu item */ ++- (IBAction) do_about_menu_item: (id) sender ++{ ++ [about_window makeKeyAndOrderFront: nil]; ++} ++ ++/* Create and display the about dialog */ ++- (void)make_about_window ++{ ++ /* Make the window */ ++ int x = 0, y = 0, about_width = 400, about_height = 200; ++ NSRect window_rect = NSMakeRect(x, y, about_width, about_height); ++ about_window = [[NSWindow alloc] initWithContentRect:window_rect ++ styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | ++ NSWindowStyleMaskMiniaturizable ++ backing:NSBackingStoreBuffered ++ defer:NO]; ++ [about_window setTitle: @"About"]; ++ [about_window setReleasedWhenClosed: NO]; ++ [about_window center]; ++ NSView *superView = [about_window contentView]; ++ ++ /* Create the dimensions of the picture */ ++ int picture_width = 80, picture_height = 80; ++ x = (about_width - picture_width)/2; ++ y = about_height - picture_height - 10; ++ NSRect picture_rect = NSMakeRect(x, y, picture_width, picture_height); ++ ++ /* Make the picture of QEMU */ ++ NSImageView *picture_view = [[NSImageView alloc] initWithFrame: ++ picture_rect]; ++ char *qemu_image_path_c = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/512x512/apps/qemu.png"); ++ NSString *qemu_image_path = [NSString stringWithUTF8String:qemu_image_path_c]; ++ g_free(qemu_image_path_c); ++ NSImage *qemu_image = [[NSImage alloc] initWithContentsOfFile:qemu_image_path]; ++ [picture_view setImage: qemu_image]; ++ [picture_view setImageScaling: NSImageScaleProportionallyUpOrDown]; ++ [superView addSubview: picture_view]; ++ ++ /* Make the name label */ ++ NSBundle *bundle = [NSBundle mainBundle]; ++ if (bundle) { ++ x = 0; ++ y = y - 25; ++ int name_width = about_width, name_height = 20; ++ NSRect name_rect = NSMakeRect(x, y, name_width, name_height); ++ NSTextField *name_label = [[NSTextField alloc] initWithFrame: name_rect]; ++ [name_label setEditable: NO]; ++ [name_label setBezeled: NO]; ++ [name_label setDrawsBackground: NO]; ++ [name_label setAlignment: NSTextAlignmentCenter]; ++ NSString *qemu_name = [[bundle executablePath] lastPathComponent]; ++ [name_label setStringValue: qemu_name]; ++ [superView addSubview: name_label]; ++ } ++ ++ /* Set the version label's attributes */ ++ x = 0; ++ y = 50; ++ int version_width = about_width, version_height = 20; ++ NSRect version_rect = NSMakeRect(x, y, version_width, version_height); ++ NSTextField *version_label = [[NSTextField alloc] initWithFrame: ++ version_rect]; ++ [version_label setEditable: NO]; ++ [version_label setBezeled: NO]; ++ [version_label setAlignment: NSTextAlignmentCenter]; ++ [version_label setDrawsBackground: NO]; ++ ++ /* Create the version string*/ ++ NSString *version_string; ++ version_string = [[NSString alloc] initWithFormat: ++ @"QEMU emulator version %s", QEMU_FULL_VERSION]; ++ [version_label setStringValue: version_string]; ++ [superView addSubview: version_label]; ++ ++ /* Make copyright label */ ++ x = 0; ++ y = 35; ++ int copyright_width = about_width, copyright_height = 20; ++ NSRect copyright_rect = NSMakeRect(x, y, copyright_width, copyright_height); ++ NSTextField *copyright_label = [[NSTextField alloc] initWithFrame: ++ copyright_rect]; ++ [copyright_label setEditable: NO]; ++ [copyright_label setBezeled: NO]; ++ [copyright_label setDrawsBackground: NO]; ++ [copyright_label setAlignment: NSTextAlignmentCenter]; ++ [copyright_label setStringValue: [NSString stringWithFormat: @"%s", ++ QEMU_COPYRIGHT]]; ++ [superView addSubview: copyright_label]; ++} ++ ++/* Used by the Speed menu items */ ++- (void)adjustSpeed:(id)sender ++{ ++ int throttle_pct; /* throttle percentage */ ++ NSMenu *menu; ++ ++ menu = [sender menu]; ++ if (menu != nil) ++ { ++ /* Unselect the currently selected item */ ++ for (NSMenuItem *item in [menu itemArray]) { ++ if (item.state == NSControlStateValueOn) { ++ [item setState: NSControlStateValueOff]; ++ break; ++ } ++ } ++ } ++ ++ // check the menu item ++ [sender setState: NSControlStateValueOn]; ++ ++ // get the throttle percentage ++ throttle_pct = [sender tag]; ++ ++ with_iothread_lock(^{ ++ cpu_throttle_set(throttle_pct); ++ }); ++ COCOA_DEBUG("cpu throttling at %d%c\n", cpu_throttle_get_percentage(), '%'); ++} ++ ++- (QemuCocoaView *)cocoaView ++{ ++ return cocoaView; ++} ++ ++@end +diff --git a/ui/cocoa/main.m b/ui/cocoa/main.m +new file mode 100644 +index 0000000000..aa670136d3 +--- /dev/null ++++ b/ui/cocoa/main.m +@@ -0,0 +1,761 @@ ++/* ++ * QEMU Cocoa CG display driver ++ * ++ * Copyright (c) 2008 Mike Kronenberg ++ * ++ * 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 "qemu/osdep.h" ++ ++#include ++ ++#include "qemu-common.h" ++#include "ui/cocoa.h" ++#include "ui/input.h" ++#include "sysemu/sysemu.h" ++ ++#ifdef CONFIG_EGL ++#include "ui/egl-context.h" ++#endif ++ ++static QEMUScreen screen; ++static QemuCocoaAppController *appController; ++ ++static int gArgc; ++static char **gArgv; ++ ++static QemuSemaphore display_init_sem; ++static QemuSemaphore app_started_sem; ++ ++#ifdef CONFIG_OPENGL ++ ++typedef struct { ++ uint32_t scanout_id; ++ DisplayGLTextureBorrower scanout_borrow; ++ bool surface_dirty; ++} DisplayGL; ++ ++static DisplayGL *dgs; ++static QEMUGLContext view_ctx; ++static QemuGLShader *gls; ++static GLuint cursor_texture; ++static int cursor_texture_width; ++static int cursor_texture_height; ++ ++#ifdef CONFIG_EGL ++static EGLSurface egl_surface; ++#endif ++ ++static void cocoa_gl_destroy_context(void *dg, QEMUGLContext ctx); ++ ++#endif ++ ++@interface QemuApplication : NSApplication ++@end ++ ++@implementation QemuApplication ++- (void)sendEvent:(NSEvent *)event ++{ ++ COCOA_DEBUG("QemuApplication: sendEvent\n"); ++ if (![[appController cocoaView] handleEvent:event]) { ++ [super sendEvent: event]; ++ } ++} ++@end ++ ++/* ++ * The startup process for the OSX/Cocoa UI is complicated, because ++ * OSX insists that the UI runs on the initial main thread, and so we ++ * need to start a second thread which runs the vl.c qemu_main(): ++ * ++ * Initial thread: 2nd thread: ++ * in main(): ++ * create qemu-main thread ++ * wait on display_init semaphore ++ * call qemu_main() ++ * ... ++ * in cocoa_display_init(): ++ * post the display_init semaphore ++ * wait on app_started semaphore ++ * create application, menus, etc ++ * enter OSX run loop ++ * in applicationDidFinishLaunching: ++ * post app_started semaphore ++ * tell main thread to fullscreen if needed ++ * [...] ++ * run qemu main-loop ++ * ++ * We do this in two stages so that we don't do the creation of the ++ * GUI application menus and so on for command line options like --help ++ * where we want to just print text to stdout and exit immediately. ++ */ ++ ++static void *call_qemu_main(void *opaque) ++{ ++ int status; ++ ++ COCOA_DEBUG("Second thread: calling qemu_main()\n"); ++ status = qemu_main(gArgc, gArgv, *_NSGetEnviron()); ++ COCOA_DEBUG("Second thread: qemu_main() returned, exiting\n"); ++ CGImageRelease(screen.cursor_cgimage); ++#ifdef CONFIG_OPENGL ++ g_free(dgs); ++ qemu_gl_fini_shader(gls); ++ if (view_ctx) { ++ cocoa_gl_destroy_context(NULL, view_ctx); ++ } ++ if (appController) { ++ [appController release]; ++ } ++#endif ++ exit(status); ++} ++ ++int main (int argc, const char * argv[]) { ++ QemuThread thread; ++ ++ COCOA_DEBUG("Entered main()\n"); ++ gArgc = argc; ++ gArgv = (char **)argv; ++ ++ qemu_sem_init(&display_init_sem, 0); ++ qemu_sem_init(&app_started_sem, 0); ++ ++ qemu_thread_create(&thread, "qemu_main", call_qemu_main, ++ NULL, QEMU_THREAD_DETACHED); ++ ++ qemu_mutex_init(&screen.draw_mutex); ++ ++ COCOA_DEBUG("Main thread: waiting for display_init_sem\n"); ++ qemu_sem_wait(&display_init_sem); ++ COCOA_DEBUG("Main thread: initializing app\n"); ++ ++ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; ++ ++ // Pull this console process up to being a fully-fledged graphical ++ // app with a menubar and Dock icon ++ ProcessSerialNumber psn = { 0, kCurrentProcess }; ++ TransformProcessType(&psn, kProcessTransformToForegroundApplication); ++ ++ [QemuApplication sharedApplication]; ++ ++ // Create an Application controller ++ appController = [[QemuCocoaAppController alloc] initWithStartedSem:&app_started_sem ++ screen:&screen]; ++ [NSApp setDelegate:appController]; ++ ++ // Start the main event loop ++ COCOA_DEBUG("Main thread: entering OSX run loop\n"); ++ [NSApp run]; ++ COCOA_DEBUG("Main thread: left OSX run loop, exiting\n"); ++ ++ [pool release]; ++ ++ return 0; ++} ++ ++ ++ ++#pragma mark qemu ++static void cocoa_update(DisplayChangeListener *dcl, ++ int x, int y, int w, int h) ++{ ++ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; ++ DisplaySurface *updated = screen.surface; ++ ++ COCOA_DEBUG("qemu_cocoa: cocoa_update\n"); ++ ++ dispatch_async(dispatch_get_main_queue(), ^{ ++ qemu_mutex_lock(&screen.draw_mutex); ++ if (updated != screen.surface) { ++ qemu_mutex_unlock(&screen.draw_mutex); ++ return; ++ } ++ int full_height = surface_height(screen.surface); ++ qemu_mutex_unlock(&screen.draw_mutex); ++ ++ CGFloat d = [[appController cocoaView] frame].size.height / full_height; ++ NSRect rect = NSMakeRect(x * d, (full_height - y - h) * d, w * d, h * d); ++ [[appController cocoaView] setNeedsDisplayInRect:rect]; ++ }); ++ ++ [pool release]; ++} ++ ++static void cocoa_switch(DisplayChangeListener *dcl, ++ DisplaySurface *new_surface) ++{ ++ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; ++ static bool updating_screen; ++ ++ COCOA_DEBUG("qemu_cocoa: cocoa_switch\n"); ++ ++ [[appController cocoaView] updateUIInfo]; ++ ++ qemu_mutex_lock(&screen.draw_mutex); ++ screen.surface = new_surface; ++ if (!updating_screen) { ++ updating_screen = true; ++ ++ dispatch_async(dispatch_get_main_queue(), ^{ ++ qemu_mutex_lock(&screen.draw_mutex); ++ updating_screen = false; ++ int w = surface_width(screen.surface); ++ int h = surface_height(screen.surface); ++ qemu_mutex_unlock(&screen.draw_mutex); ++ ++ [[appController cocoaView] updateScreenWidth:w height:h]; ++ }); ++ } ++ qemu_mutex_unlock(&screen.draw_mutex); ++ [pool release]; ++} ++ ++static void cocoa_refresh(DisplayChangeListener *dcl) ++{ ++ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; ++ ++ COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n"); ++ graphic_hw_update(NULL); ++ ++ if (qemu_input_is_absolute()) { ++ dispatch_async(dispatch_get_main_queue(), ^{ ++ if (![[appController cocoaView] isAbsoluteEnabled]) { ++ if ([[appController cocoaView] isMouseGrabbed]) { ++ [[appController cocoaView] ungrabMouse]; ++ } ++ } ++ [[appController cocoaView] setAbsoluteEnabled:YES]; ++ }); ++ } ++ [pool release]; ++} ++ ++static void cocoa_mouse_set(DisplayChangeListener *dcl, int x, int y, int on) ++{ ++ qemu_mutex_lock(&screen.draw_mutex); ++ int full_height = surface_height(screen.surface); ++ size_t cursor_width = CGImageGetWidth(screen.cursor_cgimage); ++ size_t cursor_height = CGImageGetHeight(screen.cursor_cgimage); ++ int old_x = screen.mouse_x; ++ int old_y = screen.mouse_y; ++ int old_on = screen.mouse_on; ++ screen.mouse_x = x; ++ screen.mouse_y = y; ++ screen.mouse_on = on; ++ qemu_mutex_unlock(&screen.draw_mutex); ++ ++ dispatch_async(dispatch_get_main_queue(), ^{ ++ if (old_on) { ++ [[appController cocoaView] setNeedsDisplayForCursorX:old_x ++ y:old_y ++ width:cursor_width ++ height:cursor_height ++ screenHeight:full_height]; ++ } ++ ++ if (on) { ++ [[appController cocoaView] setNeedsDisplayForCursorX:x ++ y:y ++ width:cursor_width ++ height:cursor_height ++ screenHeight:full_height]; ++ } ++ }); ++} ++ ++static void cocoa_cursor_define(DisplayChangeListener *dcl, QEMUCursor *cursor) ++{ ++ int width = cursor->width; ++ int height = cursor->height; ++ ++ CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData( ++ NULL, ++ cursor->data, ++ width * height * 4, ++ NULL ++ ); ++ ++ CGImageRef imageRef = CGImageCreate( ++ width, //width ++ height, //height ++ 8, //bitsPerComponent ++ 32, //bitsPerPixel ++ width * 4, //bytesPerRow ++ CGColorSpaceCreateWithName(kCGColorSpaceSRGB), //colorspace ++ kCGBitmapByteOrder32Little | kCGImageAlphaFirst, //bitmapInfo ++ dataProviderRef, //provider ++ NULL, //decode ++ 0, //interpolate ++ kCGRenderingIntentDefault //intent ++ ); ++ ++ qemu_mutex_lock(&screen.draw_mutex); ++ int full_height = surface_height(screen.surface); ++ int x = screen.mouse_x; ++ int y = screen.mouse_y; ++ int on = screen.mouse_on; ++ size_t old_width; ++ size_t old_height; ++ if (screen.cursor_cgimage) { ++ old_width = CGImageGetWidth(screen.cursor_cgimage); ++ old_height = CGImageGetHeight(screen.cursor_cgimage); ++ } else { ++ old_width = 0; ++ old_height = 0; ++ } ++ screen.cursor_cgimage = CGImageCreateCopy(imageRef); ++ qemu_mutex_unlock(&screen.draw_mutex); ++ ++ CGImageRelease(imageRef); ++ CGDataProviderRelease(dataProviderRef); ++ ++ if (on) { ++ dispatch_async(dispatch_get_main_queue(), ^{ ++ CGFloat d = [[appController cocoaView] frame].size.height / full_height; ++ NSRect rect; ++ ++ rect.origin.x = d * x; ++ rect.origin.y = d * (full_height - y - old_height); ++ rect.size.width = d * old_width; ++ rect.size.height = d * old_height; ++ [[appController cocoaView] setNeedsDisplayInRect:rect]; ++ ++ rect.origin.x = d * x; ++ rect.origin.y = d * (full_height - y - height); ++ rect.size.width = d * width; ++ rect.size.height = d * height; ++ [[appController cocoaView] setNeedsDisplayInRect:rect]; ++ }); ++ } ++} ++ ++static const DisplayChangeListenerOps dcl_ops = { ++ .dpy_name = "cocoa", ++ .dpy_gfx_update = cocoa_update, ++ .dpy_gfx_switch = cocoa_switch, ++ .dpy_refresh = cocoa_refresh, ++ .dpy_mouse_set = cocoa_mouse_set, ++ .dpy_cursor_define = cocoa_cursor_define, ++}; ++ ++#ifdef CONFIG_OPENGL ++ ++static void with_view_ctx(CodeBlock block) ++{ ++#ifdef CONFIG_EGL ++ if (egl_surface) { ++ eglMakeCurrent(qemu_egl_display, egl_surface, egl_surface, view_ctx); ++ block(); ++ return; ++ } ++#endif ++ ++#pragma clang diagnostic push ++#pragma clang diagnostic ignored "-Wdeprecated-declarations" ++ [(NSOpenGLContext *)view_ctx lock]; ++ [(NSOpenGLContext *)view_ctx makeCurrentContext]; ++ block(); ++ [(NSOpenGLContext *)view_ctx unlock]; ++#pragma clang diagnostic pop ++} ++ ++#pragma clang diagnostic push ++#pragma clang diagnostic ignored "-Wdeprecated-declarations" ++static NSOpenGLContext *cocoa_gl_create_context_ns(NSOpenGLContext *share_context, ++ int bpp) ++{ ++ NSOpenGLPixelFormatAttribute attributes[] = { ++ NSOpenGLPFAOpenGLProfile, ++ NSOpenGLProfileVersion4_1Core, ++ NSOpenGLPFAColorSize, ++ bpp, ++ NSOpenGLPFADoubleBuffer, ++ 0, ++ }; ++ NSOpenGLPixelFormat *format; ++ NSOpenGLContext *ctx; ++ ++ format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; ++ ctx = [[NSOpenGLContext alloc] initWithFormat:format shareContext:share_context]; ++ [format release]; ++ ++ [ctx retain]; ++ dispatch_async(dispatch_get_main_queue(), ^{ ++ [ctx setView:[appController cocoaView]]; ++ [ctx release]; ++ }); ++ ++ return (QEMUGLContext)ctx; ++} ++#pragma clang diagnostic pop ++ ++static int cocoa_gl_make_context_current(void *dg, QEMUGLContext ctx) ++{ ++#ifdef CONFIG_EGL ++ if (egl_surface) ++ return eglMakeCurrent(qemu_egl_display, egl_surface, egl_surface, ctx); ++#endif ++ ++#pragma clang diagnostic push ++#pragma clang diagnostic ignored "-Wdeprecated-declarations" ++ [(NSOpenGLContext *)ctx makeCurrentContext]; ++#pragma clang diagnostic pop ++ ++ return 0; ++} ++ ++static QEMUGLContext cocoa_gl_create_context(void *dg, QEMUGLParams *params) ++{ ++#ifdef CONFIG_EGL ++ if (egl_surface) { ++ eglMakeCurrent(qemu_egl_display, egl_surface, egl_surface, view_ctx); ++ return qemu_egl_create_context(dg, params); ++ } ++#endif ++ ++ int bpp = PIXMAN_FORMAT_BPP(surface_format(screen.surface)); ++ return cocoa_gl_create_context_ns(view_ctx, bpp); ++} ++ ++static void cocoa_gl_destroy_context(void *dg, QEMUGLContext ctx) ++{ ++#ifdef CONFIG_EGL ++ if (egl_surface) { ++ eglDestroyContext(qemu_egl_display, ctx); ++ return; ++ } ++#endif ++ ++#pragma clang diagnostic push ++#pragma clang diagnostic ignored "-Wdeprecated-declarations" ++ [(NSOpenGLContext *)ctx release]; ++#pragma clang diagnostic pop ++} ++ ++static void cocoa_gl_flush() ++{ ++#ifdef CONFIG_EGL ++ if (egl_surface) { ++ eglSwapBuffers(qemu_egl_display, egl_surface); ++ return; ++ } ++#endif ++ ++#pragma clang diagnostic push ++#pragma clang diagnostic ignored "-Wdeprecated-declarations" ++ [[NSOpenGLContext currentContext] flushBuffer]; ++ ++ dispatch_async(dispatch_get_main_queue(), ^{ ++ [(NSOpenGLContext *)view_ctx update]; ++ }); ++#pragma clang diagnostic pop ++} ++ ++static void cocoa_scanout_disable(DisplayGL *dg) ++{ ++ if (!dg->scanout_id) { ++ return; ++ } ++ ++ dg->scanout_id = 0; ++ ++ if (screen.surface) { ++ surface_gl_destroy_texture(gls, screen.surface); ++ surface_gl_create_texture(gls, screen.surface); ++ } ++} ++ ++static void cocoa_gl_render_cursor() ++{ ++ if (!screen.mouse_on) { ++ return; ++ } ++ ++ QemuCocoaView *cocoaView = [appController cocoaView]; ++ NSSize size = [cocoaView convertSizeToBacking:[cocoaView frame].size]; ++ int full_height = surface_height(screen.surface); ++ CGFloat d = size.height / full_height; ++ ++ glViewport( ++ d * screen.mouse_x, ++ d * (full_height - screen.mouse_y - cursor_texture_height), ++ d * cursor_texture_width, ++ d * cursor_texture_height ++ ); ++ glBindTexture(GL_TEXTURE_2D, cursor_texture); ++ glEnable(GL_BLEND); ++ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); ++ qemu_gl_run_texture_blit(gls, false); ++ glDisable(GL_BLEND); ++} ++ ++static void cocoa_gl_render_surface(DisplayGL *dg) ++{ ++ cocoa_scanout_disable(dg); ++ ++ QemuCocoaView *cocoaView = [appController cocoaView]; ++ NSSize size = [cocoaView convertSizeToBacking:[cocoaView frame].size]; ++ ++ surface_gl_setup_viewport(gls, screen.surface, size.width, size.height); ++ glBindTexture(GL_TEXTURE_2D, screen.surface->texture); ++ surface_gl_render_texture(gls, screen.surface); ++ ++ cocoa_gl_render_cursor(); ++ ++ cocoa_gl_flush(); ++} ++ ++static void cocoa_gl_update(DisplayChangeListener *dcl, ++ int x, int y, int w, int h) ++{ ++ with_view_ctx(^{ ++ surface_gl_update_texture(gls, screen.surface, x, y, w, h); ++ dgs[qemu_console_get_index(dcl->con)].surface_dirty = true; ++ }); ++} ++ ++static void cocoa_gl_switch(DisplayChangeListener *dcl, ++ DisplaySurface *new_surface) ++{ ++ cocoa_switch(dcl, new_surface); ++ ++ with_view_ctx(^{ ++ surface_gl_create_texture(gls, new_surface); ++ }); ++} ++ ++static void cocoa_gl_refresh(DisplayChangeListener *dcl) ++{ ++ cocoa_refresh(dcl); ++ ++ with_view_ctx(^{ ++ DisplayGL *dg = dgs + qemu_console_get_index(dcl->con); ++ ++ if (dg->surface_dirty && screen.surface) { ++ dg->surface_dirty = false; ++ cocoa_gl_render_surface(dg); ++ } ++ }); ++} ++ ++static bool cocoa_gl_scanout_get_enabled(void *dg) ++{ ++ return ((DisplayGL *)dg)->scanout_id != 0; ++} ++ ++static void cocoa_gl_scanout_disable(void *dg) ++{ ++ with_view_ctx(^{ ++ cocoa_scanout_disable((DisplayGL *)dg); ++ }); ++} ++ ++static void cocoa_gl_scanout_texture(void *dg, ++ uint32_t backing_id, ++ DisplayGLTextureBorrower backing_borrow, ++ uint32_t x, uint32_t y, ++ uint32_t w, uint32_t h) ++{ ++ ((DisplayGL *)dg)->scanout_id = backing_id; ++ ((DisplayGL *)dg)->scanout_borrow = backing_borrow; ++} ++ ++static void cocoa_gl_scanout_flush(DisplayChangeListener *dcl, ++ uint32_t x, uint32_t y, uint32_t w, uint32_t h) ++{ ++ DisplayGL *dg = dgs + qemu_console_get_index(dcl->con); ++ bool y0_top; ++ ++ if (!dg->scanout_id) { ++ return; ++ } ++ ++ GLint texture = dg->scanout_borrow(dg->scanout_id, &y0_top, NULL, NULL); ++ if (!texture) { ++ return; ++ } ++ ++ with_view_ctx(^{ ++ QemuCocoaView *cocoaView = [appController cocoaView]; ++ NSSize size = [cocoaView convertSizeToBacking:[cocoaView frame].size]; ++ ++ glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0); ++ glViewport(0, 0, size.width, size.height); ++ glBindTexture(GL_TEXTURE_2D, texture); ++ qemu_gl_run_texture_blit(gls, y0_top); ++ ++ cocoa_gl_render_cursor(); ++ ++ cocoa_gl_flush(); ++ }); ++} ++ ++static void cocoa_gl_mouse_set(DisplayChangeListener *dcl, int x, int y, int on) ++{ ++ screen.mouse_x = x; ++ screen.mouse_y = y; ++ screen.mouse_on = on; ++ ++ DisplayGL *dg = dgs + qemu_console_get_index(dcl->con); ++ ++ if (dg->scanout_id) { ++ cocoa_gl_scanout_flush(dcl, 0, 0, 0, 0); ++ } else { ++ with_view_ctx(^{ ++ cocoa_gl_render_surface(dg); ++ }); ++ } ++} ++ ++static void cocoa_gl_cursor_define(DisplayChangeListener *dcl, QEMUCursor *cursor) ++{ ++ cursor_texture_width = cursor->width; ++ cursor_texture_height = cursor->height; ++ ++ with_view_ctx(^{ ++ glBindTexture(GL_TEXTURE_2D, cursor_texture); ++ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, cursor->width); ++ glTexImage2D(GL_TEXTURE_2D, 0, ++ epoxy_is_desktop_gl() ? GL_RGBA : GL_BGRA, ++ cursor->width, ++ cursor->height, ++ 0, GL_BGRA, GL_UNSIGNED_BYTE, ++ cursor->data); ++ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); ++ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); ++ }); ++} ++ ++static const DisplayGLOps dg_ops = { ++ .dpy_gl_ctx_create = cocoa_gl_create_context, ++ .dpy_gl_ctx_destroy = cocoa_gl_destroy_context, ++ .dpy_gl_ctx_make_current = cocoa_gl_make_context_current, ++ .dpy_gl_scanout_get_enabled = cocoa_gl_scanout_get_enabled, ++ .dpy_gl_scanout_disable = cocoa_gl_scanout_disable, ++ .dpy_gl_scanout_texture = cocoa_gl_scanout_texture, ++}; ++ ++static const DisplayChangeListenerOps dcl_gl_ops = { ++ .dpy_name = "cocoa-gl", ++ .dpy_gfx_update = cocoa_gl_update, ++ .dpy_gfx_switch = cocoa_gl_switch, ++ .dpy_gfx_check_format = console_gl_check_format, ++ .dpy_refresh = cocoa_gl_refresh, ++ .dpy_mouse_set = cocoa_gl_mouse_set, ++ .dpy_cursor_define = cocoa_gl_cursor_define, ++ ++ .dpy_gl_update = cocoa_gl_scanout_flush, ++}; ++ ++#endif ++ ++static void cocoa_display_early_init(DisplayOptions *o) ++{ ++ assert(o->type == DISPLAY_TYPE_COCOA); ++ if (o->has_gl && o->gl) { ++ display_opengl = 1; ++ } ++} ++ ++static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts) ++{ ++ COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n"); ++ ++ screen.cursor_show = opts->has_show_cursor && opts->show_cursor; ++ ++ /* Tell main thread to go ahead and create the app and enter the run loop */ ++ qemu_sem_post(&display_init_sem); ++ qemu_sem_wait(&app_started_sem); ++ COCOA_DEBUG("cocoa_display_init: app start completed\n"); ++ ++ /* if fullscreen mode is to be used */ ++ if (opts->has_full_screen && opts->full_screen) { ++ dispatch_async(dispatch_get_main_queue(), ^{ ++ [[[appController cocoaView] window] toggleFullScreen: nil]; ++ }); ++ } ++ ++ if (display_opengl) { ++#ifdef CONFIG_OPENGL ++ unsigned int console_count = 0; ++ while (qemu_console_lookup_by_index(console_count)) { ++ console_count++; ++ } ++ ++ dgs = g_new0(DisplayGL, console_count); ++ ++ for (unsigned int index = 0; index < console_count; index++) { ++ QemuConsole *con = qemu_console_lookup_by_index(index); ++ console_set_displayglcontext(con, dgs + index); ++ } ++ ++ if (opts->gl == DISPLAYGL_MODE_ES) { ++#ifdef CONFIG_EGL ++ qemu_egl_init_dpy_cocoa(DISPLAYGL_MODE_ES); ++ view_ctx = qemu_egl_init_ctx(); ++ dispatch_sync(dispatch_get_main_queue(), ^{ ++ CALayer *layer = [[appController cocoaView] layer]; ++ egl_surface = qemu_egl_init_surface(view_ctx, layer); ++ }); ++#else ++ error_report("OpenGLES without EGL is not supported - exiting"); ++ exit(1); ++#endif ++ } else { ++ view_ctx = cocoa_gl_create_context_ns(nil, 32); ++#ifdef CONFIG_EGL ++ egl_surface = EGL_NO_SURFACE; ++#endif ++ cocoa_gl_make_context_current(NULL, view_ctx); ++ } ++ ++ gls = qemu_gl_init_shader(); ++ glGenTextures(1, &cursor_texture); ++ ++ // register vga output callbacks ++ screen.dcl.ops = &dcl_gl_ops; ++ ++ register_displayglops(&dg_ops); ++#else ++ error_report("OpenGL is not enabled - exiting"); ++ exit(1); ++#endif ++ } else { ++ // register vga output callbacks ++ screen.dcl.ops = &dcl_ops; ++ } ++ ++ register_displaychangelistener(&screen.dcl); ++ qatomic_store_release(&screen.inited, true); ++} ++ ++static QemuDisplay qemu_display_cocoa = { ++ .type = DISPLAY_TYPE_COCOA, ++ .early_init = cocoa_display_early_init, ++ .init = cocoa_display_init, ++}; ++ ++static void register_cocoa(void) ++{ ++ qemu_display_register(&qemu_display_cocoa); ++} ++ ++type_init(register_cocoa); +diff --git a/ui/cocoa/view.m b/ui/cocoa/view.m +new file mode 100644 +index 0000000000..7adce1b3f5 +--- /dev/null ++++ b/ui/cocoa/view.m +@@ -0,0 +1,970 @@ ++/* ++ * QEMU Cocoa CG display driver ++ * ++ * Copyright (c) 2008 Mike Kronenberg ++ * ++ * 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 "qemu/osdep.h" ++ ++#include "ui/cocoa.h" ++#include "ui/input.h" ++#include "sysemu/sysemu.h" ++#include "qemu/main-loop.h" ++#include "qemu/error-report.h" ++#include ++ ++#define cgrect(nsrect) (*(CGRect *)&(nsrect)) ++ ++// Mac to QKeyCode conversion ++static const int mac_to_qkeycode_map[] = { ++ [kVK_ANSI_A] = Q_KEY_CODE_A, ++ [kVK_ANSI_B] = Q_KEY_CODE_B, ++ [kVK_ANSI_C] = Q_KEY_CODE_C, ++ [kVK_ANSI_D] = Q_KEY_CODE_D, ++ [kVK_ANSI_E] = Q_KEY_CODE_E, ++ [kVK_ANSI_F] = Q_KEY_CODE_F, ++ [kVK_ANSI_G] = Q_KEY_CODE_G, ++ [kVK_ANSI_H] = Q_KEY_CODE_H, ++ [kVK_ANSI_I] = Q_KEY_CODE_I, ++ [kVK_ANSI_J] = Q_KEY_CODE_J, ++ [kVK_ANSI_K] = Q_KEY_CODE_K, ++ [kVK_ANSI_L] = Q_KEY_CODE_L, ++ [kVK_ANSI_M] = Q_KEY_CODE_M, ++ [kVK_ANSI_N] = Q_KEY_CODE_N, ++ [kVK_ANSI_O] = Q_KEY_CODE_O, ++ [kVK_ANSI_P] = Q_KEY_CODE_P, ++ [kVK_ANSI_Q] = Q_KEY_CODE_Q, ++ [kVK_ANSI_R] = Q_KEY_CODE_R, ++ [kVK_ANSI_S] = Q_KEY_CODE_S, ++ [kVK_ANSI_T] = Q_KEY_CODE_T, ++ [kVK_ANSI_U] = Q_KEY_CODE_U, ++ [kVK_ANSI_V] = Q_KEY_CODE_V, ++ [kVK_ANSI_W] = Q_KEY_CODE_W, ++ [kVK_ANSI_X] = Q_KEY_CODE_X, ++ [kVK_ANSI_Y] = Q_KEY_CODE_Y, ++ [kVK_ANSI_Z] = Q_KEY_CODE_Z, ++ ++ [kVK_ANSI_0] = Q_KEY_CODE_0, ++ [kVK_ANSI_1] = Q_KEY_CODE_1, ++ [kVK_ANSI_2] = Q_KEY_CODE_2, ++ [kVK_ANSI_3] = Q_KEY_CODE_3, ++ [kVK_ANSI_4] = Q_KEY_CODE_4, ++ [kVK_ANSI_5] = Q_KEY_CODE_5, ++ [kVK_ANSI_6] = Q_KEY_CODE_6, ++ [kVK_ANSI_7] = Q_KEY_CODE_7, ++ [kVK_ANSI_8] = Q_KEY_CODE_8, ++ [kVK_ANSI_9] = Q_KEY_CODE_9, ++ ++ [kVK_ANSI_Grave] = Q_KEY_CODE_GRAVE_ACCENT, ++ [kVK_ANSI_Minus] = Q_KEY_CODE_MINUS, ++ [kVK_ANSI_Equal] = Q_KEY_CODE_EQUAL, ++ [kVK_Delete] = Q_KEY_CODE_BACKSPACE, ++ [kVK_CapsLock] = Q_KEY_CODE_CAPS_LOCK, ++ [kVK_Tab] = Q_KEY_CODE_TAB, ++ [kVK_Return] = Q_KEY_CODE_RET, ++ [kVK_ANSI_LeftBracket] = Q_KEY_CODE_BRACKET_LEFT, ++ [kVK_ANSI_RightBracket] = Q_KEY_CODE_BRACKET_RIGHT, ++ [kVK_ANSI_Backslash] = Q_KEY_CODE_BACKSLASH, ++ [kVK_ANSI_Semicolon] = Q_KEY_CODE_SEMICOLON, ++ [kVK_ANSI_Quote] = Q_KEY_CODE_APOSTROPHE, ++ [kVK_ANSI_Comma] = Q_KEY_CODE_COMMA, ++ [kVK_ANSI_Period] = Q_KEY_CODE_DOT, ++ [kVK_ANSI_Slash] = Q_KEY_CODE_SLASH, ++ [kVK_Space] = Q_KEY_CODE_SPC, ++ ++ [kVK_ANSI_Keypad0] = Q_KEY_CODE_KP_0, ++ [kVK_ANSI_Keypad1] = Q_KEY_CODE_KP_1, ++ [kVK_ANSI_Keypad2] = Q_KEY_CODE_KP_2, ++ [kVK_ANSI_Keypad3] = Q_KEY_CODE_KP_3, ++ [kVK_ANSI_Keypad4] = Q_KEY_CODE_KP_4, ++ [kVK_ANSI_Keypad5] = Q_KEY_CODE_KP_5, ++ [kVK_ANSI_Keypad6] = Q_KEY_CODE_KP_6, ++ [kVK_ANSI_Keypad7] = Q_KEY_CODE_KP_7, ++ [kVK_ANSI_Keypad8] = Q_KEY_CODE_KP_8, ++ [kVK_ANSI_Keypad9] = Q_KEY_CODE_KP_9, ++ [kVK_ANSI_KeypadDecimal] = Q_KEY_CODE_KP_DECIMAL, ++ [kVK_ANSI_KeypadEnter] = Q_KEY_CODE_KP_ENTER, ++ [kVK_ANSI_KeypadPlus] = Q_KEY_CODE_KP_ADD, ++ [kVK_ANSI_KeypadMinus] = Q_KEY_CODE_KP_SUBTRACT, ++ [kVK_ANSI_KeypadMultiply] = Q_KEY_CODE_KP_MULTIPLY, ++ [kVK_ANSI_KeypadDivide] = Q_KEY_CODE_KP_DIVIDE, ++ [kVK_ANSI_KeypadEquals] = Q_KEY_CODE_KP_EQUALS, ++ [kVK_ANSI_KeypadClear] = Q_KEY_CODE_NUM_LOCK, ++ ++ [kVK_UpArrow] = Q_KEY_CODE_UP, ++ [kVK_DownArrow] = Q_KEY_CODE_DOWN, ++ [kVK_LeftArrow] = Q_KEY_CODE_LEFT, ++ [kVK_RightArrow] = Q_KEY_CODE_RIGHT, ++ ++ [kVK_Help] = Q_KEY_CODE_INSERT, ++ [kVK_Home] = Q_KEY_CODE_HOME, ++ [kVK_PageUp] = Q_KEY_CODE_PGUP, ++ [kVK_PageDown] = Q_KEY_CODE_PGDN, ++ [kVK_End] = Q_KEY_CODE_END, ++ [kVK_ForwardDelete] = Q_KEY_CODE_DELETE, ++ ++ [kVK_Escape] = Q_KEY_CODE_ESC, ++ ++ /* The Power key can't be used directly because the operating system uses ++ * it. This key can be emulated by using it in place of another key such as ++ * F1. Don't forget to disable the real key binding. ++ */ ++ /* [kVK_F1] = Q_KEY_CODE_POWER, */ ++ ++ [kVK_F1] = Q_KEY_CODE_F1, ++ [kVK_F2] = Q_KEY_CODE_F2, ++ [kVK_F3] = Q_KEY_CODE_F3, ++ [kVK_F4] = Q_KEY_CODE_F4, ++ [kVK_F5] = Q_KEY_CODE_F5, ++ [kVK_F6] = Q_KEY_CODE_F6, ++ [kVK_F7] = Q_KEY_CODE_F7, ++ [kVK_F8] = Q_KEY_CODE_F8, ++ [kVK_F9] = Q_KEY_CODE_F9, ++ [kVK_F10] = Q_KEY_CODE_F10, ++ [kVK_F11] = Q_KEY_CODE_F11, ++ [kVK_F12] = Q_KEY_CODE_F12, ++ [kVK_F13] = Q_KEY_CODE_PRINT, ++ [kVK_F14] = Q_KEY_CODE_SCROLL_LOCK, ++ [kVK_F15] = Q_KEY_CODE_PAUSE, ++ ++ // JIS keyboards only ++ [kVK_JIS_Yen] = Q_KEY_CODE_YEN, ++ [kVK_JIS_Underscore] = Q_KEY_CODE_RO, ++ [kVK_JIS_KeypadComma] = Q_KEY_CODE_KP_COMMA, ++ [kVK_JIS_Eisu] = Q_KEY_CODE_MUHENKAN, ++ [kVK_JIS_Kana] = Q_KEY_CODE_HENKAN, ++ ++ /* ++ * The eject and volume keys can't be used here because they are handled at ++ * a lower level than what an Application can see. ++ */ ++}; ++ ++static int cocoa_keycode_to_qemu(int keycode) ++{ ++ if (ARRAY_SIZE(mac_to_qkeycode_map) <= keycode) { ++ error_report("(cocoa) warning unknown keycode 0x%x", keycode); ++ return 0; ++ } ++ return mac_to_qkeycode_map[keycode]; ++} ++static CGRect compute_cursor_clip_rect(int screen_height, ++ int given_mouse_x, int given_mouse_y, ++ int cursor_width, int cursor_height) ++{ ++ CGRect rect; ++ ++ rect.origin.x = MAX(0, -given_mouse_x); ++ rect.origin.y = 0; ++ rect.size.width = MIN(cursor_width, cursor_width + given_mouse_x); ++ rect.size.height = cursor_height - rect.origin.x; ++ ++ return rect; ++} ++ ++@implementation QemuCocoaView ++- (id)initWithFrame:(NSRect)frameRect ++ screen:(QEMUScreen *)given_screen ++{ ++ COCOA_DEBUG("QemuCocoaView: initWithFrame\n"); ++ ++ self = [super initWithFrame:frameRect]; ++ if (self) { ++ ++ screen = given_screen; ++ screen_width = frameRect.size.width; ++ screen_height = frameRect.size.height; ++ kbd = qkbd_state_init(screen->dcl.con); ++ ++ /* Used for displaying pause on the screen */ ++ pauseLabel = [NSTextField new]; ++ [pauseLabel setBezeled:YES]; ++ [pauseLabel setDrawsBackground:YES]; ++ [pauseLabel setBackgroundColor: [NSColor whiteColor]]; ++ [pauseLabel setEditable:NO]; ++ [pauseLabel setSelectable:NO]; ++ [pauseLabel setStringValue: @"Paused"]; ++ [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]]; ++ [pauseLabel setTextColor: [NSColor blackColor]]; ++ [pauseLabel sizeToFit]; ++ ++ } ++ return self; ++} ++ ++- (void) dealloc ++{ ++ COCOA_DEBUG("QemuCocoaView: dealloc\n"); ++ ++ if (pauseLabel) { ++ [pauseLabel release]; ++ } ++ ++ qkbd_state_free(kbd); ++ [super dealloc]; ++} ++ ++- (BOOL) isOpaque ++{ ++ return YES; ++} ++ ++- (void) removeTrackingRect ++{ ++ if (trackingArea) { ++ [self removeTrackingArea:trackingArea]; ++ [trackingArea release]; ++ trackingArea = nil; ++ } ++} ++ ++- (void) frameUpdated ++{ ++ [self removeTrackingRect]; ++ ++ if ([self window]) { ++ NSTrackingAreaOptions options = NSTrackingActiveInKeyWindow | ++ NSTrackingMouseEnteredAndExited | ++ NSTrackingMouseMoved; ++ trackingArea = [[NSTrackingArea alloc] initWithRect:[self frame] ++ options:options ++ owner:self ++ userInfo:nil]; ++ [self addTrackingArea:trackingArea]; ++ [self updateUIInfo]; ++ } ++} ++ ++- (void) viewDidMoveToWindow ++{ ++ [self resizeWindow]; ++ [self frameUpdated]; ++} ++ ++- (void) viewWillMoveToWindow:(NSWindow *)newWindow ++{ ++ [self removeTrackingRect]; ++} ++ ++- (void) hideCursor ++{ ++ if (screen->cursor_show) { ++ return; ++ } ++ [NSCursor hide]; ++} ++ ++- (void) unhideCursor ++{ ++ if (screen->cursor_show) { ++ return; ++ } ++ [NSCursor unhide]; ++} ++ ++- (CGRect) convertCursorClipRectToDraw:(CGRect)rect ++ screenHeight:(int)given_screen_height ++ mouseX:(int)mouse_x ++ mouseY:(int)mouse_y ++{ ++ CGFloat d = [self frame].size.height / (CGFloat)given_screen_height; ++ ++ rect.origin.x = (rect.origin.x + mouse_x) * d; ++ rect.origin.y = (given_screen_height - rect.origin.y - mouse_y - rect.size.height) * d; ++ rect.size.width *= d; ++ rect.size.height *= d; ++ ++ return rect; ++} ++ ++- (void) drawRect:(NSRect) rect ++{ ++ COCOA_DEBUG("QemuCocoaView: drawRect\n"); ++ ++#ifdef CONFIG_OPENGL ++ if (display_opengl) { ++ return; ++ } ++#endif ++ ++ // get CoreGraphic context ++ CGContextRef viewContextRef = [[NSGraphicsContext currentContext] CGContext]; ++ ++ CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone); ++ CGContextSetShouldAntialias (viewContextRef, NO); ++ ++ qemu_mutex_lock(&screen->draw_mutex); ++ ++ // draw screen bitmap directly to Core Graphics context ++ if (!screen->surface) { ++ // Draw request before any guest device has set up a framebuffer: ++ // just draw an opaque black rectangle ++ CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0); ++ CGContextFillRect(viewContextRef, NSRectToCGRect(rect)); ++ } else { ++ int w = surface_width(screen->surface); ++ int h = surface_height(screen->surface); ++ int bitsPerPixel = PIXMAN_FORMAT_BPP(surface_format(screen->surface)); ++ int stride = surface_stride(screen->surface); ++ ++ CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData( ++ NULL, ++ surface_data(screen->surface), ++ stride * h, ++ NULL ++ ); ++ ++ CGImageRef imageRef = CGImageCreate( ++ w, //width ++ h, //height ++ DIV_ROUND_UP(bitsPerPixel, 8) * 2, //bitsPerComponent ++ bitsPerPixel, //bitsPerPixel ++ stride, //bytesPerRow ++ CGColorSpaceCreateWithName(kCGColorSpaceSRGB), //colorspace ++ kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, //bitmapInfo ++ dataProviderRef, //provider ++ NULL, //decode ++ 0, //interpolate ++ kCGRenderingIntentDefault //intent ++ ); ++ // selective drawing code (draws only dirty rectangles) (OS X >= 10.4) ++ const NSRect *rectList; ++ NSInteger rectCount; ++ int i; ++ CGImageRef clipImageRef; ++ CGRect clipRect; ++ CGFloat d = (CGFloat)h / [self frame].size.height; ++ ++ [self getRectsBeingDrawn:&rectList count:&rectCount]; ++ for (i = 0; i < rectCount; i++) { ++ clipRect.origin.x = rectList[i].origin.x * d; ++ clipRect.origin.y = (float)h - (rectList[i].origin.y + rectList[i].size.height) * d; ++ clipRect.size.width = rectList[i].size.width * d; ++ clipRect.size.height = rectList[i].size.height * d; ++ clipImageRef = CGImageCreateWithImageInRect( ++ imageRef, ++ clipRect ++ ); ++ CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef); ++ CGImageRelease (clipImageRef); ++ } ++ CGImageRelease (imageRef); ++ CGDataProviderRelease(dataProviderRef); ++ ++ if (screen->mouse_on) { ++ size_t cursor_width = CGImageGetWidth(screen->cursor_cgimage); ++ size_t cursor_height = CGImageGetHeight(screen->cursor_cgimage); ++ clipRect = compute_cursor_clip_rect(h, screen->mouse_x, screen->mouse_y, ++ cursor_width, ++ cursor_height); ++ CGRect drawRect = [self convertCursorClipRectToDraw:clipRect ++ screenHeight:h ++ mouseX:screen->mouse_x ++ mouseY:screen->mouse_y]; ++ clipImageRef = CGImageCreateWithImageInRect( ++ screen->cursor_cgimage, ++ clipRect ++ ); ++ CGContextDrawImage(viewContextRef, drawRect, clipImageRef); ++ CGImageRelease (clipImageRef); ++ } ++ } ++ ++ qemu_mutex_unlock(&screen->draw_mutex); ++} ++ ++- (NSSize) computeUnzoomedSize ++{ ++ CGFloat width = screen_width / [[self window] backingScaleFactor]; ++ CGFloat height = screen_height / [[self window] backingScaleFactor]; ++ ++ return NSMakeSize(width, height); ++} ++ ++- (NSSize) fixZoomedFullScreenSize:(NSSize)proposedSize ++{ ++ NSSize size; ++ ++ size.width = (CGFloat)screen_width * proposedSize.height; ++ size.height = (CGFloat)screen_height * proposedSize.width; ++ ++ if (size.width < size.height) { ++ size.width /= screen_height; ++ size.height = proposedSize.height; ++ } else { ++ size.width = proposedSize.width; ++ size.height /= screen_width; ++ } ++ ++ return size; ++} ++ ++- (void) resizeWindow ++{ ++ [[self window] setContentAspectRatio:NSMakeSize(screen_width, screen_height)]; ++ ++ if (([[self window] styleMask] & NSWindowStyleMaskResizable) == 0) { ++ [[self window] setContentSize:[self computeUnzoomedSize]]; ++ [[self window] center]; ++ } else if (([[self window] styleMask] & NSWindowStyleMaskFullScreen) != 0) { ++ [[self window] setContentSize:[self fixZoomedFullScreenSize:[[[self window] screen] frame].size]]; ++ [[self window] center]; ++ } ++} ++ ++- (void) updateUIInfo ++{ ++ NSSize frameSize; ++ QemuUIInfo info = {}; ++ ++ if (!qatomic_load_acquire(&screen->inited)) { ++ return; ++ } ++ ++ if ([self window]) { ++ NSDictionary *description = [[[self window] screen] deviceDescription]; ++ CGDirectDisplayID display = [[description objectForKey:@"NSScreenNumber"] unsignedIntValue]; ++ NSSize screenSize = [[[self window] screen] frame].size; ++ CGSize screenPhysicalSize = CGDisplayScreenSize(display); ++ CVDisplayLinkRef displayLink; ++ ++ if (([[self window] styleMask] & NSWindowStyleMaskFullScreen) == 0) { ++ frameSize = [self frame].size; ++ } else { ++ frameSize = screenSize; ++ } ++ ++ if (!CVDisplayLinkCreateWithCGDisplay(display, &displayLink)) { ++ CVTime period = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLink); ++ CVDisplayLinkRelease(displayLink); ++ if (!(period.flags & kCVTimeIsIndefinite)) { ++ update_displaychangelistener(&screen->dcl, ++ 1000 * period.timeValue / period.timeScale); ++ info.refresh_rate = (int64_t)1000 * period.timeScale / period.timeValue; ++ } ++ } ++ ++ info.width_mm = frameSize.width / screenSize.width * screenPhysicalSize.width; ++ info.height_mm = frameSize.height / screenSize.height * screenPhysicalSize.height; ++ } else { ++ frameSize = [self frame].size; ++ } ++ ++ NSSize frameBackingSize = [self convertSizeToBacking:frameSize]; ++ ++ info.width = frameBackingSize.width; ++ info.height = frameBackingSize.height; ++ ++ dpy_set_ui_info(screen->dcl.con, &info); ++} ++ ++- (void) updateScreenWidth:(int)w height:(int)h ++{ ++ COCOA_DEBUG("QemuCocoaView: updateScreenWidth:height:\n"); ++ ++ if (w != screen_width || h != screen_height) { ++ COCOA_DEBUG("updateScreenWidth:height: new size %d x %d\n", w, h); ++ screen_width = w; ++ screen_height = h; ++ [self resizeWindow]; ++ } ++} ++ ++- (void) toggleKey: (int)keycode { ++ qkbd_state_key_event(kbd, keycode, !qkbd_state_key_get(kbd, keycode)); ++} ++ ++// Does the work of sending input to the monitor ++- (void) handleMonitorInput:(NSEvent *)event ++{ ++ int keysym = 0; ++ int control_key = 0; ++ ++ // if the control key is down ++ if ([event modifierFlags] & NSEventModifierFlagControl) { ++ control_key = 1; ++ } ++ ++ /* translates Macintosh keycodes to QEMU's keysym */ ++ ++ int without_control_translation[] = { ++ [0 ... 0xff] = 0, // invalid key ++ ++ [kVK_UpArrow] = QEMU_KEY_UP, ++ [kVK_DownArrow] = QEMU_KEY_DOWN, ++ [kVK_RightArrow] = QEMU_KEY_RIGHT, ++ [kVK_LeftArrow] = QEMU_KEY_LEFT, ++ [kVK_Home] = QEMU_KEY_HOME, ++ [kVK_End] = QEMU_KEY_END, ++ [kVK_PageUp] = QEMU_KEY_PAGEUP, ++ [kVK_PageDown] = QEMU_KEY_PAGEDOWN, ++ [kVK_ForwardDelete] = QEMU_KEY_DELETE, ++ [kVK_Delete] = QEMU_KEY_BACKSPACE, ++ }; ++ ++ int with_control_translation[] = { ++ [0 ... 0xff] = 0, // invalid key ++ ++ [kVK_UpArrow] = QEMU_KEY_CTRL_UP, ++ [kVK_DownArrow] = QEMU_KEY_CTRL_DOWN, ++ [kVK_RightArrow] = QEMU_KEY_CTRL_RIGHT, ++ [kVK_LeftArrow] = QEMU_KEY_CTRL_LEFT, ++ [kVK_Home] = QEMU_KEY_CTRL_HOME, ++ [kVK_End] = QEMU_KEY_CTRL_END, ++ [kVK_PageUp] = QEMU_KEY_CTRL_PAGEUP, ++ [kVK_PageDown] = QEMU_KEY_CTRL_PAGEDOWN, ++ }; ++ ++ if (control_key != 0) { /* If the control key is being used */ ++ if ([event keyCode] < ARRAY_SIZE(with_control_translation)) { ++ keysym = with_control_translation[[event keyCode]]; ++ } ++ } else { ++ if ([event keyCode] < ARRAY_SIZE(without_control_translation)) { ++ keysym = without_control_translation[[event keyCode]]; ++ } ++ } ++ ++ // if not a key that needs translating ++ if (keysym == 0) { ++ NSString *ks = [event characters]; ++ if ([ks length] > 0) { ++ keysym = [ks characterAtIndex:0]; ++ } ++ } ++ ++ if (keysym) { ++ kbd_put_keysym(keysym); ++ } ++} ++ ++- (bool) handleEvent:(NSEvent *)event ++{ ++ if(!qatomic_read(&screen->inited)) { ++ /* ++ * Just let OSX have all events that arrive before ++ * applicationDidFinishLaunching. ++ * This avoids a deadlock on the iothread lock, which cocoa_display_init() ++ * will not drop until after the app_started_sem is posted. (In theory ++ * there should not be any such events, but OSX Catalina now emits some.) ++ */ ++ return false; ++ } ++ ++ qemu_mutex_lock_iothread(); ++ bool handled = [self handleEventLocked:event]; ++ qemu_mutex_unlock_iothread(); ++ return handled; ++} ++ ++- (bool) handleEventLocked:(NSEvent *)event ++{ ++ /* Return true if we handled the event, false if it should be given to OSX */ ++ COCOA_DEBUG("QemuCocoaView: handleEvent\n"); ++ int keycode = 0; ++ NSUInteger modifiers = [event modifierFlags]; ++ ++ /* ++ * Check -[NSEvent modifierFlags] here. ++ * ++ * There is a NSEventType for an event notifying the change of ++ * -[NSEvent modifierFlags], NSEventTypeFlagsChanged but these operations ++ * are performed for any events because a modifier state may change while ++ * the application is inactive (i.e. no events fire) and we don't want to ++ * wait for another modifier state change to detect such a change. ++ * ++ * NSEventModifierFlagCapsLock requires a special treatment. The other flags ++ * are handled in similar manners. ++ * ++ * NSEventModifierFlagCapsLock ++ * --------------------------- ++ * ++ * If CapsLock state is changed, "up" and "down" events will be fired in ++ * sequence, effectively updates CapsLock state on the guest. ++ * ++ * The other flags ++ * --------------- ++ * ++ * If a flag is not set, fire "up" events for all keys which correspond to ++ * the flag. Note that "down" events are not fired here because the flags ++ * checked here do not tell what exact keys are down. ++ * ++ * If one of the keys corresponding to a flag is down, we rely on ++ * -[NSEvent keyCode] of an event whose -[NSEvent type] is ++ * NSEventTypeFlagsChanged to know the exact key which is down, which has ++ * the following two downsides: ++ * - It does not work when the application is inactive as described above. ++ * - It malfactions *after* the modifier state is changed while the ++ * application is inactive. It is because -[NSEvent keyCode] does not tell ++ * if the key is up or down, and requires to infer the current state from ++ * the previous state. It is still possible to fix such a malfanction by ++ * completely leaving your hands from the keyboard, which hopefully makes ++ * this implementation usable enough. ++ */ ++ if (!!(modifiers & NSEventModifierFlagCapsLock) != ++ qkbd_state_modifier_get(kbd, QKBD_MOD_CAPSLOCK)) { ++ qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, true); ++ qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, false); ++ } ++ ++ if (!(modifiers & NSEventModifierFlagShift)) { ++ qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT, false); ++ qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT_R, false); ++ } ++ if (!(modifiers & NSEventModifierFlagControl)) { ++ qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL, false); ++ qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL_R, false); ++ } ++ if (!(modifiers & NSEventModifierFlagOption)) { ++ qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false); ++ qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false); ++ } ++ if (!(modifiers & NSEventModifierFlagCommand)) { ++ qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false); ++ qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false); ++ } ++ ++ switch ([event type]) { ++ case NSEventTypeFlagsChanged: ++ switch ([event keyCode]) { ++ case kVK_Shift: ++ if (!!(modifiers & NSEventModifierFlagShift)) { ++ [self toggleKey:Q_KEY_CODE_SHIFT]; ++ } ++ return true; ++ ++ case kVK_RightShift: ++ if (!!(modifiers & NSEventModifierFlagShift)) { ++ [self toggleKey:Q_KEY_CODE_SHIFT_R]; ++ } ++ return true; ++ ++ case kVK_Control: ++ if (!!(modifiers & NSEventModifierFlagControl)) { ++ [self toggleKey:Q_KEY_CODE_CTRL]; ++ } ++ return true; ++ ++ case kVK_RightControl: ++ if (!!(modifiers & NSEventModifierFlagControl)) { ++ [self toggleKey:Q_KEY_CODE_CTRL_R]; ++ } ++ return true; ++ ++ case kVK_Option: ++ if (!!(modifiers & NSEventModifierFlagOption)) { ++ [self toggleKey:Q_KEY_CODE_ALT]; ++ } ++ return true; ++ ++ case kVK_RightOption: ++ if (!!(modifiers & NSEventModifierFlagOption)) { ++ [self toggleKey:Q_KEY_CODE_ALT_R]; ++ } ++ return true; ++ ++ /* Don't pass command key changes to guest unless mouse is grabbed */ ++ case kVK_Command: ++ if (isMouseGrabbed && ++ !!(modifiers & NSEventModifierFlagCommand)) { ++ [self toggleKey:Q_KEY_CODE_META_L]; ++ } ++ return true; ++ ++ case kVK_RightCommand: ++ if (isMouseGrabbed && ++ !!(modifiers & NSEventModifierFlagCommand)) { ++ [self toggleKey:Q_KEY_CODE_META_R]; ++ } ++ return true; ++ ++ default: ++ return true; ++ } ++ case NSEventTypeKeyDown: ++ keycode = cocoa_keycode_to_qemu([event keyCode]); ++ ++ // forward command key combos to the host UI unless the mouse is grabbed ++ if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) { ++ return false; ++ } ++ ++ // default ++ ++ // handle control + alt Key Combos (ctrl+alt+[1..9,g] is reserved for QEMU) ++ if (([event modifierFlags] & NSEventModifierFlagControl) && ([event modifierFlags] & NSEventModifierFlagOption)) { ++ NSString *keychar = [event charactersIgnoringModifiers]; ++ if ([keychar length] == 1) { ++ char key = [keychar characterAtIndex:0]; ++ switch (key) { ++ ++ // enable graphic console ++ case '1' ... '9': ++ console_select(key - '0' - 1); /* ascii math */ ++ return true; ++ ++ // release the mouse grab ++ case 'g': ++ [self ungrabMouseLocked]; ++ return true; ++ } ++ } ++ } ++ ++ if (qemu_console_is_graphic(NULL)) { ++ qkbd_state_key_event(kbd, keycode, true); ++ } else { ++ [self handleMonitorInput: event]; ++ } ++ return true; ++ case NSEventTypeKeyUp: ++ keycode = cocoa_keycode_to_qemu([event keyCode]); ++ ++ // don't pass the guest a spurious key-up if we treated this ++ // command-key combo as a host UI action ++ if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) { ++ return true; ++ } ++ ++ if (qemu_console_is_graphic(NULL)) { ++ qkbd_state_key_event(kbd, keycode, false); ++ } ++ return true; ++ case NSEventTypeScrollWheel: ++ /* ++ * Send wheel events to the guest regardless of window focus. ++ * This is in-line with standard Mac OS X UI behaviour. ++ */ ++ ++ /* ++ * When deltaY is zero, it means that this scrolling event was ++ * either horizontal, or so fine that it only appears in ++ * scrollingDeltaY. So we drop the event. ++ */ ++ if ([event deltaY] != 0) { ++ /* Determine if this is a scroll up or scroll down event */ ++ int buttons = ([event deltaY] > 0) ? ++ INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN; ++ qemu_input_queue_btn(screen->dcl.con, buttons, true); ++ qemu_input_event_sync(); ++ qemu_input_queue_btn(screen->dcl.con, buttons, false); ++ qemu_input_event_sync(); ++ } ++ /* ++ * Since deltaY also reports scroll wheel events we prevent mouse ++ * movement code from executing. ++ */ ++ return true; ++ default: ++ return false; ++ } ++} ++ ++- (void) handleMouseEvent:(NSEvent *)event ++{ ++ if (!isMouseGrabbed) { ++ return; ++ } ++ ++ qemu_mutex_lock_iothread(); ++ ++ if (isAbsoluteEnabled) { ++ CGFloat d = (CGFloat)screen_height / [self frame].size.height; ++ NSPoint p = [event locationInWindow]; ++ // Note that the origin for Cocoa mouse coords is bottom left, not top left. ++ qemu_input_queue_abs(screen->dcl.con, INPUT_AXIS_X, p.x * d, 0, screen_width); ++ qemu_input_queue_abs(screen->dcl.con, INPUT_AXIS_Y, screen_height - p.y * d, 0, screen_height); ++ } else { ++ CGFloat d = (CGFloat)screen_height / [self convertSizeToBacking:[self frame].size].height; ++ qemu_input_queue_rel(screen->dcl.con, INPUT_AXIS_X, [event deltaX] * d); ++ qemu_input_queue_rel(screen->dcl.con, INPUT_AXIS_Y, [event deltaY] * d); ++ } ++ ++ qemu_input_event_sync(); ++ ++ qemu_mutex_unlock_iothread(); ++} ++ ++- (void) handleMouseEvent:(NSEvent *)event button:(InputButton)button down:(bool)down ++{ ++ if (!isMouseGrabbed) { ++ return; ++ } ++ ++ qemu_mutex_lock_iothread(); ++ qemu_input_queue_btn(screen->dcl.con, button, down); ++ qemu_mutex_unlock_iothread(); ++ ++ [self handleMouseEvent:event]; ++} ++ ++- (void) mouseExited:(NSEvent *)event ++{ ++ if (isAbsoluteEnabled && isMouseGrabbed) { ++ [self ungrabMouse]; ++ } ++} ++ ++- (void) mouseEntered:(NSEvent *)event ++{ ++ if (isAbsoluteEnabled && !isMouseGrabbed) { ++ [self grabMouse]; ++ } ++} ++ ++- (void) mouseMoved:(NSEvent *)event ++{ ++ [self handleMouseEvent:event]; ++} ++ ++- (void) mouseDown:(NSEvent *)event ++{ ++ [self handleMouseEvent:event button:INPUT_BUTTON_LEFT down:true]; ++} ++ ++- (void) rightMouseDown:(NSEvent *)event ++{ ++ [self handleMouseEvent:event button:INPUT_BUTTON_RIGHT down:true]; ++} ++ ++- (void) otherMouseDown:(NSEvent *)event ++{ ++ [self handleMouseEvent:event button:INPUT_BUTTON_MIDDLE down:true]; ++} ++ ++- (void) mouseDragged:(NSEvent *)event ++{ ++ [self handleMouseEvent:event]; ++} ++ ++- (void) rightMouseDragged:(NSEvent *)event ++{ ++ [self handleMouseEvent:event]; ++} ++ ++- (void) otherMouseDragged:(NSEvent *)event ++{ ++ [self handleMouseEvent:event]; ++} ++ ++- (void) mouseUp:(NSEvent *)event ++{ ++ if (!isMouseGrabbed) { ++ [self grabMouse]; ++ } ++ ++ [self handleMouseEvent:event button:INPUT_BUTTON_LEFT down:false]; ++} ++ ++- (void) rightMouseUp:(NSEvent *)event ++{ ++ [self handleMouseEvent:event button:INPUT_BUTTON_RIGHT down:false]; ++} ++ ++- (void) otherMouseUp:(NSEvent *)event ++{ ++ [self handleMouseEvent:event button:INPUT_BUTTON_MIDDLE down:false]; ++} ++ ++- (void) grabMouse ++{ ++ COCOA_DEBUG("QemuCocoaView: grabMouse\n"); ++ ++ if (qemu_name) ++ [[self window] setTitle:[NSString stringWithFormat:@"QEMU %s - (Press ctrl + alt + g to release Mouse)", qemu_name]]; ++ else ++ [[self window] setTitle:@"QEMU - (Press ctrl + alt + g to release Mouse)"]; ++ [self hideCursor]; ++ CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled); ++ isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:] ++} ++ ++- (void) ungrabMouse ++{ ++ qemu_mutex_lock_iothread(); ++ [self ungrabMouseLocked]; ++ qemu_mutex_unlock_iothread(); ++} ++ ++- (void) ungrabMouseLocked ++{ ++ COCOA_DEBUG("QemuCocoaView: ungrabMouseLocked\n"); ++ ++ if (qemu_name) ++ [[self window] setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; ++ else ++ [[self window] setTitle:@"QEMU"]; ++ [self unhideCursor]; ++ CGAssociateMouseAndMouseCursorPosition(TRUE); ++ isMouseGrabbed = FALSE; ++ [self raiseAllButtonsLocked]; ++} ++ ++- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled { ++ isAbsoluteEnabled = tIsAbsoluteEnabled; ++ if (isMouseGrabbed) { ++ CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled); ++ } ++} ++- (BOOL) isMouseGrabbed {return isMouseGrabbed;} ++- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;} ++ ++- (void) raiseAllButtonsLocked ++{ ++ qemu_input_queue_btn(screen->dcl.con, INPUT_BUTTON_LEFT, false); ++ qemu_input_queue_btn(screen->dcl.con, INPUT_BUTTON_RIGHT, false); ++ qemu_input_queue_btn(screen->dcl.con, INPUT_BUTTON_MIDDLE, false); ++} ++ ++- (void) setNeedsDisplayForCursorX:(int)x ++ y:(int)y ++ width:(int)width ++ height:(int)height ++ screenHeight:(int)given_screen_height ++{ ++ CGRect clip_rect = compute_cursor_clip_rect(given_screen_height, x, y, ++ width, height); ++ CGRect draw_rect = [self convertCursorClipRectToDraw:clip_rect ++ screenHeight:given_screen_height ++ mouseX:x ++ mouseY:y]; ++ [self setNeedsDisplayInRect:draw_rect]; ++} ++ ++/* Displays the word pause on the screen */ ++- (void)displayPause ++{ ++ /* Coordinates have to be calculated each time because the window can change its size */ ++ int xCoord, yCoord, width, height; ++ xCoord = ([[self window] frame].size.width - [pauseLabel frame].size.width)/2; ++ yCoord = [[self window] frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5); ++ width = [pauseLabel frame].size.width; ++ height = [pauseLabel frame].size.height; ++ [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)]; ++ [self addSubview: pauseLabel]; ++} ++ ++/* Removes the word pause from the screen */ ++- (void)removePause ++{ ++ [pauseLabel removeFromSuperview]; ++} ++@end +diff --git a/ui/console.c b/ui/console.c +index 2de5f4105b..b303da3209 100644 +--- a/ui/console.c ++++ b/ui/console.c +@@ -127,7 +127,7 @@ struct QemuConsole { + DisplayState *ds; + DisplaySurface *surface; + int dcls; +- DisplayChangeListener *gl; ++ void *dg; + bool gl_block; + int window_id; + +@@ -184,6 +184,7 @@ struct DisplayState { + QLIST_HEAD(, DisplayChangeListener) listeners; + }; + ++static const DisplayGLOps *display_gl_ops; + static DisplayState *display_state; + static QemuConsole *active_console; + static QTAILQ_HEAD(, QemuConsole) consoles = +@@ -203,7 +204,6 @@ static void gui_update(void *opaque) + uint64_t dcl_interval; + DisplayState *ds = opaque; + DisplayChangeListener *dcl; +- QemuConsole *con; + + ds->refreshing = true; + dpy_refresh(ds); +@@ -218,11 +218,6 @@ static void gui_update(void *opaque) + } + if (ds->update_interval != interval) { + ds->update_interval = interval; +- QTAILQ_FOREACH(con, &consoles, next) { +- if (con->hw_ops->update_interval) { +- con->hw_ops->update_interval(con->hw, interval); +- } +- } + trace_console_refresh(interval); + } + ds->last_update = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); +@@ -1109,8 +1104,14 @@ void console_select(unsigned int index) + } + } + if (s->surface) { +- dpy_gfx_update(s, 0, 0, surface_width(s->surface), +- surface_height(s->surface)); ++ int width = surface_width(s->surface); ++ int height = surface_height(s->surface); ++ if (display_gl_ops && ++ display_gl_ops->dpy_gl_scanout_get_enabled(s->dg)) { ++ dpy_gl_update(s, 0, 0, width, height); ++ } else { ++ dpy_gfx_update(s, 0, 0, width, height); ++ } + } + } + if (ds->have_text) { +@@ -1460,26 +1461,30 @@ void qemu_free_displaysurface(DisplaySurface *surface) + g_free(surface); + } + +-bool console_has_gl(QemuConsole *con) ++bool console_has_gl(void) ++{ ++ return display_gl_ops != NULL; ++} ++ ++void console_set_displayglcontext(QemuConsole *con, void *dg) + { +- return con->gl != NULL; ++ con->dg = dg; + } + +-static bool displaychangelistener_has_dmabuf(DisplayChangeListener *dcl) ++static bool console_has_dmabuf(QemuConsole *con) + { +- if (dcl->ops->dpy_has_dmabuf) { +- return dcl->ops->dpy_has_dmabuf(dcl); ++ if (display_gl_ops->dpy_has_dmabuf) { ++ return display_gl_ops->dpy_has_dmabuf(con->dg); + } + +- if (dcl->ops->dpy_gl_scanout_dmabuf) { ++ if (display_gl_ops->dpy_gl_scanout_dmabuf) { + return true; + } + + return false; + } + +-static bool dpy_compatible_with(QemuConsole *con, +- DisplayChangeListener *dcl, Error **errp) ++static bool dpy_compatible_with(QemuConsole *con, Error **errp) + { + ERRP_GUARD(); + int flags; +@@ -1487,14 +1492,13 @@ static bool dpy_compatible_with(QemuConsole *con, + flags = con->hw_ops->get_flags ? con->hw_ops->get_flags(con->hw) : 0; + + if (flags & GRAPHIC_FLAGS_GL && +- !console_has_gl(con)) { ++ !console_has_gl()) { + error_setg(errp, "The console requires a GL context."); + return false; +- + } + + if (flags & GRAPHIC_FLAGS_DMABUF && +- !displaychangelistener_has_dmabuf(dcl)) { ++ !console_has_dmabuf(con)) { + error_setg(errp, "The console requires display DMABUF support."); + return false; + } +@@ -1502,6 +1506,16 @@ static bool dpy_compatible_with(QemuConsole *con, + return true; + } + ++void register_displayglops(const DisplayGLOps *dg_ops) ++{ ++ if (display_gl_ops) { ++ error_report("can't register two opengl operators"); ++ exit(1); ++ } ++ ++ display_gl_ops = dg_ops; ++} ++ + void register_displaychangelistener(DisplayChangeListener *dcl) + { + static const char nodev[] = +@@ -1512,20 +1526,18 @@ void register_displaychangelistener(DisplayChangeListener *dcl) + + assert(!dcl->ds); + +- if (dcl->ops->dpy_gl_ctx_create) { +- /* display has opengl support */ +- assert(dcl->con); +- if (dcl->con->gl) { +- fprintf(stderr, "can't register two opengl displays (%s, %s)\n", +- dcl->ops->dpy_name, dcl->con->gl->ops->dpy_name); ++ if (dcl->con) { ++ if (!dpy_compatible_with(dcl->con, &err)) { ++ error_report_err(err); + exit(1); + } +- dcl->con->gl = dcl; +- } +- +- if (dcl->con && !dpy_compatible_with(dcl->con, dcl, &err)) { +- error_report_err(err); +- exit(1); ++ } else { ++ QTAILQ_FOREACH(con, &consoles, next) { ++ if (!dpy_compatible_with(con, &err)) { ++ error_report_err(err); ++ exit(1); ++ } ++ } + } + + trace_displaychangelistener_register(dcl, dcl->ops->dpy_name); +@@ -1842,86 +1854,90 @@ bool dpy_cursor_define_supported(QemuConsole *con) + QEMUGLContext dpy_gl_ctx_create(QemuConsole *con, + struct QEMUGLParams *qparams) + { +- assert(con->gl); +- return con->gl->ops->dpy_gl_ctx_create(con->gl, qparams); ++ assert(display_gl_ops); ++ return display_gl_ops->dpy_gl_ctx_create(con->dg, qparams); + } + + void dpy_gl_ctx_destroy(QemuConsole *con, QEMUGLContext ctx) + { +- assert(con->gl); +- con->gl->ops->dpy_gl_ctx_destroy(con->gl, ctx); ++ assert(display_gl_ops); ++ display_gl_ops->dpy_gl_ctx_destroy(con->dg, ctx); + } + + int dpy_gl_ctx_make_current(QemuConsole *con, QEMUGLContext ctx) + { +- assert(con->gl); +- return con->gl->ops->dpy_gl_ctx_make_current(con->gl, ctx); ++ assert(display_gl_ops); ++ return display_gl_ops->dpy_gl_ctx_make_current(con->dg, ctx); + } + + void dpy_gl_scanout_disable(QemuConsole *con) + { +- assert(con->gl); +- con->gl->ops->dpy_gl_scanout_disable(con->gl); ++ assert(display_gl_ops); ++ display_gl_ops->dpy_gl_scanout_disable(con->dg); + } + + void dpy_gl_scanout_texture(QemuConsole *con, + uint32_t backing_id, +- bool backing_y_0_top, +- uint32_t backing_width, +- uint32_t backing_height, ++ DisplayGLTextureBorrower backing_borrow, + uint32_t x, uint32_t y, + uint32_t width, uint32_t height) + { +- assert(con->gl); +- con->gl->ops->dpy_gl_scanout_texture(con->gl, backing_id, +- backing_y_0_top, +- backing_width, backing_height, +- x, y, width, height); ++ assert(display_gl_ops); ++ display_gl_ops->dpy_gl_scanout_texture(con->dg, backing_id, backing_borrow, ++ x, y, width, height); + } + + void dpy_gl_scanout_dmabuf(QemuConsole *con, + QemuDmaBuf *dmabuf) + { +- assert(con->gl); +- con->gl->ops->dpy_gl_scanout_dmabuf(con->gl, dmabuf); ++ assert(display_gl_ops); ++ display_gl_ops->dpy_gl_scanout_dmabuf(con->dg, dmabuf); + } + + void dpy_gl_cursor_dmabuf(QemuConsole *con, QemuDmaBuf *dmabuf, + bool have_hot, uint32_t hot_x, uint32_t hot_y) + { +- assert(con->gl); ++ assert(display_gl_ops); + +- if (con->gl->ops->dpy_gl_cursor_dmabuf) { +- con->gl->ops->dpy_gl_cursor_dmabuf(con->gl, dmabuf, +- have_hot, hot_x, hot_y); ++ if (display_gl_ops->dpy_gl_cursor_dmabuf) { ++ display_gl_ops->dpy_gl_cursor_dmabuf(con->dg, dmabuf, ++ have_hot, hot_x, hot_y); + } + } + + void dpy_gl_cursor_position(QemuConsole *con, + uint32_t pos_x, uint32_t pos_y) + { +- assert(con->gl); ++ assert(display_gl_ops); + +- if (con->gl->ops->dpy_gl_cursor_position) { +- con->gl->ops->dpy_gl_cursor_position(con->gl, pos_x, pos_y); ++ if (display_gl_ops->dpy_gl_cursor_position) { ++ display_gl_ops->dpy_gl_cursor_position(con->dg, pos_x, pos_y); + } + } + + void dpy_gl_release_dmabuf(QemuConsole *con, + QemuDmaBuf *dmabuf) + { +- assert(con->gl); ++ assert(display_gl_ops); + +- if (con->gl->ops->dpy_gl_release_dmabuf) { +- con->gl->ops->dpy_gl_release_dmabuf(con->gl, dmabuf); ++ if (display_gl_ops->dpy_gl_release_dmabuf) { ++ display_gl_ops->dpy_gl_release_dmabuf(con->dg, dmabuf); + } + } + + void dpy_gl_update(QemuConsole *con, + uint32_t x, uint32_t y, uint32_t w, uint32_t h) + { +- assert(con->gl); +- con->gl->ops->dpy_gl_update(con->gl, x, y, w, h); ++ DisplayChangeListener *dcl; ++ ++ QLIST_FOREACH(dcl, &con->ds->listeners, next) { ++ if (con != (dcl->con ? dcl->con : active_console)) { ++ continue; ++ } ++ if (dcl->ops->dpy_gl_update) { ++ dcl->ops->dpy_gl_update(dcl, x, y, w, h); ++ } ++} + } + + /***********************************************************/ +@@ -2032,7 +2048,7 @@ void graphic_console_close(QemuConsole *con) + object_property_set_link(OBJECT(con), "device", NULL, &error_abort); + graphic_console_set_hwops(con, &unused_ops, NULL); + +- if (con->gl) { ++ if (display_gl_ops) { + dpy_gl_scanout_disable(con); + } + surface = qemu_create_placeholder_surface(width, height, unplugged); +diff --git a/ui/egl-context.c b/ui/egl-context.c +index 368ffa49d8..07c4c34ec4 100644 +--- a/ui/egl-context.c ++++ b/ui/egl-context.c +@@ -1,8 +1,7 @@ + #include "qemu/osdep.h" + #include "ui/egl-context.h" + +-QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl, +- QEMUGLParams *params) ++QEMUGLContext qemu_egl_create_context(void *dg, QEMUGLParams *params) + { + EGLContext ctx; + EGLint ctx_att_core[] = { +@@ -24,13 +23,12 @@ QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl, + return ctx; + } + +-void qemu_egl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx) ++void qemu_egl_destroy_context(void *dg, QEMUGLContext ctx) + { + eglDestroyContext(qemu_egl_display, ctx); + } + +-int qemu_egl_make_context_current(DisplayChangeListener *dcl, +- QEMUGLContext ctx) ++int qemu_egl_make_context_current(void *dg, QEMUGLContext ctx) + { + return eglMakeCurrent(qemu_egl_display, + EGL_NO_SURFACE, EGL_NO_SURFACE, ctx); +diff --git a/ui/egl-headless.c b/ui/egl-headless.c +index da377a74af..b486887caa 100644 +--- a/ui/egl-headless.c ++++ b/ui/egl-headless.c +@@ -38,37 +38,48 @@ static void egl_gfx_switch(DisplayChangeListener *dcl, + edpy->ds = new_surface; + } + +-static QEMUGLContext egl_create_context(DisplayChangeListener *dcl, ++static QEMUGLContext egl_create_context(void *dg, + QEMUGLParams *params) + { + eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, + qemu_egl_rn_ctx); +- return qemu_egl_create_context(dcl, params); ++ return qemu_egl_create_context(dg, params); + } + +-static void egl_scanout_disable(DisplayChangeListener *dcl) ++static bool egl_scanout_get_enabled(void *dg) + { +- egl_dpy *edpy = container_of(dcl, egl_dpy, dcl); ++ return ((egl_dpy *)dg)->guest_fb.texture != 0; ++} + ++static void egl_scanout_disable(void *dg) ++{ ++ egl_dpy *edpy = dg; + egl_fb_destroy(&edpy->guest_fb); + egl_fb_destroy(&edpy->blit_fb); + } + +-static void egl_scanout_texture(DisplayChangeListener *dcl, ++static void egl_scanout_texture(void *dg, + uint32_t backing_id, +- bool backing_y_0_top, +- uint32_t backing_width, +- uint32_t backing_height, ++ DisplayGLTextureBorrower backing_borrower, + uint32_t x, uint32_t y, + uint32_t w, uint32_t h) + { +- egl_dpy *edpy = container_of(dcl, egl_dpy, dcl); ++ egl_dpy *edpy = dg; ++ bool backing_y_0_top; ++ uint32_t backing_width; ++ uint32_t backing_height; ++ ++ GLuint backing_texture = backing_borrower(backing_id, &backing_y_0_top, ++ &backing_width, &backing_height); ++ if (!texture) { ++ return; ++ } + +- edpy->y_0_top = backing_y_0_top; ++ edpy->y_0_top = y_0_top; + + /* source framebuffer */ + egl_fb_setup_for_tex(&edpy->guest_fb, +- backing_width, backing_height, backing_id, false); ++ backing_width, backing_height, backing_texture, false); + + /* dest framebuffer */ + if (edpy->blit_fb.width != backing_width || +@@ -78,24 +89,23 @@ static void egl_scanout_texture(DisplayChangeListener *dcl, + } + } + +-static void egl_scanout_dmabuf(DisplayChangeListener *dcl, +- QemuDmaBuf *dmabuf) ++static void egl_scanout_dmabuf(void *dg, QemuDmaBuf *dmabuf) + { + egl_dmabuf_import_texture(dmabuf); + if (!dmabuf->texture) { + return; + } + +- egl_scanout_texture(dcl, dmabuf->texture, ++ egl_scanout_texture(dg, dmabuf->texture, + false, dmabuf->width, dmabuf->height, + 0, 0, dmabuf->width, dmabuf->height); + } + +-static void egl_cursor_dmabuf(DisplayChangeListener *dcl, ++static void egl_cursor_dmabuf(void *dg, + QemuDmaBuf *dmabuf, bool have_hot, + uint32_t hot_x, uint32_t hot_y) + { +- egl_dpy *edpy = container_of(dcl, egl_dpy, dcl); ++ egl_dpy *edpy = dg; + + if (dmabuf) { + egl_dmabuf_import_texture(dmabuf); +@@ -109,17 +119,16 @@ static void egl_cursor_dmabuf(DisplayChangeListener *dcl, + } + } + +-static void egl_cursor_position(DisplayChangeListener *dcl, ++static void egl_cursor_position(void *dg, + uint32_t pos_x, uint32_t pos_y) + { +- egl_dpy *edpy = container_of(dcl, egl_dpy, dcl); ++ egl_dpy *edpy = dg; + + edpy->pos_x = pos_x; + edpy->pos_y = pos_y; + } + +-static void egl_release_dmabuf(DisplayChangeListener *dcl, +- QemuDmaBuf *dmabuf) ++static void egl_release_dmabuf(void *dg, QemuDmaBuf *dmabuf) + { + egl_dmabuf_release_texture(dmabuf); + } +@@ -151,22 +160,26 @@ static void egl_scanout_flush(DisplayChangeListener *dcl, + dpy_gfx_update(edpy->dcl.con, x, y, w, h); + } + +-static const DisplayChangeListenerOps egl_ops = { ++static const DisplayGLOps dg_egl_ops = { ++ .dpy_gl_ctx_create = egl_create_context, ++ .dpy_gl_ctx_destroy = qemu_egl_destroy_context, ++ .dpy_gl_ctx_make_current = qemu_egl_make_context_current, ++ ++ .dpy_gl_scanout_get_enabled = egl_scanout_get_enabled, ++ .dpy_gl_scanout_disable = egl_scanout_disable, ++ .dpy_gl_scanout_texture = egl_scanout_texture, ++ .dpy_gl_scanout_dmabuf = egl_scanout_dmabuf, ++ .dpy_gl_cursor_dmabuf = egl_cursor_dmabuf, ++ .dpy_gl_cursor_position = egl_cursor_position, ++ .dpy_gl_release_dmabuf = egl_release_dmabuf, ++}; ++ ++static const DisplayChangeListenerOps dcl_egl_ops = { + .dpy_name = "egl-headless", + .dpy_refresh = egl_refresh, + .dpy_gfx_update = egl_gfx_update, + .dpy_gfx_switch = egl_gfx_switch, + +- .dpy_gl_ctx_create = egl_create_context, +- .dpy_gl_ctx_destroy = qemu_egl_destroy_context, +- .dpy_gl_ctx_make_current = qemu_egl_make_context_current, +- +- .dpy_gl_scanout_disable = egl_scanout_disable, +- .dpy_gl_scanout_texture = egl_scanout_texture, +- .dpy_gl_scanout_dmabuf = egl_scanout_dmabuf, +- .dpy_gl_cursor_dmabuf = egl_cursor_dmabuf, +- .dpy_gl_cursor_position = egl_cursor_position, +- .dpy_gl_release_dmabuf = egl_release_dmabuf, + .dpy_gl_update = egl_scanout_flush, + }; + +@@ -187,6 +200,8 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts) + exit(1); + } + ++ register_displayglops(&dg_egl_ops); ++ + for (idx = 0;; idx++) { + con = qemu_console_lookup_by_index(idx); + if (!con || !qemu_console_is_graphic(con)) { +@@ -195,8 +210,9 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts) + + edpy = g_new0(egl_dpy, 1); + edpy->dcl.con = con; +- edpy->dcl.ops = &egl_ops; ++ edpy->dcl.ops = &dcl_egl_ops; + edpy->gls = qemu_gl_init_shader(); ++ console_set_displayglcontext(con, edpy); + register_displaychangelistener(&edpy->dcl); + } + } +diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c +index 6d0cb2b5cb..ac30d990ec 100644 +--- a/ui/egl-helpers.c ++++ b/ui/egl-helpers.c +@@ -291,7 +291,7 @@ void egl_dmabuf_release_texture(QemuDmaBuf *dmabuf) + + /* ---------------------------------------------------------------------- */ + +-EGLSurface qemu_egl_init_surface_x11(EGLContext ectx, EGLNativeWindowType win) ++EGLSurface qemu_egl_init_surface(EGLContext ectx, EGLNativeWindowType win) + { + EGLSurface esurface; + EGLBoolean b; +@@ -315,6 +315,70 @@ EGLSurface qemu_egl_init_surface_x11(EGLContext ectx, EGLNativeWindowType win) + + /* ---------------------------------------------------------------------- */ + ++static int qemu_egl_init_dpy(EGLDisplay dpy, DisplayGLMode mode) ++{ ++ static const EGLint conf_att_core[] = { ++ EGL_SURFACE_TYPE, EGL_WINDOW_BIT, ++ EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, ++ EGL_RED_SIZE, 5, ++ EGL_GREEN_SIZE, 5, ++ EGL_BLUE_SIZE, 5, ++ EGL_ALPHA_SIZE, 0, ++ EGL_NONE, ++ }; ++ static const EGLint conf_att_gles[] = { ++ EGL_SURFACE_TYPE, EGL_WINDOW_BIT, ++ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, ++ EGL_RED_SIZE, 5, ++ EGL_GREEN_SIZE, 5, ++ EGL_BLUE_SIZE, 5, ++ EGL_ALPHA_SIZE, 0, ++ EGL_NONE, ++ }; ++ EGLint major, minor; ++ EGLBoolean b; ++ EGLint n; ++ bool gles = (mode == DISPLAYGL_MODE_ES); ++ ++ qemu_egl_display = dpy; ++ ++ b = eglInitialize(qemu_egl_display, &major, &minor); ++ if (b == EGL_FALSE) { ++ error_report("egl: eglInitialize failed"); ++ return -1; ++ } ++ ++ b = eglBindAPI(gles ? EGL_OPENGL_ES_API : EGL_OPENGL_API); ++ if (b == EGL_FALSE) { ++ error_report("egl: eglBindAPI failed (%s mode)", ++ gles ? "gles" : "core"); ++ return -1; ++ } ++ ++ b = eglChooseConfig(qemu_egl_display, ++ gles ? conf_att_gles : conf_att_core, ++ &qemu_egl_config, 1, &n); ++ if (b == EGL_FALSE || n != 1) { ++ error_report("egl: eglChooseConfig failed (%s mode)", ++ gles ? "gles" : "core"); ++ return -1; ++ } ++ ++ qemu_egl_mode = gles ? DISPLAYGL_MODE_ES : DISPLAYGL_MODE_CORE; ++ return 0; ++} ++ ++int qemu_egl_init_dpy_cocoa(DisplayGLMode mode) ++{ ++ EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY); ++ if (dpy == EGL_NO_DISPLAY) { ++ error_report("egl: eglGetDisplay failed"); ++ return -1; ++ } ++ ++ return qemu_egl_init_dpy(dpy, mode); ++} ++ + #if defined(CONFIG_X11) || defined(CONFIG_GBM) + + /* +@@ -345,8 +409,9 @@ EGLSurface qemu_egl_init_surface_x11(EGLContext ectx, EGLNativeWindowType win) + * platform extensions (EGL_KHR_platform_gbm and friends) yet it doesn't seem + * like mesa will be able to advertise these (even though it can do EGL 1.5). + */ +-static EGLDisplay qemu_egl_get_display(EGLNativeDisplayType native, +- EGLenum platform) ++static int qemu_egl_init_dpy_platform(EGLNativeDisplayType native, ++ EGLenum platform, ++ DisplayGLMode mode) + { + EGLDisplay dpy = EGL_NO_DISPLAY; + +@@ -363,83 +428,30 @@ static EGLDisplay qemu_egl_get_display(EGLNativeDisplayType native, + /* fallback */ + dpy = eglGetDisplay(native); + } +- return dpy; +-} + +-static int qemu_egl_init_dpy(EGLNativeDisplayType dpy, +- EGLenum platform, +- DisplayGLMode mode) +-{ +- static const EGLint conf_att_core[] = { +- EGL_SURFACE_TYPE, EGL_WINDOW_BIT, +- EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, +- EGL_RED_SIZE, 5, +- EGL_GREEN_SIZE, 5, +- EGL_BLUE_SIZE, 5, +- EGL_ALPHA_SIZE, 0, +- EGL_NONE, +- }; +- static const EGLint conf_att_gles[] = { +- EGL_SURFACE_TYPE, EGL_WINDOW_BIT, +- EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, +- EGL_RED_SIZE, 5, +- EGL_GREEN_SIZE, 5, +- EGL_BLUE_SIZE, 5, +- EGL_ALPHA_SIZE, 0, +- EGL_NONE, +- }; +- EGLint major, minor; +- EGLBoolean b; +- EGLint n; +- bool gles = (mode == DISPLAYGL_MODE_ES); +- +- qemu_egl_display = qemu_egl_get_display(dpy, platform); +- if (qemu_egl_display == EGL_NO_DISPLAY) { ++ if (dpy == EGL_NO_DISPLAY) { + error_report("egl: eglGetDisplay failed"); + return -1; + } + +- b = eglInitialize(qemu_egl_display, &major, &minor); +- if (b == EGL_FALSE) { +- error_report("egl: eglInitialize failed"); +- return -1; +- } +- +- b = eglBindAPI(gles ? EGL_OPENGL_ES_API : EGL_OPENGL_API); +- if (b == EGL_FALSE) { +- error_report("egl: eglBindAPI failed (%s mode)", +- gles ? "gles" : "core"); +- return -1; +- } +- +- b = eglChooseConfig(qemu_egl_display, +- gles ? conf_att_gles : conf_att_core, +- &qemu_egl_config, 1, &n); +- if (b == EGL_FALSE || n != 1) { +- error_report("egl: eglChooseConfig failed (%s mode)", +- gles ? "gles" : "core"); +- return -1; +- } +- +- qemu_egl_mode = gles ? DISPLAYGL_MODE_ES : DISPLAYGL_MODE_CORE; +- return 0; ++ return qemu_egl_init_dpy(dpy, mode); + } + + int qemu_egl_init_dpy_x11(EGLNativeDisplayType dpy, DisplayGLMode mode) + { + #ifdef EGL_KHR_platform_x11 +- return qemu_egl_init_dpy(dpy, EGL_PLATFORM_X11_KHR, mode); ++ return qemu_egl_init_dpy_platform(dpy, EGL_PLATFORM_X11_KHR, mode); + #else +- return qemu_egl_init_dpy(dpy, 0, mode); ++ return qemu_egl_init_dpy_platform(dpy, 0, mode); + #endif + } + + int qemu_egl_init_dpy_mesa(EGLNativeDisplayType dpy, DisplayGLMode mode) + { + #ifdef EGL_MESA_platform_gbm +- return qemu_egl_init_dpy(dpy, EGL_PLATFORM_GBM_MESA, mode); ++ return qemu_egl_init_dpy_platform(dpy, EGL_PLATFORM_GBM_MESA, mode); + #else +- return qemu_egl_init_dpy(dpy, 0, mode); ++ return qemu_egl_init_dpy_platform(dpy, 0, mode); + #endif + } + +diff --git a/ui/gtk-egl.c b/ui/gtk-egl.c +index 2a2e6d3a17..aee577e110 100644 +--- a/ui/gtk-egl.c ++++ b/ui/gtk-egl.c +@@ -53,7 +53,7 @@ void gd_egl_init(VirtualConsole *vc) + } + + vc->gfx.ectx = qemu_egl_init_ctx(); +- vc->gfx.esurface = qemu_egl_init_surface_x11 ++ vc->gfx.esurface = qemu_egl_init_surface + (vc->gfx.ectx, (EGLNativeWindowType)x11_window); + + assert(vc->gfx.esurface); +@@ -116,8 +116,8 @@ void gd_egl_refresh(DisplayChangeListener *dcl) + { + VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); + +- vc->gfx.dcl.update_interval = gd_monitor_update_interval( +- vc->window ? vc->window : vc->gfx.drawing_area); ++ gd_update_monitor_refresh_rate( ++ vc, vc->window ? vc->window : vc->gfx.drawing_area); + + if (!vc->gfx.esurface) { + gd_egl_init(vc); +@@ -164,33 +164,37 @@ void gd_egl_switch(DisplayChangeListener *dcl, + } + } + +-QEMUGLContext gd_egl_create_context(DisplayChangeListener *dcl, +- QEMUGLParams *params) ++QEMUGLContext gd_egl_create_context(void *dg, QEMUGLParams *params) + { +- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); ++ VirtualConsole *vc = dg; + + eglMakeCurrent(qemu_egl_display, vc->gfx.esurface, + vc->gfx.esurface, vc->gfx.ectx); +- return qemu_egl_create_context(dcl, params); ++ return qemu_egl_create_context(dg, params); + } + +-void gd_egl_scanout_disable(DisplayChangeListener *dcl) ++bool gd_egl_scanout_get_enabled(void *dg) + { +- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); ++ return ((VirtualConsole *)dg)->gfx.scanout_mode; ++} ++ ++void gd_egl_scanout_disable(void *dg) ++{ ++ VirtualConsole *vc = dg; + + vc->gfx.w = 0; + vc->gfx.h = 0; + gtk_egl_set_scanout_mode(vc, false); + } + +-void gd_egl_scanout_texture(DisplayChangeListener *dcl, +- uint32_t backing_id, bool backing_y_0_top, +- uint32_t backing_width, uint32_t backing_height, +- uint32_t x, uint32_t y, +- uint32_t w, uint32_t h) ++static void gd_egl_scanout_borrowed_texture(VirtualConsole *vc, ++ GLuint backing_id, ++ bool backing_y_0_top, ++ uint32_t backing_width, ++ uint32_t backing_height, ++ uint32_t x, uint32_t y, ++ uint32_t w, uint32_t h) + { +- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); +- + vc->gfx.x = x; + vc->gfx.y = y; + vc->gfx.w = w; +@@ -205,8 +209,26 @@ void gd_egl_scanout_texture(DisplayChangeListener *dcl, + backing_id, false); + } + +-void gd_egl_scanout_dmabuf(DisplayChangeListener *dcl, +- QemuDmaBuf *dmabuf) ++void gd_egl_scanout_texture(void *dg, uint32_t backing_id, ++ DisplayGLTextureBorrower backing_borrow, ++ uint32_t x, uint32_t y, ++ uint32_t w, uint32_t h) ++{ ++ bool backing_y_0_top; ++ uint32_t backing_width; ++ uint32_t backing_height; ++ ++ GLuint backing_texture = backing_borrow(backing_id, &backing_y_0_top, ++ &backing_width, &backing_height, ++ x, y, w, h); ++ if (backing_texture) { ++ gd_egl_scanout_borrowed_texture(dg, backing_texture, backing_y_0_top, ++ backing_width, backing_height, ++ x, y, w, h); ++ } ++} ++ ++void gd_egl_scanout_dmabuf(void *dg, QemuDmaBuf *dmabuf) + { + #ifdef CONFIG_GBM + egl_dmabuf_import_texture(dmabuf); +@@ -214,18 +236,18 @@ void gd_egl_scanout_dmabuf(DisplayChangeListener *dcl, + return; + } + +- gd_egl_scanout_texture(dcl, dmabuf->texture, +- false, dmabuf->width, dmabuf->height, +- 0, 0, dmabuf->width, dmabuf->height); ++ gd_egl_scanout_borrowed_texture(dg, dmabuf->texture, ++ false, dmabuf->width, dmabuf->height, ++ 0, 0, dmabuf->width, dmabuf->height); + #endif + } + +-void gd_egl_cursor_dmabuf(DisplayChangeListener *dcl, ++void gd_egl_cursor_dmabuf(void *dg, + QemuDmaBuf *dmabuf, bool have_hot, + uint32_t hot_x, uint32_t hot_y) + { + #ifdef CONFIG_GBM +- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); ++ VirtualConsole *vc = dg; + + if (dmabuf) { + egl_dmabuf_import_texture(dmabuf); +@@ -240,17 +262,15 @@ void gd_egl_cursor_dmabuf(DisplayChangeListener *dcl, + #endif + } + +-void gd_egl_cursor_position(DisplayChangeListener *dcl, +- uint32_t pos_x, uint32_t pos_y) ++void gd_egl_cursor_position(void *dg, uint32_t pos_x, uint32_t pos_y) + { +- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); ++ VirtualConsole *vc = dg; + + vc->gfx.cursor_x = pos_x * vc->gfx.scale_x; + vc->gfx.cursor_y = pos_y * vc->gfx.scale_y; + } + +-void gd_egl_release_dmabuf(DisplayChangeListener *dcl, +- QemuDmaBuf *dmabuf) ++void gd_egl_release_dmabuf(void *dg, QemuDmaBuf *dmabuf) + { + #ifdef CONFIG_GBM + egl_dmabuf_release_texture(dmabuf); +@@ -304,10 +324,9 @@ void gtk_egl_init(DisplayGLMode mode) + display_opengl = 1; + } + +-int gd_egl_make_current(DisplayChangeListener *dcl, +- QEMUGLContext ctx) ++int gd_egl_make_current(void *dg, QEMUGLContext ctx) + { +- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); ++ VirtualConsole *vc = dg; + + return eglMakeCurrent(qemu_egl_display, vc->gfx.esurface, + vc->gfx.esurface, ctx); +diff --git a/ui/gtk-gl-area.c b/ui/gtk-gl-area.c +index dd5783fec7..fefc2f4695 100644 +--- a/ui/gtk-gl-area.c ++++ b/ui/gtk-gl-area.c +@@ -139,10 +139,9 @@ void gd_gl_area_switch(DisplayChangeListener *dcl, + } + } + +-QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl, +- QEMUGLParams *params) ++QEMUGLContext gd_gl_area_create_context(void *dg, QEMUGLParams *params) + { +- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); ++ VirtualConsole *vc = dg; + GdkWindow *window; + GdkGLContext *ctx; + GError *err = NULL; +@@ -168,21 +167,24 @@ QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl, + return ctx; + } + +-void gd_gl_area_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx) ++void gd_gl_area_destroy_context(void *dg, QEMUGLContext ctx) + { + /* FIXME */ + } + +-void gd_gl_area_scanout_texture(DisplayChangeListener *dcl, +- uint32_t backing_id, +- bool backing_y_0_top, +- uint32_t backing_width, +- uint32_t backing_height, +- uint32_t x, uint32_t y, +- uint32_t w, uint32_t h) ++bool gd_gl_area_scanout_get_enabled(void *dg) + { +- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); ++ return ((VirtualConsole *)dg)->gfx.scanout_mode; ++} + ++static void gd_gl_area_scanout_borrowed_texture(VirtualConsole *vc, ++ GLuint backing_id, ++ bool backing_y_0_top, ++ uint32_t backing_width, ++ uint32_t backing_height, ++ uint32_t x, uint32_t y, ++ uint32_t w, uint32_t h) ++{ + vc->gfx.x = x; + vc->gfx.y = y; + vc->gfx.w = w; +@@ -201,11 +203,30 @@ void gd_gl_area_scanout_texture(DisplayChangeListener *dcl, + backing_id, false); + } + +-void gd_gl_area_scanout_disable(DisplayChangeListener *dcl) ++void gd_gl_area_scanout_texture(void *dg, ++ uint32_t backing_id, ++ DisplayGLTextureBorrower backing_borrow, ++ uint32_t x, uint32_t y, ++ uint32_t w, uint32_t h) + { +- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); ++ bool backing_y_0_top; ++ uint32_t backing_width; ++ uint32_t backing_height; ++ ++ GLuint backing_texture = backing_borrow(backing_id, &backing_y_0_top, ++ &backing_width, &backing_height, ++ x, y, w, h); ++ if (backing_texture) { ++ gd_gl_area_scanout_borrowed_texture(dg, backing_texture, ++ backing_y_0_top, ++ backing_width, backing_height, ++ x, y, w, h); ++ } ++} + +- gtk_gl_area_set_scanout_mode(vc, false); ++void gd_gl_area_scanout_disable(void *dg) ++{ ++ gtk_gl_area_set_scanout_mode(dg, false); + } + + void gd_gl_area_scanout_flush(DisplayChangeListener *dcl, +@@ -216,11 +237,10 @@ void gd_gl_area_scanout_flush(DisplayChangeListener *dcl, + gtk_gl_area_queue_render(GTK_GL_AREA(vc->gfx.drawing_area)); + } + +-void gd_gl_area_scanout_dmabuf(DisplayChangeListener *dcl, +- QemuDmaBuf *dmabuf) ++void gd_gl_area_scanout_dmabuf(void *dg, QemuDmaBuf *dmabuf) + { + #ifdef CONFIG_GBM +- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); ++ VirtualConsole *vc = dg; + + gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); + egl_dmabuf_import_texture(dmabuf); +@@ -228,9 +248,9 @@ void gd_gl_area_scanout_dmabuf(DisplayChangeListener *dcl, + return; + } + +- gd_gl_area_scanout_texture(dcl, dmabuf->texture, +- false, dmabuf->width, dmabuf->height, +- 0, 0, dmabuf->width, dmabuf->height); ++ gd_gl_area_scanout_borrowed_texture(dg, dmabuf->texture, ++ false, dmabuf->width, dmabuf->height, ++ 0, 0, dmabuf->width, dmabuf->height); + #endif + } + +@@ -239,8 +259,7 @@ void gtk_gl_area_init(void) + display_opengl = 1; + } + +-int gd_gl_area_make_current(DisplayChangeListener *dcl, +- QEMUGLContext ctx) ++int gd_gl_area_make_current(void *dg, QEMUGLContext ctx) + { + gdk_gl_context_make_current(ctx); + return 0; +diff --git a/ui/gtk.c b/ui/gtk.c +index 1ea1253528..c29853d156 100644 +--- a/ui/gtk.c ++++ b/ui/gtk.c +@@ -396,7 +396,7 @@ static void gd_update_full_redraw(VirtualConsole *vc) + int ww, wh; + ww = gdk_window_get_width(gtk_widget_get_window(area)); + wh = gdk_window_get_height(gtk_widget_get_window(area)); +-#if defined(CONFIG_OPENGL) ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) + if (vc->gfx.gls && gtk_use_gl_area) { + gtk_gl_area_queue_render(GTK_GL_AREA(vc->gfx.drawing_area)); + return; +@@ -615,11 +615,11 @@ static const DisplayChangeListenerOps dcl_ops = { + }; + + +-#if defined(CONFIG_OPENGL) ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) + +-static bool gd_has_dmabuf(DisplayChangeListener *dcl) ++static bool gd_has_dmabuf(void *dg) + { +- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); ++ VirtualConsole *vc = dg; + + if (gtk_use_gl_area && !gtk_widget_get_realized(vc->gfx.drawing_area)) { + /* FIXME: Assume it will work, actual check done after realize */ +@@ -632,6 +632,17 @@ static bool gd_has_dmabuf(DisplayChangeListener *dcl) + + /** DisplayState Callbacks (opengl version) **/ + ++static const DisplayGLOps dg_gl_area_ops = { ++ .dpy_gl_ctx_create = gd_gl_area_create_context, ++ .dpy_gl_ctx_destroy = gd_gl_area_destroy_context, ++ .dpy_gl_ctx_make_current = gd_gl_area_make_current, ++ .dpy_gl_scanout_get_enabled = gd_gl_area_scanout_get_enabled, ++ .dpy_gl_scanout_texture = gd_gl_area_scanout_texture, ++ .dpy_gl_scanout_disable = gd_gl_area_scanout_disable, ++ .dpy_gl_scanout_dmabuf = gd_gl_area_scanout_dmabuf, ++ .dpy_has_dmabuf = gd_has_dmabuf, ++}; ++ + static const DisplayChangeListenerOps dcl_gl_area_ops = { + .dpy_name = "gtk-egl", + .dpy_gfx_update = gd_gl_area_update, +@@ -641,14 +652,7 @@ static const DisplayChangeListenerOps dcl_gl_area_ops = { + .dpy_mouse_set = gd_mouse_set, + .dpy_cursor_define = gd_cursor_define, + +- .dpy_gl_ctx_create = gd_gl_area_create_context, +- .dpy_gl_ctx_destroy = gd_gl_area_destroy_context, +- .dpy_gl_ctx_make_current = gd_gl_area_make_current, +- .dpy_gl_scanout_texture = gd_gl_area_scanout_texture, +- .dpy_gl_scanout_disable = gd_gl_area_scanout_disable, +- .dpy_gl_update = gd_gl_area_scanout_flush, +- .dpy_gl_scanout_dmabuf = gd_gl_area_scanout_dmabuf, +- .dpy_has_dmabuf = gd_has_dmabuf, ++ .dpy_gl_update = gd_gl_area_scanout_flush, + }; + + #ifdef CONFIG_X11 +@@ -662,6 +666,10 @@ static const DisplayChangeListenerOps dcl_egl_ops = { + .dpy_mouse_set = gd_mouse_set, + .dpy_cursor_define = gd_cursor_define, + ++ .dpy_gl_update = gd_egl_scanout_flush, ++}; ++ ++static const DisplayGLOps dg_egl_ops = { + .dpy_gl_ctx_create = gd_egl_create_context, + .dpy_gl_ctx_destroy = qemu_egl_destroy_context, + .dpy_gl_ctx_make_current = gd_egl_make_current, +@@ -671,13 +679,12 @@ static const DisplayChangeListenerOps dcl_egl_ops = { + .dpy_gl_cursor_dmabuf = gd_egl_cursor_dmabuf, + .dpy_gl_cursor_position = gd_egl_cursor_position, + .dpy_gl_release_dmabuf = gd_egl_release_dmabuf, +- .dpy_gl_update = gd_egl_scanout_flush, + .dpy_has_dmabuf = gd_has_dmabuf, + }; + + #endif + +-#endif /* CONFIG_OPENGL */ ++#endif /* defined(CONFIG_OPENGL) && defined(CONFIG_EGL) */ + + /** QEMU Events **/ + +@@ -724,17 +731,26 @@ static gboolean gd_window_close(GtkWidget *widget, GdkEvent *event, + return TRUE; + } + +-static void gd_set_ui_info(VirtualConsole *vc, gint width, gint height) ++static void gd_set_ui_refresh_rate(VirtualConsole *vc, int refresh_rate) ++{ ++ QemuUIInfo info; ++ ++ info = *dpy_get_ui_info(vc->gfx.dcl.con); ++ info.refresh_rate = refresh_rate; ++ dpy_set_ui_info(vc->gfx.dcl.con, &info); ++} ++ ++static void gd_set_ui_size(VirtualConsole *vc, gint width, gint height) + { + QemuUIInfo info; + +- memset(&info, 0, sizeof(info)); ++ info = *dpy_get_ui_info(vc->gfx.dcl.con); + info.width = width; + info.height = height; + dpy_set_ui_info(vc->gfx.dcl.con, &info); + } + +-#if defined(CONFIG_OPENGL) ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) + + static gboolean gd_render_event(GtkGLArea *area, GdkGLContext *context, + void *opaque) +@@ -752,33 +768,32 @@ static void gd_resize_event(GtkGLArea *area, + { + VirtualConsole *vc = (void *)opaque; + +- gd_set_ui_info(vc, width, height); ++ gd_set_ui_size(vc, width, height); + } + + #endif + +-/* +- * If available, return the update interval of the monitor in ms, +- * else return 0 (the default update interval). +- */ +-int gd_monitor_update_interval(GtkWidget *widget) ++void gd_update_monitor_refresh_rate(VirtualConsole *vc, GtkWidget *widget) + { + #ifdef GDK_VERSION_3_22 + GdkWindow *win = gtk_widget_get_window(widget); ++ int refresh_rate; + + if (win) { + GdkDisplay *dpy = gtk_widget_get_display(widget); + GdkMonitor *monitor = gdk_display_get_monitor_at_window(dpy, win); +- int refresh_rate = gdk_monitor_get_refresh_rate(monitor); /* [mHz] */ +- +- if (refresh_rate) { +- /* T = 1 / f = 1 [s*Hz] / f = 1000*1000 [ms*mHz] / f */ +- return MIN(1000 * 1000 / refresh_rate, +- GUI_REFRESH_INTERVAL_DEFAULT); +- } ++ refresh_rate = gdk_monitor_get_refresh_rate(monitor); /* [mHz] */ ++ } else { ++ refresh_rate = 0; + } ++ ++ gd_set_ui_refresh_rate(vc, refresh_rate); ++ ++ /* T = 1 / f = 1 [s*Hz] / f = 1000*1000 [ms*mHz] / f */ ++ vc->gfx.dcl.update_interval = refresh_rate ? ++ MIN(1000 * 1000 / refresh_rate, GUI_REFRESH_INTERVAL_DEFAULT) : ++ GUI_REFRESH_INTERVAL_DEFAULT; + #endif +- return 0; + } + + static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque) +@@ -789,7 +804,7 @@ static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque) + int ww, wh; + int fbw, fbh; + +-#if defined(CONFIG_OPENGL) ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) + if (vc->gfx.gls) { + if (gtk_use_gl_area) { + /* invoke render callback please */ +@@ -812,8 +827,7 @@ static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque) + return FALSE; + } + +- vc->gfx.dcl.update_interval = +- gd_monitor_update_interval(vc->window ? vc->window : s->window); ++ gd_update_monitor_refresh_rate(vc, vc->window ? vc->window : s->window); + + fbw = surface_width(vc->gfx.ds); + fbh = surface_height(vc->gfx.ds); +@@ -1657,7 +1671,7 @@ static gboolean gd_configure(GtkWidget *widget, + { + VirtualConsole *vc = opaque; + +- gd_set_ui_info(vc, cfg->width, cfg->height); ++ gd_set_ui_size(vc, cfg->width, cfg->height); + return FALSE; + } + +@@ -1888,7 +1902,7 @@ static void gd_connect_vc_gfx_signals(VirtualConsole *vc) + { + g_signal_connect(vc->gfx.drawing_area, "draw", + G_CALLBACK(gd_draw_event), vc); +-#if defined(CONFIG_OPENGL) ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) + if (gtk_use_gl_area) { + /* wire up GtkGlArea events */ + g_signal_connect(vc->gfx.drawing_area, "render", +@@ -2025,7 +2039,7 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc, + vc->gfx.scale_x = 1.0; + vc->gfx.scale_y = 1.0; + +-#if defined(CONFIG_OPENGL) ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) + if (display_opengl) { + if (gtk_use_gl_area) { + vc->gfx.drawing_area = gtk_gl_area_new(); +@@ -2080,6 +2094,7 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc, + vc->gfx.kbd = qkbd_state_init(con); + vc->gfx.dcl.con = con; + ++ console_set_displayglcontext(con, vc); + register_displaychangelistener(&vc->gfx.dcl); + + gd_connect_vc_gfx_signals(vc); +@@ -2169,6 +2184,18 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s) + separator = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator); + ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) ++ if (display_opengl) { ++ if (gtk_use_gl_area) { ++ register_displayglops(&dg_gl_area_ops); ++ } else { ++#ifdef CONFIG_X11 ++ register_displayglops(&dg_egl_ops); ++#endif ++ } ++ } ++#endif ++ + /* gfx */ + for (vc = 0;; vc++) { + con = qemu_console_lookup_by_index(vc); +@@ -2352,7 +2379,7 @@ static void early_gtk_display_init(DisplayOptions *opts) + + assert(opts->type == DISPLAY_TYPE_GTK); + if (opts->has_gl && opts->gl != DISPLAYGL_MODE_OFF) { +-#if defined(CONFIG_OPENGL) ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) + #if defined(GDK_WINDOWING_WAYLAND) + if (GDK_IS_WAYLAND_DISPLAY(gdk_display_get_default())) { + gtk_use_gl_area = true; +diff --git a/ui/meson.build b/ui/meson.build +index e8d3ff41b9..effa7e28d6 100644 +--- a/ui/meson.build ++++ b/ui/meson.build +@@ -15,7 +15,9 @@ softmmu_ss.add(files( + softmmu_ss.add([spice_headers, files('spice-module.c')]) + + softmmu_ss.add(when: 'CONFIG_LINUX', if_true: files('input-linux.c')) +-softmmu_ss.add(when: cocoa, if_true: files('cocoa.m')) ++softmmu_ss.add(when: cocoa, ++ if_true: files('cocoa/main.m', 'cocoa/view.m', ++ 'cocoa/app_controller.m')) + + vnc_ss = ss.source_set() + vnc_ss.add(files( +@@ -47,13 +49,15 @@ if config_host.has_key('CONFIG_OPENGL') + opengl_ss = ss.source_set() + opengl_ss.add(gbm) + opengl_ss.add(when: [opengl, pixman, 'CONFIG_OPENGL'], +- if_true: files('shader.c', 'console-gl.c', 'egl-helpers.c', 'egl-context.c')) ++ if_true: files('shader.c', 'console-gl.c')) ++ opengl_ss.add(when: [opengl, pixman, 'CONFIG_OPENGL', 'CONFIG_EGL'], ++ if_true: files('egl-helpers.c', 'egl-context.c')) + ui_modules += {'opengl' : opengl_ss} + endif + + if config_host.has_key('CONFIG_OPENGL') and gbm.found() + egl_headless_ss = ss.source_set() +- egl_headless_ss.add(when: [opengl, gbm, pixman, 'CONFIG_OPENGL'], ++ egl_headless_ss.add(when: [opengl, gbm, pixman, 'CONFIG_OPENGL', 'CONFIG_EGL'], + if_true: files('egl-headless.c')) + ui_modules += {'egl-headless' : egl_headless_ss} + endif +@@ -64,8 +68,10 @@ if gtk.found() + gtk_ss = ss.source_set() + gtk_ss.add(gtk, vte, pixman, files('gtk.c')) + gtk_ss.add(when: x11, if_true: files('x_keymap.c')) +- gtk_ss.add(when: [opengl, 'CONFIG_OPENGL'], if_true: files('gtk-gl-area.c')) +- gtk_ss.add(when: [x11, opengl, 'CONFIG_OPENGL'], if_true: files('gtk-egl.c')) ++ gtk_ss.add(when: [opengl, 'CONFIG_OPENGL', 'CONFIG_EGL'], ++ if_true: files('gtk-gl-area.c')) ++ gtk_ss.add(when: [x11, opengl, 'CONFIG_OPENGL', 'CONFIG_EGL'], ++ if_true: files('gtk-egl.c')) + ui_modules += {'gtk' : gtk_ss} + endif + +@@ -78,7 +84,7 @@ if sdl.found() + 'sdl2-input.c', + 'sdl2.c', + )) +- sdl_ss.add(when: [opengl, 'CONFIG_OPENGL'], if_true: files('sdl2-gl.c')) ++ sdl_ss.add(when: [opengl, 'CONFIG_OPENGL', 'CONFIG_EGL'], if_true: files('sdl2-gl.c')) + sdl_ss.add(when: x11, if_true: files('x_keymap.c')) + ui_modules += {'sdl' : sdl_ss} + endif +diff --git a/ui/sdl2-gl.c b/ui/sdl2-gl.c +index a21d2deed9..0decb5c255 100644 +--- a/ui/sdl2-gl.c ++++ b/ui/sdl2-gl.c +@@ -133,10 +133,9 @@ void sdl2_gl_redraw(struct sdl2_console *scon) + } + } + +-QEMUGLContext sdl2_gl_create_context(DisplayChangeListener *dcl, +- QEMUGLParams *params) ++QEMUGLContext sdl2_gl_create_context(void *dg, QEMUGLParams *params) + { +- struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl); ++ struct sdl2_console *scon = dg; + SDL_GLContext ctx; + + assert(scon->opengl); +@@ -168,17 +167,16 @@ QEMUGLContext sdl2_gl_create_context(DisplayChangeListener *dcl, + return (QEMUGLContext)ctx; + } + +-void sdl2_gl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx) ++void sdl2_gl_destroy_context(void *dg, QEMUGLContext ctx) + { + SDL_GLContext sdlctx = (SDL_GLContext)ctx; + + SDL_GL_DeleteContext(sdlctx); + } + +-int sdl2_gl_make_context_current(DisplayChangeListener *dcl, +- QEMUGLContext ctx) ++int sdl2_gl_make_context_current(void *dg, QEMUGLContext ctx) + { +- struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl); ++ struct sdl2_console *scon = dg; + SDL_GLContext sdlctx = (SDL_GLContext)ctx; + + assert(scon->opengl); +@@ -186,9 +184,9 @@ int sdl2_gl_make_context_current(DisplayChangeListener *dcl, + return SDL_GL_MakeCurrent(scon->real_window, sdlctx); + } + +-void sdl2_gl_scanout_disable(DisplayChangeListener *dcl) ++void sdl2_gl_scanout_disable(void *dg) + { +- struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl); ++ struct sdl2_console *scon = dg; + + assert(scon->opengl); + scon->w = 0; +@@ -196,17 +194,30 @@ void sdl2_gl_scanout_disable(DisplayChangeListener *dcl) + sdl2_set_scanout_mode(scon, false); + } + +-void sdl2_gl_scanout_texture(DisplayChangeListener *dcl, ++bool sdl2_gl_scanout_get_enabled(void *dg) ++{ ++ return ((struct sdl2_console *)dg)->scanout_mode; ++} ++ ++void sdl2_gl_scanout_texture(void *dg, + uint32_t backing_id, +- bool backing_y_0_top, +- uint32_t backing_width, +- uint32_t backing_height, ++ DisplayGLTextureBorrower backing_borrow, + uint32_t x, uint32_t y, + uint32_t w, uint32_t h) + { +- struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl); ++ struct sdl2_console *scon = dg; ++ bool backing_y_0_top; ++ uint32_t backing_width; ++ uint32_t backing_height; + + assert(scon->opengl); ++ ++ GLuint backing_texture = backing_borrow(backing_id, &backing_y_0_top, ++ &backing_width, &backing_height); ++ if (texture) { ++ return; ++ } ++ + scon->x = x; + scon->y = y; + scon->w = w; +@@ -217,7 +228,7 @@ void sdl2_gl_scanout_texture(DisplayChangeListener *dcl, + + sdl2_set_scanout_mode(scon, true); + egl_fb_setup_for_tex(&scon->guest_fb, backing_width, backing_height, +- backing_id, false); ++ backing_texture, false); + } + + void sdl2_gl_scanout_flush(DisplayChangeListener *dcl, +diff --git a/ui/sdl2.c b/ui/sdl2.c +index a203cb0239..fa88eb437a 100644 +--- a/ui/sdl2.c ++++ b/ui/sdl2.c +@@ -85,7 +85,7 @@ void sdl2_window_create(struct sdl2_console *scon) + if (scon->hidden) { + flags |= SDL_WINDOW_HIDDEN; + } +-#ifdef CONFIG_OPENGL ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) + if (scon->opengl) { + flags |= SDL_WINDOW_OPENGL; + } +@@ -129,7 +129,7 @@ void sdl2_window_resize(struct sdl2_console *scon) + static void sdl2_redraw(struct sdl2_console *scon) + { + if (scon->opengl) { +-#ifdef CONFIG_OPENGL ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) + sdl2_gl_redraw(scon); + #endif + } else { +@@ -768,7 +768,16 @@ static const DisplayChangeListenerOps dcl_2d_ops = { + .dpy_cursor_define = sdl_mouse_define, + }; + +-#ifdef CONFIG_OPENGL ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) ++static const DisplayGLOps dg_gl_ops = { ++ .dpy_gl_ctx_create = sdl2_gl_create_context, ++ .dpy_gl_ctx_destroy = sdl2_gl_destroy_context, ++ .dpy_gl_ctx_make_current = sdl2_gl_make_context_current, ++ .dpy_gl_scanout_get_enabled = sdl2_gl_scanout_get_enabled, ++ .dpy_gl_scanout_disable = sdl2_gl_scanout_disable, ++ .dpy_gl_scanout_texture = sdl2_gl_scanout_texture, ++}; ++ + static const DisplayChangeListenerOps dcl_gl_ops = { + .dpy_name = "sdl2-gl", + .dpy_gfx_update = sdl2_gl_update, +@@ -778,11 +787,6 @@ static const DisplayChangeListenerOps dcl_gl_ops = { + .dpy_mouse_set = sdl_mouse_warp, + .dpy_cursor_define = sdl_mouse_define, + +- .dpy_gl_ctx_create = sdl2_gl_create_context, +- .dpy_gl_ctx_destroy = sdl2_gl_destroy_context, +- .dpy_gl_ctx_make_current = sdl2_gl_make_context_current, +- .dpy_gl_scanout_disable = sdl2_gl_scanout_disable, +- .dpy_gl_scanout_texture = sdl2_gl_scanout_texture, + .dpy_gl_update = sdl2_gl_scanout_flush, + }; + #endif +@@ -791,7 +795,7 @@ static void sdl2_display_early_init(DisplayOptions *o) + { + assert(o->type == DISPLAY_TYPE_SDL); + if (o->has_gl && o->gl) { +-#ifdef CONFIG_OPENGL ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) + display_opengl = 1; + #endif + } +@@ -834,6 +838,12 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o) + + gui_fullscreen = o->has_full_screen && o->full_screen; + ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) ++ if (display_opengl) { ++ register_displayglops(&dg_gl_ops); ++ } ++#endif ++ + for (i = 0;; i++) { + QemuConsole *con = qemu_console_lookup_by_index(i); + if (!con) { +@@ -854,7 +864,7 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o) + } + sdl2_console[i].idx = i; + sdl2_console[i].opts = o; +-#ifdef CONFIG_OPENGL ++#if defined(CONFIG_OPENGL) && defined(CONFIG_EGL) + sdl2_console[i].opengl = display_opengl; + sdl2_console[i].dcl.ops = display_opengl ? &dcl_gl_ops : &dcl_2d_ops; + #else +@@ -863,6 +873,7 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o) + #endif + sdl2_console[i].dcl.con = con; + sdl2_console[i].kbd = qkbd_state_init(con); ++ console_set_displayglcontext(con, sdl2_console + i); + register_displaychangelistener(&sdl2_console[i].dcl); + + #if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_X11) +diff --git a/ui/shader.c b/ui/shader.c +index e8b8d321b7..ae1784e7c4 100644 +--- a/ui/shader.c ++++ b/ui/shader.c +@@ -150,11 +150,19 @@ static GLuint qemu_gl_create_compile_link_program(const GLchar *vert_src, + QemuGLShader *qemu_gl_init_shader(void) + { + QemuGLShader *gls = g_new0(QemuGLShader, 1); +- ++ const char *header = epoxy_is_desktop_gl() ? "#version 140\n" : "#version 300 es\n"; ++ char vert_src[256]; ++ char frag_src[256]; ++ char *vert_src_body = stpcpy(vert_src, header); ++ char *frag_src_body = stpcpy(frag_src, header); ++ ++ strcpy(vert_src_body, texture_blit_vert_src); ++ strcpy(frag_src_body, texture_blit_frag_src); + gls->texture_blit_prog = qemu_gl_create_compile_link_program +- (texture_blit_vert_src, texture_blit_frag_src); ++ (vert_src, frag_src); ++ strcpy(vert_src_body, texture_blit_flip_vert_src); + gls->texture_blit_flip_prog = qemu_gl_create_compile_link_program +- (texture_blit_flip_vert_src, texture_blit_frag_src); ++ (vert_src, frag_src); + if (!gls->texture_blit_prog || !gls->texture_blit_flip_prog) { + exit(1); + } +diff --git a/ui/shader/texture-blit-flip.vert b/ui/shader/texture-blit-flip.vert +index ba081fa5a6..1e4ac4c947 100644 +--- a/ui/shader/texture-blit-flip.vert ++++ b/ui/shader/texture-blit-flip.vert +@@ -1,6 +1,3 @@ +- +-#version 300 es +- + in vec2 in_position; + out vec2 ex_tex_coord; + +diff --git a/ui/shader/texture-blit.frag b/ui/shader/texture-blit.frag +index bfa202c22b..bd296a2ffb 100644 +--- a/ui/shader/texture-blit.frag ++++ b/ui/shader/texture-blit.frag +@@ -1,6 +1,3 @@ +- +-#version 300 es +- + uniform sampler2D image; + in mediump vec2 ex_tex_coord; + out mediump vec4 out_frag_color; +diff --git a/ui/shader/texture-blit.vert b/ui/shader/texture-blit.vert +index 6fe2744d68..ae205f6377 100644 +--- a/ui/shader/texture-blit.vert ++++ b/ui/shader/texture-blit.vert +@@ -1,6 +1,3 @@ +- +-#version 300 es +- + in vec2 in_position; + out vec2 ex_tex_coord; + +diff --git a/ui/spice-display.c b/ui/spice-display.c +index d22781a23d..2d0c2254f6 100644 +--- a/ui/spice-display.c ++++ b/ui/spice-display.c +@@ -925,18 +925,24 @@ static void qemu_spice_gl_scanout_disable(DisplayChangeListener *dcl) + } + + static void qemu_spice_gl_scanout_texture(DisplayChangeListener *dcl, +- uint32_t tex_id, +- bool y_0_top, +- uint32_t backing_width, +- uint32_t backing_height, ++ uint32_t backing_id, ++ DisplayGLTextureBorrower backing_borrow, + uint32_t x, uint32_t y, + uint32_t w, uint32_t h) + { + SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl); + EGLint stride = 0, fourcc = 0; + int fd = -1; ++ bool y_0_top; ++ uint32_t backing_width; ++ uint32_t backing_height; ++ ++ GLuint tex_id = backing_borrow(backing_id, &y_0_top, ++ &backing_width, &backing_height); ++ if (!tex_id) { ++ return; ++ } + +- assert(tex_id); + fd = egl_get_fd_for_texture(tex_id, &stride, &fourcc, NULL); + if (fd < 0) { + fprintf(stderr, "%s: failed to get fd for texture\n", __func__); +diff --git a/util/module.c b/util/module.c +index 7661d0f623..d1e540c7cc 100644 +--- a/util/module.c ++++ b/util/module.c +@@ -183,10 +183,13 @@ static const struct { + { "ui-spice-app", "chardev-spice" }, + + #ifdef CONFIG_OPENGL ++#ifdef CONFIG_EGL + { "ui-egl-headless", "ui-opengl" }, + { "ui-gtk", "ui-opengl" }, + { "ui-sdl", "ui-opengl" }, + { "ui-spice-core", "ui-opengl" }, ++#endif ++ { "ui-cocoa", "ui-opengl" }, + #endif + }; + #endif