diff --git a/repos/ports/lib/mk/spec/x86_64/virtualbox6-devices.mk b/repos/ports/lib/mk/spec/x86_64/virtualbox6-devices.mk
index ca52f34ca9..cca50fd289 100644
--- a/repos/ports/lib/mk/spec/x86_64/virtualbox6-devices.mk
+++ b/repos/ports/lib/mk/spec/x86_64/virtualbox6-devices.mk
@@ -107,6 +107,9 @@ SRC_CC += GuestHost/DragAndDrop/DnDDroppedFiles.cpp
SRC_CC += GuestHost/DragAndDrop/DnDMIME.cpp
SRC_CC += GuestHost/DragAndDrop/DnDPath.cpp
+SRC_CC += devxhci.cc
+INC_DIR += $(call select_from_repositories,src/lib/libc)
+
INC_DIR += $(VBOX_DIR)/Devices/build
INC_DIR += $(VBOX_DIR)/Devices/Bus
INC_DIR += $(VIRTUALBOX_DIR)/include/VBox/Graphics
@@ -155,5 +158,6 @@ vboxssdt_cpuhotplug.hex: vbox-cpuhotplug.dsl
)
vpath %.dsl $(VBOX_DIR)/Devices/PC
+vpath devxhci.cc $(REP_DIR)/src/virtualbox6
CC_CXX_WARN_STRICT =
diff --git a/repos/ports/ports/virtualbox6.hash b/repos/ports/ports/virtualbox6.hash
index e1f92e75e6..3f29038747 100644
--- a/repos/ports/ports/virtualbox6.hash
+++ b/repos/ports/ports/virtualbox6.hash
@@ -1 +1 @@
-53f753241f3d5253338f284aa833d7db04469026
+e680f42ecc53c4f0507a6e832a3e48915a6c3b2a
diff --git a/repos/ports/src/virtualbox6/devices.cc b/repos/ports/src/virtualbox6/devices.cc
index 71e487cb3b..0b8c019dc2 100644
--- a/repos/ports/src/virtualbox6/devices.cc
+++ b/repos/ports/src/virtualbox6/devices.cc
@@ -63,5 +63,7 @@ extern "C" int VBoxDevicesRegister(PPDMDEVREGCB pCallbacks, uint32_t u32Version)
REGISTER(DeviceGIMDev);
REGISTER(DeviceLPC);
+ REGISTER(DeviceXHCI);
+
return VINF_SUCCESS;
}
diff --git a/repos/ports/src/virtualbox6/devxhci.cc b/repos/ports/src/virtualbox6/devxhci.cc
new file mode 100644
index 0000000000..3cfadf9b08
--- /dev/null
+++ b/repos/ports/src/virtualbox6/devxhci.cc
@@ -0,0 +1,603 @@
+/*
+ * \brief NEC XHCI device frontend
+ * \author Josef Soentgen
+ * \author Sebastian Sumpf
+ * \date 2015-12-10
+ */
+
+/*
+ * Copyright (C) 2015-2021 Genode Labs GmbH
+ *
+ * This file is distributed under the terms of the GNU General Public License
+ * version 2.
+ */
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+#include
+
+/* qemu-usb includes */
+#include
+
+/* libc internal includes */
+#include
+
+/* Virtualbox includes */
+#define LOG_GROUP LOG_GROUP_DEV_EHCI
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#ifdef IN_RING3
+# include
+# include
+# include
+# include
+#endif
+#include
+#include
+
+
+/* local include */
+#include "init.h"
+
+
+static Genode::Env *_xhci_genode_env;
+
+namespace Xhci {
+
+ void init(Genode::Env &env)
+ {
+ _xhci_genode_env = &env;
+ }
+} /* namespace Xhci */
+
+
+static bool const verbose_timer = false;
+
+
+/************************
+ ** xHCI device struct **
+ ************************/
+
+struct Timer_queue;
+
+
+struct XHCI
+{
+ /** Pointer to the device instance - R3 ptr. */
+ PPDMDEVINSR3 pDevInsR3;
+
+ /** The MMIO region handle. */
+ IOMMMIOHANDLE hMmio;
+
+ PTMTIMERR3 controller_timer;
+ Timer_queue *timer_queue;
+ Qemu::Controller *ctl;
+
+ Genode::Entrypoint *usb_ep;
+};
+
+
+/** Pointer to XHCI device data. */
+typedef struct XHCI *PXHCI;
+/** Read-only pointer to the XHCI device data. */
+typedef struct XHCI const *PCXHCI;
+
+
+/*************************************
+ ** Qemu::Controller helper classes **
+ *************************************/
+
+struct Timer_queue : public Qemu::Timer_queue
+{
+ Genode::Allocator &_alloc;
+
+ struct Context : public Genode::List::Element
+ {
+ uint64_t timeout_abs_ns = ~0ULL;
+ bool pending = false;
+
+ void *qtimer = nullptr;
+ void (*cb)(void*) = nullptr;
+ void *data = nullptr;
+
+ Context(void *qtimer, void (*cb)(void*), void *data)
+ : qtimer(qtimer), cb(cb), data(data) { }
+ };
+
+ Genode::List _context_list;
+ PTMTIMER tm_timer;
+
+ void _append_new_context(void *qtimer, void (*cb)(void*), void *data)
+ {
+ Context *new_ctx = new (_alloc) Context(qtimer, cb, data);
+ _context_list.insert(new_ctx);
+ }
+
+ Context *_find_context(void const *qtimer)
+ {
+ for (Context *c = _context_list.first(); c; c = c->next())
+ if (c->qtimer == qtimer)
+ return c;
+ return nullptr;
+ }
+
+ Context *_min_pending()
+ {
+ Context *min = nullptr;
+ for (min = _context_list.first(); min; min = min->next())
+ if (min && min->pending)
+ break;
+
+ if (!min || !min->next())
+ return min;
+
+ for (Context *c = min->next(); c; c = c->next()) {
+ if ((c->timeout_abs_ns < min->timeout_abs_ns) && c->pending)
+ min = c;
+ }
+
+ return min;
+ }
+
+ void _program_min_timer()
+ {
+ Context *min = _min_pending();
+ if (min == nullptr) return;
+
+ if (TMTimerIsActive(tm_timer))
+ TMTimerStop(tm_timer);
+
+ TMTimerSetNano(tm_timer, min->timeout_abs_ns - TMTimerGetNano(tm_timer));
+ }
+
+ void _deactivate_timer(void *qtimer)
+ {
+ Context *c = _find_context(qtimer);
+ if (c == nullptr) {
+ Genode::error("qtimer: ", qtimer, " not found");
+ throw -1;
+ }
+
+ if (c == _min_pending()) {
+ c->pending = false;
+ TMTimerStop(tm_timer);
+ _program_min_timer();
+ }
+
+ c->pending = false;
+ }
+
+ Timer_queue(Genode::Allocator &alloc, PTMTIMER timer)
+ : _alloc(alloc), tm_timer(timer) { }
+
+ void timeout()
+ {
+ uint64_t now = TMTimerGetNano(tm_timer);
+
+ for (Context *c = _context_list.first(); c; c = c->next()) {
+ if (c->pending && c->timeout_abs_ns <= now) {
+ c->pending = false;
+ Qemu::usb_timer_callback(c->cb, c->data);
+ }
+ }
+
+ _program_min_timer();
+ }
+
+ /**********************
+ ** TMTimer callback **
+ **********************/
+
+ static DECLCALLBACK(void) tm_timer_cb(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
+ {
+ PXHCI pThis = PDMINS_2_DATA(pDevIns, PXHCI);
+ Timer_queue *q = pThis->timer_queue;
+
+ q->timeout();
+ }
+
+
+ unsigned count_timer()
+ {
+ unsigned res = 0;
+
+ for (Context *c = _context_list.first(); c; c = c->next()) {
+ if (c->pending) Genode::log("timer: ", c, " is pending");
+ res++;
+ }
+
+ return res;
+ }
+
+ /*********************************
+ ** Qemu::Timer_queue interface **
+ *********************************/
+
+ Qemu::int64_t get_ns() { return TMTimerGetNano(tm_timer); }
+
+ Genode::Mutex _timer_mutex { };
+
+ void register_timer(void *qtimer, void (*cb)(void*), void *data) override
+ {
+ Genode::Mutex::Guard guard(_timer_mutex);
+ if (verbose_timer)
+ Genode::log("qtimer: ", qtimer, " cb: ", cb, " data: ", data);
+
+ Context *c = _find_context(qtimer);
+ if (c != nullptr) {
+ Genode::error("qtimer: ", qtimer, " already registred");
+ throw -1;
+ }
+
+ _append_new_context(qtimer, cb, data);
+ }
+
+ void delete_timer(void *qtimer) override
+ {
+ Genode::Mutex::Guard guard(_timer_mutex);
+ if (verbose_timer)
+ Genode::log("qtimer: ", qtimer);
+
+ Context *c = _find_context(qtimer);
+ if (c == nullptr) {
+ Genode::error("qtimer: ", qtimer, " not found");
+ throw -1;
+ }
+
+ _deactivate_timer(qtimer);
+
+ _context_list.remove(c);
+ Genode::destroy(_alloc, c);
+ }
+
+ void activate_timer(void *qtimer, long long int expire_abs) override
+ {
+ Genode::Mutex::Guard guard(_timer_mutex);
+ if (verbose_timer)
+ Genode::log("qtimer: ", qtimer, " expire: ", expire_abs);
+
+ Context *c = _find_context(qtimer);
+ if (c == nullptr) {
+ Genode::error("qtimer: ", qtimer, " not found");
+ throw -1;
+ }
+
+ c->timeout_abs_ns = expire_abs;
+ c->pending = true;
+
+ _program_min_timer();
+ }
+
+ void deactivate_timer(void *qtimer) override
+ {
+ Genode::Mutex::Guard guard(_timer_mutex);
+ if (verbose_timer)
+ Genode::log("qtimer: ", qtimer);
+
+ _deactivate_timer(qtimer);
+ }
+};
+
+
+struct Pci_device : public Qemu::Pci_device
+{
+ Genode::Allocator &_alloc;
+
+ PPDMDEVINS pci_dev;
+
+ struct Dma_bounce_buffer
+ {
+ Genode::Allocator &_alloc;
+
+ Qemu::addr_t const base;
+ Qemu::size_t const size;
+ void * const addr { _alloc.alloc(size) };
+
+ Dma_bounce_buffer(Genode::Allocator &alloc,
+ Qemu::addr_t base,
+ Qemu::size_t size)
+ : _alloc { alloc }, base { base }, size { size }
+ { }
+
+ virtual ~Dma_bounce_buffer()
+ {
+ _alloc.free(addr, size);
+ }
+ };
+
+ using Reg_dma_buffer = Genode::Registered;
+ Genode::Registry _dma_buffers { };
+
+ Pci_device(Genode::Allocator &alloc, PPDMDEVINS pDevIns)
+ : _alloc(alloc), pci_dev(pDevIns) { }
+
+ void raise_interrupt(int level) override {
+ PDMDevHlpPCISetIrqNoWait(pci_dev, 0, level); }
+
+ int read_dma(Qemu::addr_t addr, void *buf, Qemu::size_t size) override {
+ return PDMDevHlpPhysRead(pci_dev, addr, buf, size); }
+
+ int write_dma(Qemu::addr_t addr, void const *buf, Qemu::size_t size) override {
+ return PDMDevHlpPhysWrite(pci_dev, addr, buf, size); }
+
+ void *map_dma(Qemu::addr_t base, Qemu::size_t size,
+ Qemu::Pci_device::Dma_direction dir) override
+ {
+ Reg_dma_buffer *dma = nullptr;
+
+ try {
+ dma = new (_alloc) Reg_dma_buffer(_dma_buffers,
+ _alloc, base, size);
+ } catch (...) {
+ return nullptr;
+ }
+
+ /* copy data for write request to bounce buffer */
+ if (dir == Qemu::Pci_device::Dma_direction::OUT) {
+ (void)PDMDevHlpPhysRead(pci_dev, base, dma->addr, size);
+ }
+
+ return dma->addr;
+ }
+
+ void unmap_dma(void *addr, Qemu::size_t size,
+ Qemu::Pci_device::Dma_direction dir) override
+ {
+ _dma_buffers.for_each([&] (Reg_dma_buffer &dma) {
+ if (dma.addr != addr) {
+ return;
+ }
+
+ /* copy data for read request from bounce buffer */
+ if (dir == Qemu::Pci_device::Dma_direction::IN) {
+ (void)PDMDevHlpPhysWrite(pci_dev,
+ dma.base, dma.addr, dma.size);
+ }
+
+ Genode::destroy(_alloc, &dma);
+ });
+ }
+};
+
+
+/***********************************************
+ ** Virtualbox Device function implementation **
+ ***********************************************/
+
+/**
+ * @callback_method_impl{FNIOMMMIOREAD}
+ */
+PDMBOTHCBDECL(VBOXSTRICTRC) xhciMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off,
+ void *pv, unsigned cb)
+{
+ PXHCI pThis = PDMINS_2_DATA(pDevIns, PXHCI);
+
+ Genode::off_t offset = off;
+ Qemu::Controller *ctl = pThis->ctl;
+
+ ctl->mmio_read(offset, pv, cb);
+ return 0;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMMMIOWRITE}
+ */
+PDMBOTHCBDECL(VBOXSTRICTRC) xhciMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off,
+ void const *pv, unsigned cb)
+{
+ PXHCI pThis = PDMINS_2_DATA(pDevIns, PXHCI);
+
+ Genode::off_t offset = off;
+ Qemu::Controller *ctl = pThis->ctl;
+
+ ctl->mmio_write(offset, pv, cb);
+ return 0;
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnReset}
+ */
+static DECLCALLBACK(void) xhciR3Reset(PPDMDEVINS pDevIns)
+{
+ PXHCI pThis = PDMINS_2_DATA(pDevIns, PXHCI);
+
+ Qemu::usb_reset();
+ Qemu::usb_update_devices();
+}
+
+
+/**
+ * @nterface_method_impl{PDMDEVREG,pfnDestruct}
+ */
+static DECLCALLBACK(int) xhciR3Destruct(PPDMDEVINS pDevIns)
+{
+ Qemu::usb_reset();
+ return 0;
+}
+
+
+struct Usb_ep : Genode::Entrypoint
+{
+ pthread_t _pthread;
+
+ void _handle_pthread_registration()
+ {
+ Genode::Thread *myself = Genode::Thread::myself();
+ if (!myself || Libc::pthread_create(&_pthread, *myself, &myself)) {
+ Genode::error("USB passthough will not work - thread for "
+ "pthread registration invalid");
+ }
+ }
+
+ Genode::Signal_handler _pthread_reg_sigh;
+
+ enum { USB_EP_STACK = 32u << 10, };
+
+ Usb_ep(Genode::Env &env)
+ :
+ Entrypoint(env, USB_EP_STACK, "usb_ep", Genode::Affinity::Location()),
+ _pthread_reg_sigh(*this, *this, &Usb_ep::_handle_pthread_registration)
+ {
+ Genode::Signal_transmitter(_pthread_reg_sigh).submit();
+ }
+};
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct,XHCI constructor}
+ */
+static DECLCALLBACK(int) xhciR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PXHCI pThis = PDMINS_2_DATA(pDevIns, PXHCI);
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+
+ pThis->usb_ep = new Usb_ep(*_xhci_genode_env);
+
+ static Libc::Allocator alloc;
+
+ int rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL, Timer_queue::tm_timer_cb,
+ pThis, TMTIMER_FLAGS_NO_CRIT_SECT,
+ "XHCI Timer", &pThis->controller_timer);
+
+ static Timer_queue timer_queue(alloc, pThis->controller_timer);
+ pThis->timer_queue = &timer_queue;
+ static Pci_device pci_device(alloc, pDevIns);
+
+ pThis->ctl = Qemu::usb_init(timer_queue, pci_device, *pThis->usb_ep,
+ alloc, *_xhci_genode_env);
+
+ Qemu::Controller::Info const ctl_info = pThis->ctl->info();
+
+ /*
+ * Init instance data.
+ */
+ pThis->pDevInsR3 = pDevIns;
+
+ PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0];
+ PDMPCIDEV_ASSERT_VALID(pDevIns, pPciDev);
+
+ PCIDevSetVendorId (pPciDev, ctl_info.vendor_id);
+ PCIDevSetDeviceId (pPciDev, ctl_info.product_id);
+ PCIDevSetClassBase (pPciDev, 0x0c); /* PCI serial */
+ PCIDevSetClassSub (pPciDev, 0x03); /* USB */
+ PCIDevSetClassProg (pPciDev, 0x30); /* xHCI */
+ PCIDevSetInterruptPin (pPciDev, 0x01);
+ PCIDevSetByte (pPciDev, 0x60, 0x30); /* Serial Bus Release Number Register */
+
+ /*
+ * Register PCI device and I/O region.
+ */
+ rc = PDMDevHlpPCIRegister(pDevIns, pPciDev);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ uint32_t const mmio_flags = IOMMMIO_FLAGS_READ_DWORD
+ | IOMMMIO_FLAGS_WRITE_DWORD_ZEROED;
+ // | IOMMMIO_FLAGS_DBGSTOP_ON_COMPLICATED_WRITE;
+
+ rc = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 0, pThis->ctl->mmio_size(),
+ PCI_ADDRESS_SPACE_MEM,
+ xhciMmioWrite, xhciMmioRead, NULL,
+ mmio_flags,
+ "QEMU XHCI", &pThis->hMmio);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ return VINF_SUCCESS;
+}
+
+const PDMDEVREG g_DeviceXHCI =
+{
+ /* .u32version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "qemu-xhci",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE,
+ /* .fClass = */ PDM_DEVREG_CLASS_BUS_USB,
+ /* .cMaxInstances = */ ~0U,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(XHCI),
+ /* .cbInstanceCC = */ 0, //sizeof(XHCICC),
+ /* .cbInstanceRC = */ 0,
+ /* .cMaxPciDevices = */ 1,
+ /* .cMaxMsixVectors = */ 0,
+ /* .pszDescription = */ "QEMU XHCI USB controller.\n",
+#if defined(IN_RING3)
+ /* .pszRCMod = */ "VBoxDDRC.rc",
+ /* .pszR0Mod = */ "VBoxDDR0.r0",
+ /* .pfnConstruct = */ xhciR3Construct,
+ /* .pfnDestruct = */ xhciR3Destruct,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ xhciR3Reset,
+ /* .pfnSuspend = */ NULL,
+ /* .pfnResume = */ NULL,
+ /* .pfnAttach = */ NULL,
+ /* .pfnDetach = */ NULL,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ NULL,
+ /* .pfnSoftReset = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RING0)
+ /* .pfnEarlyConstruct = */ NULL,
+ /* .pfnConstruct = */ NULL,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnFinalDestruct = */ NULL,
+ /* .pfnRequest = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RC)
+ /* .pfnConstruct = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#else
+# error "Not in IN_RING3, IN_RING0 or IN_RC!"
+#endif
+ /* .u32VersionEnd = */ PDM_DEVREG_VERSION
+};
+
+
+bool use_xhci_controller()
+{
+ try {
+ Genode::Attached_rom_dataspace config(*_xhci_genode_env, "config");
+ return config.xml().attribute_value("xhci", false);
+ } catch (Genode::Rom_connection::Rom_connection_failed) {
+ return false;
+ }
+}
diff --git a/repos/ports/src/virtualbox6/init.h b/repos/ports/src/virtualbox6/init.h
index 71eece96b9..3eb048fbab 100644
--- a/repos/ports/src/virtualbox6/init.h
+++ b/repos/ports/src/virtualbox6/init.h
@@ -21,4 +21,6 @@ namespace Sup { void init(Genode::Env &); }
namespace Network { void init(Genode::Env &); }
+namespace Xhci { void init(Genode::Env &); }
+
#endif /* _INIT_H_ */
diff --git a/repos/ports/src/virtualbox6/main.cc b/repos/ports/src/virtualbox6/main.cc
index 1c49d5c2b3..b62d9e37e7 100644
--- a/repos/ports/src/virtualbox6/main.cc
+++ b/repos/ports/src/virtualbox6/main.cc
@@ -360,6 +360,7 @@ void Libc::Component::construct(Libc::Env &env)
}
Sup::init(env);
+ Xhci::init(env);
try {
static Main main(env);
diff --git a/repos/ports/src/virtualbox6/patches/qemu-xhci.patch b/repos/ports/src/virtualbox6/patches/qemu-xhci.patch
new file mode 100644
index 0000000000..b5f2f6eb44
--- /dev/null
+++ b/repos/ports/src/virtualbox6/patches/qemu-xhci.patch
@@ -0,0 +1,35 @@
+--- a/src/virtualbox6/src/VBox/Main/src-client/BusAssignmentManager.cpp
++++ b/src/virtualbox6/src/VBox/Main/src-client/BusAssignmentManager.cpp
+@@ -77,6 +77,7 @@ static const DeviceAssignmentRule aGenericRules[] =
+ {"usb-ohci", 0, 6, 0, 0},
+ {"usb-ehci", 0, 11, 0, 0},
+ {"usb-xhci", 0, 12, 0, 0},
++ {"qemu-xhci", 0, 29, 0, 0},
+
+ /* ACPI controller */
+ #if 0
+diff --git a/src/VBox/Main/src-client/ConsoleImpl2.cpp b/src/VBox/Main/src-client/ConsoleImpl2.cpp
+index 9e8e481..dbb4681 100644
+--- a/src/virtualbox6/src/VBox/Main/src-client/ConsoleImpl2.cpp
++++ a/src/virtualbox6/src/VBox/Main/src-client/ConsoleImpl2.cpp
+@@ -2135,6 +2135,20 @@ int Console::i_configConstructorInner(PUVM pUVM, PVM pVM, AutoWriteLock *pAlock)
+ }
+
+ /*
++ * QEMU xHCI controllers.
++ */
++ extern bool use_xhci_controller();
++ if (use_xhci_controller()) {
++
++ InsertConfigNode(pDevices, "qemu-xhci", &pDev);
++ InsertConfigNode(pDev, "0", &pInst);
++ InsertConfigNode(pInst, "Config", &pCfg);
++ InsertConfigInteger(pInst, "Trusted", 1);
++ hrc = pBusMgr->assignPCIDevice("qemu-xhci", pInst); H();
++ }
++
++
++ /*
+ * Storage controllers.
+ */
+ com::SafeIfaceArray ctrls;
diff --git a/repos/ports/src/virtualbox6/patches/series b/repos/ports/src/virtualbox6/patches/series
index b37d452ab8..c2b7fbcb2d 100644
--- a/repos/ports/src/virtualbox6/patches/series
+++ b/repos/ports/src/virtualbox6/patches/series
@@ -1,2 +1,3 @@
serial.patch
drvtap.patch
+qemu-xhci.patch
diff --git a/repos/ports/src/virtualbox6/target.mk b/repos/ports/src/virtualbox6/target.mk
index 73cee714a7..9feca74957 100644
--- a/repos/ports/src/virtualbox6/target.mk
+++ b/repos/ports/src/virtualbox6/target.mk
@@ -11,6 +11,7 @@ SRC_CC += network.cc
LIBS += base
LIBS += stdcxx
LIBS += libiconv
+LIBS += qemu-usb
CC_OPT_main = -Wno-multistatement-macros
CC_OPT += -DProgress=ClientProgress