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