diff options
Diffstat (limited to 'src/VBox/Devices/Network/DevVirtioNet.cpp')
-rw-r--r-- | src/VBox/Devices/Network/DevVirtioNet.cpp | 2462 |
1 files changed, 2462 insertions, 0 deletions
diff --git a/src/VBox/Devices/Network/DevVirtioNet.cpp b/src/VBox/Devices/Network/DevVirtioNet.cpp new file mode 100644 index 00000000..cd942f66 --- /dev/null +++ b/src/VBox/Devices/Network/DevVirtioNet.cpp @@ -0,0 +1,2462 @@ +/* $Id: DevVirtioNet.cpp $ */ +/** @file + * DevVirtioNet - Virtio Network Device + */ + +/* + * Copyright (C) 2009-2022 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>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_VIRTIO_NET +#define VNET_WITH_GSO +#define VNET_WITH_MERGEABLE_RX_BUFS + +#include <VBox/vmm/pdmdev.h> +#include <VBox/vmm/pdmnetifs.h> +#include <iprt/asm.h> +#include <iprt/net.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#ifdef IN_RING3 +# include <iprt/uuid.h> +#endif +#include <VBox/VBoxPktDmp.h> +#include "VBoxDD.h" +#include "../VirtIO/Virtio.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#ifndef VBOX_DEVICE_STRUCT_TESTCASE + +#define INSTANCE(pThis) pThis->VPCI.szInstance + +#ifdef IN_RING3 + +# define VNET_PCI_CLASS 0x0200 +# define VNET_N_QUEUES 3 + +# if 0 +/* Virtio Block Device */ +# define VNET_PCI_CLASS 0x0180 +# define VNET_N_QUEUES 2 +# endif + +#endif /* IN_RING3 */ + +#endif /* VBOX_DEVICE_STRUCT_TESTCASE */ + +/* + * Commenting out VNET_TX_DELAY enables async transmission in a dedicated thread. + * When VNET_TX_DELAY is defined, a timer handler does the job. + */ +//#define VNET_TX_DELAY 150 /**< 150 microseconds */ +#define VNET_MAX_FRAME_SIZE 65535 + 18 /**< Max IP packet size + Ethernet header with VLAN tag */ +#define VNET_MAC_FILTER_LEN 32 +#define VNET_MAX_VID (1 << 12) + +/** @name Virtio net features + * @{ */ +#define VNET_F_CSUM 0x00000001 /**< Host handles pkts w/ partial csum */ +#define VNET_F_GUEST_CSUM 0x00000002 /**< Guest handles pkts w/ partial csum */ +#define VNET_F_MAC 0x00000020 /**< Host has given MAC address. */ +#define VNET_F_GSO 0x00000040 /**< Host handles pkts w/ any GSO type */ +#define VNET_F_GUEST_TSO4 0x00000080 /**< Guest can handle TSOv4 in. */ +#define VNET_F_GUEST_TSO6 0x00000100 /**< Guest can handle TSOv6 in. */ +#define VNET_F_GUEST_ECN 0x00000200 /**< Guest can handle TSO[6] w/ ECN in. */ +#define VNET_F_GUEST_UFO 0x00000400 /**< Guest can handle UFO in. */ +#define VNET_F_HOST_TSO4 0x00000800 /**< Host can handle TSOv4 in. */ +#define VNET_F_HOST_TSO6 0x00001000 /**< Host can handle TSOv6 in. */ +#define VNET_F_HOST_ECN 0x00002000 /**< Host can handle TSO[6] w/ ECN in. */ +#define VNET_F_HOST_UFO 0x00004000 /**< Host can handle UFO in. */ +#define VNET_F_MRG_RXBUF 0x00008000 /**< Host can merge receive buffers. */ +#define VNET_F_STATUS 0x00010000 /**< virtio_net_config.status available */ +#define VNET_F_CTRL_VQ 0x00020000 /**< Control channel available */ +#define VNET_F_CTRL_RX 0x00040000 /**< Control channel RX mode support */ +#define VNET_F_CTRL_VLAN 0x00080000 /**< Control channel VLAN filtering */ +/** @} */ + +#define VNET_S_LINK_UP 1 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +#ifdef _MSC_VER +struct VNetPCIConfig +#else /* !_MSC_VER */ +struct __attribute__ ((__packed__)) VNetPCIConfig /** @todo r=bird: Use #pragma pack if necessary, that's portable! */ +#endif /* !_MSC_VER */ +{ + RTMAC mac; + uint16_t uStatus; +}; +AssertCompileMemberOffset(struct VNetPCIConfig, uStatus, 6); + +/** + * The virtio-net shared instance data. + * + * @extends VPCISTATE + */ +typedef struct VNETSTATE +{ + VPCISTATE VPCI; + +// PDMCRITSECT csRx; /**< Protects RX queue. */ + +#ifdef VNET_TX_DELAY + /** Transmit Delay Timer. */ + TMTIMERHANDLE hTxTimer; + uint32_t u32i; + uint32_t u32AvgDiff; + uint32_t u32MinDiff; + uint32_t u32MaxDiff; + uint64_t u64NanoTS; +#else /* !VNET_TX_DELAY */ + /** The event semaphore TX thread waits on. */ + SUPSEMEVENT hTxEvent; +#endif /* !VNET_TX_DELAY */ + + /** Indicates transmission in progress -- only one thread is allowed. */ + uint32_t uIsTransmitting; + + /** PCI config area holding MAC address as well as TBD. */ + struct VNetPCIConfig config; + /** MAC address obtained from the configuration. */ + RTMAC macConfigured; + /** True if physical cable is attached in configuration. */ + bool fCableConnected; + /** Link up delay (in milliseconds). */ + uint32_t cMsLinkUpDelay; + + uint32_t alignment; + + /** Number of packet being sent/received to show in debug log. */ + uint32_t u32PktNo; + + /** N/A: */ + bool volatile fMaybeOutOfSpace; + + /** Promiscuous mode -- RX filter accepts all packets. */ + bool fPromiscuous; + /** AllMulti mode -- RX filter accepts all multicast packets. */ + bool fAllMulti; + /** The number of actually used slots in aMacTable. */ + uint32_t cMacFilterEntries; + /** Array of MAC addresses accepted by RX filter. */ + RTMAC aMacFilter[VNET_MAC_FILTER_LEN]; + /** Bit array of VLAN filter, one bit per VLAN ID. */ + uint8_t aVlanFilter[VNET_MAX_VID / sizeof(uint8_t)]; + + /* Receive-blocking-related fields ***************************************/ + + /** EMT: Gets signalled when more RX descriptors become available. */ + SUPSEMEVENT hEventMoreRxDescAvail; + + /** Handle of the I/O port range. */ + IOMIOPORTHANDLE hIoPorts; + + /** @name Statistic + * @{ */ + STAMCOUNTER StatReceiveBytes; + STAMCOUNTER StatTransmitBytes; + STAMCOUNTER StatReceiveGSO; + STAMCOUNTER StatTransmitPackets; + STAMCOUNTER StatTransmitGSO; + STAMCOUNTER StatTransmitCSum; +#ifdef VBOX_WITH_STATISTICS + STAMPROFILE StatReceive; + STAMPROFILE StatReceiveStore; + STAMPROFILEADV StatTransmit; + STAMPROFILE StatTransmitSend; + STAMPROFILE StatRxOverflow; + STAMCOUNTER StatRxOverflowWakeup; + STAMCOUNTER StatTransmitByNetwork; + STAMCOUNTER StatTransmitByThread; +#endif + /** @} */ +} VNETSTATE; +/** Pointer to the virtio-net shared instance data. */ +typedef VNETSTATE *PVNETSTATE; + + +/** + * The virtio-net ring-3 instance data. + * + * @extends VPCISTATER3 + * @implements PDMINETWORKDOWN + * @implements PDMINETWORKCONFIG + */ +typedef struct VNETSTATER3 +{ + VPCISTATER3 VPCI; + + PDMINETWORKDOWN INetworkDown; + PDMINETWORKCONFIG INetworkConfig; + R3PTRTYPE(PPDMIBASE) pDrvBase; /**< Attached network driver. */ + R3PTRTYPE(PPDMINETWORKUP) pDrv; /**< Connector of attached network driver. */ + /** The device instance. + * @note This is _only_ for use when dealing with interface callbacks. */ + PPDMDEVINSR3 pDevIns; + +#ifndef VNET_TX_DELAY + R3PTRTYPE(PPDMTHREAD) pTxThread; +#endif + + R3PTRTYPE(PVQUEUE) pRxQueue; + R3PTRTYPE(PVQUEUE) pTxQueue; + R3PTRTYPE(PVQUEUE) pCtlQueue; + + /** Link Up(/Restore) Timer. */ + TMTIMERHANDLE hLinkUpTimer; +} VNETSTATER3; +/** Pointer to the virtio-net ring-3 instance data. */ +typedef VNETSTATER3 *PVNETSTATER3; + + +/** + * The virtio-net ring-0 instance data. + * + * @extends VPCISTATER0 + */ +typedef struct VNETSTATER0 +{ + VPCISTATER0 VPCI; +} VNETSTATER0; +/** Pointer to the virtio-net ring-0 instance data. */ +typedef VNETSTATER0 *PVNETSTATER0; + + +/** + * The virtio-net raw-mode instance data. + * + * @extends VPCISTATERC + */ +typedef struct VNETSTATERC +{ + VPCISTATERC VPCI; +} VNETSTATERC; +/** Pointer to the virtio-net ring-0 instance data. */ +typedef VNETSTATERC *PVNETSTATERC; + + +/** The virtio-net currenct context instance data. */ +typedef CTX_SUFF(VNETSTATE) VNETSTATECC; +/** Pointer to the virtio-net currenct context instance data. */ +typedef CTX_SUFF(PVNETSTATE) PVNETSTATECC; + + + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE + +#define VNETHDR_F_NEEDS_CSUM 1 // Use u16CSumStart, u16CSumOffset + +#define VNETHDR_GSO_NONE 0 // Not a GSO frame +#define VNETHDR_GSO_TCPV4 1 // GSO frame, IPv4 TCP (TSO) +#define VNETHDR_GSO_UDP 3 // GSO frame, IPv4 UDP (UFO) +#define VNETHDR_GSO_TCPV6 4 // GSO frame, IPv6 TCP +#define VNETHDR_GSO_ECN 0x80 // TCP has ECN set + +struct VNetHdr +{ + uint8_t u8Flags; + uint8_t u8GSOType; + uint16_t u16HdrLen; + uint16_t u16GSOSize; + uint16_t u16CSumStart; + uint16_t u16CSumOffset; +}; +typedef struct VNetHdr VNETHDR; +typedef VNETHDR *PVNETHDR; +AssertCompileSize(VNETHDR, 10); + +struct VNetHdrMrx +{ + VNETHDR Hdr; + uint16_t u16NumBufs; +}; +typedef struct VNetHdrMrx VNETHDRMRX; +typedef VNETHDRMRX *PVNETHDRMRX; +AssertCompileSize(VNETHDRMRX, 12); + +AssertCompileMemberOffset(VNETSTATE, VPCI, 0); + +#define VNET_OK 0 +#define VNET_ERROR 1 +typedef uint8_t VNETCTLACK; + +#define VNET_CTRL_CLS_RX_MODE 0 +#define VNET_CTRL_CMD_RX_MODE_PROMISC 0 +#define VNET_CTRL_CMD_RX_MODE_ALLMULTI 1 + +#define VNET_CTRL_CLS_MAC 1 +#define VNET_CTRL_CMD_MAC_TABLE_SET 0 + +#define VNET_CTRL_CLS_VLAN 2 +#define VNET_CTRL_CMD_VLAN_ADD 0 +#define VNET_CTRL_CMD_VLAN_DEL 1 + + +typedef struct VNetCtlHdr +{ + uint8_t u8Class; + uint8_t u8Command; +} VNETCTLHDR; +AssertCompileSize(VNETCTLHDR, 2); +typedef VNETCTLHDR *PVNETCTLHDR; + + + +#ifdef IN_RING3 + +/** Returns true if large packets are written into several RX buffers. */ +DECLINLINE(bool) vnetR3MergeableRxBuffers(PVNETSTATE pThis) +{ + return !!(pThis->VPCI.uGuestFeatures & VNET_F_MRG_RXBUF); +} + +#define VNET_R3_CS_ENTER_RETURN_VOID(a_pDevIns, a_pThis) VPCI_R3_CS_ENTER_RETURN_VOID(a_pDevIns, &(a_pThis)->VPCI) + +DECLINLINE(int) vnetR3CsEnter(PPDMDEVINS pDevIns, PVNETSTATE pThis, int rcBusy) +{ + return vpciCsEnter(pDevIns, &pThis->VPCI, rcBusy); +} + +DECLINLINE(void) vnetR3CsLeave(PPDMDEVINS pDevIns, PVNETSTATE pThis) +{ + vpciCsLeave(pDevIns, &pThis->VPCI); +} + + +DECLINLINE(int) vnetCsRxEnter(PVNETSTATE pThis, int rcBusy) +{ + RT_NOREF_PV(pThis); + RT_NOREF_PV(rcBusy); + // STAM_PROFILE_START(&pThis->CTXSUFF(StatCsRx), a); + // int rc = PDMCritSectEnter(&pThis->csRx, rcBusy); + // STAM_PROFILE_STOP(&pThis->CTXSUFF(StatCsRx), a); + // return rc; + return VINF_SUCCESS; +} + +DECLINLINE(void) vnetCsRxLeave(PVNETSTATE pThis) +{ + RT_NOREF_PV(pThis); + // PDMCritSectLeave(&pThis->csRx); +} + +/** + * A helper function to detect the link state to the other side of "the wire". + * + * When deciding to bring up the link we need to take into account both if the + * cable is connected and if our device is actually connected to the outside + * world. If no driver is attached we won't start the TX thread nor we will + * initialize the TX semaphore, which is a problem for the TX queue handler. + * + * @returns true if the device is connected to something. + * + * @param pDevIns The device instance. + */ +DECLINLINE(bool) vnetR3IsConnected(PPDMDEVINS pDevIns) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + PVNETSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVNETSTATECC); + return pThis->fCableConnected && pThisCC->pDrv; +} + +/** + * Dump a packet to debug log. + * + * @param pThis The virtio-net shared instance data. + * @param pbPacket The packet. + * @param cb The size of the packet. + * @param pszText A string denoting direction of packet transfer. + */ +DECLINLINE(void) vnetR3PacketDump(PVNETSTATE pThis, const uint8_t *pbPacket, size_t cb, const char *pszText) +{ +# ifdef DEBUG +# if 0 + Log(("%s %s packet #%d (%d bytes):\n", + INSTANCE(pThis), pszText, ++pThis->u32PktNo, cb)); + Log3(("%.*Rhxd\n", cb, pbPacket)); +# else + vboxEthPacketDump(INSTANCE(pThis), pszText, pbPacket, (uint32_t)cb); +# endif +# else + RT_NOREF4(pThis, pbPacket, cb, pszText); +# endif +} +#endif /* IN_RING3 */ + +/** + * Print features given in uFeatures to debug log. + * + * @param pThis The virtio-net shared instance data. + * @param fFeatures Descriptions of which features to print. + * @param pcszText A string to print before the list of features. + */ +DECLINLINE(void) vnetPrintFeatures(PVNETSTATE pThis, uint32_t fFeatures, const char *pcszText) +{ +#ifdef LOG_ENABLED + static struct + { + uint32_t fMask; + const char *pcszDesc; + } const s_aFeatures[] = + { + { VNET_F_CSUM, "host handles pkts w/ partial csum" }, + { VNET_F_GUEST_CSUM, "guest handles pkts w/ partial csum" }, + { VNET_F_MAC, "host has given MAC address" }, + { VNET_F_GSO, "host handles pkts w/ any GSO type" }, + { VNET_F_GUEST_TSO4, "guest can handle TSOv4 in" }, + { VNET_F_GUEST_TSO6, "guest can handle TSOv6 in" }, + { VNET_F_GUEST_ECN, "guest can handle TSO[6] w/ ECN in" }, + { VNET_F_GUEST_UFO, "guest can handle UFO in" }, + { VNET_F_HOST_TSO4, "host can handle TSOv4 in" }, + { VNET_F_HOST_TSO6, "host can handle TSOv6 in" }, + { VNET_F_HOST_ECN, "host can handle TSO[6] w/ ECN in" }, + { VNET_F_HOST_UFO, "host can handle UFO in" }, + { VNET_F_MRG_RXBUF, "host can merge receive buffers" }, + { VNET_F_STATUS, "virtio_net_config.status available" }, + { VNET_F_CTRL_VQ, "control channel available" }, + { VNET_F_CTRL_RX, "control channel RX mode support" }, + { VNET_F_CTRL_VLAN, "control channel VLAN filtering" } + }; + + Log3(("%s %s:\n", INSTANCE(pThis), pcszText)); + for (unsigned i = 0; i < RT_ELEMENTS(s_aFeatures); ++i) + { + if (s_aFeatures[i].fMask & fFeatures) + Log3(("%s --> %s\n", INSTANCE(pThis), s_aFeatures[i].pcszDesc)); + } +#else /* !LOG_ENABLED */ + RT_NOREF3(pThis, fFeatures, pcszText); +#endif /* !LOG_ENABLED */ +} + +/** + * @interface_method_impl{VPCIIOCALLBACKS,pfnGetHostFeatures} + */ +static DECLCALLBACK(uint32_t) vnetIoCb_GetHostFeatures(PVPCISTATE pVPciState) +{ + RT_NOREF_PV(pVPciState); + + /* We support: + * - Host-provided MAC address + * - Link status reporting in config space + * - Control queue + * - RX mode setting + * - MAC filter table + * - VLAN filter + */ + return VNET_F_MAC + | VNET_F_STATUS + | VNET_F_CTRL_VQ + | VNET_F_CTRL_RX + | VNET_F_CTRL_VLAN +#ifdef VNET_WITH_GSO + | VNET_F_CSUM + | VNET_F_HOST_TSO4 + | VNET_F_HOST_TSO6 + | VNET_F_HOST_UFO + | VNET_F_GUEST_CSUM /* We expect the guest to accept partial TCP checksums (see @bugref{4796}) */ + | VNET_F_GUEST_TSO4 + | VNET_F_GUEST_TSO6 + | VNET_F_GUEST_UFO +#endif +#ifdef VNET_WITH_MERGEABLE_RX_BUFS + | VNET_F_MRG_RXBUF +#endif + ; +} + +/** + * @interface_method_impl{VPCIIOCALLBACKS,pfnGetHostMinimalFeatures} + */ +static DECLCALLBACK(uint32_t) vnetIoCb_GetHostMinimalFeatures(PVPCISTATE pVPciState) +{ + RT_NOREF_PV(pVPciState); + return VNET_F_MAC; +} + +/** + * @interface_method_impl{VPCIIOCALLBACKS,pfnSetHostFeatures} + */ +static DECLCALLBACK(void) vnetIoCb_SetHostFeatures(PVPCISTATE pVPciState, uint32_t fFeatures) +{ + PVNETSTATE pThis = RT_FROM_MEMBER(pVPciState, VNETSTATE, VPCI); + /** @todo Nothing to do here yet */ + LogFlow(("%s vnetIoCb_SetHostFeatures: uFeatures=%x\n", INSTANCE(pThis), fFeatures)); + vnetPrintFeatures(pThis, fFeatures, "The guest negotiated the following features"); +} + +/** + * @interface_method_impl{VPCIIOCALLBACKS,pfnGetConfig} + */ +static DECLCALLBACK(int) vnetIoCb_GetConfig(PVPCISTATE pVPciState, uint32_t offCfg, uint32_t cb, void *data) +{ + PVNETSTATE pThis = RT_FROM_MEMBER(pVPciState, VNETSTATE, VPCI); + if (offCfg + cb > sizeof(struct VNetPCIConfig)) + { + Log(("%s vnetIoCb_GetConfig: Read beyond the config structure is attempted (offCfg=%#x cb=%x).\n", INSTANCE(pThis), offCfg, cb)); + return VERR_IOM_IOPORT_UNUSED; + } + memcpy(data, (uint8_t *)&pThis->config + offCfg, cb); + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{VPCIIOCALLBACKS,pfnSetConfig} + */ +static DECLCALLBACK(int) vnetIoCb_SetConfig(PVPCISTATE pVPciState, uint32_t offCfg, uint32_t cb, void *data) +{ + PVNETSTATE pThis = RT_FROM_MEMBER(pVPciState, VNETSTATE, VPCI); + if (offCfg + cb > sizeof(struct VNetPCIConfig)) + { + Log(("%s vnetIoCb_SetConfig: Write beyond the config structure is attempted (offCfg=%#x cb=%x).\n", INSTANCE(pThis), offCfg, cb)); + if (offCfg < sizeof(struct VNetPCIConfig)) + memcpy((uint8_t *)&pThis->config + offCfg, data, sizeof(struct VNetPCIConfig) - offCfg); + return VINF_SUCCESS; + } + memcpy((uint8_t *)&pThis->config + offCfg, data, cb); + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{VPCIIOCALLBACKS,pfnReset} + * + * Hardware reset. Revert all registers to initial values. + */ +static DECLCALLBACK(int) vnetIoCb_Reset(PPDMDEVINS pDevIns) +{ +#ifndef IN_RING3 + RT_NOREF(pDevIns); + return VINF_IOM_R3_IOPORT_WRITE; +#else + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + PVNETSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVNETSTATECC); + + Log(("%s Reset triggered\n", INSTANCE(pThis))); + + int rc = vnetCsRxEnter(pThis, VERR_SEM_BUSY); + if (RT_UNLIKELY(rc != VINF_SUCCESS)) + { + LogRel(("vnetIoCb_Reset failed to enter RX critical section!\n")); + return rc; + } + vpciReset(pDevIns, &pThis->VPCI); + vnetCsRxLeave(pThis); + + /// @todo Implement reset + if (vnetR3IsConnected(pDevIns)) + pThis->config.uStatus = VNET_S_LINK_UP; + else + pThis->config.uStatus = 0; + Log(("%s vnetIoCb_Reset: Link is %s\n", INSTANCE(pThis), vnetR3IsConnected(pDevIns) ? "up" : "down")); + + /* + * By default we pass all packets up since the older guests cannot control + * virtio mode. + */ + pThis->fPromiscuous = true; + pThis->fAllMulti = false; + pThis->cMacFilterEntries = 0; + memset(pThis->aMacFilter, 0, VNET_MAC_FILTER_LEN * sizeof(RTMAC)); + memset(pThis->aVlanFilter, 0, sizeof(pThis->aVlanFilter)); + pThis->uIsTransmitting = 0; + if (pThisCC->pDrv) + pThisCC->pDrv->pfnSetPromiscuousMode(pThisCC->pDrv, true); + return VINF_SUCCESS; +#endif +} + + +/** + * Wakeup the RX thread. + */ +static void vnetWakeupReceive(PPDMDEVINS pDevIns) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + if ( pThis->fMaybeOutOfSpace + && pThis->hEventMoreRxDescAvail != NIL_SUPSEMEVENT) + { + STAM_COUNTER_INC(&pThis->StatRxOverflowWakeup); + Log(("%s Waking up Out-of-RX-space semaphore\n", INSTANCE(pThis))); + int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->hEventMoreRxDescAvail); + AssertRC(rc); + } +} + +#ifdef IN_RING3 + +/** + * Helper function that raises an interrupt if the guest is ready to receive it. + */ +static int vnetR3RaiseInterrupt(PPDMDEVINS pDevIns, PVNETSTATE pThis, int rcBusy, uint8_t u8IntCause) +{ + if (pThis->VPCI.uStatus & VPCI_STATUS_DRV_OK) + return vpciRaiseInterrupt(pDevIns, &pThis->VPCI, rcBusy, u8IntCause); + return rcBusy; +} + + +/** + * Takes down the link temporarily if it's current status is up. + * + * This is used during restore and when replumbing the network link. + * + * The temporary link outage is supposed to indicate to the OS that all network + * connections have been lost and that it for instance is appropriate to + * renegotiate any DHCP lease. + * + * @param pDevIns The device instance. + * @param pThis The virtio-net shared instance data. + * @param pThisCC The virtio-net ring-3 instance data. + */ +static void vnetR3TempLinkDown(PPDMDEVINS pDevIns, PVNETSTATE pThis, PVNETSTATECC pThisCC) +{ + if (pThis->config.uStatus & VNET_S_LINK_UP) + { + pThis->config.uStatus &= ~VNET_S_LINK_UP; + vnetR3RaiseInterrupt(pDevIns, pThis, VERR_SEM_BUSY, VPCI_ISR_CONFIG); + /* Restore the link back in 5 seconds. */ + int rc = PDMDevHlpTimerSetMillies(pDevIns, pThisCC->hLinkUpTimer, pThis->cMsLinkUpDelay); + AssertRC(rc); + Log(("%s vnetR3TempLinkDown: Link is down temporarily\n", INSTANCE(pThis))); + } +} + + +/** + * @callback_method_impl{FNTMTIMERDEV, Link Up Timer handler.} + */ +static DECLCALLBACK(void) vnetR3LinkUpTimer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + PVNETSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVNETSTATECC); + RT_NOREF(hTimer, pvUser); + + VNET_R3_CS_ENTER_RETURN_VOID(pDevIns, pThis); + + Log(("%s vnetR3LinkUpTimer: connected=%s\n", INSTANCE(pThis), vnetR3IsConnected(pDevIns)?"true":"false")); + /* Do not bring up the link if the device is not connected. */ + if (vnetR3IsConnected(pDevIns)) + { + pThis->config.uStatus |= VNET_S_LINK_UP; + vnetR3RaiseInterrupt(pDevIns, pThis, VERR_SEM_BUSY, VPCI_ISR_CONFIG); + vnetWakeupReceive(pDevIns); + } + + vnetR3CsLeave(pDevIns, pThis); + + Log(("%s vnetR3LinkUpTimer: Link is up\n", INSTANCE(pThis))); + if (pThisCC->pDrv) + pThisCC->pDrv->pfnNotifyLinkChanged(pThisCC->pDrv, PDMNETWORKLINKSTATE_UP); +} + +#endif /* IN_RING3 */ + +/** + * @interface_method_impl{VPCIIOCALLBACKS,pfnReady} + * + * This function is called when the driver becomes ready. + */ +static DECLCALLBACK(void) vnetIoCb_Ready(PPDMDEVINS pDevIns) +{ + Log(("%s Driver became ready, waking up RX thread...\n", INSTANCE(PDMDEVINS_2_DATA(pDevIns, PVNETSTATE)))); + vnetWakeupReceive(pDevIns); +} + + +/** + * I/O port callbacks. + */ +static const VPCIIOCALLBACKS g_IOCallbacks = +{ + vnetIoCb_GetHostFeatures, + vnetIoCb_GetHostMinimalFeatures, + vnetIoCb_SetHostFeatures, + vnetIoCb_GetConfig, + vnetIoCb_SetConfig, + vnetIoCb_Reset, + vnetIoCb_Ready, +}; + + +/** + * @callback_method_impl{FNIOMIOPORTNEWIN} + */ +static DECLCALLBACK(VBOXSTRICTRC) vnetIOPortIn(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + RT_NOREF(pvUser); + return vpciIOPortIn(pDevIns, &pThis->VPCI, offPort, pu32, cb, &g_IOCallbacks); +} + + +/** + * @callback_method_impl{FNIOMIOPORTNEWOUT} + */ +static DECLCALLBACK(VBOXSTRICTRC) vnetIOPortOut(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + PVNETSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVNETSTATECC); + RT_NOREF(pvUser); + return vpciIOPortOut(pDevIns, &pThis->VPCI, &pThisCC->VPCI, offPort, u32, cb, &g_IOCallbacks); +} + + +#ifdef IN_RING3 + +/** + * Check if the device can receive data now. + * This must be called before the pfnRecieve() method is called. + * + * @remarks As a side effect this function enables queue notification + * if it cannot receive because the queue is empty. + * It disables notification if it can receive. + * + * @returns VERR_NET_NO_BUFFER_SPACE if it cannot. + * @thread RX + */ +static int vnetR3CanReceive(PPDMDEVINS pDevIns, PVNETSTATE pThis, PVNETSTATECC pThisCC) +{ + int rc = vnetCsRxEnter(pThis, VERR_SEM_BUSY); + AssertRCReturn(rc, rc); + + LogFlow(("%s vnetR3CanReceive\n", INSTANCE(pThis))); + if (!(pThis->VPCI.uStatus & VPCI_STATUS_DRV_OK)) + rc = VERR_NET_NO_BUFFER_SPACE; + else if (!vqueueIsReady(pThisCC->pRxQueue)) + rc = VERR_NET_NO_BUFFER_SPACE; + else if (vqueueIsEmpty(pDevIns, pThisCC->pRxQueue)) + { + vringSetNotification(pDevIns, &pThisCC->pRxQueue->VRing, true); + rc = VERR_NET_NO_BUFFER_SPACE; + } + else + { + vringSetNotification(pDevIns, &pThisCC->pRxQueue->VRing, false); + rc = VINF_SUCCESS; + } + + LogFlow(("%s vnetR3CanReceive -> %Rrc\n", INSTANCE(pThis), rc)); + vnetCsRxLeave(pThis); + return rc; +} + +/** + * @interface_method_impl{PDMINETWORKDOWN,pfnWaitReceiveAvail} + */ +static DECLCALLBACK(int) vnetR3NetworkDown_WaitReceiveAvail(PPDMINETWORKDOWN pInterface, RTMSINTERVAL cMillies) +{ + PVNETSTATECC pThisCC = RT_FROM_MEMBER(pInterface, VNETSTATECC, INetworkDown); + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + LogFlow(("%s vnetR3NetworkDown_WaitReceiveAvail(cMillies=%u)\n", INSTANCE(pThis), cMillies)); + + int rc = vnetR3CanReceive(pDevIns, pThis, pThisCC); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + if (cMillies == 0) + return VERR_NET_NO_BUFFER_SPACE; + + rc = VERR_INTERRUPTED; + ASMAtomicXchgBool(&pThis->fMaybeOutOfSpace, true); + STAM_PROFILE_START(&pThis->StatRxOverflow, a); + + VMSTATE enmVMState; + while (RT_LIKELY( (enmVMState = PDMDevHlpVMState(pDevIns)) == VMSTATE_RUNNING + || enmVMState == VMSTATE_RUNNING_LS)) + { + int rc2 = vnetR3CanReceive(pDevIns, pThis, pThisCC); + if (RT_SUCCESS(rc2)) + { + rc = VINF_SUCCESS; + break; + } + Log(("%s vnetR3NetworkDown_WaitReceiveAvail: waiting cMillies=%u...\n", INSTANCE(pThis), cMillies)); + rc2 = PDMDevHlpSUPSemEventWaitNoResume(pDevIns, pThis->hEventMoreRxDescAvail, cMillies); + if (RT_FAILURE(rc2) && rc2 != VERR_TIMEOUT && rc2 != VERR_INTERRUPTED) + RTThreadSleep(1); + } + STAM_PROFILE_STOP(&pThis->StatRxOverflow, a); + ASMAtomicXchgBool(&pThis->fMaybeOutOfSpace, false); + + LogFlow(("%s vnetR3NetworkDown_WaitReceiveAvail -> %d\n", INSTANCE(pThis), rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface, + * For VNETSTATECC::VPCI.IBase} + */ +static DECLCALLBACK(void *) vnetQueryInterface(struct PDMIBASE *pInterface, const char *pszIID) +{ + PVNETSTATECC pThisCC = RT_FROM_MEMBER(pInterface, VNETSTATECC, VPCI.IBase); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMINETWORKDOWN, &pThisCC->INetworkDown); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMINETWORKCONFIG, &pThisCC->INetworkConfig); + + return vpciR3QueryInterface(&pThisCC->VPCI, pszIID); +} + +/** + * Returns true if it is a broadcast packet. + * + * @returns true if destination address indicates broadcast. + * @param pvBuf The ethernet packet. + */ +DECLINLINE(bool) vnetR3IsBroadcast(const void *pvBuf) +{ + static const uint8_t s_abBcastAddr[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + return memcmp(pvBuf, s_abBcastAddr, sizeof(s_abBcastAddr)) == 0; +} + +/** + * Returns true if it is a multicast packet. + * + * @remarks returns true for broadcast packets as well. + * @returns true if destination address indicates multicast. + * @param pvBuf The ethernet packet. + */ +DECLINLINE(bool) vnetR3IsMulticast(const void *pvBuf) +{ + return (*(char*)pvBuf) & 1; +} + +/** + * Determines if the packet is to be delivered to upper layer. + * + * @returns true if packet is intended for this node. + * @param pThis Pointer to the state structure. + * @param pvBuf The ethernet packet. + * @param cb Number of bytes available in the packet. + */ +static bool vnetR3AddressFilter(PVNETSTATE pThis, const void *pvBuf, size_t cb) +{ + if (pThis->fPromiscuous) + return true; + + /* Ignore everything outside of our VLANs */ + uint16_t *u16Ptr = (uint16_t*)pvBuf; + /* Compare TPID with VLAN Ether Type */ + if ( u16Ptr[6] == RT_H2BE_U16(0x8100) + && !ASMBitTest(pThis->aVlanFilter, RT_BE2H_U16(u16Ptr[7]) & 0xFFF)) + { + Log4(("%s vnetR3AddressFilter: not our VLAN, returning false\n", INSTANCE(pThis))); + return false; + } + + if (vnetR3IsBroadcast(pvBuf)) + return true; + + if (pThis->fAllMulti && vnetR3IsMulticast(pvBuf)) + return true; + + if (!memcmp(pThis->config.mac.au8, pvBuf, sizeof(RTMAC))) + return true; + Log4(("%s vnetR3AddressFilter: %RTmac (conf) != %RTmac (dest)\n", INSTANCE(pThis), pThis->config.mac.au8, pvBuf)); + + for (unsigned i = 0; i < pThis->cMacFilterEntries; i++) + if (!memcmp(&pThis->aMacFilter[i], pvBuf, sizeof(RTMAC))) + return true; + + Log2(("%s vnetR3AddressFilter: failed all tests, returning false, packet dump follows:\n", INSTANCE(pThis))); + vnetR3PacketDump(pThis, (const uint8_t *)pvBuf, cb, "<-- Incoming"); + + return false; +} + +/** + * Pad and store received packet. + * + * @remarks Make sure that the packet appears to upper layer as one coming + * from real Ethernet: pad it and insert FCS. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThis The virtio-net shared instance data. + * @param pvBuf The available data. + * @param cb Number of bytes available in the buffer. + * @thread RX + */ +static int vnetR3HandleRxPacket(PPDMDEVINS pDevIns, PVNETSTATE pThis, PVNETSTATECC pThisCC, + const void *pvBuf, size_t cb, PCPDMNETWORKGSO pGso) +{ + VNETHDRMRX Hdr; + unsigned cbHdr; + RTGCPHYS addrHdrMrx = 0; + + if (pGso) + { + Log2(("%s vnetR3HandleRxPacket: gso type=%x cbHdrsTotal=%u cbHdrsSeg=%u mss=%u off1=0x%x off2=0x%x\n", + INSTANCE(pThis), pGso->u8Type, pGso->cbHdrsTotal, pGso->cbHdrsSeg, pGso->cbMaxSeg, pGso->offHdr1, pGso->offHdr2)); + Hdr.Hdr.u8Flags = VNETHDR_F_NEEDS_CSUM; + switch (pGso->u8Type) + { + case PDMNETWORKGSOTYPE_IPV4_TCP: + Hdr.Hdr.u8GSOType = VNETHDR_GSO_TCPV4; + Hdr.Hdr.u16CSumOffset = RT_OFFSETOF(RTNETTCP, th_sum); + break; + case PDMNETWORKGSOTYPE_IPV6_TCP: + Hdr.Hdr.u8GSOType = VNETHDR_GSO_TCPV6; + Hdr.Hdr.u16CSumOffset = RT_OFFSETOF(RTNETTCP, th_sum); + break; + case PDMNETWORKGSOTYPE_IPV4_UDP: + Hdr.Hdr.u8GSOType = VNETHDR_GSO_UDP; + Hdr.Hdr.u16CSumOffset = RT_OFFSETOF(RTNETUDP, uh_sum); + break; + default: + return VERR_INVALID_PARAMETER; + } + Hdr.Hdr.u16HdrLen = pGso->cbHdrsTotal; + Hdr.Hdr.u16GSOSize = pGso->cbMaxSeg; + Hdr.Hdr.u16CSumStart = pGso->offHdr2; + Hdr.u16NumBufs = 0; + STAM_REL_COUNTER_INC(&pThis->StatReceiveGSO); + } + else + { + Hdr.Hdr.u8Flags = 0; + Hdr.Hdr.u8GSOType = VNETHDR_GSO_NONE; + Hdr.Hdr.u16HdrLen = 0; + Hdr.Hdr.u16GSOSize = 0; + Hdr.Hdr.u16CSumStart = 0; + Hdr.Hdr.u16CSumOffset = 0; + Hdr.u16NumBufs = 0; + } + + if (vnetR3MergeableRxBuffers(pThis)) + cbHdr = sizeof(VNETHDRMRX); + else + cbHdr = sizeof(VNETHDR); + + vnetR3PacketDump(pThis, (const uint8_t *)pvBuf, cb, "<-- Incoming"); + + unsigned int uOffset = 0; + unsigned int nElem; + for (nElem = 0; uOffset < cb; nElem++) + { + VQUEUEELEM elem; + unsigned int nSeg = 0, uElemSize = 0, cbReserved = 0; + + if (!vqueueGet(pDevIns, &pThis->VPCI, pThisCC->pRxQueue, &elem)) + { + /* + * @todo: It is possible to run out of RX buffers if only a few + * were added and we received a big packet. + */ + Log(("%s vnetR3HandleRxPacket: Suddenly there is no space in receive queue!\n", INSTANCE(pThis))); + return VERR_INTERNAL_ERROR; + } + + if (elem.cIn < 1) + { + Log(("%s vnetR3HandleRxPacket: No writable descriptors in receive queue!\n", INSTANCE(pThis))); + return VERR_INTERNAL_ERROR; + } + + if (nElem == 0) + { + if (vnetR3MergeableRxBuffers(pThis)) + { + addrHdrMrx = elem.aSegsIn[nSeg].addr; + cbReserved = cbHdr; + } + else + { + /* The very first segment of the very first element gets the header. */ + if (elem.aSegsIn[nSeg].cb != sizeof(VNETHDR)) + { + Log(("%s vnetR3HandleRxPacket: The first descriptor does match the header size!\n", INSTANCE(pThis))); + return VERR_INTERNAL_ERROR; + } + elem.aSegsIn[nSeg++].pv = &Hdr; + } + uElemSize += cbHdr; + } + while (nSeg < elem.cIn && uOffset < cb) + { + unsigned int uSize = (unsigned int)RT_MIN(elem.aSegsIn[nSeg].cb - (nSeg?0:cbReserved), + cb - uOffset); + elem.aSegsIn[nSeg++].pv = (uint8_t*)pvBuf + uOffset; + uOffset += uSize; + uElemSize += uSize; + } + STAM_PROFILE_START(&pThis->StatReceiveStore, a); + vqueuePut(pDevIns, &pThis->VPCI, pThisCC->pRxQueue, &elem, uElemSize, cbReserved); + STAM_PROFILE_STOP(&pThis->StatReceiveStore, a); + if (!vnetR3MergeableRxBuffers(pThis)) + break; + cbReserved = 0; + } + if (vnetR3MergeableRxBuffers(pThis)) + { + Hdr.u16NumBufs = nElem; + int rc = PDMDevHlpPCIPhysWrite(pDevIns, addrHdrMrx, + &Hdr, sizeof(Hdr)); + if (RT_FAILURE(rc)) + { + Log(("%s vnetR3HandleRxPacket: Failed to write merged RX buf header: %Rrc\n", INSTANCE(pThis), rc)); + return rc; + } + } + vqueueSync(pDevIns, &pThis->VPCI, pThisCC->pRxQueue); + if (uOffset < cb) + { + Log(("%s vnetR3HandleRxPacket: Packet did not fit into RX queue (packet size=%u)!\n", INSTANCE(pThis), cb)); + return VERR_TOO_MUCH_DATA; + } + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMINETWORKDOWN,pfnReceiveGso} + */ +static DECLCALLBACK(int) vnetR3NetworkDown_ReceiveGso(PPDMINETWORKDOWN pInterface, const void *pvBuf, + size_t cb, PCPDMNETWORKGSO pGso) +{ + PVNETSTATECC pThisCC = RT_FROM_MEMBER(pInterface, VNETSTATECC, INetworkDown); + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + + if (pGso) + { + uint32_t uFeatures = pThis->VPCI.uGuestFeatures; + + switch (pGso->u8Type) + { + case PDMNETWORKGSOTYPE_IPV4_TCP: + uFeatures &= VNET_F_GUEST_TSO4; + break; + case PDMNETWORKGSOTYPE_IPV6_TCP: + uFeatures &= VNET_F_GUEST_TSO6; + break; + case PDMNETWORKGSOTYPE_IPV4_UDP: + case PDMNETWORKGSOTYPE_IPV6_UDP: + uFeatures &= VNET_F_GUEST_UFO; + break; + default: + uFeatures = 0; + break; + } + if (!uFeatures) + { + Log2(("%s vnetR3NetworkDown_ReceiveGso: GSO type (0x%x) not supported\n", INSTANCE(pThis), pGso->u8Type)); + return VERR_NOT_SUPPORTED; + } + } + + Log2(("%s vnetR3NetworkDown_ReceiveGso: pvBuf=%p cb=%u pGso=%p\n", INSTANCE(pThis), pvBuf, cb, pGso)); + int rc = vnetR3CanReceive(pDevIns, pThis, pThisCC); + if (RT_FAILURE(rc)) + return rc; + + /* Drop packets if VM is not running or cable is disconnected. */ + VMSTATE enmVMState = PDMDevHlpVMState(pDevIns); + if (( enmVMState != VMSTATE_RUNNING + && enmVMState != VMSTATE_RUNNING_LS) + || !(pThis->config.uStatus & VNET_S_LINK_UP)) + return VINF_SUCCESS; + + STAM_PROFILE_START(&pThis->StatReceive, a); + vpciR3SetReadLed(&pThis->VPCI, true); + if (vnetR3AddressFilter(pThis, pvBuf, cb)) + { + rc = vnetCsRxEnter(pThis, VERR_SEM_BUSY); + if (RT_SUCCESS(rc)) + { + rc = vnetR3HandleRxPacket(pDevIns, pThis, pThisCC, pvBuf, cb, pGso); + STAM_REL_COUNTER_ADD(&pThis->StatReceiveBytes, cb); + vnetCsRxLeave(pThis); + } + } + vpciR3SetReadLed(&pThis->VPCI, false); + STAM_PROFILE_STOP(&pThis->StatReceive, a); + return rc; +} + +/** + * @interface_method_impl{PDMINETWORKDOWN,pfnReceive} + */ +static DECLCALLBACK(int) vnetR3NetworkDown_Receive(PPDMINETWORKDOWN pInterface, const void *pvBuf, size_t cb) +{ + return vnetR3NetworkDown_ReceiveGso(pInterface, pvBuf, cb, NULL); +} + +/** + * @interface_method_impl{PDMINETWORKCONFIG,pfnGetMac} + */ +static DECLCALLBACK(int) vnetR3NetworkConfig_GetMac(PPDMINETWORKCONFIG pInterface, PRTMAC pMac) +{ + PVNETSTATECC pThisCC = RT_FROM_MEMBER(pInterface, VNETSTATECC, INetworkConfig); + PVNETSTATE pThis = PDMDEVINS_2_DATA(pThisCC->pDevIns, PVNETSTATE); + memcpy(pMac, pThis->config.mac.au8, sizeof(RTMAC)); + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMINETWORKCONFIG,pfnGetLinkState} + */ +static DECLCALLBACK(PDMNETWORKLINKSTATE) vnetR3NetworkConfig_GetLinkState(PPDMINETWORKCONFIG pInterface) +{ + PVNETSTATECC pThisCC = RT_FROM_MEMBER(pInterface, VNETSTATECC, INetworkConfig); + PVNETSTATE pThis = PDMDEVINS_2_DATA(pThisCC->pDevIns, PVNETSTATE); + if (pThis->config.uStatus & VNET_S_LINK_UP) + return PDMNETWORKLINKSTATE_UP; + return PDMNETWORKLINKSTATE_DOWN; +} + +/** + * @interface_method_impl{PDMINETWORKCONFIG,pfnSetLinkState} + */ +static DECLCALLBACK(int) vnetR3NetworkConfig_SetLinkState(PPDMINETWORKCONFIG pInterface, PDMNETWORKLINKSTATE enmState) +{ + PVNETSTATECC pThisCC = RT_FROM_MEMBER(pInterface, VNETSTATECC, INetworkConfig); + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + bool fOldUp = !!(pThis->config.uStatus & VNET_S_LINK_UP); + bool fNewUp = enmState == PDMNETWORKLINKSTATE_UP; + + Log(("%s vnetR3NetworkConfig_SetLinkState: enmState=%d\n", INSTANCE(pThis), enmState)); + if (enmState == PDMNETWORKLINKSTATE_DOWN_RESUME) + { + if (fOldUp) + { + /* + * We bother to bring the link down only if it was up previously. The UP link state + * notification will be sent when the link actually goes up in vnetR3LinkUpTimer(). + */ + vnetR3TempLinkDown(pDevIns, pThis, pThisCC); + if (pThisCC->pDrv) + pThisCC->pDrv->pfnNotifyLinkChanged(pThisCC->pDrv, enmState); + } + } + else if (fNewUp != fOldUp) + { + if (fNewUp) + { + pThis->fCableConnected = true; + /* The link state depends both on the cable connected and device attached. */ + if (vnetR3IsConnected(pDevIns)) + { + Log(("%s Link is up\n", INSTANCE(pThis))); + pThis->config.uStatus |= VNET_S_LINK_UP; + vnetR3RaiseInterrupt(pDevIns, pThis, VERR_SEM_BUSY, VPCI_ISR_CONFIG); + } + } + else + { + /* The link was brought down explicitly, make sure it won't come up by timer. */ + PDMDevHlpTimerStop(pDevIns, pThisCC->hLinkUpTimer); + Log(("%s Link is down\n", INSTANCE(pThis))); + pThis->fCableConnected = false; + pThis->config.uStatus &= ~VNET_S_LINK_UP; + vnetR3RaiseInterrupt(pDevIns, pThis, VERR_SEM_BUSY, VPCI_ISR_CONFIG); + } + if (pThisCC->pDrv) + pThisCC->pDrv->pfnNotifyLinkChanged(pThisCC->pDrv, enmState); + } + return VINF_SUCCESS; +} + +/** + * @callback_method_impl{FNVPCIQUEUECALLBACK, The RX queue} + */ +static DECLCALLBACK(void) vnetR3QueueReceive(PPDMDEVINS pDevIns, PVQUEUE pQueue) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + RT_NOREF(pThis, pQueue); + Log(("%s Receive buffers has been added, waking up receive thread.\n", INSTANCE(pThis))); + vnetWakeupReceive(pDevIns); +} + +/** + * Sets up the GSO context according to the Virtio header. + * + * @param pGso The GSO context to setup. + * @param pCtx The context descriptor. + */ +DECLINLINE(PPDMNETWORKGSO) vnetR3SetupGsoCtx(PPDMNETWORKGSO pGso, VNETHDR const *pHdr) +{ + pGso->u8Type = PDMNETWORKGSOTYPE_INVALID; + + if (pHdr->u8GSOType & VNETHDR_GSO_ECN) + { + AssertMsgFailed(("Unsupported flag in virtio header: ECN\n")); + return NULL; + } + switch (pHdr->u8GSOType & ~VNETHDR_GSO_ECN) + { + case VNETHDR_GSO_TCPV4: + pGso->u8Type = PDMNETWORKGSOTYPE_IPV4_TCP; + pGso->cbHdrsSeg = pHdr->u16HdrLen; + break; + case VNETHDR_GSO_TCPV6: + pGso->u8Type = PDMNETWORKGSOTYPE_IPV6_TCP; + pGso->cbHdrsSeg = pHdr->u16HdrLen; + break; + case VNETHDR_GSO_UDP: + pGso->u8Type = PDMNETWORKGSOTYPE_IPV4_UDP; + pGso->cbHdrsSeg = pHdr->u16CSumStart; + break; + default: + return NULL; + } + if (pHdr->u8Flags & VNETHDR_F_NEEDS_CSUM) + pGso->offHdr2 = pHdr->u16CSumStart; + else + { + AssertMsgFailed(("GSO without checksum offloading!\n")); + return NULL; + } + pGso->offHdr1 = sizeof(RTNETETHERHDR); + pGso->cbHdrsTotal = pHdr->u16HdrLen; + pGso->cbMaxSeg = pHdr->u16GSOSize; + return pGso; +} + +DECLINLINE(uint16_t) vnetR3CSum16(const void *pvBuf, size_t cb) +{ + uint32_t csum = 0; + uint16_t *pu16 = (uint16_t *)pvBuf; + + while (cb > 1) + { + csum += *pu16++; + cb -= 2; + } + if (cb) + csum += *(uint8_t*)pu16; + while (csum >> 16) + csum = (csum >> 16) + (csum & 0xFFFF); + return ~csum; +} + +DECLINLINE(void) vnetR3CompleteChecksum(uint8_t *pBuf, size_t cbSize, uint16_t uStart, uint16_t uOffset) +{ + AssertReturnVoid(uStart < cbSize); + AssertReturnVoid(uStart + uOffset + sizeof(uint16_t) <= cbSize); + *(uint16_t *)(pBuf + uStart + uOffset) = vnetR3CSum16(pBuf + uStart, cbSize - uStart); +} + +static bool vnetR3ReadHeader(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PVNETHDR pHdr, uint32_t cbMax) +{ + int rc = PDMDevHlpPhysRead(pDevIns, GCPhys, pHdr, sizeof(*pHdr)); /** @todo r=bird: Why not PDMDevHlpPCIPhysRead? */ + if (RT_FAILURE(rc)) + return false; + + Log4(("virtio-net: header flags=%x gso-type=%x len=%x gso-size=%x csum-start=%x csum-offset=%x cb=%x\n", + pHdr->u8Flags, pHdr->u8GSOType, pHdr->u16HdrLen, pHdr->u16GSOSize, pHdr->u16CSumStart, pHdr->u16CSumOffset, cbMax)); + + if (pHdr->u8GSOType) + { + uint32_t u32MinHdrSize; + + /* Segmentation offloading cannot be done without checksumming. */ + if (RT_UNLIKELY(!(pHdr->u8Flags & VNETHDR_F_NEEDS_CSUM))) + return false; + /* We do not support ECN. */ + if (RT_UNLIKELY(pHdr->u8GSOType & VNETHDR_GSO_ECN)) + return false; + switch (pHdr->u8GSOType) + { + case VNETHDR_GSO_TCPV4: + case VNETHDR_GSO_TCPV6: + u32MinHdrSize = sizeof(RTNETTCP); + break; + case VNETHDR_GSO_UDP: + u32MinHdrSize = 0; + break; + default: + return false; + } + /* Header+MSS must not exceed the packet size. */ + if (RT_UNLIKELY(u32MinHdrSize + pHdr->u16CSumStart + pHdr->u16GSOSize > cbMax)) + return false; + } + /* Checksum must fit into the frame (validating both checksum fields). */ + if ( (pHdr->u8Flags & VNETHDR_F_NEEDS_CSUM) + && sizeof(uint16_t) + pHdr->u16CSumStart + pHdr->u16CSumOffset > cbMax) + return false; + Log4(("virtio-net: return true\n")); + return true; +} + +static int vnetR3TransmitFrame(PVNETSTATE pThis, PVNETSTATECC pThisCC, PPDMSCATTERGATHER pSgBuf, + PPDMNETWORKGSO pGso, PVNETHDR pHdr) +{ + vnetR3PacketDump(pThis, (uint8_t *)pSgBuf->aSegs[0].pvSeg, pSgBuf->cbUsed, "--> Outgoing"); + if (pGso) + { + /* Some guests (RHEL) may report HdrLen excluding transport layer header! */ + /* + * We cannot use cdHdrs provided by the guest because of different ways + * it gets filled out by different versions of kernels. + */ + //if (pGso->cbHdrs < pHdr->u16CSumStart + pHdr->u16CSumOffset + 2) + { + Log4(("%s vnetR3TransmitPendingPackets: HdrLen before adjustment %d.\n", + INSTANCE(pThis), pGso->cbHdrsTotal)); + switch (pGso->u8Type) + { + case PDMNETWORKGSOTYPE_IPV4_TCP: + case PDMNETWORKGSOTYPE_IPV6_TCP: + pGso->cbHdrsTotal = pHdr->u16CSumStart + + ((PRTNETTCP)(((uint8_t*)pSgBuf->aSegs[0].pvSeg) + pHdr->u16CSumStart))->th_off * 4; + AssertMsgReturn(pSgBuf->cbUsed > pGso->cbHdrsTotal, + ("cbHdrsTotal exceeds size of frame"), VERR_BUFFER_OVERFLOW); + pGso->cbHdrsSeg = pGso->cbHdrsTotal; + break; + case PDMNETWORKGSOTYPE_IPV4_UDP: + pGso->cbHdrsTotal = (uint8_t)(pHdr->u16CSumStart + sizeof(RTNETUDP)); + pGso->cbHdrsSeg = pHdr->u16CSumStart; + break; + } + /* Update GSO structure embedded into the frame */ + ((PPDMNETWORKGSO)pSgBuf->pvUser)->cbHdrsTotal = pGso->cbHdrsTotal; + ((PPDMNETWORKGSO)pSgBuf->pvUser)->cbHdrsSeg = pGso->cbHdrsSeg; + Log4(("%s vnetR3TransmitPendingPackets: adjusted HdrLen to %d.\n", + INSTANCE(pThis), pGso->cbHdrsTotal)); + } + Log2(("%s vnetR3TransmitPendingPackets: gso type=%x cbHdrsTotal=%u cbHdrsSeg=%u mss=%u off1=0x%x off2=0x%x\n", + INSTANCE(pThis), pGso->u8Type, pGso->cbHdrsTotal, pGso->cbHdrsSeg, pGso->cbMaxSeg, pGso->offHdr1, pGso->offHdr2)); + STAM_REL_COUNTER_INC(&pThis->StatTransmitGSO); + } + else if (pHdr->u8Flags & VNETHDR_F_NEEDS_CSUM) + { + STAM_REL_COUNTER_INC(&pThis->StatTransmitCSum); + /* + * This is not GSO frame but checksum offloading is requested. + */ + vnetR3CompleteChecksum((uint8_t*)pSgBuf->aSegs[0].pvSeg, pSgBuf->cbUsed, + pHdr->u16CSumStart, pHdr->u16CSumOffset); + } + + return pThisCC->pDrv->pfnSendBuf(pThisCC->pDrv, pSgBuf, false); +} + +static void vnetR3TransmitPendingPackets(PPDMDEVINS pDevIns, PVNETSTATE pThis, PVNETSTATECC pThisCC, + PVQUEUE pQueue, bool fOnWorkerThread) +{ + /* + * Let's deal with the cases we are not going to transmit anything, + * to avoid setting 'uIsTransmitting' on and off. + */ + if ((pThis->VPCI.uStatus & VPCI_STATUS_DRV_OK) == 0) + { + Log(("%s Ignoring transmit requests from non-existent driver (status=0x%x).\n", INSTANCE(pThis), pThis->VPCI.uStatus)); + return; + } + + if (!vnetR3IsConnected(pDevIns)) + { + Log(("%s Ignoring transmit requests while cable is disconnected.\n", INSTANCE(pThis))); + return; + } + + /* + * Only one thread is allowed to transmit at a time, others should skip + * transmission as the packets will be picked up by the transmitting + * thread. + */ + if (!ASMAtomicCmpXchgU32(&pThis->uIsTransmitting, 1, 0)) + return; + + PPDMINETWORKUP pDrv = pThisCC->pDrv; + if (pDrv) + { + int rc = pDrv->pfnBeginXmit(pDrv, fOnWorkerThread); + Assert(rc == VINF_SUCCESS || rc == VERR_TRY_AGAIN); + if (rc == VERR_TRY_AGAIN) + { + ASMAtomicWriteU32(&pThis->uIsTransmitting, 0); + return; + } + } + + unsigned int cbHdr; + if (vnetR3MergeableRxBuffers(pThis)) + cbHdr = sizeof(VNETHDRMRX); + else + cbHdr = sizeof(VNETHDR); + + Log3(("%s vnetR3TransmitPendingPackets: About to transmit %d pending packets\n", + INSTANCE(pThis), vringReadAvailIndex(pDevIns, &pThisCC->pTxQueue->VRing) - pThisCC->pTxQueue->uNextAvailIndex)); + + vpciR3SetWriteLed(&pThis->VPCI, true); + + /* + * Do not remove descriptors from available ring yet, try to allocate the + * buffer first. + */ + VQUEUEELEM elem; /* This bugger is big! ~48KB on 64-bit hosts. */ + while (vqueuePeek(pDevIns, &pThis->VPCI, pQueue, &elem)) + { + unsigned int uOffset = 0; + if (elem.cOut < 2 || elem.aSegsOut[0].cb != cbHdr) + { + Log(("%s vnetR3QueueTransmit: The first segment is not the header! (%u < 2 || %u != %u).\n", + INSTANCE(pThis), elem.cOut, elem.aSegsOut[0].cb, cbHdr)); + break; /* For now we simply ignore the header, but it must be there anyway! */ + } + RT_UNTRUSTED_VALIDATED_FENCE(); + + VNETHDR Hdr; + unsigned int uSize = 0; + STAM_PROFILE_ADV_START(&pThis->StatTransmit, a); + + /* Compute total frame size. */ + for (unsigned int i = 1; i < elem.cOut && uSize < VNET_MAX_FRAME_SIZE; i++) + uSize += elem.aSegsOut[i].cb; + Log5(("%s vnetR3TransmitPendingPackets: complete frame is %u bytes.\n", INSTANCE(pThis), uSize)); + Assert(uSize <= VNET_MAX_FRAME_SIZE); + + /* Truncate oversized frames. */ + if (uSize > VNET_MAX_FRAME_SIZE) + uSize = VNET_MAX_FRAME_SIZE; + if (pThisCC->pDrv && vnetR3ReadHeader(pDevIns, elem.aSegsOut[0].addr, &Hdr, uSize)) + { + RT_UNTRUSTED_VALIDATED_FENCE(); + STAM_REL_COUNTER_INC(&pThis->StatTransmitPackets); + STAM_PROFILE_START(&pThis->StatTransmitSend, a); + + PDMNETWORKGSO Gso; + PDMNETWORKGSO *pGso = vnetR3SetupGsoCtx(&Gso, &Hdr); + + /** @todo Optimize away the extra copying! (lazy bird) */ + PPDMSCATTERGATHER pSgBuf; + int rc = pThisCC->pDrv->pfnAllocBuf(pThisCC->pDrv, uSize, pGso, &pSgBuf); + if (RT_SUCCESS(rc)) + { + Assert(pSgBuf->cSegs == 1); + pSgBuf->cbUsed = uSize; + + /* Assemble a complete frame. */ + for (unsigned int i = 1; i < elem.cOut && uSize > 0; i++) + { + unsigned int cbSegment = RT_MIN(uSize, elem.aSegsOut[i].cb); + PDMDevHlpPhysRead(pDevIns, elem.aSegsOut[i].addr, + ((uint8_t*)pSgBuf->aSegs[0].pvSeg) + uOffset, + cbSegment); + uOffset += cbSegment; + uSize -= cbSegment; + } + rc = vnetR3TransmitFrame(pThis, pThisCC, pSgBuf, pGso, &Hdr); + } + else + { + Log4(("virtio-net: failed to allocate SG buffer: size=%u rc=%Rrc\n", uSize, rc)); + STAM_PROFILE_STOP(&pThis->StatTransmitSend, a); + STAM_PROFILE_ADV_STOP(&pThis->StatTransmit, a); + /* Stop trying to fetch TX descriptors until we get more bandwidth. */ + break; + } + + STAM_PROFILE_STOP(&pThis->StatTransmitSend, a); + STAM_REL_COUNTER_ADD(&pThis->StatTransmitBytes, uOffset); + } + + /* Remove this descriptor chain from the available ring */ + vqueueSkip(pDevIns, &pThis->VPCI, pQueue); + vqueuePut(pDevIns, &pThis->VPCI, pQueue, &elem, sizeof(VNETHDR) + uOffset); + vqueueSync(pDevIns, &pThis->VPCI, pQueue); + STAM_PROFILE_ADV_STOP(&pThis->StatTransmit, a); + } + vpciR3SetWriteLed(&pThis->VPCI, false); + + if (pDrv) + pDrv->pfnEndXmit(pDrv); + ASMAtomicWriteU32(&pThis->uIsTransmitting, 0); +} + +/** + * @interface_method_impl{PDMINETWORKDOWN,pfnXmitPending} + */ +static DECLCALLBACK(void) vnetR3NetworkDown_XmitPending(PPDMINETWORKDOWN pInterface) +{ + PVNETSTATECC pThisCC = RT_FROM_MEMBER(pInterface, VNETSTATECC, INetworkDown); + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PVNETSTATE pThis = PDMDEVINS_2_DATA(pThisCC->pDevIns, PVNETSTATE); + STAM_COUNTER_INC(&pThis->StatTransmitByNetwork); + vnetR3TransmitPendingPackets(pDevIns, pThis, pThisCC, pThisCC->pTxQueue, false /*fOnWorkerThread*/); +} + +# ifdef VNET_TX_DELAY + +/** + * @callback_method_impl{FNVPCIQUEUECALLBACK, The TX queue} + */ +static DECLCALLBACK(void) vnetR3QueueTransmit(PPDMDEVINS pDevIns, PVQUEUE pQueue) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + PVNETSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVNETSTATECC); + + if (PDMDevHlpTimerIsActive(pDevIns, pThis->hTxTimer)) + { + PDMDevHlpTimerStop(pDevIns, pThis->hTxTimer); + Log3(("%s vnetR3QueueTransmit: Got kicked with notification disabled, re-enable notification and flush TX queue\n", INSTANCE(pThis))); + vnetR3TransmitPendingPackets(pDevIns, pThis, pThisCC, pQueue, false /*fOnWorkerThread*/); + + VNET_R3_CS_ENTER_RETURN_VOID(pDevIns, pThis); + + vringSetNotification(pDevIns, &pThisCC->pTxQueue->VRing, true); + + vnetR3CsLeave(pDevIns, pThis); + } + else + { + VNET_R3_CS_ENTER_RETURN_VOID(pDevIns, pThis); + + vringSetNotification(pDevIns, &pThisCC->pTxQueue->VRing, false); + PDMDevHlpTimerSetMicro(pDevIns, pThis->hTxTimer, VNET_TX_DELAY); + pThis->u64NanoTS = RTTimeNanoTS(); + + vnetR3CsLeave(pDevIns, pThis); + } +} + +/** + * @callback_method_impl{FNTMTIMERDEV, Transmit Delay Timer handler.} + */ +static DECLCALLBACK(void) vnetR3TxTimer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + PVNETSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVNETSTATECC); + RT_NOREF(hTimer, pvUser); + + uint32_t u32MicroDiff = (uint32_t)((RTTimeNanoTS() - pThis->u64NanoTS) / 1000); + if (u32MicroDiff < pThis->u32MinDiff) + pThis->u32MinDiff = u32MicroDiff; + if (u32MicroDiff > pThis->u32MaxDiff) + pThis->u32MaxDiff = u32MicroDiff; + pThis->u32AvgDiff = (pThis->u32AvgDiff * pThis->u32i + u32MicroDiff) / (pThis->u32i + 1); + pThis->u32i++; + Log3(("vnetR3TxTimer: Expired, diff %9d usec, avg %9d usec, min %9d usec, max %9d usec\n", + u32MicroDiff, pThis->u32AvgDiff, pThis->u32MinDiff, pThis->u32MaxDiff)); + +// Log3(("%s vnetR3TxTimer: Expired\n", INSTANCE(pThis))); + vnetR3TransmitPendingPackets(pDevIns, pThis, pThisCC, pThisCC->pTxQueue, false /*fOnWorkerThread*/); + + VNET_R3_CS_ENTER_RETURN_VOID(pDevIns, pThis); + vringSetNotification(pDevIns, &pThisCC->pTxQueue->VRing, true); + vnetR3CsLeave(pDevIns, pThis); +} + +DECLINLINE(int) vnetR3CreateTxThreadAndEvent(PPDMDEVINS pDevIns, PVNETSTATE pThis, PVNETSTATECC pThisCC) +{ + RT_NOREF(pDevIns, pThis, pThisCC); + return VINF_SUCCESS; +} + +DECLINLINE(void) vnetR3DestroyTxThreadAndEvent(PPDMDEVINS pDevIns, PVNETSTATE pThis, PVNETSTATECC pThisCC) +{ + RT_NOREF(pDevIns, pThis, pThisCC); +} + +# else /* !VNET_TX_DELAY */ + +/** + * @callback_method_impl{FNPDMTHREADDEV} + */ +static DECLCALLBACK(int) vnetR3TxThread(PPDMDEVINS pDevIns, PPDMTHREAD pThread) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + PVNETSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVNETSTATECC); + + if (pThread->enmState == PDMTHREADSTATE_INITIALIZING) + return VINF_SUCCESS; + + int rc = VINF_SUCCESS; + while (pThread->enmState == PDMTHREADSTATE_RUNNING) + { + rc = PDMDevHlpSUPSemEventWaitNoResume(pDevIns, pThis->hTxEvent, RT_INDEFINITE_WAIT); + if (RT_UNLIKELY(pThread->enmState != PDMTHREADSTATE_RUNNING)) + break; + STAM_COUNTER_INC(&pThis->StatTransmitByThread); + while (true) + { + vnetR3TransmitPendingPackets(pDevIns, pThis, pThisCC, pThisCC->pTxQueue, false /*fOnWorkerThread*/); /// @todo shouldn't it be true instead? + Log(("vnetR3TxThread: enable kicking and get to sleep\n")); + vringSetNotification(pDevIns, &pThisCC->pTxQueue->VRing, true); + /* + * Break out of the loop if the device is not connected. Otherwise we will + * end up in a tight loop, not being able to transmit, if there is something + * in TX queue. See @bugref{10096}. + */ + if (vqueueIsEmpty(pDevIns, pThisCC->pTxQueue) || !vnetR3IsConnected(pDevIns)) + break; + vringSetNotification(pDevIns, &pThisCC->pTxQueue->VRing, false); + } + } + + return rc; +} + +/** + * @callback_method_impl{FNPDMTHREADWAKEUPDEV} + */ +static DECLCALLBACK(int) vnetR3TxThreadWakeUp(PPDMDEVINS pDevIns, PPDMTHREAD pThread) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + RT_NOREF(pThread); + return PDMDevHlpSUPSemEventSignal(pDevIns, pThis->hTxEvent); +} + +static int vnetR3CreateTxThreadAndEvent(PPDMDEVINS pDevIns, PVNETSTATE pThis, PVNETSTATECC pThisCC) +{ + int rc = PDMDevHlpSUPSemEventCreate(pDevIns, &pThis->hTxEvent); + if (RT_SUCCESS(rc)) + { + rc = PDMDevHlpThreadCreate(pDevIns, &pThisCC->pTxThread, NULL, vnetR3TxThread, + vnetR3TxThreadWakeUp, 0, RTTHREADTYPE_IO, INSTANCE(pThis)); + if (RT_FAILURE(rc)) + PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("VNET: Failed to create worker thread %s"), INSTANCE(pThis)); + } + else + PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("VNET: Failed to create SUP event semaphore")); + return rc; +} + +static void vnetR3DestroyTxThreadAndEvent(PPDMDEVINS pDevIns, PVNETSTATE pThis, PVNETSTATECC pThisCC) +{ + if (pThisCC->pTxThread) + { + /* Destroy the thread. */ + int rcThread; + int rc = PDMDevHlpThreadDestroy(pDevIns, pThisCC->pTxThread, &rcThread); + if (RT_FAILURE(rc) || RT_FAILURE(rcThread)) + AssertMsgFailed(("%s Failed to destroy async IO thread rc=%Rrc rcThread=%Rrc\n", __FUNCTION__, rc, rcThread)); + pThisCC->pTxThread = NULL; + } + + if (pThis->hTxEvent != NIL_SUPSEMEVENT) + { + PDMDevHlpSUPSemEventClose(pDevIns, pThis->hTxEvent); + pThis->hTxEvent = NIL_SUPSEMEVENT; + } +} + +/** + * @callback_method_impl{FNVPCIQUEUECALLBACK, The TX queue} + */ +static DECLCALLBACK(void) vnetR3QueueTransmit(PPDMDEVINS pDevIns, PVQUEUE pQueue) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + + Log(("vnetR3QueueTransmit: disable kicking and wake up TX thread\n")); + vringSetNotification(pDevIns, &pQueue->VRing, false); + int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->hTxEvent); + AssertRC(rc); +} + +# endif /* !VNET_TX_DELAY */ + +static uint8_t vnetR3ControlRx(PPDMDEVINS pDevIns, PVNETSTATE pThis, PVNETSTATECC pThisCC, + PVNETCTLHDR pCtlHdr, PVQUEUEELEM pElem) +{ + uint8_t u8Ack = VNET_OK; + uint8_t fOn, fDrvWasPromisc = pThis->fPromiscuous | pThis->fAllMulti; + PDMDevHlpPhysRead(pDevIns, pElem->aSegsOut[1].addr, &fOn, sizeof(fOn)); + Log(("%s vnetR3ControlRx: uCommand=%u fOn=%u\n", INSTANCE(pThis), pCtlHdr->u8Command, fOn)); + switch (pCtlHdr->u8Command) + { + case VNET_CTRL_CMD_RX_MODE_PROMISC: + pThis->fPromiscuous = !!fOn; + break; + case VNET_CTRL_CMD_RX_MODE_ALLMULTI: + pThis->fAllMulti = !!fOn; + break; + default: + u8Ack = VNET_ERROR; + } + if (fDrvWasPromisc != (pThis->fPromiscuous | pThis->fAllMulti) && pThisCC->pDrv) + pThisCC->pDrv->pfnSetPromiscuousMode(pThisCC->pDrv, + (pThis->fPromiscuous | pThis->fAllMulti)); + + return u8Ack; +} + +static uint8_t vnetR3ControlMac(PPDMDEVINS pDevIns, PVNETSTATE pThis, PVNETCTLHDR pCtlHdr, PVQUEUEELEM pElem) +{ + uint32_t nMacs = 0; + + if ( pCtlHdr->u8Command != VNET_CTRL_CMD_MAC_TABLE_SET + || pElem->cOut != 3 + || pElem->aSegsOut[1].cb < sizeof(nMacs) + || pElem->aSegsOut[2].cb < sizeof(nMacs)) + { + Log(("%s vnetR3ControlMac: Segment layout is wrong (u8Command=%u nOut=%u cb1=%u cb2=%u)\n", + INSTANCE(pThis), pCtlHdr->u8Command, pElem->cOut, pElem->aSegsOut[1].cb, pElem->aSegsOut[2].cb)); + return VNET_ERROR; + } + + /* Load unicast addresses */ + PDMDevHlpPhysRead(pDevIns, pElem->aSegsOut[1].addr, &nMacs, sizeof(nMacs)); + + if (pElem->aSegsOut[1].cb < nMacs * sizeof(RTMAC) + sizeof(nMacs)) + { + Log(("%s vnetR3ControlMac: The unicast mac segment is too small (nMacs=%u cb=%u)\n", + INSTANCE(pThis), nMacs, pElem->aSegsOut[1].cb)); + return VNET_ERROR; + } + + if (nMacs > VNET_MAC_FILTER_LEN) + { + Log(("%s vnetR3ControlMac: MAC table is too big, have to use promiscuous mode (nMacs=%u)\n", INSTANCE(pThis), nMacs)); + pThis->fPromiscuous = true; + } + else + { + if (nMacs) + PDMDevHlpPhysRead(pDevIns, pElem->aSegsOut[1].addr + sizeof(nMacs), pThis->aMacFilter, nMacs * sizeof(RTMAC)); + pThis->cMacFilterEntries = nMacs; +#ifdef LOG_ENABLED + Log(("%s vnetR3ControlMac: unicast macs:\n", INSTANCE(pThis))); + for(unsigned i = 0; i < nMacs; i++) + Log((" %RTmac\n", &pThis->aMacFilter[i])); +#endif + } + + /* Load multicast addresses */ + PDMDevHlpPhysRead(pDevIns, pElem->aSegsOut[2].addr, &nMacs, sizeof(nMacs)); + + if (pElem->aSegsOut[2].cb < nMacs * sizeof(RTMAC) + sizeof(nMacs)) + { + Log(("%s vnetR3ControlMac: The multicast mac segment is too small (nMacs=%u cb=%u)\n", + INSTANCE(pThis), nMacs, pElem->aSegsOut[2].cb)); + return VNET_ERROR; + } + + if (nMacs > VNET_MAC_FILTER_LEN - pThis->cMacFilterEntries) + { + Log(("%s vnetR3ControlMac: MAC table is too big, have to use allmulti mode (nMacs=%u)\n", INSTANCE(pThis), nMacs)); + pThis->fAllMulti = true; + } + else + { + if (nMacs) + PDMDevHlpPhysRead(pDevIns, + pElem->aSegsOut[2].addr + sizeof(nMacs), + &pThis->aMacFilter[pThis->cMacFilterEntries], + nMacs * sizeof(RTMAC)); +#ifdef LOG_ENABLED + Log(("%s vnetR3ControlMac: multicast macs:\n", INSTANCE(pThis))); + for(unsigned i = 0; i < nMacs; i++) + Log((" %RTmac\n", + &pThis->aMacFilter[i+pThis->cMacFilterEntries])); +#endif + pThis->cMacFilterEntries += nMacs; + } + + return VNET_OK; +} + +static uint8_t vnetR3ControlVlan(PPDMDEVINS pDevIns, PVNETSTATE pThis, PVNETCTLHDR pCtlHdr, PVQUEUEELEM pElem) +{ + uint8_t u8Ack = VNET_OK; + uint16_t u16Vid; + + if (pElem->cOut != 2 || pElem->aSegsOut[1].cb != sizeof(u16Vid)) + { + Log(("%s vnetR3ControlVlan: Segment layout is wrong (u8Command=%u nOut=%u cb=%u)\n", + INSTANCE(pThis), pCtlHdr->u8Command, pElem->cOut, pElem->aSegsOut[1].cb)); + return VNET_ERROR; + } + + PDMDevHlpPhysRead(pDevIns, pElem->aSegsOut[1].addr, &u16Vid, sizeof(u16Vid)); + + if (u16Vid >= VNET_MAX_VID) + { + Log(("%s vnetR3ControlVlan: VLAN ID is out of range (VID=%u)\n", INSTANCE(pThis), u16Vid)); + return VNET_ERROR; + } + + Log(("%s vnetR3ControlVlan: uCommand=%u VID=%u\n", INSTANCE(pThis), pCtlHdr->u8Command, u16Vid)); + + switch (pCtlHdr->u8Command) + { + case VNET_CTRL_CMD_VLAN_ADD: + ASMBitSet(pThis->aVlanFilter, u16Vid); + break; + case VNET_CTRL_CMD_VLAN_DEL: + ASMBitClear(pThis->aVlanFilter, u16Vid); + break; + default: + u8Ack = VNET_ERROR; + } + + return u8Ack; +} + + +/** + * @callback_method_impl{FNVPCIQUEUECALLBACK, The CLT queue} + */ +static DECLCALLBACK(void) vnetR3QueueControl(PPDMDEVINS pDevIns, PVQUEUE pQueue) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + PVNETSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVNETSTATECC); + + VQUEUEELEM elem; + while (vqueueGet(pDevIns, &pThis->VPCI, pQueue, &elem)) + { + if (elem.cOut < 1 || elem.aSegsOut[0].cb < sizeof(VNETCTLHDR)) + { + Log(("%s vnetR3QueueControl: The first 'out' segment is not the header! (%u < 1 || %u < %u).\n", + INSTANCE(pThis), elem.cOut, elem.aSegsOut[0].cb,sizeof(VNETCTLHDR))); + break; /* Skip the element and hope the next one is good. */ + } + if ( elem.cIn < 1 + || elem.aSegsIn[elem.cIn - 1].cb < sizeof(VNETCTLACK)) + { + Log(("%s vnetR3QueueControl: The last 'in' segment is too small to hold the acknowledge! (%u < 1 || %u < %u).\n", + INSTANCE(pThis), elem.cIn, elem.aSegsIn[elem.cIn - 1].cb, sizeof(VNETCTLACK))); + break; /* Skip the element and hope the next one is good. */ + } + + uint8_t bAck; + VNETCTLHDR CtlHdr; + PDMDevHlpPhysRead(pDevIns, elem.aSegsOut[0].addr, &CtlHdr, sizeof(CtlHdr)); + switch (CtlHdr.u8Class) + { + case VNET_CTRL_CLS_RX_MODE: + bAck = vnetR3ControlRx(pDevIns, pThis, pThisCC, &CtlHdr, &elem); + break; + case VNET_CTRL_CLS_MAC: + bAck = vnetR3ControlMac(pDevIns, pThis, &CtlHdr, &elem); + break; + case VNET_CTRL_CLS_VLAN: + bAck = vnetR3ControlVlan(pDevIns, pThis, &CtlHdr, &elem); + break; + default: + bAck = VNET_ERROR; + } + Log(("%s Processed control message %u, ack=%u.\n", INSTANCE(pThis), CtlHdr.u8Class, bAck)); + PDMDevHlpPCIPhysWrite(pDevIns, elem.aSegsIn[elem.cIn - 1].addr, &bAck, sizeof(bAck)); + + vqueuePut(pDevIns, &pThis->VPCI, pQueue, &elem, sizeof(bAck)); + vqueueSync(pDevIns, &pThis->VPCI, pQueue); + } +} + +/* -=-=-=-=- Debug info item -=-=-=-=- */ + +/** + * @callback_method_impl{FNDBGFINFOARGVDEV} + */ +static DECLCALLBACK(void) vnetR3Info(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, int cArgs, char **papszArgs) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + RT_NOREF(cArgs, papszArgs); + vpciR3DumpStateWorker(&pThis->VPCI, pHlp); +} + + +/* -=-=-=-=- Saved state -=-=-=-=- */ + +/** + * Saves the configuration. + * + * @param pDevIns The device instance. + * @param pThis The VNET state. + * @param pSSM The handle to the saved state. + */ +static void vnetR3SaveConfig(PPDMDEVINS pDevIns, PVNETSTATE pThis, PSSMHANDLE pSSM) +{ + pDevIns->pHlpR3->pfnSSMPutMem(pSSM, &pThis->macConfigured, sizeof(pThis->macConfigured)); +} + + +/** + * @callback_method_impl{FNSSMDEVLIVEEXEC} + */ +static DECLCALLBACK(int) vnetR3LiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + RT_NOREF(uPass); + vnetR3SaveConfig(pDevIns, pThis, pSSM); + return VINF_SSM_DONT_CALL_AGAIN; +} + + +/** + * @callback_method_impl{FNSSMDEVSAVEPREP} + */ +static DECLCALLBACK(int) vnetR3SavePrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + RT_NOREF(pSSM); + + int rc = vnetCsRxEnter(pThis, VERR_SEM_BUSY); + if (RT_SUCCESS(rc)) + vnetCsRxLeave(pThis); + return rc; +} + + +/** + * @callback_method_impl{FNSSMDEVSAVEEXEC} + */ +static DECLCALLBACK(int) vnetR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + + /* Save config first */ + vnetR3SaveConfig(pDevIns, pThis, pSSM); + + /* Save the common part */ + int rc = vpciR3SaveExec(pDevIns, pHlp, &pThis->VPCI, pSSM); + AssertRCReturn(rc, rc); + + /* Save device-specific part */ + pHlp->pfnSSMPutMem( pSSM, pThis->config.mac.au8, sizeof(pThis->config.mac)); + pHlp->pfnSSMPutBool( pSSM, pThis->fPromiscuous); + pHlp->pfnSSMPutBool( pSSM, pThis->fAllMulti); + pHlp->pfnSSMPutU32( pSSM, pThis->cMacFilterEntries); + pHlp->pfnSSMPutMem( pSSM, pThis->aMacFilter, pThis->cMacFilterEntries * sizeof(RTMAC)); + rc = pHlp->pfnSSMPutMem(pSSM, pThis->aVlanFilter, sizeof(pThis->aVlanFilter)); + AssertRCReturn(rc, rc); + + Log(("%s State has been saved\n", INSTANCE(pThis))); + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{FNSSMDEVLOADPREP, + * Serializes the receive thread - it may be working inside the critsect. } + */ +static DECLCALLBACK(int) vnetR3LoadPrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + RT_NOREF(pSSM); + + int rc = vnetCsRxEnter(pThis, VERR_SEM_BUSY); + if (RT_SUCCESS(rc)) + vnetCsRxLeave(pThis); + return rc; +} + + +/** + * @callback_method_impl{FNSSMDEVLOADEXEC} + */ +static DECLCALLBACK(int) vnetR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + PVNETSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVNETSTATECC); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + + /* config checks */ + RTMAC macConfigured; + int rc = pHlp->pfnSSMGetMem(pSSM, &macConfigured, sizeof(macConfigured)); + AssertRCReturn(rc, rc); + if (memcmp(&macConfigured, &pThis->macConfigured, sizeof(macConfigured)) + && (uPass == 0 || !PDMDevHlpVMTeleportedAndNotFullyResumedYet(pDevIns))) + LogRel(("%s: The mac address differs: config=%RTmac saved=%RTmac\n", INSTANCE(pThis), &pThis->macConfigured, &macConfigured)); + + rc = vpciR3LoadExec(pDevIns, pHlp, &pThis->VPCI, pSSM, uVersion, uPass, VNET_N_QUEUES); + AssertRCReturn(rc, rc); + + if (uPass == SSM_PASS_FINAL) + { + rc = pHlp->pfnSSMGetMem( pSSM, pThis->config.mac.au8, sizeof(pThis->config.mac)); + AssertRCReturn(rc, rc); + + if (uVersion > VIRTIO_SAVEDSTATE_VERSION_3_1_BETA1) + { + pHlp->pfnSSMGetBool(pSSM, &pThis->fPromiscuous); + pHlp->pfnSSMGetBool(pSSM, &pThis->fAllMulti); + pHlp->pfnSSMGetU32(pSSM, &pThis->cMacFilterEntries); + pHlp->pfnSSMGetMem(pSSM, pThis->aMacFilter, pThis->cMacFilterEntries * sizeof(RTMAC)); + + /* Clear the rest. */ + if (pThis->cMacFilterEntries < VNET_MAC_FILTER_LEN) + memset(&pThis->aMacFilter[pThis->cMacFilterEntries], + 0, + (VNET_MAC_FILTER_LEN - pThis->cMacFilterEntries) * sizeof(RTMAC)); + rc = pHlp->pfnSSMGetMem(pSSM, pThis->aVlanFilter, sizeof(pThis->aVlanFilter)); + AssertRCReturn(rc, rc); + } + else + { + pThis->fPromiscuous = true; + pThis->fAllMulti = false; + pThis->cMacFilterEntries = 0; + memset(pThis->aMacFilter, 0, VNET_MAC_FILTER_LEN * sizeof(RTMAC)); + memset(pThis->aVlanFilter, 0, sizeof(pThis->aVlanFilter)); + if (pThisCC->pDrv) + pThisCC->pDrv->pfnSetPromiscuousMode(pThisCC->pDrv, true); + } + } + Log(("%s State has been restored\n", INSTANCE(pThis))); + + return rc; +} + + +/** + * @callback_method_impl{FNSSMDEVLOADDONE, Link status adjustments after + * loading.} + */ +static DECLCALLBACK(int) vnetR3LoadDone(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + PVNETSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVNETSTATECC); + RT_NOREF(pSSM); + + if (pThisCC->pDrv) + pThisCC->pDrv->pfnSetPromiscuousMode(pThisCC->pDrv, (pThis->fPromiscuous | pThis->fAllMulti)); + /* + * Indicate link down to the guest OS that all network connections have + * been lost, unless we've been teleported here. + */ + if (!PDMDevHlpVMTeleportedAndNotFullyResumedYet(pDevIns)) + { + /* + * Since we do not restore the link state, we pretend it is up, so it will be + * lowered by vnetR3TempLinkDown, and the guest will be notified. The actual state + * of the link will be determined later by vnetR3IsConnected in vnetR3LinkUpTimer. + * See @bugref{10096}. + */ + pThis->config.uStatus |= VNET_S_LINK_UP; + vnetR3TempLinkDown(pDevIns, pThis, pThisCC); + } + + return VINF_SUCCESS; +} + + +/* -=-=-=-=- PDMDEVREG -=-=-=-=- */ + +/** + * @interface_method_impl{PDMDEVREGR3,pfnDetach} + */ +static DECLCALLBACK(void) vnetR3Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + PVNETSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVNETSTATECC); + Log(("%s vnetR3Detach:\n", INSTANCE(pThis))); + RT_NOREF(fFlags); + + AssertLogRelReturnVoid(iLUN == 0); + + VNET_R3_CS_ENTER_RETURN_VOID(pDevIns, pThis); + + vnetR3DestroyTxThreadAndEvent(pDevIns, pThis, pThisCC); + + /* + * Zero important members. + */ + pThisCC->pDrvBase = NULL; + pThisCC->pDrv = NULL; + + vnetR3CsLeave(pDevIns, pThis); +} + + +/** + * @interface_method_impl{PDMDEVREGR3,pfnAttach} + */ +static DECLCALLBACK(int) vnetR3Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + PVNETSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVNETSTATECC); + RT_NOREF(fFlags); + LogFlow(("%s vnetR3Attach:\n", INSTANCE(pThis))); + + AssertLogRelReturn(iLUN == 0, VERR_PDM_NO_SUCH_LUN); + + int rc = vnetR3CsEnter(pDevIns, pThis, VERR_SEM_BUSY); + AssertRCReturn(rc, rc); + + /* + * Attach the driver. + */ + rc = PDMDevHlpDriverAttach(pDevIns, 0, &pThisCC->VPCI.IBase, &pThisCC->pDrvBase, "Network Port"); + if (RT_SUCCESS(rc)) + { + pThisCC->pDrv = PDMIBASE_QUERY_INTERFACE(pThisCC->pDrvBase, PDMINETWORKUP); + AssertMsgStmt(pThisCC->pDrv, ("Failed to obtain the PDMINETWORKUP interface!\n"), + rc = VERR_PDM_MISSING_INTERFACE_BELOW); + + vnetR3CreateTxThreadAndEvent(pDevIns, pThis, pThisCC); + } + else if ( rc == VERR_PDM_NO_ATTACHED_DRIVER + || rc == VERR_PDM_CFG_MISSING_DRIVER_NAME) + { + /* This should never happen because this function is not called + * if there is no driver to attach! */ + Log(("%s No attached driver!\n", INSTANCE(pThis))); + } + + /* + * Temporary set the link down if it was up so that the guest + * will know that we have change the configuration of the + * network card + */ + if (RT_SUCCESS(rc)) + vnetR3TempLinkDown(pDevIns, pThis, pThisCC); + + vnetR3CsLeave(pDevIns, pThis); + return rc; +} + + +/** + * @interface_method_impl{PDMDEVREGR3,pfnSuspend} + */ +static DECLCALLBACK(void) vnetR3Suspend(PPDMDEVINS pDevIns) +{ + /* Poke thread waiting for buffer space. */ + vnetWakeupReceive(pDevIns); +} + + +/** + * @interface_method_impl{PDMDEVREGR3,pfnPowerOff} + */ +static DECLCALLBACK(void) vnetR3PowerOff(PPDMDEVINS pDevIns) +{ + /* Poke thread waiting for buffer space. */ + vnetWakeupReceive(pDevIns); +} + + +/** + * @interface_method_impl{PDMDEVREGR3,pfnDestruct} + */ +static DECLCALLBACK(int) vnetR3Destruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + +# ifdef VNET_TX_DELAY + LogRel(("TxTimer stats (avg/min/max): %7d usec %7d usec %7d usec\n", pThis->u32AvgDiff, pThis->u32MinDiff, pThis->u32MaxDiff)); +# endif + + Log(("%s Destroying instance\n", INSTANCE(pThis))); + if (pThis->hEventMoreRxDescAvail != NIL_SUPSEMEVENT) + { + PDMDevHlpSUPSemEventSignal(pDevIns, pThis->hEventMoreRxDescAvail); + PDMDevHlpSUPSemEventClose(pDevIns, pThis->hEventMoreRxDescAvail); + pThis->hEventMoreRxDescAvail = NIL_SUPSEMEVENT; + } + + // if (PDMCritSectIsInitialized(&pThis->csRx)) + // PDMR3CritSectDelete(&pThis->csRx); + + return vpciR3Term(pDevIns, &pThis->VPCI); +} + + +/** + * @interface_method_impl{PDMDEVREGR3,pfnConstruct} + */ +static DECLCALLBACK(int) vnetR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + PVNETSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVNETSTATECC); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + int rc; + + /* + * Initialize the instance data suffiencently for the destructor not to blow up. + */ + RTStrPrintf(pThis->VPCI.szInstance, sizeof(pThis->VPCI.szInstance), "VNet%d", iInstance); + pThisCC->pDevIns = pDevIns; + pThis->hEventMoreRxDescAvail = NIL_SUPSEMEVENT; +# ifndef VNET_TX_DELAY + pThis->hTxEvent = NIL_SUPSEMEVENT; + pThisCC->pTxThread = NULL; +# endif + + /* Initialize state structure */ + pThis->u32PktNo = 1; + + /* Interfaces */ + pThisCC->INetworkDown.pfnWaitReceiveAvail = vnetR3NetworkDown_WaitReceiveAvail; + pThisCC->INetworkDown.pfnReceive = vnetR3NetworkDown_Receive; + pThisCC->INetworkDown.pfnReceiveGso = vnetR3NetworkDown_ReceiveGso; + pThisCC->INetworkDown.pfnXmitPending = vnetR3NetworkDown_XmitPending; + + pThisCC->INetworkConfig.pfnGetMac = vnetR3NetworkConfig_GetMac; + pThisCC->INetworkConfig.pfnGetLinkState = vnetR3NetworkConfig_GetLinkState; + pThisCC->INetworkConfig.pfnSetLinkState = vnetR3NetworkConfig_SetLinkState; + + + /* Do our own locking. */ + rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns)); + AssertRCReturn(rc, rc); + + /* + * Initialize VPCI part. + */ + pThisCC->VPCI.IBase.pfnQueryInterface = vnetQueryInterface; + + rc = vpciR3Init(pDevIns, &pThis->VPCI, &pThisCC->VPCI, VIRTIO_NET_ID, VNET_PCI_CLASS, VNET_N_QUEUES); + AssertRCReturn(rc, rc); + + pThisCC->pRxQueue = vpciR3AddQueue(&pThis->VPCI, &pThisCC->VPCI, 256, vnetR3QueueReceive, "RX "); + pThisCC->pTxQueue = vpciR3AddQueue(&pThis->VPCI, &pThisCC->VPCI, 256, vnetR3QueueTransmit, "TX "); + pThisCC->pCtlQueue = vpciR3AddQueue(&pThis->VPCI, &pThisCC->VPCI, 16, vnetR3QueueControl, "CTL"); + AssertLogRelReturn(pThisCC->pCtlQueue && pThisCC->pTxQueue && pThisCC->pRxQueue, VERR_INTERNAL_ERROR_5); + + Log(("%s Constructing new instance\n", INSTANCE(pThis))); + + /* + * Validate configuration. + */ + PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "MAC|CableConnected|LineSpeed|LinkUpDelay|StatNo", ""); + + /* Get config params */ + rc = pHlp->pfnCFGMQueryBytes(pCfg, "MAC", pThis->macConfigured.au8, sizeof(pThis->macConfigured)); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Configuration error: Failed to get MAC address")); + + rc = pHlp->pfnCFGMQueryBool(pCfg, "CableConnected", &pThis->fCableConnected); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Configuration error: Failed to get the value of 'CableConnected'")); + + rc = pHlp->pfnCFGMQueryU32Def(pCfg, "LinkUpDelay", &pThis->cMsLinkUpDelay, 5000); /* ms */ + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Configuration error: Failed to get the value of 'LinkUpDelay'")); + Assert(pThis->cMsLinkUpDelay <= 300000); /* less than 5 minutes */ + if (pThis->cMsLinkUpDelay > 5000 || pThis->cMsLinkUpDelay < 100) + LogRel(("%s WARNING! Link up delay is set to %u seconds!\n", INSTANCE(pThis), pThis->cMsLinkUpDelay / 1000)); + Log(("%s Link up delay is set to %u seconds\n", INSTANCE(pThis), pThis->cMsLinkUpDelay / 1000)); + + uint32_t uStatNo = iInstance; + rc = pHlp->pfnCFGMQueryU32Def(pCfg, "StatNo", &uStatNo, iInstance); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Configuration error: Failed to get the \"StatNo\" value")); + + vnetPrintFeatures(pThis, vnetIoCb_GetHostFeatures(&pThis->VPCI), "Device supports the following features"); + + /* Initialize PCI config space */ + memcpy(pThis->config.mac.au8, pThis->macConfigured.au8, sizeof(pThis->config.mac.au8)); + pThis->config.uStatus = 0; + + /* Initialize critical section. */ + // char szTmp[sizeof(pThis->VPCI.szInstance) + 2]; + // RTStrPrintf(szTmp, sizeof(szTmp), "%sRX", pThis->VPCI.szInstance); + // rc = PDMDevHlpCritSectInit(pDevIns, &pThis->csRx, szTmp); + // if (RT_FAILURE(rc)) + // return rc; + + /* Map our ports to IO space. */ + rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, 0 /*iPciReion*/, VPCI_CONFIG + sizeof(VNetPCIConfig), + vnetIOPortOut, vnetIOPortIn, NULL /*pvUser*/, "VirtioNet", NULL /*paExtDescs*/, + &pThis->hIoPorts); + AssertRCReturn(rc, rc); + + /* Register save/restore state handlers. */ + rc = PDMDevHlpSSMRegisterEx(pDevIns, VIRTIO_SAVEDSTATE_VERSION, sizeof(VNETSTATE), NULL, + NULL, vnetR3LiveExec, NULL, + vnetR3SavePrep, vnetR3SaveExec, NULL, + vnetR3LoadPrep, vnetR3LoadExec, vnetR3LoadDone); + AssertRCReturn(rc, rc); + + /* Create Link Up Timer */ + rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, vnetR3LinkUpTimer, NULL, + TMTIMER_FLAGS_NO_CRIT_SECT | TMTIMER_FLAGS_NO_RING0, + "VirtioNet Link Up", &pThisCC->hLinkUpTimer); + AssertRCReturn(rc, rc); + +# ifdef VNET_TX_DELAY + /* Create Transmit Delay Timer */ + rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, vnetR3TxTimer, pThis, + TMTIMER_FLAGS_NO_CRIT_SECT | TMTIMER_FLAGS_NO_RING0, + "VirtioNet TX Delay", &pThis->hTxTimer); + AssertRCReturn(rc, rc); + + pThis->u32i = pThis->u32AvgDiff = pThis->u32MaxDiff = 0; + pThis->u32MinDiff = UINT32_MAX; +# endif /* VNET_TX_DELAY */ + + rc = PDMDevHlpDriverAttach(pDevIns, 0, &pThisCC->VPCI.IBase, &pThisCC->pDrvBase, "Network Port"); + if (RT_SUCCESS(rc)) + { + pThisCC->pDrv = PDMIBASE_QUERY_INTERFACE(pThisCC->pDrvBase, PDMINETWORKUP); + AssertMsgReturn(pThisCC->pDrv, ("Failed to obtain the PDMINETWORKUP interface!\n"), + VERR_PDM_MISSING_INTERFACE_BELOW); + + vnetR3CreateTxThreadAndEvent(pDevIns, pThis, pThisCC); + } + else if ( rc == VERR_PDM_NO_ATTACHED_DRIVER + || rc == VERR_PDM_CFG_MISSING_DRIVER_NAME ) + { + /* No error! */ + Log(("%s This adapter is not attached to any network!\n", INSTANCE(pThis))); + } + else + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to attach the network LUN")); + + rc = PDMDevHlpSUPSemEventCreate(pDevIns, &pThis->hEventMoreRxDescAvail); + AssertRCReturn(rc, rc); + + ASMCompilerBarrier(); /* paranoia */ + rc = vnetIoCb_Reset(pDevIns); + AssertRCReturn(rc, rc); + + /* + * Statistics and debug stuff. + * The /Public/ bits are official and used by session info in the GUI. + */ + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatReceiveBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, + "Amount of data received", "/Public/NetAdapter/%u/BytesReceived", uStatNo); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatTransmitBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, + "Amount of data transmitted", "/Public/NetAdapter/%u/BytesTransmitted", uStatNo); + PDMDevHlpSTAMRegisterF(pDevIns, &pDevIns->iInstance, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE, + "Device instance number", "/Public/NetAdapter/%u/%s", uStatNo, pDevIns->pReg->szName); + + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatReceiveBytes, STAMTYPE_COUNTER, "ReceiveBytes", STAMUNIT_BYTES, "Amount of data received"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTransmitBytes, STAMTYPE_COUNTER, "TransmitBytes", STAMUNIT_BYTES, "Amount of data transmitted"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatReceiveGSO, STAMTYPE_COUNTER, "Packets/ReceiveGSO", STAMUNIT_COUNT, "Number of received GSO packets"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTransmitPackets, STAMTYPE_COUNTER, "Packets/Transmit", STAMUNIT_COUNT, "Number of sent packets"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTransmitGSO, STAMTYPE_COUNTER, "Packets/Transmit-Gso", STAMUNIT_COUNT, "Number of sent GSO packets"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTransmitCSum, STAMTYPE_COUNTER, "Packets/Transmit-Csum", STAMUNIT_COUNT, "Number of completed TX checksums"); +# ifdef VBOX_WITH_STATISTICS + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatReceive, STAMTYPE_PROFILE, "Receive/Total", STAMUNIT_TICKS_PER_CALL, "Profiling receive"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatReceiveStore, STAMTYPE_PROFILE, "Receive/Store", STAMUNIT_TICKS_PER_CALL, "Profiling receive storing"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRxOverflow, STAMTYPE_PROFILE, "RxOverflow", STAMUNIT_TICKS_PER_OCCURENCE, "Profiling RX overflows"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRxOverflowWakeup, STAMTYPE_COUNTER, "RxOverflowWakeup", STAMUNIT_OCCURENCES, "Nr of RX overflow wakeups"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTransmit, STAMTYPE_PROFILE, "Transmit/Total", STAMUNIT_TICKS_PER_CALL, "Profiling transmits in HC"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTransmitSend, STAMTYPE_PROFILE, "Transmit/Send", STAMUNIT_TICKS_PER_CALL, "Profiling send transmit in HC"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTransmitByNetwork, STAMTYPE_COUNTER, "Transmit/ByNetwork", STAMUNIT_COUNT, "Network-initiated transmissions"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTransmitByThread, STAMTYPE_COUNTER, "Transmit/ByThread", STAMUNIT_COUNT, "Thread-initiated transmissions"); +# endif + + char szInfo[16]; + RTStrPrintf(szInfo, sizeof(szInfo), pDevIns->iInstance ? "vionet%u" : "vionet", pDevIns->iInstance); + PDMDevHlpDBGFInfoRegisterArgv(pDevIns, szInfo, "virtio-net info", vnetR3Info); + + return VINF_SUCCESS; +} + +#else /* !IN_RING3 */ + +/** + * @callback_method_impl{PDMDEVREGR0,pfnConstruct} + */ +static DECLCALLBACK(int) vnetRZConstruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + PVNETSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVNETSTATECC); + + int rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns)); + AssertRCReturn(rc, rc); + + rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPorts, vnetIOPortOut, vnetIOPortIn, NULL /*pvUser*/); + AssertRCReturn(rc, rc); + + return vpciRZInit(pDevIns, &pThis->VPCI, &pThisCC->VPCI); +} + +#endif /* !IN_RING3 */ + +/** + * The device registration structure. + */ +const PDMDEVREG g_DeviceVirtioNet = +{ + /* .u32version = */ PDM_DEVREG_VERSION, + /* .uReserved0 = */ 0, + /* .szName = */ "virtio-net", + /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE, + /* .fClass = */ PDM_DEVREG_CLASS_NETWORK, + /* .cMaxInstances = */ ~0U, + /* .uSharedVersion = */ 42, + /* .cbInstanceShared = */ sizeof(VNETSTATE), + /* .cbInstanceCC = */ sizeof(VNETSTATECC), + /* .cbInstanceRC = */ sizeof(VNETSTATERC), + /* .cMaxPciDevices = */ 1, + /* .cMaxMsixVectors = */ 0, + /* .pszDescription = */ "Virtio Ethernet.\n", +#if defined(IN_RING3) + /* .pszRCMod = */ "VBoxDDRC.rc", + /* .pszR0Mod = */ "VBoxDDR0.r0", + /* .pfnConstruct = */ vnetR3Construct, + /* .pfnDestruct = */ vnetR3Destruct, + /* .pfnRelocate = */ NULL, + /* .pfnMemSetup = */ NULL, + /* .pfnPowerOn = */ NULL, + /* .pfnReset = */ NULL, + /* .pfnSuspend = */ vnetR3Suspend, + /* .pfnResume = */ NULL, + /* .pfnAttach = */ vnetR3Attach, + /* .pfnDetach = */ vnetR3Detach, + /* .pfnQueryInterface = */ NULL, + /* .pfnInitComplete = */ NULL, + /* .pfnPowerOff = */ vnetR3PowerOff, + /* .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 = */ vnetRZConstruct, + /* .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 = */ vnetRZConstruct, + /* .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 +}; + +#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ |