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
+32
View File
@@ -0,0 +1,32 @@
/*
* 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/base-arch-defs.h"
#include "qemu/bitops.h"
#include "qemu/target-info-qapi.h"
bool qemu_arch_available(uint32_t arch_bitmask)
{
return extract32(arch_bitmask, target_arch(), 1);
}
+108
View File
@@ -0,0 +1,108 @@
/*
* Asynchronous teardown
*
* Copyright IBM, Corp. 2022
*
* Authors:
* Claudio Imbrenda <[email protected]>
*
* This work is licensed under the terms of the GNU GPL, version 2 or (at your
* option) any later version. See the COPYING file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include <dirent.h>
#include <sys/prctl.h>
#include <sched.h>
#include "qemu/async-teardown.h"
#ifdef _SC_THREAD_STACK_MIN
#define CLONE_STACK_SIZE sysconf(_SC_THREAD_STACK_MIN)
#else
#define CLONE_STACK_SIZE 16384
#endif
static pid_t the_ppid;
static void hup_handler(int signal)
{
/* Check every second if this process has been reparented. */
while (the_ppid == getppid()) {
/* sleep() is safe to use in a signal handler. */
sleep(1);
}
/* At this point the parent process has terminated completely. */
_exit(0);
}
static int async_teardown_fn(void *arg)
{
struct sigaction sa = { .sa_handler = hup_handler };
sigset_t hup_signal;
char name[16];
/* Set a meaningful name for this process. */
snprintf(name, 16, "cleanup/%d", the_ppid);
prctl(PR_SET_NAME, (unsigned long)name);
/*
* Close all file descriptors that might have been inherited from the
* main qemu process when doing clone, needed to make libvirt happy.
*/
qemu_close_all_open_fd(NULL, 0);
/* Set up a handler for SIGHUP and unblock SIGHUP. */
sigaction(SIGHUP, &sa, NULL);
sigemptyset(&hup_signal);
sigaddset(&hup_signal, SIGHUP);
sigprocmask(SIG_UNBLOCK, &hup_signal, NULL);
/* Ask to receive SIGHUP when the parent dies. */
prctl(PR_SET_PDEATHSIG, SIGHUP);
/*
* Sleep forever, unless the parent process has already terminated. The
* only interruption can come from the SIGHUP signal, which in normal
* operation is received when the parent process dies.
*/
if (the_ppid == getppid()) {
pause();
}
/* At this point the parent process has terminated completely. */
_exit(0);
}
/*
* Allocate a new stack of a reasonable size, and return a pointer to its top.
*/
static void *new_stack_for_clone(void)
{
size_t stack_size = CLONE_STACK_SIZE;
char *stack_ptr;
/* Allocate a new stack and get a pointer to its top. */
stack_ptr = qemu_alloc_stack(&stack_size);
stack_ptr += stack_size;
return stack_ptr;
}
/*
* Block all signals, start (clone) a new process sharing the address space
* with qemu (CLONE_VM), then restore signals.
*/
void init_async_teardown(void)
{
sigset_t all_signals, old_signals;
the_ppid = getpid();
sigfillset(&all_signals);
sigprocmask(SIG_BLOCK, &all_signals, &old_signals);
clone(async_teardown_fn, new_stack_for_clone(), CLONE_VM, NULL);
sigprocmask(SIG_SETMASK, &old_signals, NULL);
}
+106
View File
@@ -0,0 +1,106 @@
/*
* Generic Balloon handlers and management
*
* Copyright (c) 2003-2008 Fabrice Bellard
* Copyright (C) 2011 Red Hat, Inc.
* Copyright (C) 2011 Amit Shah <[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/atomic.h"
#include "system/kvm.h"
#include "system/balloon.h"
#include "qapi/error.h"
#include "qapi/qapi-commands-machine.h"
#include "qapi/qmp/qerror.h"
#include "trace.h"
static QEMUBalloonEvent *balloon_event_fn;
static QEMUBalloonStatus *balloon_stat_fn;
static void *balloon_opaque;
static bool have_balloon(Error **errp)
{
if (kvm_enabled() && !kvm_has_sync_mmu()) {
error_set(errp, ERROR_CLASS_KVM_MISSING_CAP,
"Using KVM without synchronous MMU, balloon unavailable");
return false;
}
if (!balloon_event_fn) {
error_set(errp, ERROR_CLASS_DEVICE_NOT_ACTIVE,
"No balloon device has been activated");
return false;
}
return true;
}
int qemu_add_balloon_handler(QEMUBalloonEvent *event_func,
QEMUBalloonStatus *stat_func, void *opaque)
{
if (balloon_event_fn || balloon_stat_fn || balloon_opaque) {
/* We're already registered one balloon handler. How many can
* a guest really have?
*/
return -1;
}
balloon_event_fn = event_func;
balloon_stat_fn = stat_func;
balloon_opaque = opaque;
return 0;
}
void qemu_remove_balloon_handler(void *opaque)
{
if (balloon_opaque != opaque) {
return;
}
balloon_event_fn = NULL;
balloon_stat_fn = NULL;
balloon_opaque = NULL;
}
BalloonInfo *qmp_query_balloon(Error **errp)
{
BalloonInfo *info;
if (!have_balloon(errp)) {
return NULL;
}
info = g_malloc0(sizeof(*info));
balloon_stat_fn(balloon_opaque, info);
return info;
}
void qmp_balloon(int64_t value, Error **errp)
{
if (!have_balloon(errp)) {
return;
}
if (value <= 0) {
error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "value", "a size");
return;
}
trace_balloon_event(balloon_opaque, value);
balloon_event_fn(balloon_opaque, value);
}
+433
View File
@@ -0,0 +1,433 @@
/*
* QEMU Boot Device Implement
*
* Copyright (c) 2014 HUAWEI TECHNOLOGIES CO., LTD.
*
* 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 "qapi/error.h"
#include "system/system.h"
#include "qapi/visitor.h"
#include "qemu/error-report.h"
#include "system/reset.h"
#include "hw/core/qdev.h"
#include "hw/core/boards.h"
typedef struct FWBootEntry FWBootEntry;
struct FWBootEntry {
QTAILQ_ENTRY(FWBootEntry) link;
int32_t bootindex;
DeviceState *dev;
char *suffix;
};
static QTAILQ_HEAD(, FWBootEntry) fw_boot_order =
QTAILQ_HEAD_INITIALIZER(fw_boot_order);
static QEMUBootSetHandler *boot_set_handler;
static void *boot_set_opaque;
void qemu_register_boot_set(QEMUBootSetHandler *func, void *opaque)
{
boot_set_handler = func;
boot_set_opaque = opaque;
}
void qemu_boot_set(const char *boot_order, Error **errp)
{
Error *local_err = NULL;
if (!boot_set_handler) {
error_setg(errp, "no function defined to set boot device list for"
" this architecture");
return;
}
validate_bootdevices(boot_order, &local_err);
if (local_err) {
error_propagate(errp, local_err);
return;
}
boot_set_handler(boot_set_opaque, boot_order, errp);
}
void validate_bootdevices(const char *devices, Error **errp)
{
/* We just do some generic consistency checks */
const char *p;
int bitmap = 0;
for (p = devices; *p != '\0'; p++) {
/* Allowed boot devices are:
* a-b: floppy disk drives
* c-f: IDE disk drives
* g-m: machine implementation dependent drives
* n-p: network devices
* It's up to each machine implementation to check if the given boot
* devices match the actual hardware implementation and firmware
* features.
*/
if (*p < 'a' || *p > 'p') {
error_setg(errp, "Invalid boot device '%c'", *p);
return;
}
if (bitmap & (1 << (*p - 'a'))) {
error_setg(errp, "Boot device '%c' was given twice", *p);
return;
}
bitmap |= 1 << (*p - 'a');
}
}
void restore_boot_order(void *opaque)
{
char *normal_boot_order = opaque;
static int bootcount;
switch (bootcount++) {
case 0:
/* First boot: use the one-time config */
return;
case 1:
/* Second boot: restore normal boot order */
if (boot_set_handler) {
qemu_boot_set(normal_boot_order, &error_abort);
}
g_free(normal_boot_order);
return;
default:
/* Subsequent boots: keep using normal boot order */
return;
}
}
void check_boot_index(int32_t bootindex, Error **errp)
{
FWBootEntry *i;
if (bootindex >= 0) {
QTAILQ_FOREACH(i, &fw_boot_order, link) {
if (i->bootindex == bootindex) {
error_setg(errp, "The bootindex %d has already been used",
bootindex);
return;
}
}
}
}
void del_boot_device_path(DeviceState *dev, const char *suffix)
{
FWBootEntry *i;
if (dev == NULL) {
return;
}
QTAILQ_FOREACH(i, &fw_boot_order, link) {
if ((!suffix || !g_strcmp0(i->suffix, suffix)) &&
i->dev == dev) {
QTAILQ_REMOVE(&fw_boot_order, i, link);
g_free(i->suffix);
g_free(i);
break;
}
}
}
void add_boot_device_path(int32_t bootindex, DeviceState *dev,
const char *suffix)
{
FWBootEntry *node, *i;
if (bootindex < 0) {
del_boot_device_path(dev, suffix);
return;
}
assert(dev != NULL || suffix != NULL);
del_boot_device_path(dev, suffix);
node = g_new0(FWBootEntry, 1);
node->bootindex = bootindex;
node->suffix = g_strdup(suffix);
node->dev = dev;
QTAILQ_FOREACH(i, &fw_boot_order, link) {
if (i->bootindex == bootindex) {
error_report("Two devices with same boot index %d", bootindex);
exit(1);
} else if (i->bootindex < bootindex) {
continue;
}
QTAILQ_INSERT_BEFORE(i, node, link);
return;
}
QTAILQ_INSERT_TAIL(&fw_boot_order, node, link);
}
DeviceState *get_boot_device(uint32_t position)
{
uint32_t counter = 0;
FWBootEntry *i = NULL;
DeviceState *res = NULL;
if (!QTAILQ_EMPTY(&fw_boot_order)) {
QTAILQ_FOREACH(i, &fw_boot_order, link) {
if (counter == position) {
res = i->dev;
break;
}
counter++;
}
}
return res;
}
static char *get_boot_device_path(DeviceState *dev, bool ignore_suffixes,
const char *suffix)
{
char *devpath = NULL, *s = NULL, *d, *bootpath;
if (dev) {
devpath = qdev_get_fw_dev_path(dev);
assert(devpath);
}
if (!ignore_suffixes) {
if (dev) {
d = qdev_get_own_fw_dev_path_from_handler(dev->parent_bus, dev);
if (d) {
assert(!suffix);
s = d;
} else {
s = g_strdup(suffix);
}
} else {
s = g_strdup(suffix);
}
}
bootpath = g_strdup_printf("%s%s",
devpath ? devpath : "",
s ? s : "");
g_free(devpath);
g_free(s);
return bootpath;
}
/*
* This function returns null terminated string that consist of new line
* separated device paths.
*
* memory pointed by "size" is assigned total length of the array in bytes
*
*/
char *get_boot_devices_list(size_t *size)
{
FWBootEntry *i;
size_t total = 0;
char *list = NULL;
MachineClass *mc = MACHINE_GET_CLASS(qdev_get_machine());
bool ignore_suffixes = mc->ignore_boot_device_suffixes;
QTAILQ_FOREACH(i, &fw_boot_order, link) {
char *bootpath;
size_t len;
bootpath = get_boot_device_path(i->dev, ignore_suffixes, i->suffix);
if (total) {
list[total-1] = '\n';
}
len = strlen(bootpath) + 1;
list = g_realloc(list, total + len);
memcpy(&list[total], bootpath, len);
total += len;
g_free(bootpath);
}
*size = total;
if (current_machine->boot_config.has_strict &&
current_machine->boot_config.strict && *size > 0) {
list[total-1] = '\n';
list = g_realloc(list, total + 5);
memcpy(&list[total], "HALT", 5);
*size = total + 5;
}
return list;
}
typedef struct {
int32_t *bootindex;
const char *suffix;
DeviceState *dev;
} BootIndexProperty;
static void device_get_bootindex(Object *obj, Visitor *v, const char *name,
void *opaque, Error **errp)
{
BootIndexProperty *prop = opaque;
visit_type_int32(v, name, prop->bootindex, errp);
}
static void device_set_bootindex(Object *obj, Visitor *v, const char *name,
void *opaque, Error **errp)
{
BootIndexProperty *prop = opaque;
int32_t boot_index;
Error *local_err = NULL;
if (!visit_type_int32(v, name, &boot_index, errp)) {
return;
}
/* check whether bootindex is present in fw_boot_order list */
check_boot_index(boot_index, &local_err);
if (local_err) {
error_propagate(errp, local_err);
return;
}
/* change bootindex to a new one */
*prop->bootindex = boot_index;
add_boot_device_path(*prop->bootindex, prop->dev, prop->suffix);
}
static void property_release_bootindex(Object *obj, const char *name,
void *opaque)
{
BootIndexProperty *prop = opaque;
del_boot_device_path(prop->dev, prop->suffix);
g_free(prop);
}
void device_add_bootindex_property(Object *obj, int32_t *bootindex,
const char *name, const char *suffix,
DeviceState *dev)
{
BootIndexProperty *prop = g_malloc0(sizeof(*prop));
prop->bootindex = bootindex;
prop->suffix = suffix;
prop->dev = dev;
object_property_add(obj, name, "int32",
device_get_bootindex,
device_set_bootindex,
property_release_bootindex,
prop);
/* initialize devices' bootindex property to -1 */
object_property_set_int(obj, name, -1, NULL);
}
typedef struct FWLCHSEntry FWLCHSEntry;
struct FWLCHSEntry {
QTAILQ_ENTRY(FWLCHSEntry) link;
DeviceState *dev;
char *suffix;
uint32_t lcyls;
uint32_t lheads;
uint32_t lsecs;
};
static QTAILQ_HEAD(, FWLCHSEntry) fw_lchs =
QTAILQ_HEAD_INITIALIZER(fw_lchs);
void add_boot_device_lchs(DeviceState *dev, const char *suffix,
uint32_t lcyls, uint32_t lheads, uint32_t lsecs)
{
FWLCHSEntry *node;
if (!lcyls && !lheads && !lsecs) {
return;
}
assert(dev != NULL || suffix != NULL);
node = g_new0(FWLCHSEntry, 1);
node->suffix = g_strdup(suffix);
node->dev = dev;
node->lcyls = lcyls;
node->lheads = lheads;
node->lsecs = lsecs;
QTAILQ_INSERT_TAIL(&fw_lchs, node, link);
}
void del_boot_device_lchs(DeviceState *dev, const char *suffix)
{
FWLCHSEntry *i;
if (dev == NULL) {
return;
}
QTAILQ_FOREACH(i, &fw_lchs, link) {
if ((!suffix || !g_strcmp0(i->suffix, suffix)) &&
i->dev == dev) {
QTAILQ_REMOVE(&fw_lchs, i, link);
g_free(i->suffix);
g_free(i);
break;
}
}
}
char *get_boot_devices_lchs_list(size_t *size)
{
FWLCHSEntry *i;
size_t total = 0;
char *list = NULL;
QTAILQ_FOREACH(i, &fw_lchs, link) {
char *bootpath;
char *chs_string;
size_t len;
bootpath = get_boot_device_path(i->dev, false, i->suffix);
chs_string = g_strdup_printf("%s %" PRIu32 " %" PRIu32 " %" PRIu32,
bootpath, i->lcyls, i->lheads, i->lsecs);
if (total) {
list[total - 1] = '\n';
}
len = strlen(chs_string) + 1;
list = g_realloc(list, total + len);
memcpy(&list[total], chs_string, len);
total += len;
g_free(chs_string);
g_free(bootpath);
}
*size = total;
return list;
}
+275
View File
@@ -0,0 +1,275 @@
/*
* 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 "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 "system/cpu-timers.h"
#include "system/cpu-timers-internal.h"
#include "exec/icount.h"
/* clock and ticks */
static int64_t cpu_get_ticks_locked(void)
{
int64_t ticks = timers_state.cpu_ticks_offset;
if (timers_state.cpu_ticks_enabled) {
ticks += cpu_get_host_ticks();
}
if (timers_state.cpu_ticks_prev > ticks) {
/* Non increasing ticks may happen if the host uses software suspend. */
timers_state.cpu_ticks_offset += timers_state.cpu_ticks_prev - ticks;
ticks = timers_state.cpu_ticks_prev;
}
timers_state.cpu_ticks_prev = ticks;
return ticks;
}
/*
* return the time elapsed in VM between vm_start and vm_stop.
* cpu_get_ticks() uses units of the host CPU cycle counter.
*/
int64_t cpu_get_ticks(void)
{
int64_t ticks;
qemu_spin_lock(&timers_state.vm_clock_lock);
ticks = cpu_get_ticks_locked();
qemu_spin_unlock(&timers_state.vm_clock_lock);
return ticks;
}
int64_t cpu_get_clock_locked(void)
{
int64_t time;
time = timers_state.cpu_clock_offset;
if (timers_state.cpu_ticks_enabled) {
time += get_clock();
}
return time;
}
/*
* Return the monotonic time elapsed in VM, i.e.,
* the time between vm_start and vm_stop
*/
int64_t cpu_get_clock(void)
{
int64_t ti;
unsigned start;
do {
start = seqlock_read_begin(&timers_state.vm_clock_seqlock);
ti = cpu_get_clock_locked();
} while (seqlock_read_retry(&timers_state.vm_clock_seqlock, start));
return ti;
}
/*
* enable cpu_get_ticks()
* Caller must hold BQL which serves as mutex for vm_clock_seqlock.
*/
void cpu_enable_ticks(void)
{
seqlock_write_lock(&timers_state.vm_clock_seqlock,
&timers_state.vm_clock_lock);
if (!timers_state.cpu_ticks_enabled) {
timers_state.cpu_ticks_offset -= cpu_get_host_ticks();
timers_state.cpu_clock_offset -= get_clock();
timers_state.cpu_ticks_enabled = 1;
}
seqlock_write_unlock(&timers_state.vm_clock_seqlock,
&timers_state.vm_clock_lock);
}
/*
* disable cpu_get_ticks() : the clock is stopped. You must not call
* cpu_get_ticks() after that.
* Caller must hold BQL which serves as mutex for vm_clock_seqlock.
*/
void cpu_disable_ticks(void)
{
seqlock_write_lock(&timers_state.vm_clock_seqlock,
&timers_state.vm_clock_lock);
if (timers_state.cpu_ticks_enabled) {
timers_state.cpu_ticks_offset += cpu_get_host_ticks();
timers_state.cpu_clock_offset = cpu_get_clock_locked();
timers_state.cpu_ticks_enabled = 0;
}
seqlock_write_unlock(&timers_state.vm_clock_seqlock,
&timers_state.vm_clock_lock);
}
static bool icount_state_needed(void *opaque)
{
return icount_enabled();
}
static bool warp_timer_state_needed(void *opaque)
{
TimersState *s = opaque;
return s->icount_warp_timer != NULL;
}
static bool adjust_timers_state_needed(void *opaque)
{
TimersState *s = opaque;
return s->icount_rt_timer != NULL;
}
static bool icount_shift_state_needed(void *opaque)
{
return icount_enabled() == ICOUNT_ADAPTATIVE;
}
/*
* Subsection for warp timer migration is optional, because may not be created
*/
static const VMStateDescription icount_vmstate_warp_timer = {
.name = "timer/icount/warp_timer",
.version_id = 1,
.minimum_version_id = 1,
.needed = warp_timer_state_needed,
.fields = (const VMStateField[]) {
VMSTATE_INT64(vm_clock_warp_start, TimersState),
VMSTATE_TIMER_PTR(icount_warp_timer, TimersState),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription icount_vmstate_adjust_timers = {
.name = "timer/icount/timers",
.version_id = 1,
.minimum_version_id = 1,
.needed = adjust_timers_state_needed,
.fields = (const VMStateField[]) {
VMSTATE_TIMER_PTR(icount_rt_timer, TimersState),
VMSTATE_TIMER_PTR(icount_vm_timer, TimersState),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription icount_vmstate_shift = {
.name = "timer/icount/shift",
.version_id = 2,
.minimum_version_id = 2,
.needed = icount_shift_state_needed,
.fields = (const VMStateField[]) {
VMSTATE_INT16(icount_time_shift, TimersState),
VMSTATE_INT64(last_delta, TimersState),
VMSTATE_END_OF_LIST()
}
};
/*
* This is a subsection for icount migration.
*/
static const VMStateDescription icount_vmstate_timers = {
.name = "timer/icount",
.version_id = 1,
.minimum_version_id = 1,
.needed = icount_state_needed,
.fields = (const VMStateField[]) {
VMSTATE_INT64(qemu_icount_bias, TimersState),
VMSTATE_INT64(qemu_icount, TimersState),
VMSTATE_END_OF_LIST()
},
.subsections = (const VMStateDescription * const []) {
&icount_vmstate_warp_timer,
&icount_vmstate_adjust_timers,
&icount_vmstate_shift,
NULL
}
};
static const VMStateDescription vmstate_timers = {
.name = "timer",
.version_id = 2,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_INT64(cpu_ticks_offset, TimersState),
VMSTATE_UNUSED(8),
VMSTATE_INT64_V(cpu_clock_offset, TimersState, 2),
VMSTATE_END_OF_LIST()
},
.subsections = (const VMStateDescription * const []) {
&icount_vmstate_timers,
NULL
}
};
static void do_nothing(CPUState *cpu, run_on_cpu_data unused)
{
}
void qemu_timer_notify_cb(void *opaque, QEMUClockType type)
{
if (!icount_enabled() || type != QEMU_CLOCK_VIRTUAL) {
qemu_notify_event();
return;
}
if (qemu_in_vcpu_thread()) {
/*
* A CPU is currently running; send it out of the
* tcg_cpu_exec() loop so it will recalculate its
* icount deadline immediately.
*/
cpu_exit(current_cpu);
} else if (first_cpu) {
/*
* cpu_exit() is not enough to kick a halted CPU out of
* qemu_tcg_wait_io_event. async_run_on_cpu, instead,
* causes cpu_thread_is_idle to return false. This way,
* handle_icount_deadline can run.
* If we have no CPUs at all for some reason, we don't
* need to do anything.
*/
async_run_on_cpu(first_cpu, do_nothing, RUN_ON_CPU_NULL);
}
}
TimersState timers_state;
/* initialize timers state and the cpu throttle for convenience */
void cpu_timers_init(void)
{
seqlock_init(&timers_state.vm_clock_seqlock);
qemu_spin_init(&timers_state.vm_clock_lock);
vmstate_register(NULL, 0, &vmstate_timers, &timers_state);
}
+683
View File
@@ -0,0 +1,683 @@
/*
* 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/coroutine-tls.h"
#include "qapi/error.h"
#include "exec/gdbstub.h"
#include "accel/accel-cpu-ops.h"
#include "system/hw_accel.h"
#include "exec/cpu-common.h"
#include "qemu/thread.h"
#include "qemu/main-loop.h"
#include "qemu/plugin.h"
#include "system/cpus.h"
#include "qemu/guest-random.h"
#include "system/physmem.h"
#include "system/replay.h"
#include "system/runstate.h"
#include "system/cpu-timers.h"
#include "system/whpx.h"
#include "hw/core/boards.h"
#include "hw/core/hw-error.h"
#include "trace.h"
#ifdef CONFIG_LINUX
#include <sys/prctl.h>
#ifndef PR_MCE_KILL
#define PR_MCE_KILL 33
#endif
#ifndef PR_MCE_KILL_SET
#define PR_MCE_KILL_SET 1
#endif
#ifndef PR_MCE_KILL_EARLY
#define PR_MCE_KILL_EARLY 1
#endif
#endif /* CONFIG_LINUX */
/* The Big QEMU Lock (BQL) */
static QemuMutex bql;
/*
* The chosen accelerator is supposed to register this.
*/
static const AccelOpsClass *cpus_accel;
bool cpu_is_stopped(CPUState *cpu)
{
return cpu->stopped || !runstate_is_running();
}
bool cpu_work_list_empty(CPUState *cpu)
{
return QSIMPLEQ_EMPTY_ATOMIC(&cpu->work_list);
}
bool cpu_thread_is_idle(CPUState *cpu)
{
if (cpu->stop || !cpu_work_list_empty(cpu)) {
return false;
}
if (cpu_is_stopped(cpu)) {
return true;
}
if (!cpu->halted || cpu_has_work(cpu)) {
return false;
}
if (cpus_accel->cpu_thread_is_idle) {
return cpus_accel->cpu_thread_is_idle(cpu);
}
return true;
}
bool all_cpu_threads_idle(void)
{
CPUState *cpu;
CPU_FOREACH(cpu) {
if (!cpu_thread_is_idle(cpu)) {
return false;
}
}
return true;
}
/***********************************************************/
void hw_error(const char *fmt, ...)
{
va_list ap;
CPUState *cpu;
va_start(ap, fmt);
fprintf(stderr, "qemu: hardware error: ");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
CPU_FOREACH(cpu) {
fprintf(stderr, "CPU #%d:\n", cpu->cpu_index);
cpu_dump_state(cpu, stderr, CPU_DUMP_FPU);
}
va_end(ap);
abort();
}
void cpu_synchronize_all_states(void)
{
CPUState *cpu;
CPU_FOREACH(cpu) {
cpu_synchronize_state(cpu);
}
}
void cpu_synchronize_all_post_reset(void)
{
CPUState *cpu;
CPU_FOREACH(cpu) {
cpu_synchronize_post_reset(cpu);
}
}
void cpu_synchronize_all_post_init(void)
{
CPUState *cpu;
CPU_FOREACH(cpu) {
cpu_synchronize_post_init(cpu);
}
}
void cpu_synchronize_all_pre_loadvm(void)
{
CPUState *cpu;
CPU_FOREACH(cpu) {
cpu_synchronize_pre_loadvm(cpu);
}
}
void cpu_synchronize_state(CPUState *cpu)
{
if (cpus_accel->synchronize_state) {
cpus_accel->synchronize_state(cpu);
}
}
void cpu_synchronize_post_reset(CPUState *cpu)
{
if (cpus_accel->synchronize_post_reset) {
cpus_accel->synchronize_post_reset(cpu);
}
}
void cpu_synchronize_post_init(CPUState *cpu)
{
if (cpus_accel->synchronize_post_init) {
cpus_accel->synchronize_post_init(cpu);
}
}
void cpu_synchronize_pre_loadvm(CPUState *cpu)
{
if (cpus_accel->synchronize_pre_loadvm) {
cpus_accel->synchronize_pre_loadvm(cpu);
}
}
bool cpus_are_resettable(void)
{
if (cpus_accel->cpus_are_resettable) {
return cpus_accel->cpus_are_resettable();
}
return true;
}
void cpu_exec_reset_hold(CPUState *cpu)
{
if (cpus_accel->cpu_reset_hold) {
cpus_accel->cpu_reset_hold(cpu);
}
}
int64_t cpus_get_virtual_clock(void)
{
/*
* XXX
*
* need to check that cpus_accel is not NULL, because qcow2 calls
* qemu_get_clock_ns(CLOCK_VIRTUAL) without any accel initialized and
* with ticks disabled in some io-tests:
* 030 040 041 060 099 120 127 140 156 161 172 181 191 192 195 203 229 249 256 267
*
* is this expected?
*
* XXX
*/
if (cpus_accel && cpus_accel->get_virtual_clock) {
return cpus_accel->get_virtual_clock();
}
return cpu_get_clock();
}
/*
* Signal the new virtual time to the accelerator. This is only needed
* by accelerators that need to track the changes as we warp time.
*/
void cpus_set_virtual_clock(int64_t new_time)
{
if (cpus_accel && cpus_accel->set_virtual_clock) {
cpus_accel->set_virtual_clock(new_time);
}
}
/*
* return the time elapsed in VM between vm_start and vm_stop. Unless
* icount is active, cpus_get_elapsed_ticks() uses units of the host CPU cycle
* counter.
*/
int64_t cpus_get_elapsed_ticks(void)
{
if (cpus_accel->get_elapsed_ticks) {
return cpus_accel->get_elapsed_ticks();
}
return cpu_get_ticks();
}
void cpu_set_interrupt(CPUState *cpu, int mask)
{
/* Pairs with cpu_test_interrupt(). */
qatomic_or(&cpu->interrupt_request, mask);
}
void generic_handle_interrupt(CPUState *cpu, int mask)
{
cpu_set_interrupt(cpu, mask);
if (!qemu_cpu_is_self(cpu)) {
qemu_cpu_kick(cpu);
}
}
void cpu_interrupt(CPUState *cpu, int mask)
{
g_assert(bql_locked());
cpus_accel->handle_interrupt(cpu, mask);
}
bool cpu_can_run(CPUState *cpu)
{
if (cpu->stop) {
return false;
}
if (cpu_is_stopped(cpu)) {
return false;
}
return true;
}
void cpu_handle_guest_debug(CPUState *cpu)
{
if (replay_running_debug()) {
if (!cpu_single_stepping(cpu)) {
/*
* Report about the breakpoint and
* make a single step to skip it
*/
replay_breakpoint();
cpu_single_step(cpu, SSTEP_ENABLE);
} else {
cpu_single_step(cpu, 0);
}
} else {
gdb_set_stop_cpu(cpu);
qemu_system_debug_request();
cpu->stopped = true;
}
}
#ifdef CONFIG_LINUX
static void sigbus_reraise(void)
{
sigset_t set;
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = SIG_DFL;
if (!sigaction(SIGBUS, &action, NULL)) {
raise(SIGBUS);
sigemptyset(&set);
sigaddset(&set, SIGBUS);
pthread_sigmask(SIG_UNBLOCK, &set, NULL);
}
perror("Failed to re-raise SIGBUS!");
abort();
}
static void sigbus_handler(int n, siginfo_t *siginfo, void *ctx)
{
if (siginfo->si_code != BUS_MCEERR_AO && siginfo->si_code != BUS_MCEERR_AR) {
sigbus_reraise();
}
if (current_cpu) {
/* Called asynchronously in VCPU thread. */
if (kvm_on_sigbus_vcpu(current_cpu, siginfo->si_code, siginfo->si_addr)) {
sigbus_reraise();
}
} else {
/* Called synchronously (via signalfd) in main thread. */
if (kvm_on_sigbus(siginfo->si_code, siginfo->si_addr)) {
sigbus_reraise();
}
}
}
static void qemu_init_sigbus(void)
{
struct sigaction action;
/*
* ALERT: when modifying this, take care that SIGBUS forwarding in
* qemu_prealloc_mem() will continue working as expected.
*/
memset(&action, 0, sizeof(action));
action.sa_flags = SA_SIGINFO;
action.sa_sigaction = sigbus_handler;
sigaction(SIGBUS, &action, NULL);
prctl(PR_MCE_KILL, PR_MCE_KILL_SET, PR_MCE_KILL_EARLY, 0, 0);
}
#else /* !CONFIG_LINUX */
static void qemu_init_sigbus(void)
{
}
#endif /* !CONFIG_LINUX */
static QemuThread io_thread;
/* cpu creation */
static QemuCond qemu_cpu_cond;
/* system init */
static QemuCond qemu_pause_cond;
void qemu_init_cpu_loop(void)
{
qemu_init_sigbus();
qemu_cond_init(&qemu_cpu_cond);
qemu_cond_init(&qemu_pause_cond);
qemu_mutex_init(&bql);
qemu_thread_get_self(&io_thread);
}
void run_on_cpu(CPUState *cpu, run_on_cpu_func func, run_on_cpu_data data)
{
do_run_on_cpu(cpu, func, data, &bql);
}
static void qemu_cpu_stop(CPUState *cpu, bool exit)
{
g_assert(qemu_cpu_is_self(cpu));
cpu->stop = false;
cpu->stopped = true;
if (exit) {
cpu_exit(cpu);
}
qemu_cond_broadcast(&qemu_pause_cond);
}
void qemu_process_cpu_events_common(CPUState *cpu)
{
qatomic_set_mb(&cpu->thread_kicked, false);
if (cpu->stop) {
qemu_cpu_stop(cpu, false);
}
process_queued_cpu_work(cpu);
}
void qemu_process_cpu_events(CPUState *cpu)
{
bool slept = false;
qatomic_set(&cpu->exit_request, false);
while (cpu_thread_is_idle(cpu)) {
if (!slept) {
slept = true;
qemu_plugin_vcpu_idle_cb(cpu);
}
qemu_cond_wait(cpu->halt_cond, &bql);
}
if (slept) {
qemu_plugin_vcpu_resume_cb(cpu);
}
qemu_process_cpu_events_common(cpu);
}
void cpus_kick_thread(CPUState *cpu)
{
if (qatomic_read(&cpu->thread_kicked)) {
return;
}
qatomic_set(&cpu->thread_kicked, true);
#ifndef _WIN32
int err = pthread_kill(cpu->thread->thread, SIG_IPI);
if (err && err != ESRCH) {
fprintf(stderr, "qemu:%s: %s", __func__, strerror(err));
exit(1);
}
#else
qemu_sem_post(&cpu->sem);
#endif
}
void qemu_cpu_kick(CPUState *cpu)
{
qemu_cond_broadcast(cpu->halt_cond);
if (cpus_accel->kick_vcpu_thread) {
cpus_accel->kick_vcpu_thread(cpu);
} else { /* default */
cpus_kick_thread(cpu);
}
}
void qemu_cpu_kick_self(void)
{
assert(current_cpu);
cpus_kick_thread(current_cpu);
}
bool qemu_cpu_is_self(CPUState *cpu)
{
return qemu_thread_is_self(cpu->thread);
}
bool qemu_in_vcpu_thread(void)
{
return current_cpu && qemu_cpu_is_self(current_cpu);
}
QEMU_DEFINE_STATIC_CO_TLS(bool, bql_locked)
bool mutex_is_bql(QemuMutex *mutex)
{
return mutex == &bql;
}
void bql_update_status(bool locked)
{
/* This function should only be used when an update happened.. */
assert(bql_locked() != locked);
set_bql_locked(locked);
}
static uint32_t bql_unlock_blocked;
void bql_block_unlock(bool increase)
{
uint32_t new_value;
assert(bql_locked());
/* check for overflow! */
new_value = bql_unlock_blocked + increase - !increase;
assert((new_value > bql_unlock_blocked) == increase);
bql_unlock_blocked = new_value;
}
bool bql_locked(void)
{
return get_bql_locked();
}
bool qemu_in_main_thread(void)
{
return bql_locked();
}
void rust_bql_mock_lock(void)
{
error_report("This function should be used only from tests");
abort();
}
/*
* The BQL is taken from so many places that it is worth profiling the
* callers directly, instead of funneling them all through a single function.
*/
void bql_lock_impl(const char *file, int line)
{
QemuMutexLockFunc bql_lock_fn = qatomic_read(&bql_mutex_lock_func);
g_assert(!bql_locked());
bql_lock_fn(&bql, file, line);
}
void bql_unlock(void)
{
g_assert(bql_locked());
g_assert(!bql_unlock_blocked);
qemu_mutex_unlock(&bql);
}
void qemu_cond_wait_bql(QemuCond *cond)
{
qemu_cond_wait(cond, &bql);
}
void qemu_cond_timedwait_bql(QemuCond *cond, int ms)
{
qemu_cond_timedwait(cond, &bql, ms);
}
/* signal CPU creation */
void cpu_thread_signal_created(CPUState *cpu)
{
cpu->created = true;
qemu_cond_signal(&qemu_cpu_cond);
}
/* signal CPU destruction */
void cpu_thread_signal_destroyed(CPUState *cpu)
{
cpu->created = false;
qemu_cond_signal(&qemu_cpu_cond);
}
void cpu_pause(CPUState *cpu)
{
if (qemu_cpu_is_self(cpu)) {
qemu_cpu_stop(cpu, true);
} else {
cpu->stop = true;
cpu_exit(cpu);
}
}
void cpu_resume(CPUState *cpu)
{
cpu->exception_index = -1;
cpu->stop = false;
cpu->stopped = false;
qemu_cpu_kick(cpu);
}
static bool all_vcpus_paused(void)
{
CPUState *cpu;
CPU_FOREACH(cpu) {
if (!cpu->stopped) {
return false;
}
}
return true;
}
void pause_all_vcpus(void)
{
CPUState *cpu;
qemu_clock_enable(QEMU_CLOCK_VIRTUAL, false);
CPU_FOREACH(cpu) {
cpu_pause(cpu);
}
/* We need to drop the replay_lock so any vCPU threads woken up
* can finish their replay tasks
*/
replay_mutex_unlock();
while (!all_vcpus_paused()) {
qemu_cond_wait(&qemu_pause_cond, &bql);
/* FIXME: is this needed? */
CPU_FOREACH(cpu) {
qemu_cpu_kick(cpu);
}
}
bql_unlock();
replay_mutex_lock();
bql_lock();
}
void resume_all_vcpus(void)
{
CPUState *cpu;
if (!runstate_is_running()) {
return;
}
qemu_clock_enable(QEMU_CLOCK_VIRTUAL, true);
CPU_FOREACH(cpu) {
cpu_resume(cpu);
}
}
void cpu_remove_sync(CPUState *cpu)
{
cpu->stop = true;
cpu->unplug = true;
cpu_exit(cpu);
bql_unlock();
qemu_thread_join(cpu->thread);
bql_lock();
}
void cpus_register_accel(const AccelOpsClass *ops)
{
assert(ops != NULL);
assert(ops->create_vcpu_thread != NULL); /* mandatory */
assert(ops->handle_interrupt);
cpus_accel = ops;
}
const AccelOpsClass *cpus_get_accel(void)
{
/* broken if we call this early */
assert(cpus_accel);
return cpus_accel;
}
void qemu_init_vcpu(CPUState *cpu)
{
MachineState *ms = MACHINE(qdev_get_machine());
cpu->nr_threads = ms->smp.threads;
cpu->stopped = true;
cpu->random_seed = qemu_guest_random_seed_thread_part1();
if (!cpu->as) {
/* If the target cpu hasn't set up any address spaces itself,
* give it the default one.
*/
cpu_address_space_init(cpu, 0, "cpu-memory", cpu->memory);
}
/* accelerators all implement the AccelOpsClass */
g_assert(cpus_accel != NULL && cpus_accel->create_vcpu_thread != NULL);
cpus_accel->create_vcpu_thread(cpu);
while (!cpu->created) {
qemu_cond_wait(&qemu_cpu_cond, &bql);
}
}
void cpu_stop_current(void)
{
if (current_cpu) {
current_cpu->stop = true;
cpu_exit(current_cpu);
}
}
+13
View File
@@ -0,0 +1,13 @@
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qapi/qapi-commands-machine.h"
#ifdef CONFIG_FDT
void qmp_dumpdtb(const char *filename, Error **errp)
{
ERRP_GUARD();
error_setg(errp, "This machine doesn't have an FDT");
error_append_hint(errp, "(this machine type definitely doesn't use FDT)\n");
}
#endif
+678
View File
@@ -0,0 +1,678 @@
/*
* Functions to help device tree manipulation using libfdt.
* It also provides functions to read entries from device tree proc
* interface.
*
* Copyright 2008 IBM Corporation.
* Authors: Jerone Young <[email protected]>
* Hollis Blanchard <[email protected]>
*
* This work is licensed under the GNU GPL license version 2 or later.
*
*/
#include "qemu/osdep.h"
#ifdef CONFIG_LINUX
#include <dirent.h>
#endif
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "qemu/option.h"
#include "qemu/bswap.h"
#include "qemu/cutils.h"
#include "qemu/guest-random.h"
#include "system/device_tree.h"
#include "hw/core/loader.h"
#include "hw/core/boards.h"
#include "qemu/config-file.h"
#include "qapi/qapi-commands-machine.h"
#include "qobject/qdict.h"
#include <libfdt.h>
#define FDT_MAX_SIZE 0x100000
void *create_device_tree(int *sizep)
{
void *fdt;
int ret;
*sizep = FDT_MAX_SIZE;
fdt = g_malloc0(FDT_MAX_SIZE);
ret = fdt_create(fdt, FDT_MAX_SIZE);
if (ret < 0) {
goto fail;
}
ret = fdt_finish_reservemap(fdt);
if (ret < 0) {
goto fail;
}
ret = fdt_begin_node(fdt, "");
if (ret < 0) {
goto fail;
}
ret = fdt_end_node(fdt);
if (ret < 0) {
goto fail;
}
ret = fdt_finish(fdt);
if (ret < 0) {
goto fail;
}
ret = fdt_open_into(fdt, fdt, *sizep);
if (ret) {
error_report("%s: Unable to copy device tree into memory: %s",
__func__, fdt_strerror(ret));
exit(1);
}
return fdt;
fail:
error_report("%s Couldn't create dt: %s", __func__, fdt_strerror(ret));
exit(1);
}
void *load_device_tree(const char *filename_path, int *sizep)
{
int dt_size;
int dt_file_load_size;
int ret;
void *fdt = NULL;
*sizep = 0;
dt_size = get_image_size(filename_path, NULL);
if (dt_size < 0) {
error_report("Unable to get size of device tree file '%s'",
filename_path);
goto fail;
}
if (dt_size > INT_MAX / 2 - 10000) {
error_report("Device tree file '%s' is too large", filename_path);
goto fail;
}
/* Expand to 2x size to give enough room for manipulation. */
dt_size += 10000;
dt_size *= 2;
/* First allocate space in qemu for device tree */
fdt = g_malloc0(dt_size);
dt_file_load_size = load_image_size(filename_path, fdt, dt_size);
if (dt_file_load_size < 0) {
error_report("Unable to open device tree file '%s'",
filename_path);
goto fail;
}
ret = fdt_open_into(fdt, fdt, dt_size);
if (ret) {
error_report("%s: Unable to copy device tree into memory: %s",
__func__, fdt_strerror(ret));
goto fail;
}
/* Check sanity of device tree */
if (fdt_check_header(fdt)) {
error_report("Device tree file loaded into memory is invalid: %s",
filename_path);
goto fail;
}
*sizep = dt_size;
return fdt;
fail:
g_free(fdt);
return NULL;
}
#ifdef CONFIG_LINUX
#define SYSFS_DT_BASEDIR "/proc/device-tree"
/**
* read_fstree: this function is inspired from dtc read_fstree
* @fdt: preallocated fdt blob buffer, to be populated
* @dirname: directory to scan under SYSFS_DT_BASEDIR
* the search is recursive and the tree is searched down to the
* leaves (property files).
*
* the function asserts in case of error
*/
static void read_fstree(void *fdt, const char *dirname)
{
DIR *d;
struct dirent *de;
struct stat st;
const char *root_dir = SYSFS_DT_BASEDIR;
const char *parent_node;
if (strstr(dirname, root_dir) != dirname) {
error_report("%s: %s must be searched within %s",
__func__, dirname, root_dir);
exit(1);
}
parent_node = &dirname[strlen(SYSFS_DT_BASEDIR)];
d = opendir(dirname);
if (!d) {
error_report("%s cannot open %s", __func__, dirname);
exit(1);
}
while ((de = readdir(d)) != NULL) {
char *tmpnam;
if (!g_strcmp0(de->d_name, ".")
|| !g_strcmp0(de->d_name, "..")) {
continue;
}
tmpnam = g_strdup_printf("%s/%s", dirname, de->d_name);
if (lstat(tmpnam, &st) < 0) {
error_report("%s cannot lstat %s", __func__, tmpnam);
exit(1);
}
if (S_ISREG(st.st_mode)) {
gchar *val;
gsize len;
if (!g_file_get_contents(tmpnam, &val, &len, NULL)) {
error_report("%s not able to extract info from %s",
__func__, tmpnam);
exit(1);
}
if (strlen(parent_node) > 0) {
qemu_fdt_setprop(fdt, parent_node,
de->d_name, val, len);
} else {
qemu_fdt_setprop(fdt, "/", de->d_name, val, len);
}
g_free(val);
} else if (S_ISDIR(st.st_mode)) {
char *node_name;
node_name = g_strdup_printf("%s/%s",
parent_node, de->d_name);
qemu_fdt_add_subnode(fdt, node_name);
g_free(node_name);
read_fstree(fdt, tmpnam);
}
g_free(tmpnam);
}
closedir(d);
}
/* load_device_tree_from_sysfs: extract the dt blob from host sysfs */
void *load_device_tree_from_sysfs(void)
{
void *host_fdt;
int host_fdt_size;
host_fdt = create_device_tree(&host_fdt_size);
read_fstree(host_fdt, SYSFS_DT_BASEDIR);
if (fdt_check_header(host_fdt)) {
error_report("%s host device tree extracted into memory is invalid",
__func__);
exit(1);
}
return host_fdt;
}
#endif /* CONFIG_LINUX */
static int findnode_nofail(void *fdt, const char *node_path)
{
int offset;
offset = fdt_path_offset(fdt, node_path);
if (offset < 0) {
error_report("%s Couldn't find node %s: %s", __func__, node_path,
fdt_strerror(offset));
exit(1);
}
return offset;
}
char **qemu_fdt_node_unit_path(void *fdt, const char *name, Error **errp)
{
char *prefix = g_strdup_printf("%s@", name);
unsigned int path_len = 16, n = 0;
GSList *path_list = NULL, *iter;
const char *iter_name;
int offset, len, ret;
char **path_array;
offset = fdt_next_node(fdt, -1, NULL);
while (offset >= 0) {
iter_name = fdt_get_name(fdt, offset, &len);
if (!iter_name) {
offset = len;
break;
}
if (!strcmp(iter_name, name) || g_str_has_prefix(iter_name, prefix)) {
char *path;
path = g_malloc(path_len);
while ((ret = fdt_get_path(fdt, offset, path, path_len))
== -FDT_ERR_NOSPACE) {
path_len += 16;
path = g_realloc(path, path_len);
}
path_list = g_slist_prepend(path_list, path);
n++;
}
offset = fdt_next_node(fdt, offset, NULL);
}
g_free(prefix);
if (offset < 0 && offset != -FDT_ERR_NOTFOUND) {
error_setg(errp, "%s: abort parsing dt for %s node units: %s",
__func__, name, fdt_strerror(offset));
for (iter = path_list; iter; iter = iter->next) {
g_free(iter->data);
}
g_slist_free(path_list);
return NULL;
}
path_array = g_new(char *, n + 1);
path_array[n--] = NULL;
for (iter = path_list; iter; iter = iter->next) {
path_array[n--] = iter->data;
}
g_slist_free(path_list);
return path_array;
}
char **qemu_fdt_node_path(void *fdt, const char *name, const char *compat,
Error **errp)
{
int offset, len, ret;
const char *iter_name;
unsigned int path_len = 16, n = 0;
GSList *path_list = NULL, *iter;
char **path_array;
offset = fdt_node_offset_by_compatible(fdt, -1, compat);
while (offset >= 0) {
iter_name = fdt_get_name(fdt, offset, &len);
if (!iter_name) {
offset = len;
break;
}
if (!name || !strcmp(iter_name, name)) {
char *path;
path = g_malloc(path_len);
while ((ret = fdt_get_path(fdt, offset, path, path_len))
== -FDT_ERR_NOSPACE) {
path_len += 16;
path = g_realloc(path, path_len);
}
path_list = g_slist_prepend(path_list, path);
n++;
}
offset = fdt_node_offset_by_compatible(fdt, offset, compat);
}
if (offset < 0 && offset != -FDT_ERR_NOTFOUND) {
error_setg(errp, "%s: abort parsing dt for %s/%s: %s",
__func__, name, compat, fdt_strerror(offset));
for (iter = path_list; iter; iter = iter->next) {
g_free(iter->data);
}
g_slist_free(path_list);
return NULL;
}
path_array = g_new(char *, n + 1);
path_array[n--] = NULL;
for (iter = path_list; iter; iter = iter->next) {
path_array[n--] = iter->data;
}
g_slist_free(path_list);
return path_array;
}
int qemu_fdt_setprop(void *fdt, const char *node_path,
const char *property, const void *val, int size)
{
int r;
r = fdt_setprop(fdt, findnode_nofail(fdt, node_path), property, val, size);
if (r < 0) {
error_report("%s: Couldn't set %s/%s: %s", __func__, node_path,
property, fdt_strerror(r));
exit(1);
}
return r;
}
int qemu_fdt_setprop_cell(void *fdt, const char *node_path,
const char *property, uint32_t val)
{
int r;
r = fdt_setprop_cell(fdt, findnode_nofail(fdt, node_path), property, val);
if (r < 0) {
error_report("%s: Couldn't set %s/%s = %#08x: %s", __func__,
node_path, property, val, fdt_strerror(r));
exit(1);
}
return r;
}
int qemu_fdt_setprop_u64(void *fdt, const char *node_path,
const char *property, uint64_t val)
{
val = cpu_to_be64(val);
return qemu_fdt_setprop(fdt, node_path, property, &val, sizeof(val));
}
int qemu_fdt_setprop_string(void *fdt, const char *node_path,
const char *property, const char *string)
{
int r;
r = fdt_setprop_string(fdt, findnode_nofail(fdt, node_path), property, string);
if (r < 0) {
error_report("%s: Couldn't set %s/%s = %s: %s", __func__,
node_path, property, string, fdt_strerror(r));
exit(1);
}
return r;
}
/*
* libfdt doesn't allow us to add string arrays directly but they are
* test a series of null terminated strings with a length. We build
* the string up here so we can calculate the final length.
*/
int qemu_fdt_setprop_string_array(void *fdt, const char *node_path,
const char *prop, char **array, int len)
{
int ret, i, total_len = 0;
char *str, *p;
for (i = 0; i < len; i++) {
total_len += strlen(array[i]) + 1;
}
p = str = g_malloc0(total_len);
for (i = 0; i < len; i++) {
int offset = strlen(array[i]) + 1;
pstrcpy(p, offset, array[i]);
p += offset;
}
ret = qemu_fdt_setprop(fdt, node_path, prop, str, total_len);
g_free(str);
return ret;
}
const void *qemu_fdt_getprop(void *fdt, const char *node_path,
const char *property, int *lenp, Error **errp)
{
int len;
const void *r;
if (!lenp) {
lenp = &len;
}
r = fdt_getprop(fdt, findnode_nofail(fdt, node_path), property, lenp);
if (!r) {
error_setg(errp, "%s: Couldn't get %s/%s: %s", __func__,
node_path, property, fdt_strerror(*lenp));
}
return r;
}
uint32_t qemu_fdt_getprop_cell(void *fdt, const char *node_path,
const char *property, int *lenp, Error **errp)
{
int len;
const uint32_t *p;
if (!lenp) {
lenp = &len;
}
p = qemu_fdt_getprop(fdt, node_path, property, lenp, errp);
if (!p) {
return 0;
} else if (*lenp != 4) {
error_setg(errp, "%s: %s/%s not 4 bytes long (not a cell?)",
__func__, node_path, property);
*lenp = -EINVAL;
return 0;
}
return be32_to_cpu(*p);
}
uint32_t qemu_fdt_get_phandle(void *fdt, const char *path)
{
uint32_t r;
r = fdt_get_phandle(fdt, findnode_nofail(fdt, path));
if (r == 0) {
error_report("%s: Couldn't get phandle for %s: %s", __func__,
path, fdt_strerror(r));
exit(1);
}
return r;
}
int qemu_fdt_setprop_phandle(void *fdt, const char *node_path,
const char *property,
const char *target_node_path)
{
uint32_t phandle = qemu_fdt_get_phandle(fdt, target_node_path);
return qemu_fdt_setprop_cell(fdt, node_path, property, phandle);
}
uint32_t qemu_fdt_alloc_phandle(void *fdt)
{
static int phandle = 0x0;
/*
* We need to find out if the user gave us special instruction at
* which phandle id to start allocating phandles.
*/
if (!phandle) {
phandle = machine_phandle_start(current_machine);
}
if (!phandle) {
/*
* None or invalid phandle given on the command line, so fall back to
* default starting point.
*/
phandle = 0x8000;
}
return phandle++;
}
int qemu_fdt_nop_node(void *fdt, const char *node_path)
{
int r;
r = fdt_nop_node(fdt, findnode_nofail(fdt, node_path));
if (r < 0) {
error_report("%s: Couldn't nop node %s: %s", __func__, node_path,
fdt_strerror(r));
exit(1);
}
return r;
}
int qemu_fdt_add_subnode(void *fdt, const char *name)
{
char *dupname = g_strdup(name);
char *basename = strrchr(dupname, '/');
int retval;
int parent = 0;
if (!basename) {
g_free(dupname);
return -1;
}
basename[0] = '\0';
basename++;
if (dupname[0]) {
parent = findnode_nofail(fdt, dupname);
}
retval = fdt_add_subnode(fdt, parent, basename);
if (retval < 0) {
error_report("%s: Failed to create subnode %s: %s",
__func__, name, fdt_strerror(retval));
exit(1);
}
g_free(dupname);
return retval;
}
/*
* qemu_fdt_add_path: Like qemu_fdt_add_subnode(), but will add
* all missing subnodes from the given path.
*/
int qemu_fdt_add_path(void *fdt, const char *path)
{
const char *name;
int namelen, retval;
int parent = 0;
if (path[0] != '/') {
return -1;
}
do {
name = path + 1;
path = strchr(name, '/');
namelen = path != NULL ? path - name : strlen(name);
retval = fdt_subnode_offset_namelen(fdt, parent, name, namelen);
if (retval < 0 && retval != -FDT_ERR_NOTFOUND) {
error_report("%s: Unexpected error in finding subnode %.*s: %s",
__func__, namelen, name, fdt_strerror(retval));
exit(1);
} else if (retval == -FDT_ERR_NOTFOUND) {
retval = fdt_add_subnode_namelen(fdt, parent, name, namelen);
if (retval < 0) {
error_report("%s: Failed to create subnode %.*s: %s",
__func__, namelen, name, fdt_strerror(retval));
exit(1);
}
}
parent = retval;
} while (path);
return retval;
}
int qemu_fdt_setprop_sized_cells_from_array(void *fdt,
const char *node_path,
const char *property,
int numvalues,
uint64_t *values)
{
uint32_t *propcells;
uint64_t value;
int cellnum, vnum, ncells;
uint32_t hival;
int ret;
propcells = g_new0(uint32_t, numvalues * 2);
cellnum = 0;
for (vnum = 0; vnum < numvalues; vnum++) {
ncells = values[vnum * 2];
if (ncells != 1 && ncells != 2) {
ret = -1;
goto out;
}
value = values[vnum * 2 + 1];
hival = cpu_to_be32(value >> 32);
if (ncells > 1) {
propcells[cellnum++] = hival;
} else if (hival != 0) {
ret = -1;
goto out;
}
propcells[cellnum++] = cpu_to_be32(value);
}
ret = qemu_fdt_setprop(fdt, node_path, property, propcells,
cellnum * sizeof(uint32_t));
out:
g_free(propcells);
return ret;
}
void qmp_dumpdtb(const char *filename, Error **errp)
{
ERRP_GUARD();
g_autoptr(GError) err = NULL;
uint32_t size;
if (!current_machine->fdt) {
error_setg(errp, "This machine doesn't have an FDT");
error_append_hint(errp,
"(Perhaps it doesn't support FDT at all, or perhaps "
"you need to provide an FDT with the -fdt option?)\n");
return;
}
size = fdt_totalsize(current_machine->fdt);
g_assert(size > 0);
if (!g_file_set_contents(filename, current_machine->fdt, size, &err)) {
error_setg(errp, "Error saving FDT to file %s: %s",
filename, err->message);
}
}
void qemu_fdt_randomize_seeds(void *fdt)
{
int noffset, poffset, len;
const char *name;
uint8_t *data;
for (noffset = fdt_next_node(fdt, 0, NULL);
noffset >= 0;
noffset = fdt_next_node(fdt, noffset, NULL)) {
for (poffset = fdt_first_property_offset(fdt, noffset);
poffset >= 0;
poffset = fdt_next_property_offset(fdt, poffset)) {
data = (uint8_t *)fdt_getprop_by_offset(fdt, poffset, &name, &len);
if (!data || strcmp(name, "rng-seed"))
continue;
qemu_guest_getrandom_nofail(data, len);
}
}
}
+74
View File
@@ -0,0 +1,74 @@
/*
* HMP commands related to migration dirty page rate limit
*
* Copyright (c) 2022 CHINA TELECOM CO.,LTD.
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qapi/qapi-commands-migration.h"
#include "qobject/qdict.h"
#include "monitor/hmp.h"
#include "monitor/monitor.h"
#include "system/dirtylimit.h"
void hmp_cancel_vcpu_dirty_limit(Monitor *mon, const QDict *qdict)
{
int64_t cpu_index = qdict_get_try_int(qdict, "cpu_index", -1);
Error *err = NULL;
qmp_cancel_vcpu_dirty_limit(!!(cpu_index != -1), cpu_index, &err);
if (err) {
hmp_handle_error(mon, err);
return;
}
monitor_printf(mon, "[Please use 'info vcpu_dirty_limit' to query "
"dirty limit for virtual CPU]\n");
}
void hmp_set_vcpu_dirty_limit(Monitor *mon, const QDict *qdict)
{
int64_t dirty_rate = qdict_get_int(qdict, "dirty_rate");
int64_t cpu_index = qdict_get_try_int(qdict, "cpu_index", -1);
Error *err = NULL;
if (dirty_rate < 0) {
error_setg(&err, "invalid dirty page limit %" PRId64, dirty_rate);
goto out;
}
qmp_set_vcpu_dirty_limit(!!(cpu_index != -1), cpu_index, dirty_rate, &err);
out:
hmp_handle_error(mon, err);
}
void hmp_info_vcpu_dirty_limit(Monitor *mon, const QDict *qdict)
{
DirtyLimitInfoList *info;
g_autoptr(DirtyLimitInfoList) head = NULL;
Error *err = NULL;
if (!dirtylimit_in_service()) {
monitor_printf(mon, "Dirty page limit not enabled!\n");
return;
}
head = qmp_query_vcpu_dirty_limit(&err);
if (err) {
hmp_handle_error(mon, err);
return;
}
for (info = head; info != NULL; info = info->next) {
monitor_printf(mon, "vcpu[%"PRIi64"], limit rate %"PRIi64 " (MB/s),"
" current rate %"PRIi64 " (MB/s)\n",
info->value->cpu_index,
info->value->limit_rate,
info->value->current_rate);
}
}
+614
View File
@@ -0,0 +1,614 @@
/*
* Dirty page rate limit implementation code
*
* Copyright (c) 2022 CHINA TELECOM CO.,LTD.
*
* Authors:
* Hyman Huang(黄勇) <[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/main-loop.h"
#include "qapi/qapi-commands-migration.h"
#include "qobject/qdict.h"
#include "qapi/error.h"
#include "system/dirtyrate.h"
#include "system/dirtylimit.h"
#include "system/memory.h"
#include "exec/target_page.h"
#include "hw/core/boards.h"
#include "system/kvm.h"
#include "trace.h"
#include "migration/misc.h"
/*
* Dirtylimit stop working if dirty page rate error
* value less than DIRTYLIMIT_TOLERANCE_RANGE
*/
#define DIRTYLIMIT_TOLERANCE_RANGE 25 /* MB/s */
/*
* Plus or minus vcpu sleep time linearly if dirty
* page rate error value percentage over
* DIRTYLIMIT_LINEAR_ADJUSTMENT_PCT.
* Otherwise, plus or minus a fixed vcpu sleep time.
*/
#define DIRTYLIMIT_LINEAR_ADJUSTMENT_PCT 50
/*
* Max vcpu sleep time percentage during a cycle
* composed of dirty ring full and sleep time.
*/
#define DIRTYLIMIT_THROTTLE_PCT_MAX 99
struct {
VcpuStat stat;
bool running;
QemuThread thread;
} *vcpu_dirty_rate_stat;
typedef struct VcpuDirtyLimitState {
int cpu_index;
bool enabled;
/*
* Quota dirty page rate, unit is MB/s
* zero if not enabled.
*/
uint64_t quota;
} VcpuDirtyLimitState;
struct {
VcpuDirtyLimitState *states;
/* Max cpus number configured by user */
int max_cpus;
/* Number of vcpu under dirtylimit */
int limited_nvcpu;
} *dirtylimit_state;
/* protect dirtylimit_state */
static QemuMutex dirtylimit_mutex;
/* dirtylimit thread quit if dirtylimit_quit is true */
static bool dirtylimit_quit;
static void vcpu_dirty_rate_stat_collect(void)
{
VcpuStat stat;
int i = 0;
int64_t period = DIRTYLIMIT_CALC_TIME_MS;
if (migrate_dirty_limit() && migration_is_running()) {
period = migrate_vcpu_dirty_limit_period();
}
/* calculate vcpu dirtyrate */
vcpu_calculate_dirtyrate(period,
&stat,
GLOBAL_DIRTY_LIMIT,
false);
for (i = 0; i < stat.nvcpu; i++) {
vcpu_dirty_rate_stat->stat.rates[i].id = i;
vcpu_dirty_rate_stat->stat.rates[i].dirty_rate =
stat.rates[i].dirty_rate;
}
g_free(stat.rates);
}
static void *vcpu_dirty_rate_stat_thread(void *opaque)
{
rcu_register_thread();
/* start log sync */
global_dirty_log_change(GLOBAL_DIRTY_LIMIT, true);
while (qatomic_read(&vcpu_dirty_rate_stat->running)) {
vcpu_dirty_rate_stat_collect();
if (dirtylimit_in_service()) {
dirtylimit_process();
}
}
/* stop log sync */
global_dirty_log_change(GLOBAL_DIRTY_LIMIT, false);
rcu_unregister_thread();
return NULL;
}
int64_t vcpu_dirty_rate_get(int cpu_index)
{
DirtyRateVcpu *rates = vcpu_dirty_rate_stat->stat.rates;
return qatomic_read(&rates[cpu_index].dirty_rate);
}
void vcpu_dirty_rate_stat_start(void)
{
if (qatomic_read(&vcpu_dirty_rate_stat->running)) {
return;
}
qatomic_set(&vcpu_dirty_rate_stat->running, 1);
qemu_thread_create(&vcpu_dirty_rate_stat->thread,
"dirtyrate-stat",
vcpu_dirty_rate_stat_thread,
NULL,
QEMU_THREAD_JOINABLE);
}
void vcpu_dirty_rate_stat_stop(void)
{
qatomic_set(&vcpu_dirty_rate_stat->running, 0);
dirtylimit_state_unlock();
bql_unlock();
qemu_thread_join(&vcpu_dirty_rate_stat->thread);
bql_lock();
dirtylimit_state_lock();
}
void vcpu_dirty_rate_stat_initialize(void)
{
MachineState *ms = MACHINE(qdev_get_machine());
int max_cpus = ms->smp.max_cpus;
vcpu_dirty_rate_stat =
g_malloc0(sizeof(*vcpu_dirty_rate_stat));
vcpu_dirty_rate_stat->stat.nvcpu = max_cpus;
vcpu_dirty_rate_stat->stat.rates =
g_new0(DirtyRateVcpu, max_cpus);
vcpu_dirty_rate_stat->running = false;
}
void vcpu_dirty_rate_stat_finalize(void)
{
g_free(vcpu_dirty_rate_stat->stat.rates);
vcpu_dirty_rate_stat->stat.rates = NULL;
g_free(vcpu_dirty_rate_stat);
vcpu_dirty_rate_stat = NULL;
}
void dirtylimit_state_lock(void)
{
qemu_mutex_lock(&dirtylimit_mutex);
}
void dirtylimit_state_unlock(void)
{
qemu_mutex_unlock(&dirtylimit_mutex);
}
static void
__attribute__((__constructor__)) dirtylimit_mutex_init(void)
{
qemu_mutex_init(&dirtylimit_mutex);
}
static inline VcpuDirtyLimitState *dirtylimit_vcpu_get_state(int cpu_index)
{
return &dirtylimit_state->states[cpu_index];
}
void dirtylimit_state_initialize(void)
{
MachineState *ms = MACHINE(qdev_get_machine());
int max_cpus = ms->smp.max_cpus;
int i;
dirtylimit_state = g_malloc0(sizeof(*dirtylimit_state));
dirtylimit_state->states =
g_new0(VcpuDirtyLimitState, max_cpus);
for (i = 0; i < max_cpus; i++) {
dirtylimit_state->states[i].cpu_index = i;
}
dirtylimit_state->max_cpus = max_cpus;
trace_dirtylimit_state_initialize(max_cpus);
}
void dirtylimit_state_finalize(void)
{
g_free(dirtylimit_state->states);
dirtylimit_state->states = NULL;
g_free(dirtylimit_state);
dirtylimit_state = NULL;
trace_dirtylimit_state_finalize();
}
bool dirtylimit_in_service(void)
{
return !!dirtylimit_state;
}
bool dirtylimit_vcpu_index_valid(int cpu_index)
{
MachineState *ms = MACHINE(qdev_get_machine());
return !(cpu_index < 0 ||
cpu_index >= ms->smp.max_cpus);
}
static uint64_t dirtylimit_dirty_ring_full_time(uint64_t dirtyrate)
{
static uint64_t max_dirtyrate;
uint64_t dirty_ring_size_MiB;
dirty_ring_size_MiB = qemu_target_pages_to_MiB(kvm_dirty_ring_size());
if (max_dirtyrate < dirtyrate) {
max_dirtyrate = dirtyrate;
}
return dirty_ring_size_MiB * 1000000 / max_dirtyrate;
}
static inline bool dirtylimit_done(uint64_t quota,
uint64_t current)
{
uint64_t min, max;
min = MIN(quota, current);
max = MAX(quota, current);
return ((max - min) <= DIRTYLIMIT_TOLERANCE_RANGE) ? true : false;
}
static inline bool
dirtylimit_need_linear_adjustment(uint64_t quota,
uint64_t current)
{
uint64_t min, max;
min = MIN(quota, current);
max = MAX(quota, current);
return ((max - min) * 100 / max) > DIRTYLIMIT_LINEAR_ADJUSTMENT_PCT;
}
static void dirtylimit_set_throttle(CPUState *cpu,
uint64_t quota,
uint64_t current)
{
int64_t ring_full_time_us = 0;
uint64_t sleep_pct = 0;
uint64_t throttle_us = 0;
if (current == 0) {
cpu->throttle_us_per_full = 0;
return;
}
ring_full_time_us = dirtylimit_dirty_ring_full_time(current);
if (dirtylimit_need_linear_adjustment(quota, current)) {
if (quota < current) {
sleep_pct = (current - quota) * 100 / current;
throttle_us =
ring_full_time_us * sleep_pct / (double)(100 - sleep_pct);
cpu->throttle_us_per_full += throttle_us;
} else {
sleep_pct = (quota - current) * 100 / quota;
throttle_us =
ring_full_time_us * sleep_pct / (double)(100 - sleep_pct);
cpu->throttle_us_per_full -= throttle_us;
}
trace_dirtylimit_throttle_pct(cpu->cpu_index,
sleep_pct,
throttle_us);
} else {
if (quota < current) {
cpu->throttle_us_per_full += ring_full_time_us / 10;
} else {
cpu->throttle_us_per_full -= ring_full_time_us / 10;
}
}
/*
* TODO: in the big kvm_dirty_ring_size case (eg: 65536, or other scenario),
* current dirty page rate may never reach the quota, we should stop
* increasing sleep time?
*/
cpu->throttle_us_per_full = MIN(cpu->throttle_us_per_full,
ring_full_time_us * DIRTYLIMIT_THROTTLE_PCT_MAX);
cpu->throttle_us_per_full = MAX(cpu->throttle_us_per_full, 0);
}
static void dirtylimit_adjust_throttle(CPUState *cpu)
{
uint64_t quota = 0;
uint64_t current = 0;
int cpu_index = cpu->cpu_index;
quota = dirtylimit_vcpu_get_state(cpu_index)->quota;
current = vcpu_dirty_rate_get(cpu_index);
if (!dirtylimit_done(quota, current)) {
dirtylimit_set_throttle(cpu, quota, current);
}
}
void dirtylimit_process(void)
{
CPUState *cpu;
if (!qatomic_read(&dirtylimit_quit)) {
dirtylimit_state_lock();
if (!dirtylimit_in_service()) {
dirtylimit_state_unlock();
return;
}
CPU_FOREACH(cpu) {
if (!dirtylimit_vcpu_get_state(cpu->cpu_index)->enabled) {
continue;
}
dirtylimit_adjust_throttle(cpu);
}
dirtylimit_state_unlock();
}
}
void dirtylimit_change(bool start)
{
if (start) {
qatomic_set(&dirtylimit_quit, 0);
} else {
qatomic_set(&dirtylimit_quit, 1);
}
}
void dirtylimit_set_vcpu(int cpu_index,
uint64_t quota,
bool enable)
{
trace_dirtylimit_set_vcpu(cpu_index, quota);
if (enable) {
dirtylimit_state->states[cpu_index].quota = quota;
if (!dirtylimit_vcpu_get_state(cpu_index)->enabled) {
dirtylimit_state->limited_nvcpu++;
}
} else {
dirtylimit_state->states[cpu_index].quota = 0;
if (dirtylimit_state->states[cpu_index].enabled) {
dirtylimit_state->limited_nvcpu--;
}
}
dirtylimit_state->states[cpu_index].enabled = enable;
}
void dirtylimit_set_all(uint64_t quota,
bool enable)
{
MachineState *ms = MACHINE(qdev_get_machine());
int max_cpus = ms->smp.max_cpus;
int i;
for (i = 0; i < max_cpus; i++) {
dirtylimit_set_vcpu(i, quota, enable);
}
}
void dirtylimit_vcpu_execute(CPUState *cpu)
{
if (cpu->throttle_us_per_full) {
dirtylimit_state_lock();
if (dirtylimit_in_service() &&
dirtylimit_vcpu_get_state(cpu->cpu_index)->enabled) {
dirtylimit_state_unlock();
trace_dirtylimit_vcpu_execute(cpu->cpu_index,
cpu->throttle_us_per_full);
g_usleep(cpu->throttle_us_per_full);
return;
}
dirtylimit_state_unlock();
}
}
static void dirtylimit_init(void)
{
dirtylimit_state_initialize();
dirtylimit_change(true);
vcpu_dirty_rate_stat_initialize();
vcpu_dirty_rate_stat_start();
}
static void dirtylimit_cleanup(void)
{
vcpu_dirty_rate_stat_stop();
vcpu_dirty_rate_stat_finalize();
dirtylimit_change(false);
dirtylimit_state_finalize();
}
/*
* dirty page rate limit is not allowed to set if migration
* is running with dirty-limit capability enabled.
*/
static bool dirtylimit_is_allowed(void)
{
if (migration_is_running() &&
!migration_thread_is_self() &&
migrate_dirty_limit() &&
dirtylimit_in_service()) {
return false;
}
return true;
}
void qmp_cancel_vcpu_dirty_limit(bool has_cpu_index,
int64_t cpu_index,
Error **errp)
{
if (!kvm_enabled() || !kvm_dirty_ring_enabled()) {
return;
}
if (has_cpu_index && !dirtylimit_vcpu_index_valid(cpu_index)) {
error_setg(errp, "incorrect cpu index specified");
return;
}
if (!dirtylimit_is_allowed()) {
error_setg(errp, "can't cancel dirty page rate limit while"
" migration is running");
return;
}
if (!dirtylimit_in_service()) {
return;
}
dirtylimit_state_lock();
if (has_cpu_index) {
dirtylimit_set_vcpu(cpu_index, 0, false);
} else {
dirtylimit_set_all(0, false);
}
if (!dirtylimit_state->limited_nvcpu) {
dirtylimit_cleanup();
}
dirtylimit_state_unlock();
}
void qmp_set_vcpu_dirty_limit(bool has_cpu_index,
int64_t cpu_index,
uint64_t dirty_rate,
Error **errp)
{
if (!kvm_enabled() || !kvm_dirty_ring_enabled()) {
error_setg(errp, "dirty page limit feature requires KVM with"
" accelerator property 'dirty-ring-size' set'");
return;
}
if (has_cpu_index && !dirtylimit_vcpu_index_valid(cpu_index)) {
error_setg(errp, "incorrect cpu index specified");
return;
}
if (!dirtylimit_is_allowed()) {
error_setg(errp, "can't set dirty page rate limit while"
" migration is running");
return;
}
if (!dirty_rate) {
qmp_cancel_vcpu_dirty_limit(has_cpu_index, cpu_index, errp);
return;
}
dirtylimit_state_lock();
if (!dirtylimit_in_service()) {
dirtylimit_init();
}
if (has_cpu_index) {
dirtylimit_set_vcpu(cpu_index, dirty_rate, true);
} else {
dirtylimit_set_all(dirty_rate, true);
}
dirtylimit_state_unlock();
}
/* Return the max throttle time of each virtual CPU */
uint64_t dirtylimit_throttle_time_per_round(void)
{
CPUState *cpu;
int64_t max = 0;
CPU_FOREACH(cpu) {
if (cpu->throttle_us_per_full > max) {
max = cpu->throttle_us_per_full;
}
}
return max;
}
/*
* Estimate average dirty ring full time of each virtaul CPU.
* Return 0 if guest doesn't dirty memory.
*/
uint64_t dirtylimit_ring_full_time(void)
{
CPUState *cpu;
uint64_t curr_rate = 0;
int nvcpus = 0;
CPU_FOREACH(cpu) {
if (cpu->running) {
nvcpus++;
curr_rate += vcpu_dirty_rate_get(cpu->cpu_index);
}
}
if (!curr_rate || !nvcpus) {
return 0;
}
return dirtylimit_dirty_ring_full_time(curr_rate / nvcpus);
}
static struct DirtyLimitInfo *dirtylimit_query_vcpu(int cpu_index)
{
DirtyLimitInfo *info = NULL;
info = g_malloc0(sizeof(*info));
info->cpu_index = cpu_index;
info->limit_rate = dirtylimit_vcpu_get_state(cpu_index)->quota;
info->current_rate = vcpu_dirty_rate_get(cpu_index);
return info;
}
static struct DirtyLimitInfoList *dirtylimit_query_all(void)
{
int i, index;
DirtyLimitInfo *info = NULL;
DirtyLimitInfoList *head = NULL, **tail = &head;
dirtylimit_state_lock();
if (!dirtylimit_in_service()) {
dirtylimit_state_unlock();
return NULL;
}
for (i = 0; i < dirtylimit_state->max_cpus; i++) {
index = dirtylimit_state->states[i].cpu_index;
if (dirtylimit_vcpu_get_state(index)->enabled) {
info = dirtylimit_query_vcpu(index);
QAPI_LIST_APPEND(tail, info);
}
}
dirtylimit_state_unlock();
return head;
}
struct DirtyLimitInfoList *qmp_query_vcpu_dirty_limit(Error **errp)
{
return dirtylimit_query_all();
}
+347
View File
@@ -0,0 +1,347 @@
/*
* DMA helper functions
*
* Copyright (c) 2009,2020 Red Hat
*
* This work is licensed under the terms of the GNU General Public License
* (GNU GPL), version 2 or later.
*/
#include "qemu/osdep.h"
#include "system/block-backend.h"
#include "system/dma.h"
#include "trace.h"
#include "qemu/thread.h"
#include "qemu/main-loop.h"
#include "exec/icount.h"
#include "qemu/range.h"
/* #define DEBUG_IOMMU */
MemTxResult dma_memory_set(AddressSpace *as, dma_addr_t addr,
uint8_t c, dma_addr_t len, MemTxAttrs attrs)
{
dma_barrier(as, DMA_DIRECTION_FROM_DEVICE);
return address_space_set(as, addr, c, len, attrs);
}
void qemu_sglist_init(QEMUSGList *qsg, DeviceState *dev, int alloc_hint,
AddressSpace *as)
{
qsg->sg = g_new(ScatterGatherEntry, alloc_hint);
qsg->nsg = 0;
qsg->nalloc = alloc_hint;
qsg->size = 0;
qsg->as = as;
qsg->dev = dev;
object_ref(OBJECT(dev));
}
void qemu_sglist_add(QEMUSGList *qsg, dma_addr_t base, dma_addr_t len)
{
if (qsg->nsg == qsg->nalloc) {
qsg->nalloc = 2 * qsg->nalloc + 1;
qsg->sg = g_renew(ScatterGatherEntry, qsg->sg, qsg->nalloc);
}
qsg->sg[qsg->nsg].base = base;
qsg->sg[qsg->nsg].len = len;
qsg->size += len;
++qsg->nsg;
}
void qemu_sglist_destroy(QEMUSGList *qsg)
{
object_unref(OBJECT(qsg->dev));
g_free(qsg->sg);
memset(qsg, 0, sizeof(*qsg));
}
typedef struct {
BlockAIOCB common;
AioContext *ctx;
BlockAIOCB *acb;
QEMUSGList *sg;
uint32_t align;
uint64_t offset;
DMADirection dir;
int sg_cur_index;
dma_addr_t sg_cur_byte;
QEMUIOVector iov;
QEMUBH *bh;
DMAIOFunc *io_func;
void *io_func_opaque;
} DMAAIOCB;
static void dma_blk_cb(void *opaque, int ret);
static void reschedule_dma(void *opaque)
{
DMAAIOCB *dbs = (DMAAIOCB *)opaque;
assert(!dbs->acb && dbs->bh);
qemu_bh_delete(dbs->bh);
dbs->bh = NULL;
dma_blk_cb(dbs, 0);
}
static void dma_blk_unmap(DMAAIOCB *dbs)
{
int i;
for (i = 0; i < dbs->iov.niov; ++i) {
dma_memory_unmap(dbs->sg->as, dbs->iov.iov[i].iov_base,
dbs->iov.iov[i].iov_len, dbs->dir,
dbs->iov.iov[i].iov_len);
}
qemu_iovec_reset(&dbs->iov);
}
static void dma_complete(DMAAIOCB *dbs, int ret)
{
trace_dma_complete(dbs, ret, dbs->common.cb);
assert(!dbs->acb && !dbs->bh);
dma_blk_unmap(dbs);
if (dbs->common.cb) {
dbs->common.cb(dbs->common.opaque, ret);
}
qemu_iovec_destroy(&dbs->iov);
qemu_aio_unref(dbs);
}
static void dma_blk_cb(void *opaque, int ret)
{
DMAAIOCB *dbs = (DMAAIOCB *)opaque;
AioContext *ctx = dbs->ctx;
dma_addr_t cur_addr, cur_len;
void *mem;
trace_dma_blk_cb(dbs, ret);
/* DMAAIOCB is not thread-safe and must be accessed only from dbs->ctx */
assert(ctx == qemu_get_current_aio_context());
dbs->acb = NULL;
dbs->offset += dbs->iov.size;
if (dbs->sg_cur_index == dbs->sg->nsg || ret < 0) {
dma_complete(dbs, ret);
return;
}
dma_blk_unmap(dbs);
while (dbs->sg_cur_index < dbs->sg->nsg) {
cur_addr = dbs->sg->sg[dbs->sg_cur_index].base + dbs->sg_cur_byte;
cur_len = dbs->sg->sg[dbs->sg_cur_index].len - dbs->sg_cur_byte;
mem = dma_memory_map(dbs->sg->as, cur_addr, &cur_len, dbs->dir,
MEMTXATTRS_UNSPECIFIED);
/*
* Make reads deterministic in icount mode. Windows sometimes issues
* disk read requests with overlapping SGs. It leads
* to non-determinism, because resulting buffer contents may be mixed
* from several sectors. This code splits all SGs into several
* groups. SGs in every group do not overlap.
*/
if (mem && icount_enabled() && dbs->dir == DMA_DIRECTION_FROM_DEVICE) {
int i;
for (i = 0 ; i < dbs->iov.niov ; ++i) {
if (ranges_overlap((intptr_t)dbs->iov.iov[i].iov_base,
dbs->iov.iov[i].iov_len, (intptr_t)mem,
cur_len)) {
dma_memory_unmap(dbs->sg->as, mem, cur_len,
dbs->dir, cur_len);
mem = NULL;
break;
}
}
}
if (!mem)
break;
qemu_iovec_add(&dbs->iov, mem, cur_len);
dbs->sg_cur_byte += cur_len;
if (dbs->sg_cur_byte == dbs->sg->sg[dbs->sg_cur_index].len) {
dbs->sg_cur_byte = 0;
++dbs->sg_cur_index;
}
}
if (dbs->iov.size == 0) {
trace_dma_map_wait(dbs);
dbs->bh = aio_bh_new(ctx, reschedule_dma, dbs);
address_space_register_map_client(dbs->sg->as, dbs->bh);
return;
}
if (!QEMU_IS_ALIGNED(dbs->iov.size, dbs->align)) {
qemu_iovec_discard_back(&dbs->iov,
QEMU_ALIGN_DOWN(dbs->iov.size, dbs->align));
}
dbs->acb = dbs->io_func(dbs->offset, &dbs->iov,
dma_blk_cb, dbs, dbs->io_func_opaque);
assert(dbs->acb);
}
static void dma_aio_cancel(BlockAIOCB *acb)
{
DMAAIOCB *dbs = container_of(acb, DMAAIOCB, common);
trace_dma_aio_cancel(dbs);
assert(!(dbs->acb && dbs->bh));
if (dbs->acb) {
/* This will invoke dma_blk_cb. */
blk_aio_cancel_async(dbs->acb);
return;
}
if (dbs->bh) {
address_space_unregister_map_client(dbs->sg->as, dbs->bh);
qemu_bh_delete(dbs->bh);
dbs->bh = NULL;
}
if (dbs->common.cb) {
dbs->common.cb(dbs->common.opaque, -ECANCELED);
}
}
static const AIOCBInfo dma_aiocb_info = {
.aiocb_size = sizeof(DMAAIOCB),
.cancel_async = dma_aio_cancel,
};
BlockAIOCB *dma_blk_io(
QEMUSGList *sg, uint64_t offset, uint32_t align,
DMAIOFunc *io_func, void *io_func_opaque,
BlockCompletionFunc *cb,
void *opaque, DMADirection dir)
{
DMAAIOCB *dbs = qemu_aio_get(&dma_aiocb_info, NULL, cb, opaque);
trace_dma_blk_io(dbs, io_func_opaque, offset, (dir == DMA_DIRECTION_TO_DEVICE));
dbs->acb = NULL;
dbs->sg = sg;
dbs->ctx = qemu_get_current_aio_context();
dbs->offset = offset;
dbs->align = align;
dbs->sg_cur_index = 0;
dbs->sg_cur_byte = 0;
dbs->dir = dir;
dbs->io_func = io_func;
dbs->io_func_opaque = io_func_opaque;
dbs->bh = NULL;
qemu_iovec_init(&dbs->iov, sg->nsg);
dma_blk_cb(dbs, 0);
return &dbs->common;
}
static
BlockAIOCB *dma_blk_read_io_func(int64_t offset, QEMUIOVector *iov,
BlockCompletionFunc *cb, void *cb_opaque,
void *opaque)
{
BlockBackend *blk = opaque;
return blk_aio_preadv(blk, offset, iov, 0, cb, cb_opaque);
}
BlockAIOCB *dma_blk_read(BlockBackend *blk,
QEMUSGList *sg, uint64_t offset, uint32_t align,
void (*cb)(void *opaque, int ret), void *opaque)
{
return dma_blk_io(sg, offset, align,
dma_blk_read_io_func, blk, cb, opaque,
DMA_DIRECTION_FROM_DEVICE);
}
static
BlockAIOCB *dma_blk_write_io_func(int64_t offset, QEMUIOVector *iov,
BlockCompletionFunc *cb, void *cb_opaque,
void *opaque)
{
BlockBackend *blk = opaque;
return blk_aio_pwritev(blk, offset, iov, 0, cb, cb_opaque);
}
BlockAIOCB *dma_blk_write(BlockBackend *blk,
QEMUSGList *sg, uint64_t offset, uint32_t align,
void (*cb)(void *opaque, int ret), void *opaque)
{
return dma_blk_io(sg, offset, align,
dma_blk_write_io_func, blk, cb, opaque,
DMA_DIRECTION_TO_DEVICE);
}
static MemTxResult dma_buf_rw(void *buf, dma_addr_t len, dma_addr_t *residual,
QEMUSGList *sg, DMADirection dir,
MemTxAttrs attrs)
{
uint8_t *ptr = buf;
dma_addr_t xresidual;
int sg_cur_index;
MemTxResult res = MEMTX_OK;
xresidual = sg->size;
sg_cur_index = 0;
len = MIN(len, xresidual);
while (len > 0) {
ScatterGatherEntry entry = sg->sg[sg_cur_index++];
dma_addr_t xfer = MIN(len, entry.len);
res |= dma_memory_rw(sg->as, entry.base, ptr, xfer, dir, attrs);
ptr += xfer;
len -= xfer;
xresidual -= xfer;
}
if (residual) {
*residual = xresidual;
}
return res;
}
MemTxResult dma_buf_read(void *ptr, dma_addr_t len, dma_addr_t *residual,
QEMUSGList *sg, MemTxAttrs attrs)
{
return dma_buf_rw(ptr, len, residual, sg, DMA_DIRECTION_FROM_DEVICE, attrs);
}
MemTxResult dma_buf_write(void *ptr, dma_addr_t len, dma_addr_t *residual,
QEMUSGList *sg, MemTxAttrs attrs)
{
return dma_buf_rw(ptr, len, residual, sg, DMA_DIRECTION_TO_DEVICE, attrs);
}
void dma_acct_start(BlockBackend *blk, BlockAcctCookie *cookie,
QEMUSGList *sg, enum BlockAcctType type)
{
block_acct_start(blk_get_stats(blk), cookie, sg->size, type);
}
uint64_t dma_aligned_pow2_mask(uint64_t start, uint64_t end, int max_addr_bits)
{
uint64_t max_mask = UINT64_MAX, addr_mask = end - start;
uint64_t alignment_mask, size_mask;
if (max_addr_bits != 64) {
max_mask = (1ULL << max_addr_bits) - 1;
}
alignment_mask = start ? (start & -start) - 1 : max_mask;
alignment_mask = MIN(alignment_mask, max_mask);
size_mask = MIN(addr_mask, max_mask);
if (alignment_mask <= size_mask) {
/* Increase the alignment of start */
return alignment_mask;
} else {
/* Find the largest page mask from size */
if (addr_mask == UINT64_MAX) {
return UINT64_MAX;
}
return (1ULL << (63 - clz64(addr_mask + 1))) - 1;
}
}
+141
View File
@@ -0,0 +1,141 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
* Originally derived from nbdkit common/utils/exit-with-parent.c
* Copyright Red Hat
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * 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.
*
* * Neither the name of Red Hat nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''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 RED HAT 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.
*/
/*
* Implement the --exit-with-parent feature on operating systems which
* support it.
*/
#include "qemu/osdep.h"
#include "qemu/exit-with-parent.h"
#if defined(__linux__)
#include <sys/prctl.h>
/*
* Send SIGTERM to self when the parent exits. This will cause
* qemu_system_killed() to be called.
*
* PR_SET_PDEATHSIG has been defined since Linux 2.1.57.
*/
int
set_exit_with_parent(void)
{
return prctl(PR_SET_PDEATHSIG, SIGTERM);
}
#elif defined(__FreeBSD__)
#include <sys/procctl.h>
/*
* Send SIGTERM to self when the parent exits. This will cause
* qemu_system_killed() to be called.
*
* PROC_PDEATHSIG_CTL has been defined since FreeBSD 11.2.
*/
int
set_exit_with_parent(void)
{
const int sig = SIGTERM;
return procctl(P_PID, 0, PROC_PDEATHSIG_CTL, (void *) &sig);
}
#elif defined(__APPLE__)
/* For macOS. */
#include "qemu/thread.h"
#include "qemu/error-report.h"
#include "system/runstate.h"
#include <sys/event.h>
static void *
exit_with_parent_loop(void *vp)
{
const pid_t ppid = getppid();
int fd;
struct kevent kev, res[1];
int r;
/* Register the kevent to wait for ppid to exit. */
fd = kqueue();
if (fd == -1) {
error_report("exit_with_parent_loop: kqueue: %m");
return NULL;
}
EV_SET(&kev, ppid, EVFILT_PROC, EV_ADD | EV_ENABLE, NOTE_EXIT, 0, NULL);
if (kevent(fd, &kev, 1, NULL, 0, NULL) == -1) {
error_report("exit_with_parent_loop: kevent: %m");
close(fd);
return NULL;
}
/* Wait for the kevent to happen. */
r = kevent(fd, 0, 0, res, 1, NULL);
if (r == 1 && res[0].ident == ppid) {
/* Behave like Linux and FreeBSD above, as if SIGTERM was sent */
qemu_system_killed(SIGTERM, ppid);
}
close(fd);
return NULL;
}
int
set_exit_with_parent(void)
{
QemuThread exit_with_parent_thread;
/*
* We have to block waiting for kevent, so that requires that we
* start a background thread.
*/
qemu_thread_create(&exit_with_parent_thread,
"exit-parent",
exit_with_parent_loop, NULL,
QEMU_THREAD_DETACHED);
return 0;
}
#else /* any platform that doesn't support this function */
int
set_exit_with_parent(void)
{
g_assert_not_reached();
}
#endif
+77
View File
@@ -0,0 +1,77 @@
/*
* Global variables that (mostly) should not exist
*
* Copyright (c) 2003-2020 QEMU contributors
*
* 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 "hw/display/vga.h"
#include "hw/core/loader.h"
#include "hw/xen/xen.h"
#include "net/net.h"
#include "system/cpus.h"
#include "system/system.h"
bool should_mlock(MlockState state)
{
return state == MLOCK_ON || state == MLOCK_ON_FAULT;
}
bool is_mlock_on_fault(MlockState state)
{
return state == MLOCK_ON_FAULT;
}
enum vga_retrace_method vga_retrace_method = VGA_RETRACE_DUMB;
int display_opengl;
const char* keyboard_layout;
MlockState mlock_state;
bool enable_cpu_pm;
int autostart = 1;
int vga_interface_type = VGA_NONE;
bool vga_interface_created;
int graphic_width;
int graphic_height;
int graphic_depth;
Chardev *parallel_hds[MAX_PARALLEL_PORTS];
QEMUOptionRom option_rom[MAX_OPTION_ROMS];
int nb_option_roms;
const char *qemu_name;
unsigned int nb_prom_envs;
const char *prom_envs[MAX_PROM_ENVS];
uint8_t *boot_splash_filedata;
int only_migratable; /* turn it off unless user states otherwise */
/* The bytes in qemu_uuid are in the order specified by RFC4122, _not_ in the
* little-endian "wire format" described in the SMBIOS 2.6 specification.
*/
QemuUUID qemu_uuid;
bool qemu_uuid_set;
uint32_t xen_domid;
enum xen_mode xen_mode = XEN_DISABLED;
bool xen_domid_restrict;
bool xen_is_stubdomain;
struct evtchn_backend_ops *xen_evtchn_ops;
struct gnttab_backend_ops *xen_gnttab_ops;
struct foreignmem_backend_ops *xen_foreignmem_ops;
struct xenstore_backend_ops *xen_xenstore_ops;
+371
View File
@@ -0,0 +1,371 @@
/*
* 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.
*/
/*
* split out ioport related stuffs from vl.c.
*/
#include "qemu/osdep.h"
#include "system/ioport.h"
#include "system/memory.h"
#include "system/address-spaces.h"
#include "hw/core/qdev.h"
#include "trace.h"
struct MemoryRegionPortioList {
Object obj;
MemoryRegion mr;
void *portio_opaque;
MemoryRegionPortio *ports;
};
#define TYPE_MEMORY_REGION_PORTIO_LIST "memory-region-portio-list"
OBJECT_DECLARE_SIMPLE_TYPE(MemoryRegionPortioList, MEMORY_REGION_PORTIO_LIST)
static uint64_t unassigned_io_read(void *opaque, hwaddr addr, unsigned size)
{
return -1ULL;
}
static void unassigned_io_write(void *opaque, hwaddr addr, uint64_t val,
unsigned size)
{
}
const MemoryRegionOps unassigned_io_ops = {
.read = unassigned_io_read,
.write = unassigned_io_write,
.endianness = DEVICE_LITTLE_ENDIAN,
};
void cpu_outb(uint32_t addr, uint8_t val)
{
trace_cpu_out(addr, 'b', val);
address_space_stb(&address_space_io, addr, val,
MEMTXATTRS_UNSPECIFIED, NULL);
}
void cpu_outw(uint32_t addr, uint16_t val)
{
trace_cpu_out(addr, 'w', val);
address_space_stw_le(&address_space_io, addr, val,
MEMTXATTRS_UNSPECIFIED, NULL);
}
void cpu_outl(uint32_t addr, uint32_t val)
{
trace_cpu_out(addr, 'l', val);
address_space_stl_le(&address_space_io, addr, val,
MEMTXATTRS_UNSPECIFIED, NULL);
}
uint8_t cpu_inb(uint32_t addr)
{
uint8_t val;
val = address_space_ldub(&address_space_io, addr,
MEMTXATTRS_UNSPECIFIED, NULL);
trace_cpu_in(addr, 'b', val);
return val;
}
uint16_t cpu_inw(uint32_t addr)
{
uint16_t val;
val = address_space_lduw_le(&address_space_io, addr,
MEMTXATTRS_UNSPECIFIED, NULL);
trace_cpu_in(addr, 'w', val);
return val;
}
uint32_t cpu_inl(uint32_t addr)
{
uint32_t val;
val = address_space_ldl_le(&address_space_io, addr,
MEMTXATTRS_UNSPECIFIED, NULL);
trace_cpu_in(addr, 'l', val);
return val;
}
void portio_list_init(PortioList *piolist,
Object *owner,
const MemoryRegionPortio *callbacks,
void *opaque, const char *name)
{
unsigned n = 0;
while (callbacks[n].size) {
++n;
}
piolist->ports = callbacks;
piolist->nr = 0;
piolist->regions = g_new0(MemoryRegion *, n);
piolist->address_space = NULL;
piolist->addr = 0;
piolist->opaque = opaque;
piolist->owner = owner;
piolist->name = name;
piolist->flush_coalesced_mmio = false;
}
void portio_list_set_flush_coalesced(PortioList *piolist)
{
piolist->flush_coalesced_mmio = true;
}
void portio_list_destroy(PortioList *piolist)
{
MemoryRegionPortioList *mrpio;
unsigned i;
for (i = 0; i < piolist->nr; ++i) {
mrpio = container_of(piolist->regions[i], MemoryRegionPortioList, mr);
object_unparent(OBJECT(&mrpio->mr));
object_unref(mrpio);
}
g_free(piolist->regions);
}
static const MemoryRegionPortio *find_portio(MemoryRegionPortioList *mrpio,
uint64_t offset, unsigned size,
bool write)
{
const MemoryRegionPortio *mrp;
for (mrp = mrpio->ports; mrp->size; ++mrp) {
if (offset >= mrp->offset && offset < mrp->offset + mrp->len &&
size == mrp->size &&
(write ? (bool)mrp->write : (bool)mrp->read)) {
return mrp;
}
}
return NULL;
}
static uint64_t portio_read(void *opaque, hwaddr addr, unsigned size)
{
MemoryRegionPortioList *mrpio = opaque;
const MemoryRegionPortio *mrp = find_portio(mrpio, addr, size, false);
uint64_t data;
data = ((uint64_t)1 << (size * 8)) - 1;
if (mrp) {
data = mrp->read(mrpio->portio_opaque, mrpio->mr.addr + addr);
} else if (size == 2) {
mrp = find_portio(mrpio, addr, 1, false);
if (mrp) {
data = mrp->read(mrpio->portio_opaque, mrpio->mr.addr + addr);
if (addr + 1 < mrp->offset + mrp->len) {
data |= mrp->read(mrpio->portio_opaque, mrpio->mr.addr + addr + 1) << 8;
} else {
data |= 0xff00;
}
}
}
return data;
}
static void portio_write(void *opaque, hwaddr addr, uint64_t data,
unsigned size)
{
MemoryRegionPortioList *mrpio = opaque;
const MemoryRegionPortio *mrp = find_portio(mrpio, addr, size, true);
if (mrp) {
mrp->write(mrpio->portio_opaque, mrpio->mr.addr + addr, data);
} else if (size == 2) {
mrp = find_portio(mrpio, addr, 1, true);
if (mrp) {
mrp->write(mrpio->portio_opaque, mrpio->mr.addr + addr, data & 0xff);
if (addr + 1 < mrp->offset + mrp->len) {
mrp->write(mrpio->portio_opaque, mrpio->mr.addr + addr + 1, data >> 8);
}
}
}
}
static const MemoryRegionOps portio_ops = {
.read = portio_read,
.write = portio_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid.unaligned = true,
.impl.unaligned = true,
};
static void portio_list_add_1(PortioList *piolist,
const MemoryRegionPortio *pio_init,
unsigned count, unsigned start,
unsigned off_low, unsigned off_high)
{
MemoryRegionPortioList *mrpio;
Object *owner;
char *name;
unsigned i;
/* Copy the sub-list and null-terminate it. */
mrpio = MEMORY_REGION_PORTIO_LIST(
object_new(TYPE_MEMORY_REGION_PORTIO_LIST));
mrpio->portio_opaque = piolist->opaque;
mrpio->ports = g_new0(MemoryRegionPortio, count + 1);
memcpy(mrpio->ports, pio_init, sizeof(MemoryRegionPortio) * count);
/* Adjust the offsets to all be zero-based for the region. */
for (i = 0; i < count; ++i) {
mrpio->ports[i].offset -= off_low;
}
/*
* The MemoryRegion owner is the MemoryRegionPortioList since that manages
* the lifecycle via the refcount
*/
memory_region_init_io(&mrpio->mr, OBJECT(mrpio), &portio_ops, mrpio,
piolist->name, off_high - off_low);
/* Reparent the MemoryRegion to the piolist owner */
object_ref(&mrpio->mr);
object_unparent(OBJECT(&mrpio->mr));
if (!piolist->owner) {
owner = machine_get_container("unattached");
} else {
owner = piolist->owner;
}
name = g_strdup_printf("%s[*]", piolist->name);
object_property_add_child(owner, name, OBJECT(&mrpio->mr));
g_free(name);
if (piolist->flush_coalesced_mmio) {
memory_region_set_flush_coalesced(&mrpio->mr);
}
memory_region_add_subregion(piolist->address_space,
start + off_low, &mrpio->mr);
piolist->regions[piolist->nr] = &mrpio->mr;
++piolist->nr;
}
void portio_list_add(PortioList *piolist,
MemoryRegion *address_space,
uint32_t start)
{
const MemoryRegionPortio *pio, *pio_start = piolist->ports;
unsigned int off_low, off_high, off_last, count;
piolist->address_space = address_space;
piolist->addr = start;
/* Handle the first entry specially. */
off_last = off_low = pio_start->offset;
off_high = off_low + pio_start->len + pio_start->size - 1;
count = 1;
for (pio = pio_start + 1; pio->size != 0; pio++, count++) {
/* All entries must be sorted by offset. */
assert(pio->offset >= off_last);
off_last = pio->offset;
/* If we see a hole, break the region. */
if (off_last > off_high) {
portio_list_add_1(piolist, pio_start, count, start, off_low,
off_high);
/* ... and start collecting anew. */
pio_start = pio;
off_low = off_last;
off_high = off_low + pio->len + pio_start->size - 1;
count = 0;
} else if (off_last + pio->len > off_high) {
off_high = off_last + pio->len + pio_start->size - 1;
}
}
/* There will always be an open sub-list. */
portio_list_add_1(piolist, pio_start, count, start, off_low, off_high);
}
void portio_list_del(PortioList *piolist)
{
MemoryRegionPortioList *mrpio;
unsigned i;
for (i = 0; i < piolist->nr; ++i) {
mrpio = container_of(piolist->regions[i], MemoryRegionPortioList, mr);
memory_region_del_subregion(piolist->address_space, &mrpio->mr);
}
}
void portio_list_set_enabled(PortioList *piolist, bool enabled)
{
unsigned i;
for (i = 0; i < piolist->nr; ++i) {
memory_region_set_enabled(piolist->regions[i], enabled);
}
}
void portio_list_set_address(PortioList *piolist, uint32_t addr)
{
MemoryRegionPortioList *mrpio;
unsigned i, j;
for (i = 0; i < piolist->nr; ++i) {
mrpio = container_of(piolist->regions[i], MemoryRegionPortioList, mr);
memory_region_set_address(&mrpio->mr,
mrpio->mr.addr - piolist->addr + addr);
for (j = 0; mrpio->ports[j].size; ++j) {
mrpio->ports[j].offset += addr - piolist->addr;
}
}
piolist->addr = addr;
}
static void memory_region_portio_list_finalize(Object *obj)
{
MemoryRegionPortioList *mrpio = MEMORY_REGION_PORTIO_LIST(obj);
/*
* This check makes sure any random object_new() (without doing the
* rest inits in portio_list_add_1()) will not crash when finalizing.
* One example is QMP command qom-list-properties.
*/
if (mrpio->ports) {
object_unref(&mrpio->mr);
g_free(mrpio->ports);
}
}
static const TypeInfo memory_region_portio_list_info = {
.parent = TYPE_OBJECT,
.name = TYPE_MEMORY_REGION_PORTIO_LIST,
.instance_size = sizeof(MemoryRegionPortioList),
.instance_finalize = memory_region_portio_list_finalize,
};
static void ioport_register_types(void)
{
type_register_static(&memory_region_portio_list_info);
}
type_init(ioport_register_types)
+96
View File
@@ -0,0 +1,96 @@
/*
* QEMU System Emulator
*
* Copyright (c) 2003-2020 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-main.h"
#include "qemu/main-loop.h"
#include "system/replay.h"
#include "system/system.h"
#ifdef CONFIG_SDL
/*
* SDL insists on wrapping the main() function with its own implementation on
* some platforms; it does so via a macro that renames our main function, so
* <SDL.h> must be #included here even with no SDL code called from this file.
*/
#include <SDL.h>
#endif
#ifdef CONFIG_DARWIN
#include <CoreFoundation/CoreFoundation.h>
#endif
static void *qemu_default_main(void *opaque)
{
int status;
replay_mutex_lock();
bql_lock();
status = qemu_main_loop();
qemu_cleanup(status);
bql_unlock();
replay_mutex_unlock();
exit(status);
}
int (*qemu_main)(void);
#ifdef CONFIG_DARWIN
static int os_darwin_cfrunloop_main(void)
{
CFRunLoopRun();
g_assert_not_reached();
}
int (*qemu_main)(void) = os_darwin_cfrunloop_main;
#endif
int main(int argc, char **argv)
{
qemu_init(argc, argv);
/*
* qemu_init acquires the BQL and replay mutex lock. BQL is acquired when
* initializing cpus, to block associated threads until initialization is
* complete. Replay_mutex lock is acquired on initialization, because it
* must be held when configuring icount_mode.
*
* On MacOS, qemu main event loop runs in a background thread, as main
* thread must be reserved for UI. Thus, we need to transfer lock ownership,
* and the simplest way to do that is to release them, and reacquire them
* from qemu_default_main.
*/
bql_unlock();
replay_mutex_unlock();
if (qemu_main) {
QemuThread main_loop_thread;
qemu_thread_create(&main_loop_thread, "qemu_main",
qemu_default_main, NULL, QEMU_THREAD_DETACHED);
return qemu_main();
} else {
qemu_default_main(NULL);
g_assert_not_reached();
}
}
+60
View File
@@ -0,0 +1,60 @@
/*
* Declarations for functions which are internal to the memory subsystem.
*
* Copyright 2011 Red Hat, Inc. and/or its affiliates
*
* Authors:
* Avi Kivity <[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.
*
*/
#ifndef MEMORY_INTERNAL_H
#define MEMORY_INTERNAL_H
void machine_memory_init(void);
static inline AddressSpaceDispatch *flatview_to_dispatch(FlatView *fv)
{
return fv->dispatch;
}
static inline
AddressSpaceDispatch *address_space_to_dispatch(const AddressSpace *as)
{
return flatview_to_dispatch(address_space_to_flatview(as));
}
FlatView *address_space_get_flatview(const AddressSpace *as);
void flatview_unref(FlatView *view);
extern const MemoryRegionOps unassigned_mem_ops;
void flatview_add_to_dispatch(FlatView *fv, MemoryRegionSection *section);
AddressSpaceDispatch *address_space_dispatch_new(FlatView *fv);
void address_space_dispatch_compact(AddressSpaceDispatch *d);
void address_space_dispatch_free(AddressSpaceDispatch *d);
void mtree_print_dispatch(struct AddressSpaceDispatch *d,
MemoryRegion *root);
/* returns true if end is big endian. */
static inline bool devend_big_endian(enum device_endian end)
{
#ifndef TARGET_NOT_USING_LEGACY_NATIVE_ENDIAN_API
if (end == DEVICE_NATIVE_ENDIAN) {
return target_big_endian();
}
#endif
return end == DEVICE_BIG_ENDIAN;
}
/* enum device_endian to MemOp. */
static inline MemOp devend_memop(enum device_endian end)
{
return devend_big_endian(end) ? MO_BE : MO_LE;
}
#endif
+3699
View File
File diff suppressed because it is too large Load Diff
+127
View File
@@ -0,0 +1,127 @@
/*
* Physical memory access templates
*
* Copyright (c) 2003 Fabrice Bellard
* Copyright (c) 2015 Linaro, Inc.
* 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/>.
*/
/* warning: addr must be aligned */
static inline
uint64_t glue(address_space_ldm_internal, SUFFIX)(ARG1_DECL, MemOp mop,
hwaddr addr,
MemTxAttrs attrs,
MemTxResult *result)
{
const unsigned size = memop_size(mop);
uint64_t val;
MemoryRegion *mr;
hwaddr l = size;
hwaddr addr1;
MemTxResult r;
bool release_lock = false;
RCU_READ_LOCK();
mr = TRANSLATE(addr, &addr1, &l, false, attrs);
if (l < size || !memory_access_is_direct(mr, false, attrs)) {
release_lock |= prepare_mmio_access(mr);
/* I/O case */
r = memory_region_dispatch_read(mr, addr1, &val, mop, attrs);
} else {
/* RAM case */
fuzz_dma_read_cb(addr, size, mr);
val = ldm_p(qemu_map_ram_ptr(mr->ram_block, addr1), mop);
r = MEMTX_OK;
}
if (result) {
*result = r;
}
if (release_lock) {
bql_unlock();
}
RCU_READ_UNLOCK();
return val;
}
uint8_t glue(address_space_ldub, SUFFIX)(ARG1_DECL, hwaddr addr,
MemTxAttrs attrs, MemTxResult *result)
{
return glue(address_space_ldm_internal, SUFFIX)(ARG1, MO_8, addr,
attrs, result);
}
/* warning: addr must be aligned */
static inline
void glue(address_space_stm_internal, SUFFIX)(ARG1_DECL, MemOp mop,
hwaddr addr, uint64_t val,
MemTxAttrs attrs,
MemTxResult *result)
{
const unsigned size = memop_size(mop);
MemoryRegion *mr;
hwaddr l = size;
hwaddr addr1;
MemTxResult r;
bool release_lock = false;
RCU_READ_LOCK();
mr = TRANSLATE(addr, &addr1, &l, true, attrs);
if (l < size || !memory_access_is_direct(mr, true, attrs)) {
release_lock |= prepare_mmio_access(mr);
r = memory_region_dispatch_write(mr, addr1, val, mop, attrs);
} else {
/* RAM case */
stm_p(qemu_map_ram_ptr(mr->ram_block, addr1), mop, val);
invalidate_and_set_dirty(mr, addr1, size);
r = MEMTX_OK;
}
if (result) {
*result = r;
}
if (release_lock) {
bql_unlock();
}
RCU_READ_UNLOCK();
}
void glue(address_space_stb, SUFFIX)(ARG1_DECL, hwaddr addr, uint8_t val,
MemTxAttrs attrs, MemTxResult *result)
{
glue(address_space_stm_internal, SUFFIX)(ARG1, MO_8, addr, val,
attrs, result);
}
#ifndef TARGET_NOT_USING_LEGACY_NATIVE_ENDIAN_API
#define ENDIANNESS
#define MO_ENDIAN (target_big_endian() ? MO_BE : MO_LE)
#include "memory_ldst_endian.c.inc"
#endif /* TARGET_NOT_USING_LEGACY_NATIVE_ENDIAN_API */
#define ENDIANNESS _le
#define MO_ENDIAN MO_LE
#include "memory_ldst_endian.c.inc"
#define ENDIANNESS _be
#define MO_ENDIAN MO_BE
#include "memory_ldst_endian.c.inc"
#undef ARG1_DECL
#undef ARG1
#undef SUFFIX
#undef TRANSLATE
#undef RCU_READ_LOCK
#undef RCU_READ_UNLOCK
+70
View File
@@ -0,0 +1,70 @@
/*
* Physical memory access endian templates
*
* Copyright (c) 2003 Fabrice Bellard
* Copyright (c) 2015 Linaro, Inc.
* Copyright (c) 2016 Red Hat, Inc.
* Copyright (c) 2025 Linaro Ltd.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#define ADDRESS_SPACE_LD(size) \
glue(glue(address_space_ld, size), glue(ENDIANNESS, SUFFIX))
#define ADDRESS_SPACE_LD_INTERNAL(size) \
glue(glue(address_space_ld, size), glue(_internal, SUFFIX))
#define ADDRESS_SPACE_ST(size) \
glue(glue(address_space_st, size), glue(ENDIANNESS, SUFFIX))
#define ADDRESS_SPACE_ST_INTERNAL(size) \
glue(glue(address_space_st, size), glue(_internal, SUFFIX))
uint16_t ADDRESS_SPACE_LD(uw)(ARG1_DECL, hwaddr addr,
MemTxAttrs attrs, MemTxResult *result)
{
return ADDRESS_SPACE_LD_INTERNAL(m)(ARG1, MO_ENDIAN | MO_16,
addr, attrs, result);
}
uint32_t ADDRESS_SPACE_LD(l)(ARG1_DECL, hwaddr addr,
MemTxAttrs attrs, MemTxResult *result)
{
return ADDRESS_SPACE_LD_INTERNAL(m)(ARG1, MO_ENDIAN | MO_32,
addr, attrs, result);
}
uint64_t ADDRESS_SPACE_LD(q)(ARG1_DECL, hwaddr addr,
MemTxAttrs attrs, MemTxResult *result)
{
return ADDRESS_SPACE_LD_INTERNAL(m)(ARG1, MO_ENDIAN | MO_64,
addr, attrs, result);
}
void ADDRESS_SPACE_ST(w)(ARG1_DECL, hwaddr addr, uint16_t val,
MemTxAttrs attrs, MemTxResult *result)
{
ADDRESS_SPACE_ST_INTERNAL(m)(ARG1, MO_ENDIAN | MO_16,
addr, val, attrs, result);
}
void ADDRESS_SPACE_ST(l)(ARG1_DECL, hwaddr addr, uint32_t val,
MemTxAttrs attrs, MemTxResult *result)
{
ADDRESS_SPACE_ST_INTERNAL(m)(ARG1, MO_ENDIAN | MO_32,
addr, val, attrs, result);
}
void ADDRESS_SPACE_ST(q)(ARG1_DECL, hwaddr addr, uint64_t val,
MemTxAttrs attrs, MemTxResult *result)
{
ADDRESS_SPACE_ST_INTERNAL(m)(ARG1, MO_ENDIAN | MO_64,
addr, val, attrs, result);
}
#undef ADDRESS_SPACE_LD
#undef ADDRESS_SPACE_LD_INTERNAL
#undef ADDRESS_SPACE_ST
#undef ADDRESS_SPACE_ST_INTERNAL
#undef ENDIANNESS
#undef MO_ENDIAN
+376
View File
@@ -0,0 +1,376 @@
/*
* QEMU memory mapping
*
* Copyright Fujitsu, Corp. 2011, 2012
*
* Authors:
* Wen Congyang <[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/range.h"
#include "qapi/error.h"
#include "system/memory_mapping.h"
#include "system/memory.h"
#include "system/address-spaces.h"
#include "hw/core/cpu.h"
//#define DEBUG_GUEST_PHYS_REGION_ADD
static void memory_mapping_list_add_mapping_sorted(MemoryMappingList *list,
MemoryMapping *mapping)
{
MemoryMapping *p;
QTAILQ_FOREACH(p, &list->head, next) {
if (p->phys_addr >= mapping->phys_addr) {
QTAILQ_INSERT_BEFORE(p, mapping, next);
return;
}
}
QTAILQ_INSERT_TAIL(&list->head, mapping, next);
}
static void create_new_memory_mapping(MemoryMappingList *list,
hwaddr phys_addr,
hwaddr virt_addr,
ram_addr_t length)
{
MemoryMapping *memory_mapping;
memory_mapping = g_new(MemoryMapping, 1);
memory_mapping->phys_addr = phys_addr;
memory_mapping->virt_addr = virt_addr;
memory_mapping->length = length;
list->last_mapping = memory_mapping;
list->num++;
memory_mapping_list_add_mapping_sorted(list, memory_mapping);
}
static inline bool mapping_contiguous(MemoryMapping *map,
hwaddr phys_addr,
hwaddr virt_addr)
{
return phys_addr == map->phys_addr + map->length &&
virt_addr == map->virt_addr + map->length;
}
/*
* [map->phys_addr, map->phys_addr + map->length) and
* [phys_addr, phys_addr + length) have intersection?
*/
static inline bool mapping_have_same_region(MemoryMapping *map,
hwaddr phys_addr,
ram_addr_t length)
{
return !(phys_addr + length < map->phys_addr ||
phys_addr >= map->phys_addr + map->length);
}
/*
* [map->phys_addr, map->phys_addr + map->length) and
* [phys_addr, phys_addr + length) have intersection. The virtual address in the
* intersection are the same?
*/
static inline bool mapping_conflict(MemoryMapping *map,
hwaddr phys_addr,
hwaddr virt_addr)
{
return virt_addr - map->virt_addr != phys_addr - map->phys_addr;
}
/*
* [map->virt_addr, map->virt_addr + map->length) and
* [virt_addr, virt_addr + length) have intersection. And the physical address
* in the intersection are the same.
*/
static inline void mapping_merge(MemoryMapping *map,
hwaddr virt_addr,
ram_addr_t length)
{
if (virt_addr < map->virt_addr) {
map->length += map->virt_addr - virt_addr;
map->virt_addr = virt_addr;
}
if ((virt_addr + length) >
(map->virt_addr + map->length)) {
map->length = virt_addr + length - map->virt_addr;
}
}
void memory_mapping_list_add_merge_sorted(MemoryMappingList *list,
hwaddr phys_addr,
hwaddr virt_addr,
ram_addr_t length)
{
MemoryMapping *memory_mapping, *last_mapping;
if (QTAILQ_EMPTY(&list->head)) {
create_new_memory_mapping(list, phys_addr, virt_addr, length);
return;
}
last_mapping = list->last_mapping;
if (last_mapping) {
if (mapping_contiguous(last_mapping, phys_addr, virt_addr)) {
last_mapping->length += length;
return;
}
}
QTAILQ_FOREACH(memory_mapping, &list->head, next) {
if (mapping_contiguous(memory_mapping, phys_addr, virt_addr)) {
memory_mapping->length += length;
list->last_mapping = memory_mapping;
return;
}
if (phys_addr + length < memory_mapping->phys_addr) {
/* create a new region before memory_mapping */
break;
}
if (mapping_have_same_region(memory_mapping, phys_addr, length)) {
if (mapping_conflict(memory_mapping, phys_addr, virt_addr)) {
continue;
}
/* merge this region into memory_mapping */
mapping_merge(memory_mapping, virt_addr, length);
list->last_mapping = memory_mapping;
return;
}
}
/* this region can not be merged into any existed memory mapping. */
create_new_memory_mapping(list, phys_addr, virt_addr, length);
}
void memory_mapping_list_free(MemoryMappingList *list)
{
MemoryMapping *p, *q;
QTAILQ_FOREACH_SAFE(p, &list->head, next, q) {
QTAILQ_REMOVE(&list->head, p, next);
g_free(p);
}
list->num = 0;
list->last_mapping = NULL;
}
void memory_mapping_list_init(MemoryMappingList *list)
{
list->num = 0;
list->last_mapping = NULL;
QTAILQ_INIT(&list->head);
}
void guest_phys_blocks_free(GuestPhysBlockList *list)
{
GuestPhysBlock *p, *q;
QTAILQ_FOREACH_SAFE(p, &list->head, next, q) {
QTAILQ_REMOVE(&list->head, p, next);
memory_region_unref(p->mr);
g_free(p);
}
list->num = 0;
}
void guest_phys_blocks_init(GuestPhysBlockList *list)
{
list->num = 0;
QTAILQ_INIT(&list->head);
}
typedef struct GuestPhysListener {
GuestPhysBlockList *list;
MemoryListener listener;
} GuestPhysListener;
static void guest_phys_block_add_section(GuestPhysListener *g,
const MemoryRegionSection *section)
{
const hwaddr target_start = section->offset_within_address_space;
const hwaddr target_end = target_start + int128_get64(section->size);
uint8_t *host_addr = memory_region_get_ram_ptr(section->mr) +
section->offset_within_region;
GuestPhysBlock *predecessor = NULL;
/* find continuity in guest physical address space */
if (!QTAILQ_EMPTY(&g->list->head)) {
hwaddr predecessor_size;
predecessor = QTAILQ_LAST(&g->list->head);
predecessor_size = predecessor->target_end - predecessor->target_start;
/* the memory API guarantees monotonically increasing traversal */
g_assert(predecessor->target_end <= target_start);
/* we want continuity in both guest-physical and host-virtual memory */
if (predecessor->target_end < target_start ||
predecessor->host_addr + predecessor_size != host_addr ||
predecessor->mr != section->mr) {
predecessor = NULL;
}
}
if (predecessor == NULL) {
/* isolated mapping, allocate it and add it to the list */
GuestPhysBlock *block = g_malloc0(sizeof *block);
block->target_start = target_start;
block->target_end = target_end;
block->host_addr = host_addr;
block->mr = section->mr;
memory_region_ref(section->mr);
QTAILQ_INSERT_TAIL(&g->list->head, block, next);
++g->list->num;
} else {
/* expand predecessor until @target_end; predecessor's start doesn't
* change
*/
predecessor->target_end = target_end;
}
#ifdef DEBUG_GUEST_PHYS_REGION_ADD
fprintf(stderr, "%s: target_start=" HWADDR_FMT_plx " target_end="
HWADDR_FMT_plx ": %s (count: %u)\n", __func__, target_start,
target_end, predecessor ? "joined" : "added", g->list->num);
#endif
}
static int guest_phys_ram_populate_cb(const MemoryRegionSection *section,
void *opaque)
{
GuestPhysListener *g = opaque;
guest_phys_block_add_section(g, section);
return 0;
}
static void guest_phys_blocks_region_add(MemoryListener *listener,
MemoryRegionSection *section)
{
GuestPhysListener *g = container_of(listener, GuestPhysListener, listener);
/* we only care about RAM */
if (!memory_region_is_ram(section->mr) ||
memory_region_is_ram_device(section->mr) ||
memory_region_is_nonvolatile(section->mr)) {
return;
}
/* for special sparse regions, only add populated parts */
if (memory_region_has_ram_discard_manager(section->mr)) {
RamDiscardManager *rdm;
rdm = memory_region_get_ram_discard_manager(section->mr);
ram_discard_manager_replay_populated(rdm, section,
guest_phys_ram_populate_cb, g);
return;
}
guest_phys_block_add_section(g, section);
}
void guest_phys_blocks_append(GuestPhysBlockList *list)
{
GuestPhysListener g = { 0 };
g.list = list;
g.listener.region_add = &guest_phys_blocks_region_add;
memory_listener_register(&g.listener, &address_space_memory);
memory_listener_unregister(&g.listener);
}
static CPUState *find_paging_enabled_cpu(void)
{
CPUState *cpu;
CPU_FOREACH(cpu) {
if (cpu_paging_enabled(cpu)) {
return cpu;
}
}
return NULL;
}
bool qemu_get_guest_memory_mapping(MemoryMappingList *list,
const GuestPhysBlockList *guest_phys_blocks,
Error **errp)
{
ERRP_GUARD();
CPUState *cpu, *first_paging_enabled_cpu;
GuestPhysBlock *block;
ram_addr_t offset, length;
first_paging_enabled_cpu = find_paging_enabled_cpu();
if (first_paging_enabled_cpu) {
for (cpu = first_paging_enabled_cpu; cpu != NULL;
cpu = CPU_NEXT(cpu)) {
if (!cpu_get_memory_mapping(cpu, list, errp)) {
return false;
}
}
return true;
}
/*
* If the guest doesn't use paging, the virtual address is equal to physical
* address.
*/
QTAILQ_FOREACH(block, &guest_phys_blocks->head, next) {
offset = block->target_start;
length = block->target_end - block->target_start;
create_new_memory_mapping(list, offset, offset, length);
}
return true;
}
void qemu_get_guest_simple_memory_mapping(MemoryMappingList *list,
const GuestPhysBlockList *guest_phys_blocks)
{
GuestPhysBlock *block;
QTAILQ_FOREACH(block, &guest_phys_blocks->head, next) {
create_new_memory_mapping(list, block->target_start, 0,
block->target_end - block->target_start);
}
}
void memory_mapping_filter(MemoryMappingList *list, int64_t begin,
int64_t length)
{
MemoryMapping *cur, *next;
QTAILQ_FOREACH_SAFE(cur, &list->head, next, next) {
if (!ranges_overlap(cur->phys_addr, cur->length, begin, length)) {
QTAILQ_REMOVE(&list->head, cur, next);
g_free(cur);
list->num--;
continue;
}
if (cur->phys_addr < begin) {
cur->length -= begin - cur->phys_addr;
if (cur->virt_addr) {
cur->virt_addr += begin - cur->phys_addr;
}
cur->phys_addr = begin;
}
if (cur->phys_addr + cur->length > begin + length) {
cur->length -= cur->phys_addr + cur->length - begin - length;
}
}
}
+43
View File
@@ -0,0 +1,43 @@
system_ss.add(files(
'vl.c',
), sdl, libpmem, libdaxctl)
system_ss.add(files(
'arch_init.c',
'balloon.c',
'bootdevice.c',
'cpus.c',
'cpu-timers.c',
'dirtylimit.c',
'dirtylimit-hmp-cmds.c',
'dma-helpers.c',
'exit-with-parent.c',
'globals.c',
'ioport.c',
'ram-block-attributes.c',
'ram-discard-manager.c',
'memory_mapping.c',
'memory.c',
'physmem.c',
'physmem-qmp-cmds.c',
'qdev-monitor.c',
'qtest.c',
'rtc.c',
'runstate-action.c',
'runstate-hmp-cmds.c',
'runstate.c',
'tpm-hmp-cmds.c',
'watchpoint.c',
))
if have_tpm
system_ss.add(files('tpm.c'))
endif
system_ss.add(when: seccomp, if_true: files('qemu-seccomp.c'))
system_ss.add(when: 'CONFIG_DEVICE_TREE',
if_true: [fdt, files('device_tree.c')],
if_false: files('device_tree-stub.c'))
if host_os == 'linux'
system_ss.add(files('async-teardown.c'))
endif
+107
View File
@@ -0,0 +1,107 @@
/*
* QMP commands to dump physical memory
*
* Copyright (c) 2003-2008 Fabrice Bellard
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qapi/qapi-commands-machine.h"
#include "qapi/qmp/qerror.h"
#include "hw/core/cpu.h"
#include "system/physmem.h"
#include "migration/misc.h"
void qmp_memsave(uint64_t addr, uint64_t size, const char *filename,
bool has_cpu, int64_t cpu_index, Error **errp)
{
FILE *f;
uint64_t l;
CPUState *cpu;
uint8_t buf[1024];
uint64_t orig_addr = addr, orig_size = size;
if (migration_guest_ram_loading()) {
error_setg(errp, "Guest memory access not allowed during migration");
return;
}
if (!has_cpu) {
cpu_index = 0;
}
cpu = qemu_get_cpu(cpu_index);
if (cpu == NULL) {
error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "cpu-index",
"a CPU number");
return;
}
f = fopen(filename, "wb");
if (!f) {
error_setg_file_open(errp, errno, filename);
return;
}
while (size != 0) {
l = sizeof(buf);
if (l > size) {
l = size;
}
if (cpu_memory_rw_debug(cpu, addr, buf, l, 0) != 0) {
error_setg(errp, "Invalid addr 0x%016" PRIx64 "/size %" PRIu64
" specified", orig_addr, orig_size);
goto exit;
}
if (fwrite(buf, 1, l, f) != l) {
error_setg(errp, "writing memory to '%s' failed",
filename);
goto exit;
}
addr += l;
size -= l;
}
exit:
fclose(f);
}
void qmp_pmemsave(uint64_t addr, uint64_t size, const char *filename,
Error **errp)
{
FILE *f;
uint64_t l;
uint8_t buf[1024];
if (migration_guest_ram_loading()) {
error_setg(errp, "Guest memory access not allowed during migration");
return;
}
f = fopen(filename, "wb");
if (!f) {
error_setg_file_open(errp, errno, filename);
return;
}
while (size != 0) {
l = sizeof(buf);
if (l > size) {
l = size;
}
physical_memory_read(addr, buf, l);
if (fwrite(buf, 1, l, f) != l) {
error_setg(errp, "writing memory to '%s' failed",
filename);
goto exit;
}
addr += l;
size -= l;
}
exit:
fclose(f);
}
+4536
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+486
View File
@@ -0,0 +1,486 @@
/*
* QEMU seccomp mode 2 support with libseccomp
*
* Copyright IBM, Corp. 2012
*
* Authors:
* Eduardo Otubo <[email protected]>
*
* 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 "qapi/error.h"
#include "qemu/config-file.h"
#include "qemu/option.h"
#include "qemu/module.h"
#include <sys/prctl.h>
#include <seccomp.h>
#include "system/seccomp.h"
#include <linux/seccomp.h>
/* For some architectures (notably ARM) cacheflush is not supported until
* libseccomp 2.2.3, but configure enforces that we are using a more recent
* version on those hosts, so it is OK for this check to be less strict.
*/
#if SCMP_VER_MAJOR >= 3
#define HAVE_CACHEFLUSH
#elif SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR >= 2
#define HAVE_CACHEFLUSH
#endif
struct QemuSeccompSyscall {
int32_t num;
uint8_t set;
uint8_t narg;
const struct scmp_arg_cmp *arg_cmp;
uint32_t action;
};
const struct scmp_arg_cmp sched_setscheduler_arg[] = {
/* was SCMP_A1(SCMP_CMP_NE, SCHED_IDLE), but expanded due to GCC 4.x bug */
{ .arg = 1, .op = SCMP_CMP_NE, .datum_a = SCHED_IDLE }
};
/*
* See 'NOTES' in 'man 2 clone' - s390 has 'flags' in
* different position to other architectures
*/
#if defined(HOST_S390X) || defined(HOST_S390)
#define CLONE_FLAGS_ARG 1
#else
#define CLONE_FLAGS_ARG 0
#endif
#ifndef CLONE_PIDFD
# define CLONE_PIDFD 0x00001000
#endif
#define REQUIRE_CLONE_FLAG(flag) \
const struct scmp_arg_cmp clone_arg ## flag[] = { \
{ .arg = CLONE_FLAGS_ARG, \
.op = SCMP_CMP_MASKED_EQ, \
.datum_a = flag, .datum_b = 0 } }
#define FORBID_CLONE_FLAG(flag) \
const struct scmp_arg_cmp clone_arg ## flag[] = { \
{ .arg = CLONE_FLAGS_ARG, \
.op = SCMP_CMP_MASKED_EQ, \
.datum_a = flag, .datum_b = flag } }
#define RULE_CLONE_FLAG(flag) \
{ SCMP_SYS(clone), QEMU_SECCOMP_SET_SPAWN, \
ARRAY_SIZE(clone_arg ## flag), clone_arg ## flag, SCMP_ACT_ERRNO(EPERM) }
/* If no CLONE_* flags are set, except CSIGNAL, deny */
const struct scmp_arg_cmp clone_arg_none[] = {
{ .arg = CLONE_FLAGS_ARG,
.op = SCMP_CMP_MASKED_EQ,
.datum_a = ~(CSIGNAL), .datum_b = 0 }
};
/*
* pthread_create should always set all of these.
*/
REQUIRE_CLONE_FLAG(CLONE_VM);
REQUIRE_CLONE_FLAG(CLONE_FS);
REQUIRE_CLONE_FLAG(CLONE_FILES);
REQUIRE_CLONE_FLAG(CLONE_SIGHAND);
REQUIRE_CLONE_FLAG(CLONE_THREAD);
REQUIRE_CLONE_FLAG(CLONE_SYSVSEM);
REQUIRE_CLONE_FLAG(CLONE_SETTLS);
REQUIRE_CLONE_FLAG(CLONE_PARENT_SETTID);
REQUIRE_CLONE_FLAG(CLONE_CHILD_CLEARTID);
/*
* Musl sets this in pthread_create too, but it is
* obsolete and harmless since its behaviour is
* subsumed under CLONE_THREAD
*/
/*REQUIRE_CLONE_FLAG(CLONE_DETACHED);*/
/*
* These all indicate an attempt to spawn a process
* instead of a thread, or other undesirable scenarios
*/
FORBID_CLONE_FLAG(CLONE_PIDFD);
FORBID_CLONE_FLAG(CLONE_PTRACE);
FORBID_CLONE_FLAG(CLONE_VFORK);
FORBID_CLONE_FLAG(CLONE_PARENT);
FORBID_CLONE_FLAG(CLONE_NEWNS);
FORBID_CLONE_FLAG(CLONE_UNTRACED);
FORBID_CLONE_FLAG(CLONE_NEWCGROUP);
FORBID_CLONE_FLAG(CLONE_NEWUTS);
FORBID_CLONE_FLAG(CLONE_NEWIPC);
FORBID_CLONE_FLAG(CLONE_NEWUSER);
FORBID_CLONE_FLAG(CLONE_NEWPID);
FORBID_CLONE_FLAG(CLONE_NEWNET);
FORBID_CLONE_FLAG(CLONE_IO);
static const struct QemuSeccompSyscall denylist[] = {
/* default set of syscalls that should get blocked */
{ SCMP_SYS(reboot), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(swapon), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(swapoff), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(syslog), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(mount), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(umount), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(kexec_load), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(afs_syscall), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(break), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(ftime), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(getpmsg), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(gtty), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(lock), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(mpx), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(prof), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(profil), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(putpmsg), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(security), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(stty), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(tuxcall), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(ulimit), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(vserver), QEMU_SECCOMP_SET_DEFAULT,
0, NULL, SCMP_ACT_TRAP },
/* obsolete */
{ SCMP_SYS(readdir), QEMU_SECCOMP_SET_OBSOLETE,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(_sysctl), QEMU_SECCOMP_SET_OBSOLETE,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(bdflush), QEMU_SECCOMP_SET_OBSOLETE,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(create_module), QEMU_SECCOMP_SET_OBSOLETE,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(get_kernel_syms), QEMU_SECCOMP_SET_OBSOLETE,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(query_module), QEMU_SECCOMP_SET_OBSOLETE,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(sgetmask), QEMU_SECCOMP_SET_OBSOLETE,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(ssetmask), QEMU_SECCOMP_SET_OBSOLETE,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(sysfs), QEMU_SECCOMP_SET_OBSOLETE,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(uselib), QEMU_SECCOMP_SET_OBSOLETE,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(ustat), QEMU_SECCOMP_SET_OBSOLETE,
0, NULL, SCMP_ACT_TRAP },
/* privileged */
{ SCMP_SYS(setuid), QEMU_SECCOMP_SET_PRIVILEGED,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(setgid), QEMU_SECCOMP_SET_PRIVILEGED,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(setpgid), QEMU_SECCOMP_SET_PRIVILEGED,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(setsid), QEMU_SECCOMP_SET_PRIVILEGED,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(setreuid), QEMU_SECCOMP_SET_PRIVILEGED,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(setregid), QEMU_SECCOMP_SET_PRIVILEGED,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(setresuid), QEMU_SECCOMP_SET_PRIVILEGED,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(setresgid), QEMU_SECCOMP_SET_PRIVILEGED,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(setfsuid), QEMU_SECCOMP_SET_PRIVILEGED,
0, NULL, SCMP_ACT_TRAP },
{ SCMP_SYS(setfsgid), QEMU_SECCOMP_SET_PRIVILEGED,
0, NULL, SCMP_ACT_TRAP },
/* spawn */
{ SCMP_SYS(fork), QEMU_SECCOMP_SET_SPAWN,
0, NULL, SCMP_ACT_ERRNO(EPERM) },
{ SCMP_SYS(vfork), QEMU_SECCOMP_SET_SPAWN,
0, NULL, SCMP_ACT_ERRNO(EPERM) },
{ SCMP_SYS(execve), QEMU_SECCOMP_SET_SPAWN,
0, NULL, SCMP_ACT_ERRNO(EPERM) },
{ SCMP_SYS(clone), QEMU_SECCOMP_SET_SPAWN,
ARRAY_SIZE(clone_arg_none), clone_arg_none, SCMP_ACT_ERRNO(EPERM) },
RULE_CLONE_FLAG(CLONE_VM),
RULE_CLONE_FLAG(CLONE_FS),
RULE_CLONE_FLAG(CLONE_FILES),
RULE_CLONE_FLAG(CLONE_SIGHAND),
RULE_CLONE_FLAG(CLONE_THREAD),
RULE_CLONE_FLAG(CLONE_SYSVSEM),
RULE_CLONE_FLAG(CLONE_SETTLS),
RULE_CLONE_FLAG(CLONE_PARENT_SETTID),
RULE_CLONE_FLAG(CLONE_CHILD_CLEARTID),
/*RULE_CLONE_FLAG(CLONE_DETACHED),*/
RULE_CLONE_FLAG(CLONE_PIDFD),
RULE_CLONE_FLAG(CLONE_PTRACE),
RULE_CLONE_FLAG(CLONE_VFORK),
RULE_CLONE_FLAG(CLONE_PARENT),
RULE_CLONE_FLAG(CLONE_NEWNS),
RULE_CLONE_FLAG(CLONE_UNTRACED),
RULE_CLONE_FLAG(CLONE_NEWCGROUP),
RULE_CLONE_FLAG(CLONE_NEWUTS),
RULE_CLONE_FLAG(CLONE_NEWIPC),
RULE_CLONE_FLAG(CLONE_NEWUSER),
RULE_CLONE_FLAG(CLONE_NEWPID),
RULE_CLONE_FLAG(CLONE_NEWNET),
RULE_CLONE_FLAG(CLONE_IO),
#ifdef __SNR_clone3
{ SCMP_SYS(clone3), QEMU_SECCOMP_SET_SPAWN,
0, NULL, SCMP_ACT_ERRNO(ENOSYS) },
#endif
#ifdef __SNR_execveat
{ SCMP_SYS(execveat), QEMU_SECCOMP_SET_SPAWN },
#endif
{ SCMP_SYS(setns), QEMU_SECCOMP_SET_SPAWN },
{ SCMP_SYS(unshare), QEMU_SECCOMP_SET_SPAWN },
/* resource control */
{ SCMP_SYS(setpriority), QEMU_SECCOMP_SET_RESOURCECTL,
0, NULL, SCMP_ACT_ERRNO(EPERM) },
{ SCMP_SYS(sched_setparam), QEMU_SECCOMP_SET_RESOURCECTL,
0, NULL, SCMP_ACT_ERRNO(EPERM) },
{ SCMP_SYS(sched_setscheduler), QEMU_SECCOMP_SET_RESOURCECTL,
ARRAY_SIZE(sched_setscheduler_arg), sched_setscheduler_arg,
SCMP_ACT_ERRNO(EPERM) },
{ SCMP_SYS(sched_setaffinity), QEMU_SECCOMP_SET_RESOURCECTL,
0, NULL, SCMP_ACT_ERRNO(EPERM) },
};
static inline __attribute__((unused)) int
qemu_seccomp(unsigned int operation, unsigned int flags, void *args)
{
#ifdef __NR_seccomp
return syscall(__NR_seccomp, operation, flags, args);
#else
errno = ENOSYS;
return -1;
#endif
}
static uint32_t qemu_seccomp_update_action(uint32_t action)
{
#if defined(SECCOMP_GET_ACTION_AVAIL) && defined(SCMP_ACT_KILL_PROCESS) && \
defined(SECCOMP_RET_KILL_PROCESS)
if (action == SCMP_ACT_TRAP) {
static int kill_process = -1;
if (kill_process == -1) {
uint32_t testaction = SECCOMP_RET_KILL_PROCESS;
if (qemu_seccomp(SECCOMP_GET_ACTION_AVAIL, 0, &testaction) == 0) {
kill_process = 1;
} else {
kill_process = 0;
}
}
if (kill_process == 1) {
return SCMP_ACT_KILL_PROCESS;
}
}
#endif
return action;
}
static int seccomp_start(uint32_t seccomp_opts, Error **errp)
{
int rc = -1;
unsigned int i = 0;
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_ALLOW);
if (ctx == NULL) {
error_setg(errp, "failed to initialize seccomp context");
goto seccomp_return;
}
#if defined(CONFIG_SECCOMP_SYSRAWRC)
/*
* This must be the first seccomp_attr_set() call to have full
* error propagation from subsequent seccomp APIs.
*/
rc = seccomp_attr_set(ctx, SCMP_FLTATR_API_SYSRAWRC, 1);
if (rc != 0) {
error_setg_errno(errp, -rc,
"failed to set seccomp rawrc attribute");
goto seccomp_return;
}
#endif
rc = seccomp_attr_set(ctx, SCMP_FLTATR_CTL_TSYNC, 1);
if (rc != 0) {
error_setg_errno(errp, -rc,
"failed to set seccomp thread synchronization");
goto seccomp_return;
}
for (i = 0; i < ARRAY_SIZE(denylist); i++) {
uint32_t action;
if (!(seccomp_opts & denylist[i].set)) {
continue;
}
action = qemu_seccomp_update_action(denylist[i].action);
rc = seccomp_rule_add_array(ctx, action, denylist[i].num,
denylist[i].narg, denylist[i].arg_cmp);
if (rc < 0) {
error_setg_errno(errp, -rc,
"failed to add seccomp denylist rules");
goto seccomp_return;
}
}
rc = seccomp_load(ctx);
if (rc < 0) {
error_setg_errno(errp, -rc,
"failed to load seccomp syscall filter in kernel");
}
seccomp_return:
seccomp_release(ctx);
return rc < 0 ? -1 : 0;
}
int parse_sandbox(void *opaque, QemuOpts *opts, Error **errp)
{
if (qemu_opt_get_bool(opts, "enable", false)) {
uint32_t seccomp_opts = QEMU_SECCOMP_SET_DEFAULT
| QEMU_SECCOMP_SET_OBSOLETE;
const char *value = NULL;
value = qemu_opt_get(opts, "obsolete");
if (value) {
if (g_str_equal(value, "allow")) {
seccomp_opts &= ~QEMU_SECCOMP_SET_OBSOLETE;
} else if (g_str_equal(value, "deny")) {
/* this is the default option, this if is here
* to provide a little bit of consistency for
* the command line */
} else {
error_setg(errp, "invalid argument for obsolete");
return -1;
}
}
value = qemu_opt_get(opts, "elevateprivileges");
if (value) {
if (g_str_equal(value, "deny")) {
seccomp_opts |= QEMU_SECCOMP_SET_PRIVILEGED;
} else if (g_str_equal(value, "children")) {
seccomp_opts |= QEMU_SECCOMP_SET_PRIVILEGED;
/* calling prctl directly because we're
* not sure if host has CAP_SYS_ADMIN set*/
if (prctl(PR_SET_NO_NEW_PRIVS, 1)) {
error_setg(errp, "failed to set no_new_privs aborting");
return -1;
}
} else if (g_str_equal(value, "allow")) {
/* default value */
} else {
error_setg(errp, "invalid argument for elevateprivileges");
return -1;
}
}
value = qemu_opt_get(opts, "spawn");
if (value) {
if (g_str_equal(value, "deny")) {
seccomp_opts |= QEMU_SECCOMP_SET_SPAWN;
} else if (g_str_equal(value, "allow")) {
/* default value */
} else {
error_setg(errp, "invalid argument for spawn");
return -1;
}
}
value = qemu_opt_get(opts, "resourcecontrol");
if (value) {
if (g_str_equal(value, "deny")) {
seccomp_opts |= QEMU_SECCOMP_SET_RESOURCECTL;
} else if (g_str_equal(value, "allow")) {
/* default value */
} else {
error_setg(errp, "invalid argument for resourcecontrol");
return -1;
}
}
if (seccomp_start(seccomp_opts, errp) < 0) {
return -1;
}
}
return 0;
}
static QemuOptsList qemu_sandbox_opts = {
.name = "sandbox",
.implied_opt_name = "enable",
.head = QTAILQ_HEAD_INITIALIZER(qemu_sandbox_opts.head),
.desc = {
{
.name = "enable",
.type = QEMU_OPT_BOOL,
},
{
.name = "obsolete",
.type = QEMU_OPT_STRING,
},
{
.name = "elevateprivileges",
.type = QEMU_OPT_STRING,
},
{
.name = "spawn",
.type = QEMU_OPT_STRING,
},
{
.name = "resourcecontrol",
.type = QEMU_OPT_STRING,
},
{ /* end of list */ }
},
};
static void seccomp_register(void)
{
bool add = false;
/* FIXME: use seccomp_api_get() >= 2 check when released */
#if defined(SECCOMP_FILTER_FLAG_TSYNC)
int check;
/* check host TSYNC capability, it returns errno == ENOSYS if unavailable */
check = qemu_seccomp(SECCOMP_SET_MODE_FILTER,
SECCOMP_FILTER_FLAG_TSYNC, NULL);
if (check < 0 && errno == EFAULT) {
add = true;
}
#endif
if (add) {
qemu_add_opts(&qemu_sandbox_opts);
}
}
opts_init(seccomp_register);
+1092
View File
File diff suppressed because it is too large Load Diff
+223
View File
@@ -0,0 +1,223 @@
/*
* QEMU ram block attributes
*
* Copyright Intel
*
* Author:
* Chenyi Qiang <[email protected]>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "qemu/error-report.h"
#include "system/ramblock.h"
#include "trace.h"
OBJECT_DEFINE_SIMPLE_TYPE_WITH_INTERFACES(RamBlockAttributes,
ram_block_attributes,
RAM_BLOCK_ATTRIBUTES,
OBJECT,
{ TYPE_RAM_DISCARD_SOURCE },
{ })
static size_t
ram_block_attributes_get_block_size(void)
{
/*
* Because page conversion could be manipulated in the size of at least 4K
* or 4K aligned, Use the host page size as the granularity to track the
* memory attribute.
*/
return qemu_real_host_page_size();
}
/* RamDiscardSource interface implementation */
static uint64_t
ram_block_attributes_rds_get_min_granularity(const RamDiscardSource *rds,
const MemoryRegion *mr)
{
const RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rds);
g_assert(mr == attr->ram_block->mr);
return ram_block_attributes_get_block_size();
}
static bool
ram_block_attributes_rds_is_populated(const RamDiscardSource *rds,
const MemoryRegionSection *section)
{
const RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rds);
const size_t block_size = ram_block_attributes_get_block_size();
const uint64_t first_bit = section->offset_within_region / block_size;
const uint64_t last_bit =
first_bit + int128_get64(section->size) / block_size - 1;
unsigned long first_discarded_bit;
first_discarded_bit = find_next_zero_bit(attr->bitmap, last_bit + 1,
first_bit);
return first_discarded_bit > last_bit;
}
static bool
ram_block_attributes_is_valid_range(RamBlockAttributes *attr, uint64_t offset,
uint64_t size)
{
MemoryRegion *mr = attr->ram_block->mr;
g_assert(mr);
uint64_t region_size = memory_region_size(mr);
const size_t block_size = ram_block_attributes_get_block_size();
if (!QEMU_IS_ALIGNED(offset, block_size) ||
!QEMU_IS_ALIGNED(size, block_size)) {
return false;
}
if (offset + size <= offset) {
return false;
}
if (offset + size > region_size) {
return false;
}
return true;
}
static void
ram_block_attributes_notify_discard(RamBlockAttributes *attr,
uint64_t offset,
uint64_t size)
{
RamDiscardManager *rdm = memory_region_get_ram_discard_manager(attr->ram_block->mr);
ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(attr),
offset, size);
}
static int
ram_block_attributes_notify_populate(RamBlockAttributes *attr,
uint64_t offset, uint64_t size)
{
RamDiscardManager *rdm = memory_region_get_ram_discard_manager(attr->ram_block->mr);
return ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(attr),
offset, size);
}
int ram_block_attributes_state_change(RamBlockAttributes *attr,
uint64_t offset, uint64_t size,
bool to_discard)
{
const size_t block_size = ram_block_attributes_get_block_size();
const unsigned long first_bit = offset / block_size;
const unsigned long nbits = size / block_size;
const unsigned long last_bit = first_bit + nbits - 1;
const bool is_discarded = find_next_bit(attr->bitmap, attr->bitmap_size,
first_bit) > last_bit;
const bool is_populated = find_next_zero_bit(attr->bitmap,
attr->bitmap_size, first_bit) > last_bit;
unsigned long bit;
int ret = 0;
if (!ram_block_attributes_is_valid_range(attr, offset, size)) {
error_report("%s, invalid range: offset 0x%" PRIx64 ", size "
"0x%" PRIx64, __func__, offset, size);
return -EINVAL;
}
trace_ram_block_attributes_state_change(offset, size,
is_discarded ? "discarded" :
is_populated ? "populated" :
"mixture",
to_discard ? "discarded" :
"populated");
if (to_discard) {
if (is_discarded) {
/* Already private */
} else if (is_populated) {
/* Completely shared */
bitmap_clear(attr->bitmap, first_bit, nbits);
ram_block_attributes_notify_discard(attr, offset, size);
} else {
/* Unexpected mixture: process individual blocks */
for (bit = first_bit; bit < first_bit + nbits; bit++) {
if (!test_bit(bit, attr->bitmap)) {
continue;
}
clear_bit(bit, attr->bitmap);
ram_block_attributes_notify_discard(attr, bit * block_size,
block_size);
}
}
} else {
if (is_populated) {
/* Already shared */
} else if (is_discarded) {
/* Completely private */
bitmap_set(attr->bitmap, first_bit, nbits);
ret = ram_block_attributes_notify_populate(attr, offset, size);
} else {
/* Unexpected mixture: process individual blocks */
for (bit = first_bit; bit < first_bit + nbits; bit++) {
if (test_bit(bit, attr->bitmap)) {
continue;
}
set_bit(bit, attr->bitmap);
ret = ram_block_attributes_notify_populate(attr,
bit * block_size,
block_size);
if (ret) {
break;
}
}
}
}
return ret;
}
RamBlockAttributes *ram_block_attributes_create(RAMBlock *ram_block)
{
const int block_size = ram_block_attributes_get_block_size();
RamBlockAttributes *attr;
MemoryRegion *mr = ram_block->mr;
attr = RAM_BLOCK_ATTRIBUTES(object_new(TYPE_RAM_BLOCK_ATTRIBUTES));
attr->ram_block = ram_block;
if (memory_region_add_ram_discard_source(mr, RAM_DISCARD_SOURCE(attr))) {
object_unref(OBJECT(attr));
return NULL;
}
attr->bitmap_size = DIV_ROUND_UP(int128_get64(mr->size), block_size);
attr->bitmap = bitmap_new(attr->bitmap_size);
return attr;
}
void ram_block_attributes_destroy(RamBlockAttributes *attr)
{
g_assert(attr);
g_free(attr->bitmap);
memory_region_del_ram_discard_source(attr->ram_block->mr, RAM_DISCARD_SOURCE(attr));
object_unref(OBJECT(attr));
}
static void ram_block_attributes_init(Object *obj)
{
}
static void ram_block_attributes_finalize(Object *obj)
{
}
static void ram_block_attributes_class_init(ObjectClass *klass,
const void *data)
{
RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_CLASS(klass);
rdsc->get_min_granularity = ram_block_attributes_rds_get_min_granularity;
rdsc->is_populated = ram_block_attributes_rds_is_populated;
}
+612
View File
@@ -0,0 +1,612 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* RAM Discard Manager
*
* Copyright Red Hat, Inc. 2026
*/
#include "qemu/osdep.h"
#include "qemu/error-report.h"
#include "qemu/queue.h"
#include "system/memory.h"
static uint64_t ram_discard_source_get_min_granularity(const RamDiscardSource *rds,
const MemoryRegion *mr)
{
RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds);
g_assert(rdsc->get_min_granularity);
return rdsc->get_min_granularity(rds, mr);
}
static bool ram_discard_source_is_populated(const RamDiscardSource *rds,
const MemoryRegionSection *section)
{
RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds);
g_assert(rdsc->is_populated);
return rdsc->is_populated(rds, section);
}
/*
* Iterate a single source's populated or discarded regions and call
* replay_fn for each contiguous run.
*/
static int replay_source_by_state(const RamDiscardSource *source,
const MemoryRegion *mr,
const MemoryRegionSection *section,
bool replay_populated,
ReplayRamDiscardState replay_fn,
void *opaque)
{
uint64_t granularity, offset, size, end, pos, run_start = 0;
bool in_run = false;
int ret = 0;
granularity = ram_discard_source_get_min_granularity(source, mr);
offset = section->offset_within_region;
size = int128_get64(section->size);
end = offset + size;
/* Align iteration to granularity boundaries */
pos = QEMU_ALIGN_DOWN(offset, granularity);
for (; pos < end; pos += granularity) {
MemoryRegionSection chunk = {
.mr = section->mr,
.offset_within_region = pos,
.size = int128_make64(granularity),
};
bool populated = ram_discard_source_is_populated(source, &chunk);
if (populated == replay_populated) {
if (!in_run) {
run_start = pos;
in_run = true;
}
} else if (in_run) {
MemoryRegionSection tmp = *section;
if (memory_region_section_intersect_range(&tmp, run_start,
pos - run_start)) {
ret = replay_fn(&tmp, opaque);
if (ret) {
return ret;
}
}
in_run = false;
}
}
if (in_run) {
MemoryRegionSection tmp = *section;
if (memory_region_section_intersect_range(&tmp, run_start,
pos - run_start)) {
ret = replay_fn(&tmp, opaque);
}
}
return ret;
}
RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr)
{
RamDiscardManager *rdm;
rdm = RAM_DISCARD_MANAGER(object_new(TYPE_RAM_DISCARD_MANAGER));
rdm->mr = mr;
return rdm;
}
static void ram_discard_manager_update_granularity(RamDiscardManager *rdm)
{
RamDiscardSourceEntry *entry;
uint64_t granularity = 0;
QLIST_FOREACH(entry, &rdm->source_list, next) {
uint64_t src_granularity;
src_granularity =
ram_discard_source_get_min_granularity(entry->rds, rdm->mr);
g_assert(src_granularity != 0);
if (granularity == 0) {
granularity = src_granularity;
} else {
granularity = MIN(granularity, src_granularity);
}
}
rdm->min_granularity = granularity;
}
static RamDiscardSourceEntry *
ram_discard_manager_find_source(RamDiscardManager *rdm, RamDiscardSource *rds)
{
RamDiscardSourceEntry *entry;
QLIST_FOREACH(entry, &rdm->source_list, next) {
if (entry->rds == rds) {
return entry;
}
}
return NULL;
}
static int rdl_populate_cb(const MemoryRegionSection *section, void *opaque)
{
RamDiscardListener *rdl = opaque;
MemoryRegionSection tmp = *rdl->section;
g_assert(section->mr == rdl->section->mr);
if (!memory_region_section_intersect_range(&tmp,
section->offset_within_region,
int128_get64(section->size))) {
return 0;
}
return rdl->notify_populate(rdl, &tmp);
}
static int rdl_discard_cb(const MemoryRegionSection *section, void *opaque)
{
RamDiscardListener *rdl = opaque;
MemoryRegionSection tmp = *rdl->section;
g_assert(section->mr == rdl->section->mr);
if (!memory_region_section_intersect_range(&tmp,
section->offset_within_region,
int128_get64(section->size))) {
return 0;
}
rdl->notify_discard(rdl, &tmp);
return 0;
}
static bool rdm_is_all_populated_skip(const RamDiscardManager *rdm,
const MemoryRegionSection *section,
const RamDiscardSource *skip_source)
{
RamDiscardSourceEntry *entry;
QLIST_FOREACH(entry, &rdm->source_list, next) {
if (skip_source && entry->rds == skip_source) {
continue;
}
if (!ram_discard_source_is_populated(entry->rds, section)) {
return false;
}
}
return true;
}
typedef struct SourceNotifyCtx {
RamDiscardManager *rdm;
RamDiscardListener *rdl;
RamDiscardSource *source; /* added or removed */
} SourceNotifyCtx;
/*
* Unified helper to replay regions based on populated state.
* If replay_populated is true: replay regions where ALL sources are populated.
* If replay_populated is false: replay regions where ANY source is discarded.
*/
static int replay_by_populated_state(const RamDiscardManager *rdm,
const MemoryRegionSection *section,
const RamDiscardSource *skip_source,
bool replay_populated,
ReplayRamDiscardState replay_fn,
void *user_opaque)
{
uint64_t granularity = rdm->min_granularity;
uint64_t offset, end_offset;
uint64_t run_start = 0;
bool in_run = false;
int ret = 0;
if (QLIST_EMPTY(&rdm->source_list)) {
if (replay_populated) {
return replay_fn(section, user_opaque);
}
return 0;
}
g_assert(granularity != 0);
offset = section->offset_within_region;
end_offset = offset + int128_get64(section->size);
while (offset < end_offset) {
MemoryRegionSection subsection = {
.mr = section->mr,
.offset_within_region = offset,
.size = int128_make64(MIN(granularity, end_offset - offset)),
};
bool all_populated;
bool included;
all_populated = rdm_is_all_populated_skip(rdm, &subsection,
skip_source);
included = replay_populated ? all_populated : !all_populated;
if (included) {
if (!in_run) {
run_start = offset;
in_run = true;
}
} else {
if (in_run) {
MemoryRegionSection run_section = {
.mr = section->mr,
.offset_within_region = run_start,
.size = int128_make64(offset - run_start),
};
ret = replay_fn(&run_section, user_opaque);
if (ret) {
return ret;
}
in_run = false;
}
}
if (granularity > end_offset - offset) {
break;
}
offset += granularity;
}
if (in_run) {
MemoryRegionSection run_section = {
.mr = section->mr,
.offset_within_region = run_start,
.size = int128_make64(end_offset - run_start),
};
ret = replay_fn(&run_section, user_opaque);
}
return ret;
}
static int add_source_check_discard_cb(const MemoryRegionSection *section,
void *opaque)
{
SourceNotifyCtx *ctx = opaque;
return replay_by_populated_state(ctx->rdm, section, ctx->source, true,
rdl_discard_cb, ctx->rdl);
}
static int del_source_check_populate_cb(const MemoryRegionSection *section,
void *opaque)
{
SourceNotifyCtx *ctx = opaque;
return replay_by_populated_state(ctx->rdm, section, ctx->source, true,
rdl_populate_cb, ctx->rdl);
}
int ram_discard_manager_add_source(RamDiscardManager *rdm,
RamDiscardSource *source)
{
RamDiscardSourceEntry *entry;
RamDiscardListener *rdl, *rdl2;
int ret = 0;
if (ram_discard_manager_find_source(rdm, source)) {
return -EBUSY;
}
/*
* If there are existing listeners, notify them about regions that
* become discarded due to adding this source. Only notify for regions
* that were previously populated (all other sources agreed).
*/
QLIST_FOREACH(rdl, &rdm->rdl_list, next) {
SourceNotifyCtx ctx = {
.rdm = rdm,
.rdl = rdl,
/* no need to set source */
};
ret = replay_source_by_state(source, rdm->mr, rdl->section,
false,
add_source_check_discard_cb, &ctx);
if (ret) {
break;
}
}
if (ret) {
QLIST_FOREACH(rdl2, &rdm->rdl_list, next) {
SourceNotifyCtx ctx = {
.rdm = rdm,
.rdl = rdl2,
};
replay_source_by_state(source, rdm->mr, rdl2->section,
false,
del_source_check_populate_cb,
&ctx);
if (rdl == rdl2) {
break;
}
}
return ret;
}
entry = g_new0(RamDiscardSourceEntry, 1);
entry->rds = source;
QLIST_INSERT_HEAD(&rdm->source_list, entry, next);
ram_discard_manager_update_granularity(rdm);
return ret;
}
int ram_discard_manager_del_source(RamDiscardManager *rdm,
RamDiscardSource *source)
{
RamDiscardSourceEntry *entry;
RamDiscardListener *rdl, *rdl2;
int ret = 0;
entry = ram_discard_manager_find_source(rdm, source);
if (!entry) {
return -ENOENT;
}
/*
* If there are existing listeners, check if any regions become
* populated due to removing this source.
*/
QLIST_FOREACH(rdl, &rdm->rdl_list, next) {
SourceNotifyCtx ctx = {
.rdm = rdm,
.rdl = rdl,
.source = source,
};
/*
* From the previously discarded regions, check if any
* regions become populated.
*/
ret = replay_source_by_state(source, rdm->mr, rdl->section,
false,
del_source_check_populate_cb,
&ctx);
if (ret) {
break;
}
}
if (ret) {
QLIST_FOREACH(rdl2, &rdm->rdl_list, next) {
SourceNotifyCtx ctx = {
.rdm = rdm,
.rdl = rdl2,
.source = source,
};
replay_source_by_state(source, rdm->mr, rdl2->section,
false,
add_source_check_discard_cb,
&ctx);
if (rdl == rdl2) {
break;
}
}
return ret;
}
QLIST_REMOVE(entry, next);
g_free(entry);
ram_discard_manager_update_granularity(rdm);
return ret;
}
uint64_t ram_discard_manager_get_min_granularity(const RamDiscardManager *rdm,
const MemoryRegion *mr)
{
g_assert(mr == rdm->mr);
return rdm->min_granularity;
}
/*
* Aggregated query: returns true only if ALL sources report populated (AND).
*/
bool ram_discard_manager_is_populated(const RamDiscardManager *rdm,
const MemoryRegionSection *section)
{
RamDiscardSourceEntry *entry;
QLIST_FOREACH(entry, &rdm->source_list, next) {
if (!ram_discard_source_is_populated(entry->rds, section)) {
return false;
}
}
return true;
}
int ram_discard_manager_replay_populated(const RamDiscardManager *rdm,
const MemoryRegionSection *section,
ReplayRamDiscardState replay_fn,
void *opaque)
{
return replay_by_populated_state(rdm, section, NULL, true,
replay_fn, opaque);
}
int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm,
const MemoryRegionSection *section,
ReplayRamDiscardState replay_fn,
void *opaque)
{
return replay_by_populated_state(rdm, section, NULL, false,
replay_fn, opaque);
}
static void ram_discard_manager_initfn(Object *obj)
{
RamDiscardManager *rdm = RAM_DISCARD_MANAGER(obj);
QLIST_INIT(&rdm->source_list);
QLIST_INIT(&rdm->rdl_list);
rdm->min_granularity = 0;
}
static void ram_discard_manager_finalize(Object *obj)
{
RamDiscardManager *rdm = RAM_DISCARD_MANAGER(obj);
g_assert(QLIST_EMPTY(&rdm->rdl_list));
g_assert(QLIST_EMPTY(&rdm->source_list));
}
int ram_discard_manager_notify_populate(RamDiscardManager *rdm,
RamDiscardSource *source,
uint64_t offset, uint64_t size)
{
RamDiscardListener *rdl, *rdl2;
MemoryRegionSection section = {
.mr = rdm->mr,
.offset_within_region = offset,
.size = int128_make64(size),
};
int ret = 0;
g_assert(ram_discard_manager_find_source(rdm, source));
/*
* Only notify about regions that are populated in ALL sources.
* Skip the calling source: it has implicitly declared itself populated
* for this range but may not have updated its bitmap yet.
*/
QLIST_FOREACH(rdl, &rdm->rdl_list, next) {
ret = replay_by_populated_state(rdm, &section, source, true,
rdl_populate_cb, rdl);
if (ret) {
break;
}
}
if (ret) {
/*
* Rollback: notify discard for listeners we already notified,
* including the failing listener which may have been partially
* notified. Listeners must handle discard notifications for
* regions they didn't receive populate notifications for.
*/
QLIST_FOREACH(rdl2, &rdm->rdl_list, next) {
replay_by_populated_state(rdm, &section, source, true,
rdl_discard_cb, rdl2);
if (rdl2 == rdl) {
break;
}
}
}
return ret;
}
void ram_discard_manager_notify_discard(RamDiscardManager *rdm,
RamDiscardSource *source,
uint64_t offset, uint64_t size)
{
RamDiscardListener *rdl;
MemoryRegionSection section = {
.mr = rdm->mr,
.offset_within_region = offset,
.size = int128_make64(size),
};
g_assert(ram_discard_manager_find_source(rdm, source));
/*
* Only notify about ranges that were aggregately populated before this
* source's discard. Since the source has already updated its state,
* we use replay_by_populated_state with this source skipped - it will
* replay only the ranges where all OTHER sources are populated.
*/
QLIST_FOREACH(rdl, &rdm->rdl_list, next) {
replay_by_populated_state(rdm, &section, source, true,
rdl_discard_cb, rdl);
}
}
void ram_discard_manager_notify_discard_all(RamDiscardManager *rdm,
RamDiscardSource *source)
{
RamDiscardListener *rdl;
g_assert(ram_discard_manager_find_source(rdm, source));
QLIST_FOREACH(rdl, &rdm->rdl_list, next) {
rdl->notify_discard(rdl, rdl->section);
}
}
void ram_discard_manager_register_listener(RamDiscardManager *rdm,
RamDiscardListener *rdl,
MemoryRegionSection *section)
{
int ret;
g_assert(section->mr == rdm->mr);
object_ref(rdm);
rdl->section = memory_region_section_new_copy(section);
QLIST_INSERT_HEAD(&rdm->rdl_list, rdl, next);
ret = ram_discard_manager_replay_populated(rdm, rdl->section,
rdl_populate_cb, rdl);
if (ret) {
error_report("%s: Replaying populated ranges failed: %s", __func__,
strerror(-ret));
}
}
void ram_discard_manager_unregister_listener(RamDiscardManager *rdm,
RamDiscardListener *rdl)
{
g_assert(rdl->section);
g_assert(rdl->section->mr == rdm->mr);
rdl->notify_discard(rdl, rdl->section);
memory_region_section_free_copy(rdl->section);
rdl->section = NULL;
QLIST_REMOVE(rdl, next);
object_unref(rdm);
}
int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *rdm)
{
RamDiscardListener *rdl;
int ret = 0;
QLIST_FOREACH(rdl, &rdm->rdl_list, next) {
ret = ram_discard_manager_replay_populated(rdm, rdl->section,
rdl_populate_cb, rdl);
if (ret) {
break;
}
}
return ret;
}
static const TypeInfo ram_discard_manager_info = {
.parent = TYPE_OBJECT,
.name = TYPE_RAM_DISCARD_MANAGER,
.instance_size = sizeof(RamDiscardManager),
.instance_init = ram_discard_manager_initfn,
.instance_finalize = ram_discard_manager_finalize,
};
static const TypeInfo ram_discard_source_info = {
.parent = TYPE_INTERFACE,
.name = TYPE_RAM_DISCARD_SOURCE,
.class_size = sizeof(RamDiscardSourceClass),
};
static void ram_discard_manager_register_types(void)
{
type_register_static(&ram_discard_manager_info);
type_register_static(&ram_discard_source_info);
}
type_init(ram_discard_manager_register_types)
+192
View File
@@ -0,0 +1,192 @@
/*
* RTC configuration and clock read
*
* Copyright (c) 2003-2020 QEMU contributors
*
* 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 "qapi/error.h"
#include "qemu/error-report.h"
#include "qemu/option.h"
#include "qemu/timer.h"
#include "qom/compat-properties.h"
#include "qom/object.h"
#include "system/replay.h"
#include "system/system.h"
#include "system/rtc.h"
#include "hw/rtc/mc146818rtc.h"
static enum {
RTC_BASE_UTC,
RTC_BASE_LOCALTIME,
RTC_BASE_DATETIME,
} rtc_base_type = RTC_BASE_UTC;
static time_t rtc_ref_start_datetime;
static time_t rtc_realtime_clock_offset; /* used only with QEMU_CLOCK_REALTIME */
static time_t rtc_host_datetime_offset = -1; /* valid & used only with
RTC_BASE_DATETIME */
QEMUClockType rtc_clock;
/***********************************************************/
/* RTC reference time/date access */
static time_t qemu_ref_timedate(QEMUClockType clock)
{
time_t value = qemu_clock_get_ms(clock) / 1000;
switch (clock) {
case QEMU_CLOCK_REALTIME:
value -= rtc_realtime_clock_offset;
/* fall through */
case QEMU_CLOCK_VIRTUAL:
value += rtc_ref_start_datetime;
break;
case QEMU_CLOCK_HOST:
if (rtc_base_type == RTC_BASE_DATETIME) {
value -= rtc_host_datetime_offset;
}
break;
default:
g_assert_not_reached();
}
return value;
}
void qemu_get_timedate(struct tm *tm, time_t offset)
{
time_t ti = qemu_ref_timedate(rtc_clock);
ti += offset;
switch (rtc_base_type) {
case RTC_BASE_DATETIME:
case RTC_BASE_UTC:
gmtime_r(&ti, tm);
break;
case RTC_BASE_LOCALTIME:
localtime_r(&ti, tm);
break;
}
}
time_t qemu_timedate_diff(struct tm *tm)
{
time_t seconds;
switch (rtc_base_type) {
case RTC_BASE_DATETIME:
case RTC_BASE_UTC:
seconds = mktimegm(tm);
break;
case RTC_BASE_LOCALTIME:
{
struct tm tmp = *tm;
tmp.tm_isdst = -1; /* use timezone to figure it out */
seconds = mktime(&tmp);
break;
}
default:
abort();
}
return seconds - qemu_ref_timedate(QEMU_CLOCK_HOST);
}
static void configure_rtc_base_datetime(const char *startdate)
{
time_t rtc_start_datetime;
struct tm tm;
if (sscanf(startdate, "%d-%d-%dT%d:%d:%d", &tm.tm_year, &tm.tm_mon,
&tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6) {
/* OK */
} else if (sscanf(startdate, "%d-%d-%d",
&tm.tm_year, &tm.tm_mon, &tm.tm_mday) == 3) {
tm.tm_hour = 0;
tm.tm_min = 0;
tm.tm_sec = 0;
} else {
goto date_fail;
}
tm.tm_year -= 1900;
tm.tm_mon--;
rtc_start_datetime = mktimegm(&tm);
if (rtc_start_datetime == -1) {
date_fail:
error_report("invalid datetime format");
error_printf("valid formats: "
"'2006-06-17T16:01:21' or '2006-06-17'\n");
exit(1);
}
rtc_host_datetime_offset = rtc_ref_start_datetime - rtc_start_datetime;
rtc_ref_start_datetime = rtc_start_datetime;
}
void configure_rtc(QemuOpts *opts)
{
const char *value;
/* Set defaults */
rtc_clock = QEMU_CLOCK_HOST;
rtc_ref_start_datetime = qemu_clock_get_ms(QEMU_CLOCK_HOST) / 1000;
rtc_realtime_clock_offset = qemu_clock_get_ms(QEMU_CLOCK_REALTIME) / 1000;
value = qemu_opt_get(opts, "base");
if (value) {
if (!strcmp(value, "utc")) {
rtc_base_type = RTC_BASE_UTC;
} else if (!strcmp(value, "localtime")) {
rtc_base_type = RTC_BASE_LOCALTIME;
replay_add_blocker("-rtc base=localtime");
} else {
rtc_base_type = RTC_BASE_DATETIME;
configure_rtc_base_datetime(value);
}
}
value = qemu_opt_get(opts, "clock");
if (value) {
if (!strcmp(value, "host")) {
rtc_clock = QEMU_CLOCK_HOST;
} else if (!strcmp(value, "rt")) {
rtc_clock = QEMU_CLOCK_REALTIME;
} else if (!strcmp(value, "vm")) {
rtc_clock = QEMU_CLOCK_VIRTUAL;
} else {
error_report("invalid option value '%s'", value);
exit(1);
}
}
value = qemu_opt_get(opts, "driftfix");
if (value) {
if (!strcmp(value, "slew")) {
object_register_sugar_prop(TYPE_MC146818_RTC,
"lost_tick_policy",
"slew",
false);
if (!object_class_by_name(TYPE_MC146818_RTC)) {
warn_report("driftfix 'slew' is not available with this machine");
}
} else if (!strcmp(value, "none")) {
/* discard is default */
} else {
error_report("invalid option value '%s'", value);
exit(1);
}
}
}
+46
View File
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2020 Oracle and/or its affiliates.
*
* This work is licensed under the terms of the GNU GPL, version 2.
* See the COPYING file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include "system/runstate-action.h"
#include "system/watchdog.h"
#include "qemu/config-file.h"
#include "qapi/error.h"
#include "qemu/option_int.h"
RebootAction reboot_action = REBOOT_ACTION_RESET;
ShutdownAction shutdown_action = SHUTDOWN_ACTION_POWEROFF;
PanicAction panic_action = PANIC_ACTION_SHUTDOWN;
/*
* Receives actions to be applied for specific guest events
* and sets the internal state as requested.
*/
void qmp_set_action(bool has_reboot, RebootAction reboot,
bool has_shutdown, ShutdownAction shutdown,
bool has_panic, PanicAction panic,
bool has_watchdog, WatchdogAction watchdog,
Error **errp)
{
if (has_reboot) {
reboot_action = reboot;
}
if (has_panic) {
panic_action = panic;
}
if (has_watchdog) {
qmp_watchdog_set_action(watchdog, errp);
}
/* Process shutdown last, in case the panic action needs to be altered */
if (has_shutdown) {
shutdown_action = shutdown;
}
}
+96
View File
@@ -0,0 +1,96 @@
/*
* HMP commands related to run state
*
* Copyright IBM, Corp. 2011
*
* Authors:
* Anthony Liguori <[email protected]>
*
* 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 "exec/cpu-common.h"
#include "monitor/hmp.h"
#include "monitor/hmp-completion.h"
#include "monitor/monitor.h"
#include "qapi/error.h"
#include "qapi/qapi-commands-run-state.h"
#include "qobject/qdict.h"
#include "qemu/accel.h"
void hmp_info_status(Monitor *mon, const QDict *qdict)
{
StatusInfo *info;
info = qmp_query_status(NULL);
monitor_printf(mon, "VM status: %s",
info->running ? "running" : "paused");
if (!info->running && info->status != RUN_STATE_PAUSED) {
monitor_printf(mon, " (%s)", RunState_str(info->status));
}
monitor_printf(mon, "\n");
qapi_free_StatusInfo(info);
}
void hmp_one_insn_per_tb(Monitor *mon, const QDict *qdict)
{
const char *option = qdict_get_try_str(qdict, "option");
AccelState *accel = current_accel();
bool newval;
if (!object_property_find(OBJECT(accel), "one-insn-per-tb")) {
monitor_printf(mon,
"This accelerator does not support setting one-insn-per-tb\n");
return;
}
if (!option || !strcmp(option, "on")) {
newval = true;
} else if (!strcmp(option, "off")) {
newval = false;
} else {
monitor_printf(mon, "unexpected option %s\n", option);
return;
}
/* If the property exists then setting it can never fail */
object_property_set_bool(OBJECT(accel), "one-insn-per-tb",
newval, &error_abort);
}
void hmp_watchdog_action(Monitor *mon, const QDict *qdict)
{
Error *err = NULL;
WatchdogAction action;
char *qapi_value;
qapi_value = g_ascii_strdown(qdict_get_str(qdict, "action"), -1);
action = qapi_enum_parse(&WatchdogAction_lookup, qapi_value, -1, &err);
g_free(qapi_value);
if (err) {
hmp_handle_error(mon, err);
return;
}
qmp_watchdog_set_action(action, &error_abort);
}
void watchdog_action_completion(ReadLineState *rs, int nb_args, const char *str)
{
int i;
if (nb_args != 2) {
return;
}
readline_set_completion_index(rs, strlen(str));
for (i = 0; i < WATCHDOG_ACTION__MAX; i++) {
readline_add_completion_of(rs, str, WatchdogAction_str(i));
}
}
+1205
View File
File diff suppressed because it is too large Load Diff
+65
View File
@@ -0,0 +1,65 @@
/*
* HMP commands related to TPM
*
* This work is licensed under the terms of the GNU GPL, version 2 or
* (at your option) any later version.
*/
#include "qemu/osdep.h"
#include "qapi/qapi-commands-tpm.h"
#include "monitor/monitor.h"
#include "monitor/hmp.h"
#include "qapi/error.h"
void hmp_info_tpm(Monitor *mon, const QDict *qdict)
{
#ifdef CONFIG_TPM
TPMInfoList *info_list, *info;
Error *err = NULL;
unsigned int c = 0;
TPMPassthroughOptions *tpo;
TPMEmulatorOptions *teo;
info_list = qmp_query_tpm(&err);
if (err) {
monitor_printf(mon, "TPM device not supported\n");
error_free(err);
return;
}
if (info_list) {
monitor_printf(mon, "TPM device:\n");
}
for (info = info_list; info; info = info->next) {
TPMInfo *ti = info->value;
monitor_printf(mon, " tpm%d: model=%s\n",
c, TpmModel_str(ti->model));
monitor_printf(mon, " \\ %s: type=%s",
ti->id, TpmType_str(ti->options->type));
switch (ti->options->type) {
case TPM_TYPE_PASSTHROUGH:
tpo = ti->options->u.passthrough.data;
monitor_printf(mon, "%s%s%s%s",
tpo->path ? ",path=" : "",
tpo->path ?: "",
tpo->cancel_path ? ",cancel-path=" : "",
tpo->cancel_path ?: "");
break;
case TPM_TYPE_EMULATOR:
teo = ti->options->u.emulator.data;
monitor_printf(mon, ",chardev=%s", teo->chardev);
break;
case TPM_TYPE__MAX:
break;
}
monitor_printf(mon, "\n");
c++;
}
qapi_free_TPMInfoList(info_list);
#else
monitor_printf(mon, "TPM device not supported\n");
#endif /* CONFIG_TPM */
}
+240
View File
@@ -0,0 +1,240 @@
/*
* TPM configuration
*
* Copyright (C) 2011-2013 IBM Corporation
*
* Authors:
* Stefan Berger <[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.
*
* Based on net.c
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qapi/qapi-commands-tpm.h"
#include "qapi/qmp/qerror.h"
#include "system/tpm_backend.h"
#include "system/tpm.h"
#include "qemu/config-file.h"
#include "qemu/error-report.h"
#include "qemu/help_option.h"
static QLIST_HEAD(, TPMBackend) tpm_backends =
QLIST_HEAD_INITIALIZER(tpm_backends);
static const TPMBackendClass *
tpm_be_find_by_type(enum TpmType type)
{
ObjectClass *oc;
char *typename = g_strdup_printf("tpm-%s", TpmType_str(type));
oc = object_class_by_name(typename);
g_free(typename);
if (!object_class_dynamic_cast(oc, TYPE_TPM_BACKEND)) {
return NULL;
}
return TPM_BACKEND_CLASS(oc);
}
/*
* Walk the list of available TPM backend drivers and display them on the
* screen.
*/
static void tpm_display_backend_drivers(void)
{
bool got_one = false;
int i;
for (i = 0; i < TPM_TYPE__MAX; i++) {
const TPMBackendClass *bc = tpm_be_find_by_type(i);
if (!bc) {
continue;
}
if (!got_one) {
error_printf("Supported TPM types (choose only one):\n");
got_one = true;
}
error_printf("%12s %s\n", TpmType_str(i), bc->desc);
}
if (!got_one) {
error_printf("No TPM backend types are available\n");
}
}
/*
* Find the TPM with the given Id
*/
TPMBackend *qemu_find_tpm_be(const char *id)
{
TPMBackend *drv;
if (id) {
QLIST_FOREACH(drv, &tpm_backends, list) {
if (!strcmp(drv->id, id)) {
return drv;
}
}
}
return NULL;
}
static int tpm_init_tpmdev(void *dummy, QemuOpts *opts, Error **errp)
{
/*
* Use of error_report() in a function with an Error ** parameter
* is suspicious. It is okay here. The parameter only exists to
* make the function usable with qemu_opts_foreach(). It is not
* actually used.
*/
const char *value;
const char *id;
const TPMBackendClass *be;
TPMBackend *drv;
Error *local_err = NULL;
int i;
if (!QLIST_EMPTY(&tpm_backends)) {
error_report("Only one TPM is allowed.");
return 1;
}
id = qemu_opts_id(opts);
if (id == NULL) {
error_report(QERR_MISSING_PARAMETER, "id");
return 1;
}
value = qemu_opt_get(opts, "type");
if (!value) {
error_report(QERR_MISSING_PARAMETER, "type");
tpm_display_backend_drivers();
return 1;
}
i = qapi_enum_parse(&TpmType_lookup, value, -1, NULL);
be = i >= 0 ? tpm_be_find_by_type(i) : NULL;
if (be == NULL) {
error_report(QERR_INVALID_PARAMETER_VALUE,
"type", "a TPM backend type");
tpm_display_backend_drivers();
return 1;
}
/* validate backend specific opts */
if (!qemu_opts_validate(opts, be->opts, &local_err)) {
error_report_err(local_err);
return 1;
}
drv = be->create(opts);
if (!drv) {
return 1;
}
drv->id = g_strdup(id);
QLIST_INSERT_HEAD(&tpm_backends, drv, list);
return 0;
}
/*
* Walk the list of TPM backend drivers that are in use and call their
* destroy function to have them cleaned up.
*/
void tpm_cleanup(void)
{
TPMBackend *drv, *next;
QLIST_FOREACH_SAFE(drv, &tpm_backends, list, next) {
QLIST_REMOVE(drv, list);
object_unref(OBJECT(drv));
}
}
/*
* Initialize the TPM. Process the tpmdev command line options describing the
* TPM backend.
*/
int tpm_init(void)
{
if (qemu_opts_foreach(qemu_find_opts("tpmdev"),
tpm_init_tpmdev, NULL, NULL)) {
return -1;
}
return 0;
}
/*
* Parse the TPM configuration options.
* To display all available TPM backends the user may use '-tpmdev help'
*/
int tpm_config_parse(QemuOptsList *opts_list, const char *optstr)
{
QemuOpts *opts;
if (is_help_option(optstr)) {
tpm_display_backend_drivers();
exit(EXIT_SUCCESS);
}
opts = qemu_opts_parse_noisily(opts_list, optstr, true);
if (!opts) {
return -1;
}
return 0;
}
/*
* Walk the list of active TPM backends and collect information about them.
*/
TPMInfoList *qmp_query_tpm(Error **errp)
{
TPMBackend *drv;
TPMInfoList *head = NULL, **tail = &head;
QLIST_FOREACH(drv, &tpm_backends, list) {
if (!drv->tpmif) {
continue;
}
QAPI_LIST_APPEND(tail, tpm_backend_query_tpm(drv));
}
return head;
}
TpmTypeList *qmp_query_tpm_types(Error **errp)
{
unsigned int i = 0;
TpmTypeList *head = NULL, **tail = &head;
for (i = 0; i < TPM_TYPE__MAX; i++) {
if (!tpm_be_find_by_type(i)) {
continue;
}
QAPI_LIST_APPEND(tail, i);
}
return head;
}
TpmModelList *qmp_query_tpm_models(Error **errp)
{
TpmModelList *head = NULL, **tail = &head;
GSList *e, *l = object_class_get_list(TYPE_TPM_IF, false);
for (e = l; e; e = e->next) {
TPMIfClass *c = TPM_IF_CLASS(e->data);
QAPI_LIST_APPEND(tail, c->model);
}
g_slist_free(l);
return head;
}
+61
View File
@@ -0,0 +1,61 @@
# See docs/devel/tracing.rst for syntax documentation.
# balloon.c
# Since requests are raised via monitor, not many tracepoints are needed.
balloon_event(void *opaque, unsigned long addr) "opaque %p addr %lu"
# dma-helpers.c
dma_blk_io(void *dbs, void *bs, int64_t offset, bool to_dev) "dbs=%p bs=%p offset=%" PRId64 " to_dev=%d"
dma_aio_cancel(void *dbs) "dbs=%p"
dma_complete(void *dbs, int ret, void *cb) "dbs=%p ret=%d cb=%p"
dma_blk_cb(void *dbs, int ret) "dbs=%p ret=%d"
dma_map_wait(void *dbs) "dbs=%p"
# ioport.c
cpu_in(unsigned int addr, char size, unsigned int val) "addr 0x%x(%c) value %u"
cpu_out(unsigned int addr, char size, unsigned int val) "addr 0x%x(%c) value %u"
# memory.c
memory_region_ops_read(int cpu_index, void *mr, uint64_t addr, uint64_t value, unsigned size, const char *name) "cpu %d mr %p addr 0x%"PRIx64" value 0x%"PRIx64" size %u name '%s'"
memory_region_ops_write(int cpu_index, void *mr, uint64_t addr, uint64_t value, unsigned size, const char *name) "cpu %d mr %p addr 0x%"PRIx64" value 0x%"PRIx64" size %u name '%s'"
memory_region_subpage_read(int cpu_index, void *mr, uint64_t offset, uint64_t value, unsigned size) "cpu %d mr %p offset 0x%"PRIx64" value 0x%"PRIx64" size %u"
memory_region_subpage_write(int cpu_index, void *mr, uint64_t offset, uint64_t value, unsigned size) "cpu %d mr %p offset 0x%"PRIx64" value 0x%"PRIx64" size %u"
memory_region_sync_dirty(const char *mr, const char *listener, int global) "mr '%s' listener '%s' synced (global=%d)"
flatview_new(void *view, void *root) "%p (root %p)"
flatview_destroy(void *view, void *root) "%p (root %p)"
flatview_destroy_rcu(void *view, void *root) "%p (root %p)"
global_dirty_changed(unsigned int bitmask) "bitmask 0x%"PRIx32
memory_region_finalize(const char* name) "mr %s"
# physmem.c
address_space_map(void *as, uint64_t addr, uint64_t len, bool is_write, uint32_t attrs) "as:%p addr 0x%"PRIx64":%"PRIx64" write:%d attrs:0x%x"
find_ram_offset(uint64_t size, uint64_t offset) "size: 0x%" PRIx64 " @ 0x%" PRIx64
find_ram_offset_loop(uint64_t size, uint64_t candidate, uint64_t offset, uint64_t next, uint64_t mingap) "trying size: 0x%" PRIx64 " @ 0x%" PRIx64 ", offset: 0x%" PRIx64" next: 0x%" PRIx64 " mingap: 0x%" PRIx64
ram_block_discard_shared_range(const char *rbname, void *hva, size_t length, bool need_madvise, bool need_fallocate, int ret) "%s@%p + 0x%zx: madvise: %d fallocate: %d ret: %d"
qemu_ram_alloc_shared(const char *name, size_t size, size_t max_size, int fd, void *host) "%s size %zu max_size %zu fd %d host %p"
subpage_register(void *subpage, uint32_t start, uint32_t end, int idx, int eidx, uint16_t section) "subpage %p start 0x%08x end 0x%08x idx 0x%08x eidx 0x%08x section %u"
subpage_init(void *subpage, uint64_t base, uint64_t len) "subpage %p base 0x%08" PRIx64 " len 0x%08" PRIx64
subpage_accepts(void *subpage, char access, unsigned len, uint64_t addr) "subpage %p %c len %u addr 0x%" PRIx64
subpage_read(void *subpage, unsigned len, uint64_t addr) "subpage %p len %u addr 0x%" PRIx64
subpage_write(void *subpage, unsigned len, uint64_t addr, uint64_t value) "subpage %p len %u addr 0x%" PRIx64 " value 0x%" PRIx64
# cpus.c
vm_stop_flush_all(int ret) "ret %d"
# vl.c
vm_state_notify(int running, int reason, const char *reason_str) "running %d reason %d (%s)"
runstate_set(int current_state, const char *current_state_str, int new_state, const char *new_state_str) "current_run_state %d (%s) new_state %d (%s)"
system_wakeup_request(int reason) "reason=%d"
qemu_system_shutdown_request(int reason) "reason=%d"
qemu_system_powerdown_request(void) ""
#dirtylimit.c
dirtylimit_state_initialize(int max_cpus) "dirtylimit state initialize: max cpus %d"
dirtylimit_state_finalize(void)
dirtylimit_throttle_pct(int cpu_index, uint64_t pct, int64_t time_us) "CPU[%d] throttle percent: %" PRIu64 ", throttle adjust time %"PRIi64 " us"
dirtylimit_set_vcpu(int cpu_index, uint64_t quota) "CPU[%d] set dirty page rate limit %"PRIu64
dirtylimit_vcpu_execute(int cpu_index, int64_t sleep_time_us) "CPU[%d] sleep %"PRIi64 " us"
# ram-block-attributes.c
ram_block_attributes_state_change(uint64_t offset, uint64_t size, const char *from, const char *to) "offset 0x%"PRIx64" size 0x%"PRIx64" from '%s' to '%s'"
+1
View File
@@ -0,0 +1 @@
#include "trace/trace-system.h"
+3870
View File
File diff suppressed because it is too large Load Diff
+102
View File
@@ -0,0 +1,102 @@
/*
* 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/error-report.h"
#include "exec/cputlb.h"
#include "exec/target_page.h"
#include "exec/watchpoint.h"
#include "hw/core/cpu.h"
/* Add a watchpoint. */
int cpu_watchpoint_insert(CPUState *cpu, vaddr addr, vaddr len,
int flags, CPUWatchpoint **watchpoint)
{
CPUWatchpoint *wp;
vaddr in_page;
/* forbid ranges which are empty or run off the end of the address space */
if (len == 0 || (addr + len - 1) < addr) {
error_report("tried to set invalid watchpoint at %"
VADDR_PRIx ", len=%" VADDR_PRIu, addr, len);
return -EINVAL;
}
wp = g_malloc(sizeof(*wp));
wp->vaddr = addr;
wp->len = len;
wp->flags = flags;
/* keep all GDB-injected watchpoints in front */
if (flags & BP_GDB) {
QTAILQ_INSERT_HEAD(&cpu->watchpoints, wp, entry);
} else {
QTAILQ_INSERT_TAIL(&cpu->watchpoints, wp, entry);
}
in_page = -(addr | TARGET_PAGE_MASK);
if (len <= in_page) {
tlb_flush_page(cpu, addr);
} else {
tlb_flush(cpu);
}
if (watchpoint) {
*watchpoint = wp;
}
return 0;
}
/* Remove a specific watchpoint. */
int cpu_watchpoint_remove(CPUState *cpu, vaddr addr, vaddr len,
int flags)
{
CPUWatchpoint *wp;
QTAILQ_FOREACH(wp, &cpu->watchpoints, entry) {
if (addr == wp->vaddr && len == wp->len
&& flags == (wp->flags & ~BP_WATCHPOINT_HIT)) {
cpu_watchpoint_remove_by_ref(cpu, wp);
return 0;
}
}
return -ENOENT;
}
/* Remove a specific watchpoint by reference. */
void cpu_watchpoint_remove_by_ref(CPUState *cpu, CPUWatchpoint *watchpoint)
{
QTAILQ_REMOVE(&cpu->watchpoints, watchpoint, entry);
tlb_flush_page(cpu, watchpoint->vaddr);
g_free(watchpoint);
}
/* Remove all matching watchpoints. */
void cpu_watchpoint_remove_all(CPUState *cpu, int mask)
{
CPUWatchpoint *wp, *next;
QTAILQ_FOREACH_SAFE(wp, &cpu->watchpoints, entry, next) {
if (wp->flags & mask) {
cpu_watchpoint_remove_by_ref(cpu, wp);
}
}
}