Import QEMU upstream snapshot d2e570c

Upstream: https://gitlab.com/qemu-project/qemu.git

Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
This commit is contained in:
2026-08-31 02:15:30 +02:00
commit cf256aa081
11315 changed files with 3598369 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
config WHPX
bool
config NVMM
bool
config HVF
bool
config TCG
bool
config KVM
bool
config MSHV
bool
config NITRO
bool
config XEN
bool
select FSDEV_9P if VIRTFS
select PCI_EXPRESS_GENERIC_BRIDGE
select XEN_BUS
+155
View File
@@ -0,0 +1,155 @@
/*
* Lock to inhibit accelerator ioctls
*
* Copyright (c) 2022 Red Hat Inc.
*
* Author: Emanuele Giuseppe Esposito <[email protected]>
*
* 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/lockcnt.h"
#include "qemu/thread.h"
#include "qemu/main-loop.h"
#include "hw/core/cpu.h"
#include "system/accel-blocker.h"
static QemuLockCnt accel_in_ioctl_lock;
static QemuEvent accel_in_ioctl_event;
void accel_blocker_init(void)
{
qemu_lockcnt_init(&accel_in_ioctl_lock);
qemu_event_init(&accel_in_ioctl_event, false);
}
void accel_ioctl_begin(void)
{
if (likely(bql_locked())) {
return;
}
/* block if lock is taken in kvm_ioctl_inhibit_begin() */
qemu_lockcnt_inc(&accel_in_ioctl_lock);
}
void accel_ioctl_end(void)
{
if (likely(bql_locked())) {
return;
}
qemu_lockcnt_dec(&accel_in_ioctl_lock);
/* change event to SET. If event was BUSY, wake up all waiters */
qemu_event_set(&accel_in_ioctl_event);
}
void accel_cpu_ioctl_begin(CPUState *cpu)
{
if (unlikely(bql_locked())) {
return;
}
/* block if lock is taken in kvm_ioctl_inhibit_begin() */
qemu_lockcnt_inc(&cpu->in_ioctl_lock);
}
void accel_cpu_ioctl_end(CPUState *cpu)
{
if (unlikely(bql_locked())) {
return;
}
qemu_lockcnt_dec(&cpu->in_ioctl_lock);
/* change event to SET. If event was BUSY, wake up all waiters */
qemu_event_set(&accel_in_ioctl_event);
}
static bool accel_has_to_wait(void)
{
CPUState *cpu;
bool needs_to_wait = false;
CPU_FOREACH(cpu) {
if (qemu_lockcnt_count(&cpu->in_ioctl_lock)) {
/* exit the ioctl, if vcpu is running it */
qemu_cpu_kick(cpu);
needs_to_wait = true;
}
}
return needs_to_wait || qemu_lockcnt_count(&accel_in_ioctl_lock);
}
void accel_ioctl_inhibit_begin(void)
{
CPUState *cpu;
/*
* We allow to inhibit only when holding the BQL, so we can identify
* when an inhibitor wants to issue an ioctl easily.
*/
g_assert(bql_locked());
/* Block further invocations of the ioctls outside the BQL. */
CPU_FOREACH(cpu) {
qemu_lockcnt_lock(&cpu->in_ioctl_lock);
}
qemu_lockcnt_lock(&accel_in_ioctl_lock);
/* Keep waiting until there are running ioctls */
while (true) {
/* Reset event to FREE. */
qemu_event_reset(&accel_in_ioctl_event);
if (accel_has_to_wait()) {
/*
* If event is still FREE, and there are ioctls still in progress,
* wait.
*
* If an ioctl finishes before qemu_event_wait(), it will change
* the event state to SET. This will prevent qemu_event_wait() from
* blocking, but it's not a problem because if other ioctls are
* still running the loop will iterate once more and reset the event
* status to FREE so that it can wait properly.
*
* If an ioctls finishes while qemu_event_wait() is blocking, then
* it will be waken up, but also here the while loop makes sure
* to re-enter the wait if there are other running ioctls.
*/
qemu_event_wait(&accel_in_ioctl_event);
} else {
/* No ioctl is running */
return;
}
}
}
void accel_ioctl_inhibit_end(void)
{
CPUState *cpu;
qemu_lockcnt_unlock(&accel_in_ioctl_lock);
CPU_FOREACH(cpu) {
qemu_lockcnt_unlock(&cpu->in_ioctl_lock);
}
}
+146
View File
@@ -0,0 +1,146 @@
/*
* QEMU accel class, components common to system emulation and user mode
*
* Copyright (c) 2003-2008 Fabrice Bellard
* Copyright (c) 2014 Red Hat Inc.
*
* SPDX-License-Identifier: MIT
*/
#include "qemu/osdep.h"
#include "qemu/accel.h"
#include "qemu/target-info.h"
#include "accel/accel-ops.h"
#include "accel/accel-cpu.h"
#include "accel/accel-cpu-ops.h"
#include "accel-internal.h"
/* Lookup AccelClass from opt_name. Returns NULL if not found */
AccelClass *accel_find(const char *opt_name)
{
char *class_name = g_strdup_printf(ACCEL_CLASS_NAME("%s"), opt_name);
AccelClass *ac = ACCEL_CLASS(module_object_class_by_name(class_name));
g_free(class_name);
return ac;
}
/* Return the name of the current accelerator */
const char *current_accel_name(void)
{
AccelClass *ac = ACCEL_GET_CLASS(current_accel());
return ac->name;
}
static void accel_init_cpu_int_aux(ObjectClass *klass, void *opaque)
{
CPUClass *cc = CPU_CLASS(klass);
AccelCPUClass *accel_cpu = opaque;
cc->accel_cpu = accel_cpu;
if (accel_cpu->cpu_class_init) {
accel_cpu->cpu_class_init(cc);
}
}
/* initialize the arch-specific accel CpuClass interfaces */
static void accel_init_cpu_interfaces(AccelClass *ac)
{
const char *ac_name; /* AccelClass name */
char *acc_name; /* AccelCPUClass name */
ObjectClass *acc; /* AccelCPUClass */
const char *cpu_resolving_type = target_cpu_type();
ac_name = object_class_get_name(OBJECT_CLASS(ac));
g_assert(ac_name != NULL);
acc_name = g_strdup_printf("%s-%s", ac_name, cpu_resolving_type);
acc = object_class_by_name(acc_name);
g_free(acc_name);
if (acc) {
object_class_foreach(accel_init_cpu_int_aux,
cpu_resolving_type, false, acc);
}
}
void accel_init_interfaces(AccelClass *ac)
{
accel_init_ops_interfaces(ac);
accel_init_cpu_interfaces(ac);
}
bool accel_supports_guest_debug(AccelState *accel)
{
return accel->gdbstub.sstep_flags & SSTEP_ENABLE;
}
void accel_cpu_instance_init(CPUState *cpu)
{
if (cpu->cc->accel_cpu && cpu->cc->accel_cpu->cpu_instance_init) {
cpu->cc->accel_cpu->cpu_instance_init(cpu);
}
}
bool accel_cpu_common_realize(CPUState *cpu, Error **errp)
{
AccelState *accel = current_accel();
AccelClass *acc = ACCEL_GET_CLASS(accel);
/* target specific realization */
if (cpu->cc->accel_cpu
&& cpu->cc->accel_cpu->cpu_target_realize
&& !cpu->cc->accel_cpu->cpu_target_realize(cpu, errp)) {
return false;
}
/* generic realization */
if (acc->cpu_common_realize && !acc->cpu_common_realize(cpu, errp)) {
return false;
}
if (acc->ops
&& acc->ops->cpu_target_realize
&& !acc->ops->cpu_target_realize(cpu, errp)) {
return false;
}
return true;
}
void accel_cpu_common_unrealize(CPUState *cpu)
{
AccelState *accel = current_accel();
AccelClass *acc = ACCEL_GET_CLASS(accel);
/* generic unrealization */
if (acc->cpu_common_unrealize) {
acc->cpu_common_unrealize(cpu);
}
}
static const TypeInfo accel_types[] = {
{
.name = TYPE_ACCEL,
.parent = TYPE_OBJECT,
.class_size = sizeof(AccelClass),
.instance_size = sizeof(AccelState),
.abstract = true,
},
};
DEFINE_TYPES(accel_types)
static void register_accel_target_type(void)
{
g_autofree char *name = g_strconcat("accel-", target_cpu_type(), NULL);
const TypeInfo accel_cpu_type = {
.name = name,
.parent = TYPE_OBJECT,
.abstract = true,
.class_size = sizeof(AccelCPUClass),
};
type_register_static(&accel_cpu_type);
}
type_init(register_accel_target_type);
+17
View File
@@ -0,0 +1,17 @@
/*
* QEMU accel internal functions
*
* Copyright 2021 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 ACCEL_INTERNAL_H
#define ACCEL_INTERNAL_H
#include "qemu/accel.h"
void accel_init_ops_interfaces(AccelClass *ac);
#endif /* ACCEL_SYSTEM_H */
+109
View File
@@ -0,0 +1,109 @@
/*
* Accelerated irqchip abstraction
*
* Copyright Microsoft, Corp. 2025
*
* Authors: Ziqiao Zhou <[email protected]>
* Magnus Kulke <[email protected]>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "qemu/error-report.h"
#include "hw/pci/msi.h"
#include "system/kvm.h"
#include "system/mshv.h"
#include "system/accel-irq.h"
int accel_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev)
{
if (mshv_msi_via_irqfd_enabled()) {
return mshv_irqchip_add_msi_route(c, vector, dev);
}
if (kvm_enabled()) {
return kvm_irqchip_add_msi_route(c, vector, dev);
}
return -ENOSYS;
}
int accel_irqchip_update_msi_route(int vector, MSIMessage msg, PCIDevice *dev)
{
if (mshv_msi_via_irqfd_enabled()) {
return mshv_irqchip_update_msi_route(vector, msg, dev);
}
if (kvm_enabled()) {
return kvm_irqchip_update_msi_route(kvm_state, vector, msg, dev);
}
return -ENOSYS;
}
void accel_irqchip_commit_route_changes(AccelRouteChange *c)
{
if (c->changes) {
accel_irqchip_commit_routes();
c->changes = 0;
}
}
void accel_irqchip_commit_routes(void)
{
if (mshv_msi_via_irqfd_enabled()) {
mshv_irqchip_commit_routes(mshv_state);
}
if (kvm_enabled()) {
kvm_irqchip_commit_routes(kvm_state);
}
}
void accel_irqchip_release_virq(int virq)
{
if (mshv_msi_via_irqfd_enabled()) {
mshv_irqchip_release_virq(mshv_state, virq);
}
if (kvm_enabled()) {
kvm_irqchip_release_virq(kvm_state, virq);
}
}
int accel_irqchip_add_irqfd_notifier_gsi(EventNotifier *n, EventNotifier *rn,
int virq)
{
if (mshv_msi_via_irqfd_enabled()) {
return mshv_irqchip_add_irqfd_notifier_gsi(n, rn, virq);
}
if (kvm_enabled()) {
return kvm_irqchip_add_irqfd_notifier_gsi(kvm_state, n, rn, virq);
}
return -ENOSYS;
}
int accel_irqchip_remove_irqfd_notifier_gsi(EventNotifier *n, int virq)
{
if (mshv_msi_via_irqfd_enabled()) {
return mshv_irqchip_remove_irqfd_notifier_gsi(n, virq);
}
if (kvm_enabled()) {
return kvm_irqchip_remove_irqfd_notifier_gsi(kvm_state, n, virq);
}
return -ENOSYS;
}
inline AccelRouteChange accel_irqchip_begin_route_changes(void)
{
if (mshv_msi_via_irqfd_enabled()) {
return (AccelRouteChange) {
.accel = ACCEL(mshv_state),
.changes = 0,
};
}
if (kvm_enabled()) {
return (AccelRouteChange) {
.accel = ACCEL(kvm_state),
.changes = 0,
};
}
error_report("can't initiate route change, no accel irqchip available");
abort();
}
+35
View File
@@ -0,0 +1,35 @@
/*
* QMP commands related to accelerators
*
* Copyright (c) Linaro
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "qemu/accel.h"
#include "qapi/type-helpers.h"
#include "qapi/qapi-commands-accelerator.h"
#include "accel/accel-ops.h"
#include "accel/accel-cpu-ops.h"
#include "hw/core/cpu.h"
HumanReadableText *qmp_x_accel_stats(Error **errp)
{
AccelState *accel = current_accel();
AccelClass *acc = ACCEL_GET_CLASS(accel);
g_autoptr(GString) buf = g_string_new("");
if (acc->get_stats) {
acc->get_stats(accel, buf);
}
if (acc->ops->get_vcpu_stats) {
CPUState *cpu;
CPU_FOREACH(cpu) {
acc->ops->get_vcpu_stats(cpu, buf);
}
}
return human_readable_text_from_str(buf);
}
+126
View File
@@ -0,0 +1,126 @@
/*
* QEMU accel class, system emulation components
*
* Copyright (c) 2003-2008 Fabrice Bellard
* Copyright (c) 2014 Red Hat Inc.
*
* 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/accel.h"
#include "qom/compat-properties.h"
#include "qapi/qapi-commands-accelerator.h"
#include "monitor/monitor.h"
#include "hw/core/boards.h"
#include "hw/core/cpu.h"
#include "accel/accel-ops.h"
#include "accel/accel-cpu-ops.h"
#include "system/cpus.h"
#include "qemu/error-report.h"
#include "accel-internal.h"
int accel_init_machine(AccelState *accel, MachineState *ms)
{
AccelClass *acc = ACCEL_GET_CLASS(accel);
int ret;
ms->accelerator = accel;
*(acc->allowed) = true;
ret = acc->init_machine(accel, ms);
if (ret < 0) {
ms->accelerator = NULL;
*(acc->allowed) = false;
object_unref(OBJECT(accel));
} else {
object_set_accelerator_compat_props(acc->compat_props);
}
return ret;
}
AccelState *current_accel(void)
{
return current_machine->accelerator;
}
void accel_setup_post(MachineState *ms)
{
AccelState *accel = ms->accelerator;
AccelClass *acc = ACCEL_GET_CLASS(accel);
if (acc->setup_post) {
acc->setup_post(accel);
}
}
void accel_pre_resume(MachineState *ms, bool step_pending)
{
AccelState *accel = ms->accelerator;
AccelClass *acc = ACCEL_GET_CLASS(accel);
if (acc->pre_resume_vm) {
acc->pre_resume_vm(accel, step_pending);
}
}
/* initialize the arch-independent accel operation interfaces */
void accel_init_ops_interfaces(AccelClass *ac)
{
const char *ac_name;
char *ops_name;
ObjectClass *oc;
AccelOpsClass *ops;
ac_name = object_class_get_name(OBJECT_CLASS(ac));
g_assert(ac_name != NULL);
ops_name = g_strdup_printf("%s" ACCEL_OPS_SUFFIX, ac_name);
oc = module_object_class_by_name(ops_name);
if (!oc) {
error_report("fatal: could not load module for type '%s'", ops_name);
exit(1);
}
g_free(ops_name);
/*
* all accelerators need to define ops, providing at least a mandatory
* non-NULL create_vcpu_thread operation.
*/
ops = ACCEL_OPS_CLASS(oc);
ac->ops = ops;
if (ops->ops_init) {
ops->ops_init(ac);
}
cpus_register_accel(ops);
}
static void accel_ops_class_init(ObjectClass *oc, const void *data)
{
monitor_register_hmp_info_hrt("accel", qmp_x_accel_stats);
}
static const TypeInfo accel_ops_type_info = {
.name = TYPE_ACCEL_OPS,
.parent = TYPE_OBJECT,
.abstract = true,
.class_size = sizeof(AccelOpsClass),
.class_init = accel_ops_class_init,
};
static void accel_system_register_types(void)
{
type_register_static(&accel_ops_type_info);
}
type_init(accel_system_register_types);
+30
View File
@@ -0,0 +1,30 @@
/*
* QEMU accel class, user-mode components
*
* Copyright 2021 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.
*/
#include "qemu/osdep.h"
#include "qemu/accel.h"
#include "accel-internal.h"
void accel_init_ops_interfaces(AccelClass *ac)
{
/* nothing */
}
AccelState *current_accel(void)
{
static AccelState *accel;
if (!accel) {
AccelClass *ac = accel_find("tcg");
g_assert(ac != NULL);
accel = ACCEL(object_new_with_class(OBJECT_CLASS(ac)));
}
return accel;
}
+79
View File
@@ -0,0 +1,79 @@
/*
* Dummy cpu thread code
*
* Copyright IBM, Corp. 2011
*
* Authors:
* Anthony Liguori <[email protected]>
*
* 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/rcu.h"
#include "system/cpus.h"
#include "qemu/guest-random.h"
#include "qemu/main-loop.h"
#include "hw/core/cpu.h"
#include "accel/dummy-cpus.h"
static void *dummy_cpu_thread_fn(void *arg)
{
CPUState *cpu = arg;
rcu_register_thread();
bql_lock();
qemu_thread_get_self(cpu->thread);
cpu->thread_id = qemu_get_thread_id();
current_cpu = cpu;
#ifndef _WIN32
sigset_t waitset;
int r;
sigemptyset(&waitset);
sigaddset(&waitset, SIG_IPI);
#endif
/* signal CPU creation */
cpu_thread_signal_created(cpu);
qemu_guest_random_seed_thread_part2(cpu->random_seed);
do {
qemu_process_cpu_events(cpu);
bql_unlock();
#ifndef _WIN32
do {
int sig;
r = sigwait(&waitset, &sig);
} while (r == -1 && (errno == EAGAIN || errno == EINTR));
if (r == -1) {
perror("sigwait");
exit(1);
}
#else
qemu_sem_wait(&cpu->sem);
#endif
bql_lock();
} while (!cpu->unplug);
bql_unlock();
rcu_unregister_thread();
return NULL;
}
void dummy_start_vcpu_thread(CPUState *cpu)
{
char thread_name[VCPU_THREAD_NAME_SIZE];
snprintf(thread_name, VCPU_THREAD_NAME_SIZE, "CPU %d/DUMMY",
cpu->cpu_index);
qemu_thread_create(cpu->thread, thread_name, dummy_cpu_thread_fn, cpu,
QEMU_THREAD_JOINABLE);
#ifdef _WIN32
qemu_sem_init(&cpu->sem, 0);
#endif
}
+14
View File
@@ -0,0 +1,14 @@
/*
* Dummy cpu thread code
*
* Copyright IBM, Corp. 2011
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef ACCEL_DUMMY_CPUS_H
#define ACCEL_DUMMY_CPUS_H
void dummy_start_vcpu_thread(CPUState *cpu);
#endif
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.hypervisor</key>
<true/>
</dict>
</plist>
+391
View File
@@ -0,0 +1,391 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* 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/guest-random.h"
#include "qemu/main-loop.h"
#include "qemu/queue.h"
#include "gdbstub/enums.h"
#include "exec/cpu-common.h"
#include "hw/core/cpu.h"
#include "accel/accel-cpu-ops.h"
#include "system/cpus.h"
#include "system/hvf.h"
#include "system/hvf_int.h"
#include <mach/mach_time.h>
HVFState *hvf_state;
static void do_hvf_cpu_synchronize_state(CPUState *cpu, run_on_cpu_data arg)
{
if (!cpu->vcpu_dirty) {
hvf_arch_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 dummy_signal(int sig)
{
}
static void do_hvf_get_vcpu_exec_time(CPUState *cpu, run_on_cpu_data arg)
{
int r = hv_vcpu_get_exec_time(cpu->accel->fd, arg.host_ptr);
assert_hvf_ok(r);
}
static void hvf_vcpu_destroy(CPUState *cpu)
{
hv_return_t ret = hv_vcpu_destroy(cpu->accel->fd);
assert_hvf_ok(ret);
hvf_arch_vcpu_destroy(cpu);
g_free(cpu->accel);
cpu->accel = NULL;
}
static int hvf_init_vcpu(CPUState *cpu)
{
int r;
cpu->accel = g_new0(AccelCPUState, 1);
/* init cpu signals */
struct sigaction sigact;
memset(&sigact, 0, sizeof(sigact));
sigact.sa_handler = dummy_signal;
sigaction(SIG_IPI, &sigact, NULL);
#ifdef __aarch64__
cpu->accel->guest_debug_enabled = false;
r = hv_vcpu_create(&cpu->accel->fd,
(hv_vcpu_exit_t **)&cpu->accel->exit, NULL);
#else
r = hv_vcpu_create(&cpu->accel->fd, HV_VCPU_DEFAULT);
#endif
assert_hvf_ok(r);
cpu->vcpu_dirty = true;
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();
bql_lock();
qemu_thread_get_self(cpu->thread);
cpu->thread_id = qemu_get_thread_id();
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 {
qemu_process_cpu_events(cpu);
if (cpu_can_run(cpu)) {
r = hvf_arch_vcpu_exec(cpu);
if (r == EXCP_DEBUG) {
cpu_handle_guest_debug(cpu);
}
}
} while (!cpu->unplug || cpu_can_run(cpu));
hvf_vcpu_destroy(cpu);
cpu_thread_signal_destroyed(cpu);
bql_unlock();
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());
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);
}
struct hvf_sw_breakpoint *hvf_find_sw_breakpoint(CPUState *cpu, vaddr pc)
{
struct hvf_sw_breakpoint *bp;
QTAILQ_FOREACH(bp, &hvf_state->hvf_sw_breakpoints, entry) {
if (bp->pc == pc) {
return bp;
}
}
return NULL;
}
int hvf_sw_breakpoints_active(CPUState *cpu)
{
return !QTAILQ_EMPTY(&hvf_state->hvf_sw_breakpoints);
}
static void do_hvf_update_guest_debug(CPUState *cpu, run_on_cpu_data arg)
{
hvf_arch_update_guest_debug(cpu);
}
int hvf_update_guest_debug(CPUState *cpu)
{
run_on_cpu(cpu, do_hvf_update_guest_debug, RUN_ON_CPU_NULL);
return 0;
}
static int hvf_insert_gdbstub_breakpoint(CPUState *cpu, GdbBreakpointType type,
vaddr addr, vaddr len)
{
struct hvf_sw_breakpoint *bp;
int err;
if (type == GDB_BREAKPOINT_SW) {
bp = hvf_find_sw_breakpoint(cpu, addr);
if (bp) {
bp->use_count++;
return 0;
}
bp = g_new(struct hvf_sw_breakpoint, 1);
bp->pc = addr;
bp->use_count = 1;
err = hvf_arch_insert_sw_breakpoint(cpu, bp);
if (err) {
g_free(bp);
return err;
}
QTAILQ_INSERT_HEAD(&hvf_state->hvf_sw_breakpoints, bp, entry);
} else {
err = hvf_arch_insert_gdbstub_hw_breakpoint(addr, len, type);
if (err) {
return err;
}
}
CPU_FOREACH(cpu) {
err = hvf_update_guest_debug(cpu);
if (err) {
return err;
}
}
return 0;
}
static int hvf_remove_gdbstub_breakpoint(CPUState *cpu, GdbBreakpointType type,
vaddr addr, vaddr len)
{
struct hvf_sw_breakpoint *bp;
int err;
if (type == GDB_BREAKPOINT_SW) {
bp = hvf_find_sw_breakpoint(cpu, addr);
if (!bp) {
return -ENOENT;
}
if (bp->use_count > 1) {
bp->use_count--;
return 0;
}
err = hvf_arch_remove_sw_breakpoint(cpu, bp);
if (err) {
return err;
}
QTAILQ_REMOVE(&hvf_state->hvf_sw_breakpoints, bp, entry);
g_free(bp);
} else {
err = hvf_arch_remove_gdbstub_hw_breakpoint(addr, len, type);
if (err) {
return err;
}
}
CPU_FOREACH(cpu) {
err = hvf_update_guest_debug(cpu);
if (err) {
return err;
}
}
return 0;
}
static void hvf_remove_all_gdbstub_breakpoints(CPUState *cpu)
{
struct hvf_sw_breakpoint *bp, *next;
CPUState *tmpcpu;
QTAILQ_FOREACH_SAFE(bp, &hvf_state->hvf_sw_breakpoints, entry, next) {
if (hvf_arch_remove_sw_breakpoint(cpu, bp) != 0) {
/* Try harder to find a CPU that currently sees the breakpoint. */
CPU_FOREACH(tmpcpu)
{
if (hvf_arch_remove_sw_breakpoint(tmpcpu, bp) == 0) {
break;
}
}
}
QTAILQ_REMOVE(&hvf_state->hvf_sw_breakpoints, bp, entry);
g_free(bp);
}
hvf_arch_remove_all_gdbstub_hw_breakpoints();
CPU_FOREACH(cpu) {
hvf_update_guest_debug(cpu);
}
}
static void hvf_get_vcpu_stats(CPUState *cpu, GString *buf)
{
uint64_t time_mach; /* units of mach_absolute_time() */
run_on_cpu(cpu, do_hvf_get_vcpu_exec_time, RUN_ON_CPU_HOST_PTR(&time_mach));
mach_timebase_info_data_t timebase;
mach_timebase_info(&timebase);
uint64_t time_ns = time_mach * timebase.numer / timebase.denom;
g_string_append_printf(buf, "HVF cumulative execution time: %llu.%.3llus\n",
time_ns / 1000000000,
(time_ns % 1000000000) / 1000000);
}
static void hvf_accel_ops_class_init(ObjectClass *oc, const void *data)
{
AccelOpsClass *ops = ACCEL_OPS_CLASS(oc);
ops->cpu_target_realize = hvf_arch_cpu_realize;
ops->create_vcpu_thread = hvf_start_vcpu_thread;
ops->kick_vcpu_thread = hvf_kick_vcpu_thread;
ops->handle_interrupt = generic_handle_interrupt;
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;
ops->insert_gdbstub_breakpoint = hvf_insert_gdbstub_breakpoint;
ops->remove_gdbstub_breakpoint = hvf_remove_gdbstub_breakpoint;
ops->remove_all_gdbstub_breakpoints = hvf_remove_all_gdbstub_breakpoints;
ops->update_guest_debug = hvf_update_guest_debug;
ops->get_vcpu_stats = hvf_get_vcpu_stats;
};
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);
+299
View File
@@ -0,0 +1,299 @@
/*
* 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/error-report.h"
#include "qapi/error.h"
#include "qapi/qapi-visit-common.h"
#include "accel/accel-ops.h"
#include "exec/cpu-common.h"
#include "system/address-spaces.h"
#include "system/memory.h"
#include "system/hvf.h"
#include "system/hvf_int.h"
#include "hw/core/cpu.h"
#include "hw/core/boards.h"
#include "trace.h"
bool hvf_allowed;
bool hvf_kernel_irqchip;
bool hvf_nested_virt;
static bool hvf_kernel_irqchip_override;
void hvf_nested_virt_enable(bool nested_virt)
{
hvf_nested_virt = nested_virt;
}
const char *hvf_return_string(hv_return_t ret)
{
switch (ret) {
case HV_SUCCESS: return "HV_SUCCESS";
case HV_ERROR: return "HV_ERROR";
case HV_BUSY: return "HV_BUSY";
case HV_BAD_ARGUMENT: return "HV_BAD_ARGUMENT";
case HV_NO_RESOURCES: return "HV_NO_RESOURCES";
case HV_NO_DEVICE: return "HV_NO_DEVICE";
case HV_UNSUPPORTED: return "HV_UNSUPPORTED";
case HV_DENIED: return "HV_DENIED";
default: return "[unknown hv_return value]";
}
}
void assert_hvf_ok_impl(hv_return_t ret, const char *file, unsigned int line,
const char *exp)
{
if (ret == HV_SUCCESS) {
return;
}
error_report("Error: %s = %s (0x%x, at %s:%u)",
exp, hvf_return_string(ret), ret, file, line);
abort();
}
static void do_hv_vm_protect(hwaddr start, size_t size,
hv_memory_flags_t flags)
{
intptr_t page_mask = qemu_real_host_page_mask();
hv_return_t ret;
trace_hvf_vm_protect(start, size, flags,
flags & HV_MEMORY_READ ? 'R' : '-',
flags & HV_MEMORY_WRITE ? 'W' : '-',
flags & HV_MEMORY_EXEC ? 'X' : '-');
g_assert(!((uintptr_t)start & ~page_mask));
g_assert(!(size & ~page_mask));
ret = hv_vm_protect(start, size, flags);
assert_hvf_ok(ret);
}
void hvf_protect_clean_range(hwaddr addr, size_t size)
{
do_hv_vm_protect(addr, size, HV_MEMORY_READ | HV_MEMORY_EXEC);
}
void hvf_unprotect_dirty_range(hwaddr addr, size_t size)
{
do_hv_vm_protect(addr, size,
HV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC);
}
static void hvf_set_phys_mem(MemoryRegionSection *section, bool add)
{
MemoryRegion *area = section->mr;
bool writable = !area->readonly && !area->rom_device;
hv_memory_flags_t flags;
uint64_t page_size = qemu_real_host_page_size();
uint64_t gpa = section->offset_within_address_space;
uint64_t size = int128_get64(section->size);
hv_return_t ret;
void *mem;
if (!memory_region_is_ram(area)) {
if (writable) {
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;
}
}
if (!QEMU_IS_ALIGNED(size, page_size) ||
!QEMU_IS_ALIGNED(gpa, page_size)) {
/* Not page aligned, so we can not map as RAM */
add = false;
}
if (!add) {
trace_hvf_vm_unmap(gpa, size);
ret = hv_vm_unmap(gpa, size);
assert_hvf_ok(ret);
return;
}
flags = HV_MEMORY_READ | HV_MEMORY_EXEC | (writable ? HV_MEMORY_WRITE : 0);
mem = memory_region_get_ram_ptr(area) + section->offset_within_region;
trace_hvf_vm_map(gpa, size, mem, flags,
flags & HV_MEMORY_READ ? 'R' : '-',
flags & HV_MEMORY_WRITE ? 'W' : '-',
flags & HV_MEMORY_EXEC ? 'X' : '-');
ret = hv_vm_map(mem, gpa, size, flags);
assert_hvf_ok(ret);
}
static void hvf_log_start(MemoryListener *listener,
MemoryRegionSection *section, int old, int new)
{
assert(new != 0);
if (old == 0) {
hvf_protect_clean_range(section->offset_within_address_space,
int128_get64(section->size));
}
}
static void hvf_log_stop(MemoryListener *listener,
MemoryRegionSection *section, int old, int new)
{
assert(old != 0);
if (new == 0) {
hvf_unprotect_dirty_range(section->offset_within_address_space,
int128_get64(section->size));
}
}
static void hvf_log_clear(MemoryListener *listener,
MemoryRegionSection *section)
{
/*
* The dirty page bits within section are being cleared.
* Some number of those pages may have been dirtied and
* the write permission enabled. Reset the range read-only.
*/
hvf_protect_clean_range(section->offset_within_address_space,
int128_get64(section->size));
}
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 = {
.name = "hvf",
.priority = MEMORY_LISTENER_PRIORITY_ACCEL,
.region_add = hvf_region_add,
.region_del = hvf_region_del,
.log_start = hvf_log_start,
.log_stop = hvf_log_stop,
.log_clear = hvf_log_clear,
};
static int hvf_accel_init(AccelState *as, MachineState *ms)
{
hv_return_t ret;
HVFState *s = HVF_STATE(as);
int pa_range = 36;
MachineClass *mc = MACHINE_GET_CLASS(ms);
if (mc->get_physical_address_range) {
pa_range = mc->get_physical_address_range(ms,
hvf_arch_get_default_ipa_bit_size(), hvf_arch_get_max_ipa_bit_size());
if (pa_range < 0) {
return -EINVAL;
}
}
if (mc->get_kernel_irqchip_default) {
bool kernel_irqchip_default = mc->get_kernel_irqchip_default(ms);
if (!hvf_kernel_irqchip_override) {
hvf_kernel_irqchip = kernel_irqchip_default;
}
}
ret = hvf_arch_vm_create(ms, (uint32_t)pa_range);
if (ret == HV_DENIED) {
error_report("Could not access HVF. Is the executable signed"
" with com.apple.security.hypervisor entitlement?");
exit(1);
}
assert_hvf_ok(ret);
as->gdbstub.sstep_flags = SSTEP_ENABLE | SSTEP_NOIRQ;
QTAILQ_INIT(&s->hvf_sw_breakpoints);
hvf_state = s;
memory_listener_register(&hvf_memory_listener, &address_space_memory);
return hvf_arch_init();
}
static void hvf_set_kernel_irqchip(Object *obj, Visitor *v,
const char *name, void *opaque,
Error **errp)
{
OnOffSplit mode;
hvf_kernel_irqchip_override = true;
if (!visit_type_OnOffSplit(v, name, &mode, errp)) {
return;
}
switch (mode) {
case ON_OFF_SPLIT_ON:
#ifdef HOST_X86_64
/* macOS 12 onwards exposes an HVF virtual APIC. */
error_setg(errp, "HVF: kernel irqchip is not currently implemented for x86.");
break;
#else
hvf_kernel_irqchip = true;
break;
#endif
case ON_OFF_SPLIT_OFF:
hvf_kernel_irqchip = false;
break;
case ON_OFF_SPLIT_SPLIT:
error_setg(errp, "HVF: split irqchip is not supported on HVF.");
break;
default:
/*
* The value was checked in visit_type_OnOffSplit() above. If
* we get here, then something is wrong in QEMU.
*/
abort();
}
}
static void hvf_accel_class_init(ObjectClass *oc, const void *data)
{
AccelClass *ac = ACCEL_CLASS(oc);
ac->name = "HVF";
ac->init_machine = hvf_accel_init;
ac->allowed = &hvf_allowed;
hvf_kernel_irqchip_override = false;
hvf_kernel_irqchip = false;
object_class_property_add(oc, "kernel-irqchip", "on|off|split",
NULL, hvf_set_kernel_irqchip,
NULL, NULL);
object_class_property_set_description(oc, "kernel-irqchip",
"Configure HVF irqchip");
}
static const TypeInfo hvf_accel_type = {
.name = TYPE_HVF_ACCEL,
.parent = TYPE_ACCEL,
.instance_size = sizeof(HVFState),
.class_init = hvf_accel_class_init,
};
static void hvf_type_init(void)
{
type_register_static(&hvf_accel_type);
}
type_init(hvf_type_init);
+4
View File
@@ -0,0 +1,4 @@
system_ss.add(when: 'CONFIG_HVF', if_true: files(
'hvf-all.c',
'hvf-accel-ops.c',
))
+8
View File
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# See docs/devel/tracing.rst for syntax documentation.
# hvf-accel-ops.c
hvf_vm_map(uint64_t paddr, uint64_t size, void *vaddr, uint8_t flags, const char r, const char w, const char e) "paddr:0x%016"PRIx64" size:0x%08"PRIx64" vaddr:%p flags:0x%02x/%c%c%c"
hvf_vm_unmap(uint64_t paddr, uint64_t size) "paddr:0x%016"PRIx64" size:0x%08"PRIx64
hvf_vm_protect(uint64_t paddr, size_t size, uint8_t flags, const char r, const char w, const char e) "paddr:0x%016"PRIx64" size:0x%08zx flags:0x%02x/%c%c%c"
+2
View File
@@ -0,0 +1,2 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "trace/trace-accel_hvf.h"
+128
View File
@@ -0,0 +1,128 @@
/*
* QEMU KVM support
*
* Copyright IBM, Corp. 2008
* Red Hat, Inc. 2008
*
* Authors:
* Anthony Liguori <[email protected]>
* Glauber Costa <[email protected]>
*
* 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/error-report.h"
#include "qemu/main-loop.h"
#include "exec/cpu-common.h"
#include "accel/accel-cpu-ops.h"
#include "system/kvm.h"
#include "system/kvm_int.h"
#include "system/runstate.h"
#include "system/cpus.h"
#include "qemu/guest-random.h"
#include "qapi/error.h"
#include <linux/kvm.h>
#include "kvm-cpus.h"
static void *kvm_vcpu_thread_fn(void *arg)
{
CPUState *cpu = arg;
int r;
rcu_register_thread();
bql_lock();
qemu_thread_get_self(cpu->thread);
cpu->thread_id = qemu_get_thread_id();
current_cpu = cpu;
r = kvm_init_vcpu(cpu, &error_fatal);
kvm_init_cpu_signals(cpu);
/* signal CPU creation */
cpu_thread_signal_created(cpu);
qemu_guest_random_seed_thread_part2(cpu->random_seed);
do {
qemu_process_cpu_events(cpu);
if (cpu_can_run(cpu)) {
r = kvm_cpu_exec(cpu);
if (r == EXCP_DEBUG) {
cpu_handle_guest_debug(cpu);
}
}
} while (!cpu->unplug || cpu_can_run(cpu));
kvm_destroy_vcpu(cpu);
cpu_thread_signal_destroyed(cpu);
bql_unlock();
rcu_unregister_thread();
return NULL;
}
static void kvm_start_vcpu_thread(CPUState *cpu)
{
char thread_name[VCPU_THREAD_NAME_SIZE];
snprintf(thread_name, VCPU_THREAD_NAME_SIZE, "CPU %d/KVM",
cpu->cpu_index);
qemu_thread_create(cpu->thread, thread_name, kvm_vcpu_thread_fn,
cpu, QEMU_THREAD_JOINABLE);
}
static bool kvm_vcpu_thread_is_idle(CPUState *cpu)
{
return !kvm_halt_in_kernel();
}
static bool kvm_cpus_are_resettable(void)
{
return !kvm_enabled() || !kvm_state->guest_state_protected;
}
#ifdef TARGET_KVM_HAVE_GUEST_DEBUG
static int kvm_update_guest_debug_ops(CPUState *cpu)
{
return kvm_update_guest_debug(cpu, 0);
}
#endif
static void kvm_accel_ops_class_init(ObjectClass *oc, const void *data)
{
AccelOpsClass *ops = ACCEL_OPS_CLASS(oc);
ops->create_vcpu_thread = kvm_start_vcpu_thread;
ops->cpu_thread_is_idle = kvm_vcpu_thread_is_idle;
ops->cpus_are_resettable = kvm_cpus_are_resettable;
ops->synchronize_post_reset = kvm_cpu_synchronize_post_reset;
ops->synchronize_post_init = kvm_cpu_synchronize_post_init;
ops->synchronize_state = kvm_cpu_synchronize_state;
ops->synchronize_pre_loadvm = kvm_cpu_synchronize_pre_loadvm;
ops->handle_interrupt = generic_handle_interrupt;
#ifdef TARGET_KVM_HAVE_GUEST_DEBUG
ops->update_guest_debug = kvm_update_guest_debug_ops;
ops->insert_gdbstub_breakpoint = kvm_insert_gdbstub_breakpoint;
ops->remove_gdbstub_breakpoint = kvm_remove_gdbstub_breakpoint;
ops->remove_all_gdbstub_breakpoints = kvm_remove_all_gdbstub_breakpoints;
#endif
}
static const TypeInfo kvm_accel_ops_type = {
.name = ACCEL_OPS_NAME("kvm"),
.parent = TYPE_ACCEL_OPS,
.class_init = kvm_accel_ops_class_init,
.abstract = true,
};
static void kvm_accel_ops_register_types(void)
{
type_register_static(&kvm_accel_ops_type);
}
type_init(kvm_accel_ops_register_types);
+4766
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
/*
* 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 KVM_CPUS_H
#define KVM_CPUS_H
#include "gdbstub/enums.h"
int kvm_init_vcpu(CPUState *cpu, Error **errp);
int kvm_cpu_exec(CPUState *cpu);
void kvm_destroy_vcpu(CPUState *cpu);
void kvm_cpu_synchronize_post_reset(CPUState *cpu);
void kvm_cpu_synchronize_post_init(CPUState *cpu);
void kvm_cpu_synchronize_pre_loadvm(CPUState *cpu);
int kvm_insert_gdbstub_breakpoint(CPUState *cpu, GdbBreakpointType type,
vaddr addr, vaddr len);
int kvm_remove_gdbstub_breakpoint(CPUState *cpu, GdbBreakpointType type,
vaddr addr, vaddr len);
void kvm_remove_all_gdbstub_breakpoints(CPUState *cpu);
#endif /* KVM_CPUS_H */
+7
View File
@@ -0,0 +1,7 @@
kvm_ss = ss.source_set()
kvm_ss.add(files(
'kvm-all.c',
'kvm-accel-ops.c',
))
specific_ss.add_all(when: 'CONFIG_KVM', if_true: kvm_ss)
+41
View File
@@ -0,0 +1,41 @@
# See docs/devel/tracing.rst for syntax documentation.
# kvm-all.c
kvm_ioctl(unsigned long type, void *arg) "type 0x%lx, arg %p"
kvm_vm_ioctl(unsigned long type, void *arg) "type 0x%lx, arg %p"
kvm_vcpu_ioctl(int cpu_index, unsigned long type, void *arg) "cpu_index %d, type 0x%lx, arg %p"
kvm_run_exit(int cpu_index, uint32_t reason) "cpu_index %d, reason %d"
kvm_device_ioctl(int fd, unsigned long type, void *arg) "dev fd %d, type 0x%lx, arg %p"
kvm_failed_reg_get(uint64_t id, const char *msg) "Warning: Unable to retrieve ONEREG %" PRIu64 " from KVM: %s"
kvm_failed_reg_set(uint64_t id, const char *msg) "Warning: Unable to set ONEREG %" PRIu64 " to KVM: %s"
kvm_init_vcpu(int cpu_index, unsigned long arch_cpu_id) "index: %d id: %lu"
kvm_create_vcpu(int cpu_index, unsigned long arch_cpu_id, int kvm_fd) "index: %d, id: %lu, kvm fd: %d"
kvm_destroy_vcpu(int cpu_index, unsigned long arch_cpu_id) "index: %d id: %lu"
kvm_park_vcpu(int cpu_index, unsigned long arch_cpu_id) "index: %d id: %lu"
kvm_unpark_vcpu(unsigned long arch_cpu_id, const char *msg) "id: %lu %s"
kvm_irqchip_commit_routes(void) ""
kvm_reset_vmfd(void) ""
kvm_rebind_vcpus(void) ""
kvm_irqchip_add_msi_route(char *name, int vector, int virq) "dev %s vector %d virq %d"
kvm_irqchip_update_msi_route(int virq) "Updating MSI route virq=%d"
kvm_irqchip_release_virq(int virq) "virq %d"
kvm_set_ioeventfd_mmio(int fd, uint64_t addr, uint32_t val, bool assign, uint32_t size, bool datamatch) "fd: %d @0x%" PRIx64 " val=0x%x assign: %d size: %d match: %d"
kvm_set_ioeventfd_pio(int fd, uint16_t addr, uint32_t val, bool assign, uint32_t size, bool datamatch) "fd: %d @0x%x val=0x%x assign: %d size: %d match: %d"
kvm_set_user_memory(uint16_t as, uint16_t slot, uint32_t flags, uint64_t guest_phys_addr, uint64_t memory_size, uint64_t userspace_addr, uint32_t fd, uint64_t fd_offset, int ret) "AddrSpace#%d Slot#%d flags=0x%x gpa=0x%"PRIx64 " size=0x%"PRIx64 " ua=0x%"PRIx64 " guest_memfd=%d" " guest_memfd_offset=0x%" PRIx64 " ret=%d"
kvm_clear_dirty_log(uint32_t slot, uint64_t start, uint32_t size) "slot#%"PRId32" start 0x%"PRIx64" size 0x%"PRIx32
kvm_resample_fd_notify(int gsi) "gsi %d"
kvm_dirty_ring_full(int id) "vcpu %d"
kvm_dirty_ring_reap_vcpu(int id) "vcpu %d"
kvm_dirty_ring_page(int vcpu, uint32_t slot, uint64_t offset) "vcpu %d fetch %"PRIu32" offset 0x%"PRIx64
kvm_dirty_ring_reaper(const char *s) "%s"
kvm_dirty_ring_reap(uint64_t count, int64_t t) "reaped %"PRIu64" pages (took %"PRIi64" us)"
kvm_dirty_ring_reaper_kick(const char *reason) "%s"
kvm_dirty_ring_flush(int finished) "%d"
kvm_failed_get_vcpu_mmap_size(void) ""
kvm_cpu_exec(void) ""
kvm_interrupt_exit_request(void) ""
kvm_io_window_exit(void) ""
kvm_run_exit_system_event(int cpu_index, uint32_t event_type) "cpu_index %d, system_even_type %"PRIu32
kvm_convert_memory(uint64_t start, uint64_t size, const char *msg) "start 0x%" PRIx64 " size 0x%" PRIx64 " %s"
kvm_memory_fault(uint64_t start, uint64_t size, uint64_t flags) "start 0x%" PRIx64 " size 0x%" PRIx64 " flags 0x%" PRIx64
kvm_slots_grow(unsigned int old, unsigned int new) "%u -> %u"
+1
View File
@@ -0,0 +1 @@
#include "trace/trace-accel_kvm.h"
+18
View File
@@ -0,0 +1,18 @@
common_ss.add(files('accel-common.c'))
system_ss.add(files('accel-system.c', 'accel-blocker.c', 'accel-qmp.c', 'accel-irq.c'))
user_ss.add(files('accel-user.c'))
subdir('tcg')
if have_system
subdir('hvf')
subdir('whpx')
subdir('qtest')
subdir('kvm')
subdir('xen')
subdir('stubs')
subdir('mshv')
subdir('nitro')
endif
# qtest
system_ss.add(files('dummy-cpus.c'))
+366
View File
@@ -0,0 +1,366 @@
/*
* QEMU MSHV support
*
* Copyright Microsoft, Corp. 2025
*
* Authors: Ziqiao Zhou <[email protected]>
* Magnus Kulke <[email protected]>
* Stanislav Kinsburskii <[email protected]>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "linux/mshv.h"
#include "qemu/error-report.h"
#include "hw/hyperv/hvhdk_mini.h"
#include "hw/hyperv/hvgdk_mini.h"
#include "hw/intc/ioapic.h"
#include "hw/pci/msi.h"
#include "system/mshv.h"
#include "system/mshv_int.h"
#include "trace.h"
#include <sys/ioctl.h>
#define MSHV_IRQFD_RESAMPLE_FLAG (1 << MSHV_IRQFD_BIT_RESAMPLE)
#define MSHV_IRQFD_BIT_DEASSIGN_FLAG (1 << MSHV_IRQFD_BIT_DEASSIGN)
/* Pass an eventfd which is to be used for injecting interrupts from userland */
static int irqfd(int vm_fd, int fd, int resample_fd, uint32_t gsi,
uint32_t flags)
{
int ret;
struct mshv_user_irqfd arg = {
.fd = fd,
.resamplefd = resample_fd,
.gsi = gsi,
.flags = flags,
};
ret = ioctl(vm_fd, MSHV_IRQFD, &arg);
if (ret < 0) {
error_report("Failed to set irqfd: gsi=%u, fd=%d", gsi, fd);
return -1;
}
return ret;
}
static int register_irqfd(int vm_fd, int event_fd, uint32_t gsi)
{
int ret;
trace_mshv_register_irqfd(vm_fd, event_fd, gsi);
ret = irqfd(vm_fd, event_fd, 0, gsi, 0);
if (ret < 0) {
error_report("Failed to register irqfd: gsi=%u", gsi);
return -1;
}
return 0;
}
static int register_irqfd_with_resample(int vm_fd, int event_fd,
int resample_fd, uint32_t gsi)
{
int ret;
uint32_t flags = MSHV_IRQFD_RESAMPLE_FLAG;
ret = irqfd(vm_fd, event_fd, resample_fd, gsi, flags);
if (ret < 0) {
error_report("Failed to register irqfd with resample: gsi=%u", gsi);
return -errno;
}
return 0;
}
static int unregister_irqfd(int vm_fd, int event_fd, uint32_t gsi)
{
int ret;
uint32_t flags = MSHV_IRQFD_BIT_DEASSIGN_FLAG;
ret = irqfd(vm_fd, event_fd, 0, gsi, flags);
if (ret < 0) {
error_report("Failed to unregister irqfd: gsi=%u", gsi);
return -errno;
}
return 0;
}
static int irqchip_update_irqfd_notifier_gsi(const EventNotifier *event,
const EventNotifier *resample,
int virq, bool add)
{
int fd = event_notifier_get_fd(event);
int rfd = resample ? event_notifier_get_fd(resample) : -1;
int vm_fd = mshv_state->vm;
trace_mshv_irqchip_update_irqfd_notifier_gsi(fd, rfd, virq, add);
if (!add) {
return unregister_irqfd(vm_fd, fd, virq);
}
if (rfd > 0) {
return register_irqfd_with_resample(vm_fd, fd, rfd, virq);
}
return register_irqfd(vm_fd, fd, virq);
}
static int irqchip_allocate_gsi(MshvState *s, int *gsi)
{
int next_gsi;
/* Return the lowest unused GSI in the bitmap */
next_gsi = find_first_zero_bit(s->used_gsi_bitmap, s->gsi_count);
if (next_gsi >= s->gsi_count) {
return -ENOSPC;
}
*gsi = next_gsi;
return 0;
}
static void irqchip_release_gsi(MshvState *s, int gsi)
{
clear_bit(gsi, s->used_gsi_bitmap);
}
static void add_routing_entry(MshvState *s, struct mshv_user_irq_entry *entry)
{
struct mshv_user_irq_entry *new;
int n, size;
if (s->irq_routes->nr == s->nr_allocated_irq_routes) {
n = s->nr_allocated_irq_routes * 2;
if (n < MSHV_MIN_ALLOCATED_MSI_ROUTES) {
n = MSHV_MIN_ALLOCATED_MSI_ROUTES;
}
size = sizeof(struct mshv_user_irq_table);
size += n * sizeof(*new);
s->irq_routes = g_realloc(s->irq_routes, size);
s->nr_allocated_irq_routes = n;
}
n = s->irq_routes->nr;
s->irq_routes->nr++;
new = &s->irq_routes->entries[n];
*new = *entry;
set_bit(entry->gsi, s->used_gsi_bitmap);
trace_mshv_add_msi_routing(entry->address_lo | entry->address_hi,
entry->data);
}
int mshv_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev)
{
struct mshv_user_irq_entry entry = { 0 };
MSIMessage msg = { 0 };
uint32_t data, high_addr, low_addr;
int gsi, ret;
MshvState *s = MSHV_STATE(c->accel);
if (!pci_available || !dev) {
return 0;
}
msg = pci_get_msi_message(dev, vector);
ret = irqchip_allocate_gsi(mshv_state, &gsi);
if (ret < 0) {
error_report("Could not allocate GSI for MSI route");
return -1;
}
high_addr = msg.address >> 32;
low_addr = msg.address & 0xFFFFFFFF;
data = le32_to_cpu(msg.data);
entry.gsi = gsi;
entry.address_hi = high_addr;
entry.address_lo = low_addr;
entry.data = data;
if (s->irq_routes->nr < s->gsi_count) {
add_routing_entry(s, &entry);
c->changes++;
} else {
irqchip_release_gsi(s, gsi);
return -ENOSPC;
}
return gsi;
}
void mshv_irqchip_release_virq(MshvState *s, int virq)
{
struct mshv_user_irq_entry *e;
int i;
for (i = 0; i < s->irq_routes->nr; i++) {
e = &s->irq_routes->entries[i];
if (e->gsi == virq) {
s->irq_routes->nr--;
*e = s->irq_routes->entries[s->irq_routes->nr];
}
}
irqchip_release_gsi(s, virq);
trace_mshv_remove_msi_routing(virq);
}
static int update_routing_entry(MshvState *s,
struct mshv_user_irq_entry *new_entry)
{
struct mshv_user_irq_entry *entry;
int n;
for (n = 0; n < s->irq_routes->nr; n++) {
entry = &s->irq_routes->entries[n];
if (entry->gsi != new_entry->gsi) {
continue;
}
if (!memcmp(entry, new_entry, sizeof *entry)) {
return 0;
}
*entry = *new_entry;
return 0;
}
return -ESRCH;
}
int mshv_irqchip_update_msi_route(int virq, MSIMessage msg, PCIDevice *dev)
{
uint32_t addr_hi = msg.address >> 32;
uint32_t addr_lo = msg.address & 0xFFFFFFFF;
uint32_t data = le32_to_cpu(msg.data);
struct mshv_user_irq_entry entry = {
.gsi = virq,
.address_hi = addr_hi,
.address_lo = addr_lo,
.data = data,
};
int ret;
ret = update_routing_entry(mshv_state, &entry);
if (ret < 0) {
error_report("Failed to set msi routing for gsi %d", virq);
abort();
}
trace_mshv_set_msi_routing(virq, msg.address, data);
return 0;
}
int mshv_request_interrupt(MshvState *mshv_state, uint32_t interrupt_type, uint32_t vector,
uint32_t vp_index, bool logical_dest_mode,
bool level_triggered)
{
int ret;
int vm_fd = mshv_state->vm;
if (vector == 0) {
warn_report("Ignoring request for interrupt vector 0");
return 0;
}
union hv_interrupt_control control = {
.interrupt_type = interrupt_type,
.level_triggered = level_triggered,
.logical_dest_mode = logical_dest_mode,
.rsvd = 0,
};
struct hv_input_assert_virtual_interrupt arg = {0};
arg.control = control;
arg.dest_addr = (uint64_t)vp_index;
arg.vector = vector;
struct mshv_root_hvcall args = {0};
args.code = HVCALL_ASSERT_VIRTUAL_INTERRUPT;
args.in_sz = sizeof(arg);
args.in_ptr = (uint64_t)&arg;
ret = mshv_hvcall(vm_fd, &args);
if (ret < 0) {
error_report("Failed to request interrupt");
return -errno;
}
return 0;
}
void mshv_irqchip_commit_routes(MshvState *s)
{
int ret;
int vm_fd = s->vm;
ret = ioctl(vm_fd, MSHV_SET_MSI_ROUTING, s->irq_routes);
if (ret < 0) {
error_report("Failed to commit msi routing table");
abort();
}
trace_mshv_commit_msi_routing_table(vm_fd, s->irq_routes->nr);
}
int mshv_irqchip_add_irqfd_notifier_gsi(const EventNotifier *event,
const EventNotifier *resample,
int virq)
{
return irqchip_update_irqfd_notifier_gsi(event, resample, virq, true);
}
int mshv_irqchip_remove_irqfd_notifier_gsi(const EventNotifier *event,
int virq)
{
return irqchip_update_irqfd_notifier_gsi(event, NULL, virq, false);
}
static int mshv_reserve_ioapic_msi_routes(MshvState *s)
{
int ret, i;
int gsi = 0;
struct mshv_user_irq_entry blank_entry = { 0 };
/*
* Reserve GSI 0-23 for IOAPIC pins, to avoid conflicts of legacy
* peripherals with MSI-X devices
*/
for (i = 0; i < IOAPIC_NUM_PINS; i++) {
/* ret = add_msi_routing(0, 0); */
ret = irqchip_allocate_gsi(s, &gsi);
if (ret < 0) {
error_report("Failed to reserve GSI %d: %s", gsi, strerror(-ret));
return -1;
}
blank_entry.gsi = gsi;
add_routing_entry(s, &blank_entry);
}
mshv_irqchip_commit_routes(s);
return 0;
}
void mshv_init_irq_routing(MshvState *s)
{
int ret;
int gsi_count = MSHV_MAX_MSI_ROUTES;
s->irq_routes = g_malloc0(sizeof(*s->irq_routes));
s->nr_allocated_irq_routes = 0;
s->gsi_count = gsi_count;
s->used_gsi_bitmap = bitmap_new(gsi_count);
ret = mshv_reserve_ioapic_msi_routes(s);
if (ret < 0) {
error_report("Failed to reserve IOAPIC MSI routes");
abort();
}
}
+424
View File
@@ -0,0 +1,424 @@
/*
* QEMU MSHV support
*
* Copyright Microsoft, Corp. 2025
*
* Authors:
* Magnus Kulke <[email protected]>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
*/
#include "qemu/osdep.h"
#include "qemu/error-report.h"
#include "qapi/error.h"
#include "linux/mshv.h"
#include "system/address-spaces.h"
#include "system/mshv.h"
#include "system/mshv_int.h"
#include "hw/hyperv/hvhdk_mini.h"
#include "system/physmem.h"
#include "exec/memattrs.h"
#include <sys/ioctl.h>
#include "trace.h"
static int set_guest_memory(int vm_fd,
const struct mshv_user_mem_region *region)
{
int ret;
ret = ioctl(vm_fd, MSHV_SET_GUEST_MEMORY, region);
if (ret < 0) {
error_report("failed to set guest memory");
return -errno;
}
return 0;
}
static int map_or_unmap(int vm_fd, const MshvMemoryRegion *mr, bool map)
{
struct mshv_user_mem_region region = {0};
region.guest_pfn = mr->guest_phys_addr >> MSHV_PAGE_SHIFT;
region.size = mr->memory_size;
region.userspace_addr = mr->userspace_addr;
if (!map) {
region.flags |= (1 << MSHV_SET_MEM_BIT_UNMAP);
trace_mshv_unmap_memory(mr->userspace_addr, mr->guest_phys_addr,
mr->memory_size);
return set_guest_memory(vm_fd, &region);
}
region.flags = BIT(MSHV_SET_MEM_BIT_EXECUTABLE);
if (!mr->readonly) {
region.flags |= BIT(MSHV_SET_MEM_BIT_WRITABLE);
}
trace_mshv_map_memory(mr->userspace_addr, mr->guest_phys_addr,
mr->memory_size);
return set_guest_memory(vm_fd, &region);
}
static int handle_unmapped_mmio_region_read(uint64_t gpa, uint64_t size,
uint8_t *data)
{
warn_report("read from unmapped mmio region gpa=0x%lx size=%lu", gpa, size);
if (size == 0 || size > 8) {
error_report("invalid size %lu for reading from unmapped mmio region",
size);
return -1;
}
memset(data, 0xFF, size);
return 0;
}
int mshv_guest_mem_read(uint64_t gpa, uint8_t *data, uintptr_t size,
bool is_secure_mode, bool instruction_fetch)
{
int ret;
MemTxAttrs memattr = { .secure = is_secure_mode };
if (instruction_fetch) {
trace_mshv_insn_fetch(gpa, size);
} else {
trace_mshv_mem_read(gpa, size);
}
ret = address_space_rw(&address_space_memory, gpa, memattr, (void *)data,
size, false);
if (ret == MEMTX_OK) {
return 0;
}
if (ret == MEMTX_DECODE_ERROR) {
return handle_unmapped_mmio_region_read(gpa, size, data);
}
error_report("failed to read guest memory at 0x%lx", gpa);
return -1;
}
int mshv_guest_mem_write(uint64_t gpa, const uint8_t *data, uintptr_t size,
bool is_secure_mode)
{
int ret;
MemTxAttrs memattr = { .secure = is_secure_mode };
trace_mshv_mem_write(gpa, size);
ret = address_space_rw(&address_space_memory, gpa, memattr, (void *)data,
size, true);
if (ret == MEMTX_OK) {
return 0;
}
if (ret == MEMTX_DECODE_ERROR) {
warn_report("write to unmapped mmio region gpa=0x%lx size=%lu", gpa,
size);
return 0;
}
error_report("Failed to write guest memory");
return -1;
}
static int set_memory(const MshvMemoryRegion *mshv_mr, bool add)
{
int ret = 0;
if (!mshv_mr) {
error_report("Invalid mshv_mr");
return -1;
}
trace_mshv_set_memory(add, mshv_mr->guest_phys_addr,
mshv_mr->memory_size,
mshv_mr->userspace_addr, mshv_mr->readonly,
ret);
return map_or_unmap(mshv_state->vm, mshv_mr, add);
}
/*
* Calculate and align the start address and the size of the section.
* Return the size. If the size is 0, the aligned section is empty.
*/
static hwaddr align_section(MemoryRegionSection *section, hwaddr *start)
{
hwaddr size = int128_get64(section->size);
hwaddr delta, aligned;
/*
* works in page size chunks, but the function may be called
* with sub-page size and unaligned start address. Pad the start
* address to next and truncate size to previous page boundary.
*/
aligned = ROUND_UP(section->offset_within_address_space,
qemu_real_host_page_size());
delta = aligned - section->offset_within_address_space;
*start = aligned;
if (delta > size) {
return 0;
}
return (size - delta) & qemu_real_host_page_mask();
}
void mshv_set_phys_mem(MshvMemoryListener *mml, MemoryRegionSection *section,
bool add)
{
int ret = 0;
MemoryRegion *area = section->mr;
bool writable = !area->readonly && !area->rom_device;
hwaddr start_addr, mr_offset, size;
void *ram;
MshvMemoryRegion mshv_mr = {0};
size = align_section(section, &start_addr);
trace_mshv_set_phys_mem(add, section->mr->name, start_addr);
/*
* If the memory device is a writable non-ram area, we do not
* want to map it into the guest memory. If it is not a ROM device,
* we want to remove mshv memory mapping, so accesses will trap.
*/
if (!memory_region_is_ram(area)) {
if (writable) {
return;
} else if (!area->romd_mode) {
add = false;
}
}
if (!size) {
return;
}
mr_offset = section->offset_within_region + start_addr -
section->offset_within_address_space;
ram = memory_region_get_ram_ptr(area) + mr_offset;
mshv_mr.guest_phys_addr = start_addr;
mshv_mr.memory_size = size;
mshv_mr.readonly = !writable;
mshv_mr.userspace_addr = (uint64_t)ram;
ret = set_memory(&mshv_mr, add);
if (ret < 0) {
error_report("Failed to set memory region");
abort();
}
}
static int enable_dirty_page_tracking(int vm_fd)
{
int ret;
struct hv_input_set_partition_property in = {0};
struct mshv_root_hvcall args = {0};
in.property_code = HV_PARTITION_PROPERTY_GPA_PAGE_ACCESS_TRACKING;
in.property_value = 1;
args.code = HVCALL_SET_PARTITION_PROPERTY;
args.in_sz = sizeof(in);
args.in_ptr = (uint64_t)&in;
ret = mshv_hvcall(vm_fd, &args);
if (ret < 0) {
error_report("Failed to enable dirty page tracking: %s",
strerror(errno));
return -1;
}
return 0;
}
/*
* Retrieve dirty page bitmap for a GPA range, clearing the dirty bits
* atomically. Large ranges are handled in batches.
*/
static int get_dirty_log(int vm_fd, uint64_t base_pfn, uint64_t page_count,
unsigned long *bitmap, size_t bitmap_size)
{
uint64_t batch, bitmap_offset, completed = 0;
struct mshv_gpap_access_bitmap args = {0};
int ret;
QEMU_BUILD_BUG_ON(MSHV_DIRTY_PAGES_BATCH_SIZE % BITS_PER_LONG != 0);
assert(bitmap_size >= ROUND_UP(page_count, BITS_PER_LONG) / 8);
while (completed < page_count) {
batch = MIN(MSHV_DIRTY_PAGES_BATCH_SIZE, page_count - completed);
bitmap_offset = completed / BITS_PER_LONG;
args.access_type = MSHV_GPAP_ACCESS_TYPE_DIRTY;
args.access_op = MSHV_GPAP_ACCESS_OP_CLEAR;
args.page_count = batch;
args.gpap_base = base_pfn + completed;
args.bitmap_ptr = (uint64_t)(bitmap + bitmap_offset);
ret = ioctl(vm_fd, MSHV_GET_GPAP_ACCESS_BITMAP, &args);
if (ret < 0) {
error_report("Failed to get dirty log (base_pfn=0x%" PRIx64
" batch=%" PRIu64 "): %s",
base_pfn + completed, batch, strerror(errno));
return -1;
}
completed += batch;
}
return 0;
}
bool mshv_log_global_start(MemoryListener *listener, Error **errp)
{
int ret;
ret = enable_dirty_page_tracking(mshv_state->vm);
if (ret < 0) {
error_setg_errno(errp, -ret, "Failed to enable dirty page tracking");
return false;
}
return true;
}
static int disable_dirty_page_tracking(int vm_fd)
{
int ret;
struct hv_input_set_partition_property in = {0};
struct mshv_root_hvcall args = {0};
in.property_code = HV_PARTITION_PROPERTY_GPA_PAGE_ACCESS_TRACKING;
in.property_value = 0;
args.code = HVCALL_SET_PARTITION_PROPERTY;
args.in_sz = sizeof(in);
args.in_ptr = (uint64_t)&in;
ret = mshv_hvcall(vm_fd, &args);
if (ret < 0) {
error_report("Failed to disable dirty page tracking: %s",
strerror(errno));
return -1;
}
return 0;
}
static int set_dirty_pages(int vm_fd, uint64_t base_pfn, uint64_t page_count)
{
uint64_t batch, completed = 0;
unsigned long bitmap[MSHV_DIRTY_PAGES_BATCH_SIZE / BITS_PER_LONG];
struct mshv_gpap_access_bitmap args = {0};
int ret;
while (completed < page_count) {
batch = MIN(MSHV_DIRTY_PAGES_BATCH_SIZE, page_count - completed);
args.access_type = MSHV_GPAP_ACCESS_TYPE_DIRTY;
args.access_op = MSHV_GPAP_ACCESS_OP_SET;
args.page_count = batch;
args.gpap_base = base_pfn + completed;
args.bitmap_ptr = (uint64_t)bitmap;
ret = ioctl(vm_fd, MSHV_GET_GPAP_ACCESS_BITMAP, &args);
if (ret < 0) {
error_report("Failed to set dirty pages (base_pfn=0x%" PRIx64
" batch=%" PRIu64 "): %s",
base_pfn + completed, batch, strerror(errno));
return -1;
}
completed += batch;
}
return 0;
}
static bool set_dirty_bits_cb(Int128 start, Int128 len, const MemoryRegion *mr,
hwaddr offset_in_region, void *opaque)
{
int ret, *errp = opaque;
hwaddr gpa, size;
uint64_t page_count, base_pfn;
gpa = int128_get64(start);
size = int128_get64(len);
page_count = size >> MSHV_PAGE_SHIFT;
base_pfn = gpa >> MSHV_PAGE_SHIFT;
if (!mr->ram || mr->readonly) {
return false;
}
if (page_count == 0) {
return false;
}
ret = set_dirty_pages(mshv_state->vm, base_pfn, page_count);
/* true aborts the iteration, which is what we want if there's an error */
if (ret < 0) {
*errp = ret;
return true;
}
return false;
}
void mshv_log_global_stop(MemoryListener *listener)
{
int err = 0;
/* MSHV requires all dirty bits to be set before disabling tracking. */
FlatView *fv = address_space_to_flatview(&address_space_memory);
flatview_for_each_range(fv, set_dirty_bits_cb, &err);
if (err < 0) {
error_report("Failed to set dirty bits before disabling tracking");
}
disable_dirty_page_tracking(mshv_state->vm);
}
void mshv_log_sync(MemoryListener *listener, MemoryRegionSection *section)
{
hwaddr size, start_addr, mr_offset;
uint64_t page_count, base_pfn;
size_t bitmap_size;
unsigned long *bitmap;
ram_addr_t ram_addr;
int ret;
MemoryRegion *mr = section->mr;
if (!memory_region_is_ram(mr) || memory_region_is_rom(mr)) {
return;
}
size = align_section(section, &start_addr);
if (!size) {
return;
}
page_count = size >> MSHV_PAGE_SHIFT;
base_pfn = start_addr >> MSHV_PAGE_SHIFT;
bitmap_size = ROUND_UP(page_count, BITS_PER_LONG) / 8;
bitmap = g_malloc0(bitmap_size);
ret = get_dirty_log(mshv_state->vm, base_pfn, page_count, bitmap,
bitmap_size);
if (ret < 0) {
g_free(bitmap);
return;
}
mr_offset = section->offset_within_region + start_addr -
section->offset_within_address_space;
ram_addr = memory_region_get_ram_addr(mr) + mr_offset;
physical_memory_set_dirty_lebitmap(bitmap, ram_addr, page_count);
g_free(bitmap);
}
+5
View File
@@ -0,0 +1,5 @@
system_ss.add(when: 'CONFIG_MSHV', if_true: files(
'irq.c',
'mem.c',
'mshv-all.c'
))
+858
View File
@@ -0,0 +1,858 @@
/*
* QEMU MSHV support
*
* Copyright Microsoft, Corp. 2025
*
* Authors:
* Ziqiao Zhou <[email protected]>
* Magnus Kulke <[email protected]>
* Jinank Jain <[email protected]>
* Wei Liu <[email protected]>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "qemu/event_notifier.h"
#include "qemu/module.h"
#include "qemu/main-loop.h"
#include "hw/core/boards.h"
#include "hw/hyperv/hvhdk.h"
#include "hw/hyperv/hvhdk_mini.h"
#include "hw/hyperv/hvgdk.h"
#include "hw/hyperv/hvgdk_mini.h"
#include "linux/mshv.h"
#include "qemu/accel.h"
#include "qemu/guest-random.h"
#include "accel/accel-ops.h"
#include "accel/accel-cpu-ops.h"
#include "exec/cpu-common.h"
#include "system/cpus.h"
#include "system/runstate.h"
#include "system/accel-blocker.h"
#include "system/address-spaces.h"
#include "system/mshv.h"
#include "system/mshv_int.h"
#include "system/reset.h"
#include "migration/qemu-file-types.h"
#include "migration/register.h"
#include "trace.h"
#include <err.h>
#include <sys/ioctl.h>
bool mshv_allowed;
MshvState *mshv_state;
static int init_mshv(int *mshv_fd)
{
int fd = open("/dev/mshv", O_RDWR | O_CLOEXEC);
if (fd < 0) {
error_report("Failed to open /dev/mshv: %s", strerror(errno));
return -1;
}
*mshv_fd = fd;
return 0;
}
static int mshv_load_cleanup(void *opaque)
{
CPUState *cpu;
int ret;
ret = mshv_arch_set_partition_msrs(first_cpu);
if (ret < 0) {
error_report("Failed to set partition MSRs: %s", strerror(-ret));
return -1;
}
CPU_FOREACH(cpu) {
ret = mshv_arch_set_mp_state(cpu);
if (ret < 0) {
error_report("Failed to set mp state for vCPU %d: %s",
cpu->cpu_index, strerror(-ret));
return -1;
}
}
return 0;
}
static int get_host_partition_property(int mshv_fd, uint32_t property_code,
uint64_t *value)
{
int ret;
struct hv_input_get_partition_property in = {0};
struct hv_output_get_partition_property out = {0};
struct mshv_root_hvcall args = {0};
in.property_code = property_code;
args.code = HVCALL_GET_PARTITION_PROPERTY;
args.in_sz = sizeof(in);
args.in_ptr = (uint64_t)&in;
args.out_sz = sizeof(out);
args.out_ptr = (uint64_t)&out;
ret = ioctl(mshv_fd, MSHV_ROOT_HVCALL, &args);
if (ret < 0) {
error_report("Failed to get host partition property bank: %s",
strerror(errno));
return -1;
}
*value = out.property_value;
return 0;
}
static int get_partition_property(int vm_fd, uint32_t feature_bank,
uint64_t *value)
{
struct hv_input_get_partition_property in = {0};
struct hv_output_get_partition_property out = {0};
struct mshv_root_hvcall args = {0};
int ret;
in.property_code = feature_bank;
args.code = HVCALL_GET_PARTITION_PROPERTY;
args.in_sz = sizeof(in);
args.in_ptr = (uint64_t)&in;
args.out_sz = sizeof(out);
args.out_ptr = (uint64_t)&out;
ret = ioctl(vm_fd, MSHV_ROOT_HVCALL, &args);
if (ret < 0) {
error_report("Failed to get guest partition property bank: %s",
strerror(errno));
return -1;
}
*value = out.property_value;
return 0;
}
static int get_proc_features(int vm_fd,
union hv_partition_processor_features *features)
{
int ret;
ret = get_partition_property(vm_fd,
HV_PARTITION_PROPERTY_PROCESSOR_FEATURES0,
&features->as_uint64[0]);
if (ret < 0) {
error_report("Failed to get processor features bank 0");
return -1;
}
ret = get_partition_property(vm_fd,
HV_PARTITION_PROPERTY_PROCESSOR_FEATURES1,
&features->as_uint64[1]);
if (ret < 0) {
error_report("Failed to get processor features bank 1");
return -1;
}
return 0;
}
static int create_partition(int mshv_fd, int *vm_fd)
{
int ret;
uint64_t pt_flags, host_proc_features;
union hv_partition_processor_xsave_features disabled_xsave_features;
union hv_partition_processor_features disabled_partition_features = {0};
struct mshv_create_partition_v2 args = {0};
QEMU_BUILD_BUG_ON(MSHV_NUM_CPU_FEATURES_BANKS != 2);
/* Initialize pt_flags with the desired features */
pt_flags = (1ULL << MSHV_PT_BIT_LAPIC) |
(1ULL << MSHV_PT_BIT_X2APIC) |
(1ULL << MSHV_PT_BIT_GPA_SUPER_PAGES) |
(1ULL << MSHV_PT_BIT_CPU_AND_XSAVE_FEATURES);
/* enable all */
disabled_xsave_features.as_uint64 = 0;
/*
* AMX TILE XSAVE state (XTILE_DATA) is 8KB, which exceeds the
* current fixed 4KB XSAVE buffer size.
*/
disabled_xsave_features.amx_tile_support = 1;
disabled_xsave_features.amx_bf16_support = 1;
disabled_xsave_features.amx_int8_support = 1;
disabled_xsave_features.amx_fp16_support = 1;
/*
* query host for supported processor features and disable unsupported
* features: (0 means supported, 1 means disabled, hence the negation)
*/
ret = get_host_partition_property(mshv_fd,
HV_PARTITION_PROPERTY_PROCESSOR_FEATURES0,
&host_proc_features);
if (ret < 0) {
error_report("Failed to get host processor feature bank 0");
return -1;
}
args.pt_cpu_fbanks[0] = ~host_proc_features;
ret = get_host_partition_property(mshv_fd,
HV_PARTITION_PROPERTY_PROCESSOR_FEATURES1,
&host_proc_features);
if (ret < 0) {
error_report("Failed to get host processor feature bank 1");
return -1;
}
args.pt_cpu_fbanks[1] = ~host_proc_features;
/* arch-specific features we disable regardless of host support */
mshv_arch_disable_partition_proc_features(&disabled_partition_features);
args.pt_cpu_fbanks[0] |= disabled_partition_features.as_uint64[0];
args.pt_cpu_fbanks[1] |= disabled_partition_features.as_uint64[1];
/* populate args structure */
args.pt_flags = pt_flags;
args.pt_isolation = MSHV_PT_ISOLATION_NONE;
args.pt_disabled_xsave = disabled_xsave_features.as_uint64;
args.pt_num_cpu_fbanks = MSHV_NUM_CPU_FEATURES_BANKS;
ret = ioctl(mshv_fd, MSHV_CREATE_PARTITION, &args);
if (ret < 0) {
error_report("Failed to create partition: %s", strerror(errno));
return -1;
}
*vm_fd = ret;
return 0;
}
static int set_synthetic_proc_features(int vm_fd)
{
int ret;
struct hv_input_set_partition_property in = {0};
union hv_partition_synthetic_processor_features features = {0};
/* Access the bitfield and set the desired features */
features.hypervisor_present = 1;
features.hv1 = 1;
features.access_partition_reference_counter = 1;
features.access_synic_regs = 1;
features.access_synthetic_timer_regs = 1;
features.access_partition_reference_tsc = 1;
features.access_frequency_regs = 1;
features.access_intr_ctrl_regs = 1;
features.access_vp_index = 1;
features.access_hypercall_regs = 1;
features.tb_flush_hypercalls = 1;
features.synthetic_cluster_ipi = 1;
features.direct_synthetic_timers = 1;
mshv_arch_amend_proc_features(&features);
in.property_code = HV_PARTITION_PROPERTY_SYNTHETIC_PROC_FEATURES;
in.property_value = features.as_uint64[0];
struct mshv_root_hvcall args = {0};
args.code = HVCALL_SET_PARTITION_PROPERTY;
args.in_sz = sizeof(in);
args.in_ptr = (uint64_t)&in;
trace_mshv_hvcall_args("synthetic_proc_features", args.code, args.in_sz);
ret = mshv_hvcall(vm_fd, &args);
if (ret < 0) {
error_report("Failed to set synthethic proc features");
return -errno;
}
return 0;
}
static int initialize_vm(int vm_fd)
{
int ret = ioctl(vm_fd, MSHV_INITIALIZE_PARTITION);
if (ret < 0) {
error_report("Failed to initialize partition: %s", strerror(errno));
return -1;
}
return 0;
}
static int create_vm(int mshv_fd, int *vm_fd)
{
int ret = create_partition(mshv_fd, vm_fd);
if (ret < 0) {
return -1;
}
ret = set_synthetic_proc_features(*vm_fd);
if (ret < 0) {
return -1;
}
ret = initialize_vm(*vm_fd);
if (ret < 0) {
return -1;
}
ret = mshv_arch_post_init_vm(*vm_fd);
if (ret < 0) {
return -1;
}
return 0;
}
static void mem_region_add(MemoryListener *listener,
MemoryRegionSection *section)
{
MshvMemoryListener *mml;
mml = container_of(listener, MshvMemoryListener, listener);
memory_region_ref(section->mr);
mshv_set_phys_mem(mml, section, true);
}
static void mem_region_del(MemoryListener *listener,
MemoryRegionSection *section)
{
MshvMemoryListener *mml;
mml = container_of(listener, MshvMemoryListener, listener);
mshv_set_phys_mem(mml, section, false);
memory_region_unref(section->mr);
}
typedef enum {
DATAMATCH_NONE,
DATAMATCH_U32,
DATAMATCH_U64,
} DatamatchTag;
typedef struct {
DatamatchTag tag;
union {
uint32_t u32;
uint64_t u64;
} value;
} Datamatch;
/* flags: determine whether to de/assign */
static int ioeventfd(int vm_fd, int event_fd, uint64_t addr, Datamatch dm,
uint32_t flags)
{
struct mshv_user_ioeventfd args = {0};
args.fd = event_fd;
args.addr = addr;
args.flags = flags;
if (dm.tag == DATAMATCH_NONE) {
args.datamatch = 0;
} else {
flags |= BIT(MSHV_IOEVENTFD_BIT_DATAMATCH);
args.flags = flags;
if (dm.tag == DATAMATCH_U64) {
args.len = sizeof(uint64_t);
args.datamatch = dm.value.u64;
} else {
args.len = sizeof(uint32_t);
args.datamatch = dm.value.u32;
}
}
int ret = ioctl(vm_fd, MSHV_IOEVENTFD, &args);
if (ret < 0) {
return -errno;
}
return ret;
}
static int unregister_ioevent(int vm_fd, int event_fd, uint64_t mmio_addr,
uint64_t data, uint32_t len, bool data_match)
{
uint32_t flags = 0;
Datamatch dm = {0};
flags |= BIT(MSHV_IOEVENTFD_BIT_DEASSIGN);
if (!data_match) {
dm.tag = DATAMATCH_NONE;
} else if (len == sizeof(uint64_t)) {
dm.tag = DATAMATCH_U64;
dm.value.u64 = data;
} else {
dm.tag = DATAMATCH_U32;
dm.value.u32 = data;
}
return ioeventfd(vm_fd, event_fd, mmio_addr, dm, flags);
}
static int register_ioevent(int vm_fd, int event_fd, uint64_t mmio_addr,
uint64_t val, bool is_64bit, bool is_datamatch)
{
uint32_t flags = 0;
Datamatch dm = {0};
if (!is_datamatch) {
dm.tag = DATAMATCH_NONE;
} else if (is_64bit) {
dm.tag = DATAMATCH_U64;
dm.value.u64 = val;
} else {
dm.tag = DATAMATCH_U32;
dm.value.u32 = val;
}
return ioeventfd(vm_fd, event_fd, mmio_addr, dm, flags);
}
static void mem_ioeventfd_add(MemoryListener *listener,
MemoryRegionSection *section,
bool match_data, uint64_t data,
EventNotifier *e)
{
int fd = event_notifier_get_fd(e);
int ret;
bool is_64 = int128_get64(section->size) == 8;
uint64_t addr = section->offset_within_address_space;
trace_mshv_mem_ioeventfd_add(addr, int128_get64(section->size), data);
ret = register_ioevent(mshv_state->vm, fd, addr, data, is_64, match_data);
if (ret < 0) {
error_report("Failed to register ioeventfd: %s (%d)", strerror(-ret),
-ret);
abort();
}
}
static void mem_ioeventfd_del(MemoryListener *listener,
MemoryRegionSection *section,
bool match_data, uint64_t data,
EventNotifier *e)
{
int fd = event_notifier_get_fd(e);
int ret;
uint64_t addr = section->offset_within_address_space;
uint64_t len = int128_get64(section->size);
trace_mshv_mem_ioeventfd_del(section->offset_within_address_space,
int128_get64(section->size), data);
ret = unregister_ioevent(mshv_state->vm, fd, addr, data, len, match_data);
if (ret < 0) {
error_report("Failed to unregister ioeventfd: %s (%d)", strerror(-ret),
-ret);
abort();
}
}
static MemoryListener mshv_memory_listener = {
.name = "mshv",
.priority = MEMORY_LISTENER_PRIORITY_ACCEL,
.region_add = mem_region_add,
.region_del = mem_region_del,
.eventfd_add = mem_ioeventfd_add,
.eventfd_del = mem_ioeventfd_del,
.log_sync = mshv_log_sync,
.log_global_start = mshv_log_global_start,
.log_global_stop = mshv_log_global_stop,
};
static MemoryListener mshv_io_listener = {
.name = "mshv", .priority = MEMORY_LISTENER_PRIORITY_DEV_BACKEND,
/* MSHV does not support PIO eventfd */
};
static void register_mshv_memory_listener(MshvState *s, MshvMemoryListener *mml,
AddressSpace *as, int as_id,
const char *name)
{
int i;
mml->listener = mshv_memory_listener;
mml->listener.name = name;
memory_listener_register(&mml->listener, as);
for (i = 0; i < s->nr_as; ++i) {
if (!s->as[i].as) {
s->as[i].as = as;
s->as[i].ml = mml;
break;
}
}
}
int mshv_hvcall(int fd, const mshv_root_hvcall *args)
{
int ret = 0;
ret = ioctl(fd, MSHV_ROOT_HVCALL, args);
if (ret < 0) {
error_report("Failed to perform hvcall: %s", strerror(errno));
return -1;
}
return ret;
}
static int mshv_init_vcpu(CPUState *cpu)
{
int vm_fd = mshv_state->vm;
uint8_t vp_index = cpu->cpu_index;
int ret;
cpu->accel = g_new0(AccelCPUState, 1);
ret = mshv_create_vcpu(vm_fd, vp_index, &cpu->accel->cpufd);
if (ret < 0) {
return -1;
}
mshv_arch_init_vcpu(cpu);
cpu->vcpu_dirty = true;
return 0;
}
static SaveVMHandlers savevm_mshv = {
.load_cleanup = mshv_load_cleanup,
};
static int mshv_init(AccelState *as, MachineState *ms)
{
MshvState *s;
int mshv_fd, vm_fd, ret;
if (mshv_state) {
warn_report("MSHV accelerator already initialized");
return 0;
}
s = MSHV_STATE(as);
accel_blocker_init();
s->vm = 0;
ret = init_mshv(&mshv_fd);
if (ret < 0) {
return -1;
}
mshv_init_mmio_emu();
ret = create_vm(mshv_fd, &vm_fd);
if (ret < 0) {
close(mshv_fd);
return -1;
}
s->vm = vm_fd;
s->fd = mshv_fd;
ret = get_proc_features(vm_fd, &s->processor_features);
if (ret < 0) {
return -1;
}
s->nr_as = 1;
s->as = g_new0(MshvAddressSpace, s->nr_as);
mshv_state = s;
mshv_init_irq_routing(s);
register_mshv_memory_listener(s, &s->memory_listener, &address_space_memory,
0, "mshv-memory");
memory_listener_register(&mshv_io_listener, &address_space_io);
register_savevm_live("mshv", 0, 1, &savevm_mshv, s);
mshv_clock_init();
return 0;
}
static int mshv_destroy_vcpu(CPUState *cpu)
{
int cpu_fd = mshv_vcpufd(cpu);
int vm_fd = mshv_state->vm;
mshv_remove_vcpu(vm_fd, cpu_fd);
mshv_vcpufd(cpu) = 0;
mshv_arch_destroy_vcpu(cpu);
g_clear_pointer(&cpu->accel, g_free);
return 0;
}
static int mshv_cpu_exec(CPUState *cpu)
{
hv_message mshv_msg;
enum MshvVmExit exit_reason;
int ret = 0;
bql_unlock();
cpu_exec_start(cpu);
do {
if (cpu->vcpu_dirty) {
ret = mshv_arch_store_vcpu_state(cpu);
if (ret) {
error_report("Failed to put registers after init: %s",
strerror(-ret));
ret = -1;
break;
}
cpu->vcpu_dirty = false;
}
/* Corresponding store-release is in cpu_exit. */
if (qatomic_load_acquire(&cpu->exit_request)) {
trace_mshv_interrupt_exit_request(cpu->cpu_index);
ret = EXCP_INTERRUPT;
break;
}
ret = mshv_run_vcpu(mshv_state->vm, cpu, &mshv_msg, &exit_reason);
if (ret < 0) {
error_report("Failed to run on vcpu %d", cpu->cpu_index);
abort();
}
switch (exit_reason) {
case MshvVmExitIgnore:
break;
default:
ret = EXCP_INTERRUPT;
break;
}
} while (ret == 0);
cpu_exec_end(cpu);
bql_lock();
if (ret < 0) {
cpu_dump_state(cpu, stderr, CPU_DUMP_CODE);
vm_stop(RUN_STATE_INTERNAL_ERROR);
}
return ret;
}
/*
* We need a dummy handler to make SIG_IPI a deliverable signal. The kernel
* handler will be woken up by the caught signal and instruct the hypervisor
* to suspend execution (the concrete mechanism differs between schedulers)
* and return to userspace.
*/
static void dummy_handler(int sig)
{
}
static void init_signal(CPUState *cpu)
{
/* init cpu signals */
struct sigaction sigact;
sigset_t set;
memset(&sigact, 0, sizeof(sigact));
sigact.sa_handler = dummy_handler;
sigaction(SIG_IPI, &sigact, NULL);
pthread_sigmask(SIG_BLOCK, NULL, &set);
sigdelset(&set, SIG_IPI);
pthread_sigmask(SIG_SETMASK, &set, NULL);
}
static void *mshv_vcpu_thread(void *arg)
{
CPUState *cpu = arg;
int ret;
rcu_register_thread();
bql_lock();
qemu_thread_get_self(cpu->thread);
cpu->thread_id = qemu_get_thread_id();
current_cpu = cpu;
ret = mshv_init_vcpu(cpu);
if (ret < 0) {
error_report("Failed to init vcpu %d", cpu->cpu_index);
goto cleanup;
}
init_signal(cpu);
/* signal CPU creation */
cpu_thread_signal_created(cpu);
qemu_guest_random_seed_thread_part2(cpu->random_seed);
do {
qemu_process_cpu_events(cpu);
if (cpu_can_run(cpu)) {
mshv_cpu_exec(cpu);
}
} while (!cpu->unplug || cpu_can_run(cpu));
mshv_destroy_vcpu(cpu);
cleanup:
cpu_thread_signal_destroyed(cpu);
bql_unlock();
rcu_unregister_thread();
return NULL;
}
static void mshv_start_vcpu_thread(CPUState *cpu)
{
char thread_name[VCPU_THREAD_NAME_SIZE];
snprintf(thread_name, VCPU_THREAD_NAME_SIZE, "CPU %d/MSHV",
cpu->cpu_index);
cpu->thread = g_malloc0(sizeof(QemuThread));
cpu->halt_cond = g_malloc0(sizeof(QemuCond));
qemu_cond_init(cpu->halt_cond);
trace_mshv_start_vcpu_thread(thread_name, cpu->cpu_index);
qemu_thread_create(cpu->thread, thread_name, mshv_vcpu_thread, cpu,
QEMU_THREAD_JOINABLE);
}
static void do_mshv_cpu_synchronize_post_init(CPUState *cpu,
run_on_cpu_data arg)
{
int ret = mshv_arch_store_vcpu_state(cpu);
if (ret < 0) {
error_report("Failed to put registers after init: %s", strerror(-ret));
abort();
}
cpu->vcpu_dirty = false;
}
static void mshv_cpu_synchronize_post_init(CPUState *cpu)
{
run_on_cpu(cpu, do_mshv_cpu_synchronize_post_init, RUN_ON_CPU_NULL);
}
static void mshv_cpu_synchronize_post_reset(CPUState *cpu)
{
int ret = mshv_arch_store_vcpu_state(cpu);
if (ret) {
error_report("Failed to put registers after reset: %s",
strerror(-ret));
cpu_dump_state(cpu, stderr, CPU_DUMP_CODE);
vm_stop(RUN_STATE_INTERNAL_ERROR);
}
cpu->vcpu_dirty = false;
}
static void do_mshv_cpu_synchronize_pre_loadvm(CPUState *cpu,
run_on_cpu_data arg)
{
cpu->vcpu_dirty = true;
}
static void mshv_cpu_synchronize_pre_loadvm(CPUState *cpu)
{
run_on_cpu(cpu, do_mshv_cpu_synchronize_pre_loadvm, RUN_ON_CPU_NULL);
}
static void do_mshv_cpu_synchronize(CPUState *cpu, run_on_cpu_data arg)
{
if (!cpu->vcpu_dirty) {
int ret = mshv_arch_load_vcpu_state(cpu);
if (ret < 0) {
error_report("Failed to load registers for vcpu %d",
cpu->cpu_index);
cpu_dump_state(cpu, stderr, CPU_DUMP_CODE);
vm_stop(RUN_STATE_INTERNAL_ERROR);
}
cpu->vcpu_dirty = true;
}
}
static void mshv_cpu_synchronize(CPUState *cpu)
{
if (!cpu->vcpu_dirty) {
run_on_cpu(cpu, do_mshv_cpu_synchronize, RUN_ON_CPU_NULL);
}
}
static bool mshv_cpus_are_resettable(void)
{
return false;
}
static void mshv_accel_class_init(ObjectClass *oc, const void *data)
{
AccelClass *ac = ACCEL_CLASS(oc);
ac->name = "MSHV";
ac->init_machine = mshv_init;
ac->allowed = &mshv_allowed;
}
static void mshv_accel_instance_init(Object *obj)
{
MshvState *s = MSHV_STATE(obj);
s->vm = 0;
}
static const TypeInfo mshv_accel_type = {
.name = TYPE_MSHV_ACCEL,
.parent = TYPE_ACCEL,
.instance_init = mshv_accel_instance_init,
.class_init = mshv_accel_class_init,
.instance_size = sizeof(MshvState),
};
/*
* MSHV manages secondary processors in the hypervisor. SIPI for x86 and
* PSCI for Arm are handled internally. Halted vCPUs must still enter
* mshv_cpu_exec() so that MSHV_RUN_VP is called and the hypervisor will
* wake APs.
*/
static bool mshv_vcpu_thread_is_idle(CPUState *cpu)
{
return false;
}
static void mshv_accel_ops_class_init(ObjectClass *oc, const void *data)
{
AccelOpsClass *ops = ACCEL_OPS_CLASS(oc);
ops->create_vcpu_thread = mshv_start_vcpu_thread;
ops->cpu_thread_is_idle = mshv_vcpu_thread_is_idle;
ops->synchronize_post_init = mshv_cpu_synchronize_post_init;
ops->synchronize_post_reset = mshv_cpu_synchronize_post_reset;
ops->synchronize_state = mshv_cpu_synchronize;
ops->synchronize_pre_loadvm = mshv_cpu_synchronize_pre_loadvm;
ops->cpus_are_resettable = mshv_cpus_are_resettable;
ops->cpu_thread_is_idle = mshv_vcpu_thread_is_idle;
ops->handle_interrupt = generic_handle_interrupt;
}
static const TypeInfo mshv_accel_ops_type = {
.name = ACCEL_OPS_NAME("mshv"),
.parent = TYPE_ACCEL_OPS,
.class_init = mshv_accel_ops_class_init,
.abstract = true,
};
static void mshv_type_init(void)
{
type_register_static(&mshv_accel_type);
type_register_static(&mshv_accel_ops_type);
}
type_init(mshv_type_init);
+29
View File
@@ -0,0 +1,29 @@
# Authors: Ziqiao Zhou <[email protected]>
# Magnus Kulke <[email protected]>
#
# SPDX-License-Identifier: GPL-2.0-or-later
mshv_start_vcpu_thread(const char* thread, uint32_t cpu) "thread=%s cpu_index=%d"
mshv_interrupt_exit_request(uint32_t cpu) "cpu_index=%d"
mshv_set_memory(bool add, uint64_t gpa, uint64_t size, uint64_t user_addr, bool readonly, int ret) "add=%d gpa=0x%" PRIx64 " size=0x%" PRIx64 " user=0x%" PRIx64 " readonly=%d result=%d"
mshv_mem_ioeventfd_add(uint64_t addr, uint32_t size, uint32_t data) "addr=0x%" PRIx64 " size=%d data=0x%x"
mshv_mem_ioeventfd_del(uint64_t addr, uint32_t size, uint32_t data) "addr=0x%" PRIx64 " size=%d data=0x%x"
mshv_hvcall_args(const char* hvcall, uint16_t code, uint16_t in_sz) "built args for '%s' code: %d in_sz: %d"
mshv_handle_interrupt(uint32_t cpu, int mask) "cpu_index=%d mask=0x%x"
mshv_set_msi_routing(uint32_t gsi, uint64_t addr, uint32_t data) "gsi=%d addr=0x%" PRIx64 " data=0x%x"
mshv_remove_msi_routing(uint32_t gsi) "gsi=%d"
mshv_add_msi_routing(uint64_t addr, uint32_t data) "addr=0x%" PRIx64 " data=0x%x"
mshv_commit_msi_routing_table(int vm_fd, int len) "vm_fd=%d table_size=%d"
mshv_register_irqfd(int vm_fd, int event_fd, uint32_t gsi) "vm_fd=%d event_fd=%d gsi=%d"
mshv_irqchip_update_irqfd_notifier_gsi(int event_fd, int resample_fd, int virq, bool add) "event_fd=%d resample_fd=%d virq=%d add=%d"
mshv_insn_fetch(uint64_t addr, size_t size) "gpa=0x%" PRIx64 " size=%zu"
mshv_mem_write(uint64_t addr, size_t size) "\tgpa=0x%" PRIx64 " size=%zu"
mshv_mem_read(uint64_t addr, size_t size) "\tgpa=0x%" PRIx64 " size=%zu"
mshv_map_memory(uint64_t userspace_addr, uint64_t gpa, uint64_t size) "\tu_a=0x%" PRIx64 " gpa=0x%010" PRIx64 " size=0x%08" PRIx64
mshv_unmap_memory(uint64_t userspace_addr, uint64_t gpa, uint64_t size) "\tu_a=0x%" PRIx64 " gpa=0x%010" PRIx64 " size=0x%08" PRIx64
mshv_set_phys_mem(bool add, const char *name, uint64_t gpa) "\tadd=%d name=%s gpa=0x%010" PRIx64
mshv_handle_mmio(uint64_t gva, uint64_t gpa, uint64_t size, uint8_t access_type) "\tgva=0x%" PRIx64 " gpa=0x%010" PRIx64 " size=0x%" PRIx64 " access_type=%d"
+14
View File
@@ -0,0 +1,14 @@
/*
* QEMU MSHV support
*
* Copyright Microsoft, Corp. 2025
*
* Authors:
* Ziqiao Zhou <[email protected]>
* Magnus Kulke <[email protected]>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
*/
#include "trace/trace-accel_mshv.h"
+3
View File
@@ -0,0 +1,3 @@
nitro_ss = ss.source_set()
nitro_ss.add(files('nitro-accel.c'))
system_ss.add_all(when: 'CONFIG_NITRO', if_true: nitro_ss)
+284
View File
@@ -0,0 +1,284 @@
/*
* Nitro Enclaves accelerator
*
* Copyright © 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Authors:
* Alexander Graf <[email protected]>
*
* Nitro Enclaves are a confidential compute technology which
* allows a parent instance to carve out resources from itself
* and spawn a confidential sibling VM next to itself. Similar
* to other confidential compute solutions, this sibling is
* controlled by an underlying vmm, but still has a higher level
* vmm (QEMU) to implement some of its I/O functionality and
* lifecycle.
*
* This accelerator drives /dev/nitro_enclaves to spawn a Nitro
* Enclave. It works in tandem with the nitro_enclaves machine
* which ensures the correct backend devices are available and
* that the initial seed (an EIF file) is loaded at the correct
* offset in memory.
*
* The accel starts the enclave when the machine starts, after
* all device setup is finished.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "qemu/error-report.h"
#include "qapi/error.h"
#include "qapi/visitor.h"
#include "qemu/module.h"
#include "qemu/rcu.h"
#include "qemu/accel.h"
#include "qemu/guest-random.h"
#include "qemu/main-loop.h"
#include "accel/accel-ops.h"
#include "accel/accel-cpu-ops.h"
#include "accel/dummy-cpus.h"
#include "system/cpus.h"
#include "hw/core/cpu.h"
#include "hw/core/boards.h"
#include "hw/nitro/nitro-vsock-bus.h"
#include "system/ramblock.h"
#include "system/nitro-accel.h"
#include "trace.h"
#include <sys/ioctl.h>
#include "standard-headers/linux/nitro_enclaves.h"
bool nitro_allowed;
typedef struct NitroAccelState {
AccelState parent_obj;
int ne_fd;
int enclave_fd;
uint64_t slot_uid;
uint64_t enclave_cid;
bool debug_mode;
} NitroAccelState;
static int nitro_init_machine(AccelState *as, MachineState *ms)
{
NitroAccelState *s = NITRO_ACCEL(as);
uint64_t slot_uid = 0;
int ret;
s->ne_fd = open("/dev/nitro_enclaves", O_RDWR | O_CLOEXEC);
if (s->ne_fd < 0) {
error_report("nitro: failed to open /dev/nitro_enclaves: %s",
strerror(errno));
return -errno;
}
ret = ioctl(s->ne_fd, NE_CREATE_VM, &slot_uid);
if (ret < 0) {
error_report("nitro: NE_CREATE_VM failed: %s", strerror(errno));
close(s->ne_fd);
return -errno;
}
s->enclave_fd = ret;
s->slot_uid = slot_uid;
return 0;
}
static int nitro_donate_ram_block(RAMBlock *rb, void *opaque)
{
NitroAccelState *s = opaque;
struct ne_user_memory_region region = {
.flags = 0,
.memory_size = rb->used_length,
.userspace_addr = (uint64_t)(uintptr_t)rb->host,
};
if (!rb->used_length) {
return 0;
}
if (ioctl(s->enclave_fd, NE_SET_USER_MEMORY_REGION, &region) < 0) {
error_report("nitro: NE_SET_USER_MEMORY_REGION failed for %s "
"(%" PRIu64 " bytes): %s", rb->idstr, rb->used_length,
strerror(errno));
return -errno;
}
return 0;
}
/*
* Start the Enclave. At this point memory is set up and the EIF is loaded.
* This function donates memory, adds vCPUs, and starts the enclave.
*/
static void nitro_setup_post(AccelState *as)
{
MachineState *ms = MACHINE(qdev_get_machine());
NitroAccelState *s = NITRO_ACCEL(as);
int nr_cpus = ms->smp.cpus;
int i, ret;
struct ne_enclave_start_info start_info = {
.flags = s->debug_mode ? NE_ENCLAVE_DEBUG_MODE : 0,
.enclave_cid = s->enclave_cid,
};
ret = qemu_ram_foreach_block(nitro_donate_ram_block, s);
if (ret < 0) {
error_report("nitro: failed to donate memory");
exit(1);
}
for (i = 0; i < nr_cpus; i++) {
uint32_t cpu_id = 0;
if (ioctl(s->enclave_fd, NE_ADD_VCPU, &cpu_id) < 0) {
error_report("nitro: NE_ADD_VCPU failed: %s", strerror(errno));
exit(1);
}
}
ret = ioctl(s->enclave_fd, NE_START_ENCLAVE, &start_info);
if (ret < 0) {
switch (errno) {
case NE_ERR_NO_MEM_REGIONS_ADDED:
error_report("nitro: no memory regions added");
break;
case NE_ERR_NO_VCPUS_ADDED:
error_report("nitro: no vCPUs added");
break;
case NE_ERR_ENCLAVE_MEM_MIN_SIZE:
error_report("nitro: memory is below the minimum "
"required size. Try increasing -m");
break;
case NE_ERR_FULL_CORES_NOT_USED:
error_report("nitro: requires full CPU cores. "
"Try increasing -smp to a multiple of threads "
"per core on this host (e.g. -smp 2)");
break;
case NE_ERR_NOT_IN_INIT_STATE:
error_report("nitro: not in init state");
break;
case NE_ERR_INVALID_FLAG_VALUE:
error_report("nitro: invalid flag value for NE_START_ENCLAVE");
break;
case NE_ERR_INVALID_ENCLAVE_CID:
error_report("nitro: invalid enclave CID");
break;
default:
error_report("nitro: NE_START_ENCLAVE failed: %s (errno %d)",
strerror(errno), errno);
break;
}
exit(1);
}
s->enclave_cid = start_info.enclave_cid;
trace_nitro_enclave_started(s->enclave_cid);
/*
* Notify all Nitro vsock bus devices that the enclave has started
* and provide them with the CID for vsock connections.
*/
{
NitroVsockBridge *bridge = nitro_vsock_bridge_find();
Error *err = NULL;
if (bridge) {
nitro_vsock_bridge_start_enclave(bridge,
(uint32_t)s->enclave_cid, &err);
if (err) {
error_report_err(err);
exit(1);
}
}
}
}
/* QOM properties */
static bool nitro_get_debug_mode(Object *obj, Error **errp)
{
return NITRO_ACCEL(obj)->debug_mode;
}
static void nitro_set_debug_mode(Object *obj, bool value, Error **errp)
{
NITRO_ACCEL(obj)->debug_mode = value;
}
static void nitro_get_enclave_cid(Object *obj, Visitor *v,
const char *name, void *opaque,
Error **errp)
{
uint64_t val = NITRO_ACCEL(obj)->enclave_cid;
visit_type_uint64(v, name, &val, errp);
}
static void nitro_set_enclave_cid(Object *obj, Visitor *v,
const char *name, void *opaque,
Error **errp)
{
uint64_t val;
if (visit_type_uint64(v, name, &val, errp)) {
NITRO_ACCEL(obj)->enclave_cid = val;
}
}
static void nitro_accel_class_init(ObjectClass *oc, const void *data)
{
AccelClass *ac = ACCEL_CLASS(oc);
ac->name = "Nitro";
ac->init_machine = nitro_init_machine;
ac->setup_post = nitro_setup_post;
ac->allowed = &nitro_allowed;
object_class_property_add_bool(oc, "debug-mode",
nitro_get_debug_mode,
nitro_set_debug_mode);
object_class_property_set_description(oc, "debug-mode",
"Start enclave in debug mode (enables console output)");
object_class_property_add(oc, "enclave-cid", "uint64",
nitro_get_enclave_cid,
nitro_set_enclave_cid,
NULL, NULL);
object_class_property_set_description(oc, "enclave-cid",
"Enclave CID (0 = auto-assigned by Nitro)");
}
static const TypeInfo nitro_accel_type = {
.name = TYPE_NITRO_ACCEL,
.parent = TYPE_ACCEL,
.instance_size = sizeof(NitroAccelState),
.class_init = nitro_accel_class_init,
};
module_obj(TYPE_NITRO_ACCEL);
static bool nitro_cpus_are_resettable(void)
{
return false;
}
static void nitro_accel_ops_class_init(ObjectClass *oc, const void *data)
{
AccelOpsClass *ops = ACCEL_OPS_CLASS(oc);
ops->create_vcpu_thread = dummy_start_vcpu_thread;
ops->handle_interrupt = generic_handle_interrupt;
ops->cpus_are_resettable = nitro_cpus_are_resettable;
}
static const TypeInfo nitro_accel_ops_type = {
.name = ACCEL_OPS_NAME("nitro"),
.parent = TYPE_ACCEL_OPS,
.class_init = nitro_accel_ops_class_init,
.abstract = true,
};
module_obj(ACCEL_OPS_NAME("nitro"));
static void nitro_type_init(void)
{
type_register_static(&nitro_accel_type);
type_register_static(&nitro_accel_ops_type);
}
type_init(nitro_type_init);
+6
View File
@@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# See docs/devel/tracing.rst for syntax documentation.
# nitro-accel.c
nitro_enclave_started(uint64_t cid) "nitro: enclave started, CID=%"PRIu64
+2
View File
@@ -0,0 +1,2 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "trace/trace-accel_nitro.h"
+4
View File
@@ -0,0 +1,4 @@
qtest_module_ss = ss.source_set()
qtest_module_ss.add(files('qtest.c'))
modules += {'accel': {'qtest': qtest_module_ss}}
+89
View File
@@ -0,0 +1,89 @@
/*
* QTest accelerator code
*
* Copyright IBM, Corp. 2011
*
* Authors:
* Anthony Liguori <[email protected]>
*
* 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/rcu.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "qemu/option.h"
#include "qemu/config-file.h"
#include "qemu/accel.h"
#include "accel/accel-ops.h"
#include "accel/accel-cpu-ops.h"
#include "system/qtest.h"
#include "system/cpus.h"
#include "qemu/guest-random.h"
#include "qemu/main-loop.h"
#include "hw/core/cpu.h"
#include "accel/dummy-cpus.h"
static int64_t qtest_clock_counter;
static int64_t qtest_get_virtual_clock(void)
{
return qatomic_read(&qtest_clock_counter);
}
static void qtest_set_virtual_clock(int64_t count)
{
qatomic_set(&qtest_clock_counter, count);
}
static int qtest_init_accel(AccelState *as, MachineState *ms)
{
return 0;
}
static void qtest_accel_class_init(ObjectClass *oc, const void *data)
{
AccelClass *ac = ACCEL_CLASS(oc);
ac->name = "QTest";
ac->init_machine = qtest_init_accel;
ac->allowed = &qtest_allowed;
}
#define TYPE_QTEST_ACCEL ACCEL_CLASS_NAME("qtest")
static const TypeInfo qtest_accel_type = {
.name = TYPE_QTEST_ACCEL,
.parent = TYPE_ACCEL,
.class_init = qtest_accel_class_init,
};
module_obj(TYPE_QTEST_ACCEL);
static void qtest_accel_ops_class_init(ObjectClass *oc, const void *data)
{
AccelOpsClass *ops = ACCEL_OPS_CLASS(oc);
ops->create_vcpu_thread = dummy_start_vcpu_thread;
ops->get_virtual_clock = qtest_get_virtual_clock;
ops->set_virtual_clock = qtest_set_virtual_clock;
ops->handle_interrupt = generic_handle_interrupt;
};
static const TypeInfo qtest_accel_ops_type = {
.name = ACCEL_OPS_NAME("qtest"),
.parent = TYPE_ACCEL_OPS,
.class_init = qtest_accel_ops_class_init,
.abstract = true,
};
module_obj(ACCEL_OPS_NAME("qtest"));
static void qtest_type_init(void)
{
type_register_static(&qtest_accel_type);
type_register_static(&qtest_accel_ops_type);
}
type_init(qtest_type_init);
+24
View File
@@ -0,0 +1,24 @@
/*
* HVF stubs for QEMU
*
* Copyright (c) Linaro
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "system/hvf.h"
bool hvf_allowed;
bool hvf_kernel_irqchip;
bool hvf_nested_virt;
void hvf_nested_virt_enable(bool nested_virt)
{
/*
* This is called unconditionally from hw/arm/virt.c
* because we don't know if HVF is going to be used
* as that step of initialisation happens later.
* As such, do nothing here instead of marking as unreachable.
*/
}
+145
View File
@@ -0,0 +1,145 @@
/*
* QEMU KVM stub
*
* Copyright Red Hat, Inc. 2010
*
* Author: Paolo Bonzini <[email protected]>
*
* 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 "system/kvm.h"
#include "hw/pci/msi.h"
KVMState *kvm_state;
bool kvm_kernel_irqchip;
bool kvm_async_interrupts_allowed;
bool kvm_resamplefds_allowed;
bool kvm_msi_via_irqfd_allowed;
bool kvm_gsi_routing_allowed;
bool kvm_gsi_direct_mapping;
bool kvm_allowed;
bool kvm_readonly_mem_allowed;
bool kvm_msi_use_devid;
void kvm_flush_coalesced_mmio_buffer(void)
{
}
bool kvm_has_sync_mmu(void)
{
return false;
}
int kvm_on_sigbus_vcpu(CPUState *cpu, int code, void *addr)
{
return 1;
}
int kvm_on_sigbus(int code, void *addr)
{
return 1;
}
int kvm_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev)
{
return -ENOSYS;
}
void kvm_init_irq_routing(KVMState *s)
{
}
void kvm_irqchip_release_virq(KVMState *s, int virq)
{
}
int kvm_irqchip_update_msi_route(KVMState *s, int virq, MSIMessage msg,
PCIDevice *dev)
{
return -ENOSYS;
}
void kvm_irqchip_commit_routes(KVMState *s)
{
}
void kvm_irqchip_add_change_notifier(Notifier *n)
{
}
void kvm_irqchip_remove_change_notifier(Notifier *n)
{
}
void kvm_irqchip_change_notify(void)
{
}
void kvm_vmfd_add_change_notifier(NotifierWithReturn *n)
{
}
void kvm_vmfd_remove_change_notifier(NotifierWithReturn *n)
{
}
void kvm_vcpufd_add_change_notifier(NotifierWithReturn *n)
{
return;
}
void kvm_vcpufd_remove_change_notifier(NotifierWithReturn *n)
{
return;
}
int kvm_irqchip_add_irqfd_notifier_gsi(KVMState *s, EventNotifier *n,
EventNotifier *rn, int virq)
{
return -ENOSYS;
}
int kvm_irqchip_remove_irqfd_notifier_gsi(KVMState *s, EventNotifier *n,
int virq)
{
return -ENOSYS;
}
unsigned int kvm_get_max_memslots(void)
{
return 0;
}
unsigned int kvm_get_free_memslots(void)
{
return 0;
}
bool kvm_arm_supports_user_irq(void)
{
return false;
}
bool kvm_dirty_ring_enabled(void)
{
return false;
}
uint32_t kvm_dirty_ring_size(void)
{
return 0;
}
bool kvm_hwpoisoned_mem(void)
{
return false;
}
int kvm_create_guest_memfd(uint64_t size, uint64_t flags, Error **errp)
{
return -ENOSYS;
}
+9
View File
@@ -0,0 +1,9 @@
stub_ss.add(files(
'hvf-stub.c',
'kvm-stub.c',
'nitro-stub.c',
'mshv-stub.c',
'nvmm-stub.c',
'whpx-stub.c',
'xen-stub.c',
))
+45
View File
@@ -0,0 +1,45 @@
/*
* QEMU MSHV stub
*
* Copyright Red Hat, Inc. 2025
*
* Author: Paolo Bonzini <[email protected]>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "hw/pci/msi.h"
#include "system/mshv.h"
bool mshv_allowed;
MshvState *mshv_state;
int mshv_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev)
{
return -ENOSYS;
}
void mshv_irqchip_release_virq(MshvState *s, int virq)
{
}
int mshv_irqchip_update_msi_route(int virq, MSIMessage msg, PCIDevice *dev)
{
return -ENOSYS;
}
void mshv_irqchip_commit_routes(MshvState *s)
{
}
int mshv_irqchip_add_irqfd_notifier_gsi(const EventNotifier *n,
const EventNotifier *rn, int virq)
{
return -ENOSYS;
}
int mshv_irqchip_remove_irqfd_notifier_gsi(const EventNotifier *n, int virq)
{
return -ENOSYS;
}
+11
View File
@@ -0,0 +1,11 @@
/*
* Nitro accel stubs for QEMU
*
* Copyright © 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
bool nitro_allowed;
+12
View File
@@ -0,0 +1,12 @@
/*
* NVMM stubs for QEMU
*
* Copyright (c) Linaro
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "system/nvmm.h"
bool nvmm_allowed;
+13
View File
@@ -0,0 +1,13 @@
/*
* WHPX stubs for QEMU
*
* Copyright (c) Linaro
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "system/whpx.h"
bool whpx_allowed;
bool whpx_irqchip_in_kernel;
+16
View File
@@ -0,0 +1,16 @@
/*
* Copyright (C) 2014 Citrix Systems UK Ltd.
*
* 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 "system/xen.h"
#include "qapi/qapi-commands-migration.h"
bool xen_allowed;
void qmp_xen_set_global_dirty_log(bool enable, Error **errp)
{
}
+103
View File
@@ -0,0 +1,103 @@
/*
* Common Atomic Helper Functions
*
* This file should be included before the various instantiations of
* the atomic_template.h helpers.
*
* Copyright (c) 2019 Linaro
* Written by Alex Bennée <alex.bennee@linaro.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
static void atomic_trace_rmw_post(CPUArchState *env, uint64_t addr,
uint64_t read_value_low,
uint64_t read_value_high,
uint64_t write_value_low,
uint64_t write_value_high,
MemOpIdx oi)
{
if (cpu_plugin_mem_cbs_enabled(env_cpu(env))) {
qemu_plugin_vcpu_mem_cb(env_cpu(env), addr,
read_value_low, read_value_high,
oi, QEMU_PLUGIN_MEM_R);
qemu_plugin_vcpu_mem_cb(env_cpu(env), addr,
write_value_low, write_value_high,
oi, QEMU_PLUGIN_MEM_W);
}
}
/*
* Atomic helpers callable from TCG.
* These have a common interface and all defer to cpu_atomic_*
* using the host return address from GETPC().
*/
#define CMPXCHG_HELPER(OP, TYPE) \
TYPE HELPER(atomic_##OP)(CPUArchState *env, uint64_t addr, \
TYPE oldv, TYPE newv, uint32_t oi) \
{ return cpu_atomic_##OP##_mmu(env, addr, oldv, newv, oi, GETPC()); }
CMPXCHG_HELPER(cmpxchgb, uint32_t)
CMPXCHG_HELPER(cmpxchgw_be, uint32_t)
CMPXCHG_HELPER(cmpxchgw_le, uint32_t)
CMPXCHG_HELPER(cmpxchgl_be, uint32_t)
CMPXCHG_HELPER(cmpxchgl_le, uint32_t)
CMPXCHG_HELPER(cmpxchgq_be, uint64_t)
CMPXCHG_HELPER(cmpxchgq_le, uint64_t)
#if HAVE_CMPXCHG128
CMPXCHG_HELPER(cmpxchgo_be, Int128)
CMPXCHG_HELPER(cmpxchgo_le, Int128)
#endif
#undef CMPXCHG_HELPER
#define ATOMIC_HELPER(OP, TYPE) \
TYPE HELPER(glue(atomic_,OP))(CPUArchState *env, uint64_t addr, \
TYPE val, uint32_t oi) \
{ return glue(glue(cpu_atomic_,OP),_mmu)(env, addr, val, oi, GETPC()); }
#define GEN_ATOMIC_HELPERS(OP) \
ATOMIC_HELPER(glue(OP,b), uint32_t) \
ATOMIC_HELPER(glue(OP,w_be), uint32_t) \
ATOMIC_HELPER(glue(OP,w_le), uint32_t) \
ATOMIC_HELPER(glue(OP,l_be), uint32_t) \
ATOMIC_HELPER(glue(OP,l_le), uint32_t) \
ATOMIC_HELPER(glue(OP,q_be), uint64_t) \
ATOMIC_HELPER(glue(OP,q_le), uint64_t)
GEN_ATOMIC_HELPERS(fetch_add)
GEN_ATOMIC_HELPERS(fetch_and)
GEN_ATOMIC_HELPERS(fetch_or)
GEN_ATOMIC_HELPERS(fetch_xor)
GEN_ATOMIC_HELPERS(fetch_smin)
GEN_ATOMIC_HELPERS(fetch_umin)
GEN_ATOMIC_HELPERS(fetch_smax)
GEN_ATOMIC_HELPERS(fetch_umax)
GEN_ATOMIC_HELPERS(add_fetch)
GEN_ATOMIC_HELPERS(and_fetch)
GEN_ATOMIC_HELPERS(or_fetch)
GEN_ATOMIC_HELPERS(xor_fetch)
GEN_ATOMIC_HELPERS(smin_fetch)
GEN_ATOMIC_HELPERS(umin_fetch)
GEN_ATOMIC_HELPERS(smax_fetch)
GEN_ATOMIC_HELPERS(umax_fetch)
GEN_ATOMIC_HELPERS(xchg)
#if HAVE_CMPXCHG128
ATOMIC_HELPER(xchgo_be, Int128)
ATOMIC_HELPER(xchgo_le, Int128)
ATOMIC_HELPER(fetch_ando_be, Int128)
ATOMIC_HELPER(fetch_ando_le, Int128)
ATOMIC_HELPER(fetch_oro_be, Int128)
ATOMIC_HELPER(fetch_oro_le, Int128)
#endif
#undef ATOMIC_HELPER
#undef GEN_ATOMIC_HELPERS
+405
View File
@@ -0,0 +1,405 @@
/*
* Atomic helper templates
* Included from tcg-runtime.c and cputlb.c.
*
* Copyright (c) 2016 Red Hat, Inc
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/plugin.h"
#if DATA_SIZE == 16
# define SUFFIX o
# define DATA_TYPE Int128
# define BSWAP bswap128
# define SHIFT 4
#elif DATA_SIZE == 8
# define SUFFIX q
# define DATA_TYPE uint64_t
# define SDATA_TYPE int64_t
# define BSWAP bswap64
# define SHIFT 3
#elif DATA_SIZE == 4
# define SUFFIX l
# define DATA_TYPE uint32_t
# define SDATA_TYPE int32_t
# define BSWAP bswap32
# define SHIFT 2
#elif DATA_SIZE == 2
# define SUFFIX w
# define DATA_TYPE uint16_t
# define SDATA_TYPE int16_t
# define BSWAP bswap16
# define SHIFT 1
#elif DATA_SIZE == 1
# define SUFFIX b
# define DATA_TYPE uint8_t
# define SDATA_TYPE int8_t
# define BSWAP
# define SHIFT 0
#else
# error unsupported data size
#endif
#if DATA_SIZE == 16
# define VALUE_LOW(val) int128_getlo(val)
# define VALUE_HIGH(val) int128_gethi(val)
#else
# define VALUE_LOW(val) val
# define VALUE_HIGH(val) 0
#endif
#if DATA_SIZE >= 4
# define ABI_TYPE DATA_TYPE
#else
# define ABI_TYPE uint32_t
#endif
/* Define host-endian atomic operations. Note that END is used within
the ATOMIC_NAME macro, and redefined below. */
#if DATA_SIZE == 1
# define END
#elif HOST_BIG_ENDIAN
# define END _be
#else
# define END _le
#endif
ABI_TYPE ATOMIC_NAME(cmpxchg)(CPUArchState *env, vaddr addr,
ABI_TYPE cmpv, ABI_TYPE newv,
MemOpIdx oi, uintptr_t retaddr)
{
DATA_TYPE *haddr = atomic_mmu_lookup(env_cpu(env), addr, oi,
DATA_SIZE, retaddr);
DATA_TYPE ret;
#if DATA_SIZE == 16
ret = atomic16_cmpxchg(haddr, cmpv, newv);
#else
ret = qatomic_cmpxchg__nocheck(haddr, cmpv, newv);
#endif
ATOMIC_MMU_CLEANUP;
atomic_trace_rmw_post(env, addr,
VALUE_LOW(ret),
VALUE_HIGH(ret),
VALUE_LOW(newv),
VALUE_HIGH(newv),
oi);
return ret;
}
ABI_TYPE ATOMIC_NAME(xchg)(CPUArchState *env, vaddr addr, ABI_TYPE val,
MemOpIdx oi, uintptr_t retaddr)
{
DATA_TYPE *haddr = atomic_mmu_lookup(env_cpu(env), addr, oi,
DATA_SIZE, retaddr);
DATA_TYPE ret;
#if DATA_SIZE == 16
ret = atomic16_xchg(haddr, val);
#else
ret = qatomic_xchg__nocheck(haddr, val);
#endif
ATOMIC_MMU_CLEANUP;
atomic_trace_rmw_post(env, addr,
VALUE_LOW(ret),
VALUE_HIGH(ret),
VALUE_LOW(val),
VALUE_HIGH(val),
oi);
return ret;
}
#if DATA_SIZE == 16
ABI_TYPE ATOMIC_NAME(fetch_and)(CPUArchState *env, vaddr addr, ABI_TYPE val,
MemOpIdx oi, uintptr_t retaddr)
{
DATA_TYPE *haddr = atomic_mmu_lookup(env_cpu(env), addr, oi,
DATA_SIZE, retaddr);
DATA_TYPE ret = atomic16_fetch_and(haddr, val);
ATOMIC_MMU_CLEANUP;
atomic_trace_rmw_post(env, addr,
VALUE_LOW(ret),
VALUE_HIGH(ret),
VALUE_LOW(val),
VALUE_HIGH(val),
oi);
return ret;
}
ABI_TYPE ATOMIC_NAME(fetch_or)(CPUArchState *env, vaddr addr, ABI_TYPE val,
MemOpIdx oi, uintptr_t retaddr)
{
DATA_TYPE *haddr = atomic_mmu_lookup(env_cpu(env), addr, oi,
DATA_SIZE, retaddr);
DATA_TYPE ret = atomic16_fetch_or(haddr, val);
ATOMIC_MMU_CLEANUP;
atomic_trace_rmw_post(env, addr,
VALUE_LOW(ret),
VALUE_HIGH(ret),
VALUE_LOW(val),
VALUE_HIGH(val),
oi);
return ret;
}
#else
#define GEN_ATOMIC_HELPER(X) \
ABI_TYPE ATOMIC_NAME(X)(CPUArchState *env, vaddr addr, \
ABI_TYPE val, MemOpIdx oi, uintptr_t retaddr) \
{ \
DATA_TYPE *haddr, ret; \
haddr = atomic_mmu_lookup(env_cpu(env), addr, oi, DATA_SIZE, retaddr); \
ret = qatomic_##X(haddr, val); \
ATOMIC_MMU_CLEANUP; \
atomic_trace_rmw_post(env, addr, \
VALUE_LOW(ret), \
VALUE_HIGH(ret), \
VALUE_LOW(val), \
VALUE_HIGH(val), \
oi); \
return ret; \
}
GEN_ATOMIC_HELPER(fetch_add)
GEN_ATOMIC_HELPER(fetch_and)
GEN_ATOMIC_HELPER(fetch_or)
GEN_ATOMIC_HELPER(fetch_xor)
GEN_ATOMIC_HELPER(add_fetch)
GEN_ATOMIC_HELPER(and_fetch)
GEN_ATOMIC_HELPER(or_fetch)
GEN_ATOMIC_HELPER(xor_fetch)
#undef GEN_ATOMIC_HELPER
/*
* These helpers are, as a whole, full barriers. Within the helper,
* the leading barrier is explicit and the trailing barrier is within
* cmpxchg primitive.
*
* Trace this load + RMW loop as a single RMW op. This way, regardless
* of CF_PARALLEL's value, we'll trace just a read and a write.
*/
#define GEN_ATOMIC_HELPER_FN(X, FN, XDATA_TYPE, RET) \
ABI_TYPE ATOMIC_NAME(X)(CPUArchState *env, vaddr addr, \
ABI_TYPE xval, MemOpIdx oi, uintptr_t retaddr) \
{ \
XDATA_TYPE *haddr, cmp, old, new, val = xval; \
haddr = atomic_mmu_lookup(env_cpu(env), addr, oi, DATA_SIZE, retaddr); \
smp_mb(); \
cmp = qatomic_read__nocheck(haddr); \
do { \
old = cmp; new = FN(old, val); \
cmp = qatomic_cmpxchg__nocheck(haddr, old, new); \
} while (cmp != old); \
ATOMIC_MMU_CLEANUP; \
atomic_trace_rmw_post(env, addr, \
VALUE_LOW(old), \
VALUE_HIGH(old), \
VALUE_LOW(xval), \
VALUE_HIGH(xval), \
oi); \
return RET; \
}
GEN_ATOMIC_HELPER_FN(fetch_smin, MIN, SDATA_TYPE, old)
GEN_ATOMIC_HELPER_FN(fetch_umin, MIN, DATA_TYPE, old)
GEN_ATOMIC_HELPER_FN(fetch_smax, MAX, SDATA_TYPE, old)
GEN_ATOMIC_HELPER_FN(fetch_umax, MAX, DATA_TYPE, old)
GEN_ATOMIC_HELPER_FN(smin_fetch, MIN, SDATA_TYPE, new)
GEN_ATOMIC_HELPER_FN(umin_fetch, MIN, DATA_TYPE, new)
GEN_ATOMIC_HELPER_FN(smax_fetch, MAX, SDATA_TYPE, new)
GEN_ATOMIC_HELPER_FN(umax_fetch, MAX, DATA_TYPE, new)
#undef GEN_ATOMIC_HELPER_FN
#endif /* DATA SIZE == 16 */
#undef END
#if DATA_SIZE > 1
/* Define reverse-host-endian atomic operations. Note that END is used
within the ATOMIC_NAME macro. */
#if HOST_BIG_ENDIAN
# define END _le
#else
# define END _be
#endif
ABI_TYPE ATOMIC_NAME(cmpxchg)(CPUArchState *env, vaddr addr,
ABI_TYPE cmpv, ABI_TYPE newv,
MemOpIdx oi, uintptr_t retaddr)
{
DATA_TYPE *haddr = atomic_mmu_lookup(env_cpu(env), addr, oi,
DATA_SIZE, retaddr);
DATA_TYPE ret;
#if DATA_SIZE == 16
ret = atomic16_cmpxchg(haddr, BSWAP(cmpv), BSWAP(newv));
#else
ret = qatomic_cmpxchg__nocheck(haddr, BSWAP(cmpv), BSWAP(newv));
#endif
ATOMIC_MMU_CLEANUP;
atomic_trace_rmw_post(env, addr,
VALUE_LOW(ret),
VALUE_HIGH(ret),
VALUE_LOW(newv),
VALUE_HIGH(newv),
oi);
return BSWAP(ret);
}
ABI_TYPE ATOMIC_NAME(xchg)(CPUArchState *env, vaddr addr, ABI_TYPE val,
MemOpIdx oi, uintptr_t retaddr)
{
DATA_TYPE *haddr = atomic_mmu_lookup(env_cpu(env), addr, oi,
DATA_SIZE, retaddr);
ABI_TYPE ret;
#if DATA_SIZE == 16
ret = atomic16_xchg(haddr, BSWAP(val));
#else
ret = qatomic_xchg__nocheck(haddr, BSWAP(val));
#endif
ATOMIC_MMU_CLEANUP;
atomic_trace_rmw_post(env, addr,
VALUE_LOW(ret),
VALUE_HIGH(ret),
VALUE_LOW(val),
VALUE_HIGH(val),
oi);
return BSWAP(ret);
}
#if DATA_SIZE == 16
ABI_TYPE ATOMIC_NAME(fetch_and)(CPUArchState *env, vaddr addr, ABI_TYPE val,
MemOpIdx oi, uintptr_t retaddr)
{
DATA_TYPE *haddr = atomic_mmu_lookup(env_cpu(env), addr, oi,
DATA_SIZE, retaddr);
DATA_TYPE ret = atomic16_fetch_and(haddr, BSWAP(val));
ATOMIC_MMU_CLEANUP;
atomic_trace_rmw_post(env, addr,
VALUE_LOW(ret),
VALUE_HIGH(ret),
VALUE_LOW(val),
VALUE_HIGH(val),
oi);
return BSWAP(ret);
}
ABI_TYPE ATOMIC_NAME(fetch_or)(CPUArchState *env, vaddr addr, ABI_TYPE val,
MemOpIdx oi, uintptr_t retaddr)
{
DATA_TYPE *haddr = atomic_mmu_lookup(env_cpu(env), addr, oi,
DATA_SIZE, retaddr);
DATA_TYPE ret = atomic16_fetch_or(haddr, BSWAP(val));
ATOMIC_MMU_CLEANUP;
atomic_trace_rmw_post(env, addr,
VALUE_LOW(ret),
VALUE_HIGH(ret),
VALUE_LOW(val),
VALUE_HIGH(val),
oi);
return BSWAP(ret);
}
#else
#define GEN_ATOMIC_HELPER(X) \
ABI_TYPE ATOMIC_NAME(X)(CPUArchState *env, vaddr addr, \
ABI_TYPE val, MemOpIdx oi, uintptr_t retaddr) \
{ \
DATA_TYPE *haddr, ret; \
haddr = atomic_mmu_lookup(env_cpu(env), addr, oi, DATA_SIZE, retaddr); \
ret = qatomic_##X(haddr, BSWAP(val)); \
ATOMIC_MMU_CLEANUP; \
atomic_trace_rmw_post(env, addr, \
VALUE_LOW(ret), \
VALUE_HIGH(ret), \
VALUE_LOW(val), \
VALUE_HIGH(val), \
oi); \
return BSWAP(ret); \
}
GEN_ATOMIC_HELPER(fetch_and)
GEN_ATOMIC_HELPER(fetch_or)
GEN_ATOMIC_HELPER(fetch_xor)
GEN_ATOMIC_HELPER(and_fetch)
GEN_ATOMIC_HELPER(or_fetch)
GEN_ATOMIC_HELPER(xor_fetch)
#undef GEN_ATOMIC_HELPER
/* These helpers are, as a whole, full barriers. Within the helper,
* the leading barrier is explicit and the trailing barrier is within
* cmpxchg primitive.
*
* Trace this load + RMW loop as a single RMW op. This way, regardless
* of CF_PARALLEL's value, we'll trace just a read and a write.
*/
#define GEN_ATOMIC_HELPER_FN(X, FN, XDATA_TYPE, RET) \
ABI_TYPE ATOMIC_NAME(X)(CPUArchState *env, vaddr addr, \
ABI_TYPE xval, MemOpIdx oi, uintptr_t retaddr) \
{ \
XDATA_TYPE *haddr, ldo, ldn, old, new, val = xval; \
haddr = atomic_mmu_lookup(env_cpu(env), addr, oi, DATA_SIZE, retaddr); \
smp_mb(); \
ldn = qatomic_read__nocheck(haddr); \
do { \
ldo = ldn; old = BSWAP(ldo); new = FN(old, val); \
ldn = qatomic_cmpxchg__nocheck(haddr, ldo, BSWAP(new)); \
} while (ldo != ldn); \
ATOMIC_MMU_CLEANUP; \
atomic_trace_rmw_post(env, addr, \
VALUE_LOW(old), \
VALUE_HIGH(old), \
VALUE_LOW(xval), \
VALUE_HIGH(xval), \
oi); \
return RET; \
}
GEN_ATOMIC_HELPER_FN(fetch_smin, MIN, SDATA_TYPE, old)
GEN_ATOMIC_HELPER_FN(fetch_umin, MIN, DATA_TYPE, old)
GEN_ATOMIC_HELPER_FN(fetch_smax, MAX, SDATA_TYPE, old)
GEN_ATOMIC_HELPER_FN(fetch_umax, MAX, DATA_TYPE, old)
GEN_ATOMIC_HELPER_FN(smin_fetch, MIN, SDATA_TYPE, new)
GEN_ATOMIC_HELPER_FN(umin_fetch, MIN, DATA_TYPE, new)
GEN_ATOMIC_HELPER_FN(smax_fetch, MAX, SDATA_TYPE, new)
GEN_ATOMIC_HELPER_FN(umax_fetch, MAX, DATA_TYPE, new)
/* Note that for addition, we need to use a separate cmpxchg loop instead
of bswaps for the reverse-host-endian helpers. */
#define ADD(X, Y) (X + Y)
GEN_ATOMIC_HELPER_FN(fetch_add, ADD, DATA_TYPE, old)
GEN_ATOMIC_HELPER_FN(add_fetch, ADD, DATA_TYPE, new)
#undef ADD
#undef GEN_ATOMIC_HELPER_FN
#endif /* DATA_SIZE == 16 */
#undef END
#endif /* DATA_SIZE > 1 */
#undef BSWAP
#undef ABI_TYPE
#undef DATA_TYPE
#undef SDATA_TYPE
#undef SUFFIX
#undef DATA_SIZE
#undef SHIFT
#undef VALUE_LOW
#undef VALUE_HIGH
+41
View File
@@ -0,0 +1,41 @@
/*
* Internal memory barrier helpers for QEMU (target agnostic)
*
* Copyright (c) 2003 Fabrice Bellard
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef ACCEL_TCG_BACKEND_LDST_H
#define ACCEL_TCG_BACKEND_LDST_H
#include "tcg-target-mo.h"
/**
* tcg_req_mo:
* @guest_mo: Guest default memory order
* @type: TCGBar
*
* Filter @type to the barrier that is required for the guest
* memory ordering vs the host memory ordering. A non-zero
* result indicates that some barrier is required.
*/
#define tcg_req_mo(guest_mo, type) \
((type) & guest_mo & ~TCG_TARGET_DEFAULT_MO)
/**
* cpu_req_mo:
* @cpu: CPUState
* @type: TCGBar
*
* If tcg_req_mo indicates a barrier for @type is required
* for the guest memory model, issue a host memory barrier.
*/
#define cpu_req_mo(cpu, type) \
do { \
if (tcg_req_mo(cpu->cc->tcg_ops->guest_default_memory_order, type)) { \
smp_mb(); \
} \
} while (0)
#endif
+91
View File
@@ -0,0 +1,91 @@
/*
* emulator main execution loop
*
* Copyright (c) 2003-2005 Fabrice Bellard
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "exec/log.h"
#include "system/tcg.h"
#include "qemu/plugin.h"
#include "accel/tcg/cpu-loop.h"
#include "internal-common.h"
bool tcg_allowed;
bool tcg_cflags_has(CPUState *cpu, uint32_t flags)
{
return cpu->tcg_cflags & flags;
}
void tcg_cflags_set(CPUState *cpu, uint32_t flags)
{
cpu->tcg_cflags |= flags;
}
uint32_t curr_cflags(CPUState *cpu)
{
uint32_t cflags = cpu->tcg_cflags;
/*
* Record gdb single-step. We should be exiting the TB by raising
* EXCP_DEBUG, but to simplify other tests, disable chaining too.
*
* For singlestep and -d nochain, suppress goto_tb so that
* we can log -d cpu,exec after every TB.
*/
if (unlikely(cpu_single_stepping(cpu))) {
cflags |= CF_NO_GOTO_TB | CF_NO_GOTO_PTR | CF_SINGLE_STEP | 1;
} else if (qatomic_read(&one_insn_per_tb)) {
cflags |= CF_NO_GOTO_TB | 1;
} else if (qemu_loglevel_mask(CPU_LOG_TB_NOCHAIN)) {
cflags |= CF_NO_GOTO_TB;
}
return cflags;
}
/* exit the current TB, but without causing any exception to be raised */
void cpu_loop_exit_noexc(CPUState *cpu)
{
cpu->exception_index = -1;
cpu_loop_exit(cpu);
}
void cpu_loop_exit(CPUState *cpu)
{
/* Undo the setting in cpu_tb_exec. */
cpu->neg.can_do_io = true;
/* Undo any setting in generated code. */
qemu_plugin_disable_mem_helpers(cpu);
siglongjmp(cpu->jmp_env, 1);
}
void cpu_loop_exit_restore(CPUState *cpu, uintptr_t pc)
{
if (pc) {
cpu_restore_state(cpu, pc);
}
cpu_loop_exit(cpu);
}
void cpu_loop_exit_atomic(CPUState *cpu, uintptr_t pc)
{
/* Prevent looping if already executing in a serial context. */
g_assert(!cpu_in_serial_context(cpu));
cpu->exception_index = EXCP_ATOMIC;
cpu_loop_exit_restore(cpu, pc);
}
+1090
View File
File diff suppressed because it is too large Load Diff
+2902
View File
File diff suppressed because it is too large Load Diff
+502
View File
@@ -0,0 +1,502 @@
/*
* QEMU System Emulator
*
* Copyright (c) 2003-2008 Fabrice Bellard
*
* 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/cutils.h"
#include "migration/vmstate.h"
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "system/cpus.h"
#include "system/qtest.h"
#include "qemu/main-loop.h"
#include "qemu/option.h"
#include "qemu/seqlock.h"
#include "system/replay.h"
#include "system/runstate.h"
#include "hw/core/cpu.h"
#include "exec/icount.h"
#include "system/cpu-timers-internal.h"
/*
* ICOUNT: Instruction Counter
*
* this module is split off from cpu-timers because the icount part
* is TCG-specific, and does not need to be built for other accels.
*/
static bool icount_sleep = true;
/* Arbitrarily pick 1MIPS as the minimum allowable speed. */
#define MAX_ICOUNT_SHIFT 10
bool icount_align_option;
/* Do not count executed instructions */
ICountMode use_icount = ICOUNT_DISABLED;
static void icount_enable_precise(void)
{
/* Fixed conversion of insn to ns via "shift" option */
use_icount = ICOUNT_PRECISE;
}
static void icount_enable_adaptive(void)
{
/* Runtime adaptive algorithm to compute shift */
use_icount = ICOUNT_ADAPTATIVE;
}
/*
* The current number of executed instructions is based on what we
* originally budgeted minus the current state of the decrementing
* icount counters in extra/u16.low.
*/
static int64_t icount_get_executed(CPUState *cpu)
{
return (cpu->icount_budget -
(cpu->neg.icount_decr.u16.low + cpu->icount_extra));
}
/*
* Update the global shared timer_state.qemu_icount to take into
* account executed instructions. This is done by the TCG vCPU
* thread so the main-loop can see time has moved forward.
*/
static void icount_update_locked(CPUState *cpu)
{
int64_t executed = icount_get_executed(cpu);
cpu->icount_budget -= executed;
qatomic_set(&timers_state.qemu_icount,
timers_state.qemu_icount + executed);
}
/*
* Update the global shared timer_state.qemu_icount to take into
* account executed instructions. This is done by the TCG vCPU
* thread so the main-loop can see time has moved forward.
*/
void icount_update(CPUState *cpu)
{
seqlock_write_lock(&timers_state.vm_clock_seqlock,
&timers_state.vm_clock_lock);
icount_update_locked(cpu);
seqlock_write_unlock(&timers_state.vm_clock_seqlock,
&timers_state.vm_clock_lock);
}
static int64_t icount_get_raw_locked(void)
{
CPUState *cpu = current_cpu;
if (cpu && cpu->running) {
if (!cpu->neg.can_do_io) {
error_report("Bad icount read");
exit(1);
}
/* Take into account what has run */
icount_update_locked(cpu);
}
/* The read is protected by the seqlock, but needs atomic to avoid UB */
return qatomic_read(&timers_state.qemu_icount);
}
static int64_t icount_get_locked(void)
{
int64_t icount = icount_get_raw_locked();
return qatomic_read(&timers_state.qemu_icount_bias) + icount_to_ns(icount);
}
int64_t icount_get_raw(void)
{
int64_t icount;
unsigned start;
do {
start = seqlock_read_begin(&timers_state.vm_clock_seqlock);
icount = icount_get_raw_locked();
} while (seqlock_read_retry(&timers_state.vm_clock_seqlock, start));
return icount;
}
/* Return the virtual CPU time, based on the instruction counter. */
int64_t icount_get(void)
{
int64_t icount;
unsigned start;
do {
start = seqlock_read_begin(&timers_state.vm_clock_seqlock);
icount = icount_get_locked();
} while (seqlock_read_retry(&timers_state.vm_clock_seqlock, start));
return icount;
}
int64_t icount_to_ns(int64_t icount)
{
return icount << qatomic_read(&timers_state.icount_time_shift);
}
/*
* Correlation between real and virtual time is always going to be
* fairly approximate, so ignore small variation.
* When the guest is idle real and virtual time will be aligned in
* the IO wait loop.
*/
#define ICOUNT_WOBBLE (NANOSECONDS_PER_SECOND / 10)
static void icount_adjust(void)
{
int64_t cur_time;
int64_t cur_icount;
int64_t delta;
/* If the VM is not running, then do nothing. */
if (!runstate_is_running()) {
return;
}
seqlock_write_lock(&timers_state.vm_clock_seqlock,
&timers_state.vm_clock_lock);
cur_time = REPLAY_CLOCK_LOCKED(REPLAY_CLOCK_VIRTUAL_RT,
cpu_get_clock_locked());
cur_icount = icount_get_locked();
delta = cur_icount - cur_time;
/* FIXME: This is a very crude algorithm, somewhat prone to oscillation. */
if (delta > 0
&& timers_state.last_delta + ICOUNT_WOBBLE < delta * 2
&& timers_state.icount_time_shift > 0) {
/* The guest is getting too far ahead. Slow time down. */
qatomic_set(&timers_state.icount_time_shift,
timers_state.icount_time_shift - 1);
}
if (delta < 0
&& timers_state.last_delta - ICOUNT_WOBBLE > delta * 2
&& timers_state.icount_time_shift < MAX_ICOUNT_SHIFT) {
/* The guest is getting too far behind. Speed time up. */
qatomic_set(&timers_state.icount_time_shift,
timers_state.icount_time_shift + 1);
}
timers_state.last_delta = delta;
qatomic_set(&timers_state.qemu_icount_bias,
cur_icount - (timers_state.qemu_icount
<< timers_state.icount_time_shift));
seqlock_write_unlock(&timers_state.vm_clock_seqlock,
&timers_state.vm_clock_lock);
}
static void icount_adjust_rt(void *opaque)
{
timer_mod(timers_state.icount_rt_timer,
qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL_RT) + 1000);
icount_adjust();
}
static void icount_adjust_vm(void *opaque)
{
timer_mod(timers_state.icount_vm_timer,
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
NANOSECONDS_PER_SECOND / 10);
icount_adjust();
}
int64_t icount_round(int64_t count)
{
int shift = qatomic_read(&timers_state.icount_time_shift);
return (count + (1 << shift) - 1) >> shift;
}
static void icount_warp_rt(void)
{
unsigned seq;
int64_t warp_start;
/*
* The icount_warp_timer is rescheduled soon after vm_clock_warp_start
* changes from -1 to another value, so the race here is okay.
*/
do {
seq = seqlock_read_begin(&timers_state.vm_clock_seqlock);
warp_start = timers_state.vm_clock_warp_start;
} while (seqlock_read_retry(&timers_state.vm_clock_seqlock, seq));
if (warp_start == -1) {
return;
}
seqlock_write_lock(&timers_state.vm_clock_seqlock,
&timers_state.vm_clock_lock);
if (runstate_is_running()) {
int64_t clock = REPLAY_CLOCK_LOCKED(REPLAY_CLOCK_VIRTUAL_RT,
cpu_get_clock_locked());
int64_t warp_delta;
warp_delta = clock - timers_state.vm_clock_warp_start;
if (icount_enabled() == ICOUNT_ADAPTATIVE) {
/*
* In adaptive mode, do not let QEMU_CLOCK_VIRTUAL run too far
* ahead of real time (it might already be ahead so careful not
* to go backwards).
*/
int64_t cur_icount = icount_get_locked();
int64_t delta = clock - cur_icount;
if (delta < 0) {
delta = 0;
}
warp_delta = MIN(warp_delta, delta);
}
qatomic_set(&timers_state.qemu_icount_bias,
timers_state.qemu_icount_bias + warp_delta);
}
timers_state.vm_clock_warp_start = -1;
seqlock_write_unlock(&timers_state.vm_clock_seqlock,
&timers_state.vm_clock_lock);
if (qemu_clock_expired(QEMU_CLOCK_VIRTUAL)) {
qemu_clock_notify(QEMU_CLOCK_VIRTUAL);
}
}
static void icount_timer_cb(void *opaque)
{
/*
* No need for a checkpoint because the timer already synchronizes
* with CHECKPOINT_CLOCK_VIRTUAL_RT.
*/
icount_warp_rt();
}
void icount_start_warp_timer(void)
{
int64_t clock;
int64_t deadline;
assert(icount_enabled());
/*
* Nothing to do if the VM is stopped: QEMU_CLOCK_VIRTUAL timers
* do not fire, so computing the deadline does not make sense.
*/
if (!runstate_is_running()) {
return;
}
if (replay_mode != REPLAY_MODE_PLAY) {
if (!all_cpu_threads_idle()) {
return;
}
if (qtest_enabled()) {
/* When testing, qtest commands advance icount. */
return;
}
replay_checkpoint(CHECKPOINT_CLOCK_WARP_START);
} else {
/* warp clock deterministically in record/replay mode */
if (!replay_checkpoint(CHECKPOINT_CLOCK_WARP_START)) {
/*
* vCPU is sleeping and warp can't be started.
* It is probably a race condition: notification sent
* to vCPU was processed in advance and vCPU went to sleep.
* Therefore we have to wake it up for doing something.
*/
if (replay_has_event()) {
qemu_clock_notify(QEMU_CLOCK_VIRTUAL);
}
return;
}
}
/* We want to use the earliest deadline from ALL vm_clocks */
clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL_RT);
deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL,
~QEMU_TIMER_ATTR_EXTERNAL);
if (deadline < 0) {
if (!icount_sleep) {
warn_report_once("icount sleep disabled and no active timers");
}
return;
}
if (deadline > 0) {
/*
* Ensure QEMU_CLOCK_VIRTUAL proceeds even when the virtual CPU goes to
* sleep. Otherwise, the CPU might be waiting for a future timer
* interrupt to wake it up, but the interrupt never comes because
* the vCPU isn't running any insns and thus doesn't advance the
* QEMU_CLOCK_VIRTUAL.
*/
if (!icount_sleep) {
/*
* We never let VCPUs sleep in no sleep icount mode.
* If there is a pending QEMU_CLOCK_VIRTUAL timer we just advance
* to the next QEMU_CLOCK_VIRTUAL event and notify it.
* It is useful when we want a deterministic execution time,
* isolated from host latencies.
*/
seqlock_write_lock(&timers_state.vm_clock_seqlock,
&timers_state.vm_clock_lock);
qatomic_set(&timers_state.qemu_icount_bias,
timers_state.qemu_icount_bias + deadline);
seqlock_write_unlock(&timers_state.vm_clock_seqlock,
&timers_state.vm_clock_lock);
qemu_clock_notify(QEMU_CLOCK_VIRTUAL);
} else {
/*
* We do stop VCPUs and only advance QEMU_CLOCK_VIRTUAL after some
* "real" time, (related to the time left until the next event) has
* passed. The QEMU_CLOCK_VIRTUAL_RT clock will do this.
* This avoids that the warps are visible externally; for example,
* you will not be sending network packets continuously instead of
* every 100ms.
*/
seqlock_write_lock(&timers_state.vm_clock_seqlock,
&timers_state.vm_clock_lock);
if (timers_state.vm_clock_warp_start == -1
|| timers_state.vm_clock_warp_start > clock) {
timers_state.vm_clock_warp_start = clock;
}
seqlock_write_unlock(&timers_state.vm_clock_seqlock,
&timers_state.vm_clock_lock);
timer_mod_anticipate(timers_state.icount_warp_timer,
clock + deadline);
}
} else if (deadline == 0) {
qemu_clock_notify(QEMU_CLOCK_VIRTUAL);
}
}
void icount_account_warp_timer(void)
{
if (!icount_sleep) {
return;
}
/*
* Nothing to do if the VM is stopped: QEMU_CLOCK_VIRTUAL timers
* do not fire, so computing the deadline does not make sense.
*/
if (!runstate_is_running()) {
return;
}
replay_async_events();
/* warp clock deterministically in record/replay mode */
if (!replay_checkpoint(CHECKPOINT_CLOCK_WARP_ACCOUNT)) {
return;
}
timer_del(timers_state.icount_warp_timer);
icount_warp_rt();
}
bool icount_configure(QemuOpts *opts, Error **errp)
{
const char *option = qemu_opt_get(opts, "shift");
bool sleep = qemu_opt_get_bool(opts, "sleep", true);
bool align = qemu_opt_get_bool(opts, "align", false);
long time_shift = -1;
if (!option) {
if (qemu_opt_get(opts, "align") != NULL) {
error_setg(errp, "Please specify shift option when using align");
return false;
}
return true;
}
if (align && !sleep) {
error_setg(errp, "align=on and sleep=off are incompatible");
return false;
}
if (strcmp(option, "auto") != 0) {
if (qemu_strtol(option, NULL, 0, &time_shift) < 0
|| time_shift < 0 || time_shift > MAX_ICOUNT_SHIFT) {
error_setg(errp, "icount: Invalid shift value");
return false;
}
} else if (icount_align_option) {
error_setg(errp, "shift=auto and align=on are incompatible");
return false;
} else if (!icount_sleep) {
error_setg(errp, "shift=auto and sleep=off are incompatible");
return false;
}
icount_sleep = sleep;
if (icount_sleep) {
timers_state.icount_warp_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL_RT,
icount_timer_cb, NULL);
}
icount_align_option = align;
if (time_shift >= 0) {
timers_state.icount_time_shift = time_shift;
icount_enable_precise();
return true;
}
icount_enable_adaptive();
/*
* 125MIPS seems a reasonable initial guess at the guest speed.
* It will be corrected fairly quickly anyway.
*/
timers_state.icount_time_shift = 3;
/*
* Have both realtime and virtual time triggers for speed adjustment.
* The realtime trigger catches emulated time passing too slowly,
* the virtual time trigger catches emulated time passing too fast.
* Realtime triggers occur even when idle, so use them less frequently
* than VM triggers.
*/
timers_state.vm_clock_warp_start = -1;
timers_state.icount_rt_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL_RT,
icount_adjust_rt, NULL);
timer_mod(timers_state.icount_rt_timer,
qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL_RT) + 1000);
timers_state.icount_vm_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
icount_adjust_vm, NULL);
timer_mod(timers_state.icount_vm_timer,
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
NANOSECONDS_PER_SECOND / 10);
return true;
}
void icount_notify_exit(void)
{
assert(icount_enabled());
if (current_cpu) {
qemu_cpu_kick(current_cpu);
qemu_clock_notify(QEMU_CLOCK_VIRTUAL);
}
}
+145
View File
@@ -0,0 +1,145 @@
/*
* Internal execution defines for qemu (target agnostic)
*
* Copyright (c) 2003 Fabrice Bellard
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef ACCEL_TCG_INTERNAL_COMMON_H
#define ACCEL_TCG_INTERNAL_COMMON_H
#include "exec/cpu-common.h"
#include "exec/translation-block.h"
#include "exec/mmap-lock.h"
#include "accel/tcg/tb-cpu-state.h"
extern int64_t max_delay;
extern int64_t max_advance;
extern bool one_insn_per_tb;
extern bool icount_align_option;
/*
* Return true if CS is not running in parallel with other cpus, either
* because there are no other cpus or we are within an exclusive context.
*/
static inline bool cpu_in_serial_context(CPUState *cs)
{
return !tcg_cflags_has(cs, CF_PARALLEL) || cpu_in_exclusive_context(cs);
}
/**
* cpu_plugin_mem_cbs_enabled() - are plugin memory callbacks enabled?
* @cs: CPUState pointer
*
* The memory callbacks are installed if a plugin has instrumented an
* instruction for memory. This can be useful to know if you want to
* force a slow path for a series of memory accesses.
*/
static inline bool cpu_plugin_mem_cbs_enabled(const CPUState *cpu)
{
#ifdef CONFIG_PLUGIN
return !!cpu->neg.plugin_mem_cbs;
#else
return false;
#endif
}
TranslationBlock *tb_gen_code(CPUState *cpu, TCGTBCPUState s);
void page_init(void);
void tb_htable_init(void);
void tb_reset_jump(TranslationBlock *tb, int n);
TranslationBlock *tb_link_page(TranslationBlock *tb);
void cpu_restore_state_from_tb(CPUState *cpu, TranslationBlock *tb,
uintptr_t host_pc);
/**
* tlb_init - initialize a CPU's TLB
* @cpu: CPU whose TLB should be initialized
*/
void tlb_init(CPUState *cpu);
/**
* tlb_destroy - destroy a CPU's TLB
* @cpu: CPU whose TLB should be destroyed
*/
void tlb_destroy(CPUState *cpu);
bool tcg_exec_realizefn(CPUState *cpu, Error **errp);
void tcg_exec_unrealizefn(CPUState *cpu);
/* current cflags for hashing/comparison */
uint32_t curr_cflags(CPUState *cpu);
void tb_check_watchpoint(CPUState *cpu, uintptr_t retaddr);
/**
* get_page_addr_code_hostp()
* @env: CPUArchState
* @addr: guest virtual address of guest code
*
* See get_page_addr_code() (full-system version) for documentation on the
* return value.
*
* Sets *@hostp as follows.
* If the return value is -1, sets *@hostp to NULL. Otherwise, sets *@hostp
* to the host address where @addr's content is kept.
*
* Note: this function can trigger an exception.
*/
tb_page_addr_t get_page_addr_code_hostp(CPUArchState *env, vaddr addr,
void **hostp);
/**
* get_page_addr_code()
* @env: CPUArchState
* @addr: guest virtual address of guest code
*
* If we cannot translate and execute from the entire RAM page, or if
* the region is not backed by RAM, returns -1. Otherwise, returns the
* ram_addr_t corresponding to the guest code at @addr.
*
* Note: this function can trigger an exception.
*/
static inline tb_page_addr_t get_page_addr_code(CPUArchState *env,
vaddr addr)
{
void *discard;
return get_page_addr_code_hostp(env, addr, &discard);
}
/*
* Access to the various translations structures need to be serialised
* via locks for consistency. In user-mode emulation access to the
* memory related structures are protected with mmap_lock.
* In !user-mode we use per-page locks.
*/
#ifdef CONFIG_USER_ONLY
#define assert_memory_lock() tcg_debug_assert(have_mmap_lock())
#else
#define assert_memory_lock()
#endif
#if defined(CONFIG_SOFTMMU) && defined(CONFIG_DEBUG_TCG)
void assert_no_pages_locked(void);
#else
static inline void assert_no_pages_locked(void) { }
#endif
#ifdef CONFIG_USER_ONLY
static inline void page_table_config_init(void) { }
#else
void page_table_config_init(void);
#endif
#ifndef CONFIG_USER_ONLY
G_NORETURN void cpu_io_recompile(CPUState *cpu, uintptr_t retaddr);
#endif /* CONFIG_USER_ONLY */
void tb_phys_invalidate(TranslationBlock *tb, tb_page_addr_t page_addr);
void tb_set_jmp_target(TranslationBlock *tb, int n, uintptr_t addr);
void tcg_get_stats(AccelState *accel, GString *buf);
#endif
File diff suppressed because it is too large Load Diff
+245
View File
@@ -0,0 +1,245 @@
/*
* Routines common to user and system emulation of load/store.
*
* Copyright (c) 2003 Fabrice Bellard
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
/*
* Load helpers for tcg-ldst.h
*/
tcg_target_ulong helper_ldub_mmu(CPUArchState *env, uint64_t addr,
MemOpIdx oi, uintptr_t retaddr)
{
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_8);
return do_ld1_mmu(env_cpu(env), addr, oi, retaddr, MMU_DATA_LOAD);
}
tcg_target_ulong helper_lduw_mmu(CPUArchState *env, uint64_t addr,
MemOpIdx oi, uintptr_t retaddr)
{
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_16);
return do_ld2_mmu(env_cpu(env), addr, oi, retaddr, MMU_DATA_LOAD);
}
tcg_target_ulong helper_ldul_mmu(CPUArchState *env, uint64_t addr,
MemOpIdx oi, uintptr_t retaddr)
{
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_32);
return do_ld4_mmu(env_cpu(env), addr, oi, retaddr, MMU_DATA_LOAD);
}
uint64_t helper_ldq_mmu(CPUArchState *env, uint64_t addr,
MemOpIdx oi, uintptr_t retaddr)
{
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_64);
return do_ld8_mmu(env_cpu(env), addr, oi, retaddr, MMU_DATA_LOAD);
}
/*
* Provide signed versions of the load routines as well. We can of course
* avoid this for 64-bit data, or for 32-bit data on 32-bit host.
*/
tcg_target_ulong helper_ldsb_mmu(CPUArchState *env, uint64_t addr,
MemOpIdx oi, uintptr_t retaddr)
{
return (int8_t)helper_ldub_mmu(env, addr, oi, retaddr);
}
tcg_target_ulong helper_ldsw_mmu(CPUArchState *env, uint64_t addr,
MemOpIdx oi, uintptr_t retaddr)
{
return (int16_t)helper_lduw_mmu(env, addr, oi, retaddr);
}
tcg_target_ulong helper_ldsl_mmu(CPUArchState *env, uint64_t addr,
MemOpIdx oi, uintptr_t retaddr)
{
return (int32_t)helper_ldul_mmu(env, addr, oi, retaddr);
}
Int128 helper_ld16_mmu(CPUArchState *env, uint64_t addr,
MemOpIdx oi, uintptr_t retaddr)
{
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_128);
return do_ld16_mmu(env_cpu(env), addr, oi, retaddr);
}
Int128 helper_ld_i128(CPUArchState *env, uint64_t addr, uint32_t oi)
{
return helper_ld16_mmu(env, addr, oi, GETPC());
}
/*
* Store helpers for tcg-ldst.h
*/
void helper_stb_mmu(CPUArchState *env, uint64_t addr, uint32_t val,
MemOpIdx oi, uintptr_t ra)
{
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_8);
do_st1_mmu(env_cpu(env), addr, val, oi, ra);
}
void helper_stw_mmu(CPUArchState *env, uint64_t addr, uint32_t val,
MemOpIdx oi, uintptr_t retaddr)
{
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_16);
do_st2_mmu(env_cpu(env), addr, val, oi, retaddr);
}
void helper_stl_mmu(CPUArchState *env, uint64_t addr, uint32_t val,
MemOpIdx oi, uintptr_t retaddr)
{
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_32);
do_st4_mmu(env_cpu(env), addr, val, oi, retaddr);
}
void helper_stq_mmu(CPUArchState *env, uint64_t addr, uint64_t val,
MemOpIdx oi, uintptr_t retaddr)
{
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_64);
do_st8_mmu(env_cpu(env), addr, val, oi, retaddr);
}
void helper_st16_mmu(CPUArchState *env, uint64_t addr, Int128 val,
MemOpIdx oi, uintptr_t retaddr)
{
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_128);
do_st16_mmu(env_cpu(env), addr, val, oi, retaddr);
}
void helper_st_i128(CPUArchState *env, uint64_t addr, Int128 val, MemOpIdx oi)
{
helper_st16_mmu(env, addr, val, oi, GETPC());
}
/*
* Load helpers for cpu_ldst.h
*/
static void plugin_load_cb(CPUArchState *env, vaddr addr,
uint64_t value_low,
uint64_t value_high,
MemOpIdx oi)
{
if (cpu_plugin_mem_cbs_enabled(env_cpu(env))) {
qemu_plugin_vcpu_mem_cb(env_cpu(env), addr,
value_low, value_high,
oi, QEMU_PLUGIN_MEM_R);
}
}
uint8_t cpu_ldb_mmu(CPUArchState *env, vaddr addr, MemOpIdx oi, uintptr_t ra)
{
uint8_t ret;
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_UB);
ret = do_ld1_mmu(env_cpu(env), addr, oi, ra, MMU_DATA_LOAD);
plugin_load_cb(env, addr, ret, 0, oi);
return ret;
}
uint16_t cpu_ldw_mmu(CPUArchState *env, vaddr addr,
MemOpIdx oi, uintptr_t ra)
{
uint16_t ret;
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_16);
ret = do_ld2_mmu(env_cpu(env), addr, oi, ra, MMU_DATA_LOAD);
plugin_load_cb(env, addr, ret, 0, oi);
return ret;
}
uint32_t cpu_ldl_mmu(CPUArchState *env, vaddr addr,
MemOpIdx oi, uintptr_t ra)
{
uint32_t ret;
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_32);
ret = do_ld4_mmu(env_cpu(env), addr, oi, ra, MMU_DATA_LOAD);
plugin_load_cb(env, addr, ret, 0, oi);
return ret;
}
uint64_t cpu_ldq_mmu(CPUArchState *env, vaddr addr,
MemOpIdx oi, uintptr_t ra)
{
uint64_t ret;
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_64);
ret = do_ld8_mmu(env_cpu(env), addr, oi, ra, MMU_DATA_LOAD);
plugin_load_cb(env, addr, ret, 0, oi);
return ret;
}
Int128 cpu_ld16_mmu(CPUArchState *env, vaddr addr,
MemOpIdx oi, uintptr_t ra)
{
Int128 ret;
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_128);
ret = do_ld16_mmu(env_cpu(env), addr, oi, ra);
plugin_load_cb(env, addr, int128_getlo(ret), int128_gethi(ret), oi);
return ret;
}
/*
* Store helpers for cpu_ldst.h
*/
static void plugin_store_cb(CPUArchState *env, vaddr addr,
uint64_t value_low,
uint64_t value_high,
MemOpIdx oi)
{
if (cpu_plugin_mem_cbs_enabled(env_cpu(env))) {
qemu_plugin_vcpu_mem_cb(env_cpu(env), addr,
value_low, value_high,
oi, QEMU_PLUGIN_MEM_W);
}
}
void cpu_stb_mmu(CPUArchState *env, vaddr addr, uint8_t val,
MemOpIdx oi, uintptr_t retaddr)
{
helper_stb_mmu(env, addr, val, oi, retaddr);
plugin_store_cb(env, addr, val, 0, oi);
}
void cpu_stw_mmu(CPUArchState *env, vaddr addr, uint16_t val,
MemOpIdx oi, uintptr_t retaddr)
{
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_16);
do_st2_mmu(env_cpu(env), addr, val, oi, retaddr);
plugin_store_cb(env, addr, val, 0, oi);
}
void cpu_stl_mmu(CPUArchState *env, vaddr addr, uint32_t val,
MemOpIdx oi, uintptr_t retaddr)
{
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_32);
do_st4_mmu(env_cpu(env), addr, val, oi, retaddr);
plugin_store_cb(env, addr, val, 0, oi);
}
void cpu_stq_mmu(CPUArchState *env, vaddr addr, uint64_t val,
MemOpIdx oi, uintptr_t retaddr)
{
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_64);
do_st8_mmu(env_cpu(env), addr, val, oi, retaddr);
plugin_store_cb(env, addr, val, 0, oi);
}
void cpu_st16_mmu(CPUArchState *env, vaddr addr, Int128 val,
MemOpIdx oi, uintptr_t retaddr)
{
tcg_debug_assert((get_memop(oi) & MO_SIZE) == MO_128);
do_st16_mmu(env_cpu(env), addr, val, oi, retaddr);
plugin_store_cb(env, addr, int128_getlo(val), int128_gethi(val), oi);
}
+39
View File
@@ -0,0 +1,39 @@
if not have_tcg
subdir_done()
endif
tcg_ss = ss.source_set()
tcg_ss.add(files(
'cpu-exec.c',
'cpu-exec-common.c',
'tcg-runtime.c',
'tcg-runtime-gvec.c',
'tb-maint.c',
'tcg-all.c',
'tcg-stats.c',
'translate-all.c',
'translator.c',
))
if get_option('plugins')
tcg_ss.add(files('plugin-gen.c'))
endif
user_ss.add_all(tcg_ss)
system_ss.add_all(tcg_ss)
user_ss.add(files(
'user-exec.c',
'user-exec-stub.c',
))
system_ss.add(files(
'cputlb.c',
'icount-common.c',
'monitor.c',
'tcg-accel-ops.c',
'tcg-accel-ops-icount.c',
'tcg-accel-ops-mttcg.c',
'tcg-accel-ops-rr.c',
'watchpoint.c',
))
+37
View File
@@ -0,0 +1,37 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* QEMU TCG monitor
*
* Copyright (c) 2003-2005 Fabrice Bellard
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qapi/type-helpers.h"
#include "qapi/qapi-commands-machine.h"
#include "monitor/monitor.h"
#include "system/tcg.h"
#include "tcg/tcg.h"
#include "internal-common.h"
HumanReadableText *qmp_x_query_jit(Error **errp)
{
g_autoptr(GString) buf = g_string_new("");
if (!tcg_enabled()) {
error_setg(errp, "JIT information is only available with accel=tcg");
return NULL;
}
tcg_dump_stats(buf);
return human_readable_text_from_str(buf);
}
static void hmp_tcg_register(void)
{
monitor_register_hmp_info_hrt("jit", qmp_x_query_jit);
}
type_init(hmp_tcg_register);
+510
View File
@@ -0,0 +1,510 @@
/*
* plugin-gen.c - TCG-related bits of plugin infrastructure
*
* Copyright (C) 2018, Emilio G. Cota <[email protected]>
* License: GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*
* We support instrumentation at an instruction granularity. That is,
* if a plugin wants to instrument the memory accesses performed by a
* particular instruction, it can just do that instead of instrumenting
* all memory accesses. Thus, in order to do this we first have to
* translate a TB, so that plugins can decide what/where to instrument.
*
* Injecting the desired instrumentation could be done with a second
* translation pass that combined the instrumentation requests, but that
* would be ugly and inefficient since we would decode the guest code twice.
* Instead, during TB translation we add "plugin_cb" marker opcodes
* for all possible instrumentation events, and then once we collect the
* instrumentation requests from plugins, we generate code for those markers
* or remove them if they have no requests.
*/
#include "qemu/osdep.h"
#include "qemu/plugin.h"
#include "qemu/log.h"
#include "tcg/tcg.h"
#include "tcg/tcg-temp-internal.h"
#include "tcg/tcg-op-common.h"
#include "exec/plugin-gen.h"
#include "exec/translator.h"
#include "exec/translation-block.h"
enum plugin_gen_from {
PLUGIN_GEN_FROM_TB,
PLUGIN_GEN_FROM_INSN,
PLUGIN_GEN_AFTER_INSN,
PLUGIN_GEN_AFTER_TB,
};
/* called before finishing a TB with exit_tb, goto_tb or goto_ptr */
void plugin_gen_disable_mem_helpers(void)
{
if (tcg_ctx->plugin_insn) {
tcg_gen_plugin_cb(PLUGIN_GEN_AFTER_TB);
}
}
static void gen_enable_mem_helper(struct qemu_plugin_tb *ptb,
struct qemu_plugin_insn *insn)
{
GArray *arr;
size_t len;
/*
* Tracking memory accesses performed from helpers requires extra work.
* If an instruction is emulated with helpers, we do two things:
* (1) copy the CB descriptors, and keep track of it so that they can be
* freed later on, and (2) point CPUState.neg.plugin_mem_cbs to the
* descriptors, so that we can read them at run-time
* (i.e. when the helper executes).
* This run-time access is performed from qemu_plugin_vcpu_mem_cb.
*
* Note that plugin_gen_disable_mem_helpers undoes (2). Since it
* is possible that the code we generate after the instruction is
* dead, we also add checks before generating tb_exit etc.
*/
if (!insn->calls_helpers) {
return;
}
if (!insn->mem_cbs || !insn->mem_cbs->len) {
insn->mem_helper = false;
return;
}
insn->mem_helper = true;
ptb->mem_helper = true;
/*
* TODO: It seems like we should be able to use ref/unref
* to avoid needing to actually copy this array.
* Alternately, perhaps we could allocate new memory adjacent
* to the TranslationBlock itself, so that we do not have to
* actively manage the lifetime after this.
*/
len = insn->mem_cbs->len;
arr = g_array_sized_new(false, false,
sizeof(struct qemu_plugin_dyn_cb), len);
g_array_append_vals(arr, insn->mem_cbs->data, len);
qemu_plugin_add_dyn_cb_arr(arr);
tcg_gen_st_ptr(tcg_constant_ptr((intptr_t)arr), tcg_env,
offsetof(CPUState, neg.plugin_mem_cbs) - sizeof(CPUState));
}
static void gen_disable_mem_helper(void)
{
tcg_gen_st_ptr(tcg_constant_ptr(0), tcg_env,
offsetof(CPUState, neg.plugin_mem_cbs) - sizeof(CPUState));
}
static TCGv_i32 gen_cpu_index(void)
{
/*
* Optimize when we run with a single vcpu. All values using cpu_index,
* including scoreboard index, will be optimized out.
* User-mode flushes all TBs when setting this flag.
* In system-mode, all vcpus are created before generating code.
*/
if (!tcg_cflags_has(current_cpu, CF_PARALLEL)) {
return tcg_constant_i32(current_cpu->cpu_index);
}
TCGv_i32 cpu_index = tcg_temp_ebb_new_i32();
tcg_gen_ld_i32(cpu_index, tcg_env,
offsetof(CPUState, cpu_index) - sizeof(CPUState));
return cpu_index;
}
static void gen_udata_cb(struct qemu_plugin_regular_cb *cb)
{
TCGv_i32 cpu_index = gen_cpu_index();
enum qemu_plugin_cb_flags cb_flags =
tcg_call_to_qemu_plugin_cb_flags(cb->info->flags);
TCGv_i32 flags = tcg_constant_i32(cb_flags);
TCGv_i32 clear_flags = tcg_constant_i32(QEMU_PLUGIN_CB_NO_REGS);
tcg_gen_st_i32(flags, tcg_env,
offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL,
tcgv_i32_temp(cpu_index),
tcgv_ptr_temp(tcg_constant_ptr(cb->userp)));
tcg_gen_st_i32(clear_flags, tcg_env,
offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
tcg_temp_free_i32(cpu_index);
tcg_temp_free_i32(flags);
tcg_temp_free_i32(clear_flags);
}
static TCGv_ptr gen_plugin_u64_ptr(qemu_plugin_u64 entry)
{
TCGv_ptr ptr = tcg_temp_ebb_new_ptr();
GArray *arr = entry.score->data;
char *base_ptr = arr->data + entry.offset;
size_t entry_size = g_array_get_element_size(arr);
TCGv_i32 cpu_index = gen_cpu_index();
tcg_gen_muli_i32(cpu_index, cpu_index, entry_size);
tcg_gen_ext_i32_ptr(ptr, cpu_index);
tcg_temp_free_i32(cpu_index);
tcg_gen_addi_ptr(ptr, ptr, (intptr_t) base_ptr);
return ptr;
}
static TCGCond plugin_cond_to_tcgcond(enum qemu_plugin_cond cond)
{
switch (cond) {
case QEMU_PLUGIN_COND_EQ:
return TCG_COND_EQ;
case QEMU_PLUGIN_COND_NE:
return TCG_COND_NE;
case QEMU_PLUGIN_COND_LT:
return TCG_COND_LTU;
case QEMU_PLUGIN_COND_LE:
return TCG_COND_LEU;
case QEMU_PLUGIN_COND_GT:
return TCG_COND_GTU;
case QEMU_PLUGIN_COND_GE:
return TCG_COND_GEU;
default:
/* ALWAYS and NEVER conditions should never reach */
g_assert_not_reached();
}
}
static void gen_udata_cond_cb(struct qemu_plugin_conditional_cb *cb)
{
TCGv_ptr ptr = gen_plugin_u64_ptr(cb->entry);
TCGv_i64 val = tcg_temp_ebb_new_i64();
TCGLabel *after_cb = gen_new_label();
/* Condition should be negated, as calling the cb is the "else" path */
TCGCond cond = tcg_invert_cond(plugin_cond_to_tcgcond(cb->cond));
tcg_gen_ld_i64(val, ptr, 0);
tcg_gen_brcondi_i64(cond, val, cb->imm, after_cb);
TCGv_i32 cpu_index = gen_cpu_index();
enum qemu_plugin_cb_flags cb_flags =
tcg_call_to_qemu_plugin_cb_flags(cb->info->flags);
TCGv_i32 flags = tcg_constant_i32(cb_flags);
TCGv_i32 clear_flags = tcg_constant_i32(QEMU_PLUGIN_CB_NO_REGS);
tcg_gen_st_i32(flags, tcg_env,
offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL,
tcgv_i32_temp(cpu_index),
tcgv_ptr_temp(tcg_constant_ptr(cb->userp)));
tcg_gen_st_i32(clear_flags, tcg_env,
offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
tcg_temp_free_i32(cpu_index);
tcg_temp_free_i32(flags);
tcg_temp_free_i32(clear_flags);
gen_set_label(after_cb);
tcg_temp_free_i64(val);
tcg_temp_free_ptr(ptr);
}
static void gen_inline_add_u64_cb(struct qemu_plugin_inline_cb *cb)
{
TCGv_ptr ptr = gen_plugin_u64_ptr(cb->entry);
TCGv_i64 val = tcg_temp_ebb_new_i64();
tcg_gen_ld_i64(val, ptr, 0);
tcg_gen_addi_i64(val, val, cb->imm);
tcg_gen_st_i64(val, ptr, 0);
tcg_temp_free_i64(val);
tcg_temp_free_ptr(ptr);
}
static void gen_inline_store_u64_cb(struct qemu_plugin_inline_cb *cb)
{
TCGv_ptr ptr = gen_plugin_u64_ptr(cb->entry);
TCGv_i64 val = tcg_constant_i64(cb->imm);
tcg_gen_st_i64(val, ptr, 0);
tcg_temp_free_ptr(ptr);
}
static void gen_mem_cb(struct qemu_plugin_regular_cb *cb,
qemu_plugin_meminfo_t meminfo, TCGv_i64 addr)
{
TCGv_i32 cpu_index = gen_cpu_index();
enum qemu_plugin_cb_flags cb_flags =
tcg_call_to_qemu_plugin_cb_flags(cb->info->flags);
TCGv_i32 flags = tcg_constant_i32(cb_flags);
TCGv_i32 clear_flags = tcg_constant_i32(QEMU_PLUGIN_CB_NO_REGS);
tcg_gen_st_i32(flags, tcg_env,
offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
tcg_gen_call4(cb->f.vcpu_mem, cb->info, NULL,
tcgv_i32_temp(cpu_index),
tcgv_i32_temp(tcg_constant_i32(meminfo)),
tcgv_i64_temp(addr),
tcgv_ptr_temp(tcg_constant_ptr(cb->userp)));
tcg_gen_st_i32(clear_flags, tcg_env,
offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
tcg_temp_free_i32(cpu_index);
tcg_temp_free_i32(flags);
tcg_temp_free_i32(clear_flags);
}
static void inject_cb(struct qemu_plugin_dyn_cb *cb)
{
switch (cb->type) {
case PLUGIN_CB_REGULAR:
gen_udata_cb(&cb->regular);
break;
case PLUGIN_CB_COND:
gen_udata_cond_cb(&cb->cond);
break;
case PLUGIN_CB_INLINE_ADD_U64:
gen_inline_add_u64_cb(&cb->inline_insn);
break;
case PLUGIN_CB_INLINE_STORE_U64:
gen_inline_store_u64_cb(&cb->inline_insn);
break;
default:
g_assert_not_reached();
}
}
static void inject_mem_cb(struct qemu_plugin_dyn_cb *cb,
enum qemu_plugin_mem_rw rw,
qemu_plugin_meminfo_t meminfo, TCGv_i64 addr)
{
switch (cb->type) {
case PLUGIN_CB_MEM_REGULAR:
if (rw & cb->regular.rw) {
gen_mem_cb(&cb->regular, meminfo, addr);
}
break;
case PLUGIN_CB_INLINE_ADD_U64:
case PLUGIN_CB_INLINE_STORE_U64:
if (rw & cb->inline_insn.rw) {
inject_cb(cb);
}
break;
default:
g_assert_not_reached();
}
}
static void plugin_gen_inject(struct qemu_plugin_tb *plugin_tb)
{
TCGOp *op, *next;
int insn_idx = -1;
if (unlikely(qemu_loglevel_mask(LOG_TB_OP_PLUGIN)
&& qemu_log_in_addr_range(tcg_ctx->plugin_db->pc_first))) {
FILE *logfile = qemu_log_trylock();
if (logfile) {
fprintf(logfile, "OP before plugin injection:\n");
tcg_dump_ops(tcg_ctx, logfile, false);
fprintf(logfile, "\n");
qemu_log_unlock(logfile);
}
}
/*
* While injecting code, we cannot afford to reuse any ebb temps
* that might be live within the existing opcode stream.
* The simplest solution is to release them all and create new.
*/
tcg_temp_ebb_reset_freed(tcg_ctx);
QTAILQ_FOREACH_SAFE(op, &tcg_ctx->ops, link, next) {
switch (op->opc) {
case INDEX_op_insn_start:
insn_idx++;
break;
case INDEX_op_plugin_cb:
{
enum plugin_gen_from from = op->args[0];
struct qemu_plugin_insn *insn = NULL;
const GArray *cbs;
int i, n;
if (insn_idx >= 0) {
insn = g_ptr_array_index(plugin_tb->insns, insn_idx);
}
tcg_ctx->emit_before_op = op;
switch (from) {
case PLUGIN_GEN_AFTER_TB:
if (plugin_tb->mem_helper) {
gen_disable_mem_helper();
}
break;
case PLUGIN_GEN_AFTER_INSN:
assert(insn != NULL);
if (insn->mem_helper) {
gen_disable_mem_helper();
}
break;
case PLUGIN_GEN_FROM_TB:
assert(insn == NULL);
cbs = plugin_tb->cbs;
for (i = 0, n = (cbs ? cbs->len : 0); i < n; i++) {
inject_cb(
&g_array_index(cbs, struct qemu_plugin_dyn_cb, i));
}
break;
case PLUGIN_GEN_FROM_INSN:
assert(insn != NULL);
gen_enable_mem_helper(plugin_tb, insn);
cbs = insn->insn_cbs;
for (i = 0, n = (cbs ? cbs->len : 0); i < n; i++) {
inject_cb(
&g_array_index(cbs, struct qemu_plugin_dyn_cb, i));
}
break;
default:
g_assert_not_reached();
}
tcg_ctx->emit_before_op = NULL;
tcg_op_remove(tcg_ctx, op);
break;
}
case INDEX_op_plugin_mem_cb:
{
TCGv_i64 addr = temp_tcgv_i64(arg_temp(op->args[0]));
qemu_plugin_meminfo_t meminfo = op->args[1];
enum qemu_plugin_mem_rw rw =
(qemu_plugin_mem_is_store(meminfo)
? QEMU_PLUGIN_MEM_W : QEMU_PLUGIN_MEM_R);
struct qemu_plugin_insn *insn;
const GArray *cbs;
int i, n;
assert(insn_idx >= 0);
insn = g_ptr_array_index(plugin_tb->insns, insn_idx);
tcg_ctx->emit_before_op = op;
cbs = insn->mem_cbs;
for (i = 0, n = (cbs ? cbs->len : 0); i < n; i++) {
inject_mem_cb(&g_array_index(cbs, struct qemu_plugin_dyn_cb, i),
rw, meminfo, addr);
}
tcg_ctx->emit_before_op = NULL;
tcg_op_remove(tcg_ctx, op);
break;
}
default:
/* plugins don't care about any other ops */
break;
}
}
}
bool plugin_gen_tb_start(CPUState *cpu, const DisasContextBase *db)
{
struct qemu_plugin_tb *ptb;
if (!test_bit(QEMU_PLUGIN_EV_VCPU_TB_TRANS,
cpu->plugin_state->event_mask)) {
return false;
}
tcg_ctx->plugin_db = db;
tcg_ctx->plugin_insn = NULL;
ptb = tcg_ctx->plugin_tb;
if (ptb) {
/* Reset callbacks */
if (ptb->cbs) {
g_array_set_size(ptb->cbs, 0);
}
ptb->n = 0;
ptb->mem_helper = false;
} else {
ptb = g_new0(struct qemu_plugin_tb, 1);
tcg_ctx->plugin_tb = ptb;
ptb->insns = g_ptr_array_new();
}
tcg_gen_plugin_cb(PLUGIN_GEN_FROM_TB);
return true;
}
void plugin_gen_insn_start(CPUState *cpu, const DisasContextBase *db)
{
struct qemu_plugin_tb *ptb = tcg_ctx->plugin_tb;
struct qemu_plugin_insn *insn;
size_t n = db->num_insns;
vaddr pc;
assert(n >= 1);
ptb->n = n;
if (n <= ptb->insns->len) {
insn = g_ptr_array_index(ptb->insns, n - 1);
} else {
assert(n - 1 == ptb->insns->len);
insn = g_new0(struct qemu_plugin_insn, 1);
g_ptr_array_add(ptb->insns, insn);
}
tcg_ctx->plugin_insn = insn;
insn->calls_helpers = false;
insn->mem_helper = false;
if (insn->insn_cbs) {
g_array_set_size(insn->insn_cbs, 0);
}
if (insn->mem_cbs) {
g_array_set_size(insn->mem_cbs, 0);
}
pc = db->pc_next;
insn->vaddr = pc;
tcg_gen_plugin_cb(PLUGIN_GEN_FROM_INSN);
}
void plugin_gen_insn_end(void)
{
const DisasContextBase *db = tcg_ctx->plugin_db;
struct qemu_plugin_insn *pinsn = tcg_ctx->plugin_insn;
pinsn->len = db->fake_insn ? db->record_len : db->pc_next - pinsn->vaddr;
tcg_gen_plugin_cb(PLUGIN_GEN_AFTER_INSN);
}
/*
* There are cases where we never get to finalise a translation - for
* example a page fault during translation. As a result we shouldn't
* do any clean-up here and make sure things are reset in
* plugin_gen_tb_start.
*/
void plugin_gen_tb_end(CPUState *cpu, size_t num_insns)
{
struct qemu_plugin_tb *ptb = tcg_ctx->plugin_tb;
/* translator may have removed instructions, update final count */
g_assert(num_insns <= ptb->n);
ptb->n = num_insns;
/* collect instrumentation requests */
qemu_plugin_tb_trans_cb(cpu, ptb);
/* inject the instrumentation at the appropriate places */
plugin_gen_inject(ptb);
/* reset plugin translation state (plugin_tb is reused between blocks) */
tcg_ctx->plugin_db = NULL;
tcg_ctx->plugin_insn = NULL;
}
+17
View File
@@ -0,0 +1,17 @@
/*
* QEMU page protection (system emulation)
*
* Copyright (c) 2003 Fabrice Bellard
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef SYSTEM_PAGE_PROTECTION_H
#define SYSTEM_PAGE_PROTECTION_H
#include "system/ram_addr.h"
void tlb_protect_code(ram_addr_t ram_addr);
void tlb_unprotect_code(ram_addr_t ram_addr);
#endif
+42
View File
@@ -0,0 +1,42 @@
/*
* Internal structs that QEMU exports to TCG
*
* Copyright (c) 2003 Fabrice Bellard
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef QEMU_TB_CONTEXT_H
#define QEMU_TB_CONTEXT_H
#include "qemu/thread.h"
#include "qemu/qht.h"
#define CODE_GEN_HTABLE_BITS 15
#define CODE_GEN_HTABLE_SIZE (1 << CODE_GEN_HTABLE_BITS)
typedef struct TBContext TBContext;
struct TBContext {
struct qht htable;
/* statistics */
unsigned tb_flush_count;
unsigned tb_phys_invalidate_count;
};
extern TBContext tb_ctx;
#endif
+71
View File
@@ -0,0 +1,71 @@
/*
* internal execution defines for qemu
*
* Copyright (c) 2003 Fabrice Bellard
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EXEC_TB_HASH_H
#define EXEC_TB_HASH_H
#include "exec/vaddr.h"
#include "exec/target_page.h"
#include "exec/translation-block.h"
#include "qemu/xxhash.h"
#include "tb-jmp-cache.h"
#ifdef CONFIG_SOFTMMU
/* Only the bottom TB_JMP_PAGE_BITS of the jump cache hash bits vary for
addresses on the same page. The top bits are the same. This allows
TLB invalidation to quickly clear a subset of the hash table. */
#define TB_JMP_PAGE_BITS (TB_JMP_CACHE_BITS / 2)
#define TB_JMP_PAGE_SIZE (1 << TB_JMP_PAGE_BITS)
#define TB_JMP_ADDR_MASK (TB_JMP_PAGE_SIZE - 1)
#define TB_JMP_PAGE_MASK (TB_JMP_CACHE_SIZE - TB_JMP_PAGE_SIZE)
static inline unsigned int tb_jmp_cache_hash_page(vaddr pc)
{
vaddr tmp;
tmp = pc ^ (pc >> (TARGET_PAGE_BITS - TB_JMP_PAGE_BITS));
return (tmp >> (TARGET_PAGE_BITS - TB_JMP_PAGE_BITS)) & TB_JMP_PAGE_MASK;
}
static inline unsigned int tb_jmp_cache_hash_func(vaddr pc)
{
vaddr tmp;
tmp = pc ^ (pc >> (TARGET_PAGE_BITS - TB_JMP_PAGE_BITS));
return (((tmp >> (TARGET_PAGE_BITS - TB_JMP_PAGE_BITS)) & TB_JMP_PAGE_MASK)
| (tmp & TB_JMP_ADDR_MASK));
}
#else
/* In user-mode we can get better hashing because we do not have a TLB */
static inline unsigned int tb_jmp_cache_hash_func(vaddr pc)
{
return (pc ^ (pc >> TB_JMP_CACHE_BITS)) & (TB_JMP_CACHE_SIZE - 1);
}
#endif /* CONFIG_SOFTMMU */
static inline
uint32_t tb_hash_func(tb_page_addr_t phys_pc, vaddr pc,
uint32_t flags, uint64_t flags2, uint32_t cf_mask)
{
return qemu_xxhash8(phys_pc, pc, flags2, flags, cf_mask);
}
#endif
+55
View File
@@ -0,0 +1,55 @@
/*
* TranslationBlock internal declarations (target specific)
*
* Copyright (c) 2003 Fabrice Bellard
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef ACCEL_TCG_TB_INTERNAL_TARGET_H
#define ACCEL_TCG_TB_INTERNAL_TARGET_H
#include "exec/translation-block.h"
/*
* The true return address will often point to a host insn that is part of
* the next translated guest insn. Adjust the address backward to point to
* the middle of the call insn. Subtracting one would do the job except for
* several compressed mode architectures (arm, mips) which set the low bit
* to indicate the compressed mode; subtracting two works around that. It
* is also the case that there are no host isas that contain a call insn
* smaller than 4 bytes, so we don't worry about special-casing this.
*/
#define GETPC_ADJ 2
void tb_lock_page0(tb_page_addr_t);
#ifdef CONFIG_USER_ONLY
/*
* For user-only, page_protect sets the page read-only.
* Since most execution is already on read-only pages, and we'd need to
* account for other TBs on the same page, defer undoing any page protection
* until we receive the write fault.
*/
static inline void tb_lock_page1(tb_page_addr_t p0, tb_page_addr_t p1)
{
tb_lock_page0(p1);
}
static inline void tb_unlock_page1(tb_page_addr_t p0, tb_page_addr_t p1) { }
static inline void tb_unlock_pages(TranslationBlock *tb) { }
#else
void tb_lock_page1(tb_page_addr_t, tb_page_addr_t);
void tb_unlock_page1(tb_page_addr_t, tb_page_addr_t);
void tb_unlock_pages(TranslationBlock *);
#endif
#ifdef CONFIG_SOFTMMU
void tb_invalidate_phys_range_fast(CPUState *cpu, ram_addr_t ram_addr,
unsigned size, uintptr_t retaddr);
#endif /* CONFIG_SOFTMMU */
bool tb_invalidate_phys_page_unwind(CPUState *cpu, tb_page_addr_t addr,
uintptr_t pc);
#endif
+33
View File
@@ -0,0 +1,33 @@
/*
* The per-CPU TranslationBlock jump cache.
*
* Copyright (c) 2003 Fabrice Bellard
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef ACCEL_TCG_TB_JMP_CACHE_H
#define ACCEL_TCG_TB_JMP_CACHE_H
#include "qemu/rcu.h"
#include "exec/cpu-common.h"
#define TB_JMP_CACHE_BITS 12
#define TB_JMP_CACHE_SIZE (1 << TB_JMP_CACHE_BITS)
/*
* Invalidated in parallel; all accesses to 'tb' must be atomic.
* A valid entry is read/written by a single CPU, therefore there is
* no need for qatomic_rcu_read() and pc is always consistent with a
* non-NULL value of 'tb'. Strictly speaking pc is only needed for
* CF_PCREL, but it's used always for simplicity.
*/
typedef struct CPUJumpCache {
struct rcu_head rcu;
struct {
TranslationBlock *tb;
vaddr pc;
} array[TB_JMP_CACHE_SIZE];
} CPUJumpCache;
#endif /* ACCEL_TCG_TB_JMP_CACHE_H */
+1225
View File
File diff suppressed because it is too large Load Diff
+160
View File
@@ -0,0 +1,160 @@
/*
* QEMU TCG Single Threaded vCPUs implementation using instruction counting
*
* Copyright (c) 2003-2008 Fabrice Bellard
* Copyright (c) 2014 Red Hat Inc.
*
* 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 "system/replay.h"
#include "exec/icount.h"
#include "qemu/main-loop.h"
#include "qemu/guest-random.h"
#include "hw/core/cpu.h"
#include "tcg-accel-ops.h"
#include "tcg-accel-ops-icount.h"
#include "tcg-accel-ops-rr.h"
static int64_t icount_get_limit(void)
{
int64_t deadline;
if (replay_mode != REPLAY_MODE_PLAY) {
/*
* Include all the timers, because they may need an attention.
* Too long CPU execution may create unnecessary delay in UI.
*/
deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL,
QEMU_TIMER_ATTR_ALL);
/* Check realtime timers, because they help with input processing */
deadline = qemu_soonest_timeout(deadline,
qemu_clock_deadline_ns_all(QEMU_CLOCK_REALTIME,
QEMU_TIMER_ATTR_ALL));
/*
* Maintain prior (possibly buggy) behaviour where if no deadline
* was set (as there is no QEMU_CLOCK_VIRTUAL timer) or it is more than
* INT32_MAX nanoseconds ahead, we still use INT32_MAX
* nanoseconds.
*/
if ((deadline < 0) || (deadline > INT32_MAX)) {
deadline = INT32_MAX;
}
return icount_round(deadline);
} else {
return replay_get_instructions();
}
}
static void icount_notify_aio_contexts(void)
{
/* Wake up other AioContexts. */
qemu_clock_notify(QEMU_CLOCK_VIRTUAL);
qemu_clock_run_timers(QEMU_CLOCK_VIRTUAL);
}
void icount_handle_deadline(void)
{
assert(qemu_in_vcpu_thread());
int64_t deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL,
QEMU_TIMER_ATTR_ALL);
/*
* Instructions, interrupts, and exceptions are processed in cpu-exec.
* Don't interrupt cpu thread, when these events are waiting
* (i.e., there is no checkpoint)
*/
if (deadline == 0) {
icount_notify_aio_contexts();
}
}
/* Distribute the budget evenly across all CPUs */
int64_t icount_percpu_budget(int cpu_count)
{
int64_t limit = icount_get_limit();
int64_t timeslice = limit / cpu_count;
if (timeslice == 0) {
timeslice = limit;
}
return timeslice;
}
void icount_prepare_for_run(CPUState *cpu, int64_t cpu_budget)
{
int insns_left;
/*
* These should always be cleared by icount_process_data after
* each vCPU execution. However u16.high can be raised
* asynchronously by cpu_exit/cpu_interrupt/tcg_handle_interrupt
*/
g_assert(cpu->neg.icount_decr.u16.low == 0);
g_assert(cpu->icount_extra == 0);
replay_mutex_lock();
cpu->icount_budget = MIN(icount_get_limit(), cpu_budget);
insns_left = MIN(0xffff, cpu->icount_budget);
cpu->neg.icount_decr.u16.low = insns_left;
cpu->icount_extra = cpu->icount_budget - insns_left;
if (cpu->icount_budget == 0) {
/*
* We're called without the BQL, so must take it while
* we're calling timer handlers.
*/
bql_lock();
icount_notify_aio_contexts();
bql_unlock();
}
}
void icount_process_data(CPUState *cpu)
{
/* Account for executed instructions */
icount_update(cpu);
/* Reset the counters */
cpu->neg.icount_decr.u16.low = 0;
cpu->icount_extra = 0;
cpu->icount_budget = 0;
replay_account_executed_instructions();
replay_mutex_unlock();
}
void icount_handle_interrupt(CPUState *cpu, int mask)
{
int old_mask = cpu->interrupt_request;
tcg_handle_interrupt(cpu, mask);
if (qemu_cpu_is_self(cpu) &&
!cpu->neg.can_do_io
&& (mask & ~old_mask) != 0) {
cpu_abort(cpu, "Raised interrupt while not in I/O function");
}
}
+20
View File
@@ -0,0 +1,20 @@
/*
* QEMU TCG Single Threaded vCPUs implementation using instruction counting
*
* 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 TCG_ACCEL_OPS_ICOUNT_H
#define TCG_ACCEL_OPS_ICOUNT_H
void icount_handle_deadline(void);
void icount_prepare_for_run(CPUState *cpu, int64_t cpu_budget);
int64_t icount_percpu_budget(int cpu_count);
void icount_process_data(CPUState *cpu);
void icount_handle_interrupt(CPUState *cpu, int mask);
#endif /* TCG_ACCEL_OPS_ICOUNT_H */
+138
View File
@@ -0,0 +1,138 @@
/*
* QEMU TCG Multi Threaded vCPUs implementation
*
* Copyright (c) 2003-2008 Fabrice Bellard
* Copyright (c) 2014 Red Hat Inc.
*
* 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 "exec/cpu-common.h"
#include "system/tcg.h"
#include "system/replay.h"
#include "exec/icount.h"
#include "qemu/main-loop.h"
#include "qemu/notify.h"
#include "qemu/guest-random.h"
#include "hw/core/boards.h"
#include "accel/tcg/cpu-loop.h"
#include "tcg/startup.h"
#include "tcg-accel-ops.h"
#include "tcg-accel-ops-mttcg.h"
typedef struct MttcgForceRcuNotifier {
Notifier notifier;
CPUState *cpu;
} MttcgForceRcuNotifier;
static void do_nothing(CPUState *cpu, run_on_cpu_data d)
{
}
static void mttcg_force_rcu(Notifier *notify, void *data)
{
CPUState *cpu = container_of(notify, MttcgForceRcuNotifier, notifier)->cpu;
/*
* Called with rcu_registry_lock held, using async_run_on_cpu() ensures
* that there are no deadlocks.
*/
async_run_on_cpu(cpu, do_nothing, RUN_ON_CPU_NULL);
}
/*
* In the multi-threaded case each vCPU has its own thread. The TLS
* variable current_cpu can be used deep in the code to find the
* current CPUState for a given thread.
*/
static void *mttcg_cpu_thread_fn(void *arg)
{
MttcgForceRcuNotifier force_rcu;
CPUState *cpu = arg;
assert(tcg_enabled());
g_assert(!icount_enabled());
rcu_register_thread();
force_rcu.notifier.notify = mttcg_force_rcu;
force_rcu.cpu = cpu;
rcu_add_force_rcu_notifier(&force_rcu.notifier);
tcg_register_thread();
bql_lock();
qemu_thread_get_self(cpu->thread);
cpu->thread_id = qemu_get_thread_id();
cpu->neg.can_do_io = true;
current_cpu = cpu;
cpu_thread_signal_created(cpu);
qemu_guest_random_seed_thread_part2(cpu->random_seed);
do {
qemu_process_cpu_events(cpu);
if (cpu_can_run(cpu)) {
int r;
bql_unlock();
r = tcg_cpu_exec(cpu);
bql_lock();
switch (r) {
case EXCP_DEBUG:
cpu_handle_guest_debug(cpu);
break;
case EXCP_HALTED:
/*
* Usually cpu->halted is set, but may have already been
* reset by another thread by the time we arrive here.
*/
break;
case EXCP_ATOMIC:
bql_unlock();
cpu_exec_step_atomic(cpu);
bql_lock();
default:
/* Ignore everything else? */
break;
}
}
} while (!cpu->unplug || cpu_can_run(cpu));
tcg_cpu_destroy(cpu);
bql_unlock();
rcu_remove_force_rcu_notifier(&force_rcu.notifier);
rcu_unregister_thread();
return NULL;
}
void mttcg_start_vcpu_thread(CPUState *cpu)
{
char thread_name[VCPU_THREAD_NAME_SIZE];
g_assert(tcg_enabled());
tcg_cpu_init_cflags(cpu, current_machine->smp.max_cpus > 1);
/* create a thread per vCPU with TCG (MTTCG) */
snprintf(thread_name, VCPU_THREAD_NAME_SIZE, "CPU %d/TCG",
cpu->cpu_index);
qemu_thread_create(cpu->thread, thread_name, mttcg_cpu_thread_fn,
cpu, QEMU_THREAD_JOINABLE);
}
+16
View File
@@ -0,0 +1,16 @@
/*
* QEMU TCG Multi Threaded vCPUs implementation
*
* Copyright 2021 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 TCG_ACCEL_OPS_MTTCG_H
#define TCG_ACCEL_OPS_MTTCG_H
/* start an mttcg vCPU thread */
void mttcg_start_vcpu_thread(CPUState *cpu);
#endif /* TCG_ACCEL_OPS_MTTCG_H */
+349
View File
@@ -0,0 +1,349 @@
/*
* QEMU TCG Single Threaded vCPUs implementation
*
* Copyright (c) 2003-2008 Fabrice Bellard
* Copyright (c) 2014 Red Hat Inc.
*
* 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/lockable.h"
#include "system/tcg.h"
#include "system/replay.h"
#include "exec/icount.h"
#include "qemu/main-loop.h"
#include "qemu/notify.h"
#include "qemu/guest-random.h"
#include "exec/cpu-common.h"
#include "accel/tcg/cpu-loop.h"
#include "tcg/startup.h"
#include "tcg-accel-ops.h"
#include "tcg-accel-ops-rr.h"
#include "tcg-accel-ops-icount.h"
/* Kick all RR vCPUs */
void rr_kick_vcpu_thread(CPUState *unused)
{
CPUState *cpu;
CPU_FOREACH(cpu) {
tcg_kick_vcpu_thread(cpu);
};
}
/*
* TCG vCPU kick timer
*
* The kick timer is responsible for moving single threaded vCPU
* emulation on to the next vCPU. If more than one vCPU is running a
* timer event we force a cpu->exit so the next vCPU can get
* scheduled.
*
* The timer is removed if all vCPUs are idle and restarted again once
* idleness is complete.
*/
static QEMUTimer *rr_kick_vcpu_timer;
static CPUState *rr_current_cpu;
static inline int64_t rr_next_kick_time(void)
{
return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + TCG_KICK_PERIOD;
}
/* Kick the currently round-robin scheduled vCPU to next */
static void rr_kick_next_cpu(void)
{
CPUState *cpu;
do {
cpu = qatomic_read(&rr_current_cpu);
if (cpu) {
cpu_exit(cpu);
}
/* Finish kicking this cpu before reading again. */
smp_mb();
} while (cpu != qatomic_read(&rr_current_cpu));
}
static void rr_kick_thread(void *opaque)
{
timer_mod(rr_kick_vcpu_timer, rr_next_kick_time());
rr_kick_next_cpu();
}
static void rr_start_kick_timer(void)
{
if (!rr_kick_vcpu_timer && CPU_NEXT(first_cpu)) {
rr_kick_vcpu_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
rr_kick_thread, NULL);
}
if (rr_kick_vcpu_timer && !timer_pending(rr_kick_vcpu_timer)) {
timer_mod(rr_kick_vcpu_timer, rr_next_kick_time());
}
}
static void rr_stop_kick_timer(void)
{
if (rr_kick_vcpu_timer && timer_pending(rr_kick_vcpu_timer)) {
timer_del(rr_kick_vcpu_timer);
}
}
static void rr_wait_io_event(void)
{
CPUState *cpu;
while (all_cpu_threads_idle()) {
rr_stop_kick_timer();
qemu_cond_wait_bql(first_cpu->halt_cond);
}
rr_start_kick_timer();
CPU_FOREACH(cpu) {
qemu_process_cpu_events_common(cpu);
}
}
/*
* Destroy any remaining vCPUs which have been unplugged and have
* finished running
*/
static void rr_deal_with_unplugged_cpus(void)
{
CPUState *cpu;
CPU_FOREACH(cpu) {
if (cpu->unplug && !cpu_can_run(cpu)) {
tcg_cpu_destroy(cpu);
break;
}
}
}
static void rr_force_rcu(Notifier *notify, void *data)
{
rr_kick_next_cpu();
}
/*
* Calculate the number of CPUs that we will process in a single iteration of
* the main CPU thread loop so that we can fairly distribute the instruction
* count across CPUs.
*
* The CPU count is cached based on the CPU list generation ID to avoid
* iterating the list every time.
*/
static int rr_cpu_count(void)
{
static unsigned int last_gen_id = ~0;
static int cpu_count;
CPUState *cpu;
QEMU_LOCK_GUARD(&qemu_cpu_list_lock);
if (cpu_list_generation_id_get() != last_gen_id) {
cpu_count = 0;
CPU_FOREACH(cpu) {
++cpu_count;
}
last_gen_id = cpu_list_generation_id_get();
}
return cpu_count;
}
/*
* In the single-threaded case each vCPU is simulated in turn. If
* there is more than a single vCPU we create a simple timer to kick
* the vCPU and ensure we don't get stuck in a tight loop in one vCPU.
* This is done explicitly rather than relying on side-effects
* elsewhere.
*/
static void *rr_cpu_thread_fn(void *arg)
{
Notifier force_rcu;
CPUState *cpu = arg;
assert(tcg_enabled());
rcu_register_thread();
force_rcu.notify = rr_force_rcu;
rcu_add_force_rcu_notifier(&force_rcu);
tcg_register_thread();
bql_lock();
qemu_thread_get_self(cpu->thread);
cpu->thread_id = qemu_get_thread_id();
cpu->neg.can_do_io = true;
cpu_thread_signal_created(cpu);
qemu_guest_random_seed_thread_part2(cpu->random_seed);
/* wait for initial kick-off after machine start */
while (cpu_is_stopped(first_cpu)) {
qemu_cond_wait_bql(first_cpu->halt_cond);
/* process any pending work */
CPU_FOREACH(cpu) {
current_cpu = cpu;
qemu_process_cpu_events_common(cpu);
}
}
rr_start_kick_timer();
cpu = first_cpu;
while (1) {
/* Only used for icount_enabled() */
int64_t cpu_budget = 0;
if (cpu) {
/*
* This could even reset exit_request for all CPUs, but in practice
* races between CPU exits and changes to "cpu" are so rare that
* there's no advantage in doing so.
*/
qatomic_set(&cpu->exit_request, false);
}
if (icount_enabled() && all_cpu_threads_idle()) {
/*
* When all cpus are sleeping (e.g in WFI), to avoid a deadlock
* in the main_loop, wake it up in order to start the warp timer.
*/
qemu_notify_event();
}
rr_wait_io_event();
rr_deal_with_unplugged_cpus();
bql_unlock();
replay_mutex_lock();
bql_lock();
if (icount_enabled()) {
int cpu_count = rr_cpu_count();
/* Account partial waits to QEMU_CLOCK_VIRTUAL. */
icount_account_warp_timer();
/*
* Run the timers here. This is much more efficient than
* waking up the I/O thread and waiting for completion.
*/
icount_handle_deadline();
cpu_budget = icount_percpu_budget(cpu_count);
}
replay_mutex_unlock();
if (!cpu) {
cpu = first_cpu;
}
while (cpu && cpu_work_list_empty(cpu)) {
/*
* Store rr_current_cpu before evaluating cpu->exit_request.
* Pairs with rr_kick_next_cpu().
*/
qatomic_set_mb(&rr_current_cpu, cpu);
/* Pairs with store-release in cpu_exit. */
if (qatomic_load_acquire(&cpu->exit_request)) {
break;
}
current_cpu = cpu;
qemu_clock_enable(QEMU_CLOCK_VIRTUAL,
(cpu->singlestep_flags & SSTEP_NOTIMER) == 0);
if (cpu_can_run(cpu)) {
int r;
bql_unlock();
if (icount_enabled()) {
icount_prepare_for_run(cpu, cpu_budget);
}
r = tcg_cpu_exec(cpu);
if (icount_enabled()) {
icount_process_data(cpu);
}
bql_lock();
if (r == EXCP_DEBUG) {
cpu_handle_guest_debug(cpu);
break;
} else if (r == EXCP_ATOMIC) {
bql_unlock();
cpu_exec_step_atomic(cpu);
bql_lock();
break;
}
} else if (cpu->stop) {
if (cpu->unplug) {
cpu = CPU_NEXT(cpu);
}
break;
}
cpu = CPU_NEXT(cpu);
} /* while (cpu && !cpu->exit_request).. */
/* Does not need a memory barrier because a spurious wakeup is okay. */
qatomic_set(&rr_current_cpu, NULL);
}
g_assert_not_reached();
}
void rr_start_vcpu_thread(CPUState *cpu)
{
char thread_name[VCPU_THREAD_NAME_SIZE];
static QemuCond *single_tcg_halt_cond;
static QemuThread *single_tcg_cpu_thread;
g_assert(tcg_enabled());
tcg_cpu_init_cflags(cpu, false);
if (!single_tcg_cpu_thread) {
single_tcg_halt_cond = cpu->halt_cond;
single_tcg_cpu_thread = cpu->thread;
/* share a single thread for all cpus with TCG */
snprintf(thread_name, VCPU_THREAD_NAME_SIZE, "ALL CPUs/TCG");
qemu_thread_create(cpu->thread, thread_name,
rr_cpu_thread_fn,
cpu, QEMU_THREAD_JOINABLE);
} else {
/* we share the thread, dump spare data */
g_free(cpu->thread);
qemu_cond_destroy(cpu->halt_cond);
g_free(cpu->halt_cond);
cpu->thread = single_tcg_cpu_thread;
cpu->halt_cond = single_tcg_halt_cond;
/* copy the stuff done at start of rr_cpu_thread_fn */
cpu->thread_id = first_cpu->thread_id;
cpu->neg.can_do_io = 1;
cpu->created = true;
}
}
+21
View File
@@ -0,0 +1,21 @@
/*
* QEMU TCG Single Threaded vCPUs implementation
*
* 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 TCG_ACCEL_OPS_RR_H
#define TCG_ACCEL_OPS_RR_H
#define TCG_KICK_PERIOD (NANOSECONDS_PER_SECOND / 10)
/* Kick all RR vCPUs. */
void rr_kick_vcpu_thread(CPUState *unused);
/* start the round robin vcpu thread */
void rr_start_vcpu_thread(CPUState *cpu);
#endif /* TCG_ACCEL_OPS_RR_H */
+246
View File
@@ -0,0 +1,246 @@
/*
* QEMU TCG vCPU common functionality
*
* Functionality common to all TCG vCPU variants: mttcg, rr and icount.
*
* Copyright (c) 2003-2008 Fabrice Bellard
* Copyright (c) 2014 Red Hat Inc.
*
* 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 "accel/accel-ops.h"
#include "accel/accel-cpu-ops.h"
#include "accel/tcg/cpu-loop.h"
#include "system/tcg.h"
#include "system/replay.h"
#include "exec/icount.h"
#include "qemu/main-loop.h"
#include "qemu/guest-random.h"
#include "qemu/timer.h"
#include "exec/cputlb.h"
#include "exec/hwaddr.h"
#include "exec/tb-flush.h"
#include "exec/translation-block.h"
#include "exec/watchpoint.h"
#include "gdbstub/enums.h"
#include "hw/core/cpu.h"
#include "tcg-accel-ops.h"
#include "tcg-accel-ops-mttcg.h"
#include "tcg-accel-ops-rr.h"
#include "tcg-accel-ops-icount.h"
/* common functionality among all TCG variants */
void tcg_cpu_init_cflags(CPUState *cpu, bool parallel)
{
uint32_t cflags;
/*
* Include the cluster number in the hash we use to look up TBs.
* This is important because a TB that is valid for one cluster at
* a given physical address and set of CPU flags is not necessarily
* valid for another:
* the two clusters may have different views of physical memory, or
* may have different CPU features (eg FPU present or absent).
*/
cflags = cpu->cluster_index << CF_CLUSTER_SHIFT;
cflags |= parallel ? CF_PARALLEL : 0;
cflags |= icount_enabled() ? CF_USE_ICOUNT : 0;
tcg_cflags_set(cpu, cflags);
}
void tcg_cpu_destroy(CPUState *cpu)
{
cpu_thread_signal_destroyed(cpu);
}
int tcg_cpu_exec(CPUState *cpu)
{
int ret;
assert(tcg_enabled());
cpu_exec_start(cpu);
ret = cpu_exec(cpu);
cpu_exec_end(cpu);
return ret;
}
static void tcg_cpu_reset_hold(CPUState *cpu)
{
tcg_flush_jmp_cache(cpu);
tlb_flush(cpu);
}
/* mask must never be zero, except for A20 change call */
void tcg_handle_interrupt(CPUState *cpu, int mask)
{
cpu_set_interrupt(cpu, mask);
/*
* If called from iothread context, wake the target cpu in
* case its halted.
*/
if (!qemu_cpu_is_self(cpu)) {
qemu_cpu_kick(cpu);
} else {
qatomic_set(&cpu->neg.icount_decr.u16.high, -1);
}
}
/* Translate GDB watchpoint type to a flags value for cpu_watchpoint_* */
static inline int xlat_gdb_type(CPUState *cpu, GdbBreakpointType gdbtype)
{
static const int xlat[] = {
[GDB_WATCHPOINT_WRITE] = BP_GDB | BP_MEM_WRITE,
[GDB_WATCHPOINT_READ] = BP_GDB | BP_MEM_READ,
[GDB_WATCHPOINT_ACCESS] = BP_GDB | BP_MEM_ACCESS,
};
int cputype = xlat[gdbtype];
if (cpu->cc->gdb_stop_before_watchpoint) {
cputype |= BP_STOP_BEFORE_ACCESS;
}
return cputype;
}
static int tcg_insert_gdbstub_breakpoint(CPUState *cs, GdbBreakpointType type,
vaddr addr, vaddr len)
{
CPUState *cpu;
int err = 0;
switch (type) {
case GDB_BREAKPOINT_SW:
case GDB_BREAKPOINT_HW:
CPU_FOREACH(cpu) {
err = cpu_breakpoint_insert(cpu, addr, BP_GDB, NULL);
if (err) {
break;
}
}
return err;
case GDB_WATCHPOINT_WRITE:
case GDB_WATCHPOINT_READ:
case GDB_WATCHPOINT_ACCESS:
CPU_FOREACH(cpu) {
err = cpu_watchpoint_insert(cpu, addr, len,
xlat_gdb_type(cpu, type), NULL);
if (err) {
break;
}
}
return err;
default:
return -ENOSYS;
}
}
static int tcg_remove_gdbstub_breakpoint(CPUState *cs, GdbBreakpointType type,
vaddr addr, vaddr len)
{
CPUState *cpu;
int err = 0;
switch (type) {
case GDB_BREAKPOINT_SW:
case GDB_BREAKPOINT_HW:
CPU_FOREACH(cpu) {
err = cpu_breakpoint_remove(cpu, addr, BP_GDB);
if (err) {
break;
}
}
return err;
case GDB_WATCHPOINT_WRITE:
case GDB_WATCHPOINT_READ:
case GDB_WATCHPOINT_ACCESS:
CPU_FOREACH(cpu) {
err = cpu_watchpoint_remove(cpu, addr, len,
xlat_gdb_type(cpu, type));
if (err) {
break;
}
}
return err;
default:
return -ENOSYS;
}
}
static void tcg_remove_all_gdbstub_breakpoints(CPUState *cpu)
{
cpu_breakpoint_remove_all(cpu, BP_GDB);
cpu_watchpoint_remove_all(cpu, BP_GDB);
}
static void tcg_accel_ops_init(AccelClass *ac)
{
AccelOpsClass *ops = ac->ops;
if (qemu_tcg_mttcg_enabled()) {
ops->create_vcpu_thread = mttcg_start_vcpu_thread;
ops->kick_vcpu_thread = tcg_kick_vcpu_thread;
ops->handle_interrupt = tcg_handle_interrupt;
} else {
ops->create_vcpu_thread = rr_start_vcpu_thread;
ops->kick_vcpu_thread = rr_kick_vcpu_thread;
if (icount_enabled()) {
ops->handle_interrupt = icount_handle_interrupt;
ops->get_virtual_clock = icount_get;
ops->get_elapsed_ticks = icount_get;
} else {
ops->handle_interrupt = tcg_handle_interrupt;
}
}
ops->cpu_reset_hold = tcg_cpu_reset_hold;
ops->insert_gdbstub_breakpoint = tcg_insert_gdbstub_breakpoint;
ops->remove_gdbstub_breakpoint = tcg_remove_gdbstub_breakpoint;
ops->remove_all_gdbstub_breakpoints = tcg_remove_all_gdbstub_breakpoints;
}
static void tcg_accel_ops_class_init(ObjectClass *oc, const void *data)
{
AccelOpsClass *ops = ACCEL_OPS_CLASS(oc);
ops->ops_init = tcg_accel_ops_init;
}
static const TypeInfo tcg_accel_ops_type = {
.name = ACCEL_OPS_NAME("tcg"),
.parent = TYPE_ACCEL_OPS,
.class_init = tcg_accel_ops_class_init,
.abstract = true,
};
module_obj(ACCEL_OPS_NAME("tcg"));
static void tcg_accel_ops_register_types(void)
{
type_register_static(&tcg_accel_ops_type);
}
type_init(tcg_accel_ops_register_types);
+23
View File
@@ -0,0 +1,23 @@
/*
* QEMU TCG vCPU common functionality
*
* Functionality common to all TCG vcpu variants: mttcg, rr and icount.
*
* 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 TCG_ACCEL_OPS_H
#define TCG_ACCEL_OPS_H
#include "system/cpus.h"
void tcg_cpu_destroy(CPUState *cpu);
int tcg_cpu_exec(CPUState *cpu);
void tcg_handle_interrupt(CPUState *cpu, int mask);
void tcg_cpu_init_cflags(CPUState *cpu, bool parallel);
void tcg_kick_vcpu_thread(CPUState *cpu);
#endif /* TCG_ACCEL_OPS_H */
+305
View File
@@ -0,0 +1,305 @@
/*
* QEMU System Emulator, accelerator interfaces
*
* Copyright (c) 2003-2008 Fabrice Bellard
* Copyright (c) 2014 Red Hat Inc.
*
* 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 "system/tcg.h"
#include "exec/replay-core.h"
#include "exec/icount.h"
#include "tcg/startup.h"
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "qemu/accel.h"
#include "qemu/atomic.h"
#include "qapi/qapi-types-common.h"
#include "qapi/qapi-builtin-visit.h"
#include "qemu/units.h"
#include "qemu/target-info.h"
#ifndef CONFIG_USER_ONLY
#include "hw/core/boards.h"
#include "exec/tb-flush.h"
#include "system/runstate.h"
#endif
#include "accel/accel-ops.h"
#include "accel/accel-cpu-ops.h"
#include "accel/tcg/cpu-ops.h"
#include "internal-common.h"
struct TCGState {
AccelState parent_obj;
OnOffAuto mttcg_enabled;
bool one_insn_per_tb;
int splitwx_enabled;
unsigned long tb_size;
};
typedef struct TCGState TCGState;
#define TYPE_TCG_ACCEL ACCEL_CLASS_NAME("tcg")
DECLARE_INSTANCE_CHECKER(TCGState, TCG_STATE,
TYPE_TCG_ACCEL)
#ifndef CONFIG_USER_ONLY
bool qemu_tcg_mttcg_enabled(void)
{
TCGState *s = TCG_STATE(current_accel());
return s->mttcg_enabled == ON_OFF_AUTO_ON;
}
#endif /* !CONFIG_USER_ONLY */
static void tcg_accel_instance_init(Object *obj)
{
TCGState *s = TCG_STATE(obj);
/* If debugging enabled, default "auto on", otherwise off. */
#if defined(CONFIG_DEBUG_TCG) && !defined(CONFIG_USER_ONLY)
s->splitwx_enabled = -1;
#else
s->splitwx_enabled = 0;
#endif
}
bool one_insn_per_tb;
#ifndef CONFIG_USER_ONLY
static void tcg_vm_change_state(void *opaque, bool running, RunState state)
{
if (state == RUN_STATE_RESTORE_VM) {
/*
* loadvm will update the content of RAM, bypassing the usual
* mechanisms that ensure we flush TBs for writes to memory
* we've translated code from, so we must flush all TBs.
*
* vm_stop() has just stopped all cpus, so we are exclusive.
*/
assert(!running);
tb_flush__exclusive_or_serial();
}
}
#endif
static int tcg_init_machine(AccelState *as, MachineState *ms)
{
TCGState *s = TCG_STATE(as);
unsigned max_threads = 1;
#ifndef CONFIG_USER_ONLY
CPUClass *cc = CPU_CLASS(object_class_by_name(target_cpu_type()));
bool mttcg_supported = cc->tcg_ops->mttcg_supported;
switch (s->mttcg_enabled) {
case ON_OFF_AUTO_AUTO:
/*
* We default to false if we know other options have been enabled
* which are currently incompatible with MTTCG. Otherwise when each
* guest (target) has been updated to support:
* - atomic instructions
* - memory ordering primitives (barriers)
* they can set the appropriate CONFIG flags in ${target}-softmmu.mak
*
* Once a guest architecture has been converted to the new primitives
* there is one remaining limitation to check:
* - The guest can't be oversized (e.g. 64 bit guest on 32 bit host)
*/
if (mttcg_supported && !icount_enabled()) {
s->mttcg_enabled = ON_OFF_AUTO_ON;
max_threads = ms->smp.max_cpus;
} else {
s->mttcg_enabled = ON_OFF_AUTO_OFF;
}
break;
case ON_OFF_AUTO_ON:
if (!mttcg_supported) {
warn_report("Guest not yet converted to MTTCG - "
"you may get unexpected results");
}
max_threads = ms->smp.max_cpus;
break;
case ON_OFF_AUTO_OFF:
break;
default:
g_assert_not_reached();
}
qemu_add_vm_change_state_handler(tcg_vm_change_state, NULL);
#endif
tcg_allowed = true;
as->gdbstub.sstep_flags = SSTEP_ENABLE;
if (replay_mode == REPLAY_MODE_NONE) {
/*
* In replay mode all events will come from the log and can't be
* suppressed otherwise we would break determinism. However as those
* events are tied to the number of executed instructions we won't see
* them occurring every time we single step.
*/
as->gdbstub.sstep_flags |= SSTEP_NOIRQ | SSTEP_NOTIMER;
}
if (replay_mode == REPLAY_MODE_PLAY) {
as->gdbstub.can_reverse = true;
}
page_init();
tb_htable_init();
tcg_init(s->tb_size * MiB, s->splitwx_enabled, max_threads);
#if defined(CONFIG_SOFTMMU)
/*
* There's no guest base to take into account, so go ahead and
* initialize the prologue now.
*/
tcg_prologue_init();
#endif
#ifdef CONFIG_USER_ONLY
qdev_create_fake_machine();
#endif
return 0;
}
static char *tcg_get_thread(Object *obj, Error **errp)
{
TCGState *s = TCG_STATE(obj);
return g_strdup(s->mttcg_enabled == ON_OFF_AUTO_ON ? "multi" : "single");
}
static void tcg_set_thread(Object *obj, const char *value, Error **errp)
{
TCGState *s = TCG_STATE(obj);
if (strcmp(value, "multi") == 0) {
if (icount_enabled()) {
error_setg(errp, "No MTTCG when icount is enabled");
} else {
s->mttcg_enabled = ON_OFF_AUTO_ON;
}
} else if (strcmp(value, "single") == 0) {
s->mttcg_enabled = ON_OFF_AUTO_OFF;
} else {
error_setg(errp, "Invalid 'thread' setting %s", value);
}
}
static void tcg_get_tb_size(Object *obj, Visitor *v,
const char *name, void *opaque,
Error **errp)
{
TCGState *s = TCG_STATE(obj);
uint32_t value = s->tb_size;
visit_type_uint32(v, name, &value, errp);
}
static void tcg_set_tb_size(Object *obj, Visitor *v,
const char *name, void *opaque,
Error **errp)
{
TCGState *s = TCG_STATE(obj);
uint32_t value;
if (!visit_type_uint32(v, name, &value, errp)) {
return;
}
s->tb_size = value;
}
static bool tcg_get_splitwx(Object *obj, Error **errp)
{
TCGState *s = TCG_STATE(obj);
return s->splitwx_enabled;
}
static void tcg_set_splitwx(Object *obj, bool value, Error **errp)
{
TCGState *s = TCG_STATE(obj);
s->splitwx_enabled = value;
}
static bool tcg_get_one_insn_per_tb(Object *obj, Error **errp)
{
TCGState *s = TCG_STATE(obj);
return s->one_insn_per_tb;
}
static void tcg_set_one_insn_per_tb(Object *obj, bool value, Error **errp)
{
TCGState *s = TCG_STATE(obj);
s->one_insn_per_tb = value;
/* Set the global also: this changes the behaviour */
qatomic_set(&one_insn_per_tb, value);
}
static void tcg_accel_class_init(ObjectClass *oc, const void *data)
{
AccelClass *ac = ACCEL_CLASS(oc);
ac->name = "tcg";
ac->init_machine = tcg_init_machine;
ac->cpu_common_realize = tcg_exec_realizefn;
ac->cpu_common_unrealize = tcg_exec_unrealizefn;
ac->get_stats = tcg_get_stats;
ac->allowed = &tcg_allowed;
object_class_property_add_str(oc, "thread",
tcg_get_thread,
tcg_set_thread);
object_class_property_add(oc, "tb-size", "int",
tcg_get_tb_size, tcg_set_tb_size,
NULL, NULL);
object_class_property_set_description(oc, "tb-size",
"TCG translation block cache size");
object_class_property_add_bool(oc, "split-wx",
tcg_get_splitwx, tcg_set_splitwx);
object_class_property_set_description(oc, "split-wx",
"Map jit pages into separate RW and RX regions");
object_class_property_add_bool(oc, "one-insn-per-tb",
tcg_get_one_insn_per_tb,
tcg_set_one_insn_per_tb);
object_class_property_set_description(oc, "one-insn-per-tb",
"Only put one guest insn in each translation block");
}
static const TypeInfo tcg_accel_type = {
.name = TYPE_TCG_ACCEL,
.parent = TYPE_ACCEL,
.instance_init = tcg_accel_instance_init,
.class_init = tcg_accel_class_init,
.instance_size = sizeof(TCGState),
};
module_obj(TYPE_TCG_ACCEL);
static void register_accel_types(void)
{
type_register_static(&tcg_accel_type);
}
type_init(register_accel_types);
File diff suppressed because it is too large Load Diff
+136
View File
@@ -0,0 +1,136 @@
/*
* Tiny Code Generator for QEMU
*
* Copyright (c) 2008 Fabrice Bellard
*
* 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/host-utils.h"
#include "exec/cpu-common.h"
#include "exec/helper-proto-common.h"
#include "accel/tcg/cpu-loop.h"
#include "accel/tcg/getpc.h"
#define HELPER_H "accel/tcg/tcg-runtime.h"
#include "exec/helper-info.c.inc"
#undef HELPER_H
/* 32-bit helpers */
int32_t HELPER(div_i32)(int32_t arg1, int32_t arg2)
{
return arg1 / arg2;
}
int32_t HELPER(rem_i32)(int32_t arg1, int32_t arg2)
{
return arg1 % arg2;
}
uint32_t HELPER(divu_i32)(uint32_t arg1, uint32_t arg2)
{
return arg1 / arg2;
}
uint32_t HELPER(remu_i32)(uint32_t arg1, uint32_t arg2)
{
return arg1 % arg2;
}
/* 64-bit helpers */
int64_t HELPER(div_i64)(int64_t arg1, int64_t arg2)
{
return arg1 / arg2;
}
int64_t HELPER(rem_i64)(int64_t arg1, int64_t arg2)
{
return arg1 % arg2;
}
uint64_t HELPER(divu_i64)(uint64_t arg1, uint64_t arg2)
{
return arg1 / arg2;
}
uint64_t HELPER(remu_i64)(uint64_t arg1, uint64_t arg2)
{
return arg1 % arg2;
}
uint64_t HELPER(muluh_i64)(uint64_t arg1, uint64_t arg2)
{
uint64_t l, h;
mulu64(&l, &h, arg1, arg2);
return h;
}
int64_t HELPER(mulsh_i64)(int64_t arg1, int64_t arg2)
{
uint64_t l, h;
muls64(&l, &h, arg1, arg2);
return h;
}
uint32_t HELPER(clz_i32)(uint32_t arg, uint32_t zero_val)
{
return arg ? clz32(arg) : zero_val;
}
uint32_t HELPER(ctz_i32)(uint32_t arg, uint32_t zero_val)
{
return arg ? ctz32(arg) : zero_val;
}
uint64_t HELPER(clz_i64)(uint64_t arg, uint64_t zero_val)
{
return arg ? clz64(arg) : zero_val;
}
uint64_t HELPER(ctz_i64)(uint64_t arg, uint64_t zero_val)
{
return arg ? ctz64(arg) : zero_val;
}
uint32_t HELPER(clrsb_i32)(uint32_t arg)
{
return clrsb32(arg);
}
uint64_t HELPER(clrsb_i64)(uint64_t arg)
{
return clrsb64(arg);
}
uint32_t HELPER(ctpop_i32)(uint32_t arg)
{
return ctpop32(arg);
}
uint64_t HELPER(ctpop_i64)(uint64_t arg)
{
return ctpop64(arg);
}
void HELPER(exit_atomic)(CPUArchState *env)
{
cpu_loop_exit_atomic(env_cpu(env), GETPC());
}
+314
View File
@@ -0,0 +1,314 @@
DEF_HELPER_FLAGS_2(div_i32, TCG_CALL_NO_RWG_SE, s32, s32, s32)
DEF_HELPER_FLAGS_2(rem_i32, TCG_CALL_NO_RWG_SE, s32, s32, s32)
DEF_HELPER_FLAGS_2(divu_i32, TCG_CALL_NO_RWG_SE, i32, i32, i32)
DEF_HELPER_FLAGS_2(remu_i32, TCG_CALL_NO_RWG_SE, i32, i32, i32)
DEF_HELPER_FLAGS_2(div_i64, TCG_CALL_NO_RWG_SE, s64, s64, s64)
DEF_HELPER_FLAGS_2(rem_i64, TCG_CALL_NO_RWG_SE, s64, s64, s64)
DEF_HELPER_FLAGS_2(divu_i64, TCG_CALL_NO_RWG_SE, i64, i64, i64)
DEF_HELPER_FLAGS_2(remu_i64, TCG_CALL_NO_RWG_SE, i64, i64, i64)
DEF_HELPER_FLAGS_2(mulsh_i64, TCG_CALL_NO_RWG_SE, s64, s64, s64)
DEF_HELPER_FLAGS_2(muluh_i64, TCG_CALL_NO_RWG_SE, i64, i64, i64)
DEF_HELPER_FLAGS_2(clz_i32, TCG_CALL_NO_RWG_SE, i32, i32, i32)
DEF_HELPER_FLAGS_2(ctz_i32, TCG_CALL_NO_RWG_SE, i32, i32, i32)
DEF_HELPER_FLAGS_2(clz_i64, TCG_CALL_NO_RWG_SE, i64, i64, i64)
DEF_HELPER_FLAGS_2(ctz_i64, TCG_CALL_NO_RWG_SE, i64, i64, i64)
DEF_HELPER_FLAGS_1(clrsb_i32, TCG_CALL_NO_RWG_SE, i32, i32)
DEF_HELPER_FLAGS_1(clrsb_i64, TCG_CALL_NO_RWG_SE, i64, i64)
DEF_HELPER_FLAGS_1(ctpop_i32, TCG_CALL_NO_RWG_SE, i32, i32)
DEF_HELPER_FLAGS_1(ctpop_i64, TCG_CALL_NO_RWG_SE, i64, i64)
DEF_HELPER_FLAGS_1(lookup_tb_ptr, TCG_CALL_NO_WG_SE, cptr, env)
DEF_HELPER_FLAGS_1(exit_atomic, TCG_CALL_NO_WG, noreturn, env)
#ifndef IN_HELPER_PROTO
/*
* Pass calls to memset directly to libc, without a thunk in qemu.
* Do not re-declare memset, especially since we fudge the type here;
* we assume sizeof(void *) == sizeof(size_t), which is true for
* all supported hosts.
*/
#define helper_memset memset
DEF_HELPER_FLAGS_3(memset, TCG_CALL_NO_RWG, ptr, ptr, int, ptr)
#endif /* IN_HELPER_PROTO */
DEF_HELPER_FLAGS_3(ld_i128, TCG_CALL_NO_WG, i128, env, i64, i32)
DEF_HELPER_FLAGS_4(st_i128, TCG_CALL_NO_WG, void, env, i64, i128, i32)
DEF_HELPER_FLAGS_5(atomic_cmpxchgb, TCG_CALL_NO_WG,
i32, env, i64, i32, i32, i32)
DEF_HELPER_FLAGS_5(atomic_cmpxchgw_be, TCG_CALL_NO_WG,
i32, env, i64, i32, i32, i32)
DEF_HELPER_FLAGS_5(atomic_cmpxchgw_le, TCG_CALL_NO_WG,
i32, env, i64, i32, i32, i32)
DEF_HELPER_FLAGS_5(atomic_cmpxchgl_be, TCG_CALL_NO_WG,
i32, env, i64, i32, i32, i32)
DEF_HELPER_FLAGS_5(atomic_cmpxchgl_le, TCG_CALL_NO_WG,
i32, env, i64, i32, i32, i32)
DEF_HELPER_FLAGS_5(atomic_cmpxchgq_be, TCG_CALL_NO_WG,
i64, env, i64, i64, i64, i32)
DEF_HELPER_FLAGS_5(atomic_cmpxchgq_le, TCG_CALL_NO_WG,
i64, env, i64, i64, i64, i32)
#if HAVE_CMPXCHG128
DEF_HELPER_FLAGS_5(atomic_cmpxchgo_be, TCG_CALL_NO_WG,
i128, env, i64, i128, i128, i32)
DEF_HELPER_FLAGS_5(atomic_cmpxchgo_le, TCG_CALL_NO_WG,
i128, env, i64, i128, i128, i32)
DEF_HELPER_FLAGS_4(atomic_xchgo_be, TCG_CALL_NO_WG,
i128, env, i64, i128, i32)
DEF_HELPER_FLAGS_4(atomic_xchgo_le, TCG_CALL_NO_WG,
i128, env, i64, i128, i32)
DEF_HELPER_FLAGS_4(atomic_fetch_ando_be, TCG_CALL_NO_WG,
i128, env, i64, i128, i32)
DEF_HELPER_FLAGS_4(atomic_fetch_ando_le, TCG_CALL_NO_WG,
i128, env, i64, i128, i32)
DEF_HELPER_FLAGS_4(atomic_fetch_oro_be, TCG_CALL_NO_WG,
i128, env, i64, i128, i32)
DEF_HELPER_FLAGS_4(atomic_fetch_oro_le, TCG_CALL_NO_WG,
i128, env, i64, i128, i32)
#endif
#define GEN_ATOMIC_HELPERS(NAME) \
DEF_HELPER_FLAGS_4(glue(glue(atomic_, NAME), b), \
TCG_CALL_NO_WG, i32, env, i64, i32, i32) \
DEF_HELPER_FLAGS_4(glue(glue(atomic_, NAME), w_le), \
TCG_CALL_NO_WG, i32, env, i64, i32, i32) \
DEF_HELPER_FLAGS_4(glue(glue(atomic_, NAME), w_be), \
TCG_CALL_NO_WG, i32, env, i64, i32, i32) \
DEF_HELPER_FLAGS_4(glue(glue(atomic_, NAME), l_le), \
TCG_CALL_NO_WG, i32, env, i64, i32, i32) \
DEF_HELPER_FLAGS_4(glue(glue(atomic_, NAME), l_be), \
TCG_CALL_NO_WG, i32, env, i64, i32, i32) \
DEF_HELPER_FLAGS_4(glue(glue(atomic_, NAME), q_le), \
TCG_CALL_NO_WG, i64, env, i64, i64, i32) \
DEF_HELPER_FLAGS_4(glue(glue(atomic_, NAME), q_be), \
TCG_CALL_NO_WG, i64, env, i64, i64, i32)
GEN_ATOMIC_HELPERS(fetch_add)
GEN_ATOMIC_HELPERS(fetch_and)
GEN_ATOMIC_HELPERS(fetch_or)
GEN_ATOMIC_HELPERS(fetch_xor)
GEN_ATOMIC_HELPERS(fetch_smin)
GEN_ATOMIC_HELPERS(fetch_umin)
GEN_ATOMIC_HELPERS(fetch_smax)
GEN_ATOMIC_HELPERS(fetch_umax)
GEN_ATOMIC_HELPERS(add_fetch)
GEN_ATOMIC_HELPERS(and_fetch)
GEN_ATOMIC_HELPERS(or_fetch)
GEN_ATOMIC_HELPERS(xor_fetch)
GEN_ATOMIC_HELPERS(smin_fetch)
GEN_ATOMIC_HELPERS(umin_fetch)
GEN_ATOMIC_HELPERS(smax_fetch)
GEN_ATOMIC_HELPERS(umax_fetch)
GEN_ATOMIC_HELPERS(xchg)
#undef GEN_ATOMIC_HELPERS
DEF_HELPER_FLAGS_3(gvec_mov, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_dup8, TCG_CALL_NO_RWG, void, ptr, i32, i32)
DEF_HELPER_FLAGS_3(gvec_dup16, TCG_CALL_NO_RWG, void, ptr, i32, i32)
DEF_HELPER_FLAGS_3(gvec_dup32, TCG_CALL_NO_RWG, void, ptr, i32, i32)
DEF_HELPER_FLAGS_3(gvec_dup64, TCG_CALL_NO_RWG, void, ptr, i32, i64)
DEF_HELPER_FLAGS_4(gvec_add8, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_add16, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_add32, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_add64, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_adds8, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_adds16, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_adds32, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_adds64, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_sub8, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_sub16, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_sub32, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_sub64, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_subs8, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_subs16, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_subs32, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_subs64, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_mul8, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_mul16, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_mul32, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_mul64, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_muls8, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_muls16, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_muls32, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_muls64, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_ssadd8, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_ssadd16, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_ssadd32, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_ssadd64, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_sssub8, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_sssub16, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_sssub32, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_sssub64, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_usadd8, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_usadd16, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_usadd32, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_usadd64, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_ussub8, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_ussub16, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_ussub32, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_ussub64, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_smin8, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_smin16, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_smin32, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_smin64, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_smax8, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_smax16, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_smax32, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_smax64, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_umin8, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_umin16, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_umin32, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_umin64, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_umax8, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_umax16, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_umax32, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_umax64, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_neg8, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_neg16, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_neg32, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_neg64, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_abs8, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_abs16, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_abs32, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_abs64, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_not, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_and, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_or, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_xor, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_andc, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_orc, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_nand, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_nor, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_eqv, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_ands, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_andcs, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_xors, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_ors, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_3(gvec_shl8i, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_shl16i, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_shl32i, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_shl64i, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_shr8i, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_shr16i, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_shr32i, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_shr64i, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_sar8i, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_sar16i, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_sar32i, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_sar64i, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_rotl8i, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_rotl16i, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_rotl32i, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_3(gvec_rotl64i, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_shl8v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_shl16v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_shl32v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_shl64v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_shr8v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_shr16v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_shr32v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_shr64v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_sar8v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_sar16v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_sar32v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_sar64v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_rotl8v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_rotl16v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_rotl32v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_rotl64v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_rotr8v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_rotr16v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_rotr32v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_rotr64v, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_eq8, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_eq16, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_eq32, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_eq64, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_ne8, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_ne16, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_ne32, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_ne64, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_lt8, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_lt16, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_lt32, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_lt64, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_le8, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_le16, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_le32, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_le64, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_ltu8, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_ltu16, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_ltu32, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_ltu64, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_leu8, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_leu16, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_leu32, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_leu64, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_eqs8, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_eqs16, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_eqs32, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_eqs64, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_lts8, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_lts16, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_lts32, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_lts64, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_les8, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_les16, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_les32, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_les64, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_ltus8, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_ltus16, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_ltus32, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_ltus64, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_leus8, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_leus16, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_leus32, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_leus64, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_5(gvec_bitsel, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, i32)
+219
View File
@@ -0,0 +1,219 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* QEMU TCG statistics
*
* Copyright (c) 2003-2005 Fabrice Bellard
*/
#include "qemu/osdep.h"
#include "qemu/accel.h"
#include "qemu/qht.h"
#include "qapi/error.h"
#include "system/cpu-timers.h"
#include "exec/icount.h"
#include "hw/core/cpu.h"
#include "tcg/tcg.h"
#include "internal-common.h"
#include "tb-context.h"
#include <math.h>
static void dump_drift_info(GString *buf)
{
if (!icount_enabled()) {
return;
}
g_string_append_printf(buf, "Host - Guest clock %"PRIi64" ms\n",
(cpu_get_clock() - icount_get()) / SCALE_MS);
if (icount_align_option) {
g_string_append_printf(buf, "Max guest delay %"PRIi64" ms\n",
-max_delay / SCALE_MS);
g_string_append_printf(buf, "Max guest advance %"PRIi64" ms\n",
max_advance / SCALE_MS);
} else {
g_string_append_printf(buf, "Max guest delay NA\n");
g_string_append_printf(buf, "Max guest advance NA\n");
}
}
static void dump_accel_info(AccelState *accel, GString *buf)
{
bool one_insn_per_tb = object_property_get_bool(OBJECT(accel),
"one-insn-per-tb",
&error_fatal);
g_string_append_printf(buf, "Accelerator settings:\n");
g_string_append_printf(buf, "one-insn-per-tb: %s\n\n",
one_insn_per_tb ? "on" : "off");
}
static void print_qht_statistics(struct qht_stats hst, GString *buf)
{
uint32_t hgram_opts;
size_t hgram_bins;
char *hgram;
double avg;
if (!hst.head_buckets) {
return;
}
g_string_append_printf(buf, "TB hash buckets %zu/%zu "
"(%0.2f%% head buckets used)\n",
hst.used_head_buckets, hst.head_buckets,
(double)hst.used_head_buckets /
hst.head_buckets * 100);
hgram_opts = QDIST_PR_BORDER | QDIST_PR_LABELS;
hgram_opts |= QDIST_PR_100X | QDIST_PR_PERCENT;
if (qdist_xmax(&hst.occupancy) - qdist_xmin(&hst.occupancy) == 1) {
hgram_opts |= QDIST_PR_NODECIMAL;
}
hgram = qdist_pr(&hst.occupancy, 10, hgram_opts);
avg = qdist_avg(&hst.occupancy);
if (!isnan(avg)) {
g_string_append_printf(buf, "TB hash occupancy "
"%0.2f%% avg chain occ. "
"Histogram: %s\n",
avg * 100, hgram);
}
g_free(hgram);
hgram_opts = QDIST_PR_BORDER | QDIST_PR_LABELS;
hgram_bins = qdist_xmax(&hst.chain) - qdist_xmin(&hst.chain);
if (hgram_bins > 10) {
hgram_bins = 10;
} else {
hgram_bins = 0;
hgram_opts |= QDIST_PR_NODECIMAL | QDIST_PR_NOBINRANGE;
}
hgram = qdist_pr(&hst.chain, hgram_bins, hgram_opts);
avg = qdist_avg(&hst.chain);
if (!isnan(avg)) {
g_string_append_printf(buf, "TB hash avg chain %0.3f buckets. "
"Histogram: %s\n",
avg, hgram);
}
g_free(hgram);
}
struct tb_tree_stats {
size_t nb_tbs;
size_t host_size;
size_t target_size;
size_t max_target_size;
size_t direct_jmp_count;
size_t direct_jmp2_count;
size_t cross_page;
};
static gboolean tb_tree_stats_iter(gpointer key, gpointer value, gpointer data)
{
const TranslationBlock *tb = value;
struct tb_tree_stats *tst = data;
tst->nb_tbs++;
tst->host_size += tb->tc.size;
tst->target_size += tb->size;
if (tb->size > tst->max_target_size) {
tst->max_target_size = tb->size;
}
#ifndef CONFIG_USER_ONLY
if (tb->page_addr[1] != -1) {
tst->cross_page++;
}
#endif
if (tb->jmp_reset_offset[0] != TB_JMP_OFFSET_INVALID) {
tst->direct_jmp_count++;
if (tb->jmp_reset_offset[1] != TB_JMP_OFFSET_INVALID) {
tst->direct_jmp2_count++;
}
}
return false;
}
static void tlb_flush_counts(size_t *pfull, size_t *ppart, size_t *pelide)
{
CPUState *cpu;
size_t full = 0, part = 0, elide = 0;
CPU_FOREACH(cpu) {
full += qatomic_read(&cpu->neg.tlb.c.full_flush_count);
part += qatomic_read(&cpu->neg.tlb.c.part_flush_count);
elide += qatomic_read(&cpu->neg.tlb.c.elide_flush_count);
}
*pfull = full;
*ppart = part;
*pelide = elide;
}
static void tcg_dump_flush_info(GString *buf)
{
size_t flush_full, flush_part, flush_elide;
g_string_append_printf(buf, "TB flush count %u\n",
qatomic_read(&tb_ctx.tb_flush_count));
g_string_append_printf(buf, "TB invalidate count %u\n",
qatomic_read(&tb_ctx.tb_phys_invalidate_count));
tlb_flush_counts(&flush_full, &flush_part, &flush_elide);
g_string_append_printf(buf, "TLB full flushes %zu\n", flush_full);
g_string_append_printf(buf, "TLB partial flushes %zu\n", flush_part);
g_string_append_printf(buf, "TLB elided flushes %zu\n", flush_elide);
}
static void dump_exec_info(GString *buf)
{
struct tb_tree_stats tst = {};
struct qht_stats hst;
size_t nb_tbs;
tcg_tb_foreach(tb_tree_stats_iter, &tst);
nb_tbs = tst.nb_tbs;
/* XXX: avoid using doubles ? */
g_string_append_printf(buf, "Translation buffer state:\n");
/*
* Report total code size including the padding and TB structs;
* otherwise users might think "-accel tcg,tb-size" is not honoured.
* For avg host size we use the precise numbers from tb_tree_stats though.
*/
g_string_append_printf(buf, "gen code size %zu/%zu\n",
tcg_code_size(), tcg_code_capacity());
g_string_append_printf(buf, "TB count %zu\n", nb_tbs);
g_string_append_printf(buf, "TB avg target size %zu max=%zu bytes\n",
nb_tbs ? tst.target_size / nb_tbs : 0,
tst.max_target_size);
g_string_append_printf(buf, "TB avg host size %zu bytes "
"(expansion ratio: %0.1f)\n",
nb_tbs ? tst.host_size / nb_tbs : 0,
tst.target_size ?
(double)tst.host_size / tst.target_size : 0);
g_string_append_printf(buf, "cross page TB count %zu (%zu%%)\n",
tst.cross_page,
nb_tbs ? (tst.cross_page * 100) / nb_tbs : 0);
g_string_append_printf(buf, "direct jump count %zu (%zu%%) "
"(2 jumps=%zu %zu%%)\n",
tst.direct_jmp_count,
nb_tbs ? (tst.direct_jmp_count * 100) / nb_tbs : 0,
tst.direct_jmp2_count,
nb_tbs ? (tst.direct_jmp2_count * 100) / nb_tbs : 0);
qht_statistics_init(&tb_ctx.htable, &hst);
print_qht_statistics(hst, buf);
qht_statistics_destroy(&hst);
g_string_append_printf(buf, "\nStatistics:\n");
tcg_dump_flush_info(buf);
}
void tcg_get_stats(AccelState *accel, GString *buf)
{
dump_accel_info(accel, buf);
dump_exec_info(buf);
dump_drift_info(buf);
}
void tcg_dump_stats(GString *buf)
{
tcg_get_stats(current_accel(), buf);
}
+13
View File
@@ -0,0 +1,13 @@
/*
* softmmu size bounds
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef ACCEL_TCG_TLB_BOUNDS_H
#define ACCEL_TCG_TLB_BOUNDS_H
#define CPU_TLB_DYN_MIN_BITS 6
#define CPU_TLB_DYN_MAX_BITS (32 - TARGET_PAGE_BITS)
#define CPU_TLB_DYN_DEFAULT_BITS 8
#endif /* ACCEL_TCG_TLB_BOUNDS_H */
+30
View File
@@ -0,0 +1,30 @@
# See docs/devel/tracing.rst for syntax documentation.
# TCG related tracing
# cpu-exec.c
exec_tb(void *tb, uintptr_t pc) "tb:%p pc=0x%"PRIxPTR
exec_tb_nocache(void *tb, uintptr_t pc) "tb:%p pc=0x%"PRIxPTR
exec_tb_exit(void *last_tb, unsigned int flags) "tb:%p flags=0x%x"
# cputlb.c
memory_notdirty_write_access(uint64_t vaddr, uint64_t ram_addr, unsigned size) "0x%" PRIx64 " ram_addr 0x%" PRIx64 " size %u"
memory_notdirty_set_dirty(uint64_t vaddr) "0x%" PRIx64
# translate-all.c
translate_block(void *tb, uintptr_t pc, const void *tb_code) "tb:%p, pc:0x%"PRIxPTR", tb_code:%p"
tb_gen_code_buffer_overflow(const char *reason) "reason: %s"
# ldst_atomicity
load_atom2_fallback(uint32_t memop, uintptr_t ra) "mop:0x%"PRIx32", ra:0x%"PRIxPTR""
load_atom4_fallback(uint32_t memop, uintptr_t ra) "mop:0x%"PRIx32", ra:0x%"PRIxPTR""
load_atom8_or_exit_fallback(uintptr_t ra) "ra:0x%"PRIxPTR""
load_atom8_fallback(uint32_t memop, uintptr_t ra) "mop:0x%"PRIx32", ra:0x%"PRIxPTR""
load_atom16_fallback(uint32_t memop, uintptr_t ra) "mop:0x%"PRIx32", ra:0x%"PRIxPTR""
load_atom16_or_exit_fallback(uintptr_t ra) "ra:0x%"PRIxPTR""
store_atom2_fallback(uint32_t memop, uintptr_t ra) "mop:0x%"PRIx32", ra:0x%"PRIxPTR""
store_atom4_fallback(uint32_t memop, uintptr_t ra) "mop:0x%"PRIx32", ra:0x%"PRIxPTR""
store_atom8_fallback(uint32_t memop, uintptr_t ra) "mop:0x%"PRIx32", ra:0x%"PRIxPTR""
store_atom16_fallback(uint32_t memop, uintptr_t ra) "mop:0x%"PRIx32", ra:0x%"PRIxPTR""
# tb-maint.c
tb_flush(void) ""
+1
View File
@@ -0,0 +1 @@
#include "trace/trace-accel_tcg.h"
+638
View File
@@ -0,0 +1,638 @@
/*
* Host code generation
*
* Copyright (c) 2003 Fabrice Bellard
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "trace.h"
#include "disas/disas.h"
#include "tcg/tcg.h"
#include "exec/mmap-lock.h"
#include "tb-internal.h"
#include "exec/tb-flush.h"
#include "qemu/cacheinfo.h"
#include "qemu/target-info.h"
#include "exec/log.h"
#include "exec/icount.h"
#include "accel/tcg/cpu-loop.h"
#include "accel/tcg/cpu-ops.h"
#include "tb-jmp-cache.h"
#include "tb-hash.h"
#include "tb-context.h"
#include "internal-common.h"
#include "tcg/perf.h"
#include "tcg/insn-start-words.h"
TBContext tb_ctx;
/*
* Encode VAL as a signed leb128 sequence at P.
* Return P incremented past the encoded value.
*/
static uint8_t *encode_sleb128(uint8_t *p, int64_t val)
{
int more, byte;
do {
byte = val & 0x7f;
val >>= 7;
more = !((val == 0 && (byte & 0x40) == 0)
|| (val == -1 && (byte & 0x40) != 0));
if (more) {
byte |= 0x80;
}
*p++ = byte;
} while (more);
return p;
}
/*
* Decode a signed leb128 sequence at *PP; increment *PP past the
* decoded value. Return the decoded value.
*/
static int64_t decode_sleb128(const uint8_t **pp)
{
const uint8_t *p = *pp;
int64_t val = 0;
int byte, shift = 0;
do {
byte = *p++;
val |= (int64_t)(byte & 0x7f) << shift;
shift += 7;
} while (byte & 0x80);
if (shift < 64 && (byte & 0x40)) {
val |= -(int64_t)1 << shift;
}
*pp = p;
return val;
}
/* Encode the data collected about the instructions while compiling TB.
Place the data at BLOCK, and return the number of bytes consumed.
The logical table consists of INSN_START_WORDS uint64_t's,
which come from the target's insn_start data, followed by a uintptr_t
which comes from the host pc of the end of the code implementing the insn.
Each line of the table is encoded as sleb128 deltas from the previous
line. The seed for the first line is { tb->pc, 0..., tb->tc.ptr }.
That is, the first column is seeded with the guest pc, the last column
with the host pc, and the middle columns with zeros. */
static int encode_search(TranslationBlock *tb, uint8_t *block)
{
uint8_t *highwater = tcg_ctx->code_gen_highwater;
uint64_t *insn_data = tcg_ctx->gen_insn_data;
uint16_t *insn_end_off = tcg_ctx->gen_insn_end_off;
uint8_t *p = block;
int i, j, n;
for (i = 0, n = tb->icount; i < n; ++i) {
uint64_t prev, curr;
for (j = 0; j < INSN_START_WORDS; ++j) {
if (i == 0) {
prev = (!(tb_cflags(tb) & CF_PCREL) && j == 0 ? tb->pc : 0);
} else {
prev = insn_data[(i - 1) * INSN_START_WORDS + j];
}
curr = insn_data[i * INSN_START_WORDS + j];
p = encode_sleb128(p, curr - prev);
}
prev = (i == 0 ? 0 : insn_end_off[i - 1]);
curr = insn_end_off[i];
p = encode_sleb128(p, curr - prev);
/* Test for (pending) buffer overflow. The assumption is that any
one row beginning below the high water mark cannot overrun
the buffer completely. Thus we can test for overflow after
encoding a row without having to check during encoding. */
if (unlikely(p > highwater)) {
return -1;
}
}
return p - block;
}
static int cpu_unwind_data_from_tb(TranslationBlock *tb, uintptr_t host_pc,
uint64_t *data)
{
uintptr_t iter_pc = (uintptr_t)tb->tc.ptr;
const uint8_t *p = tb->tc.ptr + tb->tc.size;
int i, j, num_insns = tb->icount;
host_pc -= GETPC_ADJ;
if (host_pc < iter_pc) {
return -1;
}
memset(data, 0, sizeof(uint64_t) * INSN_START_WORDS);
if (!(tb_cflags(tb) & CF_PCREL)) {
data[0] = tb->pc;
}
/*
* Reconstruct the stored insn data while looking for the point
* at which the end of the insn exceeds host_pc.
*/
for (i = 0; i < num_insns; ++i) {
for (j = 0; j < INSN_START_WORDS; ++j) {
data[j] += decode_sleb128(&p);
}
iter_pc += decode_sleb128(&p);
if (iter_pc > host_pc) {
return num_insns - i;
}
}
return -1;
}
/*
* The cpu state corresponding to 'host_pc' is restored in
* preparation for exiting the TB.
*/
void cpu_restore_state_from_tb(CPUState *cpu, TranslationBlock *tb,
uintptr_t host_pc)
{
uint64_t data[INSN_START_WORDS];
int insns_left = cpu_unwind_data_from_tb(tb, host_pc, data);
if (insns_left < 0) {
return;
}
if (tb_cflags(tb) & CF_USE_ICOUNT) {
assert(icount_enabled());
/*
* Reset the cycle counter to the start of the block and
* shift if to the number of actually executed instructions.
*/
cpu->neg.icount_decr.u16.low += insns_left;
}
cpu->cc->tcg_ops->restore_state_to_opc(cpu, tb, data);
}
bool cpu_restore_state(CPUState *cpu, uintptr_t host_pc)
{
/*
* The host_pc has to be in the rx region of the code buffer.
* If it is not we will not be able to resolve it here.
* The two cases where host_pc will not be correct are:
*
* - fault during translation (instruction fetch)
* - fault from helper (not using GETPC() macro)
*
* Either way we need return early as we can't resolve it here.
*/
if (in_code_gen_buffer((const void *)(host_pc - tcg_splitwx_diff))) {
TranslationBlock *tb = tcg_tb_lookup(host_pc);
if (tb) {
cpu_restore_state_from_tb(cpu, tb, host_pc);
return true;
}
}
return false;
}
bool cpu_unwind_state_data(CPUState *cpu, uintptr_t host_pc, uint64_t *data)
{
if (in_code_gen_buffer((const void *)(host_pc - tcg_splitwx_diff))) {
TranslationBlock *tb = tcg_tb_lookup(host_pc);
if (tb) {
return cpu_unwind_data_from_tb(tb, host_pc, data) >= 0;
}
}
return false;
}
void page_init(void)
{
page_table_config_init();
}
/*
* Isolate the portion of code gen which can setjmp/longjmp.
* Return the size of the generated code, or negative on error.
*/
static int setjmp_gen_code(CPUArchState *env, TranslationBlock *tb,
vaddr pc, void *host_pc,
int *max_insns, int64_t *ti)
{
int ret = sigsetjmp(tcg_ctx->jmp_trans, 0);
if (unlikely(ret != 0)) {
return ret;
}
tcg_func_start(tcg_ctx);
CPUState *cs = env_cpu(env);
tcg_ctx->cpu = cs;
cs->cc->tcg_ops->translate_code(cs, tb, max_insns, pc, host_pc);
assert(tb->size != 0);
tcg_ctx->cpu = NULL;
*max_insns = tb->icount;
return tcg_gen_code(tcg_ctx, tb, pc);
}
/* Called with mmap_lock held for user mode emulation. */
TranslationBlock *tb_gen_code(CPUState *cpu, TCGTBCPUState s)
{
CPUArchState *env = cpu_env(cpu);
TranslationBlock *tb, *existing_tb;
tb_page_addr_t phys_pc, phys_p2;
tcg_insn_unit *gen_code_buf;
int gen_code_size, search_size, max_insns;
int64_t ti;
void *host_pc;
assert_memory_lock();
qemu_thread_jit_write();
phys_pc = get_page_addr_code_hostp(env, s.pc, &host_pc);
if (phys_pc == -1) {
/* Generate a one-shot TB with 1 insn in it */
s.cflags = (s.cflags & ~CF_COUNT_MASK) | 1;
}
max_insns = s.cflags & CF_COUNT_MASK;
if (max_insns == 0) {
max_insns = TCG_MAX_INSNS;
}
QEMU_BUILD_BUG_ON(CF_COUNT_MASK + 1 != TCG_MAX_INSNS);
buffer_overflow:
assert_no_pages_locked();
tb = tcg_tb_alloc(tcg_ctx);
if (unlikely(!tb)) {
/* flush must be done */
if (cpu_in_serial_context(cpu)) {
trace_tb_gen_code_buffer_overflow("tcg_tb_alloc");
tb_flush__exclusive_or_serial();
goto buffer_overflow;
}
queue_tb_flush(cpu);
mmap_unlock();
/* Make the execution loop process the flush as soon as possible. */
cpu->exception_index = EXCP_INTERRUPT;
cpu_loop_exit(cpu);
}
gen_code_buf = tcg_ctx->code_gen_ptr;
tb->tc.ptr = tcg_splitwx_to_rx(gen_code_buf);
if (!(s.cflags & CF_PCREL)) {
tb->pc = s.pc;
}
tb->cs_base = s.cs_base;
tb->flags = s.flags;
tb->cflags = s.cflags;
tb_set_page_addr0(tb, phys_pc);
tb_set_page_addr1(tb, -1);
if (phys_pc != -1) {
tb_lock_page0(phys_pc);
}
tcg_ctx->gen_tb = tb;
tcg_ctx->guest_mo = cpu->cc->tcg_ops->guest_default_memory_order;
restart_translate:
trace_translate_block(tb, s.pc, tb->tc.ptr);
gen_code_size = setjmp_gen_code(env, tb, s.pc, host_pc, &max_insns, &ti);
if (unlikely(gen_code_size < 0)) {
switch (gen_code_size) {
case -1:
trace_tb_gen_code_buffer_overflow("setjmp_gen_code");
/*
* Overflow of code_gen_buffer, or the current slice of it.
*
* TODO: We don't need to re-do tcg_ops->translate_code, nor
* should we re-do the tcg optimization currently hidden
* inside tcg_gen_code. All that should be required is to
* flush the TBs, allocate a new TB, re-initialize it per
* above, and re-do the actual code generation.
*/
qemu_log_mask(CPU_LOG_TB_OP | CPU_LOG_TB_OP_OPT,
"Restarting code generation for "
"code_gen_buffer overflow\n");
tb_unlock_pages(tb);
tcg_ctx->gen_tb = NULL;
goto buffer_overflow;
case -2:
/*
* The code generated for the TranslationBlock is too large.
* The maximum size allowed by the unwind info is 64k.
* There may be stricter constraints from relocations
* in the tcg backend.
*
* Try again with half as many insns as we attempted this time.
* If a single insn overflows, there's a bug somewhere...
*/
assert(max_insns > 1);
max_insns /= 2;
qemu_log_mask(CPU_LOG_TB_OP | CPU_LOG_TB_OP_OPT,
"Restarting code generation with "
"smaller translation block (max %d insns)\n",
max_insns);
/*
* The half-sized TB may not cross pages.
* TODO: Fix all targets that cross pages except with
* the first insn, at which point this can't be reached.
*/
phys_p2 = tb_page_addr1(tb);
if (unlikely(phys_p2 != -1)) {
tb_unlock_page1(phys_pc, phys_p2);
tb_set_page_addr1(tb, -1);
}
goto restart_translate;
case -3:
/*
* We had a page lock ordering problem. In order to avoid
* deadlock we had to drop the lock on page0, which means
* that everything we translated so far is compromised.
* Restart with locks held on both pages.
*/
qemu_log_mask(CPU_LOG_TB_OP | CPU_LOG_TB_OP_OPT,
"Restarting code generation with re-locked pages");
goto restart_translate;
default:
g_assert_not_reached();
}
}
tcg_ctx->gen_tb = NULL;
search_size = encode_search(tb, (void *)gen_code_buf + gen_code_size);
if (unlikely(search_size < 0)) {
trace_tb_gen_code_buffer_overflow("encode_search");
tb_unlock_pages(tb);
goto buffer_overflow;
}
tb->tc.size = gen_code_size;
/*
* For CF_PCREL, attribute all executions of the generated code
* to its first mapping.
*/
perf_report_code(s.pc, tb, tcg_splitwx_to_rx(gen_code_buf));
if (qemu_loglevel_mask(CPU_LOG_TB_OUT_ASM) &&
qemu_log_in_addr_range(s.pc)) {
FILE *logfile = qemu_log_trylock();
if (logfile) {
int code_size, data_size;
const tcg_target_ulong *rx_data_gen_ptr;
size_t chunk_start;
int insn = 0;
if (tcg_ctx->data_gen_ptr) {
rx_data_gen_ptr = tcg_splitwx_to_rx(tcg_ctx->data_gen_ptr);
code_size = (const void *)rx_data_gen_ptr - tb->tc.ptr;
data_size = gen_code_size - code_size;
} else {
rx_data_gen_ptr = 0;
code_size = gen_code_size;
data_size = 0;
}
/* Dump header and the first instruction */
fprintf(logfile, "OUT: [size=%d]\n", gen_code_size);
fprintf(logfile,
" -- guest addr 0x%016" PRIx64 " + tb prologue\n",
tcg_ctx->gen_insn_data[insn * INSN_START_WORDS]);
chunk_start = tcg_ctx->gen_insn_end_off[insn];
disas(logfile, tb->tc.ptr, chunk_start);
/*
* Dump each instruction chunk, wrapping up empty chunks into
* the next instruction. The whole array is offset so the
* first entry is the beginning of the 2nd instruction.
*/
while (insn < tb->icount) {
size_t chunk_end = tcg_ctx->gen_insn_end_off[insn];
if (chunk_end > chunk_start) {
fprintf(logfile, " -- guest addr 0x%016" PRIx64 "\n",
tcg_ctx->gen_insn_data[insn * INSN_START_WORDS]);
disas(logfile, tb->tc.ptr + chunk_start,
chunk_end - chunk_start);
chunk_start = chunk_end;
}
insn++;
}
if (chunk_start < code_size) {
fprintf(logfile, " -- tb slow paths + alignment\n");
disas(logfile, tb->tc.ptr + chunk_start,
code_size - chunk_start);
}
/* Finally dump any data we may have after the block */
if (data_size) {
int i;
fprintf(logfile, " data: [size=%d]\n", data_size);
for (i = 0; i < data_size / sizeof(tcg_target_ulong); i++) {
if (sizeof(tcg_target_ulong) == 8) {
fprintf(logfile,
"0x%08" PRIxPTR ": .quad 0x%016" TCG_PRIlx "\n",
(uintptr_t)&rx_data_gen_ptr[i], rx_data_gen_ptr[i]);
} else if (sizeof(tcg_target_ulong) == 4) {
fprintf(logfile,
"0x%08" PRIxPTR ": .long 0x%08" TCG_PRIlx "\n",
(uintptr_t)&rx_data_gen_ptr[i], rx_data_gen_ptr[i]);
} else {
qemu_build_not_reached();
}
}
}
fprintf(logfile, "\n");
qemu_log_unlock(logfile);
}
}
qatomic_set(&tcg_ctx->code_gen_ptr, (void *)
ROUND_UP((uintptr_t)gen_code_buf + gen_code_size + search_size,
CODE_GEN_ALIGN));
/* init jump list */
qemu_spin_init(&tb->jmp_lock);
tb->jmp_list_head = (uintptr_t)NULL;
tb->jmp_list_next[0] = (uintptr_t)NULL;
tb->jmp_list_next[1] = (uintptr_t)NULL;
tb->jmp_dest[0] = (uintptr_t)NULL;
tb->jmp_dest[1] = (uintptr_t)NULL;
/* init original jump addresses which have been set during tcg_gen_code() */
if (tb->jmp_reset_offset[0] != TB_JMP_OFFSET_INVALID) {
tb_reset_jump(tb, 0);
}
if (tb->jmp_reset_offset[1] != TB_JMP_OFFSET_INVALID) {
tb_reset_jump(tb, 1);
}
/*
* Insert TB into the corresponding region tree before publishing it
* through QHT. Otherwise rewinding happened in the TB might fail to
* lookup itself using host PC.
*/
tcg_tb_insert(tb);
/*
* If the TB is not associated with a physical RAM page then it must be
* a temporary one-insn TB.
*
* Such TBs must be added to region trees in order to make sure that
* restore_state_to_opc() - which on some architectures is not limited to
* rewinding, but also affects exception handling! - is called when such a
* TB causes an exception.
*
* At the same time, temporary one-insn TBs must be executed at most once,
* because subsequent reads from, e.g., I/O memory may return different
* values. So return early before attempting to link to other TBs or add
* to the QHT.
*/
if (tb_page_addr0(tb) == -1) {
assert_no_pages_locked();
return tb;
}
/*
* No explicit memory barrier is required -- tb_link_page() makes the
* TB visible in a consistent state.
*/
existing_tb = tb_link_page(tb);
assert_no_pages_locked();
/* if the TB already exists, discard what we just translated */
if (unlikely(existing_tb != tb)) {
uintptr_t orig_aligned = (uintptr_t)gen_code_buf;
orig_aligned -= ROUND_UP(sizeof(*tb), qemu_icache_linesize);
qatomic_set(&tcg_ctx->code_gen_ptr, (void *)orig_aligned);
tcg_tb_remove(tb);
return existing_tb;
}
return tb;
}
/* user-mode: call with mmap_lock held */
void tb_check_watchpoint(CPUState *cpu, uintptr_t retaddr)
{
TranslationBlock *tb;
assert_memory_lock();
tb = tcg_tb_lookup(retaddr);
if (tb) {
/* We can use retranslation to find the PC. */
cpu_restore_state_from_tb(cpu, tb, retaddr);
tb_phys_invalidate(tb, -1);
} else {
/* The exception probably happened in a helper. The CPU state should
have been saved before calling it. Fetch the PC from there. */
CPUArchState *env = cpu_env(cpu);
TCGTBCPUState s = cpu->cc->tcg_ops->get_tb_cpu_state(cpu);
tb_page_addr_t addr = get_page_addr_code(env, s.pc);
if (addr != -1) {
tb_invalidate_phys_range(cpu, addr, addr);
}
}
}
#ifndef CONFIG_USER_ONLY
/*
* In deterministic execution mode, instructions doing device I/Os
* must be at the end of the TB.
*
* Called by softmmu_template.h, with iothread mutex not held.
*/
void cpu_io_recompile(CPUState *cpu, uintptr_t retaddr)
{
TranslationBlock *tb;
const CPUClass *cc = cpu->cc;
uint32_t n;
tb = tcg_tb_lookup(retaddr);
if (!tb) {
cpu_abort(cpu, "cpu_io_recompile: could not find TB for pc=%p",
(void *)retaddr);
}
cpu_restore_state_from_tb(cpu, tb, retaddr);
/*
* Some guests must re-execute the branch when re-executing a delay
* slot instruction. When this is the case, adjust icount and N
* to account for the re-execution of the branch.
*/
n = 1;
if (cc->tcg_ops->io_recompile_replay_branch &&
cc->tcg_ops->io_recompile_replay_branch(cpu, tb)) {
cpu->neg.icount_decr.u16.low++;
n = 2;
}
/*
* Exit the loop and potentially generate a new TB executing the
* just the I/O insns. We also limit instrumentation to memory
* operations only (which execute after completion) so we don't
* double instrument the instruction. Also don't let an IRQ sneak
* in before we execute it.
*/
cpu->cflags_next_tb = curr_cflags(cpu) | CF_MEMI_ONLY | CF_NOIRQ | n;
if (qemu_loglevel_mask(CPU_LOG_EXEC)) {
vaddr pc = cpu->cc->get_pc(cpu);
if (qemu_log_in_addr_range(pc)) {
qemu_log("cpu_io_recompile: rewound execution of TB to %016"
VADDR_PRIx "\n", pc);
}
}
cpu_loop_exit_noexc(cpu);
}
#endif /* CONFIG_USER_ONLY */
/*
* Called by generic code at e.g. cpu reset after cpu creation,
* therefore we must be prepared to allocate the jump cache.
*/
void tcg_flush_jmp_cache(CPUState *cpu)
{
CPUJumpCache *jc = cpu->tb_jmp_cache;
/* During early initialization, the cache may not yet be allocated. */
if (unlikely(jc == NULL)) {
return;
}
for (int i = 0; i < TB_JMP_CACHE_SIZE; i++) {
qatomic_set(&jc->array[i].tb, NULL);
}
}
+530
View File
@@ -0,0 +1,530 @@
/*
* Generic intermediate code generation.
*
* Copyright (C) 2016-2017 Lluís Vilanova <[email protected]>
*
* 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/bswap.h"
#include "qemu/log.h"
#include "qemu/error-report.h"
#include "accel/tcg/cpu-ldst-common.h"
#include "accel/tcg/cpu-mmu-index.h"
#include "exec/target_page.h"
#include "exec/translator.h"
#include "exec/plugin-gen.h"
#include "tcg/tcg-op-common.h"
#include "internal-common.h"
#include "disas/disas.h"
#include "tb-internal.h"
static void set_can_do_io(DisasContextBase *db, bool val)
{
QEMU_BUILD_BUG_ON(sizeof_field(CPUState, neg.can_do_io) != 1);
tcg_gen_st8_i32(tcg_constant_i32(val), tcg_env,
offsetof(CPUState, neg.can_do_io) - sizeof(CPUState));
}
bool translator_io_start(DisasContextBase *db)
{
/*
* Ensure that this instruction will be the last in the TB.
* The target may override this to something more forceful.
*/
if (db->is_jmp == DISAS_NEXT) {
db->is_jmp = DISAS_TOO_MANY;
}
return true;
}
static TCGOp *gen_tb_start(DisasContextBase *db, uint32_t cflags)
{
TCGv_i32 count = NULL;
TCGOp *icount_start_insn = NULL;
if ((cflags & CF_USE_ICOUNT) || !(cflags & CF_NOIRQ)) {
count = tcg_temp_new_i32();
tcg_gen_ld_i32(count, tcg_env,
offsetof(CPUState, neg.icount_decr.u32) -
sizeof(CPUState));
}
if (cflags & CF_USE_ICOUNT) {
/*
* We emit a sub with a dummy immediate argument. Keep the insn index
* of the sub so that we later (when we know the actual insn count)
* can update the argument with the actual insn count.
*/
tcg_gen_sub_i32(count, count, tcg_constant_i32(0));
icount_start_insn = tcg_last_op();
}
/*
* Emit the check against icount_decr.u32 to see if we should exit
* unless we suppress the check with CF_NOIRQ. If we are using
* icount and have suppressed interruption the higher level code
* should have ensured we don't run more instructions than the
* budget.
*/
if (cflags & CF_NOIRQ) {
tcg_ctx->exitreq_label = NULL;
} else {
tcg_ctx->exitreq_label = gen_new_label();
tcg_gen_brcondi_i32(TCG_COND_LT, count, 0, tcg_ctx->exitreq_label);
}
if (cflags & CF_USE_ICOUNT) {
tcg_gen_st16_i32(count, tcg_env,
offsetof(CPUState, neg.icount_decr.u16.low) -
sizeof(CPUState));
}
return icount_start_insn;
}
static void gen_tb_end(const TranslationBlock *tb, uint32_t cflags,
TCGOp *icount_start_insn, int num_insns)
{
if (cflags & CF_USE_ICOUNT) {
/*
* Update the num_insn immediate parameter now that we know
* the actual insn count.
*/
tcg_set_insn_param(icount_start_insn, 2,
tcgv_i32_arg(tcg_constant_i32(num_insns)));
}
if (tcg_ctx->exitreq_label) {
gen_set_label(tcg_ctx->exitreq_label);
tcg_gen_exit_tb(tb, TB_EXIT_REQUESTED);
}
}
bool translator_is_same_page(const DisasContextBase *db, vaddr addr)
{
return ((addr ^ db->pc_first) & TARGET_PAGE_MASK) == 0;
}
bool translator_use_goto_tb(DisasContextBase *db, vaddr dest)
{
/* Suppress goto_tb if requested. */
if (tb_cflags(db->tb) & CF_NO_GOTO_TB) {
return false;
}
/* Check for the dest on the same page as the start of the TB. */
return translator_is_same_page(db, dest);
}
void translator_loop(CPUState *cpu, TranslationBlock *tb, int *max_insns,
vaddr pc, void *host_pc, const TranslatorOps *ops,
DisasContextBase *db, TCGType addr_type)
{
uint32_t cflags = tb_cflags(tb);
TCGOp *icount_start_insn;
TCGOp *first_insn_start = NULL;
bool plugin_enabled;
tcg_ctx->addr_type = addr_type;
/* Initialize DisasContext */
db->tb = tb;
db->pc_first = pc;
db->pc_next = pc;
db->is_jmp = DISAS_NEXT;
db->num_insns = 0;
db->max_insns = *max_insns;
db->insn_start = NULL;
db->fake_insn = false;
db->host_addr[0] = host_pc;
db->host_addr[1] = NULL;
db->record_start = 0;
db->record_len = 0;
db->code_mmuidx = cpu_mmu_index(cpu, true);
ops->init_disas_context(db, cpu);
tcg_debug_assert(db->is_jmp == DISAS_NEXT); /* no early exit */
/* Start translating. */
icount_start_insn = gen_tb_start(db, cflags);
ops->tb_start(db, cpu);
tcg_debug_assert(db->is_jmp == DISAS_NEXT); /* no early exit */
plugin_enabled = plugin_gen_tb_start(cpu, db);
db->plugin_enabled = plugin_enabled;
while (true) {
*max_insns = ++db->num_insns;
ops->insn_start(db, cpu);
db->insn_start = tcg_last_op();
if (first_insn_start == NULL) {
first_insn_start = db->insn_start;
}
tcg_debug_assert(db->is_jmp == DISAS_NEXT); /* no early exit */
if (plugin_enabled) {
plugin_gen_insn_start(cpu, db);
}
/*
* Disassemble one instruction. The translate_insn hook should
* update db->pc_next and db->is_jmp to indicate what should be
* done next -- either exiting this loop or locate the start of
* the next instruction.
*/
ops->translate_insn(db, cpu);
/*
* We can't instrument after instructions that change control
* flow although this only really affects post-load operations.
*
* Calling plugin_gen_insn_end() before we possibly stop translation
* is important. Even if this ends up as dead code, plugin generation
* needs to see a matching plugin_gen_insn_{start,end}() pair in order
* to accurately track instrumented helpers that might access memory.
*/
if (plugin_enabled) {
plugin_gen_insn_end();
}
/* Stop translation if translate_insn so indicated. */
if (db->is_jmp != DISAS_NEXT) {
break;
}
/* Stop translation if the output buffer is full,
or we have executed all of the allowed instructions. */
if (tcg_op_buf_full() || db->num_insns >= db->max_insns) {
db->is_jmp = DISAS_TOO_MANY;
break;
}
}
/* Emit code to exit the TB, as indicated by db->is_jmp. */
ops->tb_stop(db, cpu);
gen_tb_end(tb, cflags, icount_start_insn, db->num_insns);
/*
* Manage can_do_io for the translation block: set to false before
* the first insn and set to true before the last insn.
*/
if (db->num_insns == 1) {
tcg_debug_assert(first_insn_start == db->insn_start);
} else {
tcg_debug_assert(first_insn_start != db->insn_start);
tcg_ctx->emit_before_op = first_insn_start;
set_can_do_io(db, false);
}
tcg_ctx->emit_before_op = db->insn_start;
set_can_do_io(db, true);
tcg_ctx->emit_before_op = NULL;
/* May be used by disas_log or plugin callbacks. */
tb->size = db->pc_next - db->pc_first;
tb->icount = db->num_insns;
if (plugin_enabled) {
plugin_gen_tb_end(cpu, db->num_insns);
}
if (qemu_loglevel_mask(CPU_LOG_TB_IN_ASM)
&& qemu_log_in_addr_range(db->pc_first)) {
FILE *logfile = qemu_log_trylock();
if (logfile) {
fprintf(logfile, "----------------\n");
if (!ops->disas_log ||
!ops->disas_log(db, cpu, logfile)) {
fprintf(logfile, "IN: %s\n", lookup_symbol(db->pc_first));
target_disas(logfile, cpu, db);
}
fprintf(logfile, "\n");
qemu_log_unlock(logfile);
}
}
}
static bool translator_ld(CPUArchState *env, DisasContextBase *db,
void *dest, vaddr pc, size_t len)
{
TranslationBlock *tb = db->tb;
vaddr last = pc + len - 1;
void *host;
vaddr base;
/* Use slow path if first page is MMIO. */
if (unlikely(tb_page_addr0(tb) == -1)) {
/* We capped translation with first page MMIO in tb_gen_code. */
tcg_debug_assert(db->max_insns == 1);
return false;
}
host = db->host_addr[0];
base = db->pc_first;
if (likely(((base ^ last) & TARGET_PAGE_MASK) == 0)) {
/* Entire read is from the first page. */
goto do_read;
}
if (unlikely(((base ^ pc) & TARGET_PAGE_MASK) == 0)) {
/*
* Read begins on the first page and extends to the second.
* The unaligned read is never atomic.
*/
size_t len0 = -(pc | TARGET_PAGE_MASK);
memcpy(dest, host + (pc - base), len0);
pc += len0;
dest += len0;
len -= len0;
}
/*
* The read must conclude on the second page and not extend to a third.
*
* TODO: We could allow the two pages to be virtually discontiguous,
* since we already allow the two pages to be physically discontiguous.
* The only reasonable use case would be executing an insn at the end
* of the address space wrapping around to the beginning. For that,
* we would need to know the current width of the address space.
* In the meantime, assert.
*/
base = (base & TARGET_PAGE_MASK) + TARGET_PAGE_SIZE;
assert(((base ^ pc) & TARGET_PAGE_MASK) == 0);
assert(((base ^ last) & TARGET_PAGE_MASK) == 0);
host = db->host_addr[1];
if (host == NULL) {
tb_page_addr_t page0, old_page1, new_page1;
new_page1 = get_page_addr_code_hostp(env, base, &db->host_addr[1]);
/*
* If the second page is MMIO, treat as if the first page
* was MMIO as well, so that we do not cache the TB.
*/
if (unlikely(new_page1 == -1)) {
tb_unlock_pages(tb);
tb_set_page_addr0(tb, -1);
/* Require that this be the final insn. */
db->max_insns = db->num_insns;
return false;
}
/*
* If this is not the first time around, and page1 matches,
* then we already have the page locked. Alternately, we're
* not doing anything to prevent the PTE from changing, so
* we might wind up with a different page, requiring us to
* re-do the locking.
*/
old_page1 = tb_page_addr1(tb);
if (likely(new_page1 != old_page1)) {
page0 = tb_page_addr0(tb);
if (unlikely(old_page1 != -1)) {
tb_unlock_page1(page0, old_page1);
}
tb_set_page_addr1(tb, new_page1);
tb_lock_page1(page0, new_page1);
}
host = db->host_addr[1];
}
do_read:
/*
* Assume aligned reads should be atomic, if possible.
* We're not in a position to jump out with EXCP_ATOMIC.
*/
host += pc - base;
switch (len) {
case 2:
if (QEMU_IS_ALIGNED(pc, 2)) {
uint16_t t = qatomic_read((uint16_t *)host);
stw_he_p(dest, t);
return true;
}
break;
case 4:
if (QEMU_IS_ALIGNED(pc, 4)) {
uint32_t t = qatomic_read((uint32_t *)host);
stl_he_p(dest, t);
return true;
}
break;
case 8:
if (QEMU_IS_ALIGNED(pc, 8)) {
uint64_t t = qatomic_read((uint64_t *)host);
stq_he_p(dest, t);
return true;
}
break;
}
/* Unaligned or partial read from the second page is not atomic. */
memcpy(dest, host, len);
return true;
}
static void record_save(DisasContextBase *db, vaddr pc,
const void *from, int size)
{
int offset;
/* Do not record probes before the start of TB. */
if (pc < db->pc_first) {
return;
}
/*
* In translator_access, we verified that pc is within 2 pages
* of pc_first, thus this will never overflow.
*/
offset = pc - db->pc_first;
/*
* Either the first or second page may be I/O. If it is the second,
* then the first byte we need to record will be at a non-zero offset.
* In either case, we should not need to record but a single insn.
*
* A read may re-read bytes that are already recorded: a target may
* fetch a whole aligned word to decode an insn (e.g. riscv Ziccif),
* then probe the following insn, which lies within that same word.
* Such a read extends the record only by the bytes past its end.
*/
if (db->record_len == 0) {
db->record_start = offset;
db->record_len = size;
} else {
int end = offset - db->record_start + size;
assert(offset >= db->record_start);
assert(offset <= db->record_start + db->record_len);
assert(end <= sizeof(db->record));
db->record_len = MAX(db->record_len, end);
}
memcpy(db->record + (offset - db->record_start), from, size);
}
size_t translator_st_len(const DisasContextBase *db)
{
return db->fake_insn ? db->record_len : db->tb->size;
}
bool translator_st(const DisasContextBase *db, void *dest,
vaddr addr, size_t len)
{
size_t offset, offset_end;
if (addr < db->pc_first) {
return false;
}
offset = addr - db->pc_first;
offset_end = offset + len;
if (offset_end > translator_st_len(db)) {
return false;
}
if (!db->fake_insn) {
size_t offset_page1 = -(db->pc_first | TARGET_PAGE_MASK);
/* Get all the bytes from the first page. */
if (db->host_addr[0]) {
if (offset_end <= offset_page1) {
memcpy(dest, db->host_addr[0] + offset, len);
return true;
}
if (offset < offset_page1) {
size_t len0 = offset_page1 - offset;
memcpy(dest, db->host_addr[0] + offset, len0);
offset += len0;
dest += len0;
}
}
/* Get any bytes from the second page. */
if (db->host_addr[1] && offset >= offset_page1) {
memcpy(dest, db->host_addr[1] + (offset - offset_page1),
offset_end - offset);
return true;
}
}
/* Else get recorded bytes. */
if (db->record_len != 0 &&
offset >= db->record_start &&
offset_end <= db->record_start + db->record_len) {
memcpy(dest, db->record + (offset - db->record_start),
offset_end - offset);
return true;
}
return false;
}
uint8_t translator_ldub(CPUArchState *env, DisasContextBase *db, vaddr pc)
{
uint8_t val;
if (!translator_ld(env, db, &val, pc, sizeof(val))) {
MemOpIdx oi = make_memop_idx(MO_UB, db->code_mmuidx);
val = cpu_ldb_code_mmu(env, pc, oi, 0);
record_save(db, pc, &val, sizeof(val));
}
return val;
}
uint16_t translator_lduw_end(CPUArchState *env, DisasContextBase *db,
vaddr pc, MemOp endian)
{
uint16_t val;
if (!translator_ld(env, db, &val, pc, sizeof(val))) {
MemOpIdx oi = make_memop_idx(MO_UW, db->code_mmuidx);
val = cpu_ldw_code_mmu(env, pc, oi, 0);
record_save(db, pc, &val, sizeof(val));
}
if (endian & MO_BSWAP) {
val = bswap16(val);
}
return val;
}
uint32_t translator_ldl_end(CPUArchState *env, DisasContextBase *db,
vaddr pc, MemOp endian)
{
uint32_t val;
if (!translator_ld(env, db, &val, pc, sizeof(val))) {
MemOpIdx oi = make_memop_idx(MO_UL, db->code_mmuidx);
val = cpu_ldl_code_mmu(env, pc, oi, 0);
record_save(db, pc, &val, sizeof(val));
}
if (endian & MO_BSWAP) {
val = bswap32(val);
}
return val;
}
uint64_t translator_ldq_end(CPUArchState *env, DisasContextBase *db,
vaddr pc, MemOp endian)
{
uint64_t val;
if (!translator_ld(env, db, &val, pc, sizeof(val))) {
MemOpIdx oi = make_memop_idx(MO_UQ, db->code_mmuidx);
val = cpu_ldq_code_mmu(env, pc, oi, 0);
record_save(db, pc, &val, sizeof(val));
}
if (endian & MO_BSWAP) {
val = bswap64(val);
}
return val;
}
void translator_fake_ld(DisasContextBase *db, const void *data, size_t len)
{
db->fake_insn = true;
record_save(db, db->pc_first, data, len);
}
+85
View File
@@ -0,0 +1,85 @@
#include "qemu/osdep.h"
#include "hw/core/cpu.h"
#include "accel/tcg/cpu-ops.h"
#include "exec/replay-core.h"
#include "exec/watchpoint.h"
#include "internal-common.h"
void cpu_resume(CPUState *cpu)
{
}
void cpu_remove_sync(CPUState *cpu)
{
}
void qemu_init_vcpu(CPUState *cpu)
{
}
void cpu_exec_reset_hold(CPUState *cpu)
{
}
int cpu_watchpoint_insert(CPUState *cpu, vaddr addr, vaddr len,
int flags, CPUWatchpoint **watchpoint)
{
return -ENOSYS;
}
int cpu_watchpoint_remove(CPUState *cpu, vaddr addr,
vaddr len, int flags)
{
return -ENOSYS;
}
void cpu_watchpoint_remove_by_ref(CPUState *cpu, CPUWatchpoint *wp)
{
}
void cpu_watchpoint_remove_all(CPUState *cpu, int mask)
{
}
int cpu_watchpoint_address_matches(CPUState *cpu, vaddr addr, vaddr len)
{
return 0;
}
void cpu_check_watchpoint(CPUState *cpu, vaddr addr, vaddr len,
MemTxAttrs atr, int fl, uintptr_t ra)
{
}
/* User mode emulation does not support softmmu yet. */
void tlb_init(CPUState *cpu)
{
}
void tlb_destroy(CPUState *cpu)
{
}
/* User mode emulation does not support record/replay yet. */
bool replay_exception(void)
{
return true;
}
bool replay_has_exception(void)
{
return false;
}
bool replay_interrupt(void)
{
return true;
}
bool replay_has_interrupt(void)
{
return false;
}
File diff suppressed because it is too large Load Diff
+23
View File
@@ -0,0 +1,23 @@
/*
* TaskState helpers for QEMU
*
* Copyright (c) 2023 Linaro Ltd.
*
* Authors:
* Philippe Mathieu-Daudé
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef ACCEL_TCG_VCPU_STATE_H
#define ACCEL_TCG_VCPU_STATE_H
#include "hw/core/cpu.h"
#ifdef CONFIG_USER_ONLY
static inline TaskState *get_task_state(const CPUState *cs)
{
return cs->opaque;
}
#endif
#endif
+142
View File
@@ -0,0 +1,142 @@
/*
* CPU watchpoints
*
* Copyright (c) 2003 Fabrice Bellard
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "qemu/main-loop.h"
#include "exec/breakpoint.h"
#include "exec/cpu-interrupt.h"
#include "exec/page-protection.h"
#include "exec/translation-block.h"
#include "system/tcg.h"
#include "system/replay.h"
#include "accel/tcg/cpu-loop.h"
#include "accel/tcg/cpu-ops.h"
#include "hw/core/cpu.h"
#include "internal-common.h"
/*
* Return true if this watchpoint address matches the specified
* access (ie the address range covered by the watchpoint overlaps
* partially or completely with the address range covered by the
* access).
*/
static inline bool watchpoint_address_matches(CPUWatchpoint *wp,
vaddr addr, vaddr len)
{
/*
* We know the lengths are non-zero, but a little caution is
* required to avoid errors in the case where the range ends
* exactly at the top of the address space and so addr + len
* wraps round to zero.
*/
vaddr wpend = wp->vaddr + wp->len - 1;
vaddr addrend = addr + len - 1;
return !(addr > wpend || wp->vaddr > addrend);
}
/* Return flags for watchpoints that match addr + prot. */
int cpu_watchpoint_address_matches(CPUState *cpu, vaddr addr, vaddr len)
{
CPUWatchpoint *wp;
int ret = 0;
QTAILQ_FOREACH(wp, &cpu->watchpoints, entry) {
if (watchpoint_address_matches(wp, addr, len)) {
ret |= wp->flags;
}
}
return ret;
}
/* Generate a debug exception if a watchpoint has been hit. */
void cpu_check_watchpoint(CPUState *cpu, vaddr addr, vaddr len,
MemTxAttrs attrs, int flags, uintptr_t ra)
{
CPUWatchpoint *wp;
assert(tcg_enabled());
if (cpu->watchpoint_hit) {
/*
* We re-entered the check after replacing the TB.
* Now raise the debug interrupt so that it will
* trigger after the current instruction.
*/
bql_lock();
cpu_interrupt(cpu, CPU_INTERRUPT_DEBUG);
bql_unlock();
return;
}
if (cpu->cc->tcg_ops->adjust_watchpoint_address) {
/* this is currently used only by ARM BE32 */
addr = cpu->cc->tcg_ops->adjust_watchpoint_address(cpu, addr, len);
}
assert((flags & ~BP_MEM_ACCESS) == 0);
QTAILQ_FOREACH(wp, &cpu->watchpoints, entry) {
int hit_flags = wp->flags & flags;
if (hit_flags && watchpoint_address_matches(wp, addr, len)) {
if (replay_running_debug()) {
/*
* replay_breakpoint reads icount.
* Force recompile to succeed, because icount may
* be read only at the end of the block.
*/
if (!cpu->neg.can_do_io) {
/* Force execution of one insn next time. */
cpu->cflags_next_tb = 1 | CF_NOIRQ | curr_cflags(cpu);
cpu_loop_exit_restore(cpu, ra);
}
/*
* Don't process the watchpoints when we are
* in a reverse debugging operation.
*/
replay_breakpoint();
return;
}
wp->flags |= hit_flags << BP_HIT_SHIFT;
wp->hitaddr = MAX(addr, wp->vaddr);
wp->hitattrs = attrs;
if (wp->flags & BP_CPU
&& cpu->cc->tcg_ops->debug_check_watchpoint
&& !cpu->cc->tcg_ops->debug_check_watchpoint(cpu, wp)) {
wp->flags &= ~BP_WATCHPOINT_HIT;
continue;
}
cpu->watchpoint_hit = wp;
/* This call also restores vCPU state */
tb_check_watchpoint(cpu, ra);
if (wp->flags & BP_STOP_BEFORE_ACCESS) {
cpu->exception_index = EXCP_DEBUG;
cpu_loop_exit(cpu);
} else {
/* Force execution of one insn next time. */
cpu->cflags_next_tb = 1 | CF_NOIRQ | curr_cflags(cpu);
cpu_loop_exit_noexc(cpu);
}
} else {
wp->flags &= ~BP_WATCHPOINT_HIT;
}
}
}
+7
View File
@@ -0,0 +1,7 @@
whpx_ss = ss.source_set()
whpx_ss.add(files(
'whpx-accel-ops.c',
'whpx-common.c'
))
specific_ss.add_all(when: 'CONFIG_WHPX', if_true: whpx_ss)
+113
View File
@@ -0,0 +1,113 @@
/*
* QEMU Windows Hypervisor Platform accelerator (WHPX)
*
* Copyright Microsoft Corp. 2017
*
* 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 "system/kvm_int.h"
#include "qemu/main-loop.h"
#include "accel/accel-cpu-ops.h"
#include "system/cpus.h"
#include "qemu/guest-random.h"
#include "system/whpx.h"
#include "system/whpx-internal.h"
#include "system/whpx-all.h"
#include "system/whpx-accel-ops.h"
static void *whpx_cpu_thread_fn(void *arg)
{
CPUState *cpu = arg;
int r;
rcu_register_thread();
bql_lock();
qemu_thread_get_self(cpu->thread);
cpu->thread_id = qemu_get_thread_id();
current_cpu = cpu;
r = whpx_init_vcpu(cpu);
if (r < 0) {
fprintf(stderr, "whpx_init_vcpu failed: %s\n", strerror(-r));
exit(1);
}
/* signal CPU creation */
cpu_thread_signal_created(cpu);
qemu_guest_random_seed_thread_part2(cpu->random_seed);
do {
qemu_process_cpu_events(cpu);
if (cpu_can_run(cpu)) {
r = whpx_vcpu_exec(cpu);
if (r == EXCP_DEBUG) {
cpu_handle_guest_debug(cpu);
}
}
} while (!cpu->unplug || cpu_can_run(cpu));
whpx_destroy_vcpu(cpu);
cpu_thread_signal_destroyed(cpu);
bql_unlock();
rcu_unregister_thread();
return NULL;
}
static void whpx_start_vcpu_thread(CPUState *cpu)
{
char thread_name[VCPU_THREAD_NAME_SIZE];
snprintf(thread_name, VCPU_THREAD_NAME_SIZE, "CPU %d/WHPX",
cpu->cpu_index);
qemu_thread_create(cpu->thread, thread_name, whpx_cpu_thread_fn,
cpu, QEMU_THREAD_JOINABLE);
}
static void whpx_kick_vcpu_thread(CPUState *cpu)
{
if (!qemu_cpu_is_self(cpu)) {
whpx_vcpu_kick(cpu);
}
}
static bool whpx_vcpu_thread_is_idle(CPUState *cpu)
{
return !whpx_irqchip_in_kernel();
}
static void whpx_accel_ops_class_init(ObjectClass *oc, const void *data)
{
AccelOpsClass *ops = ACCEL_OPS_CLASS(oc);
ops->create_vcpu_thread = whpx_start_vcpu_thread;
ops->kick_vcpu_thread = whpx_kick_vcpu_thread;
ops->cpu_thread_is_idle = whpx_vcpu_thread_is_idle;
ops->handle_interrupt = generic_handle_interrupt;
ops->synchronize_post_reset = whpx_cpu_synchronize_post_reset;
ops->synchronize_post_init = whpx_cpu_synchronize_post_init;
ops->synchronize_state = whpx_cpu_synchronize_state;
ops->synchronize_pre_loadvm = whpx_cpu_synchronize_pre_loadvm;
}
static const TypeInfo whpx_accel_ops_type = {
.name = ACCEL_OPS_NAME("whpx"),
.parent = TYPE_ACCEL_OPS,
.class_init = whpx_accel_ops_class_init,
.abstract = true,
};
static void whpx_accel_ops_register_types(void)
{
type_register_static(&whpx_accel_ops_type);
}
type_init(whpx_accel_ops_register_types);
+596
View File
@@ -0,0 +1,596 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* QEMU Windows Hypervisor Platform accelerator (WHPX)
*
* Copyright Microsoft Corp. 2017
*/
#include "qemu/osdep.h"
#include "cpu.h"
#include "system/address-spaces.h"
#include "system/ioport.h"
#include "gdbstub/helpers.h"
#include "qemu/accel.h"
#include "accel/accel-ops.h"
#include "system/memory.h"
#include "system/whpx.h"
#include "system/cpus.h"
#include "system/runstate.h"
#include "qemu/main-loop.h"
#include "hw/core/boards.h"
#include "hw/intc/ioapic.h"
#include "qemu/error-report.h"
#include "qapi/error.h"
#include "qapi/qapi-types-common.h"
#include "qapi/qapi-visit-common.h"
#include "migration/blocker.h"
#include "accel/accel-cpu-target.h"
#include <winerror.h>
#include "system/whpx-internal.h"
#include "system/whpx-accel-ops.h"
#include "system/whpx-common.h"
#include "system/whpx-all.h"
#include <winhvplatform.h>
#include <winhvplatformdefs.h>
bool whpx_allowed;
bool whpx_irqchip_in_kernel;
static bool whp_dispatch_initialized;
static HMODULE hWinHvPlatform;
struct whpx_state whpx_global;
struct WHPDispatch whp_dispatch;
void whpx_flush_cpu_state(CPUState *cpu)
{
if (cpu->vcpu_dirty) {
whpx_set_registers(cpu, WHPX_LEVEL_RUNTIME_STATE);
cpu->vcpu_dirty = false;
}
}
void whpx_get_reg(CPUState *cpu, WHV_REGISTER_NAME reg, WHV_REGISTER_VALUE* val)
{
struct whpx_state *whpx = &whpx_global;
HRESULT hr;
whpx_flush_cpu_state(cpu);
hr = whp_dispatch.WHvGetVirtualProcessorRegisters(whpx->partition, cpu->cpu_index,
&reg, 1, val);
if (FAILED(hr)) {
error_report("WHPX: Failed to get register %08x, hr=%08lx", reg, hr);
}
}
void whpx_set_reg(CPUState *cpu, WHV_REGISTER_NAME reg, WHV_REGISTER_VALUE val)
{
struct whpx_state *whpx = &whpx_global;
HRESULT hr;
hr = whp_dispatch.WHvSetVirtualProcessorRegisters(whpx->partition, cpu->cpu_index,
&reg, 1, &val);
if (FAILED(hr)) {
error_report("WHPX: Failed to set register %08x, hr=%08lx", reg, hr);
}
}
/* Tries to find a breakpoint at the specified address. */
struct whpx_breakpoint *whpx_lookup_breakpoint_by_addr(uint64_t address)
{
struct whpx_state *whpx = &whpx_global;
int i;
if (whpx->breakpoints.breakpoints) {
for (i = 0; i < whpx->breakpoints.breakpoints->used; i++) {
if (address == whpx->breakpoints.breakpoints->data[i].address) {
return &whpx->breakpoints.breakpoints->data[i];
}
}
}
return NULL;
}
/*
* This function is called when the a VCPU is about to start and no other
* VCPUs have been started so far. Since the VCPU start order could be
* arbitrary, it doesn't have to be VCPU#0.
*
* It is used to commit the breakpoints into memory, and configure WHPX
* to intercept debug exceptions.
*
* Note that whpx_set_exception_exit_bitmap() cannot be called if one or
* more VCPUs are already running, so this is the best place to do it.
*/
int whpx_first_vcpu_starting(CPUState *cpu)
{
struct whpx_state *whpx = &whpx_global;
g_assert(bql_locked());
if (!QTAILQ_EMPTY(&cpu->breakpoints) ||
(whpx->breakpoints.breakpoints &&
whpx->breakpoints.breakpoints->used)) {
CPUBreakpoint *bp;
int i = 0;
bool update_pending = false;
QTAILQ_FOREACH(bp, &cpu->breakpoints, entry) {
if (i >= whpx->breakpoints.original_address_count ||
bp->pc != whpx->breakpoints.original_addresses[i]) {
update_pending = true;
}
i++;
}
if (i != whpx->breakpoints.original_address_count) {
update_pending = true;
}
if (update_pending) {
/*
* The CPU breakpoints have changed since the last call to
* whpx_translate_cpu_breakpoints(). WHPX breakpoints must
* now be recomputed.
*/
whpx_translate_cpu_breakpoints(&whpx->breakpoints, cpu, i);
}
/* Actually insert the breakpoints into the memory. */
whpx_apply_breakpoints(whpx->breakpoints.breakpoints, cpu, true);
}
HRESULT hr;
uint64_t exception_mask;
if (whpx->step_pending ||
(whpx->breakpoints.breakpoints &&
whpx->breakpoints.breakpoints->used)) {
/*
* We are either attempting to single-step one or more CPUs, or
* have one or more breakpoints enabled. Both require intercepting
* the WHvX64ExceptionTypeBreakpointTrap exception.
*/
exception_mask = 1UL << WHPX_INTERCEPT_DEBUG_TRAPS;
} else {
/* Let the guest handle all exceptions. */
exception_mask = 0;
}
hr = whpx_set_exception_exit_bitmap(exception_mask);
if (!SUCCEEDED(hr)) {
error_report("WHPX: Failed to update exception exit mask,"
"hr=%08lx.", hr);
return 1;
}
return 0;
}
/*
* This function is called when the last VCPU has finished running.
* It is used to remove any previously set breakpoints from memory.
*/
int whpx_last_vcpu_stopping(CPUState *cpu)
{
whpx_apply_breakpoints(whpx_global.breakpoints.breakpoints, cpu, false);
return 0;
}
static void do_whpx_cpu_synchronize_state(CPUState *cpu, run_on_cpu_data arg)
{
if (!cpu->vcpu_dirty) {
whpx_get_registers(cpu, WHPX_LEVEL_FULL_STATE);
cpu->vcpu_dirty = true;
}
}
static void do_whpx_cpu_synchronize_post_reset(CPUState *cpu,
run_on_cpu_data arg)
{
whpx_set_registers(cpu, WHPX_LEVEL_RESET_STATE);
cpu->vcpu_dirty = false;
}
static void do_whpx_cpu_synchronize_post_init(CPUState *cpu,
run_on_cpu_data arg)
{
whpx_set_registers(cpu, WHPX_LEVEL_FULL_STATE);
cpu->vcpu_dirty = false;
}
static void do_whpx_cpu_synchronize_pre_loadvm(CPUState *cpu,
run_on_cpu_data arg)
{
cpu->vcpu_dirty = true;
}
/*
* CPU support.
*/
void whpx_cpu_synchronize_state(CPUState *cpu)
{
if (!cpu->vcpu_dirty) {
run_on_cpu(cpu, do_whpx_cpu_synchronize_state, RUN_ON_CPU_NULL);
}
}
void whpx_cpu_synchronize_post_reset(CPUState *cpu)
{
run_on_cpu(cpu, do_whpx_cpu_synchronize_post_reset, RUN_ON_CPU_NULL);
}
void whpx_cpu_synchronize_post_init(CPUState *cpu)
{
run_on_cpu(cpu, do_whpx_cpu_synchronize_post_init, RUN_ON_CPU_NULL);
}
void whpx_cpu_synchronize_pre_loadvm(CPUState *cpu)
{
run_on_cpu(cpu, do_whpx_cpu_synchronize_pre_loadvm, RUN_ON_CPU_NULL);
}
static void whpx_pre_resume_vm(AccelState *as, bool step_pending)
{
whpx_global.step_pending = step_pending;
}
/*
* Vcpu support.
*/
int whpx_vcpu_exec(CPUState *cpu)
{
int ret;
int fatal;
for (;;) {
if (cpu->exception_index >= EXCP_INTERRUPT) {
ret = cpu->exception_index;
cpu->exception_index = -1;
break;
}
fatal = whpx_vcpu_run(cpu);
if (fatal) {
error_report("WHPX: Failed to exec a virtual processor");
abort();
}
}
return ret;
}
void whpx_destroy_vcpu(CPUState *cpu)
{
struct whpx_state *whpx = &whpx_global;
whp_dispatch.WHvDeleteVirtualProcessor(whpx->partition, cpu->cpu_index);
whpx_arch_destroy_vcpu(cpu);
g_free(cpu->accel);
}
void whpx_vcpu_kick(CPUState *cpu)
{
struct whpx_state *whpx = &whpx_global;
whp_dispatch.WHvCancelRunVirtualProcessor(
whpx->partition, cpu->cpu_index, 0);
}
/*
* Memory support.
*/
static void whpx_set_phys_mem(MemoryRegionSection *section, bool add)
{
struct whpx_state *whpx = &whpx_global;
MemoryRegion *area = section->mr;
bool writable = !area->readonly && !area->rom_device;
WHV_MAP_GPA_RANGE_FLAGS flags;
uint64_t page_size = qemu_real_host_page_size();
uint64_t gva = section->offset_within_address_space;
uint64_t size = int128_get64(section->size);
HRESULT hr;
void *mem;
if (!memory_region_is_ram(area)) {
if (writable) {
return;
} else if (!memory_region_is_romd(area)) {
add = false;
}
}
if (!QEMU_IS_ALIGNED(size, page_size) ||
!QEMU_IS_ALIGNED(gva, page_size)) {
/* Not page aligned, so we can not map as RAM */
add = false;
}
if (!add) {
hr = whp_dispatch.WHvUnmapGpaRange(whpx->partition,
gva, size);
if (FAILED(hr)) {
error_report("WHPX: failed to unmap GPA range");
abort();
}
return;
}
flags = WHvMapGpaRangeFlagRead | WHvMapGpaRangeFlagExecute
| (writable ? WHvMapGpaRangeFlagWrite : 0);
mem = memory_region_get_ram_ptr(area) + section->offset_within_region;
hr = whp_dispatch.WHvMapGpaRange(whpx->partition,
mem, gva, size, flags);
if (FAILED(hr)) {
error_report("WHPX: failed to map GPA range");
abort();
}
}
static void whpx_region_add(MemoryListener *listener,
MemoryRegionSection *section)
{
whpx_set_phys_mem(section, true);
}
static void whpx_region_del(MemoryListener *listener,
MemoryRegionSection *section)
{
whpx_set_phys_mem(section, false);
}
static void whpx_transaction_begin(MemoryListener *listener)
{
}
static void whpx_transaction_commit(MemoryListener *listener)
{
}
static void whpx_log_sync(MemoryListener *listener,
MemoryRegionSection *section)
{
MemoryRegion *mr = section->mr;
if (!memory_region_is_ram(mr)) {
return;
}
memory_region_set_dirty(mr, 0, int128_get64(section->size));
}
static MemoryListener whpx_memory_listener = {
.name = "whpx",
.begin = whpx_transaction_begin,
.commit = whpx_transaction_commit,
.region_add = whpx_region_add,
.region_del = whpx_region_del,
.log_sync = whpx_log_sync,
.priority = MEMORY_LISTENER_PRIORITY_ACCEL,
};
void whpx_memory_init(void)
{
memory_listener_register(&whpx_memory_listener, &address_space_memory);
}
/*
* Load the functions from the given library, using the given handle. If a
* handle is provided, it is used, otherwise the library is opened. The
* handle will be updated on return with the opened one.
*/
static bool load_whp_dispatch_fns(HMODULE *handle,
WHPFunctionList function_list)
{
HMODULE hLib = *handle;
#define WINHV_PLATFORM_DLL "WinHvPlatform.dll"
#define WHP_LOAD_FIELD_OPTIONAL(return_type, function_name, signature) \
whp_dispatch.function_name = \
(function_name ## _t)GetProcAddress(hLib, #function_name); \
#define WHP_LOAD_FIELD(return_type, function_name, signature) \
whp_dispatch.function_name = \
(function_name ## _t)GetProcAddress(hLib, #function_name); \
if (!whp_dispatch.function_name) { \
error_report("Could not load function %s", #function_name); \
goto error; \
} \
#define WHP_LOAD_LIB(lib_name, handle_lib) \
if (!handle_lib) { \
handle_lib = LoadLibrary(lib_name); \
if (!handle_lib) { \
error_report("Could not load library %s.", lib_name); \
goto error; \
} \
} \
switch (function_list) {
case WINHV_PLATFORM_FNS_DEFAULT:
WHP_LOAD_LIB(WINHV_PLATFORM_DLL, hLib)
LIST_WINHVPLATFORM_FUNCTIONS(WHP_LOAD_FIELD)
break;
case WINHV_PLATFORM_FNS_SUPPLEMENTAL:
WHP_LOAD_LIB(WINHV_PLATFORM_DLL, hLib)
LIST_WINHVPLATFORM_FUNCTIONS_SUPPLEMENTAL(WHP_LOAD_FIELD_OPTIONAL)
break;
}
*handle = hLib;
return true;
error:
if (hLib) {
FreeLibrary(hLib);
}
return false;
}
static void whpx_set_kernel_irqchip(Object *obj, Visitor *v,
const char *name, void *opaque,
Error **errp)
{
struct whpx_state *whpx = &whpx_global;
OnOffSplit mode;
if (!visit_type_OnOffSplit(v, name, &mode, errp)) {
return;
}
switch (mode) {
case ON_OFF_SPLIT_ON:
whpx->kernel_irqchip_allowed = true;
whpx->kernel_irqchip_required = true;
break;
case ON_OFF_SPLIT_OFF:
whpx->kernel_irqchip_allowed = false;
whpx->kernel_irqchip_required = false;
break;
case ON_OFF_SPLIT_SPLIT:
error_setg(errp, "WHPX: split irqchip currently not supported");
error_append_hint(errp,
"Try without kernel-irqchip or with kernel-irqchip=on|off");
break;
default:
/*
* The value was checked in visit_type_OnOffSplit() above. If
* we get here, then something is wrong in QEMU.
*/
abort();
}
}
static void whpx_set_hyperv(Object *obj, Visitor *v,
const char *name, void *opaque,
Error **errp)
{
struct whpx_state *whpx = &whpx_global;
OnOffAuto mode;
if (!visit_type_OnOffAuto(v, name, &mode, errp)) {
return;
}
switch (mode) {
case ON_OFF_AUTO_ON:
whpx->hyperv_enlightenments_allowed = true;
whpx->hyperv_enlightenments_required = true;
break;
case ON_OFF_AUTO_OFF:
whpx->hyperv_enlightenments_allowed = false;
whpx->hyperv_enlightenments_required = false;
break;
case ON_OFF_AUTO_AUTO:
whpx->hyperv_enlightenments_allowed = true;
whpx->hyperv_enlightenments_required = false;
break;
default:
/*
* The value was checked in visit_type_OnOffAuto() above. If
* we get here, then something is wrong in QEMU.
*/
abort();
}
}
static void whpx_cpu_accel_class_init(ObjectClass *oc, const void *data)
{
AccelCPUClass *acc = ACCEL_CPU_CLASS(oc);
acc->cpu_instance_init = whpx_cpu_instance_init;
}
static const TypeInfo whpx_cpu_accel_type = {
.name = ACCEL_CPU_NAME("whpx"),
.parent = TYPE_ACCEL_CPU,
.class_init = whpx_cpu_accel_class_init,
.abstract = true,
};
static void whpx_accel_class_init(ObjectClass *oc, const void *data)
{
AccelClass *ac = ACCEL_CLASS(oc);
ac->name = "WHPX";
ac->init_machine = whpx_accel_init;
ac->pre_resume_vm = whpx_pre_resume_vm;
ac->allowed = &whpx_allowed;
object_class_property_add(oc, "kernel-irqchip", "on|off|split",
NULL, whpx_set_kernel_irqchip,
NULL, NULL);
object_class_property_set_description(oc, "kernel-irqchip",
"Configure WHPX in-kernel irqchip");
object_class_property_add(oc, "hyperv", "OnOffAuto",
NULL, whpx_set_hyperv,
NULL, NULL);
object_class_property_set_description(oc, "hyperv",
"Configure Hyper-V enlightenments");
whpx_arch_accel_class_init(oc);
}
static void whpx_accel_instance_init(Object *obj)
{
struct whpx_state *whpx = &whpx_global;
memset(whpx, 0, sizeof(struct whpx_state));
/* Turn on kernel-irqchip, by default */
whpx->kernel_irqchip_allowed = true;
whpx->hyperv_enlightenments_allowed = true;
whpx->hyperv_enlightenments_required = false;
/* Value determined at whpx_accel_init */
whpx->hyperv_enlightenments_enabled = false;
whpx->ignore_unknown_msr = true;
whpx->intercept_msr_gp = false;
whpx->separate_security_domain = true;
}
static const TypeInfo whpx_accel_type = {
.name = ACCEL_CLASS_NAME("whpx"),
.parent = TYPE_ACCEL,
.instance_init = whpx_accel_instance_init,
.class_init = whpx_accel_class_init,
};
static void whpx_type_init(void)
{
type_register_static(&whpx_accel_type);
type_register_static(&whpx_cpu_accel_type);
}
bool init_whp_dispatch(void)
{
if (whp_dispatch_initialized) {
return true;
}
if (!load_whp_dispatch_fns(&hWinHvPlatform, WINHV_PLATFORM_FNS_DEFAULT)) {
goto error;
}
assert(load_whp_dispatch_fns(&hWinHvPlatform,
WINHV_PLATFORM_FNS_SUPPLEMENTAL));
whp_dispatch_initialized = true;
return true;
error:
if (hWinHvPlatform) {
FreeLibrary(hWinHvPlatform);
}
return false;
}
type_init(whpx_type_init);
+1
View File
@@ -0,0 +1 @@
system_ss.add(when: 'CONFIG_XEN', if_true: files('xen-all.c'))
+173
View File
@@ -0,0 +1,173 @@
/*
* Copyright (C) 2014 Citrix Systems UK Ltd.
*
* 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/error-report.h"
#include "qemu/module.h"
#include "qapi/error.h"
#include "hw/xen/xen_native.h"
#include "hw/xen/xen-legacy-backend.h"
#include "hw/xen/xen_pt.h"
#include "hw/xen/xen_igd.h"
#include "chardev/char.h"
#include "qemu/accel.h"
#include "accel/dummy-cpus.h"
#include "accel/accel-ops.h"
#include "accel/accel-cpu-ops.h"
#include "system/cpus.h"
#include "system/xen.h"
#include "system/runstate.h"
#include "migration/misc.h"
#include "migration/global_state.h"
#include "hw/core/boards.h"
bool xen_allowed;
xc_interface *xen_xc;
xenforeignmemory_handle *xen_fmem;
xendevicemodel_handle *xen_dmod;
static void xenstore_record_dm_state(const char *state)
{
char path[50];
snprintf(path, sizeof (path), "device-model/%u/state", xen_domid);
if (!qemu_xen_xs_write(xenstore, XBT_NULL, path, state, strlen(state))) {
error_report("error recording dm state");
exit(1);
}
}
static void xen_change_state_handler(void *opaque, bool running,
RunState state)
{
if (running) {
/* record state running */
xenstore_record_dm_state("running");
}
}
static bool xen_get_igd_gfx_passthru(Object *obj, Error **errp)
{
return xen_igd_gfx_pt_enabled();
}
static void xen_set_igd_gfx_passthru(Object *obj, bool value, Error **errp)
{
xen_igd_gfx_pt_set(value, errp);
}
static void xen_setup_post(AccelState *as)
{
int rc;
if (xen_domid_restrict) {
rc = xen_restrict(xen_domid);
if (rc < 0) {
perror("xen: failed to restrict");
exit(1);
}
}
}
static int xen_init(AccelState *as, MachineState *ms)
{
MachineClass *mc = MACHINE_GET_CLASS(ms);
xen_xc = xc_interface_open(0, 0, 0);
if (xen_xc == NULL) {
xen_pv_printf(NULL, 0, "can't open xen interface\n");
return -1;
}
xen_fmem = xenforeignmemory_open(0, 0);
if (xen_fmem == NULL) {
xen_pv_printf(NULL, 0, "can't open xen fmem interface\n");
xc_interface_close(xen_xc);
return -1;
}
xen_dmod = xendevicemodel_open(0, 0);
if (xen_dmod == NULL) {
xen_pv_printf(NULL, 0, "can't open xen devicemodel interface\n");
xenforeignmemory_close(xen_fmem);
xc_interface_close(xen_xc);
return -1;
}
/*
* The XenStore write would fail when running restricted so don't attempt
* it in that case. Toolstacks should instead use QMP to listen for state
* changes.
*/
if (!xen_domid_restrict) {
qemu_add_vm_change_state_handler(xen_change_state_handler, NULL);
}
/*
* opt out of system RAM being allocated by generic code
*/
mc->default_ram_id = NULL;
xen_mode = XEN_ATTACH;
return 0;
}
static void xen_accel_class_init(ObjectClass *oc, const void *data)
{
AccelClass *ac = ACCEL_CLASS(oc);
static GlobalProperty compat[] = {
{ "migration", "store-global-state", "off" },
{ "migration", "send-configuration", "off" },
{ "migration", "send-section-footer", "off" },
};
ac->name = "Xen";
ac->init_machine = xen_init;
ac->setup_post = xen_setup_post;
ac->allowed = &xen_allowed;
ac->compat_props = g_ptr_array_new();
compat_props_add(ac->compat_props, compat, G_N_ELEMENTS(compat));
object_class_property_add_bool(oc, "igd-passthru",
xen_get_igd_gfx_passthru, xen_set_igd_gfx_passthru);
object_class_property_set_description(oc, "igd-passthru",
"Set on/off to enable/disable igd passthrou");
}
#define TYPE_XEN_ACCEL ACCEL_CLASS_NAME("xen")
static const TypeInfo xen_accel_type = {
.name = TYPE_XEN_ACCEL,
.parent = TYPE_ACCEL,
.class_init = xen_accel_class_init,
};
static void xen_accel_ops_class_init(ObjectClass *oc, const void *data)
{
AccelOpsClass *ops = ACCEL_OPS_CLASS(oc);
ops->create_vcpu_thread = dummy_start_vcpu_thread;
ops->handle_interrupt = generic_handle_interrupt;
}
static const TypeInfo xen_accel_ops_type = {
.name = ACCEL_OPS_NAME("xen"),
.parent = TYPE_ACCEL_OPS,
.class_init = xen_accel_ops_class_init,
.abstract = true,
};
static void xen_type_init(void)
{
type_register_static(&xen_accel_type);
type_register_static(&xen_accel_ops_type);
}
type_init(xen_type_init);