---
docs/formatdomain.html.in | 27 ++++
docs/schemas/domain.rng | 13 ++
src/conf/domain_conf.c | 157 +++++++++++++++++++++-
src/conf/domain_conf.h | 20 +++
src/libvirt_private.syms | 2 +
src/qemu/qemu_capabilities.c | 3 +
src/qemu/qemu_capabilities.h | 1 +
src/qemu/qemu_command.c | 56 ++++++++-
src/qemu/qemu_command.h | 1 +
tests/qemuhelptest.c | 9 +-
tests/qemuxml2argvdata/qemuxml2argv-usb-hub.args | 1 +
tests/qemuxml2argvdata/qemuxml2argv-usb-hub.xml | 19 +++
tests/qemuxml2argvtest.c | 3 +
13 files changed, 307 insertions(+), 5 deletions(-)
create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-usb-hub.args
create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-usb-hub.xml
diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in
index 633cea1..e35b76b 100644
--- a/docs/formatdomain.html.in
+++ b/docs/formatdomain.html.in
@@ -2084,6 +2084,33 @@ qemu-kvm -net nic,model=? /dev/null
device to a particular PCI slot.
</p>
+ <h4><a name="elementsHub">Hub devices</a></h4>
+
+ <p>
+ A hub is a device that expands a single port into several so
+ that there are more ports available to connect devices to a host
+ system.
+ </p>
+
+<pre>
+ ...
+ <devices>
+ <hub type='usb'/>
+ </devices>
+ ...</pre>
+
+ <dl>
+ <dt><code>hub</code></dt>
+ <dd>The <code>hub</code> element has one mandatory attribute,
+ the <code>type</code> whose value can only be
'usb'.</dd>
+ </dl>
+
+ <p>
+ The <code>hub</code> element has an optional
+ sub-element <code><address></code> which can tie the
+ device to a particular controller.
+ </p>
+
<h4><a name="elementsGraphics">Graphical
framebuffers</a></h4>
<p>
diff --git a/docs/schemas/domain.rng b/docs/schemas/domain.rng
index 455f57d..16e9687 100644
--- a/docs/schemas/domain.rng
+++ b/docs/schemas/domain.rng
@@ -1962,6 +1962,18 @@
</optional>
</element>
</define>
+ <define name="hub">
+ <element name="hub">
+ <attribute name="type">
+ <choice>
+ <value>usb</value>
+ </choice>
+ </attribute>
+ <optional>
+ <ref name="address"/>
+ </optional>
+ </element>
+ </define>
<define name="hostdev">
<element name="hostdev">
<optional>
@@ -2125,6 +2137,7 @@
<ref name="serial"/>
<ref name="channel"/>
<ref name="smartcard"/>
+ <ref name="hub"/>
</choice>
</zeroOrMore>
<optional>
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 7d03bcd..af08a03 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -126,7 +126,8 @@ VIR_ENUM_IMPL(virDomainDevice, VIR_DOMAIN_DEVICE_LAST,
"hostdev",
"watchdog",
"controller",
- "graphics")
+ "graphics",
+ "hub")
VIR_ENUM_IMPL(virDomainDeviceAddress, VIR_DOMAIN_DEVICE_ADDRESS_TYPE_LAST,
"none",
@@ -436,6 +437,9 @@ VIR_ENUM_IMPL(virDomainState, VIR_DOMAIN_CRASHED+1,
"shutoff",
"crashed")
+VIR_ENUM_IMPL(virDomainHub, VIR_DOMAIN_HUB_TYPE_LAST,
+ "usb")
+
#define VIR_DOMAIN_NOSTATE_LAST (VIR_DOMAIN_NOSTATE_UNKNOWN + 1)
VIR_ENUM_IMPL(virDomainNostateReason, VIR_DOMAIN_NOSTATE_LAST,
"unknown")
@@ -999,6 +1003,15 @@ void virDomainHostdevDefFree(virDomainHostdevDefPtr def)
VIR_FREE(def);
}
+void virDomainHubDefFree(virDomainHubDefPtr def)
+{
+ if (!def)
+ return;
+
+ virDomainDeviceInfoClear(&def->info);
+ VIR_FREE(def);
+}
+
void virDomainDeviceDefFree(virDomainDeviceDefPtr def)
{
if (!def)
@@ -1035,6 +1048,9 @@ void virDomainDeviceDefFree(virDomainDeviceDefPtr def)
case VIR_DOMAIN_DEVICE_GRAPHICS:
virDomainGraphicsDefFree(def->data.graphics);
break;
+ case VIR_DOMAIN_DEVICE_HUB:
+ virDomainHubDefFree(def->data.hub);
+ break;
}
VIR_FREE(def);
@@ -1144,6 +1160,10 @@ void virDomainDefFree(virDomainDefPtr def)
virDomainHostdevDefFree(def->hostdevs[i]);
VIR_FREE(def->hostdevs);
+ for (i = 0 ; i < def->nhubs ; i++)
+ virDomainHubDefFree(def->hubs[i]);
+ VIR_FREE(def->hubs);
+
VIR_FREE(def->os.type);
VIR_FREE(def->os.arch);
VIR_FREE(def->os.machine);
@@ -1527,6 +1547,9 @@ int virDomainDeviceInfoIterate(virDomainDefPtr def,
if (def->console)
if (cb(def, &def->console->info, opaque) < 0)
return -1;
+ for (i = 0; i < def->nhubs ; i++)
+ if (cb(def, &def->hubs[i]->info, opaque) < 0)
+ return -1;
return 0;
}
@@ -1596,6 +1619,12 @@ virDomainDeviceInfoFormat(virBufferPtr buf,
info->addr.ccid.slot);
break;
+ case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB:
+ virBufferAsprintf(buf, " bus='%d' port='%d'",
+ info->addr.usb.bus,
+ info->addr.usb.port);
+ break;
+
default:
virDomainReportError(VIR_ERR_INTERNAL_ERROR,
_("unknown address type '%d'"),
info->type);
@@ -3995,6 +4024,47 @@ error:
}
+/* Parse the XML definition for an hub device */
+static virDomainHubDefPtr
+virDomainHubDefParseXML(xmlNodePtr node, unsigned int flags)
+{
+ virDomainHubDefPtr def;
+ char *type = NULL;
+
+ if (VIR_ALLOC(def) < 0) {
+ virReportOOMError();
+ return NULL;
+ }
+
+ type = virXMLPropString(node, "type");
+
+ if (!type) {
+ virDomainReportError(VIR_ERR_INTERNAL_ERROR,
+ "%s", _("missing hub device type"));
+ goto error;
+ }
+
+ if ((def->type = virDomainHubTypeFromString(type)) < 0) {
+ virDomainReportError(VIR_ERR_INTERNAL_ERROR,
+ _("unknown hub device type '%s'"), type);
+ goto error;
+ }
+
+ if (virDomainDeviceInfoParseXML(node, &def->info, flags) < 0)
+ goto error;
+
+cleanup:
+ VIR_FREE(type);
+
+ return def;
+
+error:
+ virDomainHubDefFree(def);
+ def = NULL;
+ goto cleanup;
+}
+
+
/* Parse the XML definition for a clock timer */
static virDomainTimerDefPtr
virDomainTimerDefParseXML(const xmlNodePtr node,
@@ -5572,6 +5642,10 @@ virDomainDeviceDefPtr virDomainDeviceDefParse(virCapsPtr caps,
dev->type = VIR_DOMAIN_DEVICE_GRAPHICS;
if (!(dev->data.graphics = virDomainGraphicsDefParseXML(node, ctxt, flags)))
goto error;
+ } else if (xmlStrEqual(node->name, BAD_CAST "hub")) {
+ dev->type = VIR_DOMAIN_DEVICE_HUB;
+ if (!(dev->data.hub = virDomainHubDefParseXML(node, flags)))
+ goto error;
} else {
virDomainReportError(VIR_ERR_XML_ERROR,
"%s", _("unknown device type"));
@@ -6976,6 +7050,21 @@ static virDomainDefPtr virDomainDefParseXML(virCapsPtr caps,
}
}
+ /* analysis of the hub devices */
+ if ((n = virXPathNodeSet("./devices/hub", ctxt, &nodes)) < 0) {
+ goto error;
+ }
+ if (n && VIR_ALLOC_N(def->hubs, n) < 0)
+ goto no_memory;
+ for (i = 0 ; i < n ; i++) {
+ virDomainHubDefPtr hub = virDomainHubDefParseXML(nodes[i], flags);
+ if (!hub)
+ goto error;
+
+ def->hubs[def->nhubs++] = hub;
+ }
+ VIR_FREE(nodes);
+
/* analysis of security label */
if (virSecurityLabelDefParseXML(def, ctxt, flags) == -1)
goto error;
@@ -7886,6 +7975,29 @@ cleanup:
}
+static bool virDomainHubDefCheckABIStability(virDomainHubDefPtr src,
+ virDomainHubDefPtr dst)
+{
+ bool identical = false;
+
+ if (src->type != dst->type) {
+ virDomainReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+ _("Target hub device type %s does not match source
%s"),
+ virDomainHubTypeToString(dst->type),
+ virDomainHubTypeToString(src->type));
+ goto cleanup;
+ }
+
+ if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
+ goto cleanup;
+
+ identical = true;
+
+cleanup:
+ return identical;
+}
+
+
/* This compares two configurations and looks for any differences
* which will affect the guest ABI. This is primarily to allow
* validation of custom XML config passed in during migration
@@ -8119,6 +8231,17 @@ bool virDomainDefCheckABIStability(virDomainDefPtr src,
goto cleanup;
}
+ if (src->nhubs != dst->nhubs) {
+ virDomainReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+ _("Target domain hub device count %d does not match
source %d"),
+ dst->nhubs, src->nhubs);
+ goto cleanup;
+ }
+
+ for (i = 0 ; i < src->nhubs ; i++)
+ if (!virDomainHubDefCheckABIStability(src->hubs[i], dst->hubs[i]))
+ goto cleanup;
+
if (src->console &&
!virDomainConsoleDefCheckABIStability(src->console, dst->console))
goto cleanup;
@@ -10032,6 +10155,34 @@ virDomainHostdevDefFormat(virBufferPtr buf,
}
+static int
+virDomainHubDefFormat(virBufferPtr buf,
+ virDomainHubDefPtr def,
+ unsigned int flags)
+{
+ const char *type = virDomainHubTypeToString(def->type);
+
+ if (!type) {
+ virDomainReportError(VIR_ERR_INTERNAL_ERROR,
+ _("unexpected hub type %d"), def->type);
+ return -1;
+ }
+
+ virBufferAsprintf(buf, " <hub type='%s'", type);
+
+ if (virDomainDeviceInfoIsSet(&def->info, flags)) {
+ virBufferAddLit(buf, ">\n");
+ if (virDomainDeviceInfoFormat(buf, &def->info, flags) < 0)
+ return -1;
+ virBufferAddLit(buf, " </hub>\n");
+ } else {
+ virBufferAddLit(buf, "/>\n");
+ }
+
+ return 0;
+}
+
+
#define DUMPXML_FLAGS \
(VIR_DOMAIN_XML_SECURE | \
VIR_DOMAIN_XML_INACTIVE | \
@@ -10437,6 +10588,10 @@ virDomainDefFormatInternal(virDomainDefPtr def,
if (virDomainHostdevDefFormat(&buf, def->hostdevs[n], flags) < 0)
goto cleanup;
+ for (n = 0 ; n < def->nhubs ; n++)
+ if (virDomainHubDefFormat(&buf, def->hubs[n], flags) < 0)
+ goto cleanup;
+
if (def->watchdog)
virDomainWatchdogDefFormat (&buf, def->watchdog, flags);
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index 7d5193a..569e840 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -615,6 +615,13 @@ struct _virDomainSmartcardDef {
virDomainDeviceInfo info;
};
+typedef struct _virDomainHubDef virDomainHubDef;
+typedef virDomainHubDef *virDomainHubDefPtr;
+struct _virDomainHubDef {
+ int type;
+ virDomainDeviceInfo info;
+};
+
enum virDomainInputType {
VIR_DOMAIN_INPUT_TYPE_MOUSE,
VIR_DOMAIN_INPUT_TYPE_TABLET,
@@ -824,6 +831,12 @@ enum virDomainGraphicsListenType {
VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_LAST,
};
+enum virDomainHubType {
+ VIR_DOMAIN_HUB_TYPE_USB,
+
+ VIR_DOMAIN_HUB_TYPE_LAST,
+};
+
typedef struct _virDomainGraphicsListenDef virDomainGraphicsListenDef;
typedef virDomainGraphicsListenDef *virDomainGraphicsListenDefPtr;
struct _virDomainGraphicsListenDef {
@@ -964,6 +977,7 @@ enum virDomainDeviceType {
VIR_DOMAIN_DEVICE_WATCHDOG,
VIR_DOMAIN_DEVICE_CONTROLLER,
VIR_DOMAIN_DEVICE_GRAPHICS,
+ VIR_DOMAIN_DEVICE_HUB,
VIR_DOMAIN_DEVICE_LAST,
};
@@ -984,6 +998,7 @@ struct _virDomainDeviceDef {
virDomainHostdevDefPtr hostdev;
virDomainWatchdogDefPtr watchdog;
virDomainGraphicsDefPtr graphics;
+ virDomainHubDefPtr hub;
} data;
};
@@ -1311,6 +1326,9 @@ struct _virDomainDef {
size_t nleases;
virDomainLeaseDefPtr *leases;
+ int nhubs;
+ virDomainHubDefPtr *hubs;
+
/* Only 1 */
virDomainChrDefPtr console;
virSecurityLabelDef seclabel;
@@ -1457,6 +1475,7 @@ void virDomainMemballoonDefFree(virDomainMemballoonDefPtr def);
void virDomainWatchdogDefFree(virDomainWatchdogDefPtr def);
void virDomainVideoDefFree(virDomainVideoDefPtr def);
void virDomainHostdevDefFree(virDomainHostdevDefPtr def);
+void virDomainHubDefFree(virDomainHubDefPtr def);
void virDomainDeviceDefFree(virDomainDeviceDefPtr def);
int virDomainDeviceAddressIsValid(virDomainDeviceInfoPtr info,
int type);
@@ -1737,6 +1756,7 @@ VIR_ENUM_DECL(virDomainWatchdogAction)
VIR_ENUM_DECL(virDomainVideo)
VIR_ENUM_DECL(virDomainHostdevMode)
VIR_ENUM_DECL(virDomainHostdevSubsys)
+VIR_ENUM_DECL(virDomainHub)
VIR_ENUM_DECL(virDomainInput)
VIR_ENUM_DECL(virDomainInputBus)
VIR_ENUM_DECL(virDomainGraphics)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 74948b8..6642ba9 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -327,6 +327,8 @@ virDomainGraphicsTypeToString;
virDomainHostdevDefFree;
virDomainHostdevModeTypeToString;
virDomainHostdevSubsysTypeToString;
+virDomainHubTypeFromString;
+virDomainHubTypeToString;
virDomainInputDefFree;
virDomainIoEventFdTypeFromString;
virDomainIoEventFdTypeToString;
diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c
index a776a0c..5b72a60 100644
--- a/src/qemu/qemu_capabilities.c
+++ b/src/qemu/qemu_capabilities.c
@@ -134,6 +134,7 @@ VIR_ENUM_IMPL(qemuCaps, QEMU_CAPS_LAST,
"pci-ohci", /* 70 */
"usb-redir",
+ "usb-hub",
);
struct qemu_feature_flags {
@@ -1220,6 +1221,8 @@ qemuCapsParseDeviceStr(const char *str, virBitmapPtr flags)
qemuCapsSet(flags, QEMU_CAPS_PCI_OHCI);
if (strstr(str, "name \"usb-redir\""))
qemuCapsSet(flags, QEMU_CAPS_USB_REDIR);
+ if (strstr(str, "name \"usb-hub\""))
+ qemuCapsSet(flags, QEMU_CAPS_USB_HUB);
/* Prefer -chardev spicevmc (detected earlier) over -device spicevmc */
if (!qemuCapsGet(flags, QEMU_CAPS_CHARDEV_SPICEVMC) &&
diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h
index f4bb368..c9b9b45 100644
--- a/src/qemu/qemu_capabilities.h
+++ b/src/qemu/qemu_capabilities.h
@@ -108,6 +108,7 @@ enum qemuCapsFlags {
QEMU_CAPS_VT82C686B_USB_UHCI = 69, /* -device vt82c686b-usb-uhci */
QEMU_CAPS_PCI_OHCI = 70, /* -device pci-ohci */
QEMU_CAPS_USB_REDIR = 71, /* -device usb-redir */
+ QEMU_CAPS_USB_HUB = 72, /* -device usb-hub */
QEMU_CAPS_LAST, /* this must always be the last item */
};
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index ba87b04..6962862 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -679,6 +679,10 @@ qemuAssignDeviceAliases(virDomainDefPtr def, virBitmapPtr qemuCaps)
if (virAsprintf(&def->smartcards[i]->info.alias,
"smartcard%d", i) < 0)
goto no_memory;
}
+ for (i = 0; i < def->nhubs ; i++) {
+ if (virAsprintf(&def->hubs[i]->info.alias, "hub%d", i) <
0)
+ goto no_memory;
+ }
if (def->console) {
if (virAsprintf(&def->console->info.alias, "console%d", i)
< 0)
goto no_memory;
@@ -1262,6 +1266,9 @@ qemuAssignDevicePCISlots(virDomainDefPtr def,
qemuDomainPCIAddressSetPtr addrs)
for (i = 0; i < def->nchannels ; i++) {
/* Nada - none are PCI based (yet) */
}
+ for (i = 0; i < def->nhubs ; i++) {
+ /* Nada - none are PCI based (yet) */
+ }
return 0;
@@ -1757,7 +1764,6 @@ qemuBuildUSBControllerDevStr(virDomainControllerDefPtr def,
virBufferAsprintf(buf, ",id=usb%d", def->idx);
}
-
return 0;
}
@@ -2326,6 +2332,43 @@ error:
char *
+qemuBuildHubDevStr(virDomainHubDefPtr dev,
+ virBitmapPtr qemuCaps)
+{
+ virBuffer buf = VIR_BUFFER_INITIALIZER;
+
+ if (dev->type != VIR_DOMAIN_HUB_TYPE_USB) {
+ qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+ _("hub type %s not supported"),
+ virDomainHubTypeToString(dev->type));
+ goto error;
+ }
+
+ if (!qemuCapsGet(qemuCaps, QEMU_CAPS_USB_HUB)) {
+ qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+ _("usb-hub not supported by QEMU binary"));
+ goto error;
+ }
+
+ virBufferAddLit(&buf, "usb-hub");
+ virBufferAsprintf(&buf, ",id=%s", dev->info.alias);
+ if (qemuBuildDeviceAddressStr(&buf, &dev->info, qemuCaps) < 0)
+ goto error;
+
+ if (virBufferError(&buf)) {
+ virReportOOMError();
+ goto error;
+ }
+
+ return virBufferContentAndReset(&buf);
+
+error:
+ virBufferFreeAndReset(&buf);
+ return NULL;
+}
+
+
+char *
qemuBuildUSBHostdevUsbDevStr(virDomainHostdevDefPtr dev)
{
char *ret = NULL;
@@ -4235,6 +4278,17 @@ qemuBuildCommandLine(virConnectPtr conn,
if (usbcontroller == 0)
virCommandAddArg(cmd, "-usb");
+ for (i = 0 ; i < def->nhubs ; i++) {
+ virDomainHubDefPtr hub = def->hubs[i];
+ char *optstr;
+
+ virCommandAddArg(cmd, "-device");
+ if (!(optstr = qemuBuildHubDevStr(hub, qemuCaps)))
+ goto error;
+ virCommandAddArg(cmd, optstr);
+ VIR_FREE(optstr);
+ }
+
for (i = 0 ; i < def->ninputs ; i++) {
virDomainInputDefPtr input = def->inputs[i];
diff --git a/src/qemu/qemu_command.h b/src/qemu/qemu_command.h
index de09577..22bc15d 100644
--- a/src/qemu/qemu_command.h
+++ b/src/qemu/qemu_command.h
@@ -119,6 +119,7 @@ char * qemuBuildUSBHostdevUsbDevStr(virDomainHostdevDefPtr dev);
char * qemuBuildUSBHostdevDevStr(virDomainHostdevDefPtr dev,
virBitmapPtr qemuCaps);
+char * qemuBuildHubDevStr(virDomainHubDefPtr dev, virBitmapPtr qemuCaps);
int qemuNetworkIfaceConnect(virDomainDefPtr def,
diff --git a/tests/qemuhelptest.c b/tests/qemuhelptest.c
index d57e499..ffd30e2 100644
--- a/tests/qemuhelptest.c
+++ b/tests/qemuhelptest.c
@@ -349,7 +349,8 @@ mymain(void)
QEMU_CAPS_DRIVE_AIO,
QEMU_CAPS_DEVICE_SPICEVMC,
QEMU_CAPS_PIIX3_USB_UHCI,
- QEMU_CAPS_PIIX4_USB_UHCI);
+ QEMU_CAPS_PIIX4_USB_UHCI,
+ QEMU_CAPS_USB_HUB);
DO_TEST("qemu-kvm-0.12.3", 12003, 1, 0,
QEMU_CAPS_VNC_COLON,
QEMU_CAPS_NO_REBOOT,
@@ -437,7 +438,8 @@ mymain(void)
QEMU_CAPS_PIIX3_USB_UHCI,
QEMU_CAPS_PIIX4_USB_UHCI,
QEMU_CAPS_VT82C686B_USB_UHCI,
- QEMU_CAPS_PCI_OHCI);
+ QEMU_CAPS_PCI_OHCI,
+ QEMU_CAPS_USB_HUB);
DO_TEST("qemu-kvm-0.12.1.2-rhel61", 12001, 1, 0,
QEMU_CAPS_VNC_COLON,
QEMU_CAPS_NO_REBOOT,
@@ -484,7 +486,8 @@ mymain(void)
QEMU_CAPS_VIRTIO_TX_ALG,
QEMU_CAPS_VIRTIO_IOEVENTFD,
QEMU_CAPS_PIIX3_USB_UHCI,
- QEMU_CAPS_PIIX4_USB_UHCI);
+ QEMU_CAPS_PIIX4_USB_UHCI,
+ QEMU_CAPS_USB_HUB);
return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
diff --git a/tests/qemuxml2argvdata/qemuxml2argv-usb-hub.args
b/tests/qemuxml2argvdata/qemuxml2argv-usb-hub.args
new file mode 100644
index 0000000..4911dd4
--- /dev/null
+++ b/tests/qemuxml2argvdata/qemuxml2argv-usb-hub.args
@@ -0,0 +1 @@
+LC_ALL=C PATH=/bin HOME=/home/test USER=test LOGNAME=test /usr/bin/qemu -S -M pc -m 214
-smp 1 -nographic -nodefconfig -nodefaults -chardev
socket,id=charmonitor,path=/tmp/test-monitor,server,nowait -mon
chardev=charmonitor,id=monitor,mode=readline -no-acpi -boot c -usb -device
usb-hub,id=hub0,bus=usb.0,port=1 -device
virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x4
diff --git a/tests/qemuxml2argvdata/qemuxml2argv-usb-hub.xml
b/tests/qemuxml2argvdata/qemuxml2argv-usb-hub.xml
new file mode 100644
index 0000000..5e0b256
--- /dev/null
+++ b/tests/qemuxml2argvdata/qemuxml2argv-usb-hub.xml
@@ -0,0 +1,19 @@
+<domain type='qemu'>
+ <name>QEMUGuest1</name>
+ <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid>
+ <memory>219136</memory>
+ <currentMemory>219200</currentMemory>
+ <vcpu>1</vcpu>
+ <os>
+ <type arch='i686' machine='pc'>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <devices>
+ <emulator>/usr/bin/qemu</emulator>
+ <controller type='usb' index='0'/>
+ <memballoon model='virtio'/>
+ <hub type='usb'>
+ <address type='usb' bus='0' port='1'/>
+ </hub>
+ </devices>
+</domain>
diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c
index 33588d0..a053693 100644
--- a/tests/qemuxml2argvtest.c
+++ b/tests/qemuxml2argvtest.c
@@ -498,6 +498,9 @@ mymain(void)
DO_TEST("usb-ich9-companion", false,
QEMU_CAPS_CHARDEV, QEMU_CAPS_DEVICE, QEMU_CAPS_NODEFCONFIG,
QEMU_CAPS_PCI_MULTIFUNCTION, QEMU_CAPS_ICH9_USB_EHCI1);
+ DO_TEST("usb-hub", false,
+ QEMU_CAPS_CHARDEV, QEMU_CAPS_DEVICE, QEMU_CAPS_USB_HUB,
+ QEMU_CAPS_NODEFCONFIG);
DO_TEST("smbios", false, QEMU_CAPS_SMBIOS_TYPE);
--
1.7.6