diff --git a/tools/libxl/libxl.c b/tools/libxl/libxl.c index b89b7f4e0d..4292c78010 100644 --- a/tools/libxl/libxl.c +++ b/tools/libxl/libxl.c @@ -3528,6 +3528,345 @@ DEFINE_DEVICE_ADD(vtpm) /******************************************************************************/ +/* + * Data structures used to track devices handled by driver domains + */ + +/* + * Structure that describes a device handled by a driver domain + */ +typedef struct libxl__ddomain_device { + libxl__device *dev; + LIBXL_SLIST_ENTRY(struct libxl__ddomain_device) next; +} libxl__ddomain_device; + +/* + * Structure that describes a domain and it's associated devices + */ +typedef struct libxl__ddomain_guest { + uint32_t domid; + int num_vifs, num_vbds, num_qdisks; + LIBXL_SLIST_HEAD(, struct libxl__ddomain_device) devices; + LIBXL_SLIST_ENTRY(struct libxl__ddomain_guest) next; +} libxl__ddomain_guest; + +/* + * Main structure used by a driver domain to keep track of devices + * currently in use + */ +typedef struct { + libxl__ao *ao; + libxl__ev_xswatch watch; + LIBXL_SLIST_HEAD(, struct libxl__ddomain_guest) guests; +} libxl__ddomain; + +static libxl__ddomain_guest *search_for_guest(libxl__ddomain *ddomain, + uint32_t domid) +{ + libxl__ddomain_guest *dguest; + + LIBXL_SLIST_FOREACH(dguest, &ddomain->guests, next) { + if (dguest->domid == domid) + return dguest; + } + return NULL; +} + +static libxl__ddomain_device *search_for_device(libxl__ddomain_guest *dguest, + libxl__device *dev) +{ + libxl__ddomain_device *ddev; + + LIBXL_SLIST_FOREACH(ddev, &dguest->devices, next) { +#define LIBXL_DEVICE_CMP(dev1, dev2, entry) (dev1->entry == dev2->entry) + if (LIBXL_DEVICE_CMP(ddev->dev, dev, backend_devid) && + LIBXL_DEVICE_CMP(ddev->dev, dev, backend_domid) && + LIBXL_DEVICE_CMP(ddev->dev, dev, devid) && + LIBXL_DEVICE_CMP(ddev->dev, dev, domid) && + LIBXL_DEVICE_CMP(ddev->dev, dev, backend_kind) && + LIBXL_DEVICE_CMP(ddev->dev, dev, kind)) + return ddev; +#undef LIBXL_DEVICE_CMP + } + + return NULL; +} + +static void device_complete(libxl__egc *egc, libxl__ao_device *aodev) +{ + STATE_AO_GC(aodev->ao); + + if (aodev->action == LIBXL__DEVICE_ACTION_REMOVE) + free(aodev->dev); + + LOG(DEBUG, "device %s %s %s", + libxl__device_backend_path(gc, aodev->dev), + libxl__device_action_to_string(aodev->action), + aodev->rc ? "failed" : "succeed"); + + libxl__nested_ao_free(aodev->ao); +} + +static void qdisk_spawn_outcome(libxl__egc *egc, libxl__dm_spawn_state *dmss, + int rc) +{ + STATE_AO_GC(dmss->spawn.ao); + + LOG(DEBUG, "qdisk backend spawn for domain %u %s", dmss->guest_domid, + rc ? "failed" : "succeed"); + + libxl__nested_ao_free(dmss->spawn.ao); +} + +/* + * The following comment applies to both add_device and remove_device. + * + * If the return value is greater than 0, it means there's no ao dispatched, + * so the free of the nested ao should be done by the parent when it has + * finished. + */ +static int add_device(libxl__egc *egc, libxl__ao *ao, + libxl__ddomain_guest *dguest, + libxl__ddomain_device *ddev) +{ + AO_GC; + libxl__device *dev = ddev->dev; + libxl__ao_device *aodev; + libxl__dm_spawn_state *dmss; + int rc = 0; + + switch(dev->backend_kind) { + case LIBXL__DEVICE_KIND_VBD: + case LIBXL__DEVICE_KIND_VIF: + if (dev->backend_kind == LIBXL__DEVICE_KIND_VBD) dguest->num_vbds++; + if (dev->backend_kind == LIBXL__DEVICE_KIND_VIF) dguest->num_vifs++; + + GCNEW(aodev); + libxl__prepare_ao_device(ao, aodev); + aodev->dev = dev; + aodev->action = LIBXL__DEVICE_ACTION_ADD; + aodev->callback = device_complete; + libxl__wait_device_connection(egc, aodev); + + break; + case LIBXL__DEVICE_KIND_QDISK: + if (dguest->num_qdisks == 0) { + GCNEW(dmss); + dmss->guest_domid = dev->domid; + dmss->spawn.ao = ao; + dmss->callback = qdisk_spawn_outcome; + + libxl__spawn_qdisk_backend(egc, dmss); + } + dguest->num_qdisks++; + + break; + default: + rc = 1; + break; + } + + return rc; +} + +static int remove_device(libxl__egc *egc, libxl__ao *ao, + libxl__ddomain_guest *dguest, + libxl__ddomain_device *ddev) +{ + AO_GC; + libxl__device *dev = ddev->dev; + libxl__ao_device *aodev; + int rc = 0; + + switch(ddev->dev->backend_kind) { + case LIBXL__DEVICE_KIND_VBD: + case LIBXL__DEVICE_KIND_VIF: + if (dev->backend_kind == LIBXL__DEVICE_KIND_VBD) dguest->num_vbds--; + if (dev->backend_kind == LIBXL__DEVICE_KIND_VIF) dguest->num_vifs--; + + GCNEW(aodev); + libxl__prepare_ao_device(ao, aodev); + aodev->dev = dev; + aodev->action = LIBXL__DEVICE_ACTION_REMOVE; + aodev->callback = device_complete; + libxl__initiate_device_remove(egc, aodev); + break; + case LIBXL__DEVICE_KIND_QDISK: + if (--dguest->num_qdisks == 0) { + rc = libxl__destroy_qdisk_backend(gc, dev->domid); + if (rc) + goto out; + } + libxl__device_destroy(gc, dev); + free(dev); + /* Fall through to return > 0, no ao has been dispatched */ + default: + rc = 1; + break; + } + +out: + return rc; +} + +static void backend_watch_callback(libxl__egc *egc, libxl__ev_xswatch *watch, + const char *watch_path, + const char *event_path) +{ + libxl__ddomain *ddomain = CONTAINER_OF(watch, *ddomain, watch); + libxl__ao *nested_ao = libxl__nested_ao_create(ddomain->ao); + STATE_AO_GC(nested_ao); + char *p, *path; + const char *sstate; + int state, rc, num_devs; + libxl__device *dev = NULL; + libxl__ddomain_device *ddev = NULL; + libxl__ddomain_guest *dguest = NULL; + bool free_ao = false; + + /* Check if event_path ends with "state" and truncate it */ + if (strlen(event_path) < strlen("state")) + goto skip; + + path = libxl__strdup(gc, event_path); + p = path + strlen(path) - strlen("state") - 1; + if (*p != '/') + goto skip; + *p = '\0'; + p++; + if (strcmp(p, "state") != 0) + goto skip; + + /* Check if the state is 1 (XenbusStateInitialising) or greater */ + rc = libxl__xs_read_checked(gc, XBT_NULL, event_path, &sstate); + if (rc || !sstate) + goto skip; + state = atoi(sstate); + + dev = libxl__zalloc(NOGC, sizeof(*dev)); + rc = libxl__parse_backend_path(gc, path, dev); + if (rc) + goto skip; + + dguest = search_for_guest(ddomain, dev->domid); + if (dguest == NULL && state == XenbusStateClosed) { + /* + * Spurious state change, device has already been disconnected + * or never attached. + */ + goto skip; + } + if (dguest == NULL) { + /* Create a new guest struct and initialize it */ + dguest = libxl__zalloc(NOGC, sizeof(*dguest)); + dguest->domid = dev->domid; + LIBXL_SLIST_INIT(&dguest->devices); + LIBXL_SLIST_INSERT_HEAD(&ddomain->guests, dguest, next); + LOG(DEBUG, "added domain %u to the list of active guests", + dguest->domid); + } + ddev = search_for_device(dguest, dev); + if (ddev == NULL && state == XenbusStateClosed) { + /* + * Spurious state change, device has already been disconnected + * or never attached. + */ + goto skip; + } else if (ddev == NULL) { + /* + * New device addition, allocate a struct to hold it and add it + * to the list of active devices for a given guest. + */ + ddev = libxl__zalloc(NOGC, sizeof(*ddev)); + ddev->dev = dev; + LIBXL_SLIST_INSERT_HEAD(&dguest->devices, ddev, next); + LOG(DEBUG, "added device %s to the list of active devices", path); + rc = add_device(egc, nested_ao, dguest, ddev); + if (rc > 0) + free_ao = true; + } else if (state == XenbusStateClosed) { + /* + * Removal of an active device, remove it from the list and + * free it's data structures if they are no longer needed. + * + * The free of the associated libxl__device is left to the + * helper remove_device function. + */ + LIBXL_SLIST_REMOVE(&dguest->devices, ddev, libxl__ddomain_device, + next); + LOG(DEBUG, "removed device %s from the list of active devices", path); + rc = remove_device(egc, nested_ao, dguest, ddev); + if (rc > 0) + free_ao = true; + + free(ddev); + /* If this was the last device in the domain, remove it from the list */ + num_devs = dguest->num_vifs + dguest->num_vbds + dguest->num_qdisks; + if (num_devs == 0) { + LIBXL_SLIST_REMOVE(&ddomain->guests, dguest, libxl__ddomain_guest, + next); + LOG(DEBUG, "removed domain %u from the list of active guests", + dguest->domid); + /* Clear any leftovers in libxl/ */ + libxl__xs_rm_checked(gc, XBT_NULL, + GCSPRINTF("libxl/%u", dguest->domid)); + free(dguest); + } + } + + if (free_ao) + libxl__nested_ao_free(nested_ao); + + return; + +skip: + libxl__nested_ao_free(nested_ao); + free(dev); + free(ddev); + free(dguest); + return; +} + +/* Handler of events for device driver domains */ +int libxl_device_events_handler(libxl_ctx *ctx, + const libxl_asyncop_how *ao_how) +{ + AO_CREATE(ctx, 0, ao_how); + int rc; + uint32_t domid; + libxl__ddomain ddomain; + char *be_path; + + ddomain.ao = ao; + LIBXL_SLIST_INIT(&ddomain.guests); + + rc = libxl__get_domid(gc, &domid); + if (rc) { + LOG(ERROR, "unable to get domain id"); + goto out; + } + + rc = libxl__xs_write_checked(gc, XBT_NULL, DISABLE_UDEV_PATH, "1"); + if (rc) { + LOGE(ERROR, "unable to write %s = 1", DISABLE_UDEV_PATH); + goto out; + } + + /* + * We use absolute paths because we want xswatch to also return + * absolute paths that can be parsed by libxl__parse_backend_path. + */ + be_path = GCSPRINTF("/local/domain/%u/backend", domid); + rc = libxl__ev_xswatch_register(gc, &ddomain.watch, backend_watch_callback, + be_path); + +out: + GC_FREE; + return rc ? : AO_INPROGRESS; +} + +/******************************************************************************/ + int libxl_domain_setmaxmem(libxl_ctx *ctx, uint32_t domid, uint32_t max_memkb) { GC_INIT(ctx); diff --git a/tools/libxl/libxl.h b/tools/libxl/libxl.h index 18dd420169..a9663e47e9 100644 --- a/tools/libxl/libxl.h +++ b/tools/libxl/libxl.h @@ -910,6 +910,18 @@ int libxl_device_pci_destroy(libxl_ctx *ctx, uint32_t domid, libxl_device_pci *libxl_device_pci_list(libxl_ctx *ctx, uint32_t domid, int *num); +/* + * Turns the current process into a backend device service daemon + * for a driver domain. + * + * From a libxl API point of view, this starts a long-running + * operation. That operation consists of "being a driver domain" + * and never completes. + */ +int libxl_device_events_handler(libxl_ctx *ctx, + const libxl_asyncop_how *ao_how) + LIBXL_EXTERNAL_CALLERS_ONLY; + /* * Functions related to making devices assignable -- that is, bound to * the pciback driver, ready to be given to a guest via diff --git a/tools/libxl/xl.h b/tools/libxl/xl.h index e005c393da..c876a33614 100644 --- a/tools/libxl/xl.h +++ b/tools/libxl/xl.h @@ -105,6 +105,7 @@ int main_getenforce(int argc, char **argv); int main_setenforce(int argc, char **argv); int main_loadpolicy(int argc, char **argv); int main_remus(int argc, char **argv); +int main_devd(int argc, char **argv); void help(const char *command); diff --git a/tools/libxl/xl_cmdimpl.c b/tools/libxl/xl_cmdimpl.c index 2d147d4fe4..341863e1f7 100644 --- a/tools/libxl/xl_cmdimpl.c +++ b/tools/libxl/xl_cmdimpl.c @@ -7210,6 +7210,30 @@ int main_remus(int argc, char **argv) return -ERROR_FAIL; } +int main_devd(int argc, char **argv) +{ + int ret = 0, opt = 0, daemonize = 1; + + SWITCH_FOREACH_OPT(opt, "F", NULL, "devd", 0) { + case 'F': + daemonize = 0; + break; + } + + if (daemonize) { + ret = do_daemonize("xldevd"); + if (ret) { + ret = (ret == 1) ? 0 : ret; + goto out; + } + } + + libxl_device_events_handler(ctx, 0); + +out: + return ret; +} + /* * Local variables: * mode: C diff --git a/tools/libxl/xl_cmdtable.c b/tools/libxl/xl_cmdtable.c index 326a6606e0..77092069ac 100644 --- a/tools/libxl/xl_cmdtable.c +++ b/tools/libxl/xl_cmdtable.c @@ -488,6 +488,12 @@ struct cmd_spec cmd_table[] = { " of the domain." }, + { "devd", + &main_devd, 0, 1, + "Daemon that listens for devices and launches backends", + "[options]", + "-F Run in the foreground", + }, }; int cmdtable_len = sizeof(cmd_table)/sizeof(struct cmd_spec);