638181a180
Originally I tried to move where vCPU thread initialisation to later in realize. However pulling that thread (sic) got gnarly really quickly. It turns out some steps of CPU realization need values that can only be determined from the running vCPU thread. However having moved enough out of the thread creation we can now queue work before the thread starts (at least for TCG guests) and avoid the race between vcpu_init and other vcpu states a plugin might subscribe to. Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org> Message-ID: <20240530194250.1801701-6-alex.bennee@linaro.org> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org>
336 lines
8.7 KiB
C
336 lines
8.7 KiB
C
/*
|
|
* QEMU CPU model
|
|
*
|
|
* Copyright (c) 2012-2014 SUSE LINUX Products GmbH
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* 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/gpl-2.0.html>
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qapi/error.h"
|
|
#include "hw/core/cpu.h"
|
|
#include "sysemu/hw_accel.h"
|
|
#include "qemu/log.h"
|
|
#include "qemu/main-loop.h"
|
|
#include "exec/log.h"
|
|
#include "exec/gdbstub.h"
|
|
#include "sysemu/tcg.h"
|
|
#include "hw/boards.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "trace.h"
|
|
#ifdef CONFIG_PLUGIN
|
|
#include "qemu/plugin.h"
|
|
#endif
|
|
|
|
CPUState *cpu_by_arch_id(int64_t id)
|
|
{
|
|
CPUState *cpu;
|
|
|
|
CPU_FOREACH(cpu) {
|
|
CPUClass *cc = CPU_GET_CLASS(cpu);
|
|
|
|
if (cc->get_arch_id(cpu) == id) {
|
|
return cpu;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool cpu_exists(int64_t id)
|
|
{
|
|
return !!cpu_by_arch_id(id);
|
|
}
|
|
|
|
CPUState *cpu_create(const char *typename)
|
|
{
|
|
Error *err = NULL;
|
|
CPUState *cpu = CPU(object_new(typename));
|
|
if (!qdev_realize(DEVICE(cpu), NULL, &err)) {
|
|
error_report_err(err);
|
|
object_unref(OBJECT(cpu));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
return cpu;
|
|
}
|
|
|
|
/* Resetting the IRQ comes from across the code base so we take the
|
|
* BQL here if we need to. cpu_interrupt assumes it is held.*/
|
|
void cpu_reset_interrupt(CPUState *cpu, int mask)
|
|
{
|
|
bool need_lock = !bql_locked();
|
|
|
|
if (need_lock) {
|
|
bql_lock();
|
|
}
|
|
cpu->interrupt_request &= ~mask;
|
|
if (need_lock) {
|
|
bql_unlock();
|
|
}
|
|
}
|
|
|
|
void cpu_exit(CPUState *cpu)
|
|
{
|
|
qatomic_set(&cpu->exit_request, 1);
|
|
/* Ensure cpu_exec will see the exit request after TCG has exited. */
|
|
smp_wmb();
|
|
qatomic_set(&cpu->neg.icount_decr.u16.high, -1);
|
|
}
|
|
|
|
static int cpu_common_gdb_read_register(CPUState *cpu, GByteArray *buf, int reg)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int cpu_common_gdb_write_register(CPUState *cpu, uint8_t *buf, int reg)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void cpu_dump_state(CPUState *cpu, FILE *f, int flags)
|
|
{
|
|
CPUClass *cc = CPU_GET_CLASS(cpu);
|
|
|
|
if (cc->dump_state) {
|
|
cpu_synchronize_state(cpu);
|
|
cc->dump_state(cpu, f, flags);
|
|
}
|
|
}
|
|
|
|
void cpu_reset(CPUState *cpu)
|
|
{
|
|
device_cold_reset(DEVICE(cpu));
|
|
|
|
trace_cpu_reset(cpu->cpu_index);
|
|
}
|
|
|
|
static void cpu_common_reset_hold(Object *obj, ResetType type)
|
|
{
|
|
CPUState *cpu = CPU(obj);
|
|
CPUClass *cc = CPU_GET_CLASS(cpu);
|
|
|
|
if (qemu_loglevel_mask(CPU_LOG_RESET)) {
|
|
qemu_log("CPU Reset (CPU %d)\n", cpu->cpu_index);
|
|
log_cpu_state(cpu, cc->reset_dump_flags);
|
|
}
|
|
|
|
cpu->interrupt_request = 0;
|
|
cpu->halted = cpu->start_powered_off;
|
|
cpu->mem_io_pc = 0;
|
|
cpu->icount_extra = 0;
|
|
qatomic_set(&cpu->neg.icount_decr.u32, 0);
|
|
cpu->neg.can_do_io = true;
|
|
cpu->exception_index = -1;
|
|
cpu->crash_occurred = false;
|
|
cpu->cflags_next_tb = -1;
|
|
|
|
cpu_exec_reset_hold(cpu);
|
|
}
|
|
|
|
static bool cpu_common_has_work(CPUState *cs)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ObjectClass *cpu_class_by_name(const char *typename, const char *cpu_model)
|
|
{
|
|
ObjectClass *oc;
|
|
CPUClass *cc;
|
|
|
|
oc = object_class_by_name(typename);
|
|
cc = CPU_CLASS(oc);
|
|
assert(cc->class_by_name);
|
|
assert(cpu_model);
|
|
oc = cc->class_by_name(cpu_model);
|
|
if (object_class_dynamic_cast(oc, typename) &&
|
|
!object_class_is_abstract(oc)) {
|
|
return oc;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void cpu_common_parse_features(const char *typename, char *features,
|
|
Error **errp)
|
|
{
|
|
char *val;
|
|
static bool cpu_globals_initialized;
|
|
/* Single "key=value" string being parsed */
|
|
char *featurestr = features ? strtok(features, ",") : NULL;
|
|
|
|
/* should be called only once, catch invalid users */
|
|
assert(!cpu_globals_initialized);
|
|
cpu_globals_initialized = true;
|
|
|
|
while (featurestr) {
|
|
val = strchr(featurestr, '=');
|
|
if (val) {
|
|
GlobalProperty *prop = g_new0(typeof(*prop), 1);
|
|
*val = 0;
|
|
val++;
|
|
prop->driver = typename;
|
|
prop->property = g_strdup(featurestr);
|
|
prop->value = g_strdup(val);
|
|
qdev_prop_register_global(prop);
|
|
} else {
|
|
error_setg(errp, "Expected key=value format, found %s.",
|
|
featurestr);
|
|
return;
|
|
}
|
|
featurestr = strtok(NULL, ",");
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_PLUGIN
|
|
static void qemu_plugin_vcpu_init__async(CPUState *cpu, run_on_cpu_data unused)
|
|
{
|
|
qemu_plugin_vcpu_init_hook(cpu);
|
|
}
|
|
#endif
|
|
|
|
static void cpu_common_realizefn(DeviceState *dev, Error **errp)
|
|
{
|
|
CPUState *cpu = CPU(dev);
|
|
Object *machine = qdev_get_machine();
|
|
|
|
/* qdev_get_machine() can return something that's not TYPE_MACHINE
|
|
* if this is one of the user-only emulators; in that case there's
|
|
* no need to check the ignore_memory_transaction_failures board flag.
|
|
*/
|
|
if (object_dynamic_cast(machine, TYPE_MACHINE)) {
|
|
MachineClass *mc = MACHINE_GET_CLASS(machine);
|
|
|
|
if (mc) {
|
|
cpu->ignore_memory_transaction_failures =
|
|
mc->ignore_memory_transaction_failures;
|
|
}
|
|
}
|
|
|
|
if (dev->hotplugged) {
|
|
cpu_synchronize_post_init(cpu);
|
|
cpu_resume(cpu);
|
|
}
|
|
|
|
/* NOTE: latest generic point where the cpu is fully realized */
|
|
}
|
|
|
|
static void cpu_common_unrealizefn(DeviceState *dev)
|
|
{
|
|
CPUState *cpu = CPU(dev);
|
|
|
|
/* Call the plugin hook before clearing the cpu is fully unrealized */
|
|
#ifdef CONFIG_PLUGIN
|
|
if (tcg_enabled()) {
|
|
qemu_plugin_vcpu_exit_hook(cpu);
|
|
}
|
|
#endif
|
|
|
|
/* NOTE: latest generic point before the cpu is fully unrealized */
|
|
cpu_exec_unrealizefn(cpu);
|
|
}
|
|
|
|
static void cpu_common_initfn(Object *obj)
|
|
{
|
|
CPUState *cpu = CPU(obj);
|
|
|
|
gdb_init_cpu(cpu);
|
|
cpu->cpu_index = UNASSIGNED_CPU_INDEX;
|
|
cpu->cluster_index = UNASSIGNED_CLUSTER_INDEX;
|
|
/* user-mode doesn't have configurable SMP topology */
|
|
/* the default value is changed by qemu_init_vcpu() for system-mode */
|
|
cpu->nr_cores = 1;
|
|
cpu->nr_threads = 1;
|
|
cpu->cflags_next_tb = -1;
|
|
|
|
/* allocate storage for thread info, initialise condition variables */
|
|
cpu->thread = g_new0(QemuThread, 1);
|
|
cpu->halt_cond = g_new0(QemuCond, 1);
|
|
qemu_cond_init(cpu->halt_cond);
|
|
|
|
qemu_mutex_init(&cpu->work_mutex);
|
|
qemu_lockcnt_init(&cpu->in_ioctl_lock);
|
|
QSIMPLEQ_INIT(&cpu->work_list);
|
|
QTAILQ_INIT(&cpu->breakpoints);
|
|
QTAILQ_INIT(&cpu->watchpoints);
|
|
|
|
cpu_exec_initfn(cpu);
|
|
|
|
/*
|
|
* Plugin initialization must wait until the cpu start executing
|
|
* code, but we must queue this work before the threads are
|
|
* created to ensure we don't race.
|
|
*/
|
|
#ifdef CONFIG_PLUGIN
|
|
if (tcg_enabled()) {
|
|
cpu->plugin_state = qemu_plugin_create_vcpu_state();
|
|
async_run_on_cpu(cpu, qemu_plugin_vcpu_init__async, RUN_ON_CPU_NULL);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void cpu_common_finalize(Object *obj)
|
|
{
|
|
CPUState *cpu = CPU(obj);
|
|
|
|
g_array_free(cpu->gdb_regs, TRUE);
|
|
qemu_lockcnt_destroy(&cpu->in_ioctl_lock);
|
|
qemu_mutex_destroy(&cpu->work_mutex);
|
|
}
|
|
|
|
static int64_t cpu_common_get_arch_id(CPUState *cpu)
|
|
{
|
|
return cpu->cpu_index;
|
|
}
|
|
|
|
static void cpu_common_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
ResettableClass *rc = RESETTABLE_CLASS(klass);
|
|
CPUClass *k = CPU_CLASS(klass);
|
|
|
|
k->parse_features = cpu_common_parse_features;
|
|
k->get_arch_id = cpu_common_get_arch_id;
|
|
k->has_work = cpu_common_has_work;
|
|
k->gdb_read_register = cpu_common_gdb_read_register;
|
|
k->gdb_write_register = cpu_common_gdb_write_register;
|
|
set_bit(DEVICE_CATEGORY_CPU, dc->categories);
|
|
dc->realize = cpu_common_realizefn;
|
|
dc->unrealize = cpu_common_unrealizefn;
|
|
rc->phases.hold = cpu_common_reset_hold;
|
|
cpu_class_init_props(dc);
|
|
/*
|
|
* Reason: CPUs still need special care by board code: wiring up
|
|
* IRQs, adding reset handlers, halting non-first CPUs, ...
|
|
*/
|
|
dc->user_creatable = false;
|
|
}
|
|
|
|
static const TypeInfo cpu_type_info = {
|
|
.name = TYPE_CPU,
|
|
.parent = TYPE_DEVICE,
|
|
.instance_size = sizeof(CPUState),
|
|
.instance_init = cpu_common_initfn,
|
|
.instance_finalize = cpu_common_finalize,
|
|
.abstract = true,
|
|
.class_size = sizeof(CPUClass),
|
|
.class_init = cpu_common_class_init,
|
|
};
|
|
|
|
static void cpu_register_types(void)
|
|
{
|
|
type_register_static(&cpu_type_info);
|
|
}
|
|
|
|
type_init(cpu_register_types)
|