|  | /* | 
|  | * 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" | 
|  | #include "qemu/main-loop.h" | 
|  | #include "qemu/module.h" | 
|  | #include "qemu/uuid.h" | 
|  | #include "hw/qdev-properties.h" | 
|  | #include "hw/sysbus.h" | 
|  | #include "hw/xen/xen.h" | 
|  | #include "hw/xen/xen-backend.h" | 
|  | #include "hw/xen/xen-legacy-backend.h" /* xen_be_init() */ | 
|  | #include "hw/xen/xen-bus.h" | 
|  | #include "hw/xen/xen-bus-helper.h" | 
|  | #include "monitor/monitor.h" | 
|  | #include "qapi/error.h" | 
|  | #include "qapi/qmp/qdict.h" | 
|  | #include "sysemu/sysemu.h" | 
|  | #include "net/net.h" | 
|  | #include "trace.h" | 
|  |  | 
|  | 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); | 
|  | } | 
|  |  | 
|  | static void xen_device_unplug(XenDevice *xendev, Error **errp) | 
|  | { | 
|  | ERRP_GUARD(); | 
|  | 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: | 
|  | tid = qemu_xen_xs_transaction_start(xenbus->xsh); | 
|  | if (tid == XBT_NULL) { | 
|  | error_setg_errno(errp, errno, "failed xs_transaction_start"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | xs_node_printf(xenbus->xsh, tid, xendev->backend_path, "online", | 
|  | errp, "%u", 0); | 
|  | if (*errp) { | 
|  | goto abort; | 
|  | } | 
|  |  | 
|  | xs_node_printf(xenbus->xsh, tid, xendev->backend_path, "state", | 
|  | errp, "%u", XenbusStateClosing); | 
|  | if (*errp) { | 
|  | goto abort; | 
|  | } | 
|  |  | 
|  | if (!qemu_xen_xs_transaction_end(xenbus->xsh, tid, false)) { | 
|  | 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. | 
|  | */ | 
|  | qemu_xen_xs_transaction_end(xenbus->xsh, tid, true); | 
|  | } | 
|  |  | 
|  | 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)); | 
|  | } | 
|  |  | 
|  | static void xen_bus_backend_create(XenBus *xenbus, const char *type, | 
|  | const char *name, char *path, | 
|  | Error **errp) | 
|  | { | 
|  | ERRP_GUARD(); | 
|  | xs_transaction_t tid; | 
|  | char **key; | 
|  | QDict *opts; | 
|  | unsigned int i, n; | 
|  |  | 
|  | trace_xen_bus_backend_create(type, path); | 
|  |  | 
|  | again: | 
|  | tid = qemu_xen_xs_transaction_start(xenbus->xsh); | 
|  | if (tid == XBT_NULL) { | 
|  | error_setg(errp, "failed xs_transaction_start"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | key = qemu_xen_xs_directory(xenbus->xsh, tid, path, &n); | 
|  | if (!key) { | 
|  | if (!qemu_xen_xs_transaction_end(xenbus->xsh, tid, true)) { | 
|  | 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); | 
|  |  | 
|  | if (!qemu_xen_xs_transaction_end(xenbus->xsh, tid, false)) { | 
|  | qobject_unref(opts); | 
|  |  | 
|  | if (errno == EAGAIN) { | 
|  | goto again; | 
|  | } | 
|  |  | 
|  | error_setg_errno(errp, errno, "failed xs_transaction_end"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | xen_backend_device_create(xenbus, type, name, opts, errp); | 
|  | qobject_unref(opts); | 
|  |  | 
|  | if (*errp) { | 
|  | error_prepend(errp, "failed to create '%s' device '%s': ", type, name); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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); | 
|  |  | 
|  | backend = qemu_xen_xs_directory(xenbus->xsh, XBT_NULL, domain_path, &n); | 
|  | if (!backend) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < n; i++) { | 
|  | char *backend_path = g_strdup_printf("%s/%s", domain_path, | 
|  | backend[i]); | 
|  | enum xenbus_state state; | 
|  | unsigned int online; | 
|  |  | 
|  | if (xs_node_scanf(xenbus->xsh, XBT_NULL, backend_path, "state", | 
|  | NULL, "%u", &state) != 1) | 
|  | state = XenbusStateUnknown; | 
|  |  | 
|  | if (xs_node_scanf(xenbus->xsh, XBT_NULL, backend_path, "online", | 
|  | NULL, "%u", &online) != 1) | 
|  | online = 0; | 
|  |  | 
|  | if (online && state == XenbusStateInitialising && | 
|  | !xen_backend_exists(type, backend[i])) { | 
|  | 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); | 
|  | } | 
|  |  | 
|  | static void xen_bus_enumerate(XenBus *xenbus) | 
|  | { | 
|  | char **type; | 
|  | unsigned int i, n; | 
|  |  | 
|  | trace_xen_bus_enumerate(); | 
|  |  | 
|  | type = qemu_xen_xs_directory(xenbus->xsh, XBT_NULL, "backend", &n); | 
|  | if (!type) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < n; i++) { | 
|  | xen_bus_type_enumerate(xenbus, type[i]); | 
|  | } | 
|  |  | 
|  | free(type); | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void xen_bus_backend_changed(void *opaque, const char *path) | 
|  | { | 
|  | XenBus *xenbus = opaque; | 
|  |  | 
|  | xen_bus_enumerate(xenbus); | 
|  | xen_bus_cleanup(xenbus); | 
|  | } | 
|  |  | 
|  | static void xen_bus_unrealize(BusState *bus) | 
|  | { | 
|  | XenBus *xenbus = XEN_BUS(bus); | 
|  |  | 
|  | trace_xen_bus_unrealize(); | 
|  |  | 
|  | if (xenbus->backend_watch) { | 
|  | unsigned int i; | 
|  |  | 
|  | for (i = 0; i < xenbus->backend_types; i++) { | 
|  | if (xenbus->backend_watch[i]) { | 
|  | xs_node_unwatch(xenbus->xsh, xenbus->backend_watch[i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | g_free(xenbus->backend_watch); | 
|  | xenbus->backend_watch = NULL; | 
|  | } | 
|  |  | 
|  | if (xenbus->xsh) { | 
|  | qemu_xen_xs_close(xenbus->xsh); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void xen_bus_realize(BusState *bus, Error **errp) | 
|  | { | 
|  | char *key = g_strdup_printf("%u", xen_domid); | 
|  | XenBus *xenbus = XEN_BUS(bus); | 
|  | unsigned int domid; | 
|  | const char **type; | 
|  | unsigned int i; | 
|  | Error *local_err = NULL; | 
|  |  | 
|  | trace_xen_bus_realize(); | 
|  |  | 
|  | xenbus->xsh = qemu_xen_xs_open(); | 
|  | if (!xenbus->xsh) { | 
|  | error_setg_errno(errp, errno, "failed xs_open"); | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | /* Initialize legacy backend core & drivers */ | 
|  | xen_be_init(); | 
|  |  | 
|  | 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 */ | 
|  | } | 
|  |  | 
|  | module_call_init(MODULE_INIT_XEN_BACKEND); | 
|  |  | 
|  | type = xen_backend_get_types(&xenbus->backend_types); | 
|  | xenbus->backend_watch = g_new(struct qemu_xs_watch *, | 
|  | xenbus->backend_types); | 
|  |  | 
|  | for (i = 0; i < xenbus->backend_types; i++) { | 
|  | char *node = g_strdup_printf("backend/%s", type[i]); | 
|  |  | 
|  | xenbus->backend_watch[i] = | 
|  | xs_node_watch(xenbus->xsh, node, key, xen_bus_backend_changed, | 
|  | xenbus, &local_err); | 
|  | 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); | 
|  | } | 
|  |  | 
|  | g_free(type); | 
|  | g_free(key); | 
|  | return; | 
|  |  | 
|  | fail: | 
|  | xen_bus_unrealize(bus); | 
|  | g_free(key); | 
|  | } | 
|  |  | 
|  | static void xen_bus_unplug_request(HotplugHandler *hotplug, | 
|  | DeviceState *dev, | 
|  | Error **errp) | 
|  | { | 
|  | XenDevice *xendev = XEN_DEVICE(dev); | 
|  |  | 
|  | xen_device_unplug(xendev, errp); | 
|  | } | 
|  |  | 
|  | static void xen_bus_class_init(ObjectClass *class, void *data) | 
|  | { | 
|  | BusClass *bus_class = BUS_CLASS(class); | 
|  | HotplugHandlerClass *hotplug_class = HOTPLUG_HANDLER_CLASS(class); | 
|  |  | 
|  | bus_class->print_dev = xen_bus_print_dev; | 
|  | bus_class->get_dev_path = xen_bus_get_dev_path; | 
|  | bus_class->realize = xen_bus_realize; | 
|  | bus_class->unrealize = xen_bus_unrealize; | 
|  |  | 
|  | hotplug_class->unplug_request = xen_bus_unplug_request; | 
|  | } | 
|  |  | 
|  | 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 }, | 
|  | { } | 
|  | }, | 
|  | }; | 
|  |  | 
|  | void xen_device_backend_printf(XenDevice *xendev, const char *key, | 
|  | const char *fmt, ...) | 
|  | { | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | G_GNUC_SCANF(3, 4) | 
|  | 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) | 
|  | { | 
|  | 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); | 
|  | } | 
|  |  | 
|  | enum xenbus_state xen_device_backend_get_state(XenDevice *xendev) | 
|  | { | 
|  | return xendev->backend_state; | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Tell from the state whether the frontend is likely alive, | 
|  | * i.e. it will react to a change of state of the backend. | 
|  | */ | 
|  | static bool xen_device_frontend_is_active(XenDevice *xendev) | 
|  | { | 
|  | switch (xendev->frontend_state) { | 
|  | case XenbusStateInitWait: | 
|  | case XenbusStateInitialised: | 
|  | case XenbusStateConnected: | 
|  | case XenbusStateClosing: | 
|  | return true; | 
|  | default: | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void xen_device_backend_changed(void *opaque, const char *path) | 
|  | { | 
|  | 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 | 
|  | * state to Closing, but there is no active frontend then set the | 
|  | * backend state to Closed. | 
|  | */ | 
|  | if (state == XenbusStateClosing && | 
|  | !xen_device_frontend_is_active(xendev)) { | 
|  | xen_device_backend_set_state(xendev, XenbusStateClosed); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * If a backend is still 'online' then we should leave it alone but, | 
|  | * 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(). | 
|  | */ | 
|  | if (!online && | 
|  | (state == XenbusStateClosed ||  state == XenbusStateInitialising || | 
|  | state == XenbusStateInitWait || state == XenbusStateUnknown) && | 
|  | !xendev->inactive) { | 
|  | XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev))); | 
|  |  | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void xen_device_backend_create(XenDevice *xendev, Error **errp) | 
|  | { | 
|  | ERRP_GUARD(); | 
|  | XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev))); | 
|  |  | 
|  | xendev->backend_path = xen_device_get_backend_path(xendev); | 
|  |  | 
|  | g_assert(xenbus->xsh); | 
|  |  | 
|  | xs_node_create(xenbus->xsh, XBT_NULL, xendev->backend_path, | 
|  | xenbus->backend_id, xendev->frontend_id, XS_PERM_READ, errp); | 
|  | if (*errp) { | 
|  | error_prepend(errp, "failed to create backend: "); | 
|  | return; | 
|  | } | 
|  |  | 
|  | xendev->backend_state_watch = | 
|  | xs_node_watch(xendev->xsh, xendev->backend_path, | 
|  | "state", xen_device_backend_changed, xendev, | 
|  | errp); | 
|  | if (*errp) { | 
|  | error_prepend(errp, "failed to watch backend state: "); | 
|  | return; | 
|  | } | 
|  |  | 
|  | xendev->backend_online_watch = | 
|  | xs_node_watch(xendev->xsh, xendev->backend_path, | 
|  | "online", xen_device_backend_changed, xendev, | 
|  | errp); | 
|  | if (*errp) { | 
|  | error_prepend(errp, "failed to watch backend online: "); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void xen_device_backend_destroy(XenDevice *xendev) | 
|  | { | 
|  | XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev))); | 
|  | Error *local_err = NULL; | 
|  |  | 
|  | if (xendev->backend_online_watch) { | 
|  | xs_node_unwatch(xendev->xsh, xendev->backend_online_watch); | 
|  | xendev->backend_online_watch = NULL; | 
|  | } | 
|  |  | 
|  | if (xendev->backend_state_watch) { | 
|  | xs_node_unwatch(xendev->xsh, xendev->backend_state_watch); | 
|  | xendev->backend_state_watch = NULL; | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | void xen_device_frontend_printf(XenDevice *xendev, const char *key, | 
|  | const char *fmt, ...) | 
|  | { | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | int xen_device_frontend_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->frontend_path, key, | 
|  | NULL, fmt, ap); | 
|  | va_end(ap); | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static void xen_device_frontend_set_state(XenDevice *xendev, | 
|  | enum xenbus_state state, | 
|  | bool publish) | 
|  | { | 
|  | 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; | 
|  | if (publish) { | 
|  | xen_device_frontend_printf(xendev, "state", "%u", state); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void xen_device_frontend_changed(void *opaque, const char *path) | 
|  | { | 
|  | 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; | 
|  | } | 
|  |  | 
|  | xen_device_frontend_set_state(xendev, state, false); | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | 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: "); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static bool xen_device_frontend_exists(XenDevice *xendev) | 
|  | { | 
|  | enum xenbus_state state; | 
|  |  | 
|  | return (xen_device_frontend_scanf(xendev, "state", "%u", &state) == 1); | 
|  | } | 
|  |  | 
|  | static void xen_device_frontend_create(XenDevice *xendev, Error **errp) | 
|  | { | 
|  | ERRP_GUARD(); | 
|  | XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev))); | 
|  | XenDeviceClass *xendev_class = XEN_DEVICE_GET_CLASS(xendev); | 
|  |  | 
|  | 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); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * The frontend area may have already been created by a legacy | 
|  | * toolstack. | 
|  | */ | 
|  | if (!xen_device_frontend_exists(xendev)) { | 
|  | g_assert(xenbus->xsh); | 
|  |  | 
|  | xs_node_create(xenbus->xsh, XBT_NULL, xendev->frontend_path, | 
|  | xendev->frontend_id, xenbus->backend_id, | 
|  | XS_PERM_READ | XS_PERM_WRITE, errp); | 
|  | if (*errp) { | 
|  | error_prepend(errp, "failed to create frontend: "); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | xendev->frontend_state_watch = | 
|  | xs_node_watch(xendev->xsh, xendev->frontend_path, "state", | 
|  | xen_device_frontend_changed, xendev, errp); | 
|  | if (*errp) { | 
|  | error_prepend(errp, "failed to watch frontend state: "); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void xen_device_frontend_destroy(XenDevice *xendev) | 
|  | { | 
|  | XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev))); | 
|  | Error *local_err = NULL; | 
|  |  | 
|  | if (xendev->frontend_state_watch) { | 
|  | xs_node_unwatch(xendev->xsh, xendev->frontend_state_watch); | 
|  | xendev->frontend_state_watch = NULL; | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | void xen_device_set_max_grant_refs(XenDevice *xendev, unsigned int nr_refs, | 
|  | Error **errp) | 
|  | { | 
|  | if (qemu_xen_gnttab_set_max_grants(xendev->xgth, nr_refs)) { | 
|  | 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) | 
|  | { | 
|  | void *map = qemu_xen_gnttab_map_refs(xendev->xgth, nr_refs, | 
|  | xendev->frontend_id, refs, prot); | 
|  |  | 
|  | if (!map) { | 
|  | error_setg_errno(errp, errno, | 
|  | "xengnttab_map_domain_grant_refs failed"); | 
|  | } | 
|  |  | 
|  | return map; | 
|  | } | 
|  |  | 
|  | void xen_device_unmap_grant_refs(XenDevice *xendev, void *map, uint32_t *refs, | 
|  | unsigned int nr_refs, Error **errp) | 
|  | { | 
|  | if (qemu_xen_gnttab_unmap(xendev->xgth, map, refs, nr_refs)) { | 
|  | 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) | 
|  | { | 
|  | qemu_xen_gnttab_grant_copy(xendev->xgth, to_domain, xendev->frontend_id, | 
|  | (XenGrantCopySegment *)segs, nr_segs, errp); | 
|  | } | 
|  |  | 
|  | struct XenEventChannel { | 
|  | QLIST_ENTRY(XenEventChannel) list; | 
|  | AioContext *ctx; | 
|  | xenevtchn_handle *xeh; | 
|  | evtchn_port_t local_port; | 
|  | XenEventHandler handler; | 
|  | void *opaque; | 
|  | }; | 
|  |  | 
|  | static bool xen_device_poll(void *opaque) | 
|  | { | 
|  | XenEventChannel *channel = opaque; | 
|  |  | 
|  | return channel->handler(channel->opaque); | 
|  | } | 
|  |  | 
|  | static void xen_device_event(void *opaque) | 
|  | { | 
|  | XenEventChannel *channel = opaque; | 
|  | unsigned long port = qemu_xen_evtchn_pending(channel->xeh); | 
|  |  | 
|  | if (port == channel->local_port) { | 
|  | xen_device_poll(channel); | 
|  |  | 
|  | qemu_xen_evtchn_unmask(channel->xeh, port); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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_set_fd_handler(channel->ctx, qemu_xen_evtchn_fd(channel->xeh), | 
|  | NULL, NULL, NULL, NULL, NULL); | 
|  |  | 
|  | channel->ctx = ctx; | 
|  | if (ctx) { | 
|  | aio_set_fd_handler(channel->ctx, qemu_xen_evtchn_fd(channel->xeh), | 
|  | xen_device_event, NULL, xen_device_poll, NULL, | 
|  | channel); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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; | 
|  |  | 
|  | channel->xeh = qemu_xen_evtchn_open(); | 
|  | if (!channel->xeh) { | 
|  | error_setg_errno(errp, errno, "failed xenevtchn_open"); | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | local_port = qemu_xen_evtchn_bind_interdomain(channel->xeh, | 
|  | xendev->frontend_id, | 
|  | port); | 
|  | if (local_port < 0) { | 
|  | error_setg_errno(errp, errno, "xenevtchn_bind_interdomain failed"); | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | channel->local_port = local_port; | 
|  | channel->handler = handler; | 
|  | channel->opaque = opaque; | 
|  |  | 
|  | /* Only reason for failure is a NULL channel */ | 
|  | xen_device_set_event_channel_context(xendev, channel, | 
|  | qemu_get_aio_context(), | 
|  | &error_abort); | 
|  |  | 
|  | QLIST_INSERT_HEAD(&xendev->event_channels, channel, list); | 
|  |  | 
|  | return channel; | 
|  |  | 
|  | fail: | 
|  | if (channel->xeh) { | 
|  | qemu_xen_evtchn_close(channel->xeh); | 
|  | } | 
|  |  | 
|  | g_free(channel); | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | void xen_device_notify_event_channel(XenDevice *xendev, | 
|  | XenEventChannel *channel, | 
|  | Error **errp) | 
|  | { | 
|  | if (!channel) { | 
|  | error_setg(errp, "bad channel"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (qemu_xen_evtchn_notify(channel->xeh, channel->local_port) < 0) { | 
|  | error_setg_errno(errp, errno, "xenevtchn_notify failed"); | 
|  | } | 
|  | } | 
|  |  | 
|  | unsigned int xen_event_channel_get_local_port(XenEventChannel *channel) | 
|  | { | 
|  | return channel->local_port; | 
|  | } | 
|  |  | 
|  | void xen_device_unbind_event_channel(XenDevice *xendev, | 
|  | XenEventChannel *channel, | 
|  | Error **errp) | 
|  | { | 
|  | if (!channel) { | 
|  | error_setg(errp, "bad channel"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | QLIST_REMOVE(channel, list); | 
|  |  | 
|  | if (channel->ctx) { | 
|  | aio_set_fd_handler(channel->ctx, qemu_xen_evtchn_fd(channel->xeh), | 
|  | NULL, NULL, NULL, NULL, NULL); | 
|  | } | 
|  |  | 
|  | if (qemu_xen_evtchn_unbind(channel->xeh, channel->local_port) < 0) { | 
|  | error_setg_errno(errp, errno, "xenevtchn_unbind failed"); | 
|  | } | 
|  |  | 
|  | qemu_xen_evtchn_close(channel->xeh); | 
|  | g_free(channel); | 
|  | } | 
|  |  | 
|  | static void xen_device_unrealize(DeviceState *dev) | 
|  | { | 
|  | XenDevice *xendev = XEN_DEVICE(dev); | 
|  | XenDeviceClass *xendev_class = XEN_DEVICE_GET_CLASS(xendev); | 
|  | const char *type = object_get_typename(OBJECT(xendev)); | 
|  | XenEventChannel *channel, *next; | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | if (xendev_class->unrealize) { | 
|  | xendev_class->unrealize(xendev); | 
|  | } | 
|  |  | 
|  | /* 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); | 
|  | } | 
|  |  | 
|  | xen_device_frontend_destroy(xendev); | 
|  | xen_device_backend_destroy(xendev); | 
|  |  | 
|  | if (xendev->xgth) { | 
|  | qemu_xen_gnttab_close(xendev->xgth); | 
|  | xendev->xgth = NULL; | 
|  | } | 
|  |  | 
|  | if (xendev->xsh) { | 
|  | qemu_xen_xs_close(xendev->xsh); | 
|  | xendev->xsh = NULL; | 
|  | } | 
|  |  | 
|  | g_free(xendev->name); | 
|  | xendev->name = NULL; | 
|  | } | 
|  |  | 
|  | static void xen_device_exit(Notifier *n, void *data) | 
|  | { | 
|  | XenDevice *xendev = container_of(n, XenDevice, exit); | 
|  |  | 
|  | xen_device_unrealize(DEVICE(xendev)); | 
|  | } | 
|  |  | 
|  | static void xen_device_realize(DeviceState *dev, Error **errp) | 
|  | { | 
|  | ERRP_GUARD(); | 
|  | XenDevice *xendev = XEN_DEVICE(dev); | 
|  | XenDeviceClass *xendev_class = XEN_DEVICE_GET_CLASS(xendev); | 
|  | XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev))); | 
|  | const char *type = object_get_typename(OBJECT(xendev)); | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | xendev->name = xendev_class->get_name(xendev, errp); | 
|  | if (*errp) { | 
|  | error_prepend(errp, "failed to get device name: "); | 
|  | goto unrealize; | 
|  | } | 
|  |  | 
|  | trace_xen_device_realize(type, xendev->name); | 
|  |  | 
|  | xendev->xsh = qemu_xen_xs_open(); | 
|  | if (!xendev->xsh) { | 
|  | error_setg_errno(errp, errno, "failed xs_open"); | 
|  | goto unrealize; | 
|  | } | 
|  |  | 
|  | xendev->xgth = qemu_xen_gnttab_open(); | 
|  | if (!xendev->xgth) { | 
|  | error_setg_errno(errp, errno, "failed xengnttab_open"); | 
|  | goto unrealize; | 
|  | } | 
|  |  | 
|  | xen_device_backend_create(xendev, errp); | 
|  | if (*errp) { | 
|  | goto unrealize; | 
|  | } | 
|  |  | 
|  | xen_device_frontend_create(xendev, errp); | 
|  | if (*errp) { | 
|  | goto unrealize; | 
|  | } | 
|  |  | 
|  | 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"); | 
|  |  | 
|  | xen_device_backend_set_online(xendev, true); | 
|  | xen_device_backend_set_state(xendev, XenbusStateInitWait); | 
|  |  | 
|  | 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); | 
|  |  | 
|  | xen_device_frontend_set_state(xendev, XenbusStateInitialising, true); | 
|  | } | 
|  |  | 
|  | if (xendev_class->realize) { | 
|  | xendev_class->realize(xendev, errp); | 
|  | if (*errp) { | 
|  | goto unrealize; | 
|  | } | 
|  | } | 
|  |  | 
|  | xendev->exit.notify = xen_device_exit; | 
|  | qemu_add_exit_notifier(&xendev->exit); | 
|  | return; | 
|  |  | 
|  | unrealize: | 
|  | xen_device_unrealize(dev); | 
|  | } | 
|  |  | 
|  | static Property xen_device_props[] = { | 
|  | DEFINE_PROP_UINT16("frontend-id", XenDevice, frontend_id, | 
|  | DOMID_INVALID), | 
|  | DEFINE_PROP_END_OF_LIST() | 
|  | }; | 
|  |  | 
|  | 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; | 
|  | device_class_set_props(dev_class, xen_device_props); | 
|  | 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) | 
|  |  | 
|  | void xen_bus_init(void) | 
|  | { | 
|  | DeviceState *dev = qdev_new(TYPE_XEN_BRIDGE); | 
|  | BusState *bus = qbus_new(TYPE_XEN_BUS, dev, NULL); | 
|  |  | 
|  | sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); | 
|  | qbus_set_bus_hotplug_handler(bus); | 
|  |  | 
|  | qemu_create_nic_bus_devices(bus, TYPE_XEN_DEVICE, "xen-net-device", | 
|  | "xen", "xen-net-device"); | 
|  | } |