Skip to content

NVNet Networking

Mainkill1 edited this page Sep 27, 2026 · 2 revisions

NVNet Networking

xemu models the Xbox MCPX Ethernet controller as a PCI NIC with guest-visible registers, descriptor rings, DMA, PHY/MDIO state, link negotiation, packet filtering and interrupts. The physical Ethernet PHY and wire are not simulated electrically; packet payloads cross into QEMU's host networking backend after the guest NIC semantics have been processed.

End-to-end architecture

flowchart LR
    A[Guest network driver] --> B[NVNet MMIO]
    A --> C[TX/RX descriptor rings in guest RAM]
    B --> D[NvNetState]
    C --> E[pci_dma_read/write]
    D --> E
    E --> F[TX/RX packet assembly]
    F --> G[QEMU NetClient backend]
    G --> H[Host network backend]
    F --> I[Descriptor/status update]
    I --> J[PCI IRQ]
    J --> A
Loading

The guest still sees a DMA-driven NIC. The abstraction boundary is below the emulated MAC/descriptor engine and above physical Ethernet signaling.

PCI and register model

hw/xbox/mcpx/nvnet/nvnet.c exposes an MMIO register area of 0x400 bytes plus an I/O region. NvNetState stores the register bytes directly and provides helpers for 8-, 16- and 32-bit accesses.

The model includes state for:

  • interrupt status/mask;
  • transmitter and receiver control/status;
  • MAC and multicast addresses/masks;
  • TX and RX ring physical addresses and sizes;
  • current/next descriptor addresses;
  • PHY/MDIO state;
  • link speed/control;
  • power/wakeup-related registers;
  • TX/RX temporary packet buffers;
  • virtual auto-negotiation timer.

Some register meanings remain named UNKNOWN_*, reflecting incomplete hardware knowledge rather than hidden complete behavior.

Descriptor format

Current ring descriptors are modeled as packed guest-memory structures:

uint32 buffer_addr
uint16 length
uint16 flags

The device reads and writes descriptors with pci_dma_read() and pci_dma_write(), converting little-endian guest fields to host values explicitly.

This is important for fidelity: descriptor ownership is not replaced by a host socket API. xemu first reproduces the NIC's guest-memory protocol, then hands a completed packet to QEMU networking.

Transmit path

sequenceDiagram
    participant G as Guest driver
    participant R as Guest TX ring
    participant N as NVNet model
    participant Q as QEMU network backend

    G->>R: Fill descriptors + packet buffers
    G->>N: Write TX/RX CONTROL KICK
    N->>R: DMA-read current descriptor
    loop valid descriptors
        N->>R: DMA-read packet fragment
        N->>N: Append to tx_dma_buf
        N->>R: Clear/update descriptor flags
    end
    N->>Q: qemu_send_packet() on LASTPACKET
    N->>G: Set TX IRQ status and assert PCI IRQ
Loading

dma_packet_from_guest() stops when it finds an invalid descriptor or after the current packet's LASTPACKET. The implementation currently contains a FIXME at the post-last-packet break, so unusual multi-packet/ring scheduling deserves explicit testing.

The temporary TX assembly buffer is bounded. The source currently asserts that accumulated fragment length fits the buffer instead of modeling a hardware overflow/error path.

Receive path

sequenceDiagram
    participant Q as QEMU network backend
    participant N as NVNet model
    participant R as Guest RX ring/RAM
    participant G as Guest driver

    Q->>N: Packet arrives
    N->>N: Size/filter check
    N->>R: Find available RX descriptor
    N->>R: DMA-write packet bytes
    N->>R: Write actual length + completion flags
    N->>G: Set RX IRQ status and assert PCI IRQ
    N->>N: Advance ring pointer
Loading

A packet can be accepted only when receive is enabled, DMA is enabled, link is up and an RX descriptor is available.

The current receive path asserts that the selected descriptor's buffer is large enough for the packet (FIXME in source). Physical hardware would normally have defined truncation/error/drop behavior; an assertion-enabled emulator build can therefore fail more strongly on malformed or unusual guest ring state.

Ring-pointer behavior

Current and next descriptor addresses are kept as guest-visible registers. If a computed next/current pointer falls outside the configured ring, xemu wraps it back to the base ring address.

base <= descriptor < base + ring_size * 8
    → use descriptor
otherwise
    → wrap to base

Ring size register fields are interpreted as encoded value + 1 descriptors.

Packet filtering

The receive filter implements broadcast, multicast and unicast checks using guest-programmed registers.

Current known approximations include source FIXMEs around:

  • broadcast filtering behavior;
  • exact meaning of PFF_MYADDR for multicast;
  • MII status mask handling.

This is a good example of a model that is substantially functional but still has protocol corners where hardware behavior is not fully established.

PHY and MDIO

The PHY is represented by a small logical register array rather than an analog Ethernet transceiver. PHY_ADDR is fixed to 1, and implemented PHY registers include common MII identity/control/status/autonegotiation fields.

Guest MDIO access is handled synchronously:

flowchart LR
    A[Guest writes MDIO address/control] --> B[nvnet_mmio_write]
    B --> C{read or write?}
    C -->|Read| D[phy_reg_read]
    C -->|Write| E[phy_reg_write]
    D --> F[MDIO data register]
    E --> F
    F --> G[Clear INUSE]
Loading

There is no serial MDIO bit-clock model in this path.

Virtual auto-negotiation

Link negotiation is one place where xemu deliberately retains visible latency. When autonegotiation is enabled and the host link comes up, xemu schedules a QEMU_CLOCK_VIRTUAL timer for 250 ms.

stateDiagram-v2
    [*] --> LinkDown
    LinkDown --> Negotiating: host link up + AUTOEN
    Negotiating --> LinkUp: 250 ms virtual timer expires
    LinkUp --> LinkDown: host link down
    LinkUp --> Negotiating: renegotiation
Loading

At completion the model marks partner ACK/autoneg complete, marks link up and raises the appropriate MII/link-change interrupt state.

This is virtual device timing, not a real PHY waveform simulation.

Interrupt flow

update_irq() compares NVNET_IRQ_STATUS with NVNET_IRQ_MASK and asserts the PCI interrupt whenever any enabled status bit is pending.

device event
    ↓
set status bit
    ↓
status & mask != 0 ?
    ├─ yes → pci_irq_assert
    └─ no  → pci_irq_deassert

Guest writes to IRQ/MII status registers clear selected bits and recompute the line.

DMA ownership and memory effects

NVNet deliberately uses QEMU PCI DMA APIs rather than caching a permanent raw pointer for descriptor-ring traffic. That preserves the device's PCI/DMA address-space abstraction and makes reads/writes visible through QEMU's memory system.

register contains guest physical/DMA address
    ↓
pci_dma_read/write
    ↓
QEMU DMA/address-space translation
    ↓
guest RAM

This is different from the APU's direct RAM-pointer fast path and is useful when comparing memory overhead between devices.

Host networking boundary

Once a complete guest TX packet is assembled, qemu_send_packet() transfers it into QEMU's network backend. For RX, the configured backend invokes the NVNet receive callback with a host-side packet.

What xemu does not model at that boundary includes:

  • analog PHY signaling;
  • magnetics;
  • cable propagation;
  • physical collision waveforms;
  • electrical link partner behavior.

Any latency or loss introduced by TAP/socket/bridge/NAT/host networking belongs below the emulated NIC and should be profiled separately.

Fast paths and simplifications

Mechanism Effect Risk/limitation
Direct register byte array Cheap ordinary MMIO storage Unknown registers may lack side effects
Synchronous MDIO reads/writes No serial-bus timing overhead PHY transaction latency omitted
250 ms virtual autoneg timer Preserves coarse link delay Real negotiation details are simplified
Whole packet assembled in bounded host buffer Simple descriptor-fragment handling Buffer limits currently protected by assertions
pci_dma_read/write Preserves DMA abstraction More host overhead than a raw RAM pointer, but safer for device semantics
QEMU network backend Reuses mature host I/O plumbing Host backend behavior can dominate end-to-end latency

Known bug/edge surfaces

Current source contains explicit uncertainty or strong assertions around several areas:

  • RX descriptor buffer-too-small condition uses an assertion with FIXME.
  • TX temporary-buffer overflow uses an assertion.
  • TX loop stops after LASTPACKET with FIXME.
  • broadcast filtering is not fully implemented;
  • multicast filter semantics contain a confirmation FIXME;
  • MII status masking is incomplete/uncertain;
  • unaligned/unsupported MMIO access can assert rather than emulate undefined hardware behavior.

These should be treated as architecture constraints when fuzzing drivers or evaluating homebrew that programs NVNet differently from the retail kernel.

Performance investigation flow

flowchart TD
    A[Slow networking] --> B{Where is time?}
    B -->|Guest CPU| C[driver / IRQ / descriptor handling]
    B -->|NVNet model| D[MMIO + ring walks + DMA copies]
    B -->|QEMU backend| E[host network queue/backend]
    B -->|Host network| F[OS/TAP/socket/bridge]
    D --> G{Packet copy or DMA lookup dominant?}
    E --> H{Backend wait/backpressure?}
    C --> I[Profile vCPU/device exits]
Loading

Do not optimize pci_dma_read() away without proving the target address is always ordinary RAM and preserving dirty/mapping semantics.

Source map

Source Responsibility
hw/xbox/mcpx/nvnet/nvnet.c Device, MMIO, descriptor/DMA, filtering, PHY, IRQ, network backend integration
hw/xbox/mcpx/nvnet/nvnet_regs.h Register and descriptor flag definitions
hw/xbox/mcpx/nvnet/trace-events Existing tracepoints useful for diagnosis
hw/xbox/xbox.c PCI device creation and NIC configuration

Related pages

Clone this wiki locally