diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Additions/solaris/Virtio | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Additions/solaris/Virtio')
-rw-r--r-- | src/VBox/Additions/solaris/Virtio/Makefile.kmk | 68 | ||||
-rw-r--r-- | src/VBox/Additions/solaris/Virtio/Virtio-solaris.c | 224 | ||||
-rw-r--r-- | src/VBox/Additions/solaris/Virtio/Virtio-solaris.h | 205 | ||||
-rw-r--r-- | src/VBox/Additions/solaris/Virtio/VirtioNet-solaris.c | 852 | ||||
-rw-r--r-- | src/VBox/Additions/solaris/Virtio/VirtioPci-solaris.c | 665 | ||||
-rw-r--r-- | src/VBox/Additions/solaris/Virtio/VirtioPci-solaris.h | 48 | ||||
-rw-r--r-- | src/VBox/Additions/solaris/Virtio/VirtioRing-solaris.c | 148 |
7 files changed, 2210 insertions, 0 deletions
diff --git a/src/VBox/Additions/solaris/Virtio/Makefile.kmk b/src/VBox/Additions/solaris/Virtio/Makefile.kmk new file mode 100644 index 00000000..cd56717e --- /dev/null +++ b/src/VBox/Additions/solaris/Virtio/Makefile.kmk @@ -0,0 +1,68 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for Solaris Virtio drivers. +# + +# +# Copyright (C) 2010-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# 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 +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +#ifneq ($(KBUILD_HOST),solaris) +#$(error "The Solaris guest additions can only be built on Solaris!") +#endif + +# +# virtionet ("virtnet" on Solaris) - The Virtio Network Drivers +# +SYSMODS.solaris += virtionet +virtionet_NAME.solaris = virtnet +virtionet_TEMPLATE = VBoxGuestR0Drv +ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING + virtionet_DEFS = VBOX_WITH_HGCM VBOX_SVN_REV=$(VBOX_SVN_REV) VBOX_VERSION_STRING="$(VBOX_VERSION_STRING)" +else + virtionet_DEFS = VBOX_WITH_HGCM VBOX_SVN_REV=$(VBOX_SVN_REV) VBOX_VERSION_STRING=\"$(VBOX_VERSION_STRING)\" +endif +virtionet_DEPS = $(VBOX_SVN_REV_KMK) +virtionet_INCS := . +virtionet_SOURCES = \ + Virtio-solaris.c \ + VirtioPci-solaris.c \ + VirtioRing-solaris.c \ + VirtioNet-solaris.c +# virtionet should resolve all IPRT bits from vboxguest. +#virtionet_LIBS = \ +# $(VBOX_LIB_IPRT_GUEST_R0) +virtionet_LDFLAGS += -N drv/vboxguest -N misc/mac + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Additions/solaris/Virtio/Virtio-solaris.c b/src/VBox/Additions/solaris/Virtio/Virtio-solaris.c new file mode 100644 index 00000000..7406f68d --- /dev/null +++ b/src/VBox/Additions/solaris/Virtio/Virtio-solaris.c @@ -0,0 +1,224 @@ +/* $Id: Virtio-solaris.c $ */ +/** @file + * VirtualBox Guest Additions - Virtio Driver for Solaris. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "Virtio-solaris.h" + +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <VBox/log.h> + + +/** + * Virtio Attach routine that should be called from all Virtio drivers' attach + * routines. + * + * @param pDip The module structure instance. + * @param enmCmd Operation type (attach/resume). + * @param pDeviceOps Pointer to device ops structure. + * @param pHyperOps Pointer to hypervisor ops structure. + * + * @return Solaris DDI error code. DDI_SUCCESS or DDI_FAILURE. + */ +int VirtioAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd, PVIRTIODEVICEOPS pDeviceOps, PVIRTIOHYPEROPS pHyperOps) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioAttach: pDip=%p enmCmd=%d pDeviceOps=%p pHyperOps=%p\n", pDip, enmCmd, pDeviceOps, pHyperOps)); + + AssertReturn(pDip, DDI_EINVAL); + AssertReturn(pDeviceOps, DDI_EINVAL); + AssertReturn(pHyperOps, DDI_EINVAL); + + if (enmCmd != DDI_ATTACH) + { + LogRel((VIRTIOLOGNAME ":VirtioAttach: Invalid enmCmd=%#x expected DDI_ATTACH\n", enmCmd)); + return DDI_FAILURE; + } + + int rc = DDI_FAILURE; + PVIRTIODEVICE pDevice = RTMemAllocZ(sizeof(VIRTIODEVICE)); + if (RT_LIKELY(pDevice)) + { + pDevice->pDip = pDip; + pDevice->pDeviceOps = pDeviceOps; + pDevice->pHyperOps = pHyperOps; + + pDevice->pvDevice = pDevice->pDeviceOps->pfnAlloc(pDevice); + if (RT_LIKELY(pDevice->pvDevice)) + { + pDevice->pvHyper = pDevice->pHyperOps->pfnAlloc(pDevice); + if (RT_LIKELY(pDevice->pvHyper)) + { + /* + * Attach hypervisor interface and obtain features supported by host. + */ + rc = pDevice->pHyperOps->pfnAttach(pDevice); + if (rc == DDI_SUCCESS) + { + pDevice->fHostFeatures = pDevice->pHyperOps->pfnGetFeatures(pDevice); + LogFlow((VIRTIOLOGNAME ":VirtioAttach: Host features=%#x\n", pDevice->fHostFeatures)); + + /* + * Attach the device type interface. + */ + rc = pDevice->pDeviceOps->pfnAttach(pDevice); + if (rc == DDI_SUCCESS) + { + ddi_set_driver_private(pDip, pDevice); + return DDI_SUCCESS; + } + else + LogRel((VIRTIOLOGNAME ":VirtioAttach: DeviceOps pfnAttach failed. rc=%d\n", rc)); + + pDevice->pHyperOps->pfnDetach(pDevice); + } + else + LogRel((VIRTIOLOGNAME ":VirtioAttach: HyperOps pfnAttach failed. rc=%d\n", rc)); + + pDevice->pHyperOps->pfnFree(pDevice); + } + else + LogRel((VIRTIOLOGNAME ":VirtioAttach: HyperOps->pfnAlloc failed!\n")); + + pDevice->pDeviceOps->pfnFree(pDevice); + } + else + LogRel((VIRTIOLOGNAME ":VirtioAttach: DeviceOps->pfnAlloc failed!\n")); + + RTMemFree(pDevice); + } + else + LogRel((VIRTIOLOGNAME ":VirtioAttach: failed to alloc %u bytes for device structure.\n", sizeof(VIRTIODEVICE))); + + return DDI_FAILURE; +} + + +/** + * Virtio Detach routine that should be called from all Virtio drivers' detach + * routines. + * + * @param pDip The module structure instance. + * @param enmCmd Operation type (detach/suspend). + * + * @return Solaris DDI error code. DDI_SUCCESS or DDI_FAILURE. + */ +int VirtioDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioDetach pDip=%p enmCmd=%d\n", pDip, enmCmd)); + + PVIRTIODEVICE pDevice = ddi_get_driver_private(pDip); + if (RT_UNLIKELY(!pDevice)) + return DDI_FAILURE; + + if (enmCmd != DDI_DETACH) + { + LogRel((VIRTIOLOGNAME ":VirtioDetach: Invalid enmCmd=%#x expected DDI_DETACH.\n", enmCmd)); + return DDI_FAILURE; + } + + int rc = pDevice->pDeviceOps->pfnDetach(pDevice); + if (rc == DDI_SUCCESS) + { + pDevice->pHyperOps->pfnDetach(pDevice); + pDevice->pDeviceOps->pfnFree(pDevice); + pDevice->pvDevice = NULL; + pDevice->pHyperOps->pfnFree(pDevice); + pDevice->pvHyper = NULL; + + ddi_set_driver_private(pDevice->pDip, NULL); + RTMemFree(pDevice); + return DDI_SUCCESS; + } + else + LogRel((VIRTIOLOGNAME ":VirtioDetach: DeviceOps pfnDetach failed. rc=%d\n", rc)); + + return DDI_FAILURE; +} + + +/** + * Allocates a Virtio Queue object and assigns it an index. + * + * @param pDevice Pointer to the Virtio device instance. + * @param Index Queue index. + * + * @return A pointer to a Virtio Queue instance. + */ +PVIRTIOQUEUE VirtioGetQueue(PVIRTIODEVICE pDevice, uint16_t Index) +{ + PVIRTIOQUEUE pQueue = RTMemAllocZ(sizeof(VIRTIOQUEUE)); + if (RT_UNLIKELY(!pQueue)) + { + LogRel((VIRTIOLOGNAME ":VirtioGetQueue: failed to alloc memory for %u bytes.\n", sizeof(VIRTIOQUEUE))); + return NULL; + } + + pQueue->QueueIndex = Index; + pQueue->pvData = pDevice->pHyperOps->pfnGetQueue(pDevice, pQueue); + if (RT_UNLIKELY(!pQueue->pvData)) + { + LogRel((VIRTIOLOGNAME ":VirtioGetQueue: HyperOps GetQueue failed!\n")); + RTMemFree(pQueue); + return NULL; + } + + AssertReturn(pQueue->pQueue, NULL); + AssertReturn(pQueue->Ring.cDesc > 0, NULL); + + /** @todo enable interrupt. */ + + return pQueue; +} + + +/** + * Puts a queue and destroys the instance. + * + * @param pDevice Pointer to the Virtio device instance. + * @param pQueue Pointer to the Virtio queue. + */ +void VirtioPutQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue) +{ + AssertReturnVoid(pDevice); + AssertReturnVoid(pQueue); + + pDevice->pHyperOps->pfnPutQueue(pDevice, pQueue); + RTMemFree(pQueue); +} + diff --git a/src/VBox/Additions/solaris/Virtio/Virtio-solaris.h b/src/VBox/Additions/solaris/Virtio/Virtio-solaris.h new file mode 100644 index 00000000..9badca92 --- /dev/null +++ b/src/VBox/Additions/solaris/Virtio/Virtio-solaris.h @@ -0,0 +1,205 @@ +/* $Id: Virtio-solaris.h $ */ +/** @file + * VirtualBox Guest Additions: Virtio Driver for Solaris, header. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef GA_INCLUDED_SRC_solaris_Virtio_Virtio_solaris_h +#define GA_INCLUDED_SRC_solaris_Virtio_Virtio_solaris_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <sys/sunddi.h> + +/** Release log descriptive prefix. */ +#define VIRTIOLOGNAME "Virtio" +/** Buffer continues via the Next field */ +#define VIRTIO_FLAGS_RING_DESC_NEXT RT_BIT(0) +/** Buffer is write-only, else is read-only. */ +#define VIRTIO_FLAGS_RING_DESC_WRITE RT_BIT(1) +/** Indirect buffer (buffer contains list of buffer descriptors) */ +#define VIRTIO_FLAGS_RING_DESC_INDIRECT RT_BIT(2) + +/* Values from our Virtio.h */ +#define VIRTIO_PCI_STATUS_ACK 0x01 +#define VIRTIO_PCI_STATUS_DRV 0x02 +#define VIRTIO_PCI_STATUS_DRV_OK 0x04 +#define VIRTIO_PCI_STATUS_FAILED 0x80 + +/** + * The ring descriptor table refers to the buffers the guest is using for the + * device. + */ +struct VirtioRingDesc +{ + uint64_t AddrBuf; /* Physical address of buffer. */ + uint32_t cbBuf; /* Length of the buffer in bytes. */ + uint16_t fFlags; /* Flags of the next buffer. */ + uint16_t Next; /* Index of the next buffer. */ +}; +typedef struct VirtioRingDesc VIRTIORINGDESC; +typedef VIRTIORINGDESC *PVIRTIORINGDESC; + +/** + * The available ring refers to what descriptors are being offered to the + * device. + */ +struct VirtioRingAvail +{ + uint16_t fFlags; /* Interrupt supression flag. */ + uint16_t Index; /* Index of available ring. */ + uint16_t aRings[1]; /* Array of indices into descriptor table. */ +}; +typedef struct VirtioRingAvail VIRTIORINGAVAIL; +typedef VIRTIORINGAVAIL *PVIRTIORINGAVAIL; + +/** + * The used ring refers to the buffers the device is done using them. The + * element is a pair-descriptor refers to the buffer once the device is done + * with the buffer. + */ +struct VirtioRingUsedElem +{ + uint32_t Index; /* Index of start of used descriptor chain. */ + uint32_t cbElem; /* Number of bytes written into the buffer. */ +}; +typedef struct VirtioRingUsedElem VIRTIORINGUSEDELEM; +typedef VIRTIORINGUSEDELEM *PVIRTIORINGUSEDELEM; + +/** + * The Virtio Ring which contains the descriptors. + */ +struct VirtioRing +{ + uint_t cDesc; /* Number of descriptors. */ + PVIRTIORINGDESC pRingDesc; /* Pointer to ring descriptor. */ + PVIRTIORINGAVAIL pRingAvail; /* Pointer to available ring. */ + PVIRTIORINGUSEDELEM pRingUsedElem; /* Pointer to used ring element. */ +}; +typedef struct VirtioRing VIRTIORING; +typedef VIRTIORING *PVIRTIORING; + +struct VirtioDevice; +struct VirtioQueue; + +typedef void *(*PFNVIRTIOALLOC)(struct VirtioDevice *pDevice); +typedef void (*PFNVIRTIOFREE)(struct VirtioDevice *pDevice); +typedef int (*PFNVIRTIOATTACH)(struct VirtioDevice *pDevice); +typedef int (*PFNVIRTIODETACH)(struct VirtioDevice *pDevice); +typedef uint32_t (*PFNVIRTIOGETFEATURES)(struct VirtioDevice *pDevice); +typedef void (*PFNVIRTIOSETFEATURES)(struct VirtioDevice *pDevice, uint32_t fFeatures); +typedef void (*PFNVIRTIOGET)(struct VirtioDevice *pDevice, off_t off, void *pv, size_t cb); +typedef void (*PFNVIRTIOSET)(struct VirtioDevice *pDevice, off_t off, void *pv, size_t cb); +typedef void *(*PFNVIRTIOGETQUEUE)(struct VirtioDevice *pDevice, struct VirtioQueue *pQueue); +typedef void (*PFNVIRTIOPUTQUEUE)(struct VirtioDevice *pDevice, struct VirtioQueue *pQueue); +typedef int (*PFNVIRTIONOTIFYQUEUE)(struct VirtioDevice *pDevice, struct VirtioQueue *pQueue); +typedef void (*PFNVIRTIOSETSTATUS)(struct VirtioDevice *pDevice, uint8_t Status); + +/** + * Virtio device operations. + */ +struct VirtioDeviceOps +{ + PFNVIRTIOALLOC pfnAlloc; /* Device alloc. */ + PFNVIRTIOFREE pfnFree; /* Device free. */ + PFNVIRTIOATTACH pfnAttach; /* Device attach. */ + PFNVIRTIODETACH pfnDetach; /* Device detach. */ +}; +typedef struct VirtioDeviceOps VIRTIODEVICEOPS; +typedef VIRTIODEVICEOPS *PVIRTIODEVICEOPS; + +/** + * Hypervisor access operations. + */ +struct VirtioHyperOps +{ + PFNVIRTIOALLOC pfnAlloc; /* Hypervisor alloc. */ + PFNVIRTIOFREE pfnFree; /* Hypervisor free */ + PFNVIRTIOATTACH pfnAttach; /* Hypervisor attach. */ + PFNVIRTIODETACH pfnDetach; /* Hypervisor detach. */ + PFNVIRTIOGETFEATURES pfnGetFeatures; /* Hypervisor get features. */ + PFNVIRTIOSETFEATURES pfnSetFeatures; /* Hypervisor set features. */ + PFNVIRTIONOTIFYQUEUE pfnNotifyQueue; /* Hypervisor notify queue. */ + PFNVIRTIOGET pfnGet; /* Hypervisor get. */ + PFNVIRTIOSET pfnSet; /* Hypervisor set. */ + PFNVIRTIOGETQUEUE pfnGetQueue; /* Hypervisor get queue. */ + PFNVIRTIOPUTQUEUE pfnPutQueue; /* Hypervisor put queue. */ + PFNVIRTIOSETSTATUS pfnSetStatus; /* Hypervisor set status. */ +}; +typedef struct VirtioHyperOps VIRTIOHYPEROPS; +typedef VIRTIOHYPEROPS *PVIRTIOHYPEROPS; + +/** + * Virtio Queue into which buffers are posted. + */ +struct VirtioQueue +{ + VIRTIORING Ring; /* Ring buffer of this queue. */ + uint16_t cBufs; /* Number of pushed, unnotified buffers. */ + uint16_t FreeHeadIndex; /* Index of head of free list. */ + uint16_t QueueIndex; /* Index of this queue. */ + caddr_t pQueue; /* Allocated DMA region for queue. */ + void *pvData; /* Queue private data. */ +}; +typedef struct VirtioQueue VIRTIOQUEUE; +typedef VIRTIOQUEUE *PVIRTIOQUEUE; + +/** + * Virtio device descriptor, common to all Virtio devices. + */ +struct VirtioDevice +{ + dev_info_t *pDip; /* OS device info. */ + PVIRTIODEVICEOPS pDeviceOps; /* Device hooks. */ + void *pvDevice; /* Device opaque data. */ + PVIRTIOHYPEROPS pHyperOps; /* Hypervisor hooks. */ + void *pvHyper; /* Hypervisor opaque data. */ + uint32_t fHostFeatures; /* Features provided by the host. */ +}; +typedef struct VirtioDevice VIRTIODEVICE; +typedef VIRTIODEVICE *PVIRTIODEVICE; + + +int VirtioAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd, PVIRTIODEVICEOPS pDeviceOps, PVIRTIOHYPEROPS pHyperOps); +int VirtioDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd); + +PVIRTIOQUEUE VirtioGetQueue(PVIRTIODEVICE pDevice, uint16_t Index); +void VirtioPutQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue); + +void VirtioRingInit(PVIRTIOQUEUE pQueue, uint_t cDescs, caddr_t virtBuf, ulong_t Align); +int VirtioRingPush(PVIRTIOQUEUE pQueue, paddr_t physBuf, uint32_t cbBuf, uint16_t fFlags); +size_t VirtioRingSize(uint64_t cElements, ulong_t Align); + +#endif /* !GA_INCLUDED_SRC_solaris_Virtio_Virtio_solaris_h */ + diff --git a/src/VBox/Additions/solaris/Virtio/VirtioNet-solaris.c b/src/VBox/Additions/solaris/Virtio/VirtioNet-solaris.c new file mode 100644 index 00000000..dee46ad7 --- /dev/null +++ b/src/VBox/Additions/solaris/Virtio/VirtioNet-solaris.c @@ -0,0 +1,852 @@ +/* $Id: VirtioNet-solaris.c $ */ +/** @file + * VirtualBox Guest Additions - Virtio Network Driver for Solaris. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "Virtio-solaris.h" +#include "VirtioPci-solaris.h" + +#include <sys/conf.h> +#include <sys/sunddi.h> +#include <sys/mac_provider.h> +#include <sys/strsun.h> +#include <sys/cmn_err.h> + +#include <iprt/assert.h> +#include <iprt/initterm.h> +#include <iprt/errcore.h> +#include <VBox/log.h> +#include <iprt/mem.h> +#include <iprt/rand.h> +#include <iprt/string.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define DEVICE_NAME "virtnet" +/** The module descriptions as seen in 'modinfo'. */ +#define DEVICE_DESC_DRV "VirtualBox VirtioNet" + +/** Copied from "mac_ether.h" - why the heck is this not public?? All Solaris + * mac clients use it... */ +#define MAC_PLUGIN_IDENT_ETHER "mac_ether" + +/* Copied from our Virtio Device emulation. */ +#define VIRTIO_NET_CSUM 0x00000001 /* Host handles pkts w/ partial csum */ +#define VIRTIO_NET_GUEST_CSUM 0x00000002 /* Guest handles pkts w/ partial csum */ +#define VIRTIO_NET_MAC 0x00000020 /* Host has given MAC address. */ +#define VIRTIO_NET_GSO 0x00000040 /* Host handles pkts w/ any GSO type */ +#define VIRTIO_NET_GUEST_TSO4 0x00000080 /* Guest can handle TSOv4 in. */ +#define VIRTIO_NET_GUEST_TSO6 0x00000100 /* Guest can handle TSOv6 in. */ +#define VIRTIO_NET_GUEST_ECN 0x00000200 /* Guest can handle TSO[6] w/ ECN in. */ +#define VIRTIO_NET_GUEST_UFO 0x00000400 /* Guest can handle UFO in. */ +#define VIRTIO_NET_HOST_TSO4 0x00000800 /* Host can handle TSOv4 in. */ +#define VIRTIO_NET_HOST_TSO6 0x00001000 /* Host can handle TSOv6 in. */ +#define VIRTIO_NET_HOST_ECN 0x00002000 /* Host can handle TSO[6] w/ ECN in. */ +#define VIRTIO_NET_HOST_UFO 0x00004000 /* Host can handle UFO in. */ +#define VIRTIO_NET_MRG_RXBUF 0x00008000 /* Host can merge receive buffers. */ +#define VIRTIO_NET_STATUS 0x00010000 /* virtio_net_config.status available */ +#define VIRTIO_NET_CTRL_VQ 0x00020000 /* Control channel available */ +#define VIRTIO_NET_CTRL_RX 0x00040000 /* Control channel RX mode support */ +#define VIRTIO_NET_CTRL_VLAN 0x00080000 /* Control channel VLAN filtering */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void *VirtioNetDevAlloc(PVIRTIODEVICE pDevice); +static void VirtioNetDevFree(PVIRTIODEVICE pDevice); +static int VirtioNetDevAttach(PVIRTIODEVICE pDevice); +static int VirtioNetDevDetach(PVIRTIODEVICE pDevice); + +static int VirtioNetAttach(dev_info_t *pDip, ddi_attach_cmd_t Cmd); +static int VirtioNetDetach(dev_info_t *pDip, ddi_detach_cmd_t Cmd); + +static int VirtioNetStat(void *pvArg, uint_t cmdStat, uint64_t *pu64Val); +static int VirtioNetStart(void *pvArg); +static void VirtioNetStop(void *pvArg); +static int VirtioNetSetPromisc(void *pvArg, boolean_t fPromiscOn); +static int VirtioNetSetMulticast(void *pvArg, boolean_t fAdd, const uint8_t *pbMac); +static int VirtioNetSetUnicast(void *pvArg, const uint8_t *pbMac); +static boolean_t VirtioNetGetCapab(void *pvArg, mac_capab_t Capab, void *pvCapabData); +static mblk_t *VirtioNetXmit(void *pvArg, mblk_t *pMsg); +static uint_t VirtioNetISR(caddr_t addrArg); + +static int VirtioNetAttachQueues(PVIRTIODEVICE pDevice); +static void VirtioNetDetachQueues(PVIRTIODEVICE pDevice); + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Device operations for Virtio Net. + */ +VIRTIODEVICEOPS g_VirtioDeviceOpsNet = +{ + VirtioNetDevAlloc, + VirtioNetDevFree, + VirtioNetDevAttach, + VirtioNetDevDetach +}; + +/** + * virtio_net_t: Private data per Virtio Device. + */ +typedef struct virtio_net_t +{ + mac_handle_t hMac; /* Handle to the MAC layer. */ + RTMAC MacAddr; /* MAC address. */ + PVIRTIOQUEUE pRxQueue; /* Receive Queue. */ + PVIRTIOQUEUE pTxQueue; /* Xmit Queue. */ + PVIRTIOQUEUE pCtrlQueue; /* Control Queue. */ + kmem_cache_t *pTxCache; /* TX buffer cache. */ +} virtio_net_t; + +/** + * virtio_txbuf_t: Virtio Net TX buffer. + */ +typedef struct virtio_net_txbuf_t +{ + ddi_dma_handle_t hDMA; /* DMA TX handle. */ +} virtio_net_txbuf_t; + +/* + * virtio_net_header_t: Virtio Net TX/RX buffer header. + */ +typedef struct virtio_net_header_t +{ + uint8_t u8Flags; /* Flags. */ + uint8_t u8GSOType; /* GSO type. */ + uint16_t u16HdrLen; /* Length of this header. */ + uint16_t u16GSOSize; /* GSO length if applicable. */ + uint16_t u16CSumStart; /* Checksum start.*/ + uint16_t u16CSumOffset; /* Checksum offset.*/ +} virtio_net_header_t; + +/** + * MAC layer hooks for VirtioNet. + */ +static mac_callbacks_t g_VirtioNetCallbacks = +{ + MC_GETCAPAB, /* Mask of available extra hooks. */ + VirtioNetStat, + VirtioNetStart, + VirtioNetStop, + VirtioNetSetPromisc, + VirtioNetSetMulticast, + VirtioNetSetUnicast, + VirtioNetXmit, + NULL, /* Reserved. */ + NULL, /* IOCtl. */ + VirtioNetGetCapab, +}; + +/** + * DMA transfer attributes for Xmit/Recv. buffers. + */ +static ddi_dma_attr_t g_VirtioNetBufDmaAttr = +{ + DMA_ATTR_V0, /* Version. */ + 0, /* Lowest usable address. */ + 0xffffffffffffffffULL, /* Highest usable address. */ + 0x7fffffff, /* Maximum DMAable byte count. */ + MMU_PAGESIZE, /* Alignment in bytes. */ + 0x7ff, /* Bitmap of burst sizes */ + 1, /* Minimum transfer. */ + 0xffffffffU, /* Maximum transfer. */ + 0xffffffffffffffffULL, /* Maximum segment length. */ + 1, /* Maximum number of segments. */ + 1, /* Granularity. */ + 0 /* Flags (reserved). */ +}; + +/** + * cb_ops: driver char/block entry points + */ +static struct cb_ops g_VirtioNetCbOps = +{ + nulldev, /* cb open */ + nulldev, /* cb close */ + nodev, /* b strategy */ + nodev, /* b dump */ + nodev, /* b print */ + nodev, /* cb read */ + nodev, /* cb write */ + nodev, /* cb ioctl */ + nodev, /* c devmap */ + nodev, /* c mmap */ + nodev, /* c segmap */ + nochpoll, /* c poll */ + ddi_prop_op, /* property ops */ + NULL, /* streamtab */ + D_MP, /* compat. flag */ + CB_REV /* revision */ +}; + +/** + * dev_ops: driver entry/exit and other ops. + */ +static struct dev_ops g_VirtioNetDevOps = +{ + DEVO_REV, /* driver build revision */ + 0, /* ref count */ + NULL, /* get info */ + nulldev, /* identify */ + nulldev, /* probe */ + VirtioNetAttach, + VirtioNetDetach, + nodev, /* reset */ + &g_VirtioNetCbOps, + (struct bus_ops *)0, + nodev /* power */ +}; + +/** + * modldrv: export driver specifics to kernel + */ +static struct modldrv g_VirtioNetDriver = +{ + &mod_driverops, /* extern from kernel */ + DEVICE_DESC_DRV " " VBOX_VERSION_STRING "r" RT_XSTR(VBOX_SVN_REV), + &g_VirtioNetDevOps +}; + +/** + * modlinkage: export install/remove/info to the kernel + */ +static struct modlinkage g_VirtioNetModLinkage = +{ + MODREV_1, /* loadable module system revision */ + { + &g_VirtioNetDriver, /* driver framework */ + NULL /* terminate array of linkage structures */ + } +}; + + +/** + * Kernel entry points + */ +int _init(void) +{ + LogFlowFunc((VIRTIOLOGNAME ":_init\n")); + + /* + * Prevent module autounloading. + */ + modctl_t *pModCtl = mod_getctl(&g_VirtioNetModLinkage); + if (pModCtl) + pModCtl->mod_loadflags |= MOD_NOAUTOUNLOAD; + else + LogRel((VIRTIOLOGNAME ":failed to disable autounloading!\n")); + + /* + * Initialize IPRT. + */ + int rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + /* + * Initialize Solaris specific globals here. + */ + mac_init_ops(&g_VirtioNetDevOps, DEVICE_NAME); + rc = mod_install(&g_VirtioNetModLinkage); + if (!rc) + return rc; + + LogRel((VIRTIOLOGNAME ":mod_install failed. rc=%d\n", rc)); + mac_fini_ops(&g_VirtioNetDevOps); + RTR0Term(); + return rc; + } + else + LogRel((VIRTIOLOGNAME ":failed to initialize IPRT (rc=%d)\n", rc)); + + return RTErrConvertToErrno(rc); +} + + +int _fini(void) +{ + int rc; + LogFlowFunc((VIRTIOLOGNAME ":_fini\n")); + + rc = mod_remove(&g_VirtioNetModLinkage); + if (!rc) + { + mac_fini_ops(&g_VirtioNetDevOps); + RTR0Term(); + } + return rc; +} + + +int _info(struct modinfo *pModInfo) +{ + LogFlowFunc((VIRTIOLOGNAME ":_info\n")); + + int rc = mod_info(&g_VirtioNetModLinkage, pModInfo); + + LogFlow((VIRTIOLOGNAME ":_info returns %d\n", rc)); + return rc; +} + + +/** + * Attach entry point, to attach a device to the system or resume it. + * + * @param pDip The module structure instance. + * @param Cmd Operation type (attach/resume). + * + * @return corresponding solaris error code. + */ +static int VirtioNetAttach(dev_info_t *pDip, ddi_attach_cmd_t Cmd) +{ + return VirtioAttach(pDip, Cmd, &g_VirtioDeviceOpsNet, &g_VirtioHyperOpsPci); +} + + +/** + * Detach entry point, to detach a device to the system or suspend it. + * + * @param pDip The module structure instance. + * @param Cmd Operation type (detach/suspend). + * + * @return corresponding solaris error code. + */ +static int VirtioNetDetach(dev_info_t *pDip, ddi_detach_cmd_t Cmd) +{ + return VirtioDetach(pDip, Cmd); +} + + +/** + * Virtio Net TX buffer constructor for kmem_cache_create(). + * + * @param pvBuf Pointer to the allocated buffer. + * @param pvArg Opaque private data. + * @param fFlags Propagated KM flag values. + * + * @return 0 on success, or -1 on failure. + */ +static int VirtioNetTxBufCreate(void *pvBuf, void *pvArg, int fFlags) +{ + virtio_net_txbuf_t *pTxBuf = pvBuf; + PVIRTIODEVICE pDevice = pvArg; + + /** @todo ncookies handles? */ + int rc = ddi_dma_alloc_handle(pDevice->pDip, &g_VirtioNetBufDmaAttr, + fFlags & KM_NOSLEEP ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP, + 0 /* Arg */, &pTxBuf->hDMA); + if (rc == DDI_SUCCESS) + return 0; + return -1; +} + + +/** + * Virtio Net TX buffer destructor for kmem_cache_create(). + * + * @param pvBuf Pointer to the allocated buffer. + * @param pvArg + */ +static void VirtioNetTxBufDestroy(void *pvBuf, void *pvArg) +{ + NOREF(pvArg); + virtio_net_txbuf_t *pTxBuf = pvBuf; + + ddi_dma_free_handle(&pTxBuf->hDMA); +} + + +/** + * Virtio Net private data allocation routine. + * + * @param pDevice Pointer to the Virtio device instance. + * + * @return Allocated private data that must only be freed by calling + * VirtioNetDevFree(). + */ +static void *VirtioNetDevAlloc(PVIRTIODEVICE pDevice) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioNetDevAlloc pDevice=%p\n", pDevice)); + + AssertReturn(pDevice, NULL); + virtio_net_t *pNet = RTMemAllocZ(sizeof(virtio_net_t)); + if (RT_LIKELY(pNet)) + { + /* + * Create a kernel memory cache for frequently allocated/deallocated + * buffers. + */ + char szCachename[KSTAT_STRLEN]; + RTStrPrintf(szCachename, sizeof(szCachename), "VirtioNet_Cache_%d", ddi_get_instance(pDevice->pDip)); + pNet->pTxCache = kmem_cache_create(szCachename, /* Cache name */ + sizeof(virtio_net_txbuf_t), /* Size of buffers in cache */ + 0, /* Align */ + VirtioNetTxBufCreate, /* Buffer constructor */ + VirtioNetTxBufDestroy, /* Buffer destructor */ + NULL, /* pfnReclaim */ + pDevice, /* Private data */ + NULL, /* "vmp", MBZ (man page) */ + 0 /* "cflags", MBZ (man page) */ + ); + if (RT_LIKELY(pNet->pTxCache)) + return pNet; + else + LogRel((VIRTIOLOGNAME ":kmem_cache_create failed.\n")); + } + else + LogRel((VIRTIOLOGNAME ":failed to alloc %u bytes for Net instance.\n", sizeof(virtio_net_t))); + + return NULL; +} + + +/** + * Virtio Net private data free routine. + * + * @param pDevice Pointer to the Virtio device instance. + */ +static void VirtioNetDevFree(PVIRTIODEVICE pDevice) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioNetDevFree pDevice=%p\n", pDevice)); + AssertReturnVoid(pDevice); + + virtio_net_t *pNet = pDevice->pvDevice; + kmem_cache_destroy(pNet->pTxCache); + RTMemFree(pNet); + pDevice->pvDevice = NULL; +} + + +/** + * Virtio Net device attach rountine. + * + * @param pDevice Pointer to the Virtio device instance. + * + * @return corresponding solaris error code. + */ +static int VirtioNetDevAttach(PVIRTIODEVICE pDevice) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioNetDevAttach pDevice=%p\n", pDevice)); + + virtio_net_t *pNet = pDevice->pvDevice; + mac_register_t *pMacRegHandle = mac_alloc(MAC_VERSION); + if (pMacRegHandle) + { + pMacRegHandle->m_driver = pDevice; + pMacRegHandle->m_dip = pDevice->pDip; + pMacRegHandle->m_callbacks = &g_VirtioNetCallbacks; + pMacRegHandle->m_type_ident = MAC_PLUGIN_IDENT_ETHER; + pMacRegHandle->m_min_sdu = 0; + pMacRegHandle->m_max_sdu = 1500; /** @todo verify */ + /** @todo should we set the margin size? */ + pMacRegHandle->m_src_addr = pNet->MacAddr.au8; + + /* + * Get MAC from the host or generate a random MAC address. + */ + if (pDevice->fHostFeatures & VIRTIO_NET_MAC) + { + pDevice->pHyperOps->pfnGet(pDevice, 0 /* offset */, &pNet->MacAddr.au8, sizeof(pNet->MacAddr)); + LogFlow((VIRTIOLOGNAME ":VirtioNetDevAttach: Obtained MAC address from host: %.6Rhxs\n", pNet->MacAddr.au8)); + } + else + { + pNet->MacAddr.au8[0] = 0x08; + pNet->MacAddr.au8[1] = 0x00; + pNet->MacAddr.au8[2] = 0x27; + RTRandBytes(&pNet->MacAddr.au8[3], 3); + LogFlow((VIRTIOLOGNAME ":VirtioNetDevAttach: Generated MAC address %.6Rhxs\n", pNet->MacAddr.au8)); + } + + int rc = VirtioNetAttachQueues(pDevice); + if (rc == DDI_SUCCESS) + { + rc = mac_register(pMacRegHandle, &pNet->hMac); + if (rc == 0) + { + mac_link_update(pNet->hMac, LINK_STATE_DOWN); + mac_free(pMacRegHandle); + LogFlow((VIRTIOLOGNAME ":VirtioNetDevAttach: successfully registered mac.\n")); + return DDI_SUCCESS; + } + else + LogRel((VIRTIOLOGNAME ":VirtioNetDevAttach: mac_register failed. rc=%d\n", rc)); + + VirtioNetDetachQueues(pDevice); + } + else + LogRel((VIRTIOLOGNAME ":VirtioNetDevAttach: VirtioNetAttachQueues failed. rc=%d\n", rc)); + + mac_free(pMacRegHandle); + } + else + LogRel((VIRTIOLOGNAME ":VirtioNetDevAttach: mac_alloc failed. Invalid version!?!\n")); + + return DDI_FAILURE; +} + + +/** + * Virtio Net device detach routine. + * + * @param pDevice Pointer to the Virtio device instance. + * + * @return corresponding solaris error code. + */ +static int VirtioNetDevDetach(PVIRTIODEVICE pDevice) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioNetDevDetach pDevice=%p\n", pDevice)); + virtio_net_t *pNet = pDevice->pvDevice; + + int rc = mac_unregister(pNet->hMac); + if (rc == 0) + { + VirtioNetDetachQueues(pDevice); + return DDI_SUCCESS; + } + else + LogRel((VIRTIOLOGNAME ":VirtioNetDevDetach: mac_unregister failed. rc=%d\n", rc)); + + return DDI_FAILURE; +} + + +/** + * Attach the Virtio Net TX, RX and control queues. + * + * @param pDevice Pointer to the Virtio device instance. + * + * @return corresponding solaris error code. + */ +static int VirtioNetAttachQueues(PVIRTIODEVICE pDevice) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioNetAttachQueues pDevice=%p\n", pDevice)); + + virtio_net_t *pNet = pDevice->pvDevice; + + pNet->pRxQueue = VirtioGetQueue(pDevice, 0 /* index */ ); + if (pNet->pRxQueue) + { + pNet->pTxQueue = VirtioGetQueue(pDevice, 1 /* index */); + if (pNet->pTxQueue) + { + if (pDevice->fHostFeatures & VIRTIO_NET_CTRL_VQ) + { + pNet->pCtrlQueue = VirtioGetQueue(pDevice, 2 /* index */); + if (pNet->pCtrlQueue) + { + LogFlow((VIRTIOLOGNAME ":VirtioNetAttachQueues successfully obtained 3 queues.\n")); + return DDI_SUCCESS; + } + else + LogRel((VIRTIOLOGNAME ":VirtioNetAttachQueues: failed to get Control queue.\n")); + } + else + { + LogFlow((VIRTIOLOGNAME ":VirtioNetAttachQueues successfully obtained 2 queues.\n")); + return DDI_SUCCESS; + } + + VirtioPutQueue(pDevice, pNet->pTxQueue); + pNet->pTxQueue = NULL; + } + else + LogRel((VIRTIOLOGNAME ":VirtioNetAttachQueues: failed to get TX queue.\n")); + + VirtioPutQueue(pDevice, pNet->pRxQueue); + pNet->pRxQueue = NULL; + } + else + LogRel((VIRTIOLOGNAME ":VirtioNetAttachQueues: failed to get RX queue.\n")); + + return DDI_FAILURE; +} + + +/** + * Detach the Virtio Net TX, RX and control queues. + * + * @param pDevice Pointer to the Virtio device instance. + */ +static void VirtioNetDetachQueues(PVIRTIODEVICE pDevice) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioNetDetachQueues pDevice=%p\n", pDevice)); + virtio_net_t *pNet = pDevice->pvDevice; + + if ( pDevice->fHostFeatures & VIRTIO_NET_CTRL_VQ + && pNet->pCtrlQueue) + VirtioPutQueue(pDevice, pNet->pCtrlQueue); + + if (pNet->pTxCache) + VirtioPutQueue(pDevice, pNet->pTxQueue); + + if (pNet->pRxQueue) + VirtioPutQueue(pDevice, pNet->pRxQueue); +} + + + +/* -=-=-=-=- Virtio Net MAC layer callbacks -=-=-=-=- */ + +/** + * Virtio Net statistics. + * + * @param pvArg Pointer to private data. + * @param cmdStat Which statistics to provide. + * @param pu64Val Where to write statistics data. + * + * @return corresponding solaris error code. + */ +static int VirtioNetStat(void *pvArg, uint_t cmdStat, uint64_t *pu64Val) +{ + NOREF(pvArg); + NOREF(cmdStat); + NOREF(pu64Val); + return ENOTSUP; +} + + +/** + * Virtio Net Start. + * + * @param pvArg Pointer to private data. + * + * @return corresponding solaris error code. + */ +static int VirtioNetStart(void *pvArg) +{ + PVIRTIODEVICE pDevice = pvArg; + virtio_net_t *pNet = pDevice->pvDevice; + mac_link_update(pNet->hMac, LINK_STATE_UP); + + pDevice->pHyperOps->pfnSetStatus(pDevice, VIRTIO_PCI_STATUS_DRV_OK); + return 0; +} + + +/** + * Virtio Net Stop. + * + * @param pvArg Pointer to private data. + */ +static void VirtioNetStop(void *pvArg) +{ + PVIRTIODEVICE pDevice = pvArg; + virtio_net_t *pNet = pDevice->pvDevice; + mac_link_update(pNet->hMac, LINK_STATE_DOWN); + + /* + * I don't think we should set status here as the host checks the status on every Xmit. This means pending Xmits + * would also be dropped. + * @todo: Not sure what's the best way to signal connect/disconnect of the link to the host. Figure it out. + */ +} + + +/** + * Virtio Net toggle Promiscuous mode. + * + * @param pvArg Pointer to private data. + * @param fPromiscOn Promiscuous On/Off. + * + * @return corresponding solaris error code. + */ +static int VirtioNetSetPromisc(void *pvArg, boolean_t fPromiscOn) +{ + NOREF(pvArg); + NOREF(fPromiscOn); + return 0; +} + + +/** + * Virtio Net set/add multicast address. + * + * @param pvArg Pointer to private data. + * @param fAdd Whether to add multicast address or not. + * @param pbMac Pointer to the multicast MAC address to set/add. + * + * @return corresponding solaris error code. + */ +static int VirtioNetSetMulticast(void *pvArg, boolean_t fAdd, const uint8_t *pbMac) +{ + NOREF(pvArg); + NOREF(fAdd); + NOREF(pbMac); + return 1; +} + + +/** + * Virtio Net set unicast address. + * + * @param pvArg Pointer to private data. + * @param pbMac Pointer to the unicast MAC address to set. + * + * @return corresponding solaris error code. + */ +static int VirtioNetSetUnicast(void *pvArg, const uint8_t *pbMac) +{ + NOREF(pvArg); + NOREF(pbMac); + return ENOTSUP; +} + + +/** + * Virtio Net get capabilities hook. + * + * @param pvArg Pointer to private data. + * @param Capab MAC layer capabilities. + * @param pvCapabData Pointer to store capability info. + * + * @return B_TRUE upon success, otherwise B_FALSE. + */ +static boolean_t VirtioNetGetCapab(void *pvArg, mac_capab_t Capab, void *pvCapabData) +{ + NOREF(pvArg); + NOREF(Capab); + NOREF(pvCapabData); + return B_FALSE; +} + + +/** + * Virtio Net Xmit hook. + * + * @param pvArg Pointer to private data. + * @param pMsg Pointer to the message. + * + * @return Pointer to message not Xmited. + */ +static mblk_t *VirtioNetXmit(void *pvArg, mblk_t *pMsg) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioNetXmit pMsg=%p\n", pMsg)); + cmn_err(CE_NOTE, "Xmit pMsg=%p\n", pMsg); + + PVIRTIODEVICE pDevice = pvArg; + virtio_net_t *pNet = pDevice->pvDevice; + bool fNotify = false; + + while (pMsg) + { + mblk_t *pNextMsg = pMsg->b_next; + +#if 0 + mblk_t *pHdr = allocb(sizeof(virtio_net_header_t), BPRI_HI); + if (RT_UNLIKELY(!pHdr)) + break; + + virtio_net_header_t *pNetHdr = pHdr->b_rptr; + memset(pNetHdr, 0, sizeof(virtio_net_header_t)); + pNetHdr->u8Flags = VIRTIO_NET_GUEST_CSUM; + pNetHdr->u16HdrLen = sizeof(virtio_net_header_t); + + pHdr->b_wptr += sizeof(virtio_net_header_t); + pHdr->b_cont = pMsg; +#endif + + virtio_net_txbuf_t *pTxBuf = kmem_cache_alloc(pNet->pTxCache, KM_SLEEP); + if (!pTxBuf) + break; + + ddi_dma_cookie_t DmaCookie; + uint_t cCookies; + int rc = ddi_dma_addr_bind_handle(pTxBuf->hDMA, NULL /* addrspace */, (char *)pMsg->b_rptr, MBLKL(pMsg), + DDI_DMA_WRITE | DDI_DMA_STREAMING, DDI_DMA_SLEEP, 0 /* addr */, + &DmaCookie, &cCookies); + cmn_err(CE_NOTE, "VirtioNetXmit: MBLKL pMsg=%u\n", MBLKL(pMsg)); + if (rc != DDI_DMA_MAPPED) + { + LogRel((VIRTIOLOGNAME ":VirtioNetXmit failed to map address to DMA handle. rc=%d\n", rc)); + kmem_cache_free(pNet->pTxCache, pTxBuf); + break; + } + + /** @todo get 'cCookies' slots from the ring. */ + + for (uint_t i = 0; i < cCookies; i++) + { + uint16_t fFlags = 0; + if (i < cCookies - 1) + fFlags |= VIRTIO_FLAGS_RING_DESC_NEXT; + + rc = VirtioRingPush(pNet->pTxQueue, DmaCookie.dmac_laddress, DmaCookie.dmac_size, fFlags); + if (RT_FAILURE(rc)) + { + LogRel((VIRTIOLOGNAME ":VirtioNetXmit failed. rc=%Rrc\n", rc)); + break; + } + + ddi_dma_nextcookie(pTxBuf->hDMA, &DmaCookie); + } + + pMsg = pNextMsg; + fNotify = true; + if (RT_FAILURE(rc)) + { + ddi_dma_unbind_handle(pTxBuf->hDMA); + break; + } + } + + if (fNotify) + pDevice->pHyperOps->pfnNotifyQueue(pDevice, pNet->pTxQueue); + + return pMsg; +} + + +/** + * Interrupt Service Routine for Virtio Net. + * + * @param Arg Private data (unused, will be NULL). + * @returns DDI_INTR_CLAIMED if it's our interrupt, DDI_INTR_UNCLAIMED if it isn't. + */ +static uint_t VirtioNetISR(caddr_t Arg) +{ + cmn_err(CE_NOTE, "VirtioNetISR Arg=%p\n", Arg); + NOREF(Arg); + return DDI_INTR_UNCLAIMED; +} + diff --git a/src/VBox/Additions/solaris/Virtio/VirtioPci-solaris.c b/src/VBox/Additions/solaris/Virtio/VirtioPci-solaris.c new file mode 100644 index 00000000..36f69e1b --- /dev/null +++ b/src/VBox/Additions/solaris/Virtio/VirtioPci-solaris.c @@ -0,0 +1,665 @@ +/* $Id: VirtioPci-solaris.c $ */ +/** @file + * VirtualBox Guest Additions - Virtio Driver for Solaris, PCI Hypervisor Interface. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VirtioPci-solaris.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/param.h> +#include <VBox/log.h> + +#include <sys/pci.h> +#include <sys/param.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* + * Pci Register offsets. + */ +#define VIRTIO_PCI_HOST_FEATURES 0x00 +#define VIRTIO_PCI_GUEST_FEATURES 0x04 +#define VIRTIO_PCI_QUEUE_PFN 0x08 +#define VIRTIO_PCI_QUEUE_NUM 0x0C +#define VIRTIO_PCI_QUEUE_SEL 0x0E +#define VIRTIO_PCI_QUEUE_NOTIFY 0x10 +#define VIRTIO_PCI_STATUS 0x12 +#define VIRTIO_PCI_ISR 0x13 +#define VIRTIO_PCI_CONFIG 0x14 + +#define VIRTIO_PCI_RING_ALIGN PAGE_SIZE +#define VIRTIO_PCI_QUEUE_ADDR_SHIFT PAGE_SHIFT + +/** + * virtio_pci_t: Private data per device instance. + */ +typedef struct virtio_pci_t +{ + ddi_acc_handle_t hIO; /* IO handle */ + caddr_t addrIOBase; /* IO base address */ +} virtio_pci_t; + +/** + * virtio_pci_queue_t: Private data per queue instance. + */ +typedef struct virtio_pci_queue_t +{ + ddi_dma_handle_t hDMA; /* DMA handle. */ + ddi_acc_handle_t hIO; /* IO handle. */ + size_t cbBuf; /* Physical address of buffer. */ + paddr_t physBuf; /* Size of buffer. */ + pfn_t pageBuf; /* Page frame number of buffer. */ +} virtio_pci_queue_t; + +static ddi_device_acc_attr_t g_VirtioPciAccAttrRegs = +{ + DDI_DEVICE_ATTR_V0, /* Version */ + DDI_STRUCTURE_LE_ACC, /* Structural data access in little endian. */ + DDI_STRICTORDER_ACC, /* Strict ordering. */ + DDI_DEFAULT_ACC /* Default access, possible panic on errors*/ +}; + +static ddi_device_acc_attr_t g_VirtioPciAccAttrRing = +{ + DDI_DEVICE_ATTR_V0, /* Version. */ + DDI_NEVERSWAP_ACC, /* Data access with no byte swapping*/ + DDI_STRICTORDER_ACC, /* Strict ordering. */ + DDI_DEFAULT_ACC /* Default access, possible panic on errors*/ +}; + +static ddi_dma_attr_t g_VirtioPciDmaAttrRing = +{ + DMA_ATTR_V0, /* Version. */ + 0, /* Lowest usable address. */ + 0xffffffffffffffffULL, /* Highest usable address. */ + 0x7fffffff, /* Maximum DMAable byte count. */ + VIRTIO_PCI_RING_ALIGN, /* Alignment in bytes. */ + 0x7ff, /* Bitmap of burst sizes */ + 1, /* Minimum transfer. */ + 0xffffffffU, /* Maximum transfer. */ + 0xffffffffffffffffULL, /* Maximum segment length. */ + 1, /* Maximum number of segments. */ + 1, /* Granularity. */ + 0 /* Flags (reserved). */ +}; + +/** Pointer to the interrupt handle vector */ +static ddi_intr_handle_t *g_pIntr; +/** Number of actually allocated interrupt handles */ +static size_t g_cIntrAllocated; +/** The IRQ Mutex */ +static kmutex_t g_IrqMtx; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void *VirtioPciAlloc(PVIRTIODEVICE pDevice); +static void VirtioPciFree(PVIRTIODEVICE pDevice); +static int VirtioPciAttach(PVIRTIODEVICE pDevice); +static int VirtioPciDetach(PVIRTIODEVICE pDevice); +static uint32_t VirtioPciGetFeatures(PVIRTIODEVICE pDevice); +static void VirtioPciSetFeatures(PVIRTIODEVICE pDevice, uint32_t fFeatures); +static int VirtioPciNotifyQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue); +static void VirtioPciGet(PVIRTIODEVICE pDevice, off_t off, void *pv, size_t cb); +static void VirtioPciSet(PVIRTIODEVICE pDevice, off_t off, void *pv, size_t cb); +static void *VirtioPciGetQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue); +static void VirtioPciPutQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue); +static void VirtioPciSetStatus(PVIRTIODEVICE pDevice, uint8_t Status); + +static uint_t VirtioPciISR(caddr_t Arg); +static int VirtioPciSetupIRQ(dev_info_t *pDip); +static void VirtioPciRemoveIRQ(dev_info_t *pDip); + +/** + * Hypervisor operations for Virtio Pci. + */ +VIRTIOHYPEROPS g_VirtioHyperOpsPci = +{ + VirtioPciAlloc, + VirtioPciFree, + VirtioPciAttach, + VirtioPciDetach, + VirtioPciGetFeatures, + VirtioPciSetFeatures, + VirtioPciNotifyQueue, + VirtioPciGet, + VirtioPciSet, + VirtioPciGetQueue, + VirtioPciPutQueue, + VirtioPciSetStatus +}; + + +/** + * Virtio Pci private data allocation routine. + * + * @param pDevice Pointer to the Virtio device instance. + * @return Allocated private data structure which must only be freed by calling + * VirtioPciFree(). + */ +static void *VirtioPciAlloc(PVIRTIODEVICE pDevice) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioPciAlloc pDevice=%p\n", pDevice)); + virtio_pci_t *pPciData = RTMemAllocZ(sizeof(virtio_pci_t)); + return pPciData; +} + + +/** + * Virtio Pci private data deallocation routine. + * + * @param pDevice Pointer to the Virtio device instance. + */ +static void VirtioPciFree(PVIRTIODEVICE pDevice) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioPciFree pDevice=%p\n", pDevice)); + virtio_pci_t *pPciData = pDevice->pvHyper; + if (pPciData) + { + RTMemFree(pDevice->pvHyper); + pDevice->pvHyper = NULL; + } +} + + +/** + * Virtio Pci attach routine, called from driver attach. + * + * @param pDevice Pointer to the Virtio device instance. + * + * @return Solaris DDI error code. DDI_SUCCESS or DDI_FAILURE. + */ +static int VirtioPciAttach(PVIRTIODEVICE pDevice) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioPciAttach pDevice=%p\n", pDevice)); + virtio_pci_t *pPciData = pDevice->pvHyper; + AssertReturn(pPciData, DDI_FAILURE); + + int rc = ddi_regs_map_setup(pDevice->pDip, + 1, /* reg. num */ + &pPciData->addrIOBase, + 0, /* offset */ + 0, /* length */ + &g_VirtioPciAccAttrRegs, + &pPciData->hIO); + if (rc == DDI_SUCCESS) + { + /* + * Reset the device. + */ + VirtioPciSetStatus(pDevice, 0); + + /* + * Add interrupt handler. + */ + VirtioPciSetupIRQ(pDevice->pDip); + + LogFlow((VIRTIOLOGNAME ":VirtioPciAttach: successfully mapped registers.\n")); + return DDI_SUCCESS; + } + else + LogRel((VIRTIOLOGNAME ":VirtioPciAttach: ddi_regs_map_setup failed. rc=%d\n", rc)); + return DDI_FAILURE; +} + + +/** + * Virtio Pci detach routine, called from driver detach. + * + * @param pDevice Pointer to the Virtio device instance. + * + * @return Solaris DDI error code. DDI_SUCCESS or DDI_FAILURE. + */ +static int VirtioPciDetach(PVIRTIODEVICE pDevice) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioPciDetach pDevice=%p\n", pDevice)); + virtio_pci_t *pPciData = pDevice->pvHyper; + AssertReturn(pPciData, DDI_FAILURE); + + VirtioPciRemoveIRQ(pDevice->pDip); + ddi_regs_map_free(&pPciData->hIO); + return DDI_SUCCESS; +} + + +/** + * Get host supported features. + * + * @param pDevice Pointer to the Virtio device instance. + * + * @return Mask of host features. + */ +static uint32_t VirtioPciGetFeatures(PVIRTIODEVICE pDevice) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioPciGetFeatures pDevice=%p\n", pDevice)); + virtio_pci_t *pPciData = pDevice->pvHyper; + AssertReturn(pPciData, 0); + + return ddi_get32(pPciData->hIO, (uint32_t *)(pPciData->addrIOBase + VIRTIO_PCI_HOST_FEATURES)); +} + + +/** + * Set guest supported features. + * + * @param pDevice Pointer to the Virtio device instance. + * @param u32Features Mask of guest supported features. + */ +static void VirtioPciSetFeatures(PVIRTIODEVICE pDevice, uint32_t u32Features) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioPciSetFeatures pDevice=%p\n", pDevice)); + virtio_pci_t *pPciData = pDevice->pvHyper; + AssertReturnVoid(pPciData); + + ddi_put32(pPciData->hIO, (uint32_t *)(pPciData->addrIOBase + VIRTIO_PCI_GUEST_FEATURES), u32Features); +} + + +/** + * Update the queue, notify the host. + * + * @param pDevice Pointer to the Virtio device instance. + * @param pQueue Pointer to the Queue that is doing the notification. + * + * @return Solaris DDI error code. DDI_SUCCESS or DDI_FAILURE. + */ +static int VirtioPciNotifyQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioPciNotifyQueue pDevice=%p pQueue=%p\n", pDevice, pQueue)); + virtio_pci_t *pPciData = pDevice->pvHyper; + AssertReturn(pPciData, DDI_FAILURE); + + pQueue->Ring.pRingAvail->Index += pQueue->cBufs; + pQueue->cBufs = 0; + + ASMCompilerBarrier(); + + ddi_put16(pPciData->hIO, (uint16_t *)(pPciData->addrIOBase + VIRTIO_PCI_QUEUE_NOTIFY), pQueue->QueueIndex); + cmn_err(CE_NOTE, "VirtioPciNotifyQueue\n"); +} + + + +/** + * Virtio Pci set (write) routine. + * + * @param pDevice Pointer to the Virtio device instance. + * @param off Offset into the PCI config space. + * @param pv Pointer to the buffer to write from. + * @param cb Size of the buffer in bytes. + */ +static void VirtioPciSet(PVIRTIODEVICE pDevice, off_t off, void *pv, size_t cb) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioPciSet pDevice=%p\n", pDevice)); + virtio_pci_t *pPciData = pDevice->pvHyper; + AssertReturnVoid(pPciData); + + uint8_t *pb = pv; + for (size_t i = 0; i < cb; i++, pb++) + ddi_put8(pPciData->hIO, (uint8_t *)(pPciData->addrIOBase + VIRTIO_PCI_CONFIG + off + i), *pb); +} + + +/** + * Virtio Pci get (read) routine. + * + * @param pDevice Pointer to the Virtio device instance. + * @param off Offset into the PCI config space. + * @param pv Where to store the read data. + * @param cb Size of the buffer in bytes. + */ +static void VirtioPciGet(PVIRTIODEVICE pDevice, off_t off, void *pv, size_t cb) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioPciGet pDevice=%p off=%u pv=%p cb=%u\n", pDevice, off, pv, cb)); + virtio_pci_t *pPciData = pDevice->pvHyper; + AssertReturnVoid(pPciData); + + uint8_t *pb = pv; + for (size_t i = 0; i < cb; i++, pb++) + *pb = ddi_get8(pPciData->hIO, (uint8_t *)(pPciData->addrIOBase + VIRTIO_PCI_CONFIG + off + i)); +} + + +/** + * Virtio Pci put queue routine. Places the queue and frees associated queue. + * + * @param pDevice Pointer to the Virtio device instance. + * @param pQueue Pointer to the queue. + */ +static void VirtioPciPutQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioPciPutQueue pDevice=%p pQueue=%p\n", pDevice, pQueue)); + AssertReturnVoid(pDevice); + AssertReturnVoid(pQueue); + + virtio_pci_t *pPci = pDevice->pvHyper; + AssertReturnVoid(pPci); + virtio_pci_queue_t *pPciQueue = pQueue->pvData; + if (RT_UNLIKELY(!pPciQueue)) + { + LogRel((VIRTIOLOGNAME ":VirtioPciPutQueue missing Pci queue.\n")); + return; + } + + ddi_put16(pPci->hIO, (uint16_t *)(pPci->addrIOBase + VIRTIO_PCI_QUEUE_SEL), pQueue->QueueIndex); + ddi_put32(pPci->hIO, (uint32_t *)(pPci->addrIOBase + VIRTIO_PCI_QUEUE_PFN), 0); + + ddi_dma_unbind_handle(pPciQueue->hDMA); + ddi_dma_mem_free(&pPciQueue->hIO); + ddi_dma_free_handle(&pPciQueue->hDMA); + RTMemFree(pPciQueue); +} + + +/** + * Virtio Pci get queue routine. Allocates a PCI queue and DMA resources. + * + * @param pDevice Pointer to the Virtio device instance. + * @param pQueue Where to store the queue. + * + * @return An allocated Virtio Pci queue, or NULL in case of errors. + */ +static void *VirtioPciGetQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue) +{ + LogFlowFunc((VIRTIOLOGNAME ":VirtioPciGetQueue pDevice=%p pQueue=%p\n", pDevice, pQueue)); + AssertReturn(pDevice, NULL); + + virtio_pci_t *pPci = pDevice->pvHyper; + AssertReturn(pPci, NULL); + + /* + * Select a Queue. + */ + ddi_put16(pPci->hIO, (uint16_t *)(pPci->addrIOBase + VIRTIO_PCI_QUEUE_SEL), pQueue->QueueIndex); + + /* + * Get the currently selected Queue's size. + */ + pQueue->Ring.cDesc = ddi_get16(pPci->hIO, (uint16_t *)(pPci->addrIOBase + VIRTIO_PCI_QUEUE_NUM)); + if (RT_UNLIKELY(!pQueue->Ring.cDesc)) + { + LogRel((VIRTIOLOGNAME ": VirtioPciGetQueue: Queue[%d] has no descriptors.\n", pQueue->QueueIndex)); + return NULL; + } + + /* + * Check if it's already active. + */ + uint32_t QueuePFN = ddi_get32(pPci->hIO, (uint32_t *)(pPci->addrIOBase + VIRTIO_PCI_QUEUE_PFN)); + if (QueuePFN != 0) + { + LogRel((VIRTIOLOGNAME ":VirtioPciGetQueue: Queue[%d] is already used.\n", pQueue->QueueIndex)); + return NULL; + } + + LogFlow(("Queue[%d] has %d slots.\n", pQueue->QueueIndex, pQueue->Ring.cDesc)); + + /* + * Allocate and initialize Pci queue data. + */ + virtio_pci_queue_t *pPciQueue = RTMemAllocZ(sizeof(virtio_pci_queue_t)); + if (pPciQueue) + { + /* + * Setup DMA. + */ + size_t cbQueue = VirtioRingSize(pQueue->Ring.cDesc, VIRTIO_PCI_RING_ALIGN); + int rc = ddi_dma_alloc_handle(pDevice->pDip, &g_VirtioPciDmaAttrRing, DDI_DMA_SLEEP, 0 /* addr */, &pPciQueue->hDMA); + if (rc == DDI_SUCCESS) + { + rc = ddi_dma_mem_alloc(pPciQueue->hDMA, cbQueue, &g_VirtioPciAccAttrRing, DDI_DMA_CONSISTENT, + DDI_DMA_SLEEP, 0 /* addr */, &pQueue->pQueue, &pPciQueue->cbBuf, + &pPciQueue->hIO); + if (rc == DDI_SUCCESS) + { + AssertRelease(pPciQueue->cbBuf >= cbQueue); + ddi_dma_cookie_t DmaCookie; + uint_t cCookies; + rc = ddi_dma_addr_bind_handle(pPciQueue->hDMA, NULL /* addrspace */, pQueue->pQueue, pPciQueue->cbBuf, + DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, + 0 /* addr */, &DmaCookie, &cCookies); + if (rc == DDI_SUCCESS) + { + pPciQueue->physBuf = DmaCookie.dmac_laddress; + pPciQueue->pageBuf = pPciQueue->physBuf >> VIRTIO_PCI_QUEUE_ADDR_SHIFT; + + LogFlow((VIRTIOLOGNAME ":VirtioPciGetQueue: Queue[%d]%p physBuf=%x pfn of Buf %#x\n", pQueue->QueueIndex, + pQueue->pQueue, pPciQueue->physBuf, pPciQueue->pageBuf)); + cmn_err(CE_NOTE, ":VirtioPciGetQueue: Queue[%d]%p physBuf=%x pfn of Buf %x\n", pQueue->QueueIndex, + pQueue->pQueue, pPciQueue->physBuf, pPciQueue->pageBuf); + + /* + * Activate the queue and initialize a ring for the queue. + */ + memset(pQueue->pQueue, 0, pPciQueue->cbBuf); + ddi_put32(pPci->hIO, (uint32_t *)(pPci->addrIOBase + VIRTIO_PCI_QUEUE_PFN), pPciQueue->pageBuf); + VirtioRingInit(pQueue, pQueue->Ring.cDesc, pQueue->pQueue, VIRTIO_PCI_RING_ALIGN); + return pPciQueue; + } + else + LogRel((VIRTIOLOGNAME ":VirtioPciGetQueue: ddi_dma_addr_bind_handle failed. rc=%d\n", rc)); + + ddi_dma_mem_free(&pPciQueue->hIO); + } + else + LogRel((VIRTIOLOGNAME ":VirtioPciGetQueue: ddi_dma_mem_alloc failed for %u bytes rc=%d\n", cbQueue, rc)); + + ddi_dma_free_handle(&pPciQueue->hDMA); + } + else + LogRel((VIRTIOLOGNAME ":VirtioPciGetQueue: ddi_dma_alloc_handle failed. rc=%d\n", rc)); + + RTMemFree(pPciQueue); + } + else + LogRel((VIRTIOLOGNAME ":VirtioPciGetQueue: failed to alloc %u bytes for Pci Queue data.\n", sizeof(virtio_pci_queue_t))); + + return NULL; +} + + +/** + * Set the Virtio PCI status bit. + * + * @param pDevice Pointer to the Virtio device instance. + * @param Status The status to set. + */ +static void VirtioPciSetStatus(PVIRTIODEVICE pDevice, uint8_t Status) +{ + virtio_pci_t *pPciData = pDevice->pvHyper; + ddi_put8(pPciData->hIO, (uint8_t *)(pPciData->addrIOBase + VIRTIO_PCI_STATUS), Status); +} + + +/** + * Sets up IRQ for Virtio PCI. + * + * @param pDip Pointer to the device info structure. + * + * @return Solaris error code. + */ +static int VirtioPciSetupIRQ(dev_info_t *pDip) +{ + LogFlow((VIRTIOLOGNAME ":VirtioPciSetupIRQ: pDip=%p\n", pDip)); + cmn_err(CE_NOTE, "VirtioPciSetupIRQ\n"); + + int IntrType = 0; + int rc = ddi_intr_get_supported_types(pDip, &IntrType); + if (rc == DDI_SUCCESS) + { + /* We won't need to bother about MSIs. */ + if (IntrType & DDI_INTR_TYPE_FIXED) + { + int IntrCount = 0; + rc = ddi_intr_get_nintrs(pDip, IntrType, &IntrCount); + if ( rc == DDI_SUCCESS + && IntrCount > 0) + { + int IntrAvail = 0; + rc = ddi_intr_get_navail(pDip, IntrType, &IntrAvail); + if ( rc == DDI_SUCCESS + && IntrAvail > 0) + { + /* Allocated kernel memory for the interrupt handles. The allocation size is stored internally. */ + g_pIntr = RTMemAllocZ(IntrCount * sizeof(ddi_intr_handle_t)); + if (g_pIntr) + { + int IntrAllocated; + rc = ddi_intr_alloc(pDip, g_pIntr, IntrType, 0, IntrCount, &IntrAllocated, DDI_INTR_ALLOC_NORMAL); + if ( rc == DDI_SUCCESS + && IntrAllocated > 0) + { + g_cIntrAllocated = IntrAllocated; + uint_t uIntrPriority; + rc = ddi_intr_get_pri(g_pIntr[0], &uIntrPriority); + if (rc == DDI_SUCCESS) + { + /* Initialize the mutex. */ + mutex_init(&g_IrqMtx, NULL, MUTEX_DRIVER, DDI_INTR_PRI(uIntrPriority)); + + /* Assign interrupt handler functions and enable interrupts. */ + for (int i = 0; i < IntrAllocated; i++) + { + rc = ddi_intr_add_handler(g_pIntr[i], (ddi_intr_handler_t *)VirtioPciISR, + NULL /* No Private Data */, NULL); + if (rc == DDI_SUCCESS) + rc = ddi_intr_enable(g_pIntr[i]); + if (rc != DDI_SUCCESS) + { + /* Changing local IntrAllocated to hold so-far allocated handles for freeing. */ + IntrAllocated = i; + break; + } + } + if (rc == DDI_SUCCESS) + { + cmn_err(CE_NOTE, "VirtioPciSetupIRQ success\n"); + return rc; + } + + /* Remove any assigned handlers */ + LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ failed to assign IRQs allocated=%d\n", IntrAllocated)); + for (int x = 0; x < IntrAllocated; x++) + ddi_intr_remove_handler(g_pIntr[x]); + } + else + LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ failed to get priority of interrupt. rc=%d\n", rc)); + + /* Remove allocated IRQs, too bad we can free only one handle at a time. */ + for (int k = 0; k < g_cIntrAllocated; k++) + ddi_intr_free(g_pIntr[k]); + } + else + LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ: failed to allocated IRQs. count=%d\n", IntrCount)); + RTMemFree(g_pIntr); + } + else + LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ: failed to allocated IRQs. count=%d\n", IntrCount)); + } + else + { + LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ: failed to get or insufficient available IRQs. rc=%d IntrAvail=%d\n", + rc, IntrAvail)); + } + } + else + { + LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ: failed to get or insufficient number of IRQs. rc=%d IntrCount=%d\n", rc, + IntrCount)); + } + } + else + LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ: invalid irq type. IntrType=%#x\n", IntrType)); + } + else + LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ: failed to get supported interrupt types\n")); + return rc; +} + + +/** + * Removes IRQ for Virtio PCI device. + * + * @param pDip Pointer to the device info structure. + */ +static void VirtioPciRemoveIRQ(dev_info_t *pDip) +{ + LogFlow((VIRTIOLOGNAME ":VirtioPciRemoveIRQ pDip=%p:\n", pDip)); + + for (int i = 0; i < g_cIntrAllocated; i++) + { + int rc = ddi_intr_disable(g_pIntr[i]); + if (rc == DDI_SUCCESS) + { + rc = ddi_intr_remove_handler(g_pIntr[i]); + if (rc == DDI_SUCCESS) + ddi_intr_free(g_pIntr[i]); + } + } + RTMemFree(g_pIntr); + mutex_destroy(&g_IrqMtx); +} + + +/** + * Interrupt Service Routine for Virtio PCI device. + * + * @param Arg Private data (unused, will be NULL). + * @returns DDI_INTR_CLAIMED if it's our interrupt, DDI_INTR_UNCLAIMED if it isn't. + */ +static uint_t VirtioPciISR(caddr_t Arg) +{ + LogFlow((VIRTIOLOGNAME ":VBoxGuestSolarisISR\n")); + cmn_err(CE_NOTE, "VBoxGuestSolarisISRd Arg=%p\n", Arg); + + mutex_enter(&g_IrqMtx); + bool fOurIRQ = false; + /* + * Call the DeviceOps ISR routine somehow which should notify all Virtio queues + * on the interrupt. + */ + mutex_exit(&g_IrqMtx); + + return fOurIRQ ? DDI_INTR_CLAIMED : DDI_INTR_UNCLAIMED; +} + diff --git a/src/VBox/Additions/solaris/Virtio/VirtioPci-solaris.h b/src/VBox/Additions/solaris/Virtio/VirtioPci-solaris.h new file mode 100644 index 00000000..5d0aaf0c --- /dev/null +++ b/src/VBox/Additions/solaris/Virtio/VirtioPci-solaris.h @@ -0,0 +1,48 @@ +/* $Id: VirtioPci-solaris.h $ */ +/** @file + * VirtualBox Guest Additions: Virtio Driver for Solaris, PCI Hypervisor Interface. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef GA_INCLUDED_SRC_solaris_Virtio_VirtioPci_solaris_h +#define GA_INCLUDED_SRC_solaris_Virtio_VirtioPci_solaris_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "Virtio-solaris.h" + +extern VIRTIOHYPEROPS g_VirtioHyperOpsPci; + +#endif /* !GA_INCLUDED_SRC_solaris_Virtio_VirtioPci_solaris_h */ + diff --git a/src/VBox/Additions/solaris/Virtio/VirtioRing-solaris.c b/src/VBox/Additions/solaris/Virtio/VirtioRing-solaris.c new file mode 100644 index 00000000..004075c1 --- /dev/null +++ b/src/VBox/Additions/solaris/Virtio/VirtioRing-solaris.c @@ -0,0 +1,148 @@ +/* $Id: VirtioRing-solaris.c $ */ +/** @file + * VirtualBox Guest Additions: Virtio Driver for Solaris, Ring implementation. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "Virtio-solaris.h" + +#include <iprt/asm.h> +#include <iprt/cdefs.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/log.h> + +#include <sys/cmn_err.h> + +/** + * Returns the size of the ring in bytes given the number of elements and + * alignment requirements. + * + * @param cElements Number of elements. + * @param Align Alignment (must be a power of two). + * + * @return Size of the Virtio ring. + */ +size_t VirtioRingSize(uint64_t cElements, ulong_t Align) +{ + size_t cb = 0; + cb = cElements * sizeof(VIRTIORINGDESC); /* Ring descriptors. */ + cb += 2 * sizeof(uint16_t); /* Available flags and index. */ + cb += cElements * sizeof(uint16_t); /* Available descriptors. */ + + size_t cbAlign = RT_ALIGN_Z(cb, Align); + cbAlign += 2 * sizeof(uint16_t); /* Used flags and index. */ + cbAlign += cElements * sizeof(VIRTIORINGUSEDELEM); /* Used descriptors. */ + + return cbAlign; +} + + +/** + * Initializes a ring of a queue. This associates the DMA virtual address + * with the Ring structure's "pRingDesc". + * + * @param pQueue Pointer to the Virtio Queue. + * @param cDescs Number of descriptors. + * @param virtBuf Buffer associated with the ring. + * @param Align Alignment (must be power of two). + */ +void VirtioRingInit(PVIRTIOQUEUE pQueue, uint_t cDescs, caddr_t virtBuf, ulong_t Align) +{ + PVIRTIORING pRing = &pQueue->Ring; + pRing->cDesc = cDescs; + pRing->pRingDesc = (void *)virtBuf; + pRing->pRingAvail = (PVIRTIORINGAVAIL)(virtBuf + (cDescs * sizeof(pRing->pRingDesc[0]))); + pRing->pRingUsedElem = RT_ALIGN_PT(pRing->pRingAvail + RT_UOFFSETOF_DYN(VIRTIORINGAVAIL, aRings[pQueue->Ring.cDesc]), Align, + PVIRTIORINGUSEDELEM); + + for (uint_t i = 0; i < pRing->cDesc - 1; i++) + pRing->pRingDesc[i].Next = i + 1; + + pQueue->FreeHeadIndex = 0; + + cmn_err(CE_NOTE, "cDesc=%u pRingDesc=%p pRingAvail=%p\n", pRing->cDesc, pRing->pRingDesc, pRing->pRingAvail); +} + + +/** + * Push a buffer into the ring. + * + * @param pQueue Pointer to the Virtio queue. + * @param physBuf Physical address of the buffer. + * @param cbBuf Size of the buffer in bytes. + * @param fFlags Buffer flags, see VIRTIO_FLAGS_RING_DESC_*. + * + * @return IPRT error code. + */ +int VirtioRingPush(PVIRTIOQUEUE pQueue, paddr_t physBuf, uint32_t cbBuf, uint16_t fFlags) +{ + /* + * Claim a slot, fill the buffer and move head pointer. + */ + uint_t FreeIndex = pQueue->FreeHeadIndex; + PVIRTIORING pRing = &pQueue->Ring; + + if (FreeIndex >= pRing->cDesc - 1) + { + LogRel((VIRTIOLOGNAME ":VirtioRingPush: failed. No free descriptors. cDesc=%u\n", pRing->cDesc)); + return VERR_BUFFER_OVERFLOW; + } + + PVIRTIORINGDESC pRingDesc = &pRing->pRingDesc[FreeIndex]; + + AssertCompile(sizeof(physBuf) == sizeof(pRingDesc->AddrBuf)); + + pQueue->cBufs++; + uint_t AvailIndex = (pRing->pRingAvail->Index + pQueue->cBufs) % pQueue->Ring.cDesc; + pRing->pRingAvail->aRings[AvailIndex - 1] = FreeIndex; + + pRingDesc->AddrBuf = physBuf; + pRingDesc->cbBuf = cbBuf; + pRingDesc->fFlags = fFlags; + + pQueue->FreeHeadIndex = pRingDesc->Next; + + ASMCompilerBarrier(); + + cmn_err(CE_NOTE, "VirtioRingPush: cbBuf=%u FreeIndex=%u AvailIndex=%u cDesc=%u Queue->cBufs=%u\n", + cbBuf, FreeIndex, AvailIndex, pQueue->Ring.cDesc, + pQueue->cBufs); + + return VINF_SUCCESS; +} + |