From 43c68c22a0d621b40a344e3de0e1fc8b9454c391 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Fri, 20 Jun 2025 03:18:07 -0700 Subject: [PATCH] nvnet: Handle link status changes --- hw/xbox/mcpx/nvnet/nvnet.c | 173 ++++++++++++++++++++++++++++++++----- ui/xemu-net.c | 4 + 2 files changed, 156 insertions(+), 21 deletions(-) diff --git a/hw/xbox/mcpx/nvnet/nvnet.c b/hw/xbox/mcpx/nvnet/nvnet.c index 925668d5d2..f7870c0894 100644 --- a/hw/xbox/mcpx/nvnet/nvnet.c +++ b/hw/xbox/mcpx/nvnet/nvnet.c @@ -69,6 +69,8 @@ typedef struct NvNetState { uint8_t tx_dma_buf[TX_ALLOC_BUFSIZE]; uint8_t rx_dma_buf[RX_ALLOC_BUFSIZE]; + QEMUTimer *autoneg_timer; + /* Deprecated */ uint8_t tx_ring_index; uint8_t rx_ring_index; @@ -501,23 +503,103 @@ static ssize_t nvnet_receive(NetClientState *nc, const uint8_t *buf, return nvnet_receive_iov(nc, &iov, 1); } +static void nvnet_update_regs_on_link_down(NvNetState *s) +{ + s->phy_regs[MII_BMSR] &= ~MII_BMSR_LINK_ST; + s->phy_regs[MII_BMSR] &= ~MII_BMSR_AN_COMP; + s->phy_regs[MII_ANLPAR] &= ~MII_ANLPAR_ACK; + + uint32_t adapter_ctrl = nvnet_get_reg(s, NVNET_ADAPTER_CONTROL, 4); + adapter_ctrl &= ~NVNET_ADAPTER_CONTROL_LINKUP; + nvnet_set_reg(s, NVNET_ADAPTER_CONTROL, adapter_ctrl, 4); +} + static void nvnet_link_down(NvNetState *s) { NVNET_DPRINTF("nvnet_link_down called\n"); + + nvnet_update_regs_on_link_down(s); + + uint32_t mii_status = nvnet_get_reg(s, NVNET_MII_STATUS, 4); + mii_status |= NVNET_MII_STATUS_LINKCHANGE; + nvnet_set_reg(s, NVNET_MII_STATUS, mii_status, 4); + + uint32_t irq_status = nvnet_get_reg(s, NVNET_IRQ_STATUS, 4); + irq_status |= NVNET_IRQ_STATUS_MIIEVENT; + nvnet_set_reg(s, NVNET_IRQ_STATUS, irq_status, 4); + // FIXME: MII status mask? + + nvnet_update_irq(s); +} + +static void nvent_update_regs_on_link_up(NvNetState *s) +{ + s->phy_regs[MII_BMSR] |= MII_BMSR_LINK_ST; + + uint32_t adapter_ctrl = nvnet_get_reg(s, NVNET_ADAPTER_CONTROL, 4); + adapter_ctrl |= NVNET_ADAPTER_CONTROL_LINKUP; + nvnet_set_reg(s, NVNET_ADAPTER_CONTROL, adapter_ctrl, 4); } static void nvnet_link_up(NvNetState *s) { NVNET_DPRINTF("nvnet_link_up called\n"); + + nvent_update_regs_on_link_up(s); + + uint32_t mii_status = nvnet_get_reg(s, NVNET_MII_STATUS, 4); + mii_status |= NVNET_MII_STATUS_LINKCHANGE; + nvnet_set_reg(s, NVNET_MII_STATUS, mii_status, 4); + + uint32_t irq_status = nvnet_get_reg(s, NVNET_IRQ_STATUS, 4); + irq_status |= NVNET_IRQ_STATUS_MIIEVENT; + nvnet_set_reg(s, NVNET_IRQ_STATUS, irq_status, 4); + // FIXME: MII status mask? + + nvnet_update_irq(s); +} + +static void nvnet_restart_autoneg(NvNetState *s) +{ + timer_mod(s->autoneg_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 500); +} + +static void nvnet_autoneg_done(void *opaque) +{ + NvNetState *s = opaque; + + s->phy_regs[MII_ANLPAR] |= MII_ANLPAR_ACK; + s->phy_regs[MII_BMSR] |= MII_BMSR_AN_COMP; + + nvnet_link_up(s); +} + +static void nvnet_autoneg_timer(void *opaque) +{ + NvNetState *s = opaque; + + if (!qemu_get_queue(s->nic)->link_down) { + nvnet_autoneg_done(s); + } +} + +static bool have_autoneg(NvNetState *s) +{ + return (s->phy_regs[MII_BMCR] & MII_BMCR_AUTOEN); } static void nvnet_set_link_status(NetClientState *nc) { NvNetState *s = qemu_get_nic_opaque(nc); + if (nc->link_down) { nvnet_link_down(s); } else { - nvnet_link_up(s); + if (have_autoneg(s) && !(s->phy_regs[MII_BMSR] & MII_BMSR_AN_COMP)) { + nvnet_restart_autoneg(s); + } else { + nvnet_link_up(s); + } } } @@ -534,22 +616,10 @@ static uint16_t nvnet_phy_reg_read(NvNetState *s, uint8_t reg) { uint16_t value; - switch (reg) { - case MII_BMSR: - value = MII_BMSR_AN_COMP | MII_BMSR_LINK_ST; - break; - - case MII_ANAR: - /* Fall through... */ - - case MII_ANLPAR: - value = MII_ANLPAR_10 | MII_ANLPAR_10FD | MII_ANLPAR_TX | - MII_ANLPAR_TXFD | MII_ANLPAR_T4; - break; - - default: + if (reg < ARRAY_SIZE(s->phy_regs)) { + value = s->phy_regs[reg]; + } else { value = 0; - break; } trace_nvnet_phy_reg_read(PHY_ADDR, reg, nvnet_get_phy_reg_name(reg), value); @@ -559,6 +629,10 @@ static uint16_t nvnet_phy_reg_read(NvNetState *s, uint8_t reg) static void nvnet_phy_reg_write(NvNetState *s, uint8_t reg, uint16_t value) { trace_nvnet_phy_reg_write(PHY_ADDR, reg, nvnet_get_phy_reg_name(reg), value); + + if (reg < ARRAY_SIZE(s->phy_regs)) { + s->phy_regs[reg] = value; + } } static void nvnet_mdio_read(NvNetState *s) @@ -701,6 +775,11 @@ static void nvnet_mmio_write(void *opaque, hwaddr addr, uint64_t val, nvnet_update_irq(s); break; + case NVNET_MII_STATUS: + nvnet_set_reg(s, addr, nvnet_get_reg(s, addr, size) & ~val, size); + nvnet_update_irq(s); + break; + default: break; } @@ -752,28 +831,66 @@ static void nvnet_realize(PCIDevice *pci_dev, Error **errp) qemu_new_nic(&net_nvnet_info, &s->conf, object_get_typename(OBJECT(s)), dev->id, &dev->mem_reentrancy_guard, s); assert(s->nic); + + s->autoneg_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, nvnet_autoneg_timer, s); } static void nvnet_uninit(PCIDevice *dev) { NvNetState *s = NVNET(dev); qemu_del_nic(s->nic); + timer_free(s->autoneg_timer); +} + +// clang-format off + +static const uint32_t phy_reg_init[] = { + [MII_BMCR] = + MII_BMCR_FD | + MII_BMCR_AUTOEN, + [MII_BMSR] = + MII_BMSR_AUTONEG | + MII_BMSR_AN_COMP | + MII_BMSR_LINK_ST, + [MII_ANAR] = + MII_ANLPAR_10 | + MII_ANLPAR_10FD | + MII_ANLPAR_TX | + MII_ANLPAR_TXFD | + MII_ANLPAR_T4, + [MII_ANLPAR] = + MII_ANLPAR_10 | + MII_ANLPAR_10FD | + MII_ANLPAR_TX | + MII_ANLPAR_TXFD | + MII_ANLPAR_T4, +}; + +// clang-format on + +static void reset_phy_regs(NvNetState *s) +{ + assert(sizeof(s->phy_regs) >= sizeof(phy_reg_init)); + memset(s->phy_regs, 0, sizeof(s->phy_regs)); + memcpy(s->phy_regs, phy_reg_init, sizeof(phy_reg_init)); } static void nvnet_reset(void *opaque) { NvNetState *s = opaque; - if (qemu_get_queue(s->nic)->link_down) { - nvnet_link_down(s); - } - memset(&s->regs, 0, sizeof(s->regs)); - memset(&s->phy_regs, 0, sizeof(s->phy_regs)); + reset_phy_regs(s); memset(&s->tx_dma_buf, 0, sizeof(s->tx_dma_buf)); memset(&s->rx_dma_buf, 0, sizeof(s->rx_dma_buf)); s->tx_dma_buf_offset = 0; + timer_del(s->autoneg_timer); + + if (qemu_get_queue(s->nic)->link_down) { + nvnet_update_regs_on_link_down(s); + } + /* Deprecated */ s->tx_ring_index = 0; s->rx_ring_index = 0; @@ -788,8 +905,12 @@ static void nvnet_reset_hold(Object *obj, ResetType type) static int nvnet_post_load(void *opaque, int version_id) { NvNetState *s = NVNET(opaque); + NetClientState *nc = qemu_get_queue(s->nic); if (version_id < 2) { + /* PHY regs were stored but not used until version 2 */ + reset_phy_regs(s); + /* Migrate old snapshot tx descriptor index */ uint32_t next_desc_addr = nvnet_get_reg(s, NVNET_TX_RING_PHYS_ADDR, 4) + @@ -805,6 +926,16 @@ static int nvnet_post_load(void *opaque, int version_id) s->rx_ring_index = 0; } + /* nc.link_down can't be migrated, so infer link_down according + * to link status bit in PHY regs. + * Alternatively, restart link negotiation if it was in progress. */ + nc->link_down = (s->phy_regs[MII_BMSR] & MII_BMSR_LINK_ST) == 0; + + if (have_autoneg(s) && !(s->phy_regs[MII_BMSR] & MII_BMSR_AN_COMP)) { + nc->link_down = false; + nvnet_restart_autoneg(s); + } + return 0; } diff --git a/ui/xemu-net.c b/ui/xemu-net.c index 79b8752235..8170f2cee4 100644 --- a/ui/xemu-net.c +++ b/ui/xemu-net.c @@ -23,6 +23,7 @@ #include "xemu-settings.h" #include "qemu/osdep.h" +#include "qapi/qapi-commands-net.h" #include "qemu/sockets.h" #include "hw/qdev-core.h" #include "hw/qdev-properties.h" @@ -138,6 +139,7 @@ void xemu_net_enable(void) xemu_net_disable(); } + qmp_set_link("nvnet.0", true, NULL); g_config.net.enable = true; } @@ -179,6 +181,8 @@ void xemu_net_disable(void) remove_netdev(id); remove_netdev(id_hubport); + + qmp_set_link("nvnet.0", false, NULL); g_config.net.enable = false; }