diff mbox

[V14,4/6] libxl: add pvusb API

Message ID 1455878393-31807-5-git-send-email-cyliu@suse.com (mailing list archive)
State New, archived
Headers show

Commit Message

Chunyan Liu Feb. 19, 2016, 10:39 a.m. UTC
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: Simon Cao <caobosimon@gmail.com>
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
Signed-off-by: Chunyan Liu <cyliu@suse.com>
---
changes:
  Address Olaf's comments:
  * move DEFINE_DEVICE_REMOVE changes to a separate patch
  Address Ian's comments:
  * adjust order of removing xenstore and bind/unbind driver in usb_remove.
  * reuse libxl_write_exactly in usbintf_bind/unbind
  * several coding style fix

 tools/libxl/Makefile                 |    2 +-
 tools/libxl/libxl.c                  |   18 +
 tools/libxl/libxl.h                  |   77 ++
 tools/libxl/libxl_device.c           |    5 +-
 tools/libxl/libxl_internal.h         |   18 +
 tools/libxl/libxl_osdeps.h           |   13 +
 tools/libxl/libxl_pvusb.c            | 1567 ++++++++++++++++++++++++++++++++++
 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, 1768 insertions(+), 2 deletions(-)
 create mode 100644 tools/libxl/libxl_pvusb.c

Comments

George Dunlap Feb. 23, 2016, 5:10 p.m. UTC | #1
On Fri, Feb 19, 2016 at 10:39 AM, Chunyan Liu <cyliu@suse.com> wrote:
> 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: Simon Cao <caobosimon@gmail.com>
> Signed-off-by: George Dunlap <george.dunlap@citrix.com>
> Signed-off-by: Chunyan Liu <cyliu@suse.com>
> ---
> changes:
>   Address Olaf's comments:
>   * move DEFINE_DEVICE_REMOVE changes to a separate patch
>   Address Ian's comments:
>   * adjust order of removing xenstore and bind/unbind driver in usb_remove.
>   * reuse libxl_write_exactly in usbintf_bind/unbind
>   * several coding style fix

[snip]

> +/* 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;
> +
> +    rc = usbdev_get_all_interfaces(gc, busid, &intfs, &num);
> +    if (rc) goto out;
> +
> +    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 */
> +            rc = unbind_usbintf(gc, intf);
> +            if (rc) {
> +                LOGE(ERROR, "Couldn't unbind %s from usbback", intf);
> +                goto out;
> +            }
> +        }
> +
> +        /* rebind USB interface to its originial driver */
> +        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) goto out;
> +
> +        if (drvpath) {
> +            rc = bind_usbintf(gc, intf, drvpath);
> +            if (rc) {
> +                LOGE(ERROR, "Couldn't rebind %s to %s", intf, drvpath);
> +                goto out;
> +            }
> +        }
> +    }

So I see below that you're calling this before removing things from
xenstore, so that if any of these fail, the user can still call "xl
usb-remove" to retry.

Unfortunately, since you reassign interfaces to the original driver
before all interfaces are de-assigned from usbback, you can end up in
a situation where the device is partially re-plugged into the original
drivers, partially still assigned to pciback.

I think this whole process should be three steps:

1. Unassign all interfaces from usbback, stopping and returning an
error as soon as one attempt fails

2. Remove the pvusb xenstore nodes (stopping and returning an error if it fails)

3. Attempt to re-assign all interfaces to the original drivers,
stopping and returning an error as soon as one attempt fails.

And in the case of #3, the log message should give a suggestion as to
what might help; for instance, "Couldn't rebind USB device to driver
[driver name].  Reloading the module may help."  (I think you should
be able to get the driver name from the path, right?)

A couple of properties this gives us:

* If the un-assign partially succeeds, the user can re-try the unassign

* If one of the re-assignments fail, then the user will have to reload
the drivers, reboot, or mess around with sysfs to fix things.
*However*, this avoids a scenario where a user is completely unable to
remove a device from a domain because of a buggy driver.

What do you think?

I realize this falls short of the "crash-only" design IanJ suggested
we try to do, but I think that in this case the work required to do
such a design would be a lot more work than the benefit it gives us.

I haven't re-reviewed the whole patch, but all the other changes look
good to me.

 -George
Chunyan Liu Feb. 26, 2016, 5:56 a.m. UTC | #2
>>> On 2/24/2016 at 01:10 AM, in message
<CAFLBxZYrdhLO-euMioWiitqh7+FDUKc7rxO3g=m=-3tPGFuRxw@mail.gmail.com>, George
Dunlap <George.Dunlap@eu.citrix.com> wrote: 
> On Fri, Feb 19, 2016 at 10:39 AM, Chunyan Liu <cyliu@suse.com> wrote: 
> > 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: Simon Cao <caobosimon@gmail.com> 
> > Signed-off-by: George Dunlap <george.dunlap@citrix.com> 
> > Signed-off-by: Chunyan Liu <cyliu@suse.com> 
> > --- 
> > changes: 
> >   Address Olaf's comments: 
> >   * move DEFINE_DEVICE_REMOVE changes to a separate patch 
> >   Address Ian's comments: 
> >   * adjust order of removing xenstore and bind/unbind driver in usb_remove. 
> >   * reuse libxl_write_exactly in usbintf_bind/unbind 
> >   * several coding style fix 
>  
> [snip] 
>  
> > +/* 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; 
> > + 
> > +    rc = usbdev_get_all_interfaces(gc, busid, &intfs, &num); 
> > +    if (rc) goto out; 
> > + 
> > +    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 */ 
> > +            rc = unbind_usbintf(gc, intf); 
> > +            if (rc) { 
> > +                LOGE(ERROR, "Couldn't unbind %s from usbback", intf); 
> > +                goto out; 
> > +            } 
> > +        } 
> > + 
> > +        /* rebind USB interface to its originial driver */ 
> > +        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) goto out; 
> > + 
> > +        if (drvpath) { 
> > +            rc = bind_usbintf(gc, intf, drvpath); 
> > +            if (rc) { 
> > +                LOGE(ERROR, "Couldn't rebind %s to %s", intf, drvpath); 
> > +                goto out; 
> > +            } 
> > +        } 
> > +    } 
>  
> So I see below that you're calling this before removing things from 
> xenstore, so that if any of these fail, the user can still call "xl 
> usb-remove" to retry. 
>  
> Unfortunately, since you reassign interfaces to the original driver 
> before all interfaces are de-assigned from usbback, you can end up in 
> a situation where the device is partially re-plugged into the original 
> drivers, partially still assigned to pciback. 
>  
> I think this whole process should be three steps: 
>  
> 1. Unassign all interfaces from usbback, stopping and returning an 
> error as soon as one attempt fails 
>  
> 2. Remove the pvusb xenstore nodes (stopping and returning an error if it  
> fails) 
>  
> 3. Attempt to re-assign all interfaces to the original drivers, 
> stopping and returning an error as soon as one attempt fails. 
>  
> And in the case of #3, the log message should give a suggestion as to 
> what might help; for instance, "Couldn't rebind USB device to driver 
> [driver name].  Reloading the module may help."  (I think you should 
> be able to get the driver name from the path, right?) 

Yes, that's right.

>  
> A couple of properties this gives us: 
>  
> * If the un-assign partially succeeds, the user can re-try the unassign 
>  
> * If one of the re-assignments fail, then the user will have to reload 
> the drivers, reboot, or mess around with sysfs to fix things. 
> *However*, this avoids a scenario where a user is completely unable to 
> remove a device from a domain because of a buggy driver. 

To guest or host, this situation that some interface is bound to pciback
but some is to original driver, is indeed a mess. But to toolstack, it can
still have chance to redo 'xl usbdev-remove' to retry the left work (unassign
interfaces those bound to pciback, and reassign to original interface).

>  
> What do you think? 

The 3 steps above are indeed more reasonable. Codes need to be
reorganized (previously doing unassingn_form_pciback and rebind to
original driver in a same cycle for each interface, now will be split into
two separate processes: walk through each interface to do
unassign_from_pciback, and walk through each interface to rebind to
original driver).

But now that if error happens in rebinding to original driver, user will
have to manually deal with driver things, user can do the same when
error happens in unassign_from_pciback. So, now I just wonder:
is it really better to switch the order of unassign_from_pciback and
remove_from_xenstore?  (A situation that "a USB device is assigned
to guest but some interface is bound to pciback and some is
unbound" is also a mess.)

-Chunyan
 
>  
> I realize this falls short of the "crash-only" design IanJ suggested 
> we try to do, but I think that in this case the work required to do 
> such a design would be a lot more work than the benefit it gives us. 
>  
> I haven't re-reviewed the whole patch, but all the other changes look 
> good to me. 
>  
>  -George 
>  
>
Ian Jackson Feb. 26, 2016, 12:09 p.m. UTC | #3
George Dunlap writes ("Re: [Xen-devel] [PATCH V14 4/6] libxl: add pvusb API"):
> On Fri, Feb 19, 2016 at 10:39 AM, Chunyan Liu <cyliu@suse.com> wrote:
> > +  [...]
> 
> So I see below that you're calling this before removing things from
> xenstore, so that if any of these fail, the user can still call "xl
> usb-remove" to retry.
> 
> Unfortunately, since you reassign interfaces to the original driver
> before all interfaces are de-assigned from usbback, you can end up in
> a situation where the device is partially re-plugged into the original
> drivers, partially still assigned to pciback.
> 
> I think this whole process should be three steps:
> 
> 1. Unassign all interfaces from usbback, stopping and returning an
> error as soon as one attempt fails
> 
> 2. Remove the pvusb xenstore nodes (stopping and returning an error if it fails)
> 
> 3. Attempt to re-assign all interfaces to the original drivers,
> stopping and returning an error as soon as one attempt fails.

This seems like a good plan to me.

(Making 3 after 2 re-attemptable would mean that the original driver
information needs to be saved in a different location in xenstore to
the pvusb control nodes, but that is not a problem.)

> * If one of the re-assignments fail, then the user will have to reload
> the drivers, reboot, or mess around with sysfs to fix things.
> *However*, this avoids a scenario where a user is completely unable to
> remove a device from a domain because of a buggy driver.

Right.

> I realize this falls short of the "crash-only" design IanJ suggested
> we try to do, but I think that in this case the work required to do
> such a design would be a lot more work than the benefit it gives us.

I think what you have above is indeed crash-only.  You can tell by all
the "if any error occurs, stop immediately".

The only wrinkle is that the obvious implementation reads the old
bindings from xenstore between 1 and 2, deletes the information from
xenstore in 2, and uses that information in step 3, which is cheating
(and leads to the sysfs-wrangling-required state).  But that is quite
easy to avoid:
  - record the old driver bindings somewhere in xenstore (keyed by
    the physical host device, not the guest domain)
  - provide a libxl call and corresponding xl command which attempts
    to rebind

But, FAOD, I do not want to block this patch until such a thing is
implemented.  I think it is sufficient to document the existence of
the awkward state, and the likely workarounds.

Ian.
George Dunlap Feb. 29, 2016, 10:14 a.m. UTC | #4
On 26/02/16 12:09, Ian Jackson wrote:
> George Dunlap writes ("Re: [Xen-devel] [PATCH V14 4/6] libxl: add pvusb API"):
>> On Fri, Feb 19, 2016 at 10:39 AM, Chunyan Liu <cyliu@suse.com> wrote:
>>> +  [...]
>>
>> So I see below that you're calling this before removing things from
>> xenstore, so that if any of these fail, the user can still call "xl
>> usb-remove" to retry.
>>
>> Unfortunately, since you reassign interfaces to the original driver
>> before all interfaces are de-assigned from usbback, you can end up in
>> a situation where the device is partially re-plugged into the original
>> drivers, partially still assigned to pciback.
>>
>> I think this whole process should be three steps:
>>
>> 1. Unassign all interfaces from usbback, stopping and returning an
>> error as soon as one attempt fails
>>
>> 2. Remove the pvusb xenstore nodes (stopping and returning an error if it fails)
>>
>> 3. Attempt to re-assign all interfaces to the original drivers,
>> stopping and returning an error as soon as one attempt fails.
> 
> This seems like a good plan to me.
> 
> (Making 3 after 2 re-attemptable would mean that the original driver
> information needs to be saved in a different location in xenstore to
> the pvusb control nodes, but that is not a problem.)
> 
>> * If one of the re-assignments fail, then the user will have to reload
>> the drivers, reboot, or mess around with sysfs to fix things.
>> *However*, this avoids a scenario where a user is completely unable to
>> remove a device from a domain because of a buggy driver.
> 
> Right.
> 
>> I realize this falls short of the "crash-only" design IanJ suggested
>> we try to do, but I think that in this case the work required to do
>> such a design would be a lot more work than the benefit it gives us.
> 
> I think what you have above is indeed crash-only.  You can tell by all
> the "if any error occurs, stop immediately".
> 
> The only wrinkle is that the obvious implementation reads the old
> bindings from xenstore between 1 and 2, deletes the information from
> xenstore in 2, and uses that information in step 3, which is cheating
> (and leads to the sysfs-wrangling-required state).  But that is quite
> easy to avoid:
>   - record the old driver bindings somewhere in xenstore (keyed by
>     the physical host device, not the guest domain)
>   - provide a libxl call and corresponding xl command which attempts
>     to rebind

The re-bind information is already stored in a different location, keyed
by physical host device. :-) (Search for uses of USBBACK_INFO_PATH.)

But we would, as you say, need to add a separate function/command for
doing clean-up (simply calling xl usb-detach on the virtual device again
doesn't make much sense, since the virtual devices no longer shows up in
xl usb-list).  Since we don't have another such command to copy, we'd be
inventing a new one, which means thinking very carefully about the
design of the interface (since we'd want future such functions to follow
this precedent if possible), &c &c, so...

> But, FAOD, I do not want to block this patch until such a thing is
> implemented.  I think it is sufficient to document the existence of
> the awkward state, and the likely workarounds.

Great, thanks. :-)

 -George
Chunyan Liu March 1, 2016, 3:37 a.m. UTC | #5
>>> On 2/29/2016 at 06:14 PM, in message <56D419F6.8030704@citrix.com>, George
Dunlap <george.dunlap@citrix.com> wrote: 
> On 26/02/16 12:09, Ian Jackson wrote: 
> > George Dunlap writes ("Re: [Xen-devel] [PATCH V14 4/6] libxl: add pvusb  
> API"): 
> >> On Fri, Feb 19, 2016 at 10:39 AM, Chunyan Liu <cyliu@suse.com> wrote: 
> >>> +  [...] 
> >> 
> >> So I see below that you're calling this before removing things from 
> >> xenstore, so that if any of these fail, the user can still call "xl 
> >> usb-remove" to retry. 
> >> 
> >> Unfortunately, since you reassign interfaces to the original driver 
> >> before all interfaces are de-assigned from usbback, you can end up in 
> >> a situation where the device is partially re-plugged into the original 
> >> drivers, partially still assigned to pciback. 
> >> 
> >> I think this whole process should be three steps: 
> >> 
> >> 1. Unassign all interfaces from usbback, stopping and returning an 
> >> error as soon as one attempt fails 
> >> 
> >> 2. Remove the pvusb xenstore nodes (stopping and returning an error if it  
> fails) 
> >> 
> >> 3. Attempt to re-assign all interfaces to the original drivers, 
> >> stopping and returning an error as soon as one attempt fails. 

I'll update codes to this order and post.

-Chunyan

> >  
> > This seems like a good plan to me. 
> >  
> > (Making 3 after 2 re-attemptable would mean that the original driver 
> > information needs to be saved in a different location in xenstore to 
> > the pvusb control nodes, but that is not a problem.) 
> >  
> >> * If one of the re-assignments fail, then the user will have to reload 
> >> the drivers, reboot, or mess around with sysfs to fix things. 
> >> *However*, this avoids a scenario where a user is completely unable to 
> >> remove a device from a domain because of a buggy driver. 
> >  
> > Right. 
> >  
> >> I realize this falls short of the "crash-only" design IanJ suggested 
> >> we try to do, but I think that in this case the work required to do 
> >> such a design would be a lot more work than the benefit it gives us. 
> >  
> > I think what you have above is indeed crash-only.  You can tell by all 
> > the "if any error occurs, stop immediately". 
> >  
> > The only wrinkle is that the obvious implementation reads the old 
> > bindings from xenstore between 1 and 2, deletes the information from 
> > xenstore in 2, and uses that information in step 3, which is cheating 
> > (and leads to the sysfs-wrangling-required state).  But that is quite 
> > easy to avoid: 
> >   - record the old driver bindings somewhere in xenstore (keyed by 
> >     the physical host device, not the guest domain) 
> >   - provide a libxl call and corresponding xl command which attempts 
> >     to rebind 
>  
> The re-bind information is already stored in a different location, keyed 
> by physical host device. :-) (Search for uses of USBBACK_INFO_PATH.) 
>  
> But we would, as you say, need to add a separate function/command for 
> doing clean-up (simply calling xl usb-detach on the virtual device again 
> doesn't make much sense, since the virtual devices no longer shows up in 
> xl usb-list).  Since we don't have another such command to copy, we'd be 
> inventing a new one, which means thinking very carefully about the 
> design of the interface (since we'd want future such functions to follow 
> this precedent if possible), &c &c, so... 
>  
> > But, FAOD, I do not want to block this patch until such a thing is 
> > implemented.  I think it is sufficient to document the existence of 
> > the awkward state, and the likely workarounds. 
>  
> Great, thanks. :-) 
>  
>  -George 
>  
>  
>  
>
diff mbox

Patch

diff --git a/tools/libxl/Makefile b/tools/libxl/Makefile
index 620720e..459fc51 100644
--- a/tools/libxl/Makefile
+++ b/tools/libxl/Makefile
@@ -105,7 +105,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 f385134..aa879d2 100644
--- a/tools/libxl/libxl.c
+++ b/tools/libxl/libxl.c
@@ -4167,6 +4167,8 @@  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_EXT(type, remtype, removedestroy, f)        \
     int libxl_device_##type##_##removedestroy(libxl_ctx *ctx,           \
@@ -4224,6 +4226,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. */
@@ -4239,6 +4245,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)                                         \
@@ -4270,6 +4278,12 @@  DEFINE_DEVICE_ADD(nic)
 /* vtpm */
 DEFINE_DEVICE_ADD(vtpm)
 
+/* usbctrl */
+DEFINE_DEVICE_ADD(usbctrl)
+
+/* usb */
+DEFINE_DEVICE_ADD(usbdev)
+
 #undef DEFINE_DEVICE_ADD
 
 /******************************************************************************/
@@ -6815,6 +6829,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 ea7a4b8..e72d001 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
@@ -1517,6 +1523,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 a356e2a..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_generic_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);
             }
         }
     }
diff --git a/tools/libxl/libxl_internal.h b/tools/libxl/libxl_internal.h
index dd8a681..46272c6 100644
--- a/tools/libxl/libxl_internal.h
+++ b/tools/libxl/libxl_internal.h
@@ -2587,6 +2587,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);
@@ -2619,6 +2627,13 @@  _hidden void libxl__wait_device_connection(libxl__egc*,
 _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
  * be passed to the hotplug script for the requested device.
@@ -3973,6 +3988,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 <util.h>
 #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 <stropts.h>
 #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 <sys/endian.h>
 #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..3bf5002
--- /dev/null
+++ b/tools/libxl/libxl_pvusb.c
@@ -0,0 +1,1567 @@ 
+/*
+ * Copyright (C) 2015 SUSE LINUX Products GmbH, Nuernberg, Germany.
+ * Author Chunyan Liu <cyliu@suse.com>
+ *
+ * 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 <inttypes.h>
+
+#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++) {
+        rc = libxl__device_usbdev_remove(gc, domid, &usbdevs[i]);
+        if (rc) {
+            LOG(ERROR, "libxl__device_usbdev_remove failed: controller %d, "
+                "port %d", usbdevs[i].ctrl, usbdevs[i].port);
+            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;
+    }
+
+    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);
+            rc = libxl__xs_read_checked(gc, XBT_NULL, path, &tmp);
+            if (rc) goto out;
+
+            if (tmp && !strcmp(tmp, "")) {
+                usbdev->ctrl = usbctrls[i].devid;
+                usbdev->port = j + 1;
+                rc = 0;
+                goto out;
+            }
+        }
+    }
+
+    /* no available controller:port */
+    rc = ERROR_FAIL;
+
+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);
+
+        rc = libxl__xs_write_checked(gc, t, be_path, busid);
+        if (rc) 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 r;
+
+    spath = GCSPRINTF(SYSFS_USB_DEV "/%s/driver", intf);
+
+    r = lstat(spath, &st);
+    if (r == 0) {
+        /* Find the canonical path to the driver. */
+        dp = libxl__zalloc(gc, PATH_MAX);
+        dp = realpath(spath, dp);
+        if (!dp) {
+            LOGE(ERROR, "get realpath failed: '%s'", spath);
+            return ERROR_FAIL;
+        }
+    } else if (errno == ENOENT) {
+        /* driver path doesn't exist */
+        dp = NULL;
+    } else {
+        LOGE(ERROR, "lstat failed: '%s'", spath);
+        return ERROR_FAIL;
+    }
+
+    *drvpath = dp;
+
+    return 0;
+}
+
+static int unbind_usbintf(libxl__gc *gc, const char *intf)
+{
+    char *path;
+    int fd = -1;
+    int rc;
+
+    path = GCSPRINTF(SYSFS_USB_DEV "/%s/driver/unbind", intf);
+
+    fd = open(path, O_WRONLY);
+    if (fd < 0) {
+        LOGE(ERROR, "open file failed: '%s'", path);
+        rc = ERROR_FAIL;
+        goto out;
+    }
+
+    if (libxl_write_exactly(CTX, fd, intf, sizeof(intf), path, intf)) {
+        rc = ERROR_FAIL;
+        goto out;
+    }
+
+    rc = 0;
+
+out:
+    if (fd >= 0) close(fd);
+    return rc;
+}
+
+static int bind_usbintf(libxl__gc *gc, const char *intf, const char *drvpath)
+{
+    char *path;
+    struct stat st;
+    int fd = -1;
+    int rc, r;
+
+    path = GCSPRINTF("%s/%s", drvpath, intf);
+
+    /* check through lstat, if intf already exists under drvpath,
+     * it's already bound, return directly; if it doesn't exist,
+     * continue to do bind work; otherwise, return error.
+     */
+    r = lstat(path, &st);
+    if (r == 0)
+        return 0;
+    if (r < 0 && errno != ENOENT)
+        return ERROR_FAIL;
+
+    path = GCSPRINTF("%s/bind", drvpath);
+
+    fd = open(path, O_WRONLY);
+    if (fd < 0) {
+        LOGE(ERROR, "open file failed: '%s'", path);
+        rc = ERROR_FAIL;
+        goto out;
+    }
+
+    if (libxl_write_exactly(CTX, fd, intf, sizeof(intf), path, intf)) {
+        rc = ERROR_FAIL;
+        goto out;
+    }
+
+    rc = 0;
+
+out:
+    if (fd >= 0) close(fd);
+    return rc;
+}
+
+/* Is usb interface bound to usbback? */
+static int usbintf_is_assigned(libxl__gc *gc, char *intf)
+{
+    char *spath;
+    int r;
+    struct stat st;
+
+    spath = GCSPRINTF(SYSFS_USBBACK_DRIVER "/%s", intf);
+    r = lstat(spath, &st);
+
+    if (r == 0)
+        return 1;
+    if (r < 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;
+
+    rc = usbdev_get_all_interfaces(gc, busid, &intfs, &num);
+    if (rc) goto out;
+
+    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 */
+            rc = unbind_usbintf(gc, intf);
+            if (rc) {
+                LOGE(ERROR, "Couldn't unbind %s from usbback", intf);
+                goto out;
+            }
+        }
+
+        /* rebind USB interface to its originial driver */
+        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) goto out;
+
+        if (drvpath) {
+            rc = bind_usbintf(gc, intf, drvpath);
+            if (rc) {
+                LOGE(ERROR, "Couldn't rebind %s to %s", intf, drvpath);
+                goto out;
+            }
+        }
+    }
+
+    /* finally, remove xenstore driver path. */
+    path = GCSPRINTF(USBBACK_INFO_PATH "/%s", usbdev_encode);
+    rc = libxl__xs_rm_checked(gc, XBT_NULL, path);
+
+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) 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);
+            rc = libxl__xs_write_checked(gc, XBT_NULL, path, drvpath);
+            if (rc) goto out;
+
+            /* unbind interface from original driver */
+            rc = unbind_usbintf(gc, intf);
+            if (rc) goto out;
+        }
+
+        /* bind interface to usbback */
+        rc = bind_usbintf(gc, intf, SYSFS_USBBACK_DRIVER);
+        if (rc) {
+            LOG(ERROR, "Couldn't bind %s to %s", intf, SYSFS_USBBACK_DRIVER);
+            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_INVAL;
+        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 = usbback_dev_unassign(gc, busid);
+        if (rc) goto out;
+
+        rc = libxl__device_usbdev_remove_xenstore(gc, domid, usbdev);
+        if (rc) 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;
+}
+
+/* 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 9ad7eba..a92ddcc 100644
--- a/tools/libxl/libxl_types.idl
+++ b/tools/libxl/libxl_types.idl
@@ -607,6 +607,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),
     ])
@@ -645,6 +674,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),
@@ -688,6 +719,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