Import QEMU upstream snapshot d2e570c
Upstream: https://gitlab.com/qemu-project/qemu.git Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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)
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
@@ -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, §ion, 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, §ion, 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, §ion, 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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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'"
|
||||
@@ -0,0 +1 @@
|
||||
#include "trace/trace-system.h"
|
||||
+3870
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user