2019-01-08 17:48:47 +03:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2018 Citrix Systems Inc.
|
|
|
|
*
|
|
|
|
* 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"
|
2019-01-08 17:48:50 +03:00
|
|
|
#include "qemu/main-loop.h"
|
2019-05-23 17:35:07 +03:00
|
|
|
#include "qemu/module.h"
|
2019-01-08 17:48:50 +03:00
|
|
|
#include "qemu/uuid.h"
|
2019-08-12 08:23:51 +03:00
|
|
|
#include "hw/qdev-properties.h"
|
2019-01-08 17:48:47 +03:00
|
|
|
#include "hw/sysbus.h"
|
2019-01-08 17:48:49 +03:00
|
|
|
#include "hw/xen/xen.h"
|
2019-01-08 17:49:00 +03:00
|
|
|
#include "hw/xen/xen-backend.h"
|
2019-01-08 17:48:47 +03:00
|
|
|
#include "hw/xen/xen-bus.h"
|
2019-01-08 17:48:49 +03:00
|
|
|
#include "hw/xen/xen-bus-helper.h"
|
|
|
|
#include "monitor/monitor.h"
|
2019-01-08 17:48:47 +03:00
|
|
|
#include "qapi/error.h"
|
2019-01-08 17:49:00 +03:00
|
|
|
#include "qapi/qmp/qdict.h"
|
2019-01-08 17:48:49 +03:00
|
|
|
#include "sysemu/sysemu.h"
|
2023-10-17 19:53:58 +03:00
|
|
|
#include "net/net.h"
|
2019-01-08 17:48:47 +03:00
|
|
|
#include "trace.h"
|
|
|
|
|
2019-01-08 17:48:49 +03:00
|
|
|
static char *xen_device_get_backend_path(XenDevice *xendev)
|
|
|
|
{
|
|
|
|
XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev)));
|
|
|
|
XenDeviceClass *xendev_class = XEN_DEVICE_GET_CLASS(xendev);
|
|
|
|
const char *type = object_get_typename(OBJECT(xendev));
|
|
|
|
const char *backend = xendev_class->backend;
|
|
|
|
|
|
|
|
if (!backend) {
|
|
|
|
backend = type;
|
|
|
|
}
|
|
|
|
|
|
|
|
return g_strdup_printf("/local/domain/%u/backend/%s/%u/%s",
|
|
|
|
xenbus->backend_id, backend, xendev->frontend_id,
|
|
|
|
xendev->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *xen_device_get_frontend_path(XenDevice *xendev)
|
|
|
|
{
|
|
|
|
XenDeviceClass *xendev_class = XEN_DEVICE_GET_CLASS(xendev);
|
|
|
|
const char *type = object_get_typename(OBJECT(xendev));
|
|
|
|
const char *device = xendev_class->device;
|
|
|
|
|
|
|
|
if (!device) {
|
|
|
|
device = type;
|
|
|
|
}
|
|
|
|
|
|
|
|
return g_strdup_printf("/local/domain/%u/device/%s/%s",
|
|
|
|
xendev->frontend_id, device, xendev->name);
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:59 +03:00
|
|
|
static void xen_device_unplug(XenDevice *xendev, Error **errp)
|
|
|
|
{
|
2020-07-07 19:50:37 +03:00
|
|
|
ERRP_GUARD();
|
2019-01-08 17:48:59 +03:00
|
|
|
XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev)));
|
|
|
|
const char *type = object_get_typename(OBJECT(xendev));
|
|
|
|
xs_transaction_t tid;
|
|
|
|
|
|
|
|
trace_xen_device_unplug(type, xendev->name);
|
|
|
|
|
|
|
|
/* Mimic the way the Xen toolstack does an unplug */
|
|
|
|
again:
|
2023-01-02 14:05:16 +03:00
|
|
|
tid = qemu_xen_xs_transaction_start(xenbus->xsh);
|
2019-01-08 17:48:59 +03:00
|
|
|
if (tid == XBT_NULL) {
|
|
|
|
error_setg_errno(errp, errno, "failed xs_transaction_start");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
xs_node_printf(xenbus->xsh, tid, xendev->backend_path, "online",
|
2020-07-07 19:50:37 +03:00
|
|
|
errp, "%u", 0);
|
|
|
|
if (*errp) {
|
2019-01-08 17:48:59 +03:00
|
|
|
goto abort;
|
|
|
|
}
|
|
|
|
|
|
|
|
xs_node_printf(xenbus->xsh, tid, xendev->backend_path, "state",
|
2020-07-07 19:50:37 +03:00
|
|
|
errp, "%u", XenbusStateClosing);
|
|
|
|
if (*errp) {
|
2019-01-08 17:48:59 +03:00
|
|
|
goto abort;
|
|
|
|
}
|
|
|
|
|
2023-01-02 14:05:16 +03:00
|
|
|
if (!qemu_xen_xs_transaction_end(xenbus->xsh, tid, false)) {
|
2019-01-08 17:48:59 +03:00
|
|
|
if (errno == EAGAIN) {
|
|
|
|
goto again;
|
|
|
|
}
|
|
|
|
|
|
|
|
error_setg_errno(errp, errno, "failed xs_transaction_end");
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
abort:
|
|
|
|
/*
|
|
|
|
* We only abort if there is already a failure so ignore any error
|
|
|
|
* from ending the transaction.
|
|
|
|
*/
|
2023-01-02 14:05:16 +03:00
|
|
|
qemu_xen_xs_transaction_end(xenbus->xsh, tid, true);
|
2019-01-08 17:48:59 +03:00
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:49 +03:00
|
|
|
static void xen_bus_print_dev(Monitor *mon, DeviceState *dev, int indent)
|
|
|
|
{
|
|
|
|
XenDevice *xendev = XEN_DEVICE(dev);
|
|
|
|
|
|
|
|
monitor_printf(mon, "%*sname = '%s' frontend_id = %u\n",
|
|
|
|
indent, "", xendev->name, xendev->frontend_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *xen_bus_get_dev_path(DeviceState *dev)
|
|
|
|
{
|
|
|
|
return xen_device_get_backend_path(XEN_DEVICE(dev));
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:49:00 +03:00
|
|
|
static void xen_bus_backend_create(XenBus *xenbus, const char *type,
|
|
|
|
const char *name, char *path,
|
|
|
|
Error **errp)
|
|
|
|
{
|
2020-07-07 19:50:37 +03:00
|
|
|
ERRP_GUARD();
|
2019-01-08 17:49:00 +03:00
|
|
|
xs_transaction_t tid;
|
|
|
|
char **key;
|
|
|
|
QDict *opts;
|
|
|
|
unsigned int i, n;
|
|
|
|
|
|
|
|
trace_xen_bus_backend_create(type, path);
|
|
|
|
|
|
|
|
again:
|
2023-01-02 14:05:16 +03:00
|
|
|
tid = qemu_xen_xs_transaction_start(xenbus->xsh);
|
2019-01-08 17:49:00 +03:00
|
|
|
if (tid == XBT_NULL) {
|
|
|
|
error_setg(errp, "failed xs_transaction_start");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-01-02 14:05:16 +03:00
|
|
|
key = qemu_xen_xs_directory(xenbus->xsh, tid, path, &n);
|
2019-01-08 17:49:00 +03:00
|
|
|
if (!key) {
|
2023-01-02 14:05:16 +03:00
|
|
|
if (!qemu_xen_xs_transaction_end(xenbus->xsh, tid, true)) {
|
2019-01-08 17:49:00 +03:00
|
|
|
error_setg_errno(errp, errno, "failed xs_transaction_end");
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
opts = qdict_new();
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
char *val;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Assume anything found in the xenstore backend area, other than
|
|
|
|
* the keys created for a generic XenDevice, are parameters
|
|
|
|
* to be used to configure the backend.
|
|
|
|
*/
|
|
|
|
if (!strcmp(key[i], "state") ||
|
|
|
|
!strcmp(key[i], "online") ||
|
|
|
|
!strcmp(key[i], "frontend") ||
|
|
|
|
!strcmp(key[i], "frontend-id") ||
|
|
|
|
!strcmp(key[i], "hotplug-status"))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (xs_node_scanf(xenbus->xsh, tid, path, key[i], NULL, "%ms",
|
|
|
|
&val) == 1) {
|
|
|
|
qdict_put_str(opts, key[i], val);
|
|
|
|
free(val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
free(key);
|
|
|
|
|
2023-01-02 14:05:16 +03:00
|
|
|
if (!qemu_xen_xs_transaction_end(xenbus->xsh, tid, false)) {
|
2019-01-08 17:49:00 +03:00
|
|
|
qobject_unref(opts);
|
|
|
|
|
|
|
|
if (errno == EAGAIN) {
|
|
|
|
goto again;
|
|
|
|
}
|
|
|
|
|
|
|
|
error_setg_errno(errp, errno, "failed xs_transaction_end");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-07 19:50:37 +03:00
|
|
|
xen_backend_device_create(xenbus, type, name, opts, errp);
|
2019-01-08 17:49:00 +03:00
|
|
|
qobject_unref(opts);
|
|
|
|
|
2020-07-07 19:50:37 +03:00
|
|
|
if (*errp) {
|
|
|
|
error_prepend(errp, "failed to create '%s' device '%s': ", type, name);
|
2019-01-08 17:49:00 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xen_bus_type_enumerate(XenBus *xenbus, const char *type)
|
|
|
|
{
|
|
|
|
char *domain_path = g_strdup_printf("backend/%s/%u", type, xen_domid);
|
|
|
|
char **backend;
|
|
|
|
unsigned int i, n;
|
|
|
|
|
|
|
|
trace_xen_bus_type_enumerate(type);
|
|
|
|
|
2023-01-02 14:05:16 +03:00
|
|
|
backend = qemu_xen_xs_directory(xenbus->xsh, XBT_NULL, domain_path, &n);
|
2019-01-08 17:49:00 +03:00
|
|
|
if (!backend) {
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
char *backend_path = g_strdup_printf("%s/%s", domain_path,
|
|
|
|
backend[i]);
|
2019-09-13 11:21:58 +03:00
|
|
|
enum xenbus_state state;
|
|
|
|
unsigned int online;
|
2019-01-08 17:49:00 +03:00
|
|
|
|
|
|
|
if (xs_node_scanf(xenbus->xsh, XBT_NULL, backend_path, "state",
|
2019-09-13 11:21:58 +03:00
|
|
|
NULL, "%u", &state) != 1)
|
|
|
|
state = XenbusStateUnknown;
|
2019-01-08 17:49:00 +03:00
|
|
|
|
2019-09-13 11:21:58 +03:00
|
|
|
if (xs_node_scanf(xenbus->xsh, XBT_NULL, backend_path, "online",
|
|
|
|
NULL, "%u", &online) != 1)
|
|
|
|
online = 0;
|
|
|
|
|
2023-10-16 12:28:17 +03:00
|
|
|
if (online && state == XenbusStateInitialising &&
|
|
|
|
!xen_backend_exists(type, backend[i])) {
|
2019-01-08 17:49:00 +03:00
|
|
|
Error *local_err = NULL;
|
|
|
|
|
|
|
|
xen_bus_backend_create(xenbus, type, backend[i], backend_path,
|
|
|
|
&local_err);
|
|
|
|
if (local_err) {
|
|
|
|
error_report_err(local_err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
g_free(backend_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(backend);
|
|
|
|
|
|
|
|
out:
|
|
|
|
g_free(domain_path);
|
|
|
|
}
|
|
|
|
|
2019-09-13 11:21:58 +03:00
|
|
|
static void xen_bus_enumerate(XenBus *xenbus)
|
2019-01-08 17:49:00 +03:00
|
|
|
{
|
|
|
|
char **type;
|
|
|
|
unsigned int i, n;
|
|
|
|
|
|
|
|
trace_xen_bus_enumerate();
|
|
|
|
|
2023-01-02 14:05:16 +03:00
|
|
|
type = qemu_xen_xs_directory(xenbus->xsh, XBT_NULL, "backend", &n);
|
2019-01-08 17:49:00 +03:00
|
|
|
if (!type) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
xen_bus_type_enumerate(xenbus, type[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(type);
|
|
|
|
}
|
|
|
|
|
2019-09-13 11:21:58 +03:00
|
|
|
static void xen_bus_device_cleanup(XenDevice *xendev)
|
|
|
|
{
|
|
|
|
const char *type = object_get_typename(OBJECT(xendev));
|
|
|
|
Error *local_err = NULL;
|
|
|
|
|
|
|
|
trace_xen_bus_device_cleanup(type, xendev->name);
|
|
|
|
|
|
|
|
g_assert(!xendev->backend_online);
|
|
|
|
|
|
|
|
if (!xen_backend_try_device_destroy(xendev, &local_err)) {
|
|
|
|
object_unparent(OBJECT(xendev));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (local_err) {
|
|
|
|
error_report_err(local_err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xen_bus_cleanup(XenBus *xenbus)
|
|
|
|
{
|
|
|
|
XenDevice *xendev, *next;
|
|
|
|
|
|
|
|
trace_xen_bus_cleanup();
|
|
|
|
|
|
|
|
QLIST_FOREACH_SAFE(xendev, &xenbus->inactive_devices, list, next) {
|
|
|
|
g_assert(xendev->inactive);
|
|
|
|
QLIST_REMOVE(xendev, list);
|
|
|
|
xen_bus_device_cleanup(xendev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-02 14:05:16 +03:00
|
|
|
static void xen_bus_backend_changed(void *opaque, const char *path)
|
2019-09-13 11:21:58 +03:00
|
|
|
{
|
|
|
|
XenBus *xenbus = opaque;
|
|
|
|
|
|
|
|
xen_bus_enumerate(xenbus);
|
|
|
|
xen_bus_cleanup(xenbus);
|
|
|
|
}
|
|
|
|
|
qdev: Unrealize must not fail
Devices may have component devices and buses.
Device realization may fail. Realization is recursive: a device's
realize() method realizes its components, and device_set_realized()
realizes its buses (which should in turn realize the devices on that
bus, except bus_set_realized() doesn't implement that, yet).
When realization of a component or bus fails, we need to roll back:
unrealize everything we realized so far. If any of these unrealizes
failed, the device would be left in an inconsistent state. Must not
happen.
device_set_realized() lets it happen: it ignores errors in the roll
back code starting at label child_realize_fail.
Since realization is recursive, unrealization must be recursive, too.
But how could a partly failed unrealize be rolled back? We'd have to
re-realize, which can fail. This design is fundamentally broken.
device_set_realized() does not roll back at all. Instead, it keeps
unrealizing, ignoring further errors.
It can screw up even for a device with no buses: if the lone
dc->unrealize() fails, it still unregisters vmstate, and calls
listeners' unrealize() callback.
bus_set_realized() does not roll back either. Instead, it stops
unrealizing.
Fortunately, no unrealize method can fail, as we'll see below.
To fix the design error, drop parameter @errp from all the unrealize
methods.
Any unrealize method that uses @errp now needs an update. This leads
us to unrealize() methods that can fail. Merely passing it to another
unrealize method cannot cause failure, though. Here are the ones that
do other things with @errp:
* virtio_serial_device_unrealize()
Fails when qbus_set_hotplug_handler() fails, but still does all the
other work. On failure, the device would stay realized with its
resources completely gone. Oops. Can't happen, because
qbus_set_hotplug_handler() can't actually fail here. Pass
&error_abort to qbus_set_hotplug_handler() instead.
* hw/ppc/spapr_drc.c's unrealize()
Fails when object_property_del() fails, but all the other work is
already done. On failure, the device would stay realized with its
vmstate registration gone. Oops. Can't happen, because
object_property_del() can't actually fail here. Pass &error_abort
to object_property_del() instead.
* spapr_phb_unrealize()
Fails and bails out when remove_drcs() fails, but other work is
already done. On failure, the device would stay realized with some
of its resources gone. Oops. remove_drcs() fails only when
chassis_from_bus()'s object_property_get_uint() fails, and it can't
here. Pass &error_abort to remove_drcs() instead.
Therefore, no unrealize method can fail before this patch.
device_set_realized()'s recursive unrealization via bus uses
object_property_set_bool(). Can't drop @errp there, so pass
&error_abort.
We similarly unrealize with object_property_set_bool() elsewhere,
always ignoring errors. Pass &error_abort instead.
Several unrealize methods no longer handle errors from other unrealize
methods: virtio_9p_device_unrealize(),
virtio_input_device_unrealize(), scsi_qdev_unrealize(), ...
Much of the deleted error handling looks wrong anyway.
One unrealize methods no longer ignore such errors:
usb_ehci_pci_exit().
Several realize methods no longer ignore errors when rolling back:
v9fs_device_realize_common(), pci_qdev_unrealize(),
spapr_phb_realize(), usb_qdev_realize(), vfio_ccw_realize(),
virtio_device_realize().
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
Message-Id: <20200505152926.18877-17-armbru@redhat.com>
2020-05-05 18:29:24 +03:00
|
|
|
static void xen_bus_unrealize(BusState *bus)
|
2019-01-08 17:48:47 +03:00
|
|
|
{
|
2019-01-08 17:48:49 +03:00
|
|
|
XenBus *xenbus = XEN_BUS(bus);
|
|
|
|
|
2019-01-08 17:48:47 +03:00
|
|
|
trace_xen_bus_unrealize();
|
2019-01-08 17:48:49 +03:00
|
|
|
|
2019-01-08 17:49:00 +03:00
|
|
|
if (xenbus->backend_watch) {
|
2020-10-01 11:15:00 +03:00
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < xenbus->backend_types; i++) {
|
|
|
|
if (xenbus->backend_watch[i]) {
|
2023-01-02 14:05:16 +03:00
|
|
|
xs_node_unwatch(xenbus->xsh, xenbus->backend_watch[i]);
|
2020-10-01 11:15:00 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
g_free(xenbus->backend_watch);
|
2019-01-08 17:49:00 +03:00
|
|
|
xenbus->backend_watch = NULL;
|
|
|
|
}
|
|
|
|
|
2019-09-13 11:21:56 +03:00
|
|
|
if (xenbus->xsh) {
|
2023-01-02 14:05:16 +03:00
|
|
|
qemu_xen_xs_close(xenbus->xsh);
|
2019-01-08 17:48:50 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:47 +03:00
|
|
|
static void xen_bus_realize(BusState *bus, Error **errp)
|
|
|
|
{
|
2020-10-01 11:15:00 +03:00
|
|
|
char *key = g_strdup_printf("%u", xen_domid);
|
2019-01-08 17:48:49 +03:00
|
|
|
XenBus *xenbus = XEN_BUS(bus);
|
|
|
|
unsigned int domid;
|
2020-10-01 11:15:00 +03:00
|
|
|
const char **type;
|
|
|
|
unsigned int i;
|
2019-01-08 17:49:00 +03:00
|
|
|
Error *local_err = NULL;
|
2019-01-08 17:48:49 +03:00
|
|
|
|
2019-01-08 17:48:47 +03:00
|
|
|
trace_xen_bus_realize();
|
2019-01-08 17:48:49 +03:00
|
|
|
|
2023-01-02 14:05:16 +03:00
|
|
|
xenbus->xsh = qemu_xen_xs_open();
|
2019-01-08 17:48:49 +03:00
|
|
|
if (!xenbus->xsh) {
|
|
|
|
error_setg_errno(errp, errno, "failed xs_open");
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xs_node_scanf(xenbus->xsh, XBT_NULL, "", /* domain root node */
|
|
|
|
"domid", NULL, "%u", &domid) == 1) {
|
|
|
|
xenbus->backend_id = domid;
|
|
|
|
} else {
|
|
|
|
xenbus->backend_id = 0; /* Assume lack of node means dom0 */
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:49:00 +03:00
|
|
|
module_call_init(MODULE_INIT_XEN_BACKEND);
|
|
|
|
|
2020-10-01 11:15:00 +03:00
|
|
|
type = xen_backend_get_types(&xenbus->backend_types);
|
2023-01-02 14:05:16 +03:00
|
|
|
xenbus->backend_watch = g_new(struct qemu_xs_watch *,
|
|
|
|
xenbus->backend_types);
|
2020-10-01 11:15:00 +03:00
|
|
|
|
|
|
|
for (i = 0; i < xenbus->backend_types; i++) {
|
|
|
|
char *node = g_strdup_printf("backend/%s", type[i]);
|
|
|
|
|
|
|
|
xenbus->backend_watch[i] =
|
2023-01-02 14:05:16 +03:00
|
|
|
xs_node_watch(xenbus->xsh, node, key, xen_bus_backend_changed,
|
|
|
|
xenbus, &local_err);
|
2020-10-01 11:15:00 +03:00
|
|
|
if (local_err) {
|
|
|
|
/* This need not be treated as a hard error so don't propagate */
|
|
|
|
error_reportf_err(local_err,
|
|
|
|
"failed to set up '%s' enumeration watch: ",
|
|
|
|
type[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
g_free(node);
|
2019-01-08 17:49:00 +03:00
|
|
|
}
|
|
|
|
|
2020-10-01 11:15:00 +03:00
|
|
|
g_free(type);
|
|
|
|
g_free(key);
|
2019-01-08 17:48:49 +03:00
|
|
|
return;
|
|
|
|
|
|
|
|
fail:
|
qdev: Unrealize must not fail
Devices may have component devices and buses.
Device realization may fail. Realization is recursive: a device's
realize() method realizes its components, and device_set_realized()
realizes its buses (which should in turn realize the devices on that
bus, except bus_set_realized() doesn't implement that, yet).
When realization of a component or bus fails, we need to roll back:
unrealize everything we realized so far. If any of these unrealizes
failed, the device would be left in an inconsistent state. Must not
happen.
device_set_realized() lets it happen: it ignores errors in the roll
back code starting at label child_realize_fail.
Since realization is recursive, unrealization must be recursive, too.
But how could a partly failed unrealize be rolled back? We'd have to
re-realize, which can fail. This design is fundamentally broken.
device_set_realized() does not roll back at all. Instead, it keeps
unrealizing, ignoring further errors.
It can screw up even for a device with no buses: if the lone
dc->unrealize() fails, it still unregisters vmstate, and calls
listeners' unrealize() callback.
bus_set_realized() does not roll back either. Instead, it stops
unrealizing.
Fortunately, no unrealize method can fail, as we'll see below.
To fix the design error, drop parameter @errp from all the unrealize
methods.
Any unrealize method that uses @errp now needs an update. This leads
us to unrealize() methods that can fail. Merely passing it to another
unrealize method cannot cause failure, though. Here are the ones that
do other things with @errp:
* virtio_serial_device_unrealize()
Fails when qbus_set_hotplug_handler() fails, but still does all the
other work. On failure, the device would stay realized with its
resources completely gone. Oops. Can't happen, because
qbus_set_hotplug_handler() can't actually fail here. Pass
&error_abort to qbus_set_hotplug_handler() instead.
* hw/ppc/spapr_drc.c's unrealize()
Fails when object_property_del() fails, but all the other work is
already done. On failure, the device would stay realized with its
vmstate registration gone. Oops. Can't happen, because
object_property_del() can't actually fail here. Pass &error_abort
to object_property_del() instead.
* spapr_phb_unrealize()
Fails and bails out when remove_drcs() fails, but other work is
already done. On failure, the device would stay realized with some
of its resources gone. Oops. remove_drcs() fails only when
chassis_from_bus()'s object_property_get_uint() fails, and it can't
here. Pass &error_abort to remove_drcs() instead.
Therefore, no unrealize method can fail before this patch.
device_set_realized()'s recursive unrealization via bus uses
object_property_set_bool(). Can't drop @errp there, so pass
&error_abort.
We similarly unrealize with object_property_set_bool() elsewhere,
always ignoring errors. Pass &error_abort instead.
Several unrealize methods no longer handle errors from other unrealize
methods: virtio_9p_device_unrealize(),
virtio_input_device_unrealize(), scsi_qdev_unrealize(), ...
Much of the deleted error handling looks wrong anyway.
One unrealize methods no longer ignore such errors:
usb_ehci_pci_exit().
Several realize methods no longer ignore errors when rolling back:
v9fs_device_realize_common(), pci_qdev_unrealize(),
spapr_phb_realize(), usb_qdev_realize(), vfio_ccw_realize(),
virtio_device_realize().
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
Message-Id: <20200505152926.18877-17-armbru@redhat.com>
2020-05-05 18:29:24 +03:00
|
|
|
xen_bus_unrealize(bus);
|
2020-10-01 11:15:00 +03:00
|
|
|
g_free(key);
|
2019-01-08 17:48:47 +03:00
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:59 +03:00
|
|
|
static void xen_bus_unplug_request(HotplugHandler *hotplug,
|
|
|
|
DeviceState *dev,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
XenDevice *xendev = XEN_DEVICE(dev);
|
|
|
|
|
|
|
|
xen_device_unplug(xendev, errp);
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:47 +03:00
|
|
|
static void xen_bus_class_init(ObjectClass *class, void *data)
|
|
|
|
{
|
|
|
|
BusClass *bus_class = BUS_CLASS(class);
|
2019-01-08 17:48:59 +03:00
|
|
|
HotplugHandlerClass *hotplug_class = HOTPLUG_HANDLER_CLASS(class);
|
2019-01-08 17:48:47 +03:00
|
|
|
|
2019-01-08 17:48:49 +03:00
|
|
|
bus_class->print_dev = xen_bus_print_dev;
|
|
|
|
bus_class->get_dev_path = xen_bus_get_dev_path;
|
2019-01-08 17:48:47 +03:00
|
|
|
bus_class->realize = xen_bus_realize;
|
|
|
|
bus_class->unrealize = xen_bus_unrealize;
|
2019-01-08 17:48:59 +03:00
|
|
|
|
|
|
|
hotplug_class->unplug_request = xen_bus_unplug_request;
|
2019-01-08 17:48:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static const TypeInfo xen_bus_type_info = {
|
|
|
|
.name = TYPE_XEN_BUS,
|
|
|
|
.parent = TYPE_BUS,
|
|
|
|
.instance_size = sizeof(XenBus),
|
|
|
|
.class_size = sizeof(XenBusClass),
|
|
|
|
.class_init = xen_bus_class_init,
|
|
|
|
.interfaces = (InterfaceInfo[]) {
|
|
|
|
{ TYPE_HOTPLUG_HANDLER },
|
|
|
|
{ }
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2019-01-08 17:48:59 +03:00
|
|
|
void xen_device_backend_printf(XenDevice *xendev, const char *key,
|
|
|
|
const char *fmt, ...)
|
2019-01-08 17:48:49 +03:00
|
|
|
{
|
|
|
|
XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev)));
|
|
|
|
Error *local_err = NULL;
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
g_assert(xenbus->xsh);
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
xs_node_vprintf(xenbus->xsh, XBT_NULL, xendev->backend_path, key,
|
|
|
|
&local_err, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
if (local_err) {
|
|
|
|
error_report_err(local_err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-19 16:02:01 +03:00
|
|
|
G_GNUC_SCANF(3, 4)
|
2019-01-08 17:48:50 +03:00
|
|
|
static int xen_device_backend_scanf(XenDevice *xendev, const char *key,
|
|
|
|
const char *fmt, ...)
|
|
|
|
{
|
|
|
|
XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev)));
|
|
|
|
va_list ap;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
g_assert(xenbus->xsh);
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
rc = xs_node_vscanf(xenbus->xsh, XBT_NULL, xendev->backend_path, key,
|
|
|
|
NULL, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
void xen_device_backend_set_state(XenDevice *xendev,
|
|
|
|
enum xenbus_state state)
|
2019-01-08 17:48:49 +03:00
|
|
|
{
|
|
|
|
const char *type = object_get_typename(OBJECT(xendev));
|
|
|
|
|
|
|
|
if (xendev->backend_state == state) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
trace_xen_device_backend_state(type, xendev->name,
|
|
|
|
xs_strstate(state));
|
|
|
|
|
|
|
|
xendev->backend_state = state;
|
|
|
|
xen_device_backend_printf(xendev, "state", "%u", state);
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:50 +03:00
|
|
|
enum xenbus_state xen_device_backend_get_state(XenDevice *xendev)
|
|
|
|
{
|
|
|
|
return xendev->backend_state;
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:59 +03:00
|
|
|
static void xen_device_backend_set_online(XenDevice *xendev, bool online)
|
|
|
|
{
|
|
|
|
const char *type = object_get_typename(OBJECT(xendev));
|
|
|
|
|
|
|
|
if (xendev->backend_online == online) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
trace_xen_device_backend_online(type, xendev->name, online);
|
|
|
|
|
|
|
|
xendev->backend_online = online;
|
|
|
|
xen_device_backend_printf(xendev, "online", "%u", online);
|
|
|
|
}
|
|
|
|
|
2019-08-23 13:15:33 +03:00
|
|
|
/*
|
|
|
|
* Tell from the state whether the frontend is likely alive,
|
|
|
|
* i.e. it will react to a change of state of the backend.
|
|
|
|
*/
|
2019-09-13 11:21:58 +03:00
|
|
|
static bool xen_device_frontend_is_active(XenDevice *xendev)
|
2019-08-23 13:15:33 +03:00
|
|
|
{
|
2019-09-13 11:21:58 +03:00
|
|
|
switch (xendev->frontend_state) {
|
2019-08-23 13:15:33 +03:00
|
|
|
case XenbusStateInitWait:
|
|
|
|
case XenbusStateInitialised:
|
|
|
|
case XenbusStateConnected:
|
|
|
|
case XenbusStateClosing:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-02 14:05:16 +03:00
|
|
|
static void xen_device_backend_changed(void *opaque, const char *path)
|
2019-01-08 17:48:59 +03:00
|
|
|
{
|
|
|
|
XenDevice *xendev = opaque;
|
|
|
|
const char *type = object_get_typename(OBJECT(xendev));
|
|
|
|
enum xenbus_state state;
|
|
|
|
unsigned int online;
|
|
|
|
|
|
|
|
trace_xen_device_backend_changed(type, xendev->name);
|
|
|
|
|
|
|
|
if (xen_device_backend_scanf(xendev, "state", "%u", &state) != 1) {
|
|
|
|
state = XenbusStateUnknown;
|
|
|
|
}
|
|
|
|
|
|
|
|
xen_device_backend_set_state(xendev, state);
|
|
|
|
|
|
|
|
if (xen_device_backend_scanf(xendev, "online", "%u", &online) != 1) {
|
|
|
|
online = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
xen_device_backend_set_online(xendev, !!online);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the toolstack (or unplug request callback) has set the backend
|
2019-08-23 13:15:33 +03:00
|
|
|
* state to Closing, but there is no active frontend then set the
|
|
|
|
* backend state to Closed.
|
2019-01-08 17:48:59 +03:00
|
|
|
*/
|
2019-09-13 11:21:58 +03:00
|
|
|
if (state == XenbusStateClosing &&
|
|
|
|
!xen_device_frontend_is_active(xendev)) {
|
2019-01-08 17:48:59 +03:00
|
|
|
xen_device_backend_set_state(xendev, XenbusStateClosed);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2019-01-22 18:53:46 +03:00
|
|
|
* If a backend is still 'online' then we should leave it alone but,
|
2019-09-13 11:21:58 +03:00
|
|
|
* if a backend is not 'online', then the device is a candidate
|
|
|
|
* for destruction. Hence add it to the 'inactive' list to be cleaned
|
|
|
|
* by xen_bus_cleanup().
|
2019-01-08 17:48:59 +03:00
|
|
|
*/
|
2019-09-13 11:21:58 +03:00
|
|
|
if (!online &&
|
|
|
|
(state == XenbusStateClosed || state == XenbusStateInitialising ||
|
|
|
|
state == XenbusStateInitWait || state == XenbusStateUnknown) &&
|
|
|
|
!xendev->inactive) {
|
|
|
|
XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev)));
|
2019-01-08 17:49:00 +03:00
|
|
|
|
2019-09-13 11:21:58 +03:00
|
|
|
xendev->inactive = true;
|
|
|
|
QLIST_INSERT_HEAD(&xenbus->inactive_devices, xendev, list);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Re-write the state to cause a XenBus backend_watch notification,
|
|
|
|
* resulting in a call to xen_bus_cleanup().
|
|
|
|
*/
|
|
|
|
xen_device_backend_printf(xendev, "state", "%u", state);
|
2019-01-08 17:48:59 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:49 +03:00
|
|
|
static void xen_device_backend_create(XenDevice *xendev, Error **errp)
|
|
|
|
{
|
2020-07-07 19:50:37 +03:00
|
|
|
ERRP_GUARD();
|
2019-01-08 17:48:49 +03:00
|
|
|
XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev)));
|
|
|
|
|
|
|
|
xendev->backend_path = xen_device_get_backend_path(xendev);
|
|
|
|
|
|
|
|
g_assert(xenbus->xsh);
|
|
|
|
|
2023-01-02 14:05:16 +03:00
|
|
|
xs_node_create(xenbus->xsh, XBT_NULL, xendev->backend_path,
|
|
|
|
xenbus->backend_id, xendev->frontend_id, XS_PERM_READ, errp);
|
2020-07-07 19:50:37 +03:00
|
|
|
if (*errp) {
|
|
|
|
error_prepend(errp, "failed to create backend: ");
|
2019-01-08 17:48:59 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
xendev->backend_state_watch =
|
2023-01-02 14:05:16 +03:00
|
|
|
xs_node_watch(xendev->xsh, xendev->backend_path,
|
|
|
|
"state", xen_device_backend_changed, xendev,
|
|
|
|
errp);
|
2020-07-07 19:50:37 +03:00
|
|
|
if (*errp) {
|
|
|
|
error_prepend(errp, "failed to watch backend state: ");
|
2019-01-08 17:48:59 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
xendev->backend_online_watch =
|
2023-01-02 14:05:16 +03:00
|
|
|
xs_node_watch(xendev->xsh, xendev->backend_path,
|
|
|
|
"online", xen_device_backend_changed, xendev,
|
|
|
|
errp);
|
2020-07-07 19:50:37 +03:00
|
|
|
if (*errp) {
|
|
|
|
error_prepend(errp, "failed to watch backend online: ");
|
2019-01-08 17:48:59 +03:00
|
|
|
return;
|
2019-01-08 17:48:49 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xen_device_backend_destroy(XenDevice *xendev)
|
|
|
|
{
|
|
|
|
XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev)));
|
|
|
|
Error *local_err = NULL;
|
|
|
|
|
2019-01-08 17:48:59 +03:00
|
|
|
if (xendev->backend_online_watch) {
|
2023-01-02 14:05:16 +03:00
|
|
|
xs_node_unwatch(xendev->xsh, xendev->backend_online_watch);
|
2019-01-08 17:48:59 +03:00
|
|
|
xendev->backend_online_watch = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xendev->backend_state_watch) {
|
2023-01-02 14:05:16 +03:00
|
|
|
xs_node_unwatch(xendev->xsh, xendev->backend_state_watch);
|
2019-01-08 17:48:59 +03:00
|
|
|
xendev->backend_state_watch = NULL;
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:49 +03:00
|
|
|
if (!xendev->backend_path) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_assert(xenbus->xsh);
|
|
|
|
|
|
|
|
xs_node_destroy(xenbus->xsh, XBT_NULL, xendev->backend_path,
|
|
|
|
&local_err);
|
|
|
|
g_free(xendev->backend_path);
|
|
|
|
xendev->backend_path = NULL;
|
|
|
|
|
|
|
|
if (local_err) {
|
|
|
|
error_report_err(local_err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:59 +03:00
|
|
|
void xen_device_frontend_printf(XenDevice *xendev, const char *key,
|
|
|
|
const char *fmt, ...)
|
2019-01-08 17:48:49 +03:00
|
|
|
{
|
|
|
|
XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev)));
|
|
|
|
Error *local_err = NULL;
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
g_assert(xenbus->xsh);
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
xs_node_vprintf(xenbus->xsh, XBT_NULL, xendev->frontend_path, key,
|
|
|
|
&local_err, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
if (local_err) {
|
|
|
|
error_report_err(local_err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:59 +03:00
|
|
|
int xen_device_frontend_scanf(XenDevice *xendev, const char *key,
|
|
|
|
const char *fmt, ...)
|
2019-01-08 17:48:50 +03:00
|
|
|
{
|
|
|
|
XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev)));
|
|
|
|
va_list ap;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
g_assert(xenbus->xsh);
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
rc = xs_node_vscanf(xenbus->xsh, XBT_NULL, xendev->frontend_path, key,
|
|
|
|
NULL, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:49 +03:00
|
|
|
static void xen_device_frontend_set_state(XenDevice *xendev,
|
2019-08-23 13:15:34 +03:00
|
|
|
enum xenbus_state state,
|
|
|
|
bool publish)
|
2019-01-08 17:48:49 +03:00
|
|
|
{
|
|
|
|
const char *type = object_get_typename(OBJECT(xendev));
|
|
|
|
|
|
|
|
if (xendev->frontend_state == state) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
trace_xen_device_frontend_state(type, xendev->name,
|
|
|
|
xs_strstate(state));
|
|
|
|
|
|
|
|
xendev->frontend_state = state;
|
2019-08-23 13:15:34 +03:00
|
|
|
if (publish) {
|
|
|
|
xen_device_frontend_printf(xendev, "state", "%u", state);
|
|
|
|
}
|
2019-01-08 17:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-01-02 14:05:16 +03:00
|
|
|
static void xen_device_frontend_changed(void *opaque, const char *path)
|
2019-01-08 17:48:50 +03:00
|
|
|
{
|
|
|
|
XenDevice *xendev = opaque;
|
|
|
|
XenDeviceClass *xendev_class = XEN_DEVICE_GET_CLASS(xendev);
|
|
|
|
const char *type = object_get_typename(OBJECT(xendev));
|
|
|
|
enum xenbus_state state;
|
|
|
|
|
|
|
|
trace_xen_device_frontend_changed(type, xendev->name);
|
|
|
|
|
|
|
|
if (xen_device_frontend_scanf(xendev, "state", "%u", &state) != 1) {
|
|
|
|
state = XenbusStateUnknown;
|
|
|
|
}
|
|
|
|
|
2019-08-23 13:15:34 +03:00
|
|
|
xen_device_frontend_set_state(xendev, state, false);
|
2019-01-08 17:48:50 +03:00
|
|
|
|
2019-01-22 18:53:46 +03:00
|
|
|
if (state == XenbusStateInitialising &&
|
|
|
|
xendev->backend_state == XenbusStateClosed &&
|
|
|
|
xendev->backend_online) {
|
|
|
|
/*
|
|
|
|
* The frontend is re-initializing so switch back to
|
|
|
|
* InitWait.
|
|
|
|
*/
|
|
|
|
xen_device_backend_set_state(xendev, XenbusStateInitWait);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:50 +03:00
|
|
|
if (xendev_class->frontend_changed) {
|
|
|
|
Error *local_err = NULL;
|
|
|
|
|
|
|
|
xendev_class->frontend_changed(xendev, state, &local_err);
|
|
|
|
|
|
|
|
if (local_err) {
|
|
|
|
error_reportf_err(local_err, "frontend change error: ");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-18 14:57:44 +03:00
|
|
|
static bool xen_device_frontend_exists(XenDevice *xendev)
|
|
|
|
{
|
|
|
|
enum xenbus_state state;
|
|
|
|
|
|
|
|
return (xen_device_frontend_scanf(xendev, "state", "%u", &state) == 1);
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:49 +03:00
|
|
|
static void xen_device_frontend_create(XenDevice *xendev, Error **errp)
|
|
|
|
{
|
2020-07-07 19:50:37 +03:00
|
|
|
ERRP_GUARD();
|
2019-01-08 17:48:49 +03:00
|
|
|
XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev)));
|
2023-10-14 18:53:23 +03:00
|
|
|
XenDeviceClass *xendev_class = XEN_DEVICE_GET_CLASS(xendev);
|
2019-01-08 17:48:49 +03:00
|
|
|
|
2023-10-14 18:53:23 +03:00
|
|
|
if (xendev_class->get_frontend_path) {
|
|
|
|
xendev->frontend_path = xendev_class->get_frontend_path(xendev, errp);
|
|
|
|
if (!xendev->frontend_path) {
|
|
|
|
error_prepend(errp, "failed to create frontend: ");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
xendev->frontend_path = xen_device_get_frontend_path(xendev);
|
|
|
|
}
|
2019-01-08 17:48:49 +03:00
|
|
|
|
2019-09-18 14:57:44 +03:00
|
|
|
/*
|
|
|
|
* The frontend area may have already been created by a legacy
|
|
|
|
* toolstack.
|
|
|
|
*/
|
|
|
|
if (!xen_device_frontend_exists(xendev)) {
|
|
|
|
g_assert(xenbus->xsh);
|
2019-01-08 17:48:49 +03:00
|
|
|
|
2023-01-02 14:05:16 +03:00
|
|
|
xs_node_create(xenbus->xsh, XBT_NULL, xendev->frontend_path,
|
|
|
|
xendev->frontend_id, xenbus->backend_id,
|
|
|
|
XS_PERM_READ | XS_PERM_WRITE, errp);
|
2020-07-07 19:50:37 +03:00
|
|
|
if (*errp) {
|
|
|
|
error_prepend(errp, "failed to create frontend: ");
|
2019-09-18 14:57:44 +03:00
|
|
|
return;
|
|
|
|
}
|
2019-01-08 17:48:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
xendev->frontend_state_watch =
|
2023-01-02 14:05:16 +03:00
|
|
|
xs_node_watch(xendev->xsh, xendev->frontend_path, "state",
|
|
|
|
xen_device_frontend_changed, xendev, errp);
|
2020-07-07 19:50:37 +03:00
|
|
|
if (*errp) {
|
|
|
|
error_prepend(errp, "failed to watch frontend state: ");
|
2019-01-08 17:48:49 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xen_device_frontend_destroy(XenDevice *xendev)
|
|
|
|
{
|
|
|
|
XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev)));
|
|
|
|
Error *local_err = NULL;
|
|
|
|
|
2019-01-08 17:48:50 +03:00
|
|
|
if (xendev->frontend_state_watch) {
|
2023-01-02 14:05:16 +03:00
|
|
|
xs_node_unwatch(xendev->xsh, xendev->frontend_state_watch);
|
2019-01-08 17:48:50 +03:00
|
|
|
xendev->frontend_state_watch = NULL;
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:49 +03:00
|
|
|
if (!xendev->frontend_path) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_assert(xenbus->xsh);
|
|
|
|
|
|
|
|
xs_node_destroy(xenbus->xsh, XBT_NULL, xendev->frontend_path,
|
|
|
|
&local_err);
|
|
|
|
g_free(xendev->frontend_path);
|
|
|
|
xendev->frontend_path = NULL;
|
|
|
|
|
|
|
|
if (local_err) {
|
|
|
|
error_report_err(local_err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:51 +03:00
|
|
|
void xen_device_set_max_grant_refs(XenDevice *xendev, unsigned int nr_refs,
|
|
|
|
Error **errp)
|
|
|
|
{
|
2023-01-02 00:31:37 +03:00
|
|
|
if (qemu_xen_gnttab_set_max_grants(xendev->xgth, nr_refs)) {
|
2019-01-08 17:48:51 +03:00
|
|
|
error_setg_errno(errp, errno, "xengnttab_set_max_grants failed");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void *xen_device_map_grant_refs(XenDevice *xendev, uint32_t *refs,
|
|
|
|
unsigned int nr_refs, int prot,
|
|
|
|
Error **errp)
|
|
|
|
{
|
2023-01-02 00:31:37 +03:00
|
|
|
void *map = qemu_xen_gnttab_map_refs(xendev->xgth, nr_refs,
|
|
|
|
xendev->frontend_id, refs, prot);
|
2019-01-08 17:48:51 +03:00
|
|
|
|
|
|
|
if (!map) {
|
|
|
|
error_setg_errno(errp, errno,
|
|
|
|
"xengnttab_map_domain_grant_refs failed");
|
|
|
|
}
|
|
|
|
|
|
|
|
return map;
|
|
|
|
}
|
|
|
|
|
2023-01-10 03:03:49 +03:00
|
|
|
void xen_device_unmap_grant_refs(XenDevice *xendev, void *map, uint32_t *refs,
|
2019-01-08 17:48:51 +03:00
|
|
|
unsigned int nr_refs, Error **errp)
|
|
|
|
{
|
2023-01-10 03:03:49 +03:00
|
|
|
if (qemu_xen_gnttab_unmap(xendev->xgth, map, refs, nr_refs)) {
|
2019-01-08 17:48:51 +03:00
|
|
|
error_setg_errno(errp, errno, "xengnttab_unmap failed");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void xen_device_copy_grant_refs(XenDevice *xendev, bool to_domain,
|
|
|
|
XenDeviceGrantCopySegment segs[],
|
|
|
|
unsigned int nr_segs, Error **errp)
|
|
|
|
{
|
2023-01-02 00:31:37 +03:00
|
|
|
qemu_xen_gnttab_grant_copy(xendev->xgth, to_domain, xendev->frontend_id,
|
|
|
|
(XenGrantCopySegment *)segs, nr_segs, errp);
|
2019-01-08 17:48:51 +03:00
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:52 +03:00
|
|
|
struct XenEventChannel {
|
2019-04-08 18:16:15 +03:00
|
|
|
QLIST_ENTRY(XenEventChannel) list;
|
2019-04-08 18:16:16 +03:00
|
|
|
AioContext *ctx;
|
2019-04-08 18:16:15 +03:00
|
|
|
xenevtchn_handle *xeh;
|
2019-01-08 17:48:52 +03:00
|
|
|
evtchn_port_t local_port;
|
|
|
|
XenEventHandler handler;
|
|
|
|
void *opaque;
|
|
|
|
};
|
|
|
|
|
2019-04-08 18:16:17 +03:00
|
|
|
static bool xen_device_poll(void *opaque)
|
|
|
|
{
|
|
|
|
XenEventChannel *channel = opaque;
|
|
|
|
|
|
|
|
return channel->handler(channel->opaque);
|
|
|
|
}
|
|
|
|
|
2019-04-08 18:16:15 +03:00
|
|
|
static void xen_device_event(void *opaque)
|
2019-01-08 17:48:52 +03:00
|
|
|
{
|
2019-04-08 18:16:15 +03:00
|
|
|
XenEventChannel *channel = opaque;
|
2023-01-01 20:54:41 +03:00
|
|
|
unsigned long port = qemu_xen_evtchn_pending(channel->xeh);
|
2019-01-08 17:48:52 +03:00
|
|
|
|
|
|
|
if (port == channel->local_port) {
|
2019-04-08 18:16:17 +03:00
|
|
|
xen_device_poll(channel);
|
2019-04-08 18:16:15 +03:00
|
|
|
|
2023-01-01 20:54:41 +03:00
|
|
|
qemu_xen_evtchn_unmask(channel->xeh, port);
|
2019-01-08 17:48:52 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-16 17:34:51 +03:00
|
|
|
void xen_device_set_event_channel_context(XenDevice *xendev,
|
|
|
|
XenEventChannel *channel,
|
|
|
|
AioContext *ctx,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
if (!channel) {
|
|
|
|
error_setg(errp, "bad channel");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (channel->ctx)
|
aio: remove aio_disable_external() API
All callers now pass is_external=false to aio_set_fd_handler() and
aio_set_event_notifier(). The aio_disable_external() API that
temporarily disables fd handlers that were registered is_external=true
is therefore dead code.
Remove aio_disable_external(), aio_enable_external(), and the
is_external arguments to aio_set_fd_handler() and
aio_set_event_notifier().
The entire test-fdmon-epoll test is removed because its sole purpose was
testing aio_disable_external().
Parts of this patch were generated using the following coccinelle
(https://coccinelle.lip6.fr/) semantic patch:
@@
expression ctx, fd, is_external, io_read, io_write, io_poll, io_poll_ready, opaque;
@@
- aio_set_fd_handler(ctx, fd, is_external, io_read, io_write, io_poll, io_poll_ready, opaque)
+ aio_set_fd_handler(ctx, fd, io_read, io_write, io_poll, io_poll_ready, opaque)
@@
expression ctx, notifier, is_external, io_read, io_poll, io_poll_ready;
@@
- aio_set_event_notifier(ctx, notifier, is_external, io_read, io_poll, io_poll_ready)
+ aio_set_event_notifier(ctx, notifier, io_read, io_poll, io_poll_ready)
Reviewed-by: Juan Quintela <quintela@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Message-Id: <20230516190238.8401-21-stefanha@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
2023-05-16 22:02:38 +03:00
|
|
|
aio_set_fd_handler(channel->ctx, qemu_xen_evtchn_fd(channel->xeh),
|
aio-posix: split poll check from ready handler
Adaptive polling measures the execution time of the polling check plus
handlers called when a polled event becomes ready. Handlers can take a
significant amount of time, making it look like polling was running for
a long time when in fact the event handler was running for a long time.
For example, on Linux the io_submit(2) syscall invoked when a virtio-blk
device's virtqueue becomes ready can take 10s of microseconds. This
can exceed the default polling interval (32 microseconds) and cause
adaptive polling to stop polling.
By excluding the handler's execution time from the polling check we make
the adaptive polling calculation more accurate. As a result, the event
loop now stays in polling mode where previously it would have fallen
back to file descriptor monitoring.
The following data was collected with virtio-blk num-queues=2
event_idx=off using an IOThread. Before:
168k IOPS, IOThread syscalls:
9837.115 ( 0.020 ms): IO iothread1/620155 io_submit(ctx_id: 140512552468480, nr: 16, iocbpp: 0x7fcb9f937db0) = 16
9837.158 ( 0.002 ms): IO iothread1/620155 write(fd: 103, buf: 0x556a2ef71b88, count: 8) = 8
9837.161 ( 0.001 ms): IO iothread1/620155 write(fd: 104, buf: 0x556a2ef71b88, count: 8) = 8
9837.163 ( 0.001 ms): IO iothread1/620155 ppoll(ufds: 0x7fcb90002800, nfds: 4, tsp: 0x7fcb9f1342d0, sigsetsize: 8) = 3
9837.164 ( 0.001 ms): IO iothread1/620155 read(fd: 107, buf: 0x7fcb9f939cc0, count: 512) = 8
9837.174 ( 0.001 ms): IO iothread1/620155 read(fd: 105, buf: 0x7fcb9f939cc0, count: 512) = 8
9837.176 ( 0.001 ms): IO iothread1/620155 read(fd: 106, buf: 0x7fcb9f939cc0, count: 512) = 8
9837.209 ( 0.035 ms): IO iothread1/620155 io_submit(ctx_id: 140512552468480, nr: 32, iocbpp: 0x7fca7d0cebe0) = 32
174k IOPS (+3.6%), IOThread syscalls:
9809.566 ( 0.036 ms): IO iothread1/623061 io_submit(ctx_id: 140539805028352, nr: 32, iocbpp: 0x7fd0cdd62be0) = 32
9809.625 ( 0.001 ms): IO iothread1/623061 write(fd: 103, buf: 0x5647cfba5f58, count: 8) = 8
9809.627 ( 0.002 ms): IO iothread1/623061 write(fd: 104, buf: 0x5647cfba5f58, count: 8) = 8
9809.663 ( 0.036 ms): IO iothread1/623061 io_submit(ctx_id: 140539805028352, nr: 32, iocbpp: 0x7fd0d0388b50) = 32
Notice that ppoll(2) and eventfd read(2) syscalls are eliminated because
the IOThread stays in polling mode instead of falling back to file
descriptor monitoring.
As usual, polling is not implemented on Windows so this patch ignores
the new io_poll_read() callback in aio-win32.c.
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Stefano Garzarella <sgarzare@redhat.com>
Message-id: 20211207132336.36627-2-stefanha@redhat.com
[Fixed up aio_set_event_notifier() calls in
tests/unit/test-fdmon-epoll.c added after this series was queued.
--Stefan]
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
2021-12-07 16:23:31 +03:00
|
|
|
NULL, NULL, NULL, NULL, NULL);
|
2019-12-16 17:34:51 +03:00
|
|
|
|
|
|
|
channel->ctx = ctx;
|
2023-05-16 22:02:29 +03:00
|
|
|
if (ctx) {
|
|
|
|
aio_set_fd_handler(channel->ctx, qemu_xen_evtchn_fd(channel->xeh),
|
aio: remove aio_disable_external() API
All callers now pass is_external=false to aio_set_fd_handler() and
aio_set_event_notifier(). The aio_disable_external() API that
temporarily disables fd handlers that were registered is_external=true
is therefore dead code.
Remove aio_disable_external(), aio_enable_external(), and the
is_external arguments to aio_set_fd_handler() and
aio_set_event_notifier().
The entire test-fdmon-epoll test is removed because its sole purpose was
testing aio_disable_external().
Parts of this patch were generated using the following coccinelle
(https://coccinelle.lip6.fr/) semantic patch:
@@
expression ctx, fd, is_external, io_read, io_write, io_poll, io_poll_ready, opaque;
@@
- aio_set_fd_handler(ctx, fd, is_external, io_read, io_write, io_poll, io_poll_ready, opaque)
+ aio_set_fd_handler(ctx, fd, io_read, io_write, io_poll, io_poll_ready, opaque)
@@
expression ctx, notifier, is_external, io_read, io_poll, io_poll_ready;
@@
- aio_set_event_notifier(ctx, notifier, is_external, io_read, io_poll, io_poll_ready)
+ aio_set_event_notifier(ctx, notifier, io_read, io_poll, io_poll_ready)
Reviewed-by: Juan Quintela <quintela@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Message-Id: <20230516190238.8401-21-stefanha@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
2023-05-16 22:02:38 +03:00
|
|
|
xen_device_event, NULL, xen_device_poll, NULL,
|
|
|
|
channel);
|
2023-05-16 22:02:29 +03:00
|
|
|
}
|
2019-12-16 17:34:51 +03:00
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:52 +03:00
|
|
|
XenEventChannel *xen_device_bind_event_channel(XenDevice *xendev,
|
|
|
|
unsigned int port,
|
|
|
|
XenEventHandler handler,
|
|
|
|
void *opaque, Error **errp)
|
|
|
|
{
|
|
|
|
XenEventChannel *channel = g_new0(XenEventChannel, 1);
|
|
|
|
xenevtchn_port_or_error_t local_port;
|
|
|
|
|
2023-01-01 20:54:41 +03:00
|
|
|
channel->xeh = qemu_xen_evtchn_open();
|
2019-04-08 18:16:15 +03:00
|
|
|
if (!channel->xeh) {
|
|
|
|
error_setg_errno(errp, errno, "failed xenevtchn_open");
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2023-01-01 20:54:41 +03:00
|
|
|
local_port = qemu_xen_evtchn_bind_interdomain(channel->xeh,
|
2019-01-08 17:48:52 +03:00
|
|
|
xendev->frontend_id,
|
|
|
|
port);
|
|
|
|
if (local_port < 0) {
|
|
|
|
error_setg_errno(errp, errno, "xenevtchn_bind_interdomain failed");
|
2019-04-08 18:16:15 +03:00
|
|
|
goto fail;
|
2019-01-08 17:48:52 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
channel->local_port = local_port;
|
|
|
|
channel->handler = handler;
|
|
|
|
channel->opaque = opaque;
|
|
|
|
|
2019-12-16 17:34:51 +03:00
|
|
|
/* Only reason for failure is a NULL channel */
|
|
|
|
xen_device_set_event_channel_context(xendev, channel,
|
|
|
|
qemu_get_aio_context(),
|
|
|
|
&error_abort);
|
2019-04-08 18:16:15 +03:00
|
|
|
|
|
|
|
QLIST_INSERT_HEAD(&xendev->event_channels, channel, list);
|
2019-01-08 17:48:52 +03:00
|
|
|
|
|
|
|
return channel;
|
2019-04-08 18:16:15 +03:00
|
|
|
|
|
|
|
fail:
|
|
|
|
if (channel->xeh) {
|
2023-01-01 20:54:41 +03:00
|
|
|
qemu_xen_evtchn_close(channel->xeh);
|
2019-04-08 18:16:15 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
g_free(channel);
|
|
|
|
|
|
|
|
return NULL;
|
2019-01-08 17:48:52 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void xen_device_notify_event_channel(XenDevice *xendev,
|
|
|
|
XenEventChannel *channel,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
if (!channel) {
|
|
|
|
error_setg(errp, "bad channel");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-01-01 20:54:41 +03:00
|
|
|
if (qemu_xen_evtchn_notify(channel->xeh, channel->local_port) < 0) {
|
2019-01-08 17:48:52 +03:00
|
|
|
error_setg_errno(errp, errno, "xenevtchn_notify failed");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-16 18:00:23 +03:00
|
|
|
unsigned int xen_event_channel_get_local_port(XenEventChannel *channel)
|
|
|
|
{
|
|
|
|
return channel->local_port;
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:52 +03:00
|
|
|
void xen_device_unbind_event_channel(XenDevice *xendev,
|
|
|
|
XenEventChannel *channel,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
if (!channel) {
|
|
|
|
error_setg(errp, "bad channel");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-04-08 18:16:15 +03:00
|
|
|
QLIST_REMOVE(channel, list);
|
2019-01-08 17:48:52 +03:00
|
|
|
|
xen-block: fix segv on unrealize
Backtrace:
qemu_lockcnt_lock (lockcnt=0xb4) at ../util/lockcnt.c:238
aio_set_fd_handler (ctx=0x0, fd=51, is_external=true, io_read=0x0, io_write=0x0, io_poll=0x0, io_poll_ready=0x0, opaque=0x0) at ../util/aio-posix.c:119
xen_device_unbind_event_channel (xendev=0x55c6da5b5000, channel=0x55c6da6c4c80, errp=0x7fff641ac608) at ../hw/xen/xen-bus.c:926
xen_block_dataplane_stop (dataplane=0x55c6da6ddbe0) at ../hw/block/dataplane/xen-block.c:719
xen_block_disconnect (xendev=0x55c6da5b5000, errp=0x0) at ../hw/block/xen-block.c:48
xen_block_unrealize (xendev=0x55c6da5b5000) at ../hw/block/xen-block.c:154
xen_device_unrealize (dev=0x55c6da5b5000) at ../hw/xen/xen-bus.c:956
xen_device_exit (n=0x55c6da5b50d0, data=0x0) at ../hw/xen/xen-bus.c:985
notifier_list_notify (list=0x55c6d91f9820 <exit_notifiers>, data=0x0) at ../util/notify.c:39
qemu_run_exit_notifiers () at ../softmmu/runstate.c:760
Fixes: f6eac904f682 ("xen-block: implement BlockDevOps->drained_begin()")
Signed-off-by: Anthony PERARD <anthony.perard@citrix.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Message-Id: <20230606131605.55596-1-anthony.perard@citrix.com>
Signed-off-by: Anthony PERARD <anthony.perard@citrix.com>
2023-06-06 16:16:05 +03:00
|
|
|
if (channel->ctx) {
|
|
|
|
aio_set_fd_handler(channel->ctx, qemu_xen_evtchn_fd(channel->xeh),
|
|
|
|
NULL, NULL, NULL, NULL, NULL);
|
|
|
|
}
|
2019-04-08 18:16:15 +03:00
|
|
|
|
2023-01-01 20:54:41 +03:00
|
|
|
if (qemu_xen_evtchn_unbind(channel->xeh, channel->local_port) < 0) {
|
2019-01-08 17:48:52 +03:00
|
|
|
error_setg_errno(errp, errno, "xenevtchn_unbind failed");
|
|
|
|
}
|
|
|
|
|
2023-01-01 20:54:41 +03:00
|
|
|
qemu_xen_evtchn_close(channel->xeh);
|
2019-01-08 17:48:52 +03:00
|
|
|
g_free(channel);
|
|
|
|
}
|
|
|
|
|
qdev: Unrealize must not fail
Devices may have component devices and buses.
Device realization may fail. Realization is recursive: a device's
realize() method realizes its components, and device_set_realized()
realizes its buses (which should in turn realize the devices on that
bus, except bus_set_realized() doesn't implement that, yet).
When realization of a component or bus fails, we need to roll back:
unrealize everything we realized so far. If any of these unrealizes
failed, the device would be left in an inconsistent state. Must not
happen.
device_set_realized() lets it happen: it ignores errors in the roll
back code starting at label child_realize_fail.
Since realization is recursive, unrealization must be recursive, too.
But how could a partly failed unrealize be rolled back? We'd have to
re-realize, which can fail. This design is fundamentally broken.
device_set_realized() does not roll back at all. Instead, it keeps
unrealizing, ignoring further errors.
It can screw up even for a device with no buses: if the lone
dc->unrealize() fails, it still unregisters vmstate, and calls
listeners' unrealize() callback.
bus_set_realized() does not roll back either. Instead, it stops
unrealizing.
Fortunately, no unrealize method can fail, as we'll see below.
To fix the design error, drop parameter @errp from all the unrealize
methods.
Any unrealize method that uses @errp now needs an update. This leads
us to unrealize() methods that can fail. Merely passing it to another
unrealize method cannot cause failure, though. Here are the ones that
do other things with @errp:
* virtio_serial_device_unrealize()
Fails when qbus_set_hotplug_handler() fails, but still does all the
other work. On failure, the device would stay realized with its
resources completely gone. Oops. Can't happen, because
qbus_set_hotplug_handler() can't actually fail here. Pass
&error_abort to qbus_set_hotplug_handler() instead.
* hw/ppc/spapr_drc.c's unrealize()
Fails when object_property_del() fails, but all the other work is
already done. On failure, the device would stay realized with its
vmstate registration gone. Oops. Can't happen, because
object_property_del() can't actually fail here. Pass &error_abort
to object_property_del() instead.
* spapr_phb_unrealize()
Fails and bails out when remove_drcs() fails, but other work is
already done. On failure, the device would stay realized with some
of its resources gone. Oops. remove_drcs() fails only when
chassis_from_bus()'s object_property_get_uint() fails, and it can't
here. Pass &error_abort to remove_drcs() instead.
Therefore, no unrealize method can fail before this patch.
device_set_realized()'s recursive unrealization via bus uses
object_property_set_bool(). Can't drop @errp there, so pass
&error_abort.
We similarly unrealize with object_property_set_bool() elsewhere,
always ignoring errors. Pass &error_abort instead.
Several unrealize methods no longer handle errors from other unrealize
methods: virtio_9p_device_unrealize(),
virtio_input_device_unrealize(), scsi_qdev_unrealize(), ...
Much of the deleted error handling looks wrong anyway.
One unrealize methods no longer ignore such errors:
usb_ehci_pci_exit().
Several realize methods no longer ignore errors when rolling back:
v9fs_device_realize_common(), pci_qdev_unrealize(),
spapr_phb_realize(), usb_qdev_realize(), vfio_ccw_realize(),
virtio_device_realize().
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
Message-Id: <20200505152926.18877-17-armbru@redhat.com>
2020-05-05 18:29:24 +03:00
|
|
|
static void xen_device_unrealize(DeviceState *dev)
|
2019-01-08 17:48:47 +03:00
|
|
|
{
|
|
|
|
XenDevice *xendev = XEN_DEVICE(dev);
|
|
|
|
XenDeviceClass *xendev_class = XEN_DEVICE_GET_CLASS(xendev);
|
|
|
|
const char *type = object_get_typename(OBJECT(xendev));
|
2019-04-08 18:16:15 +03:00
|
|
|
XenEventChannel *channel, *next;
|
2019-01-08 17:48:47 +03:00
|
|
|
|
2019-01-08 17:48:49 +03:00
|
|
|
if (!xendev->name) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
trace_xen_device_unrealize(type, xendev->name);
|
|
|
|
|
|
|
|
if (xendev->exit.notify) {
|
|
|
|
qemu_remove_exit_notifier(&xendev->exit);
|
|
|
|
xendev->exit.notify = NULL;
|
|
|
|
}
|
2019-01-08 17:48:47 +03:00
|
|
|
|
|
|
|
if (xendev_class->unrealize) {
|
qdev: Unrealize must not fail
Devices may have component devices and buses.
Device realization may fail. Realization is recursive: a device's
realize() method realizes its components, and device_set_realized()
realizes its buses (which should in turn realize the devices on that
bus, except bus_set_realized() doesn't implement that, yet).
When realization of a component or bus fails, we need to roll back:
unrealize everything we realized so far. If any of these unrealizes
failed, the device would be left in an inconsistent state. Must not
happen.
device_set_realized() lets it happen: it ignores errors in the roll
back code starting at label child_realize_fail.
Since realization is recursive, unrealization must be recursive, too.
But how could a partly failed unrealize be rolled back? We'd have to
re-realize, which can fail. This design is fundamentally broken.
device_set_realized() does not roll back at all. Instead, it keeps
unrealizing, ignoring further errors.
It can screw up even for a device with no buses: if the lone
dc->unrealize() fails, it still unregisters vmstate, and calls
listeners' unrealize() callback.
bus_set_realized() does not roll back either. Instead, it stops
unrealizing.
Fortunately, no unrealize method can fail, as we'll see below.
To fix the design error, drop parameter @errp from all the unrealize
methods.
Any unrealize method that uses @errp now needs an update. This leads
us to unrealize() methods that can fail. Merely passing it to another
unrealize method cannot cause failure, though. Here are the ones that
do other things with @errp:
* virtio_serial_device_unrealize()
Fails when qbus_set_hotplug_handler() fails, but still does all the
other work. On failure, the device would stay realized with its
resources completely gone. Oops. Can't happen, because
qbus_set_hotplug_handler() can't actually fail here. Pass
&error_abort to qbus_set_hotplug_handler() instead.
* hw/ppc/spapr_drc.c's unrealize()
Fails when object_property_del() fails, but all the other work is
already done. On failure, the device would stay realized with its
vmstate registration gone. Oops. Can't happen, because
object_property_del() can't actually fail here. Pass &error_abort
to object_property_del() instead.
* spapr_phb_unrealize()
Fails and bails out when remove_drcs() fails, but other work is
already done. On failure, the device would stay realized with some
of its resources gone. Oops. remove_drcs() fails only when
chassis_from_bus()'s object_property_get_uint() fails, and it can't
here. Pass &error_abort to remove_drcs() instead.
Therefore, no unrealize method can fail before this patch.
device_set_realized()'s recursive unrealization via bus uses
object_property_set_bool(). Can't drop @errp there, so pass
&error_abort.
We similarly unrealize with object_property_set_bool() elsewhere,
always ignoring errors. Pass &error_abort instead.
Several unrealize methods no longer handle errors from other unrealize
methods: virtio_9p_device_unrealize(),
virtio_input_device_unrealize(), scsi_qdev_unrealize(), ...
Much of the deleted error handling looks wrong anyway.
One unrealize methods no longer ignore such errors:
usb_ehci_pci_exit().
Several realize methods no longer ignore errors when rolling back:
v9fs_device_realize_common(), pci_qdev_unrealize(),
spapr_phb_realize(), usb_qdev_realize(), vfio_ccw_realize(),
virtio_device_realize().
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
Message-Id: <20200505152926.18877-17-armbru@redhat.com>
2020-05-05 18:29:24 +03:00
|
|
|
xendev_class->unrealize(xendev);
|
2019-01-08 17:48:47 +03:00
|
|
|
}
|
2019-01-08 17:48:49 +03:00
|
|
|
|
2019-04-08 18:16:15 +03:00
|
|
|
/* Make sure all event channels are cleaned up */
|
|
|
|
QLIST_FOREACH_SAFE(channel, &xendev->event_channels, list, next) {
|
|
|
|
xen_device_unbind_event_channel(xendev, channel, NULL);
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:49 +03:00
|
|
|
xen_device_frontend_destroy(xendev);
|
|
|
|
xen_device_backend_destroy(xendev);
|
|
|
|
|
2019-01-08 17:48:51 +03:00
|
|
|
if (xendev->xgth) {
|
2023-01-02 00:31:37 +03:00
|
|
|
qemu_xen_gnttab_close(xendev->xgth);
|
2019-01-08 17:48:51 +03:00
|
|
|
xendev->xgth = NULL;
|
|
|
|
}
|
|
|
|
|
2019-09-13 11:21:57 +03:00
|
|
|
if (xendev->xsh) {
|
2023-01-02 14:05:16 +03:00
|
|
|
qemu_xen_xs_close(xendev->xsh);
|
2019-09-13 11:21:57 +03:00
|
|
|
xendev->xsh = NULL;
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:49 +03:00
|
|
|
g_free(xendev->name);
|
|
|
|
xendev->name = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xen_device_exit(Notifier *n, void *data)
|
|
|
|
{
|
|
|
|
XenDevice *xendev = container_of(n, XenDevice, exit);
|
|
|
|
|
qdev: Unrealize must not fail
Devices may have component devices and buses.
Device realization may fail. Realization is recursive: a device's
realize() method realizes its components, and device_set_realized()
realizes its buses (which should in turn realize the devices on that
bus, except bus_set_realized() doesn't implement that, yet).
When realization of a component or bus fails, we need to roll back:
unrealize everything we realized so far. If any of these unrealizes
failed, the device would be left in an inconsistent state. Must not
happen.
device_set_realized() lets it happen: it ignores errors in the roll
back code starting at label child_realize_fail.
Since realization is recursive, unrealization must be recursive, too.
But how could a partly failed unrealize be rolled back? We'd have to
re-realize, which can fail. This design is fundamentally broken.
device_set_realized() does not roll back at all. Instead, it keeps
unrealizing, ignoring further errors.
It can screw up even for a device with no buses: if the lone
dc->unrealize() fails, it still unregisters vmstate, and calls
listeners' unrealize() callback.
bus_set_realized() does not roll back either. Instead, it stops
unrealizing.
Fortunately, no unrealize method can fail, as we'll see below.
To fix the design error, drop parameter @errp from all the unrealize
methods.
Any unrealize method that uses @errp now needs an update. This leads
us to unrealize() methods that can fail. Merely passing it to another
unrealize method cannot cause failure, though. Here are the ones that
do other things with @errp:
* virtio_serial_device_unrealize()
Fails when qbus_set_hotplug_handler() fails, but still does all the
other work. On failure, the device would stay realized with its
resources completely gone. Oops. Can't happen, because
qbus_set_hotplug_handler() can't actually fail here. Pass
&error_abort to qbus_set_hotplug_handler() instead.
* hw/ppc/spapr_drc.c's unrealize()
Fails when object_property_del() fails, but all the other work is
already done. On failure, the device would stay realized with its
vmstate registration gone. Oops. Can't happen, because
object_property_del() can't actually fail here. Pass &error_abort
to object_property_del() instead.
* spapr_phb_unrealize()
Fails and bails out when remove_drcs() fails, but other work is
already done. On failure, the device would stay realized with some
of its resources gone. Oops. remove_drcs() fails only when
chassis_from_bus()'s object_property_get_uint() fails, and it can't
here. Pass &error_abort to remove_drcs() instead.
Therefore, no unrealize method can fail before this patch.
device_set_realized()'s recursive unrealization via bus uses
object_property_set_bool(). Can't drop @errp there, so pass
&error_abort.
We similarly unrealize with object_property_set_bool() elsewhere,
always ignoring errors. Pass &error_abort instead.
Several unrealize methods no longer handle errors from other unrealize
methods: virtio_9p_device_unrealize(),
virtio_input_device_unrealize(), scsi_qdev_unrealize(), ...
Much of the deleted error handling looks wrong anyway.
One unrealize methods no longer ignore such errors:
usb_ehci_pci_exit().
Several realize methods no longer ignore errors when rolling back:
v9fs_device_realize_common(), pci_qdev_unrealize(),
spapr_phb_realize(), usb_qdev_realize(), vfio_ccw_realize(),
virtio_device_realize().
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
Message-Id: <20200505152926.18877-17-armbru@redhat.com>
2020-05-05 18:29:24 +03:00
|
|
|
xen_device_unrealize(DEVICE(xendev));
|
2019-01-08 17:48:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static void xen_device_realize(DeviceState *dev, Error **errp)
|
|
|
|
{
|
2020-07-07 19:50:37 +03:00
|
|
|
ERRP_GUARD();
|
2019-01-08 17:48:47 +03:00
|
|
|
XenDevice *xendev = XEN_DEVICE(dev);
|
|
|
|
XenDeviceClass *xendev_class = XEN_DEVICE_GET_CLASS(xendev);
|
2019-01-08 17:48:49 +03:00
|
|
|
XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev)));
|
2019-01-08 17:48:47 +03:00
|
|
|
const char *type = object_get_typename(OBJECT(xendev));
|
|
|
|
|
2019-01-08 17:48:49 +03:00
|
|
|
if (xendev->frontend_id == DOMID_INVALID) {
|
|
|
|
xendev->frontend_id = xen_domid;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xendev->frontend_id >= DOMID_FIRST_RESERVED) {
|
|
|
|
error_setg(errp, "invalid frontend-id");
|
|
|
|
goto unrealize;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!xendev_class->get_name) {
|
|
|
|
error_setg(errp, "get_name method not implemented");
|
|
|
|
goto unrealize;
|
|
|
|
}
|
|
|
|
|
2020-07-07 19:50:37 +03:00
|
|
|
xendev->name = xendev_class->get_name(xendev, errp);
|
|
|
|
if (*errp) {
|
|
|
|
error_prepend(errp, "failed to get device name: ");
|
2019-01-08 17:48:49 +03:00
|
|
|
goto unrealize;
|
|
|
|
}
|
|
|
|
|
|
|
|
trace_xen_device_realize(type, xendev->name);
|
|
|
|
|
2023-01-02 14:05:16 +03:00
|
|
|
xendev->xsh = qemu_xen_xs_open();
|
2019-09-13 11:21:57 +03:00
|
|
|
if (!xendev->xsh) {
|
|
|
|
error_setg_errno(errp, errno, "failed xs_open");
|
|
|
|
goto unrealize;
|
|
|
|
}
|
|
|
|
|
2023-01-02 00:31:37 +03:00
|
|
|
xendev->xgth = qemu_xen_gnttab_open();
|
2019-01-08 17:48:51 +03:00
|
|
|
if (!xendev->xgth) {
|
|
|
|
error_setg_errno(errp, errno, "failed xengnttab_open");
|
|
|
|
goto unrealize;
|
|
|
|
}
|
|
|
|
|
2020-07-07 19:50:37 +03:00
|
|
|
xen_device_backend_create(xendev, errp);
|
|
|
|
if (*errp) {
|
2019-01-08 17:48:49 +03:00
|
|
|
goto unrealize;
|
|
|
|
}
|
|
|
|
|
2020-07-07 19:50:37 +03:00
|
|
|
xen_device_frontend_create(xendev, errp);
|
|
|
|
if (*errp) {
|
2019-01-08 17:48:49 +03:00
|
|
|
goto unrealize;
|
|
|
|
}
|
2019-01-08 17:48:47 +03:00
|
|
|
|
2019-01-08 17:48:49 +03:00
|
|
|
xen_device_backend_printf(xendev, "frontend", "%s",
|
|
|
|
xendev->frontend_path);
|
|
|
|
xen_device_backend_printf(xendev, "frontend-id", "%u",
|
|
|
|
xendev->frontend_id);
|
|
|
|
xen_device_backend_printf(xendev, "hotplug-status", "connected");
|
|
|
|
|
2019-01-08 17:48:59 +03:00
|
|
|
xen_device_backend_set_online(xendev, true);
|
2019-01-08 17:48:49 +03:00
|
|
|
xen_device_backend_set_state(xendev, XenbusStateInitWait);
|
|
|
|
|
2019-09-18 14:57:44 +03:00
|
|
|
if (!xen_device_frontend_exists(xendev)) {
|
|
|
|
xen_device_frontend_printf(xendev, "backend", "%s",
|
|
|
|
xendev->backend_path);
|
|
|
|
xen_device_frontend_printf(xendev, "backend-id", "%u",
|
|
|
|
xenbus->backend_id);
|
2019-01-08 17:48:49 +03:00
|
|
|
|
2019-09-18 14:57:44 +03:00
|
|
|
xen_device_frontend_set_state(xendev, XenbusStateInitialising, true);
|
|
|
|
}
|
2019-01-08 17:48:49 +03:00
|
|
|
|
2023-01-30 17:35:28 +03:00
|
|
|
if (xendev_class->realize) {
|
|
|
|
xendev_class->realize(xendev, errp);
|
|
|
|
if (*errp) {
|
|
|
|
goto unrealize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:49 +03:00
|
|
|
xendev->exit.notify = xen_device_exit;
|
|
|
|
qemu_add_exit_notifier(&xendev->exit);
|
2019-01-08 17:48:47 +03:00
|
|
|
return;
|
|
|
|
|
|
|
|
unrealize:
|
qdev: Unrealize must not fail
Devices may have component devices and buses.
Device realization may fail. Realization is recursive: a device's
realize() method realizes its components, and device_set_realized()
realizes its buses (which should in turn realize the devices on that
bus, except bus_set_realized() doesn't implement that, yet).
When realization of a component or bus fails, we need to roll back:
unrealize everything we realized so far. If any of these unrealizes
failed, the device would be left in an inconsistent state. Must not
happen.
device_set_realized() lets it happen: it ignores errors in the roll
back code starting at label child_realize_fail.
Since realization is recursive, unrealization must be recursive, too.
But how could a partly failed unrealize be rolled back? We'd have to
re-realize, which can fail. This design is fundamentally broken.
device_set_realized() does not roll back at all. Instead, it keeps
unrealizing, ignoring further errors.
It can screw up even for a device with no buses: if the lone
dc->unrealize() fails, it still unregisters vmstate, and calls
listeners' unrealize() callback.
bus_set_realized() does not roll back either. Instead, it stops
unrealizing.
Fortunately, no unrealize method can fail, as we'll see below.
To fix the design error, drop parameter @errp from all the unrealize
methods.
Any unrealize method that uses @errp now needs an update. This leads
us to unrealize() methods that can fail. Merely passing it to another
unrealize method cannot cause failure, though. Here are the ones that
do other things with @errp:
* virtio_serial_device_unrealize()
Fails when qbus_set_hotplug_handler() fails, but still does all the
other work. On failure, the device would stay realized with its
resources completely gone. Oops. Can't happen, because
qbus_set_hotplug_handler() can't actually fail here. Pass
&error_abort to qbus_set_hotplug_handler() instead.
* hw/ppc/spapr_drc.c's unrealize()
Fails when object_property_del() fails, but all the other work is
already done. On failure, the device would stay realized with its
vmstate registration gone. Oops. Can't happen, because
object_property_del() can't actually fail here. Pass &error_abort
to object_property_del() instead.
* spapr_phb_unrealize()
Fails and bails out when remove_drcs() fails, but other work is
already done. On failure, the device would stay realized with some
of its resources gone. Oops. remove_drcs() fails only when
chassis_from_bus()'s object_property_get_uint() fails, and it can't
here. Pass &error_abort to remove_drcs() instead.
Therefore, no unrealize method can fail before this patch.
device_set_realized()'s recursive unrealization via bus uses
object_property_set_bool(). Can't drop @errp there, so pass
&error_abort.
We similarly unrealize with object_property_set_bool() elsewhere,
always ignoring errors. Pass &error_abort instead.
Several unrealize methods no longer handle errors from other unrealize
methods: virtio_9p_device_unrealize(),
virtio_input_device_unrealize(), scsi_qdev_unrealize(), ...
Much of the deleted error handling looks wrong anyway.
One unrealize methods no longer ignore such errors:
usb_ehci_pci_exit().
Several realize methods no longer ignore errors when rolling back:
v9fs_device_realize_common(), pci_qdev_unrealize(),
spapr_phb_realize(), usb_qdev_realize(), vfio_ccw_realize(),
virtio_device_realize().
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
Message-Id: <20200505152926.18877-17-armbru@redhat.com>
2020-05-05 18:29:24 +03:00
|
|
|
xen_device_unrealize(dev);
|
2019-01-08 17:48:47 +03:00
|
|
|
}
|
|
|
|
|
2019-01-08 17:48:49 +03:00
|
|
|
static Property xen_device_props[] = {
|
|
|
|
DEFINE_PROP_UINT16("frontend-id", XenDevice, frontend_id,
|
|
|
|
DOMID_INVALID),
|
|
|
|
DEFINE_PROP_END_OF_LIST()
|
|
|
|
};
|
|
|
|
|
2019-01-08 17:48:47 +03:00
|
|
|
static void xen_device_class_init(ObjectClass *class, void *data)
|
|
|
|
{
|
|
|
|
DeviceClass *dev_class = DEVICE_CLASS(class);
|
|
|
|
|
|
|
|
dev_class->realize = xen_device_realize;
|
|
|
|
dev_class->unrealize = xen_device_unrealize;
|
2020-01-10 18:30:32 +03:00
|
|
|
device_class_set_props(dev_class, xen_device_props);
|
2019-01-08 17:48:47 +03:00
|
|
|
dev_class->bus_type = TYPE_XEN_BUS;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const TypeInfo xen_device_type_info = {
|
|
|
|
.name = TYPE_XEN_DEVICE,
|
|
|
|
.parent = TYPE_DEVICE,
|
|
|
|
.instance_size = sizeof(XenDevice),
|
|
|
|
.abstract = true,
|
|
|
|
.class_size = sizeof(XenDeviceClass),
|
|
|
|
.class_init = xen_device_class_init,
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef struct XenBridge {
|
|
|
|
SysBusDevice busdev;
|
|
|
|
} XenBridge;
|
|
|
|
|
|
|
|
#define TYPE_XEN_BRIDGE "xen-bridge"
|
|
|
|
|
|
|
|
static const TypeInfo xen_bridge_type_info = {
|
|
|
|
.name = TYPE_XEN_BRIDGE,
|
|
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
|
|
.instance_size = sizeof(XenBridge),
|
|
|
|
};
|
|
|
|
|
|
|
|
static void xen_register_types(void)
|
|
|
|
{
|
|
|
|
type_register_static(&xen_bridge_type_info);
|
|
|
|
type_register_static(&xen_bus_type_info);
|
|
|
|
type_register_static(&xen_device_type_info);
|
|
|
|
}
|
|
|
|
|
|
|
|
type_init(xen_register_types)
|
|
|
|
|
2023-10-17 19:53:58 +03:00
|
|
|
void xen_bus_init(void)
|
2019-01-08 17:48:47 +03:00
|
|
|
{
|
qdev: Convert uses of qdev_create() with Coccinelle
This is the transformation explained in the commit before previous.
Takes care of just one pattern that needs conversion. More to come in
this series.
Coccinelle script:
@ depends on !(file in "hw/arm/highbank.c")@
expression bus, type_name, dev, expr;
@@
- dev = qdev_create(bus, type_name);
+ dev = qdev_new(type_name);
... when != dev = expr
- qdev_init_nofail(dev);
+ qdev_realize_and_unref(dev, bus, &error_fatal);
@@
expression bus, type_name, dev, expr;
identifier DOWN;
@@
- dev = DOWN(qdev_create(bus, type_name));
+ dev = DOWN(qdev_new(type_name));
... when != dev = expr
- qdev_init_nofail(DEVICE(dev));
+ qdev_realize_and_unref(DEVICE(dev), bus, &error_fatal);
@@
expression bus, type_name, expr;
identifier dev;
@@
- DeviceState *dev = qdev_create(bus, type_name);
+ DeviceState *dev = qdev_new(type_name);
... when != dev = expr
- qdev_init_nofail(dev);
+ qdev_realize_and_unref(dev, bus, &error_fatal);
@@
expression bus, type_name, dev, expr, errp;
symbol true;
@@
- dev = qdev_create(bus, type_name);
+ dev = qdev_new(type_name);
... when != dev = expr
- object_property_set_bool(OBJECT(dev), true, "realized", errp);
+ qdev_realize_and_unref(dev, bus, errp);
@@
expression bus, type_name, expr, errp;
identifier dev;
symbol true;
@@
- DeviceState *dev = qdev_create(bus, type_name);
+ DeviceState *dev = qdev_new(type_name);
... when != dev = expr
- object_property_set_bool(OBJECT(dev), true, "realized", errp);
+ qdev_realize_and_unref(dev, bus, errp);
The first rule exempts hw/arm/highbank.c, because it matches along two
control flow paths there, with different @type_name. Covered by the
next commit's manual conversions.
Missing #include "qapi/error.h" added manually.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
Message-Id: <20200610053247.1583243-10-armbru@redhat.com>
[Conflicts in hw/misc/empty_slot.c and hw/sparc/leon3.c resolved]
2020-06-10 08:31:58 +03:00
|
|
|
DeviceState *dev = qdev_new(TYPE_XEN_BRIDGE);
|
2021-09-23 15:11:52 +03:00
|
|
|
BusState *bus = qbus_new(TYPE_XEN_BUS, dev, NULL);
|
2019-01-08 17:48:47 +03:00
|
|
|
|
sysbus: Convert to sysbus_realize() etc. with Coccinelle
Convert from qdev_realize(), qdev_realize_and_unref() with null @bus
argument to sysbus_realize(), sysbus_realize_and_unref().
Coccinelle script:
@@
expression dev, errp;
@@
- qdev_realize(DEVICE(dev), NULL, errp);
+ sysbus_realize(SYS_BUS_DEVICE(dev), errp);
@@
expression sysbus_dev, dev, errp;
@@
+ sysbus_dev = SYS_BUS_DEVICE(dev);
- qdev_realize_and_unref(dev, NULL, errp);
+ sysbus_realize_and_unref(sysbus_dev, errp);
- sysbus_dev = SYS_BUS_DEVICE(dev);
@@
expression sysbus_dev, dev, errp;
expression expr;
@@
sysbus_dev = SYS_BUS_DEVICE(dev);
... when != dev = expr;
- qdev_realize_and_unref(dev, NULL, errp);
+ sysbus_realize_and_unref(sysbus_dev, errp);
@@
expression dev, errp;
@@
- qdev_realize_and_unref(DEVICE(dev), NULL, errp);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), errp);
@@
expression dev, errp;
@@
- qdev_realize_and_unref(dev, NULL, errp);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), errp);
Whitespace changes minimized manually.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Acked-by: Alistair Francis <alistair.francis@wdc.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
Message-Id: <20200610053247.1583243-46-armbru@redhat.com>
[Conflicts in hw/misc/empty_slot.c and hw/sparc/leon3.c resolved]
2020-06-10 08:32:34 +03:00
|
|
|
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
|
2020-06-30 12:03:38 +03:00
|
|
|
qbus_set_bus_hotplug_handler(bus);
|
2023-10-17 19:53:58 +03:00
|
|
|
|
2023-10-17 19:53:58 +03:00
|
|
|
qemu_create_nic_bus_devices(bus, TYPE_XEN_DEVICE, "xen-net-device",
|
|
|
|
"xen", "xen-net-device");
|
2019-01-08 17:48:47 +03:00
|
|
|
}
|