From patchwork Wed Dec 23 02:24:40 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Chunyan Liu X-Patchwork-Id: 7908621 Return-Path: X-Original-To: patchwork-xen-devel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 8595B9F1AF for ; Wed, 23 Dec 2015 02:28:57 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 4F5E220425 for ; Wed, 23 Dec 2015 02:28:54 +0000 (UTC) Received: from lists.xen.org (lists.xenproject.org [50.57.142.19]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id D0D3F2041C for ; Wed, 23 Dec 2015 02:28:50 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=lists.xen.org) by lists.xen.org with esmtp (Exim 4.72) (envelope-from ) id 1aBZ7D-0001l6-WC; Wed, 23 Dec 2015 02:25:24 +0000 Received: from mail6.bemta4.messagelabs.com ([85.158.143.247]) by lists.xen.org with esmtp (Exim 4.72) (envelope-from ) id 1aBZ7B-0001iy-BC for xen-devel@lists.xen.org; Wed, 23 Dec 2015 02:25:21 +0000 Received: from [85.158.143.35] by server-1.bemta-4.messagelabs.com id 99/1B-21571-0160A765; Wed, 23 Dec 2015 02:25:20 +0000 X-Env-Sender: cyliu@suse.com X-Msg-Ref: server-14.tower-21.messagelabs.com!1450837517!6776706!1 X-Originating-IP: [137.65.250.26] X-SpamReason: No, hits=0.0 required=7.0 tests= X-StarScan-Received: X-StarScan-Version: 7.35.1; banners=-,-,- X-VirusChecked: Checked Received: (qmail 28164 invoked from network); 23 Dec 2015 02:25:18 -0000 Received: from prv3-mh.provo.novell.com (HELO prv3-mh.provo.novell.com) (137.65.250.26) by server-14.tower-21.messagelabs.com with DHE-RSA-AES256-SHA encrypted SMTP; 23 Dec 2015 02:25:18 -0000 Received: from linux-3hvf.site (prv-ext-foundry1int.gns.novell.com [137.65.251.240]) by prv3-mh.provo.novell.com with ESMTP (TLS encrypted); Tue, 22 Dec 2015 19:25:05 -0700 From: Chunyan Liu To: xen-devel@lists.xen.org Date: Wed, 23 Dec 2015 10:24:40 +0800 Message-Id: <1450837482-5074-4-git-send-email-cyliu@suse.com> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1450837482-5074-1-git-send-email-cyliu@suse.com> References: <1450837482-5074-1-git-send-email-cyliu@suse.com> Cc: jgross@suse.com, wei.liu2@citrix.com, ian.campbell@citrix.com, george.dunlap@eu.citrix.com, Ian.Jackson@eu.citrix.com, Chunyan Liu , George Dunlap , jfehlig@suse.com, Simon Cao Subject: [Xen-devel] [PATCH V12 3/5] libxl: add pvusb API X-BeenThere: xen-devel@lists.xen.org X-Mailman-Version: 2.1.13 Precedence: list List-Id: Xen developer discussion List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: xen-devel-bounces@lists.xen.org Errors-To: xen-devel-bounces@lists.xen.org X-Spam-Status: No, score=-4.2 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Add pvusb APIs, including: - attach/detach (create/destroy) virtual usb controller. - attach/detach usb device - list usb controller and usb devices - some other helper functions Signed-off-by: Chunyan Liu Signed-off-by: Simon Cao Signed-off-by: George Dunlap Reviewed-by: George Dunlap --- tools/libxl/Makefile | 2 +- tools/libxl/libxl.c | 34 +- tools/libxl/libxl.h | 77 ++ tools/libxl/libxl_device.c | 13 +- tools/libxl/libxl_internal.h | 22 +- tools/libxl/libxl_osdeps.h | 13 + tools/libxl/libxl_pvusb.c | 1548 ++++++++++++++++++++++++++++++++++ tools/libxl/libxl_types.idl | 46 + tools/libxl/libxl_types_internal.idl | 1 + tools/libxl/libxl_utils.c | 18 + tools/libxl/libxl_utils.h | 5 + 11 files changed, 1766 insertions(+), 13 deletions(-) create mode 100644 tools/libxl/libxl_pvusb.c diff --git a/tools/libxl/Makefile b/tools/libxl/Makefile index 6ff5bee..a36145a 100644 --- a/tools/libxl/Makefile +++ b/tools/libxl/Makefile @@ -103,7 +103,7 @@ LIBXL_OBJS = flexarray.o libxl.o libxl_create.o libxl_dm.o libxl_pci.o \ libxl_stream_read.o libxl_stream_write.o \ libxl_save_callout.o _libxl_save_msgs_callout.o \ libxl_qmp.o libxl_event.o libxl_fork.o \ - libxl_dom_suspend.o $(LIBXL_OBJS-y) + libxl_dom_suspend.o libxl_pvusb.o $(LIBXL_OBJS-y) LIBXL_OBJS += libxl_genid.o LIBXL_OBJS += _libxl_types.o libxl_flask.o _libxl_types_internal.o diff --git a/tools/libxl/libxl.c b/tools/libxl/libxl.c index 43d5709..920c135 100644 --- a/tools/libxl/libxl.c +++ b/tools/libxl/libxl.c @@ -3204,7 +3204,7 @@ void libxl__device_disk_local_initiate_detach(libxl__egc *egc, aodev->dev = device; aodev->callback = local_device_detach_cb; aodev->force = 0; - libxl__initiate_device_remove(egc, aodev); + libxl__initiate_device_generic_remove(egc, aodev); return; } @@ -4172,8 +4172,10 @@ out: * libxl_device_vkb_destroy * libxl_device_vfb_remove * libxl_device_vfb_destroy + * libxl_device_usbctrl_remove + * libxl_device_usbctrl_destroy */ -#define DEFINE_DEVICE_REMOVE(type, removedestroy, f) \ +#define DEFINE_DEVICE_REMOVE_EXT(type, remtype, removedestroy, f) \ int libxl_device_##type##_##removedestroy(libxl_ctx *ctx, \ uint32_t domid, libxl_device_##type *type, \ const libxl_asyncop_how *ao_how) \ @@ -4193,13 +4195,19 @@ out: aodev->dev = device; \ aodev->callback = device_addrm_aocomplete; \ aodev->force = f; \ - libxl__initiate_device_remove(egc, aodev); \ + libxl__initiate_device_##remtype##_remove(egc, aodev); \ \ out: \ - if (rc) return AO_CREATE_FAIL(rc); \ + if (rc) return AO_CREATE_FAIL(rc); \ return AO_INPROGRESS; \ } +#define DEFINE_DEVICE_REMOVE(type, removedestroy, f) \ + DEFINE_DEVICE_REMOVE_EXT(type, generic, removedestroy, f) + +#define DEFINE_DEVICE_REMOVE_CUSTOM(type, removedestroy, f) \ + DEFINE_DEVICE_REMOVE_EXT(type, type, removedestroy, f) + /* Define all remove/destroy functions and undef the macro */ /* disk */ @@ -4223,6 +4231,10 @@ DEFINE_DEVICE_REMOVE(vfb, destroy, 1) DEFINE_DEVICE_REMOVE(vtpm, remove, 0) DEFINE_DEVICE_REMOVE(vtpm, destroy, 1) +/* usbctrl */ +DEFINE_DEVICE_REMOVE_CUSTOM(usbctrl, remove, 0) +DEFINE_DEVICE_REMOVE_CUSTOM(usbctrl, destroy, 1) + /* channel/console hotunplug is not implemented. There are 2 possibilities: * 1. add support for secondary consoles to xenconsoled * 2. dynamically add/remove qemu chardevs via qmp messages. */ @@ -4236,6 +4248,8 @@ DEFINE_DEVICE_REMOVE(vtpm, destroy, 1) * libxl_device_disk_add * libxl_device_nic_add * libxl_device_vtpm_add + * libxl_device_usbctrl_add + * libxl_device_usbdev_add */ #define DEFINE_DEVICE_ADD(type) \ @@ -4267,6 +4281,12 @@ DEFINE_DEVICE_ADD(nic) /* vtpm */ DEFINE_DEVICE_ADD(vtpm) +/* usbctrl */ +DEFINE_DEVICE_ADD(usbctrl) + +/* usb */ +DEFINE_DEVICE_ADD(usbdev) + #undef DEFINE_DEVICE_ADD /******************************************************************************/ @@ -4432,7 +4452,7 @@ static int remove_device(libxl__egc *egc, libxl__ao *ao, aodev->dev = dev; aodev->action = LIBXL__DEVICE_ACTION_REMOVE; aodev->callback = device_complete; - libxl__initiate_device_remove(egc, aodev); + libxl__initiate_device_generic_remove(egc, aodev); break; case LIBXL__DEVICE_KIND_QDISK: if (--dguest->num_qdisks == 0) { @@ -6807,6 +6827,10 @@ int libxl_retrieve_domain_configuration(libxl_ctx *ctx, uint32_t domid, MERGE(pci, pcidevs, COMPARE_PCI, {}); + MERGE(usbctrl, usbctrls, COMPARE_USBCTRL, {}); + + MERGE(usbdev, usbdevs, COMPARE_USB, {}); + /* Take care of removable device. We maintain invariant in the * insert / remove operation so that: * 1. if xenstore is "empty" while JSON is not, the result diff --git a/tools/libxl/libxl.h b/tools/libxl/libxl.h index 0e347b9..c708cc8 100644 --- a/tools/libxl/libxl.h +++ b/tools/libxl/libxl.h @@ -123,6 +123,12 @@ #define LIBXL_HAVE_DOMAIN_NODEAFFINITY 1 /* + * LIBXL_HAVE_PVUSB indicates functions for plugging in USB devices + * through pvusb -- both hotplug and at domain creation time.. + */ +#define LIBXL_HAVE_PVUSB 1 + +/* * LIBXL_HAVE_BUILDINFO_HVM_VENDOR_DEVICE indicates that the * libxl_vendor_device field is present in the hvm sections of * libxl_domain_build_info. This field tells libxl which @@ -1503,6 +1509,77 @@ int libxl_cdrom_insert(libxl_ctx *ctx, uint32_t domid, libxl_device_disk *disk, const libxl_asyncop_how *ao_how) LIBXL_EXTERNAL_CALLERS_ONLY; +/* + * USB + * + * For each device removed or added, one of these protocols is available: + * - PV (i.e., PVUSB) + * - DEVICEMODEL (i.e, qemu) + * + * PV is available for either PV or HVM domains. DEVICEMODEL is only + * available for HVM domains. The caller can additionally specify + * "AUTO", in which case the library will try to determine the best + * protocol automatically. + * + * At the moment, the only protocol implemented is PV. + * + * One can add/remove USB controllers to/from guest, and attach/detach USB + * devices to/from USB controllers. + * + * To add USB controllers and USB devices, one can adding USB controllers + * first and then attaching USB devices to some USB controller, or adding + * USB devices to guest directly, it will automatically create a USB + * controller for USB devices to attach. + * + * To remove USB controllers or USB devices, one can remove USB devices + * under USB controller one by one and then remove USB controller, or + * remove USB controller directly, it will remove all USB devices under + * it automatically. + * + */ +/* USB Controllers*/ +int libxl_device_usbctrl_add(libxl_ctx *ctx, uint32_t domid, + libxl_device_usbctrl *usbctrl, + const libxl_asyncop_how *ao_how) + LIBXL_EXTERNAL_CALLERS_ONLY; + +int libxl_device_usbctrl_remove(libxl_ctx *ctx, uint32_t domid, + libxl_device_usbctrl *usbctrl, + const libxl_asyncop_how *ao_how) + LIBXL_EXTERNAL_CALLERS_ONLY; + +int libxl_device_usbctrl_destroy(libxl_ctx *ctx, uint32_t domid, + libxl_device_usbctrl *usbctrl, + const libxl_asyncop_how *ao_how) + LIBXL_EXTERNAL_CALLERS_ONLY; + +libxl_device_usbctrl *libxl_device_usbctrl_list(libxl_ctx *ctx, + uint32_t domid, int *num); + +void libxl_device_usbctrl_list_free(libxl_device_usbctrl *list, int nr); + + +int libxl_device_usbctrl_getinfo(libxl_ctx *ctx, uint32_t domid, + libxl_device_usbctrl *usbctrl, + libxl_usbctrlinfo *usbctrlinfo); + +/* USB Devices */ + +int libxl_device_usbdev_add(libxl_ctx *ctx, uint32_t domid, + libxl_device_usbdev *usbdev, + const libxl_asyncop_how *ao_how) + LIBXL_EXTERNAL_CALLERS_ONLY; + +int libxl_device_usbdev_remove(libxl_ctx *ctx, uint32_t domid, + libxl_device_usbdev *usbdev, + const libxl_asyncop_how *ao_how) + LIBXL_EXTERNAL_CALLERS_ONLY; + +libxl_device_usbdev * +libxl_device_usbdev_list(libxl_ctx *ctx, uint32_t domid, int *num); + +void libxl_device_usbdev_list_free(libxl_device_usbdev *list, int nr); + /* Network Interfaces */ int libxl_device_nic_add(libxl_ctx *ctx, uint32_t domid, libxl_device_nic *nic, const libxl_asyncop_how *ao_how) diff --git a/tools/libxl/libxl_device.c b/tools/libxl/libxl_device.c index 8bb5e93..b7a6a13 100644 --- a/tools/libxl/libxl_device.c +++ b/tools/libxl/libxl_device.c @@ -676,7 +676,10 @@ void libxl__devices_destroy(libxl__egc *egc, libxl__devices_remove_state *drs) aodev->action = LIBXL__DEVICE_ACTION_REMOVE; aodev->dev = dev; aodev->force = drs->force; - libxl__initiate_device_remove(egc, aodev); + if (dev->backend_kind == LIBXL__DEVICE_KIND_VUSB) + libxl__initiate_device_usbctrl_remove(egc, aodev); + else + libxl__initiate_device_generic_remove(egc, aodev); } } } @@ -775,8 +778,8 @@ out: return; } -void libxl__initiate_device_remove(libxl__egc *egc, - libxl__ao_device *aodev) +void libxl__initiate_device_generic_remove(libxl__egc *egc, + libxl__ao_device *aodev) { STATE_AO_GC(aodev->ao); xs_transaction_t t = 0; @@ -806,7 +809,7 @@ void libxl__initiate_device_remove(libxl__egc *egc, (info.paused || info.dying || info.shutdown)) { /* * TODO: 4.2 Bodge due to QEMU, see comment on top of - * libxl__initiate_device_remove in libxl_internal.h + * libxl__initiate_device_generic_remove in libxl_internal.h */ rc = libxl__ev_time_register_rel(ao, &aodev->timeout, device_qemu_timeout, @@ -942,7 +945,7 @@ static void device_backend_callback(libxl__egc *egc, libxl__ev_devstate *ds, !aodev->force) { LOG(DEBUG, "Timeout reached, initiating forced remove"); aodev->force = 1; - libxl__initiate_device_remove(egc, aodev); + libxl__initiate_device_generic_remove(egc, aodev); return; } diff --git a/tools/libxl/libxl_internal.h b/tools/libxl/libxl_internal.h index d1eb18f..0ccad9a 100644 --- a/tools/libxl/libxl_internal.h +++ b/tools/libxl/libxl_internal.h @@ -2583,6 +2583,14 @@ _hidden void libxl__device_vtpm_add(libxl__egc *egc, uint32_t domid, libxl_device_vtpm *vtpm, libxl__ao_device *aodev); +_hidden void libxl__device_usbctrl_add(libxl__egc *egc, uint32_t domid, + libxl_device_usbctrl *usbctrl, + libxl__ao_device *aodev); + +_hidden void libxl__device_usbdev_add(libxl__egc *egc, uint32_t domid, + libxl_device_usbdev *usbdev, + libxl__ao_device *aodev); + /* Internal function to connect a vkb device */ _hidden int libxl__device_vkb_add(libxl__gc *gc, uint32_t domid, libxl_device_vkb *vkb); @@ -2612,8 +2620,15 @@ _hidden void libxl__wait_device_connection(libxl__egc*, * * Once finished, aodev->callback will be executed. */ -_hidden void libxl__initiate_device_remove(libxl__egc *egc, - libxl__ao_device *aodev); +_hidden void libxl__initiate_device_generic_remove(libxl__egc *egc, + libxl__ao_device *aodev); + +_hidden int libxl__device_from_usbctrl(libxl__gc *gc, uint32_t domid, + libxl_device_usbctrl *usbctrl, + libxl__device *device); + +_hidden void libxl__initiate_device_usbctrl_remove(libxl__egc *egc, + libxl__ao_device *aodev); /* * libxl__get_hotplug_script_info returns the args and env that should @@ -3975,6 +3990,9 @@ static inline void libxl__update_config_vtpm(libxl__gc *gc, #define COMPARE_PCI(a, b) ((a)->func == (b)->func && \ (a)->bus == (b)->bus && \ (a)->dev == (b)->dev) +#define COMPARE_USB(a, b) ((a)->ctrl == (b)->ctrl && \ + (a)->port == (b)->port) +#define COMPARE_USBCTRL(a, b) ((a)->devid == (b)->devid) /* DEVICE_ADD * diff --git a/tools/libxl/libxl_osdeps.h b/tools/libxl/libxl_osdeps.h index d9661c9..802c762 100644 --- a/tools/libxl/libxl_osdeps.h +++ b/tools/libxl/libxl_osdeps.h @@ -24,6 +24,8 @@ #define _GNU_SOURCE #if defined(__NetBSD__) +#define SYSFS_USB_DEV "/sys/bus/usb/devices" +#define SYSFS_USBBACK_DRIVER "/kern/xen/usb" #define SYSFS_PCI_DEV "/sys/bus/pci/devices" #define SYSFS_PCIBACK_DRIVER "/kern/xen/pci" #define NETBACK_NIC_NAME "xvif%ui%d" @@ -31,6 +33,8 @@ #elif defined(__OpenBSD__) #include #elif defined(__linux__) +#define SYSFS_USB_DEV "/sys/bus/usb/devices" +#define SYSFS_USBBACK_DRIVER "/sys/bus/usb/drivers/usbback" #define SYSFS_PCI_DEV "/sys/bus/pci/devices" #define SYSFS_PCIBACK_DRIVER "/sys/bus/pci/drivers/pciback" #define NETBACK_NIC_NAME "vif%u.%d" @@ -38,6 +42,8 @@ #elif defined(__sun__) #include #elif defined(__FreeBSD__) +#define SYSFS_USB_DEV "/dev/null" +#define SYSFS_USBBACK_DRIVER "/dev/null" #define SYSFS_PCI_DEV "/dev/null" #define SYSFS_PCIBACK_DRIVER "/dev/null" #define NETBACK_NIC_NAME "xnb%u.%d" @@ -45,6 +51,13 @@ #include #endif +#ifndef SYSFS_USBBACK_DRIVER +#error define SYSFS_USBBACK_DRIVER for your platform +#endif +#ifndef SYSFS_USB_DEV +#error define SYSFS_USB_DEV for your platform +#endif + #ifndef SYSFS_PCIBACK_DRIVER #error define SYSFS_PCIBACK_DRIVER for your platform #endif diff --git a/tools/libxl/libxl_pvusb.c b/tools/libxl/libxl_pvusb.c new file mode 100644 index 0000000..44ba6f0 --- /dev/null +++ b/tools/libxl/libxl_pvusb.c @@ -0,0 +1,1548 @@ +/* + * Copyright (C) 2015 SUSE LINUX Products GmbH, Nuernberg, Germany. + * Author Chunyan Liu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; version 2.1 only. with the special + * exception on linking described in file LICENSE. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +#include "libxl_osdeps.h" /* must come before any other headers */ + +#include "libxl_internal.h" +#include + +#define USBBACK_INFO_PATH "/libxl/usbback" + +#define USBHUB_CLASS_CODE 9 + +static int libxl__device_usbctrl_setdefault(libxl__gc *gc, uint32_t domid, + libxl_device_usbctrl *usbctrl) +{ + int rc; + libxl_domain_type domtype = libxl__domain_type(gc, domid); + + if (!usbctrl->version) + usbctrl->version = 2; + + if (!usbctrl->ports) + usbctrl->ports = 8; + + if (usbctrl->type == LIBXL_USBCTRL_TYPE_AUTO) { + if (domtype == LIBXL_DOMAIN_TYPE_PV) { + usbctrl->type = LIBXL_USBCTRL_TYPE_PV; + } else if (domtype == LIBXL_DOMAIN_TYPE_HVM) { + /* FIXME: See if we can detect PV frontend */ + usbctrl->type = LIBXL_USBCTRL_TYPE_DEVICEMODEL; + } + } + + rc = libxl__resolve_domid(gc, usbctrl->backend_domname, + &usbctrl->backend_domid); + return rc; +} + +int libxl__device_from_usbctrl(libxl__gc *gc, uint32_t domid, + libxl_device_usbctrl *usbctrl, + libxl__device *device) +{ + device->backend_devid = usbctrl->devid; + device->backend_domid = usbctrl->backend_domid; + device->backend_kind = LIBXL__DEVICE_KIND_VUSB; + device->devid = usbctrl->devid; + device->domid = domid; + device->kind = LIBXL__DEVICE_KIND_VUSB; + + return 0; +} + +/* Add usbctrl information to xenstore. + * + * Adding a usb controller will add a new 'vusb' device in xenstore, and + * add corresponding frontend, backend information to it. According to + * "update_json", decide wether to update json config file. + */ +static int libxl__device_usbctrl_add_xenstore(libxl__gc *gc, uint32_t domid, + libxl_device_usbctrl *usbctrl, + bool update_json) +{ + libxl__device *device; + flexarray_t *front; + flexarray_t *back; + xs_transaction_t t = XBT_NULL; + int i, rc; + libxl_domain_config d_config; + libxl_device_usbctrl usbctrl_saved; + libxl__domain_userdata_lock *lock = NULL; + + libxl_domain_config_init(&d_config); + libxl_device_usbctrl_init(&usbctrl_saved); + libxl_device_usbctrl_copy(CTX, &usbctrl_saved, usbctrl); + + GCNEW(device); + rc = libxl__device_from_usbctrl(gc, domid, usbctrl, device); + if (rc) goto out; + + front = flexarray_make(gc, 4, 1); + back = flexarray_make(gc, 12, 1); + + flexarray_append_pair(back, "frontend-id", GCSPRINTF("%d", domid)); + flexarray_append_pair(back, "online", "1"); + flexarray_append_pair(back, "state", + GCSPRINTF("%d", XenbusStateInitialising)); + flexarray_append_pair(back, "type", + (char *)libxl_usbctrl_type_to_string(usbctrl->type)); + flexarray_append_pair(back, "usb-ver", GCSPRINTF("%d", usbctrl->version)); + flexarray_append_pair(back, "num-ports", GCSPRINTF("%d", usbctrl->ports)); + flexarray_append_pair(back, "port", ""); + for (i = 0; i < usbctrl->ports; i++) + flexarray_append_pair(back, GCSPRINTF("port/%d", i + 1), ""); + + flexarray_append_pair(front, "backend-id", + GCSPRINTF("%d", usbctrl->backend_domid)); + flexarray_append_pair(front, "state", + GCSPRINTF("%d", XenbusStateInitialising)); + + if (update_json) { + lock = libxl__lock_domain_userdata(gc, domid); + if (!lock) { + rc = ERROR_LOCK_FAIL; + goto out; + } + + rc = libxl__get_domain_configuration(gc, domid, &d_config); + if (rc) goto out; + + DEVICE_ADD(usbctrl, usbctrls, domid, &usbctrl_saved, + COMPARE_USBCTRL, &d_config); + } + + for (;;) { + rc = libxl__xs_transaction_start(gc, &t); + if (rc) goto out; + + rc = libxl__device_exists(gc, t, device); + if (rc < 0) goto out; + if (rc == 1) { + /* already exists in xenstore */ + LOG(ERROR, "device already exists in xenstore"); + rc = ERROR_DEVICE_EXISTS; + goto out; + } + + if (update_json) { + rc = libxl__set_domain_configuration(gc, domid, &d_config); + if (rc) goto out; + } + + libxl__device_generic_add(gc, t, device, + libxl__xs_kvs_of_flexarray(gc, back, back->count), + libxl__xs_kvs_of_flexarray(gc, front, front->count), + NULL); + + rc = libxl__xs_transaction_commit(gc, &t); + if (!rc) break; + if (rc < 0) goto out; + } + +out: + libxl__xs_transaction_abort(gc, &t); + if (lock) libxl__unlock_domain_userdata(lock); + libxl_device_usbctrl_dispose(&usbctrl_saved); + libxl_domain_config_dispose(&d_config); + return rc; +} + +/* AO operation to add a usb controller. + * + * Generally, it does: + * 1) fill in necessary usb controler information with default value + * 2) write usb controller frontend/backend info to xenstore, update json + * config file if necessary. + * 3) wait for device connection. PVUSB frontend and backend driver will + * probe xenstore paths and build connection between frontend and backend. + * + * Before calling this function, aodev should be properly filled: + * aodev->ao, aodev->callback, aodev->update_json, ... + */ +void libxl__device_usbctrl_add(libxl__egc *egc, uint32_t domid, + libxl_device_usbctrl *usbctrl, + libxl__ao_device *aodev) +{ + STATE_AO_GC(aodev->ao); + libxl__device *device; + int rc; + + rc = libxl__device_usbctrl_setdefault(gc, domid, usbctrl); + if (rc < 0) goto out; + + if (usbctrl->devid == -1) { + usbctrl->devid = libxl__device_nextid(gc, domid, "vusb"); + if (usbctrl->devid < 0) { + rc = ERROR_FAIL; + goto out; + } + } + + if (usbctrl->type != LIBXL_USBCTRL_TYPE_PV) { + LOG(ERROR, "Unsupported USB controller type"); + rc = ERROR_FAIL; + goto out; + } + + rc = libxl__device_usbctrl_add_xenstore(gc, domid, usbctrl, + aodev->update_json); + if (rc) goto out; + + GCNEW(device); + rc = libxl__device_from_usbctrl(gc, domid, usbctrl, device); + if (rc) goto out; + + aodev->dev = device; + aodev->action = LIBXL__DEVICE_ACTION_ADD; + libxl__wait_device_connection(egc, aodev); + return; + +out: + aodev->rc = rc; + aodev->callback(egc, aodev); + return; +} + +static int libxl__device_usbdev_list_for_usbctrl(libxl__gc *gc, uint32_t domid, + libxl_devid usbctrl, + libxl_device_usbdev **usbdevs, + int *num); + +static int libxl__device_usbdev_remove(libxl__gc *gc, uint32_t domid, + libxl_device_usbdev *usbdev); + +/* AO function to remove a usb controller. + * + * Generally, it does: + * 1) check if the usb controller exists or not + * 2) remove all usb devices under controller + * 3) remove usb controller information from xenstore + * + * Before calling this function, aodev should be properly filled: + * aodev->ao, aodev->dev, aodev->callback, ... + */ +void libxl__initiate_device_usbctrl_remove(libxl__egc *egc, + libxl__ao_device *aodev) +{ + STATE_AO_GC(aodev->ao); + libxl_device_usbdev *usbdevs = NULL; + int num_usbdev = 0; + int i, rc; + uint32_t domid = ao->domid; + int usbctrl_devid = aodev->dev->devid; + libxl_device_usbctrl usbctrl; + libxl_usbctrlinfo usbctrlinfo; + + libxl_device_usbctrl_init(&usbctrl); + libxl_usbctrlinfo_init(&usbctrlinfo); + usbctrl.devid = usbctrl_devid; + + rc = libxl_device_usbctrl_getinfo(CTX, domid, &usbctrl, &usbctrlinfo); + if (rc) goto out; + + if (usbctrlinfo.type != LIBXL_USBCTRL_TYPE_PV) { + LOG(ERROR, "Unsupported USB controller type"); + rc = ERROR_FAIL; + goto out; + } + + /* Remove usb devices first */ + rc = libxl__device_usbdev_list_for_usbctrl(gc, domid, usbctrl_devid, + &usbdevs, &num_usbdev); + if (rc) goto out; + + for (i = 0; i < num_usbdev; i++) { + if (libxl__device_usbdev_remove(gc, domid, &usbdevs[i])) { + LOG(ERROR, "libxl__device_usbdev_remove failed: controller %d, " + "port %d", usbdevs[i].ctrl, usbdevs[i].port); + rc = ERROR_FAIL; + goto out; + } + } + + libxl_device_usbctrl_dispose(&usbctrl); + libxl_usbctrlinfo_dispose(&usbctrlinfo); + + /* Remove usbctrl */ + libxl__initiate_device_generic_remove(egc, aodev); + return; + +out: + libxl_device_usbctrl_dispose(&usbctrl); + libxl_usbctrlinfo_dispose(&usbctrlinfo); + aodev->rc = rc; + aodev->callback(egc, aodev); + return; +} + +static const char *vusb_be_from_xs_fe(libxl__gc *gc, const char *fe_path, + uint32_t tgt_domid) +{ + const char *be_path; + int r; + uint32_t be_domid, fe_domid; + + r = libxl__xs_read_checked(gc, XBT_NULL, GCSPRINTF("%s/backend", fe_path), + &be_path); + if (r || !be_path) return NULL; + + /* Check to see that it has the proper form, and that fe_domid == + * target domid */ + r = sscanf(be_path, "/local/domain/%d/backend/vusb/%d", + &be_domid, &fe_domid); + + if (r != 2 || fe_domid != tgt_domid) { + LOG(ERROR, "Malformed backend, refusing to use"); + return NULL; + } + + return be_path; +} + +libxl_device_usbctrl * +libxl_device_usbctrl_list(libxl_ctx *ctx, uint32_t domid, int *num) +{ + GC_INIT(ctx); + libxl_device_usbctrl *usbctrls = NULL; + char *path = NULL; + char **entry = NULL; + unsigned int nentries = 0; + + *num = 0; + + path = GCSPRINTF("%s/device/vusb", + libxl__xs_get_dompath(gc, domid)); + entry = libxl__xs_directory(gc, XBT_NULL, path, &nentries); + + if (entry && nentries) { + usbctrls = libxl__zalloc(NOGC, sizeof(*usbctrls) * nentries); + libxl_device_usbctrl *usbctrl; + libxl_device_usbctrl *end = usbctrls + nentries; + for (usbctrl = usbctrls; + usbctrl < end; + usbctrl++, entry++, (*num)++) { + const char *tmp, *be_path, *fe_path; + int ret; + + libxl_device_usbctrl_init(usbctrl); + usbctrl->devid = atoi(*entry); + +#define READ_SUBPATH(path, subpath) ({ \ + ret = libxl__xs_read_checked(gc, XBT_NULL, \ + GCSPRINTF("%s/" subpath, path), \ + &tmp); \ + if (ret) goto out; \ + (char *)tmp; \ + }) + +#define READ_SUBPATH_INT(path, subpath) ({ \ + ret = libxl__xs_read_checked(gc, XBT_NULL, \ + GCSPRINTF("%s/" subpath, path), \ + &tmp); \ + if (ret) goto out; \ + tmp ? atoi(tmp) : -1; \ + }) + + fe_path = GCSPRINTF("%s/%s", path, *entry); + be_path = vusb_be_from_xs_fe(gc, fe_path, domid); + if (!be_path) goto out; + usbctrl->backend_domid = READ_SUBPATH_INT(fe_path, "backend-id"); + usbctrl->version = READ_SUBPATH_INT(be_path, "usb-ver"); + usbctrl->ports = READ_SUBPATH_INT(be_path, "num-ports"); + libxl_usbctrl_type_from_string(READ_SUBPATH(be_path, "type"), + &usbctrl->type); + +#undef READ_SUBPATH +#undef READ_SUBPATH_INT + } + } + + GC_FREE; + return usbctrls; + +out: + LOG(ERROR, "Unable to list USB Controllers"); + libxl_device_usbctrl_list_free(usbctrls, *num); + GC_FREE; + *num = 0; + return NULL; +} + +int libxl_device_usbctrl_getinfo(libxl_ctx *ctx, uint32_t domid, + libxl_device_usbctrl *usbctrl, + libxl_usbctrlinfo *usbctrlinfo) +{ + GC_INIT(ctx); + const char *dompath, *fe_path, *be_path, *tmp; + int rc; + + usbctrlinfo->devid = usbctrl->devid; + +#define READ_SUBPATH(path, subpath) ({ \ + rc = libxl__xs_read_checked(gc, XBT_NULL, \ + GCSPRINTF("%s/" subpath, path), \ + &tmp); \ + if (rc) goto out; \ + (char *)tmp; \ + }) + +#define READ_SUBPATH_INT(path, subpath) ({ \ + rc = libxl__xs_read_checked(gc, XBT_NULL, \ + GCSPRINTF("%s/" subpath, path), \ + &tmp); \ + if (rc) goto out; \ + tmp ? atoi(tmp) : -1; \ + }) + + dompath = libxl__xs_get_dompath(gc, domid); + fe_path = GCSPRINTF("%s/device/vusb/%d", dompath, usbctrl->devid); + be_path = READ_SUBPATH(fe_path, "backend"); + usbctrlinfo->backend = libxl__strdup(NOGC, be_path); + usbctrlinfo->backend_id = READ_SUBPATH_INT(fe_path, "backend-id"); + usbctrlinfo->state = READ_SUBPATH_INT(fe_path, "state"); + usbctrlinfo->evtch = READ_SUBPATH_INT(fe_path, "event-channel"); + usbctrlinfo->ref_urb = READ_SUBPATH_INT(fe_path, "urb-ring-ref"); + usbctrlinfo->ref_conn = READ_SUBPATH_INT(fe_path, "urb-ring-ref"); + tmp = READ_SUBPATH(be_path, "frontend"); + usbctrlinfo->frontend = libxl__strdup(NOGC, tmp); + usbctrlinfo->frontend_id = READ_SUBPATH_INT(be_path, "frontend-id"); + usbctrlinfo->ports = READ_SUBPATH_INT(be_path, "num-ports"); + usbctrlinfo->version = READ_SUBPATH_INT(be_path, "usb-ver");; + tmp = READ_SUBPATH(be_path, "type"); + libxl_usbctrl_type_from_string(tmp, &usbctrlinfo->type); + +#undef READ_SUBPATH +#undef READ_SUBPATH_INT + + rc = 0; + +out: + GC_FREE; + return rc; +} + +int libxl_devid_to_device_usbctrl(libxl_ctx *ctx, + uint32_t domid, + int devid, + libxl_device_usbctrl *usbctrl) +{ + libxl_device_usbctrl *usbctrls; + int nb = 0; + int i, rc; + + usbctrls = libxl_device_usbctrl_list(ctx, domid, &nb); + if (!usbctrls) + return ERROR_FAIL; + + rc = ERROR_FAIL; + for (i = 0; i < nb; i++) { + if (devid == usbctrls[i].devid) { + libxl_device_usbctrl_copy(ctx, usbctrl, &usbctrls[i]); + rc = 0; + break; + } + } + + libxl_device_usbctrl_list_free(usbctrls, nb); + return rc; +} + +static void *zalloc_dirent(libxl__gc *gc, const char *dirpath) +{ + size_t need = offsetof(struct dirent, d_name) + + pathconf(dirpath, _PC_NAME_MAX) + 1; + + return libxl__zalloc(gc, need); +} + +static char *usbdev_busaddr_to_busid(libxl__gc *gc, int bus, int addr) +{ + DIR *dir; + char *busid = NULL; + struct dirent *de_buf; + struct dirent *de; + + /* invalid hostbus or hostaddr */ + if (bus < 1 || addr < 1) + return NULL; + + dir = opendir(SYSFS_USB_DEV); + if (!dir) { + LOGE(ERROR, "opendir failed: '%s'", SYSFS_USB_DEV); + return NULL; + } + + de_buf = zalloc_dirent(gc, SYSFS_USB_DEV); + + for (;;) { + char *filename; + void *buf; + int busnum = -1; + int devnum = -1; + + int r = readdir_r(dir, de_buf, &de); + if (r) { + LOGE(ERROR, "failed to readdir %s", SYSFS_USB_DEV); + break; + } + if (!de) + break; + + if (!strcmp(de->d_name, ".") || + !strcmp(de->d_name, "..")) + continue; + + filename = GCSPRINTF(SYSFS_USB_DEV "/%s/devnum", de->d_name); + if (!libxl__read_sysfs_file_contents(gc, filename, &buf, NULL)) + devnum = atoi(buf); + + filename = GCSPRINTF(SYSFS_USB_DEV "/%s/busnum", de->d_name); + if (!libxl__read_sysfs_file_contents(gc, filename, &buf, NULL)) + busnum = atoi(buf); + + if (bus == busnum && addr == devnum) { + busid = libxl__strdup(gc, de->d_name); + break; + } + } + + closedir(dir); + return busid; +} + +static int usbdev_busaddr_from_busid(libxl__gc *gc, const char *busid, + uint8_t *bus, uint8_t *addr) +{ + char *filename; + void *buf; + + filename = GCSPRINTF(SYSFS_USB_DEV "/%s/busnum", busid); + if (!libxl__read_sysfs_file_contents(gc, filename, &buf, NULL)) + *bus = atoi(buf); + else + return ERROR_FAIL; + + filename = GCSPRINTF(SYSFS_USB_DEV "/%s/devnum", busid); + if (!libxl__read_sysfs_file_contents(gc, filename, &buf, NULL)) + *addr = atoi(buf); + else + return ERROR_FAIL; + + return 0; +} + +static int get_assigned_devices(libxl__gc *gc, + libxl_device_usbdev **list, int *num) +{ + char **domlist; + unsigned int ndom = 0; + int i, j, k; + int rc; + + *list = NULL; + *num = 0; + + domlist = libxl__xs_directory(gc, XBT_NULL, "/local/domain", &ndom); + for (i = 0; i < ndom; i++) { + char *path; + char **usbctrls; + unsigned int nc = 0; + uint32_t domid = atoi(domlist[i]); + + path = GCSPRINTF("%s/device/vusb", libxl__xs_get_dompath(gc, domid)); + usbctrls = libxl__xs_directory(gc, XBT_NULL, path, &nc); + + for (j = 0; j < nc; j++) { + libxl_device_usbdev *tmp = NULL; + int nd = 0; + + rc = libxl__device_usbdev_list_for_usbctrl(gc, domid, + atoi(usbctrls[j]), + &tmp, &nd); + if (rc) goto out; + + if (!nd) continue; + + GCREALLOC_ARRAY(*list, *num + nd); + for (k = 0; k < nd; k++) { + libxl_device_usbdev_copy(CTX, *list + *num, tmp + k); + (*num)++; + } + } + } + + return 0; + +out: + LOG(ERROR, "fail to get assigned devices"); + return rc; +} + +static bool is_usbdev_in_array(libxl_device_usbdev *usbdevs, int num, + libxl_device_usbdev *usbdev) +{ + int i; + + for (i = 0; i < num; i++) { + if (usbdevs[i].u.hostdev.hostbus == usbdev->u.hostdev.hostbus && + usbdevs[i].u.hostdev.hostaddr == usbdev->u.hostdev.hostaddr) + return true; + } + + return false; +} + +/* check if USB device type is assignable */ +static bool is_usbdev_assignable(libxl__gc *gc, libxl_device_usbdev *usbdev) +{ + int classcode; + char *filename; + void *buf = NULL; + char *busid = NULL; + + busid = usbdev_busaddr_to_busid(gc, usbdev->u.hostdev.hostbus, + usbdev->u.hostdev.hostaddr); + if (!busid) return false; + + filename = GCSPRINTF(SYSFS_USB_DEV "/%s/bDeviceClass", busid); + if (libxl__read_sysfs_file_contents(gc, filename, &buf, NULL)) + return false; + + classcode = atoi(buf); + return classcode != USBHUB_CLASS_CODE; +} + +/* get usb devices under certain usb controller */ +static int +libxl__device_usbdev_list_for_usbctrl(libxl__gc *gc, + uint32_t domid, + libxl_devid usbctrl, + libxl_device_usbdev **usbdevs, + int *num) +{ + const char *fe_path, *be_path, *num_devs; + int n, i, rc; + + *usbdevs = NULL; + *num = 0; + + fe_path = GCSPRINTF("%s/device/vusb/%d", + libxl__xs_get_dompath(gc, domid), usbctrl); + + be_path = vusb_be_from_xs_fe(gc, fe_path, domid); + if (!be_path) { + rc = ERROR_FAIL; + goto out; + } + + rc = libxl__xs_read_checked(gc, XBT_NULL, + GCSPRINTF("%s/num-ports", be_path), + &num_devs); + if (rc) goto out; + + n = num_devs ? atoi(num_devs) : 0; + + for (i = 0; i < n; i++) { + const char *busid; + libxl_device_usbdev *usbdev; + + rc = libxl__xs_read_checked(gc, XBT_NULL, + GCSPRINTF("%s/port/%d", be_path, i + 1), + &busid); + if (rc) goto out; + + if (busid && strcmp(busid, "")) { + GCREALLOC_ARRAY(*usbdevs, *num + 1); + usbdev = *usbdevs + *num; + (*num)++; + libxl_device_usbdev_init(usbdev); + usbdev->ctrl = usbctrl; + usbdev->port = i + 1; + usbdev->type = LIBXL_USBDEV_TYPE_HOSTDEV; + rc = usbdev_busaddr_from_busid(gc, busid, + &usbdev->u.hostdev.hostbus, + &usbdev->u.hostdev.hostaddr); + if (rc) goto out; + } + } + + rc = 0; + +out: + return rc; +} + +/* get all usb devices of the domain */ +libxl_device_usbdev * +libxl_device_usbdev_list(libxl_ctx *ctx, uint32_t domid, int *num) +{ + GC_INIT(ctx); + libxl_device_usbdev *usbdevs = NULL; + const char *path; + char **usbctrls; + unsigned int nc = 0; + int i, j; + + *num = 0; + + path = GCSPRINTF("%s/device/vusb", + libxl__xs_get_dompath(gc, domid)); + usbctrls = libxl__xs_directory(gc, XBT_NULL, path, &nc); + + for (i = 0; i < nc; i++) { + int r, nd = 0; + libxl_device_usbdev *tmp = NULL; + + r = libxl__device_usbdev_list_for_usbctrl(gc, domid, + atoi(usbctrls[i]), + &tmp, &nd); + if (!r || !nd) continue; + + usbdevs = libxl__realloc(NOGC, usbdevs, + sizeof(*usbdevs) * (*num + nd)); + for (j = 0; j < nd; j++) { + libxl_device_usbdev_copy(ctx, usbdevs + *num, tmp + j); + (*num)++; + } + } + + GC_FREE; + return usbdevs; +} + +/* find first unused controller:port and give that to usb device */ +static int +libxl__device_usbdev_set_default_usbctrl(libxl__gc *gc, uint32_t domid, + libxl_device_usbdev *usbdev) +{ + libxl_device_usbctrl *usbctrls = NULL; + int numctrl = 0; + int i, j, rc; + + usbctrls = libxl_device_usbctrl_list(CTX, domid, &numctrl); + if (!numctrl || !usbctrls) { + rc = ERROR_FAIL; + goto out; + } + + rc = ERROR_FAIL; + for (i = 0; i < numctrl; i++) { + for (j = 0; j < usbctrls[i].ports; j++) { + const char *path, *tmp; + + path = GCSPRINTF("%s/backend/vusb/%d/%d/port/%d", + libxl__xs_get_dompath(gc, LIBXL_TOOLSTACK_DOMID), + domid, usbctrls[i].devid, j + 1); + tmp = libxl__xs_read(gc, XBT_NULL, path); + if (tmp && !strcmp(tmp, "")) { + usbdev->ctrl = usbctrls[i].devid; + usbdev->port = j + 1; + rc = 0; + goto out; + } + } + } + +out: + libxl_device_usbctrl_list_free(usbctrls, numctrl); + return rc; +} + +/* Fill in usb information with default value. + * + * Generally, it does: + * 1) if "controller" is not specified: + * - if "port" is not specified, try to find an available controller:port, + * if found, use that; otherwise, create a new controller, use this + * controller and its first port + * - if "port" is specified, report error. + * 2) if "controller" is specified, but port is not specified: + * try to find an available port under this controller, if found, use + * that, otherwise, report error. + * 3) if both "controller" and "port" are specified: + * check the controller:port is available, if not, report error. + */ +static int libxl__device_usbdev_setdefault(libxl__gc *gc, + uint32_t domid, + libxl_device_usbdev *usbdev, + bool update_json) +{ + int rc; + + if (!usbdev->type) + usbdev->type = LIBXL_USBDEV_TYPE_HOSTDEV; + + if (usbdev->ctrl == -1) { + if (usbdev->port) { + LOG(ERROR, "USB controller must be specified if you specify port"); + return ERROR_INVAL; + } + + rc = libxl__device_usbdev_set_default_usbctrl(gc, domid, usbdev); + /* If no existing controller to host this usb device, add a new one */ + if (rc) { + libxl_device_usbctrl *usbctrl; + + GCNEW(usbctrl); + libxl_device_usbctrl_init(usbctrl); + rc = libxl__device_usbctrl_setdefault(gc, domid, usbctrl); + if (rc < 0) goto out; + + if (usbctrl->devid == -1) { + usbctrl->devid = libxl__device_nextid(gc, domid, "vusb"); + if (usbctrl->devid < 0) { + rc = ERROR_FAIL; + goto out; + } + } + + rc = libxl__device_usbctrl_add_xenstore(gc, domid, usbctrl, + update_json); + if (rc) goto out; + + usbdev->ctrl = usbctrl->devid; + usbdev->port = 1; + } + } else { + /* A controller was specified; look it up */ + const char *fe_path, *be_path, *tmp; + + fe_path = GCSPRINTF("%s/device/vusb/%d", + libxl__xs_get_dompath(gc, domid), + usbdev->ctrl); + + be_path = vusb_be_from_xs_fe(gc, fe_path, domid); + if (!be_path) { + rc = ERROR_FAIL; + goto out; + } + + if (usbdev->port) { + /* A specific port was requested; see if it's available */ + rc = libxl__xs_read_checked(gc, XBT_NULL, + GCSPRINTF("%s/port/%d", + be_path, usbdev->port), + &tmp); + if (rc) goto out; + + if (tmp && strcmp(tmp, "")) { + LOG(ERROR, "The controller port isn't available"); + rc = ERROR_FAIL; + goto out; + } + } else { + /* No port was requested. Choose free port. */ + int i, ports; + + rc = libxl__xs_read_checked(gc, XBT_NULL, + GCSPRINTF("%s/num-ports", be_path), &tmp); + if (rc) goto out; + + ports = tmp ? atoi(tmp) : 0; + + for (i = 0; i < ports; i++) { + rc = libxl__xs_read_checked(gc, XBT_NULL, + GCSPRINTF("%s/port/%d", be_path, i + 1), + &tmp); + if (rc) goto out; + + if (tmp && !strcmp(tmp, "")) { + usbdev->port = i + 1; + break; + } + } + + if (!usbdev->port) { + LOG(ERROR, "No available port under specified controller"); + rc = ERROR_FAIL; + goto out; + } + } + } + + rc = 0; + +out: + return rc; +} + +/* Add usb information to xenstore + * + * Adding a usb device won't create new 'vusb' device, but only write + * the device busid to the controller:port in xenstore. + */ +static int libxl__device_usbdev_add_xenstore(libxl__gc *gc, uint32_t domid, + libxl_device_usbdev *usbdev, + bool update_json) +{ + char *be_path, *busid; + int rc; + xs_transaction_t t = XBT_NULL; + libxl_domain_config d_config; + libxl_device_usbdev usbdev_saved; + libxl__domain_userdata_lock *lock = NULL; + + libxl_domain_config_init(&d_config); + libxl_device_usbdev_init(&usbdev_saved); + libxl_device_usbdev_copy(CTX, &usbdev_saved, usbdev); + + busid = usbdev_busaddr_to_busid(gc, usbdev->u.hostdev.hostbus, + usbdev->u.hostdev.hostaddr); + if (!busid) { + LOG(DEBUG, "Fail to get busid of usb device"); + goto out; + } + + if (update_json) { + lock = libxl__lock_domain_userdata(gc, domid); + if (!lock) { + rc = ERROR_LOCK_FAIL; + goto out; + } + + rc = libxl__get_domain_configuration(gc, domid, &d_config); + if (rc) goto out; + + DEVICE_ADD(usbdev, usbdevs, domid, &usbdev_saved, + COMPARE_USB, &d_config); + } + + for (;;) { + rc = libxl__xs_transaction_start(gc, &t); + if (rc) goto out; + + if (update_json) { + rc = libxl__set_domain_configuration(gc, domid, &d_config); + if (rc) goto out; + } + + be_path = GCSPRINTF("%s/backend/vusb/%d/%d/port/%d", + libxl__xs_get_dompath(gc, LIBXL_TOOLSTACK_DOMID), + domid, usbdev->ctrl, usbdev->port); + + LOG(DEBUG, "Adding usb device %s to xenstore: controller %d, port %d", + busid, usbdev->ctrl, usbdev->port); + + if (libxl__xs_write_checked(gc, t, be_path, busid)) + goto out; + + rc = libxl__xs_transaction_commit(gc, &t); + if (!rc) break; + if (rc < 0) goto out; + } + + rc = 0; + +out: + if (lock) libxl__unlock_domain_userdata(lock); + libxl_device_usbdev_dispose(&usbdev_saved); + libxl_domain_config_dispose(&d_config); + return rc; +} + +static int libxl__device_usbdev_remove_xenstore(libxl__gc *gc, uint32_t domid, + libxl_device_usbdev *usbdev) +{ + char *be_path; + + be_path = GCSPRINTF("%s/backend/vusb/%d/%d/port/%d", + libxl__xs_get_dompath(gc, LIBXL_TOOLSTACK_DOMID), + domid, usbdev->ctrl, usbdev->port); + + LOG(DEBUG, "Removing usb device from xenstore: controller %d, port %d", + usbdev->ctrl, usbdev->port); + + return libxl__xs_write_checked(gc, XBT_NULL, be_path, ""); +} + +static char *usbdev_busid_from_ctrlport(libxl__gc *gc, uint32_t domid, + libxl_device_usbdev *usbdev) +{ + return libxl__xs_read(gc, XBT_NULL, + GCSPRINTF("%s/backend/vusb/%d/%d/port/%d", + libxl__xs_get_dompath(gc, LIBXL_TOOLSTACK_DOMID), + domid, usbdev->ctrl, usbdev->port)); +} + +/* get original driver path of usb interface, stored in @drvpath */ +static int usbintf_get_drvpath(libxl__gc *gc, const char *intf, char **drvpath) +{ + char *spath, *dp = NULL; + struct stat st; + int rc; + + spath = GCSPRINTF(SYSFS_USB_DEV "/%s/driver", intf); + + rc = lstat(spath, &st); + if (rc == 0) { + /* Find the canonical path to the driver. */ + dp = libxl__zalloc(gc, PATH_MAX); + dp = realpath(spath, dp); + } else if (errno != ENOENT) { + LOGE(ERROR, "lstat failed: '%s'", spath); + return ERROR_FAIL; + } + + *drvpath = dp; + + return 0; +} + +static int sysfs_write_intf(libxl__gc *gc, const char *intf, const char *path) +{ + int rc, fd = -1; + + fd = open(path, O_WRONLY); + if (fd < 0) { + LOGE(ERROR, "open file failed: '%s'", path); + return ERROR_FAIL; + } + + rc = write(fd, intf, strlen(intf)); + close(fd); + if (rc < 0) { + LOGE(ERROR, "write '%s' to '%s' failed", intf, path); + return ERROR_FAIL; + } + if (rc != strlen(intf)) { + LOG(ERROR, "write '%s' to '%s' failed: incorrect write count", + intf, path); + return ERROR_FAIL; + } + + return 0; +} + +static int unbind_usbintf(libxl__gc *gc, const char *intf) +{ + char *path; + + path = GCSPRINTF(SYSFS_USB_DEV "/%s/driver/unbind", intf); + return sysfs_write_intf(gc, intf, path); +} + +static int bind_usbintf(libxl__gc *gc, const char *intf, const char *drvpath) +{ + char *path; + struct stat st; + + path = GCSPRINTF("%s/%s", drvpath, intf); + /* if already bound, return */ + if (!lstat(path, &st)) + return 0; + + path = GCSPRINTF("%s/bind", drvpath); + return sysfs_write_intf(gc, intf, path); +} + +/* Is usb interface bound to usbback? */ +static int usbintf_is_assigned(libxl__gc *gc, char *intf) +{ + char *spath; + int rc; + struct stat st; + + spath = GCSPRINTF(SYSFS_USBBACK_DRIVER "/%s", intf); + rc = lstat(spath, &st); + + if (rc == 0) + return 1; + if (rc < 0 && errno == ENOENT) + return 0; + LOGE(ERROR, "Accessing %s", spath); + return -1; +} + +static int usbdev_get_all_interfaces(libxl__gc *gc, const char *busid, + char ***intfs, int *num) +{ + DIR *dir; + char *buf; + struct dirent *de_buf; + struct dirent *de; + int rc; + + *intfs = NULL; + *num = 0; + + buf = GCSPRINTF("%s:", busid); + + dir = opendir(SYSFS_USB_DEV); + if (!dir) { + LOGE(ERROR, "opendir failed: '%s'", SYSFS_USB_DEV); + return ERROR_FAIL; + } + + de_buf = zalloc_dirent(gc, SYSFS_USB_DEV); + + for (;;) { + int r = readdir_r(dir, de_buf, &de); + + if (r) { + LOGE(ERROR, "failed to readdir %s", SYSFS_USB_DEV); + rc = ERROR_FAIL; + goto out; + } + if (!de) + break; + + if (!strcmp(de->d_name, ".") || + !strcmp(de->d_name, "..")) + continue; + + if (!strncmp(de->d_name, buf, strlen(buf))) { + GCREALLOC_ARRAY(*intfs, *num + 1); + (*intfs)[*num] = libxl__strdup(gc, de->d_name); + (*num)++; + } + } + + rc = 0; + +out: + closedir(dir); + return rc; +} + +/* Encode usb interface so that it could be written to xenstore as a key. + * + * Since xenstore key cannot include '.' or ':', we'll change '.' to '_', + * change ':' to '@'. For example, 3-1:2.1 will be encoded to 3-1@2_1. + * This will be used to save original driver of USB device to xenstore. + */ +static char *usb_interface_xenstore_encode(libxl__gc *gc, const char *busid) +{ + char *str = libxl__strdup(gc, busid); + int i, len = strlen(str); + + for (i = 0; i < len; i++) { + if (str[i] == '.') + str[i] = '_'; + if (str[i] == ':') + str[i] = '@'; + } + return str; +} + +/* Unbind USB device from "usbback" driver. + * + * If there are many interfaces under USB device, check each interface, + * unbind from "usbback" driver and rebind to its original driver. + */ +static int usbback_dev_unassign(libxl__gc *gc, const char *busid) +{ + char **intfs = NULL; + char *usbdev_encode = NULL; + char *path = NULL; + int i, num = 0; + int rc; + + if (usbdev_get_all_interfaces(gc, busid, &intfs, &num) < 0) + return ERROR_FAIL; + + usbdev_encode = usb_interface_xenstore_encode(gc, busid); + + for (i = 0; i < num; i++) { + char *intf = intfs[i]; + char *usbintf_encode = NULL; + const char *drvpath; + + /* check if the USB interface is already bound to "usbback" */ + if (usbintf_is_assigned(gc, intf) > 0) { + /* unbind interface from usbback driver */ + if (unbind_usbintf(gc, intf) < 0) { + rc = ERROR_FAIL; + goto out; + } + } + + /* try to rebind USB interface to its originial driver. + * If rebinding failed, export warning so that user can + * handle it later. + */ + usbintf_encode = usb_interface_xenstore_encode(gc, intf); + path = GCSPRINTF(USBBACK_INFO_PATH "/%s/%s/driver_path", + usbdev_encode, usbintf_encode); + rc = libxl__xs_read_checked(gc, XBT_NULL, path, &drvpath); + if (rc) continue; + + if (drvpath && bind_usbintf(gc, intf, drvpath)) + LOGE(WARN, "Couldn't rebind %s to %s", intf, drvpath); + } + + /* finally, remove xenstore driver path */ + path = GCSPRINTF(USBBACK_INFO_PATH "/%s", usbdev_encode); + libxl__xs_rm_checked(gc, XBT_NULL, path); + rc = 0; + +out: + return rc; +} + +/* Bind USB device to "usbback" driver. + * + * If there are many interfaces under USB device, check each interface, + * unbind from original driver and bind to "usbback" driver. + */ +static int usbback_dev_assign(libxl__gc *gc, const char *busid) +{ + char **intfs = NULL; + int num = 0, i; + int rc; + char *usbdev_encode = NULL; + + rc = usbdev_get_all_interfaces(gc, busid, &intfs, &num); + if (rc) + return rc; + + usbdev_encode = usb_interface_xenstore_encode(gc, busid); + + for (i = 0; i < num; i++) { + char *intf = intfs[i]; + char *drvpath = NULL; + + /* already assigned to usbback */ + if (usbintf_is_assigned(gc, intf) > 0) + continue; + + rc = usbintf_get_drvpath(gc, intf, &drvpath); + if (rc < 0) goto out; + + if (drvpath) { + /* write driver path to xenstore for later rebinding */ + char *usbintf_encode = NULL; + char *path; + + usbintf_encode = usb_interface_xenstore_encode(gc, intf); + path = GCSPRINTF(USBBACK_INFO_PATH "/%s/%s/driver_path", + usbdev_encode, usbintf_encode); + if (libxl__xs_write_checked(gc, XBT_NULL, path, drvpath) < 0) + goto out; + + /* unbind interface from original driver */ + if (unbind_usbintf(gc, intf) < 0) { + rc = ERROR_FAIL; + goto out; + } + } + + /* bind interface to usbback */ + if (bind_usbintf(gc, intf, SYSFS_USBBACK_DRIVER) < 0) { + LOG(ERROR, "Couldn't bind %s to %s", intf, SYSFS_USBBACK_DRIVER); + rc = ERROR_FAIL; + goto out; + } + } + + return 0; + +out: + /* some interfaces might be bound to usbback, unbind it and + * rebind it to its original driver + */ + usbback_dev_unassign(gc, busid); + return rc; +} + +static int do_usbdev_add(libxl__gc *gc, uint32_t domid, + libxl_device_usbdev *usbdev, + bool update_json) +{ + int rc; + char *busid; + libxl_device_usbctrl usbctrl; + libxl_usbctrlinfo usbctrlinfo; + + libxl_device_usbctrl_init(&usbctrl); + libxl_usbctrlinfo_init(&usbctrlinfo); + usbctrl.devid = usbdev->ctrl; + + rc = libxl_device_usbctrl_getinfo(CTX, domid, &usbctrl, &usbctrlinfo); + if (rc) goto out; + + switch (usbctrlinfo.type) { + case LIBXL_USBCTRL_TYPE_PV: + busid = usbdev_busaddr_to_busid(gc, usbdev->u.hostdev.hostbus, + usbdev->u.hostdev.hostaddr); + if (!busid) { + rc = ERROR_FAIL; + goto out; + } + + rc = libxl__device_usbdev_add_xenstore(gc, domid, usbdev, update_json); + if (rc) goto out; + + rc = usbback_dev_assign(gc, busid); + if (rc) { + libxl__device_usbdev_remove_xenstore(gc, domid, usbdev); + goto out; + } + break; + case LIBXL_USBCTRL_TYPE_DEVICEMODEL: + default: + LOG(ERROR, "Unsupported usb controller type"); + rc = ERROR_FAIL; + goto out; + } + + rc = 0; + +out: + libxl_device_usbctrl_dispose(&usbctrl); + libxl_usbctrlinfo_dispose(&usbctrlinfo); + return rc; +} + +/* AO operation to add a usb device. + * + * Generally, it does: + * 1) check if the usb device type is assignable + * 2) check if the usb device is already assigned to a domain + * 3) add 'busid' of the usb device to xenstore contoller/port/. + * (PVUSB driver watches the xenstore changes and will detect that.) + * 4) unbind usb device from original driver and bind to usbback. + * If usb device has many interfaces, then: + * - unbind each interface from its original driver and bind to usbback. + * - store the original driver to xenstore for later rebinding when + * detaching the device. + * + * Before calling this function, aodev should be properly filled: + * aodev->ao, aodev->callback, aodev->update_json, ... + */ +void libxl__device_usbdev_add(libxl__egc *egc, uint32_t domid, + libxl_device_usbdev *usbdev, + libxl__ao_device *aodev) +{ + STATE_AO_GC(aodev->ao); + int rc; + libxl_device_usbdev *assigned; + int num_assigned; + libxl_device_usbctrl usbctrl; + libxl_usbctrlinfo usbctrlinfo; + + libxl_device_usbctrl_init(&usbctrl); + libxl_usbctrlinfo_init(&usbctrlinfo); + + /* Currently only support adding USB device from Dom0 backend. + * So, if USB controller is specified, check its backend domain, + * if it's not Dom0, report error. + */ + if (usbdev->ctrl != -1) { + usbctrl.devid = usbdev->ctrl; + rc = libxl_device_usbctrl_getinfo(CTX, domid, &usbctrl, &usbctrlinfo); + if (rc) goto out; + + if (usbctrlinfo.backend_id != LIBXL_TOOLSTACK_DOMID) { + LOG(ERROR, "Don't support adding USB device from non-Dom0 backend"); + rc = ERROR_INVAL; + goto out; + } + } + + /* check usb device is assignable type */ + if (!is_usbdev_assignable(gc, usbdev)) { + LOG(ERROR, "USB device is not assignable."); + rc = ERROR_FAIL; + goto out; + } + + /* check usb device is already assigned */ + rc = get_assigned_devices(gc, &assigned, &num_assigned); + if (rc) { + LOG(ERROR, "cannot determine if device is assigned," + " refusing to continue"); + goto out; + } + + if (is_usbdev_in_array(assigned, num_assigned, usbdev)) { + LOG(ERROR, "USB device already attached to a domain"); + rc = ERROR_FAIL; + goto out; + } + + /* fill default values, e.g, if usbdev->ctrl and usbdev->port + * not specified, choose available controller:port and fill in. */ + rc = libxl__device_usbdev_setdefault(gc, domid, usbdev, + aodev->update_json); + if (rc) goto out; + + /* do actual adding usb device operation */ + rc = do_usbdev_add(gc, domid, usbdev, aodev->update_json); + +out: + libxl_device_usbctrl_dispose(&usbctrl); + libxl_usbctrlinfo_dispose(&usbctrlinfo); + aodev->rc = rc; + aodev->callback(egc, aodev); + return; +} + +static int do_usbdev_remove(libxl__gc *gc, uint32_t domid, + libxl_device_usbdev *usbdev) +{ + int rc; + char *busid; + libxl_device_usbctrl usbctrl; + libxl_usbctrlinfo usbctrlinfo; + + libxl_device_usbctrl_init(&usbctrl); + libxl_usbctrlinfo_init(&usbctrlinfo); + usbctrl.devid = usbdev->ctrl; + + rc = libxl_device_usbctrl_getinfo(CTX, domid, &usbctrl, &usbctrlinfo); + if (rc) goto out; + + switch (usbctrlinfo.type) { + case LIBXL_USBCTRL_TYPE_PV: + busid = usbdev_busid_from_ctrlport(gc, domid, usbdev); + if (!busid) { + rc = ERROR_FAIL; + goto out; + } + + rc = libxl__device_usbdev_remove_xenstore(gc, domid, usbdev); + if (rc) goto out; + + rc = usbback_dev_unassign(gc, busid); + if (rc) { + /* Till here, usb device information is already removed + * from xenstore, usb list couldn't list it any more. + * If unassign usb device from usbback failed, export + * warning only so that user could handle driver status + * later. + */ + LOG(WARN, "unbind usb device from usbback and rebind to its " + "original driver failed"); + } + break; + case LIBXL_USBCTRL_TYPE_DEVICEMODEL: + default: + LOG(ERROR, "Unsupported usb controller type"); + rc = ERROR_FAIL; + goto out; + } + + rc = 0; + +out: + libxl_device_usbctrl_dispose(&usbctrl); + libxl_usbctrlinfo_dispose(&usbctrlinfo); + return rc; +} + +/* Operation to remove usb device. + * + * Generally, it does: + * 1) check if the usb device is assigned to the domain + * 2) remove the usb device from xenstore controller/port. + * 3) unbind usb device from usbback and rebind to its original driver. + * If usb device has many interfaces, do it to each interface. + */ +static int libxl__device_usbdev_remove(libxl__gc *gc, uint32_t domid, + libxl_device_usbdev *usbdev) +{ + libxl_usbctrlinfo usbctrlinfo; + libxl_device_usbctrl usbctrl; + int rc; + + if (usbdev->ctrl < 0 || usbdev->port < 1) { + LOG(ERROR, "Invalid USB device"); + return ERROR_FAIL; + } + + libxl_device_usbctrl_init(&usbctrl); + libxl_usbctrlinfo_init(&usbctrlinfo); + usbctrl.devid = usbdev->ctrl; + + rc = libxl_device_usbctrl_getinfo(CTX, domid, &usbctrl, &usbctrlinfo); + if (rc) goto out; + + if (usbctrlinfo.backend_id != LIBXL_TOOLSTACK_DOMID) { + LOG(ERROR, "Don't support removing USB device from non-Dom0 backend"); + rc = ERROR_INVAL; + goto out; + } + + /* do actual removing usb device operation */ + rc = do_usbdev_remove(gc, domid, usbdev); + +out: + libxl_device_usbctrl_dispose(&usbctrl); + libxl_usbctrlinfo_dispose(&usbctrlinfo); + return rc; +} + +int libxl_device_usbdev_remove(libxl_ctx *ctx, uint32_t domid, + libxl_device_usbdev *usbdev, + const libxl_asyncop_how *ao_how) + +{ + AO_CREATE(ctx, domid, ao_how); + int rc; + + rc = libxl__device_usbdev_remove(gc, domid, usbdev); + + libxl__ao_complete(egc, ao, rc); + return AO_INPROGRESS; +} + +int libxl_ctrlport_to_device_usbdev(libxl_ctx *ctx, + uint32_t domid, + int ctrl, + int port, + libxl_device_usbdev *usbdev) +{ + GC_INIT(ctx); + const char *dompath, *fe_path, *be_path, *busid; + int rc; + + dompath = libxl__xs_get_dompath(gc, domid); + + fe_path = GCSPRINTF("%s/device/vusb/%d", dompath, ctrl); + + be_path = vusb_be_from_xs_fe(gc, fe_path, domid); + if (!be_path) { + rc = ERROR_FAIL; + goto out; + } + + rc = libxl__xs_read_checked(gc, XBT_NULL, + GCSPRINTF("%s/port/%d", be_path, port), + &busid); + if (rc) goto out; + + if (!busid || !strcmp(busid, "")) { + rc = ERROR_FAIL; + goto out; + } + + usbdev->ctrl = ctrl; + usbdev->port = port; + usbdev->type = LIBXL_USBDEV_TYPE_HOSTDEV; + rc = usbdev_busaddr_from_busid(gc, busid, + &usbdev->u.hostdev.hostbus, + &usbdev->u.hostdev.hostaddr); + +out: + GC_FREE; + return rc; +} +/* + * Local variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/tools/libxl/libxl_types.idl b/tools/libxl/libxl_types.idl index 9658356..befee94 100644 --- a/tools/libxl/libxl_types.idl +++ b/tools/libxl/libxl_types.idl @@ -606,6 +606,35 @@ libxl_device_rdm = Struct("device_rdm", [ ("policy", libxl_rdm_reserve_policy), ]) +libxl_usbctrl_type = Enumeration("usbctrl_type", [ + (0, "AUTO"), + (1, "PV"), + (2, "DEVICEMODEL"), + ]) + +libxl_usbdev_type = Enumeration("usbdev_type", [ + (1, "hostdev"), + ]) + +libxl_device_usbctrl = Struct("device_usbctrl", [ + ("type", libxl_usbctrl_type), + ("devid", libxl_devid), + ("version", integer), + ("ports", integer), + ("backend_domid", libxl_domid), + ("backend_domname", string), + ]) + +libxl_device_usbdev = Struct("device_usbdev", [ + ("ctrl", libxl_devid), + ("port", integer), + ("u", KeyedUnion(None, libxl_usbdev_type, "type", + [("hostdev", Struct(None, [ + ("hostbus", uint8), + ("hostaddr", uint8)])), + ])), + ]) + libxl_device_dtdev = Struct("device_dtdev", [ ("path", string), ]) @@ -644,6 +673,8 @@ libxl_domain_config = Struct("domain_config", [ # a channel manifests as a console with a name, # see docs/misc/channels.txt ("channels", Array(libxl_device_channel, "num_channels")), + ("usbctrls", Array(libxl_device_usbctrl, "num_usbctrls")), + ("usbdevs", Array(libxl_device_usbdev, "num_usbdevs")), ("on_poweroff", libxl_action_on_shutdown), ("on_reboot", libxl_action_on_shutdown), @@ -687,6 +718,21 @@ libxl_vtpminfo = Struct("vtpminfo", [ ("uuid", libxl_uuid), ], dir=DIR_OUT) +libxl_usbctrlinfo = Struct("usbctrlinfo", [ + ("type", libxl_usbctrl_type), + ("devid", libxl_devid), + ("version", integer), + ("ports", integer), + ("backend", string), + ("backend_id", uint32), + ("frontend", string), + ("frontend_id", uint32), + ("state", integer), + ("evtch", integer), + ("ref_urb", integer), + ("ref_conn", integer), + ], dir=DIR_OUT) + libxl_vcpuinfo = Struct("vcpuinfo", [ ("vcpuid", uint32), ("cpu", uint32), diff --git a/tools/libxl/libxl_types_internal.idl b/tools/libxl/libxl_types_internal.idl index 5e55685..696f5f8 100644 --- a/tools/libxl/libxl_types_internal.idl +++ b/tools/libxl/libxl_types_internal.idl @@ -22,6 +22,7 @@ libxl__device_kind = Enumeration("device_kind", [ (6, "VKBD"), (7, "CONSOLE"), (8, "VTPM"), + (9, "VUSB"), ]) libxl__console_backend = Enumeration("console_backend", [ diff --git a/tools/libxl/libxl_utils.c b/tools/libxl/libxl_utils.c index e64f301..94dac4e 100644 --- a/tools/libxl/libxl_utils.c +++ b/tools/libxl/libxl_utils.c @@ -1311,6 +1311,24 @@ int libxl__random_bytes(libxl__gc *gc, uint8_t *buf, size_t len) return ret; } +void libxl_device_usbctrl_list_free(libxl_device_usbctrl *list, int nr) +{ + int i; + + for (i = 0; i < nr; i++) + libxl_device_usbctrl_dispose(&list[i]); + free(list); +} + +void libxl_device_usbdev_list_free(libxl_device_usbdev *list, int nr) +{ + int i; + + for (i = 0; i < nr; i++) + libxl_device_usbdev_dispose(&list[i]); + free(list); +} + /* * Local variables: * mode: C diff --git a/tools/libxl/libxl_utils.h b/tools/libxl/libxl_utils.h index 339ebdf..4495417 100644 --- a/tools/libxl/libxl_utils.h +++ b/tools/libxl/libxl_utils.h @@ -76,6 +76,11 @@ int libxl_uuid_to_device_vtpm(libxl_ctx *ctx, uint32_t domid, libxl_uuid *uuid, libxl_device_vtpm *vtpm); int libxl_devid_to_device_vtpm(libxl_ctx *ctx, uint32_t domid, int devid, libxl_device_vtpm *vtpm); +int libxl_devid_to_device_usbctrl(libxl_ctx *ctx, uint32_t domid, + int devid, libxl_device_usbctrl *usbctrl); +int libxl_ctrlport_to_device_usbdev(libxl_ctx *ctx, uint32_t domid, + int ctrl, int port, + libxl_device_usbdev *usbdev); int libxl_bitmap_alloc(libxl_ctx *ctx, libxl_bitmap *bitmap, int n_bits); /* Allocated bimap is from malloc, libxl_bitmap_dispose() to be