a87e64519b
Add the cpr-reboot migration mode. Usage: $ qemu-system-$arch -monitor stdio ... QEMU 8.1.50 monitor - type 'help' for more information (qemu) migrate_set_capability x-ignore-shared on (qemu) migrate_set_parameter mode cpr-reboot (qemu) migrate -d file:vm.state (qemu) info status VM status: paused (postmigrate) (qemu) quit $ qemu-system-$arch -monitor stdio -incoming defer ... QEMU 8.1.50 monitor - type 'help' for more information (qemu) migrate_set_capability x-ignore-shared on (qemu) migrate_set_parameter mode cpr-reboot (qemu) migrate_incoming file:vm.state (qemu) info status VM status: running In this mode, the migrate command saves state to a file, allowing one to quit qemu, reboot to an updated kernel, and restart an updated version of qemu. The caller must specify a migration URI that writes to and reads from a file. Unlike normal mode, the use of certain local storage options does not block the migration, but the caller must not modify guest block devices between the quit and restart. To avoid saving guest RAM to the file, the memory backend must be shared, and the @x-ignore-shared migration capability must be set. Guest RAM must be non-volatile across reboot, such as by backing it with a dax device, but this is not enforced. The restarted qemu arguments must match those used to initially start qemu, plus the -incoming option. Signed-off-by: Steve Sistare <steven.sistare@oracle.com> Reviewed-by: Juan Quintela <quintela@redhat.com> Signed-off-by: Juan Quintela <quintela@redhat.com> Message-ID: <1698263069-406971-6-git-send-email-steven.sistare@oracle.com>
1169 lines
31 KiB
C
1169 lines
31 KiB
C
/*
|
|
* qdev property parsing
|
|
* (parts specific for qemu-system-*)
|
|
*
|
|
* This file is based on code from hw/qdev-properties.c from
|
|
* commit 074a86fccd185616469dfcdc0e157f438aebba18,
|
|
* Copyright (c) Gerd Hoffmann <kraxel@redhat.com> and other contributors.
|
|
*
|
|
* 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 "hw/qdev-properties.h"
|
|
#include "hw/qdev-properties-system.h"
|
|
#include "qapi/error.h"
|
|
#include "qapi/visitor.h"
|
|
#include "qapi/qapi-types-block.h"
|
|
#include "qapi/qapi-types-machine.h"
|
|
#include "qapi/qapi-types-migration.h"
|
|
#include "qapi/qmp/qerror.h"
|
|
#include "qemu/ctype.h"
|
|
#include "qemu/cutils.h"
|
|
#include "qemu/units.h"
|
|
#include "qemu/uuid.h"
|
|
#include "qemu/error-report.h"
|
|
#include "qdev-prop-internal.h"
|
|
|
|
#include "audio/audio.h"
|
|
#include "chardev/char-fe.h"
|
|
#include "sysemu/block-backend.h"
|
|
#include "sysemu/blockdev.h"
|
|
#include "net/net.h"
|
|
#include "hw/pci/pci.h"
|
|
#include "hw/pci/pcie.h"
|
|
#include "hw/i386/x86.h"
|
|
#include "util/block-helpers.h"
|
|
|
|
static bool check_prop_still_unset(Object *obj, const char *name,
|
|
const void *old_val, const char *new_val,
|
|
bool allow_override, Error **errp)
|
|
{
|
|
const GlobalProperty *prop = qdev_find_global_prop(obj, name);
|
|
|
|
if (!old_val || (!prop && allow_override)) {
|
|
return true;
|
|
}
|
|
|
|
if (prop) {
|
|
error_setg(errp, "-global %s.%s=... conflicts with %s=%s",
|
|
prop->driver, prop->property, name, new_val);
|
|
} else {
|
|
/* Error message is vague, but a better one would be hard */
|
|
error_setg(errp, "%s=%s conflicts, and override is not implemented",
|
|
name, new_val);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/* --- drive --- */
|
|
|
|
static void get_drive(Object *obj, Visitor *v, const char *name, void *opaque,
|
|
Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
void **ptr = object_field_prop_ptr(obj, prop);
|
|
const char *value;
|
|
char *p;
|
|
|
|
if (*ptr) {
|
|
value = blk_name(*ptr);
|
|
if (!*value) {
|
|
BlockDriverState *bs = blk_bs(*ptr);
|
|
if (bs) {
|
|
value = bdrv_get_node_name(bs);
|
|
}
|
|
}
|
|
} else {
|
|
value = "";
|
|
}
|
|
|
|
p = g_strdup(value);
|
|
visit_type_str(v, name, &p, errp);
|
|
g_free(p);
|
|
}
|
|
|
|
static void set_drive_helper(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, bool iothread, Error **errp)
|
|
{
|
|
DeviceState *dev = DEVICE(obj);
|
|
Property *prop = opaque;
|
|
void **ptr = object_field_prop_ptr(obj, prop);
|
|
char *str;
|
|
BlockBackend *blk;
|
|
bool blk_created = false;
|
|
int ret;
|
|
BlockDriverState *bs;
|
|
AioContext *ctx;
|
|
|
|
if (!visit_type_str(v, name, &str, errp)) {
|
|
return;
|
|
}
|
|
|
|
if (!check_prop_still_unset(obj, name, *ptr, str, true, errp)) {
|
|
return;
|
|
}
|
|
|
|
if (*ptr) {
|
|
/* BlockBackend already exists. So, we want to change attached node */
|
|
blk = *ptr;
|
|
ctx = blk_get_aio_context(blk);
|
|
bs = bdrv_lookup_bs(NULL, str, errp);
|
|
if (!bs) {
|
|
return;
|
|
}
|
|
|
|
if (ctx != bdrv_get_aio_context(bs)) {
|
|
error_setg(errp, "Different aio context is not supported for new "
|
|
"node");
|
|
}
|
|
|
|
aio_context_acquire(ctx);
|
|
blk_replace_bs(blk, bs, errp);
|
|
aio_context_release(ctx);
|
|
return;
|
|
}
|
|
|
|
if (!*str) {
|
|
g_free(str);
|
|
*ptr = NULL;
|
|
return;
|
|
}
|
|
|
|
blk = blk_by_name(str);
|
|
if (!blk) {
|
|
bs = bdrv_lookup_bs(NULL, str, NULL);
|
|
if (bs) {
|
|
/*
|
|
* If the device supports iothreads, it will make sure to move the
|
|
* block node to the right AioContext if necessary (or fail if this
|
|
* isn't possible because of other users). Devices that are not
|
|
* aware of iothreads require their BlockBackends to be in the main
|
|
* AioContext.
|
|
*/
|
|
ctx = bdrv_get_aio_context(bs);
|
|
blk = blk_new(iothread ? ctx : qemu_get_aio_context(),
|
|
0, BLK_PERM_ALL);
|
|
blk_created = true;
|
|
|
|
aio_context_acquire(ctx);
|
|
ret = blk_insert_bs(blk, bs, errp);
|
|
aio_context_release(ctx);
|
|
|
|
if (ret < 0) {
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
if (!blk) {
|
|
error_setg(errp, "Property '%s.%s' can't find value '%s'",
|
|
object_get_typename(OBJECT(dev)), name, str);
|
|
goto fail;
|
|
}
|
|
if (blk_attach_dev(blk, dev) < 0) {
|
|
DriveInfo *dinfo = blk_legacy_dinfo(blk);
|
|
|
|
if (dinfo && dinfo->type != IF_NONE) {
|
|
error_setg(errp, "Drive '%s' is already in use because "
|
|
"it has been automatically connected to another "
|
|
"device (did you need 'if=none' in the drive options?)",
|
|
str);
|
|
} else {
|
|
error_setg(errp, "Drive '%s' is already in use by another device",
|
|
str);
|
|
}
|
|
goto fail;
|
|
}
|
|
|
|
*ptr = blk;
|
|
|
|
fail:
|
|
if (blk_created) {
|
|
/* If we need to keep a reference, blk_attach_dev() took it */
|
|
blk_unref(blk);
|
|
}
|
|
|
|
g_free(str);
|
|
}
|
|
|
|
static void set_drive(Object *obj, Visitor *v, const char *name, void *opaque,
|
|
Error **errp)
|
|
{
|
|
set_drive_helper(obj, v, name, opaque, false, errp);
|
|
}
|
|
|
|
static void set_drive_iothread(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
set_drive_helper(obj, v, name, opaque, true, errp);
|
|
}
|
|
|
|
static void release_drive(Object *obj, const char *name, void *opaque)
|
|
{
|
|
DeviceState *dev = DEVICE(obj);
|
|
Property *prop = opaque;
|
|
BlockBackend **ptr = object_field_prop_ptr(obj, prop);
|
|
|
|
if (*ptr) {
|
|
AioContext *ctx = blk_get_aio_context(*ptr);
|
|
|
|
aio_context_acquire(ctx);
|
|
blockdev_auto_del(*ptr);
|
|
blk_detach_dev(*ptr, dev);
|
|
aio_context_release(ctx);
|
|
}
|
|
}
|
|
|
|
const PropertyInfo qdev_prop_drive = {
|
|
.name = "str",
|
|
.description = "Node name or ID of a block device to use as a backend",
|
|
.realized_set_allowed = true,
|
|
.get = get_drive,
|
|
.set = set_drive,
|
|
.release = release_drive,
|
|
};
|
|
|
|
const PropertyInfo qdev_prop_drive_iothread = {
|
|
.name = "str",
|
|
.description = "Node name or ID of a block device to use as a backend",
|
|
.realized_set_allowed = true,
|
|
.get = get_drive,
|
|
.set = set_drive_iothread,
|
|
.release = release_drive,
|
|
};
|
|
|
|
/* --- character device --- */
|
|
|
|
static void get_chr(Object *obj, Visitor *v, const char *name, void *opaque,
|
|
Error **errp)
|
|
{
|
|
CharBackend *be = object_field_prop_ptr(obj, opaque);
|
|
char *p;
|
|
|
|
p = g_strdup(be->chr && be->chr->label ? be->chr->label : "");
|
|
visit_type_str(v, name, &p, errp);
|
|
g_free(p);
|
|
}
|
|
|
|
static void set_chr(Object *obj, Visitor *v, const char *name, void *opaque,
|
|
Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
CharBackend *be = object_field_prop_ptr(obj, prop);
|
|
Chardev *s;
|
|
char *str;
|
|
|
|
if (!visit_type_str(v, name, &str, errp)) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* TODO Should this really be an error? If no, the old value
|
|
* needs to be released before we store the new one.
|
|
*/
|
|
if (!check_prop_still_unset(obj, name, be->chr, str, false, errp)) {
|
|
return;
|
|
}
|
|
|
|
if (!*str) {
|
|
g_free(str);
|
|
be->chr = NULL;
|
|
return;
|
|
}
|
|
|
|
s = qemu_chr_find(str);
|
|
if (s == NULL) {
|
|
error_setg(errp, "Property '%s.%s' can't find value '%s'",
|
|
object_get_typename(obj), name, str);
|
|
} else if (!qemu_chr_fe_init(be, s, errp)) {
|
|
error_prepend(errp, "Property '%s.%s' can't take value '%s': ",
|
|
object_get_typename(obj), name, str);
|
|
}
|
|
g_free(str);
|
|
}
|
|
|
|
static void release_chr(Object *obj, const char *name, void *opaque)
|
|
{
|
|
Property *prop = opaque;
|
|
CharBackend *be = object_field_prop_ptr(obj, prop);
|
|
|
|
qemu_chr_fe_deinit(be, false);
|
|
}
|
|
|
|
const PropertyInfo qdev_prop_chr = {
|
|
.name = "str",
|
|
.description = "ID of a chardev to use as a backend",
|
|
.get = get_chr,
|
|
.set = set_chr,
|
|
.release = release_chr,
|
|
};
|
|
|
|
/* --- mac address --- */
|
|
|
|
/*
|
|
* accepted syntax versions:
|
|
* 01:02:03:04:05:06
|
|
* 01-02-03-04-05-06
|
|
*/
|
|
static void get_mac(Object *obj, Visitor *v, const char *name, void *opaque,
|
|
Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
MACAddr *mac = object_field_prop_ptr(obj, prop);
|
|
char buffer[2 * 6 + 5 + 1];
|
|
char *p = buffer;
|
|
|
|
snprintf(buffer, sizeof(buffer), "%02x:%02x:%02x:%02x:%02x:%02x",
|
|
mac->a[0], mac->a[1], mac->a[2],
|
|
mac->a[3], mac->a[4], mac->a[5]);
|
|
|
|
visit_type_str(v, name, &p, errp);
|
|
}
|
|
|
|
static void set_mac(Object *obj, Visitor *v, const char *name, void *opaque,
|
|
Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
MACAddr *mac = object_field_prop_ptr(obj, prop);
|
|
int i, pos;
|
|
char *str;
|
|
const char *p;
|
|
|
|
if (!visit_type_str(v, name, &str, errp)) {
|
|
return;
|
|
}
|
|
|
|
for (i = 0, pos = 0; i < 6; i++, pos += 3) {
|
|
long val;
|
|
|
|
if (!qemu_isxdigit(str[pos])) {
|
|
goto inval;
|
|
}
|
|
if (!qemu_isxdigit(str[pos + 1])) {
|
|
goto inval;
|
|
}
|
|
if (i == 5) {
|
|
if (str[pos + 2] != '\0') {
|
|
goto inval;
|
|
}
|
|
} else {
|
|
if (str[pos + 2] != ':' && str[pos + 2] != '-') {
|
|
goto inval;
|
|
}
|
|
}
|
|
if (qemu_strtol(str + pos, &p, 16, &val) < 0 || val > 0xff) {
|
|
goto inval;
|
|
}
|
|
mac->a[i] = val;
|
|
}
|
|
g_free(str);
|
|
return;
|
|
|
|
inval:
|
|
error_set_from_qdev_prop_error(errp, EINVAL, obj, name, str);
|
|
g_free(str);
|
|
}
|
|
|
|
const PropertyInfo qdev_prop_macaddr = {
|
|
.name = "str",
|
|
.description = "Ethernet 6-byte MAC Address, example: 52:54:00:12:34:56",
|
|
.get = get_mac,
|
|
.set = set_mac,
|
|
};
|
|
|
|
void qdev_prop_set_macaddr(DeviceState *dev, const char *name,
|
|
const uint8_t *value)
|
|
{
|
|
char str[2 * 6 + 5 + 1];
|
|
snprintf(str, sizeof(str), "%02x:%02x:%02x:%02x:%02x:%02x",
|
|
value[0], value[1], value[2], value[3], value[4], value[5]);
|
|
|
|
object_property_set_str(OBJECT(dev), name, str, &error_abort);
|
|
}
|
|
|
|
/* --- netdev device --- */
|
|
static void get_netdev(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
NICPeers *peers_ptr = object_field_prop_ptr(obj, prop);
|
|
char *p = g_strdup(peers_ptr->ncs[0] ? peers_ptr->ncs[0]->name : "");
|
|
|
|
visit_type_str(v, name, &p, errp);
|
|
g_free(p);
|
|
}
|
|
|
|
static void set_netdev(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
NICPeers *peers_ptr = object_field_prop_ptr(obj, prop);
|
|
NetClientState **ncs = peers_ptr->ncs;
|
|
NetClientState *peers[MAX_QUEUE_NUM];
|
|
int queues, err = 0, i = 0;
|
|
char *str;
|
|
|
|
if (!visit_type_str(v, name, &str, errp)) {
|
|
return;
|
|
}
|
|
|
|
queues = qemu_find_net_clients_except(str, peers,
|
|
NET_CLIENT_DRIVER_NIC,
|
|
MAX_QUEUE_NUM);
|
|
if (queues == 0) {
|
|
err = -ENOENT;
|
|
goto out;
|
|
}
|
|
|
|
if (queues > MAX_QUEUE_NUM) {
|
|
error_setg(errp, "queues of backend '%s'(%d) exceeds QEMU limitation(%d)",
|
|
str, queues, MAX_QUEUE_NUM);
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < queues; i++) {
|
|
if (peers[i]->peer) {
|
|
err = -EEXIST;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* TODO Should this really be an error? If no, the old value
|
|
* needs to be released before we store the new one.
|
|
*/
|
|
if (!check_prop_still_unset(obj, name, ncs[i], str, false, errp)) {
|
|
goto out;
|
|
}
|
|
|
|
if (peers[i]->info->check_peer_type) {
|
|
if (!peers[i]->info->check_peer_type(peers[i], obj->class, errp)) {
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
ncs[i] = peers[i];
|
|
ncs[i]->queue_index = i;
|
|
}
|
|
|
|
peers_ptr->queues = queues;
|
|
|
|
out:
|
|
error_set_from_qdev_prop_error(errp, err, obj, name, str);
|
|
g_free(str);
|
|
}
|
|
|
|
const PropertyInfo qdev_prop_netdev = {
|
|
.name = "str",
|
|
.description = "ID of a netdev to use as a backend",
|
|
.get = get_netdev,
|
|
.set = set_netdev,
|
|
};
|
|
|
|
|
|
/* --- audiodev --- */
|
|
static void get_audiodev(Object *obj, Visitor *v, const char* name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
QEMUSoundCard *card = object_field_prop_ptr(obj, prop);
|
|
char *p = g_strdup(audio_get_id(card));
|
|
|
|
visit_type_str(v, name, &p, errp);
|
|
g_free(p);
|
|
}
|
|
|
|
static void set_audiodev(Object *obj, Visitor *v, const char* name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
QEMUSoundCard *card = object_field_prop_ptr(obj, prop);
|
|
AudioState *state;
|
|
g_autofree char *str = NULL;
|
|
|
|
if (!visit_type_str(v, name, &str, errp)) {
|
|
return;
|
|
}
|
|
|
|
state = audio_state_by_name(str, errp);
|
|
if (state) {
|
|
card->state = state;
|
|
}
|
|
}
|
|
|
|
const PropertyInfo qdev_prop_audiodev = {
|
|
.name = "str",
|
|
.description = "ID of an audiodev to use as a backend",
|
|
/* release done on shutdown */
|
|
.get = get_audiodev,
|
|
.set = set_audiodev,
|
|
};
|
|
|
|
bool qdev_prop_set_drive_err(DeviceState *dev, const char *name,
|
|
BlockBackend *value, Error **errp)
|
|
{
|
|
const char *ref = "";
|
|
|
|
if (value) {
|
|
ref = blk_name(value);
|
|
if (!*ref) {
|
|
const BlockDriverState *bs = blk_bs(value);
|
|
if (bs) {
|
|
ref = bdrv_get_node_name(bs);
|
|
}
|
|
}
|
|
}
|
|
|
|
return object_property_set_str(OBJECT(dev), name, ref, errp);
|
|
}
|
|
|
|
void qdev_prop_set_drive(DeviceState *dev, const char *name,
|
|
BlockBackend *value)
|
|
{
|
|
qdev_prop_set_drive_err(dev, name, value, &error_abort);
|
|
}
|
|
|
|
void qdev_prop_set_chr(DeviceState *dev, const char *name,
|
|
Chardev *value)
|
|
{
|
|
assert(!value || value->label);
|
|
object_property_set_str(OBJECT(dev), name, value ? value->label : "",
|
|
&error_abort);
|
|
}
|
|
|
|
void qdev_prop_set_netdev(DeviceState *dev, const char *name,
|
|
NetClientState *value)
|
|
{
|
|
assert(!value || value->name);
|
|
object_property_set_str(OBJECT(dev), name, value ? value->name : "",
|
|
&error_abort);
|
|
}
|
|
|
|
void qdev_set_nic_properties(DeviceState *dev, NICInfo *nd)
|
|
{
|
|
qdev_prop_set_macaddr(dev, "mac", nd->macaddr.a);
|
|
if (nd->netdev) {
|
|
qdev_prop_set_netdev(dev, "netdev", nd->netdev);
|
|
}
|
|
if (nd->nvectors != DEV_NVECTORS_UNSPECIFIED &&
|
|
object_property_find(OBJECT(dev), "vectors")) {
|
|
qdev_prop_set_uint32(dev, "vectors", nd->nvectors);
|
|
}
|
|
nd->instantiated = 1;
|
|
}
|
|
|
|
/* --- lost tick policy --- */
|
|
|
|
static void qdev_propinfo_set_losttickpolicy(Object *obj, Visitor *v,
|
|
const char *name, void *opaque,
|
|
Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
int *ptr = object_field_prop_ptr(obj, prop);
|
|
int value;
|
|
|
|
if (!visit_type_enum(v, name, &value, prop->info->enum_table, errp)) {
|
|
return;
|
|
}
|
|
|
|
if (value == LOST_TICK_POLICY_SLEW) {
|
|
MachineState *ms = MACHINE(qdev_get_machine());
|
|
|
|
if (!object_dynamic_cast(OBJECT(ms), TYPE_X86_MACHINE)) {
|
|
error_setg(errp,
|
|
"the 'slew' policy is only available for x86 machines");
|
|
return;
|
|
}
|
|
}
|
|
|
|
*ptr = value;
|
|
}
|
|
|
|
QEMU_BUILD_BUG_ON(sizeof(LostTickPolicy) != sizeof(int));
|
|
|
|
const PropertyInfo qdev_prop_losttickpolicy = {
|
|
.name = "LostTickPolicy",
|
|
.enum_table = &LostTickPolicy_lookup,
|
|
.get = qdev_propinfo_get_enum,
|
|
.set = qdev_propinfo_set_losttickpolicy,
|
|
.set_default_value = qdev_propinfo_set_default_value_enum,
|
|
};
|
|
|
|
/* --- blocksize --- */
|
|
|
|
static void set_blocksize(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
DeviceState *dev = DEVICE(obj);
|
|
Property *prop = opaque;
|
|
uint32_t *ptr = object_field_prop_ptr(obj, prop);
|
|
uint64_t value;
|
|
Error *local_err = NULL;
|
|
|
|
if (!visit_type_size(v, name, &value, errp)) {
|
|
return;
|
|
}
|
|
check_block_size(dev->id ? : "", name, value, &local_err);
|
|
if (local_err) {
|
|
error_propagate(errp, local_err);
|
|
return;
|
|
}
|
|
*ptr = value;
|
|
}
|
|
|
|
const PropertyInfo qdev_prop_blocksize = {
|
|
.name = "size",
|
|
.description = "A power of two between " MIN_BLOCK_SIZE_STR
|
|
" and " MAX_BLOCK_SIZE_STR,
|
|
.get = qdev_propinfo_get_size32,
|
|
.set = set_blocksize,
|
|
.set_default_value = qdev_propinfo_set_default_value_uint,
|
|
};
|
|
|
|
/* --- Block device error handling policy --- */
|
|
|
|
QEMU_BUILD_BUG_ON(sizeof(BlockdevOnError) != sizeof(int));
|
|
|
|
const PropertyInfo qdev_prop_blockdev_on_error = {
|
|
.name = "BlockdevOnError",
|
|
.description = "Error handling policy, "
|
|
"report/ignore/enospc/stop/auto",
|
|
.enum_table = &BlockdevOnError_lookup,
|
|
.get = qdev_propinfo_get_enum,
|
|
.set = qdev_propinfo_set_enum,
|
|
.set_default_value = qdev_propinfo_set_default_value_enum,
|
|
};
|
|
|
|
/* --- BIOS CHS translation */
|
|
|
|
QEMU_BUILD_BUG_ON(sizeof(BiosAtaTranslation) != sizeof(int));
|
|
|
|
const PropertyInfo qdev_prop_bios_chs_trans = {
|
|
.name = "BiosAtaTranslation",
|
|
.description = "Logical CHS translation algorithm, "
|
|
"auto/none/lba/large/rechs",
|
|
.enum_table = &BiosAtaTranslation_lookup,
|
|
.get = qdev_propinfo_get_enum,
|
|
.set = qdev_propinfo_set_enum,
|
|
.set_default_value = qdev_propinfo_set_default_value_enum,
|
|
};
|
|
|
|
/* --- FDC default drive types */
|
|
|
|
const PropertyInfo qdev_prop_fdc_drive_type = {
|
|
.name = "FdcDriveType",
|
|
.description = "FDC drive type, "
|
|
"144/288/120/none/auto",
|
|
.enum_table = &FloppyDriveType_lookup,
|
|
.get = qdev_propinfo_get_enum,
|
|
.set = qdev_propinfo_set_enum,
|
|
.set_default_value = qdev_propinfo_set_default_value_enum,
|
|
};
|
|
|
|
/* --- MultiFDCompression --- */
|
|
|
|
const PropertyInfo qdev_prop_multifd_compression = {
|
|
.name = "MultiFDCompression",
|
|
.description = "multifd_compression values, "
|
|
"none/zlib/zstd",
|
|
.enum_table = &MultiFDCompression_lookup,
|
|
.get = qdev_propinfo_get_enum,
|
|
.set = qdev_propinfo_set_enum,
|
|
.set_default_value = qdev_propinfo_set_default_value_enum,
|
|
};
|
|
|
|
/* --- MigMode --- */
|
|
|
|
QEMU_BUILD_BUG_ON(sizeof(MigMode) != sizeof(int));
|
|
|
|
const PropertyInfo qdev_prop_mig_mode = {
|
|
.name = "MigMode",
|
|
.description = "mig_mode values, "
|
|
"normal,cpr-reboot",
|
|
.enum_table = &MigMode_lookup,
|
|
.get = qdev_propinfo_get_enum,
|
|
.set = qdev_propinfo_set_enum,
|
|
.set_default_value = qdev_propinfo_set_default_value_enum,
|
|
};
|
|
|
|
/* --- Reserved Region --- */
|
|
|
|
/*
|
|
* Accepted syntax:
|
|
* <low address>:<high address>:<type>
|
|
* where low/high addresses are uint64_t in hexadecimal
|
|
* and type is a non-negative decimal integer
|
|
*/
|
|
static void get_reserved_region(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
ReservedRegion *rr = object_field_prop_ptr(obj, prop);
|
|
char buffer[64];
|
|
char *p = buffer;
|
|
int rc;
|
|
|
|
rc = snprintf(buffer, sizeof(buffer), "0x%"PRIx64":0x%"PRIx64":%u",
|
|
rr->low, rr->high, rr->type);
|
|
assert(rc < sizeof(buffer));
|
|
|
|
visit_type_str(v, name, &p, errp);
|
|
}
|
|
|
|
static void set_reserved_region(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
ReservedRegion *rr = object_field_prop_ptr(obj, prop);
|
|
const char *endptr;
|
|
char *str;
|
|
int ret;
|
|
|
|
if (!visit_type_str(v, name, &str, errp)) {
|
|
return;
|
|
}
|
|
|
|
ret = qemu_strtou64(str, &endptr, 16, &rr->low);
|
|
if (ret) {
|
|
error_setg(errp, "start address of '%s'"
|
|
" must be a hexadecimal integer", name);
|
|
goto out;
|
|
}
|
|
if (*endptr != ':') {
|
|
goto separator_error;
|
|
}
|
|
|
|
ret = qemu_strtou64(endptr + 1, &endptr, 16, &rr->high);
|
|
if (ret) {
|
|
error_setg(errp, "end address of '%s'"
|
|
" must be a hexadecimal integer", name);
|
|
goto out;
|
|
}
|
|
if (*endptr != ':') {
|
|
goto separator_error;
|
|
}
|
|
|
|
ret = qemu_strtoui(endptr + 1, &endptr, 10, &rr->type);
|
|
if (ret) {
|
|
error_setg(errp, "type of '%s'"
|
|
" must be a non-negative decimal integer", name);
|
|
}
|
|
goto out;
|
|
|
|
separator_error:
|
|
error_setg(errp, "reserved region fields must be separated with ':'");
|
|
out:
|
|
g_free(str);
|
|
return;
|
|
}
|
|
|
|
const PropertyInfo qdev_prop_reserved_region = {
|
|
.name = "reserved_region",
|
|
.description = "Reserved Region, example: 0xFEE00000:0xFEEFFFFF:0",
|
|
.get = get_reserved_region,
|
|
.set = set_reserved_region,
|
|
};
|
|
|
|
/* --- pci address --- */
|
|
|
|
/*
|
|
* bus-local address, i.e. "$slot" or "$slot.$fn"
|
|
*/
|
|
static void set_pci_devfn(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
int32_t value, *ptr = object_field_prop_ptr(obj, prop);
|
|
unsigned int slot, fn, n;
|
|
char *str;
|
|
|
|
if (!visit_type_str(v, name, &str, NULL)) {
|
|
if (!visit_type_int32(v, name, &value, errp)) {
|
|
return;
|
|
}
|
|
if (value < -1 || value > 255) {
|
|
error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
|
|
name ? name : "null", "a value between -1 and 255");
|
|
return;
|
|
}
|
|
*ptr = value;
|
|
return;
|
|
}
|
|
|
|
if (sscanf(str, "%x.%x%n", &slot, &fn, &n) != 2) {
|
|
fn = 0;
|
|
if (sscanf(str, "%x%n", &slot, &n) != 1) {
|
|
goto invalid;
|
|
}
|
|
}
|
|
if (str[n] != '\0' || fn > 7 || slot > 31) {
|
|
goto invalid;
|
|
}
|
|
*ptr = slot << 3 | fn;
|
|
g_free(str);
|
|
return;
|
|
|
|
invalid:
|
|
error_set_from_qdev_prop_error(errp, EINVAL, obj, name, str);
|
|
g_free(str);
|
|
}
|
|
|
|
static int print_pci_devfn(Object *obj, Property *prop, char *dest,
|
|
size_t len)
|
|
{
|
|
int32_t *ptr = object_field_prop_ptr(obj, prop);
|
|
|
|
if (*ptr == -1) {
|
|
return snprintf(dest, len, "<unset>");
|
|
} else {
|
|
return snprintf(dest, len, "%02x.%x", *ptr >> 3, *ptr & 7);
|
|
}
|
|
}
|
|
|
|
const PropertyInfo qdev_prop_pci_devfn = {
|
|
.name = "int32",
|
|
.description = "Slot and optional function number, example: 06.0 or 06",
|
|
.print = print_pci_devfn,
|
|
.get = qdev_propinfo_get_int32,
|
|
.set = set_pci_devfn,
|
|
.set_default_value = qdev_propinfo_set_default_value_int,
|
|
};
|
|
|
|
/* --- pci host address --- */
|
|
|
|
static void get_pci_host_devaddr(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
PCIHostDeviceAddress *addr = object_field_prop_ptr(obj, prop);
|
|
char buffer[] = "ffff:ff:ff.f";
|
|
char *p = buffer;
|
|
int rc = 0;
|
|
|
|
/*
|
|
* Catch "invalid" device reference from vfio-pci and allow the
|
|
* default buffer representing the non-existent device to be used.
|
|
*/
|
|
if (~addr->domain || ~addr->bus || ~addr->slot || ~addr->function) {
|
|
rc = snprintf(buffer, sizeof(buffer), "%04x:%02x:%02x.%0d",
|
|
addr->domain, addr->bus, addr->slot, addr->function);
|
|
assert(rc == sizeof(buffer) - 1);
|
|
}
|
|
|
|
visit_type_str(v, name, &p, errp);
|
|
}
|
|
|
|
/*
|
|
* Parse [<domain>:]<bus>:<slot>.<func>
|
|
* if <domain> is not supplied, it's assumed to be 0.
|
|
*/
|
|
static void set_pci_host_devaddr(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
PCIHostDeviceAddress *addr = object_field_prop_ptr(obj, prop);
|
|
char *str, *p;
|
|
char *e;
|
|
unsigned long val;
|
|
unsigned long dom = 0, bus = 0;
|
|
unsigned int slot = 0, func = 0;
|
|
|
|
if (!visit_type_str(v, name, &str, errp)) {
|
|
return;
|
|
}
|
|
|
|
p = str;
|
|
val = strtoul(p, &e, 16);
|
|
if (e == p || *e != ':') {
|
|
goto inval;
|
|
}
|
|
bus = val;
|
|
|
|
p = e + 1;
|
|
val = strtoul(p, &e, 16);
|
|
if (e == p) {
|
|
goto inval;
|
|
}
|
|
if (*e == ':') {
|
|
dom = bus;
|
|
bus = val;
|
|
p = e + 1;
|
|
val = strtoul(p, &e, 16);
|
|
if (e == p) {
|
|
goto inval;
|
|
}
|
|
}
|
|
slot = val;
|
|
|
|
if (*e != '.') {
|
|
goto inval;
|
|
}
|
|
p = e + 1;
|
|
val = strtoul(p, &e, 10);
|
|
if (e == p) {
|
|
goto inval;
|
|
}
|
|
func = val;
|
|
|
|
if (dom > 0xffff || bus > 0xff || slot > 0x1f || func > 7) {
|
|
goto inval;
|
|
}
|
|
|
|
if (*e) {
|
|
goto inval;
|
|
}
|
|
|
|
addr->domain = dom;
|
|
addr->bus = bus;
|
|
addr->slot = slot;
|
|
addr->function = func;
|
|
|
|
g_free(str);
|
|
return;
|
|
|
|
inval:
|
|
error_set_from_qdev_prop_error(errp, EINVAL, obj, name, str);
|
|
g_free(str);
|
|
}
|
|
|
|
const PropertyInfo qdev_prop_pci_host_devaddr = {
|
|
.name = "str",
|
|
.description = "Address (bus/device/function) of "
|
|
"the host device, example: 04:10.0",
|
|
.get = get_pci_host_devaddr,
|
|
.set = set_pci_host_devaddr,
|
|
};
|
|
|
|
/* --- OffAutoPCIBAR off/auto/bar0/bar1/bar2/bar3/bar4/bar5 --- */
|
|
|
|
const PropertyInfo qdev_prop_off_auto_pcibar = {
|
|
.name = "OffAutoPCIBAR",
|
|
.description = "off/auto/bar0/bar1/bar2/bar3/bar4/bar5",
|
|
.enum_table = &OffAutoPCIBAR_lookup,
|
|
.get = qdev_propinfo_get_enum,
|
|
.set = qdev_propinfo_set_enum,
|
|
.set_default_value = qdev_propinfo_set_default_value_enum,
|
|
};
|
|
|
|
/* --- PCIELinkSpeed 2_5/5/8/16 -- */
|
|
|
|
static void get_prop_pcielinkspeed(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
PCIExpLinkSpeed *p = object_field_prop_ptr(obj, prop);
|
|
int speed;
|
|
|
|
switch (*p) {
|
|
case QEMU_PCI_EXP_LNK_2_5GT:
|
|
speed = PCIE_LINK_SPEED_2_5;
|
|
break;
|
|
case QEMU_PCI_EXP_LNK_5GT:
|
|
speed = PCIE_LINK_SPEED_5;
|
|
break;
|
|
case QEMU_PCI_EXP_LNK_8GT:
|
|
speed = PCIE_LINK_SPEED_8;
|
|
break;
|
|
case QEMU_PCI_EXP_LNK_16GT:
|
|
speed = PCIE_LINK_SPEED_16;
|
|
break;
|
|
default:
|
|
/* Unreachable */
|
|
abort();
|
|
}
|
|
|
|
visit_type_enum(v, name, &speed, prop->info->enum_table, errp);
|
|
}
|
|
|
|
static void set_prop_pcielinkspeed(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
PCIExpLinkSpeed *p = object_field_prop_ptr(obj, prop);
|
|
int speed;
|
|
|
|
if (!visit_type_enum(v, name, &speed, prop->info->enum_table,
|
|
errp)) {
|
|
return;
|
|
}
|
|
|
|
switch (speed) {
|
|
case PCIE_LINK_SPEED_2_5:
|
|
*p = QEMU_PCI_EXP_LNK_2_5GT;
|
|
break;
|
|
case PCIE_LINK_SPEED_5:
|
|
*p = QEMU_PCI_EXP_LNK_5GT;
|
|
break;
|
|
case PCIE_LINK_SPEED_8:
|
|
*p = QEMU_PCI_EXP_LNK_8GT;
|
|
break;
|
|
case PCIE_LINK_SPEED_16:
|
|
*p = QEMU_PCI_EXP_LNK_16GT;
|
|
break;
|
|
default:
|
|
/* Unreachable */
|
|
abort();
|
|
}
|
|
}
|
|
|
|
const PropertyInfo qdev_prop_pcie_link_speed = {
|
|
.name = "PCIELinkSpeed",
|
|
.description = "2_5/5/8/16",
|
|
.enum_table = &PCIELinkSpeed_lookup,
|
|
.get = get_prop_pcielinkspeed,
|
|
.set = set_prop_pcielinkspeed,
|
|
.set_default_value = qdev_propinfo_set_default_value_enum,
|
|
};
|
|
|
|
/* --- PCIELinkWidth 1/2/4/8/12/16/32 -- */
|
|
|
|
static void get_prop_pcielinkwidth(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
PCIExpLinkWidth *p = object_field_prop_ptr(obj, prop);
|
|
int width;
|
|
|
|
switch (*p) {
|
|
case QEMU_PCI_EXP_LNK_X1:
|
|
width = PCIE_LINK_WIDTH_1;
|
|
break;
|
|
case QEMU_PCI_EXP_LNK_X2:
|
|
width = PCIE_LINK_WIDTH_2;
|
|
break;
|
|
case QEMU_PCI_EXP_LNK_X4:
|
|
width = PCIE_LINK_WIDTH_4;
|
|
break;
|
|
case QEMU_PCI_EXP_LNK_X8:
|
|
width = PCIE_LINK_WIDTH_8;
|
|
break;
|
|
case QEMU_PCI_EXP_LNK_X12:
|
|
width = PCIE_LINK_WIDTH_12;
|
|
break;
|
|
case QEMU_PCI_EXP_LNK_X16:
|
|
width = PCIE_LINK_WIDTH_16;
|
|
break;
|
|
case QEMU_PCI_EXP_LNK_X32:
|
|
width = PCIE_LINK_WIDTH_32;
|
|
break;
|
|
default:
|
|
/* Unreachable */
|
|
abort();
|
|
}
|
|
|
|
visit_type_enum(v, name, &width, prop->info->enum_table, errp);
|
|
}
|
|
|
|
static void set_prop_pcielinkwidth(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
PCIExpLinkWidth *p = object_field_prop_ptr(obj, prop);
|
|
int width;
|
|
|
|
if (!visit_type_enum(v, name, &width, prop->info->enum_table,
|
|
errp)) {
|
|
return;
|
|
}
|
|
|
|
switch (width) {
|
|
case PCIE_LINK_WIDTH_1:
|
|
*p = QEMU_PCI_EXP_LNK_X1;
|
|
break;
|
|
case PCIE_LINK_WIDTH_2:
|
|
*p = QEMU_PCI_EXP_LNK_X2;
|
|
break;
|
|
case PCIE_LINK_WIDTH_4:
|
|
*p = QEMU_PCI_EXP_LNK_X4;
|
|
break;
|
|
case PCIE_LINK_WIDTH_8:
|
|
*p = QEMU_PCI_EXP_LNK_X8;
|
|
break;
|
|
case PCIE_LINK_WIDTH_12:
|
|
*p = QEMU_PCI_EXP_LNK_X12;
|
|
break;
|
|
case PCIE_LINK_WIDTH_16:
|
|
*p = QEMU_PCI_EXP_LNK_X16;
|
|
break;
|
|
case PCIE_LINK_WIDTH_32:
|
|
*p = QEMU_PCI_EXP_LNK_X32;
|
|
break;
|
|
default:
|
|
/* Unreachable */
|
|
abort();
|
|
}
|
|
}
|
|
|
|
const PropertyInfo qdev_prop_pcie_link_width = {
|
|
.name = "PCIELinkWidth",
|
|
.description = "1/2/4/8/12/16/32",
|
|
.enum_table = &PCIELinkWidth_lookup,
|
|
.get = get_prop_pcielinkwidth,
|
|
.set = set_prop_pcielinkwidth,
|
|
.set_default_value = qdev_propinfo_set_default_value_enum,
|
|
};
|
|
|
|
/* --- UUID --- */
|
|
|
|
static void get_uuid(Object *obj, Visitor *v, const char *name, void *opaque,
|
|
Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
QemuUUID *uuid = object_field_prop_ptr(obj, prop);
|
|
char buffer[UUID_FMT_LEN + 1];
|
|
char *p = buffer;
|
|
|
|
qemu_uuid_unparse(uuid, buffer);
|
|
|
|
visit_type_str(v, name, &p, errp);
|
|
}
|
|
|
|
#define UUID_VALUE_AUTO "auto"
|
|
|
|
static void set_uuid(Object *obj, Visitor *v, const char *name, void *opaque,
|
|
Error **errp)
|
|
{
|
|
Property *prop = opaque;
|
|
QemuUUID *uuid = object_field_prop_ptr(obj, prop);
|
|
char *str;
|
|
|
|
if (!visit_type_str(v, name, &str, errp)) {
|
|
return;
|
|
}
|
|
|
|
if (!strcmp(str, UUID_VALUE_AUTO)) {
|
|
qemu_uuid_generate(uuid);
|
|
} else if (qemu_uuid_parse(str, uuid) < 0) {
|
|
error_set_from_qdev_prop_error(errp, EINVAL, obj, name, str);
|
|
}
|
|
g_free(str);
|
|
}
|
|
|
|
static void set_default_uuid_auto(ObjectProperty *op, const Property *prop)
|
|
{
|
|
object_property_set_default_str(op, UUID_VALUE_AUTO);
|
|
}
|
|
|
|
const PropertyInfo qdev_prop_uuid = {
|
|
.name = "str",
|
|
.description = "UUID (aka GUID) or \"" UUID_VALUE_AUTO
|
|
"\" for random value (default)",
|
|
.get = get_uuid,
|
|
.set = set_uuid,
|
|
.set_default_value = set_default_uuid_auto,
|
|
};
|
|
|
|
/* --- s390 cpu entitlement policy --- */
|
|
|
|
QEMU_BUILD_BUG_ON(sizeof(CpuS390Entitlement) != sizeof(int));
|
|
|
|
const PropertyInfo qdev_prop_cpus390entitlement = {
|
|
.name = "CpuS390Entitlement",
|
|
.description = "low/medium (default)/high",
|
|
.enum_table = &CpuS390Entitlement_lookup,
|
|
.get = qdev_propinfo_get_enum,
|
|
.set = qdev_propinfo_set_enum,
|
|
.set_default_value = qdev_propinfo_set_default_value_enum,
|
|
};
|