/** $Id: DevE1000Phy.cpp 98172 2023-01-21 13:01:48Z vboxsync $ */ /** @file * DevE1000Phy - Intel 82540EM Ethernet Controller Internal PHY Emulation. * * Implemented in accordance with the specification: * PCI/PCI-X Family of Gigabit Ethernet Controllers Software Developer's * Manual 82540EP/EM, 82541xx, 82544GC/EI, 82545GM/EM, 82546GB/EB, and * 82547xx * * 317453-002 Revision 3.5 */ /* * Copyright (C) 2007-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 . * * SPDX-License-Identifier: GPL-3.0-only */ #define LOG_GROUP LOG_GROUP_DEV_E1000 /** @todo Remove me! For now I want asserts to work in release code. */ // #ifndef RT_STRICT // #define RT_STRICT #include // #undef RT_STRICT // #endif #include #include #ifdef IN_RING3 # include #endif #include "DevE1000Phy.h" /* Little helpers ************************************************************/ #ifdef PHY_UNIT_TEST # ifdef CPP_UNIT # include # define PhyLog(a) printf a # else # include # define PhyLogC99(...) RTTestIPrintf(RTTESTLVL_ALWAYS, __VA_ARGS__) # define PhyLog(a) PhyLogC99 a # endif #else /* !PHY_UNIT_TEST */ # define PhyLog(a) Log(a) #endif /* !PHY_UNIT_TEST */ #define REG(x) pPhy->au16Regs[x##_IDX] /* Internals */ namespace Phy { #if defined(LOG_ENABLED) || defined(PHY_UNIT_TEST) /** Retrieves state name by id */ static const char * getStateName(uint16_t u16State); #endif /** Look up register index by address. */ static int lookupRegister(uint32_t u32Address); /** Software-triggered reset. */ static void softReset(PPHY pPhy, PPDMDEVINS pDevIns); /** Read callback. */ typedef uint16_t FNREAD(PPHY pPhy, uint32_t index, PPDMDEVINS pDevIns); /** Write callback. */ typedef void FNWRITE(PPHY pPhy, uint32_t index, uint16_t u16Value, PPDMDEVINS pDevIns); /** @name Generic handlers * @{ */ static FNREAD regReadDefault; static FNWRITE regWriteDefault; static FNREAD regReadForbidden; static FNWRITE regWriteForbidden; static FNREAD regReadUnimplemented; static FNWRITE regWriteUnimplemented; /** @} */ /** @name Register-specific handlers * @{ */ static FNWRITE regWritePCTRL; static FNREAD regReadPSTATUS; static FNREAD regReadGSTATUS; /** @} */ /** * PHY register map table. * * Override pfnRead and pfnWrite to implement register-specific behavior. */ static struct RegMap_st { /** PHY register address. */ uint32_t u32Address; /** Read callback. */ FNREAD *pfnRead; /** Write callback. */ FNWRITE *pfnWrite; /** Abbreviated name. */ const char *pszAbbrev; /** Full name. */ const char *pszName; } s_regMap[NUM_OF_PHY_REGS] = { /*ra read callback write callback abbrev full name */ /*-- ------------------------- -------------------------- ---------- ------------------------------*/ { 0, Phy::regReadDefault , Phy::regWritePCTRL , "PCTRL" , "PHY Control" }, { 1, Phy::regReadPSTATUS , Phy::regWriteForbidden , "PSTATUS" , "PHY Status" }, { 2, Phy::regReadDefault , Phy::regWriteForbidden , "PID" , "PHY Identifier" }, { 3, Phy::regReadDefault , Phy::regWriteForbidden , "EPID" , "Extended PHY Identifier" }, { 4, Phy::regReadDefault , Phy::regWriteDefault , "ANA" , "Auto-Negotiation Advertisement" }, { 5, Phy::regReadDefault , Phy::regWriteForbidden , "LPA" , "Link Partner Ability" }, { 6, Phy::regReadUnimplemented, Phy::regWriteForbidden , "ANE" , "Auto-Negotiation Expansion" }, { 7, Phy::regReadUnimplemented, Phy::regWriteUnimplemented, "NPT" , "Next Page Transmit" }, { 8, Phy::regReadUnimplemented, Phy::regWriteForbidden , "LPN" , "Link Partner Next Page" }, { 9, Phy::regReadDefault , Phy::regWriteUnimplemented, "GCON" , "1000BASE-T Control" }, { 10, Phy::regReadGSTATUS , Phy::regWriteForbidden , "GSTATUS" , "1000BASE-T Status" }, { 15, Phy::regReadUnimplemented, Phy::regWriteForbidden , "EPSTATUS" , "Extended PHY Status" }, { 16, Phy::regReadDefault , Phy::regWriteDefault , "PSCON" , "PHY Specific Control" }, { 17, Phy::regReadDefault , Phy::regWriteForbidden , "PSSTAT" , "PHY Specific Status" }, { 18, Phy::regReadUnimplemented, Phy::regWriteUnimplemented, "PINTE" , "PHY Interrupt Enable" }, { 19, Phy::regReadUnimplemented, Phy::regWriteForbidden , "PINTS" , "PHY Interrupt Status" }, { 20, Phy::regReadUnimplemented, Phy::regWriteUnimplemented, "EPSCON1" , "Extended PHY Specific Control 1" }, { 21, Phy::regReadUnimplemented, Phy::regWriteForbidden , "PREC" , "PHY Receive Error Counter" }, { 26, Phy::regReadUnimplemented, Phy::regWriteUnimplemented, "EPSCON2" , "Extended PHY Specific Control 2" }, { 29, Phy::regReadForbidden , Phy::regWriteUnimplemented, "R30PS" , "MDI Register 30 Page Select" }, { 30, Phy::regReadUnimplemented, Phy::regWriteUnimplemented, "R30AW" , "MDI Register 30 Access Window" } }; } /** * Default read handler. * * Fetches register value from the state structure. * * @returns Register value * * @param index Register index in register array. */ static uint16_t Phy::regReadDefault(PPHY pPhy, uint32_t index, PPDMDEVINS pDevIns) { RT_NOREF(pDevIns); AssertReturn(indexau16Regs[index]; } /** * Default write handler. * * Writes the specified register value to the state structure. * * @param index Register index in register array. * @param value The value to store (ignored). */ static void Phy::regWriteDefault(PPHY pPhy, uint32_t index, uint16_t u16Value, PPDMDEVINS pDevIns) { RT_NOREF(pDevIns); AssertReturnVoid(index < NUM_OF_PHY_REGS); pPhy->au16Regs[index] = u16Value; } /** * Read handler for write-only registers. * * Merely reports reads from write-only registers. * * @returns Register value (always 0) * * @param index Register index in register array. */ static uint16_t Phy::regReadForbidden(PPHY pPhy, uint32_t index, PPDMDEVINS pDevIns) { RT_NOREF(pPhy, index, pDevIns); PhyLog(("PHY#%d At %02d read attempted from write-only '%s'\n", pPhy->iInstance, s_regMap[index].u32Address, s_regMap[index].pszName)); return 0; } /** * Write handler for read-only registers. * * Merely reports writes to read-only registers. * * @param index Register index in register array. * @param value The value to store (ignored). */ static void Phy::regWriteForbidden(PPHY pPhy, uint32_t index, uint16_t u16Value, PPDMDEVINS pDevIns) { RT_NOREF(pPhy, index, u16Value, pDevIns); PhyLog(("PHY#%d At %02d write attempted to read-only '%s'\n", pPhy->iInstance, s_regMap[index].u32Address, s_regMap[index].pszName)); } /** * Read handler for unimplemented registers. * * Merely reports reads from unimplemented registers. * * @returns Register value (always 0) * * @param index Register index in register array. */ static uint16_t Phy::regReadUnimplemented(PPHY pPhy, uint32_t index, PPDMDEVINS pDevIns) { RT_NOREF(pPhy, index, pDevIns); PhyLog(("PHY#%d At %02d read attempted from unimplemented '%s'\n", pPhy->iInstance, s_regMap[index].u32Address, s_regMap[index].pszName)); return 0; } /** * Write handler for unimplemented registers. * * Merely reports writes to unimplemented registers. * * @param index Register index in register array. * @param value The value to store (ignored). */ static void Phy::regWriteUnimplemented(PPHY pPhy, uint32_t index, uint16_t u16Value, PPDMDEVINS pDevIns) { RT_NOREF(pPhy, index, u16Value, pDevIns); PhyLog(("PHY#%d At %02d write attempted to unimplemented '%s'\n", pPhy->iInstance, s_regMap[index].u32Address, s_regMap[index].pszName)); } /** * Search PHY register table for register with matching address. * * @returns Index in the register table or -1 if not found. * * @param u32Address Register address. */ static int Phy::lookupRegister(uint32_t u32Address) { unsigned int index; for (index = 0; index < RT_ELEMENTS(s_regMap); index++) { if (s_regMap[index].u32Address == u32Address) { return (int)index; } } return -1; } /** * Read PHY register. * * @returns Value of specified PHY register. * * @param u32Address Register address. */ uint16_t Phy::readRegister(PPHY pPhy, uint32_t u32Address, PPDMDEVINS pDevIns) { int index = Phy::lookupRegister(u32Address); uint16_t u16 = 0; if (index >= 0) { u16 = s_regMap[index].pfnRead(pPhy, (uint32_t)index, pDevIns); PhyLog(("PHY#%d At %02d read %04X from %s (%s)\n", pPhy->iInstance, s_regMap[index].u32Address, u16, s_regMap[index].pszAbbrev, s_regMap[index].pszName)); } else { PhyLog(("PHY#%d read attempted from non-existing register %08x\n", pPhy->iInstance, u32Address)); } return u16; } /** * Write to PHY register. * * @param u32Address Register address. * @param u16Value Value to store. */ void Phy::writeRegister(PPHY pPhy, uint32_t u32Address, uint16_t u16Value, PPDMDEVINS pDevIns) { int index = Phy::lookupRegister(u32Address); if (index >= 0) { PhyLog(("PHY#%d At %02d write %04X to %s (%s)\n", pPhy->iInstance, s_regMap[index].u32Address, u16Value, s_regMap[index].pszAbbrev, s_regMap[index].pszName)); s_regMap[index].pfnWrite(pPhy, (uint32_t)index, u16Value, pDevIns); } else { PhyLog(("PHY#%d write attempted to non-existing register %08x\n", pPhy->iInstance, u32Address)); } } /** * PHY constructor. * * Stores E1000 instance internally. Triggers PHY hard reset. * * @param iNICInstance Number of network controller instance this PHY is * attached to. * @param u16EPid Extended PHY Id. */ void Phy::init(PPHY pPhy, int iNICInstance, uint16_t u16EPid) { pPhy->iInstance = iNICInstance; /* The PHY identifier composed of bits 3 through 18 of the OUI */ /* (Organizationally Unique Identifier). OUI is 0x05043. */ REG(PID) = 0x0141; /* Extended PHY identifier */ REG(EPID) = u16EPid; hardReset(pPhy); } /** * Hardware PHY reset. * * Sets all PHY registers to their initial values. */ void Phy::hardReset(PPHY pPhy) { PhyLog(("PHY#%d Hard reset\n", pPhy->iInstance)); REG(PCTRL) = PCTRL_SPDSELM | PCTRL_DUPMOD | PCTRL_ANEG; /* * 100 and 10 FD/HD, Extended Status, MF Preamble Suppression, * AUTO NEG AB, EXT CAP */ REG(PSTATUS) = 0x7949; REG(ANA) = 0x01E1; /* No flow control by our link partner, all speeds */ REG(LPA) = 0x01E0; REG(ANE) = 0x0000; REG(NPT) = 0x2001; REG(LPN) = 0x0000; REG(GCON) = 0x1E00; REG(GSTATUS) = 0x0000; REG(EPSTATUS) = 0x3000; REG(PSCON) = 0x0068; REG(PSSTAT) = 0x0000; REG(PINTE) = 0x0000; REG(PINTS) = 0x0000; REG(EPSCON1) = 0x0D60; REG(PREC) = 0x0000; REG(EPSCON2) = 0x000C; REG(R30PS) = 0x0000; REG(R30AW) = 0x0000; pPhy->u16State = MDIO_IDLE; } /** * Software PHY reset. */ static void Phy::softReset(PPHY pPhy, PPDMDEVINS pDevIns) { PhyLog(("PHY#%d Soft reset\n", pPhy->iInstance)); REG(PCTRL) = REG(PCTRL) & (PCTRL_SPDSELM | PCTRL_DUPMOD | PCTRL_ANEG | PCTRL_SPDSELL); /* * 100 and 10 FD/HD, Extended Status, MF Preamble Suppression, * AUTO NEG AB, EXT CAP */ REG(PSTATUS) = 0x7949; REG(PSSTAT) &= 0xe001; PhyLog(("PHY#%d PSTATUS=%04x PSSTAT=%04x\n", pPhy->iInstance, REG(PSTATUS), REG(PSSTAT))); #ifndef PHY_UNIT_TEST e1kPhyLinkResetCallback(pDevIns); #else RT_NOREF(pDevIns); #endif } /** * Get the current state of the link. * * @returns true if link is up. */ bool Phy::isLinkUp(PPHY pPhy) { return (REG(PSSTAT) & PSSTAT_LINK) != 0; } /** * Set the current state of the link. * * @remarks Link Status bit in PHY Status register is latched-low and does * not change the state when the link goes up. * * @param fLinkIsUp New state of the link. */ void Phy::setLinkStatus(PPHY pPhy, bool fLinkIsUp) { if (fLinkIsUp) { REG(PSSTAT) |= PSSTAT_LINK_ALL; REG(PSTATUS) |= PSTATUS_NEGCOMP; /* PSTATUS_LNKSTAT is latched low */ } else { REG(PSSTAT) &= ~PSSTAT_LINK_ALL; REG(PSTATUS) &= ~(PSTATUS_LNKSTAT | PSTATUS_NEGCOMP); } PhyLog(("PHY#%d setLinkStatus: PSTATUS=%04x PSSTAT=%04x\n", pPhy->iInstance, REG(PSTATUS), REG(PSSTAT))); } #ifdef IN_RING3 /** * Save PHY state. * * @remarks Since PHY is aggregated into E1K it does not currently supports * versioning of its own. * * @returns VBox status code. * @param pHlp Device helper table. * @param pSSM The handle to save the state to. * @param pPhy The pointer to this instance. */ int Phy::saveState(PCPDMDEVHLPR3 pHlp, PSSMHANDLE pSSM, PPHY pPhy) { pHlp->pfnSSMPutMem(pSSM, pPhy->au16Regs, sizeof(pPhy->au16Regs)); return VINF_SUCCESS; } /** * Restore previously saved PHY state. * * @remarks Since PHY is aggregated into E1K it does not currently supports * versioning of its own. * * @returns VBox status code. * @param pHlp Device helper table. * @param pSSM The handle to save the state to. * @param pPhy The pointer to this instance. */ int Phy::loadState(PCPDMDEVHLPR3 pHlp, PSSMHANDLE pSSM, PPHY pPhy) { return pHlp->pfnSSMGetMem(pSSM, pPhy->au16Regs, sizeof(pPhy->au16Regs)); } #endif /* IN_RING3 */ /* Register-specific handlers ************************************************/ /** * Write handler for PHY Control register. * * Handles reset. * * @param index Register index in register array. * @param value The value to store (ignored). */ static void Phy::regWritePCTRL(PPHY pPhy, uint32_t index, uint16_t u16Value, PPDMDEVINS pDevIns) { if (u16Value & PCTRL_RESET) softReset(pPhy, pDevIns); else regWriteDefault(pPhy, index, u16Value, pDevIns); } /** * Read handler for PHY Status register. * * Handles Latched-Low Link Status bit. * * @returns Register value * * @param index Register index in register array. */ static uint16_t Phy::regReadPSTATUS(PPHY pPhy, uint32_t index, PPDMDEVINS pDevIns) { RT_NOREF(pPhy, index, pDevIns); /* Read latched value */ uint16_t u16 = REG(PSTATUS); if (REG(PSSTAT) & PSSTAT_LINK) REG(PSTATUS) |= PSTATUS_LNKSTAT; else REG(PSTATUS) &= ~PSTATUS_LNKSTAT; return u16; } /** * Read handler for 1000BASE-T Status register. * * @returns Register value * * @param index Register index in register array. */ static uint16_t Phy::regReadGSTATUS(PPHY pPhy, uint32_t index, PPDMDEVINS pDevIns) { RT_NOREF(pPhy, index, pDevIns); /* * - Link partner is capable of 1000BASE-T half duplex * - Link partner is capable of 1000BASE-T full duplex * - Remote receiver OK * - Local receiver OK * - Local PHY config resolved to SLAVE */ return 0x3C00; } #if defined(LOG_ENABLED) || defined(PHY_UNIT_TEST) static const char * Phy::getStateName(uint16_t u16State) { static const char *pcszState[] = { "MDIO_IDLE", "MDIO_ST", "MDIO_OP_ADR", "MDIO_TA_RD", "MDIO_TA_WR", "MDIO_READ", "MDIO_WRITE" }; return (u16State < RT_ELEMENTS(pcszState)) ? pcszState[u16State] : ""; } #endif bool Phy::readMDIO(PPHY pPhy) { bool fPin = false; switch (pPhy->u16State) { case MDIO_TA_RD: Assert(pPhy->u16Cnt == 1); fPin = false; pPhy->u16State = MDIO_READ; pPhy->u16Cnt = 16; break; case MDIO_READ: /* Bits are shifted out in MSB to LSB order */ fPin = (pPhy->u16Acc & 0x8000) != 0; pPhy->u16Acc <<= 1; if (--pPhy->u16Cnt == 0) pPhy->u16State = MDIO_IDLE; break; default: PhyLog(("PHY#%d WARNING! MDIO pin read in %s state\n", pPhy->iInstance, Phy::getStateName(pPhy->u16State))); pPhy->u16State = MDIO_IDLE; } return fPin; } /** Set the value of MDIO pin. */ void Phy::writeMDIO(PPHY pPhy, bool fPin, PPDMDEVINS pDevIns) { switch (pPhy->u16State) { case MDIO_IDLE: if (!fPin) pPhy->u16State = MDIO_ST; break; case MDIO_ST: if (fPin) { pPhy->u16State = MDIO_OP_ADR; pPhy->u16Cnt = 12; /* OP + PHYADR + REGADR */ pPhy->u16Acc = 0; } break; case MDIO_OP_ADR: Assert(pPhy->u16Cnt); /* Shift in 'u16Cnt' bits into accumulator */ pPhy->u16Acc <<= 1; if (fPin) pPhy->u16Acc |= 1; if (--pPhy->u16Cnt == 0) { /* Got OP(2) + PHYADR(5) + REGADR(5) */ /* Note: A single PHY is supported, ignore PHYADR */ switch (pPhy->u16Acc >> 10) { case MDIO_READ_OP: pPhy->u16Acc = readRegister(pPhy, pPhy->u16Acc & 0x1F, pDevIns); pPhy->u16State = MDIO_TA_RD; pPhy->u16Cnt = 1; break; case MDIO_WRITE_OP: pPhy->u16RegAdr = pPhy->u16Acc & 0x1F; pPhy->u16State = MDIO_TA_WR; pPhy->u16Cnt = 2; break; default: PhyLog(("PHY#%d ERROR! Invalid MDIO op: %d\n", pPhy->iInstance, pPhy->u16Acc >> 10)); pPhy->u16State = MDIO_IDLE; break; } } break; case MDIO_TA_WR: Assert(pPhy->u16Cnt <= 2); Assert(pPhy->u16Cnt > 0); if (--pPhy->u16Cnt == 0) { pPhy->u16State = MDIO_WRITE; pPhy->u16Cnt = 16; } break; case MDIO_WRITE: Assert(pPhy->u16Cnt); pPhy->u16Acc <<= 1; if (fPin) pPhy->u16Acc |= 1; if (--pPhy->u16Cnt == 0) { writeRegister(pPhy, pPhy->u16RegAdr, pPhy->u16Acc, pDevIns); pPhy->u16State = MDIO_IDLE; } break; default: PhyLog(("PHY#%d ERROR! MDIO pin write in %s state\n", pPhy->iInstance, Phy::getStateName(pPhy->u16State))); pPhy->u16State = MDIO_IDLE; break; } }