using System.Threading; namespace XFESystem.Network; public readonly record struct DmaMemory(nuint Address, ulong PhysicalAddress, uint Pages); public interface IE1000Hardware { uint ReadPci(uint bus, uint device, uint function, uint offset); void WritePci(uint bus, uint device, uint function, uint offset, uint value); nuint MapMmio(ulong physical, uint bytes); uint ReadMmio(nuint address); void WriteMmio(nuint address, uint value); DmaMemory AllocateDma(uint pages); void FreeDma(DmaMemory memory); } /// 82540EM (8086:100e), legacy descriptor rings, polled DMA. Not an e1000e or physical NIC acceptance claim. public sealed unsafe class E1000Device : IEthernetDevice, IDisposable { const int ReceiveDescriptors = 64, TransmitDescriptors = 32, BufferBytes = 2048; const uint Ctrl = 0x0000, Status = 0x0008, Icr = 0x00c0, Imc = 0x00d8, Rctl = 0x0100, Tctl = 0x0400; readonly IE1000Hardware hardware; readonly INetworkPlatform platform; readonly byte[] mac = new byte[6]; readonly long initializationDeadline; nuint registers; DmaMemory rxRing, txRing, rxBuffers, txBuffers; uint bus, slot, function, savedCommand; int rxIndex, txIndex; bool enabled, disposed, pciEnabled; public ReadOnlySpan MacAddress => mac; uint Read(uint register) => hardware.ReadMmio(registers + register); void Write(uint register, uint value) => hardware.WriteMmio(registers + register, value); public E1000Device(IE1000Hardware hardware, INetworkPlatform platform) : this(hardware, platform, checked(platform.Milliseconds + 5000)) { } public E1000Device(IE1000Hardware hardware, INetworkPlatform platform, long deadlineMilliseconds) { this.hardware = hardware; this.platform = platform; initializationDeadline = deadlineMilliseconds; CheckInitializationBudget(); if (initializationDeadline - platform.Milliseconds > 120000) throw new ArgumentOutOfRangeException(nameof(deadlineMilliseconds)); if (!Discover()) throw new IOException("No supported e1000 8086:100e PCI device; e1000e/other NICs are not implemented."); try { uint bar = ReadConfiguration(bus, slot, function, 0x10); if ((bar & 1) != 0 || (bar & 6) != 0 || (bar & 0xfffffff0) == 0) throw new IOException("e1000 requires its assigned 32-bit memory BAR0."); savedCommand = ReadConfiguration(bus, slot, function, 4) & 65535; // Upper PCI status bits are W1C: never echo them when changing COMMAND. hardware.WritePci(bus, slot, function, 4, savedCommand | 6); pciEnabled = true; CheckInitializationBudget(); registers = hardware.MapMmio(bar & 0xfffffff0, 0x20000); if (registers == 0) throw new IOException("e1000 MMIO mapping failed."); CheckInitializationBudget(); Write(Imc, uint.MaxValue); Write(Rctl, 0); Write(Tctl, 0); Write(Ctrl, Read(Ctrl) | (1u << 26)); while ((Read(Ctrl) & (1u << 26)) != 0) { CheckInitializationBudget(); platform.Wait(1); } CheckInitializationBudget(); Write(Imc, uint.MaxValue); _ = Read(Icr); Write(Ctrl, Read(Ctrl) | (1u << 6)); uint low = Read(0x5400), high = Read(0x5404); if ((high & 0x80000000) == 0) throw new IOException("e1000 factory MAC is not valid."); for (int i = 0; i < 4; i++) mac[i] = (byte)(low >> (8 * i)); mac[4] = (byte)high; mac[5] = (byte)(high >> 8); if (!NetworkPackets.IsUnicastMac(mac)) throw new IOException("e1000 MAC is not unicast."); rxRing = Allocate(1); txRing = Allocate(1); rxBuffers = Allocate(ReceiveDescriptors * BufferBytes / 4096); txBuffers = Allocate(TransmitDescriptors * BufferBytes / 4096); for (int i = 0; i < ReceiveDescriptors; i++) *(ulong*)(rxRing.Address + (nuint)(i * 16)) = rxBuffers.PhysicalAddress + (ulong)(i * BufferBytes); for (int i = 0; i < TransmitDescriptors; i++) { *(ulong*)(txRing.Address + (nuint)(i * 16)) = txBuffers.PhysicalAddress + (ulong)(i * BufferBytes); *((byte*)txRing.Address + i * 16 + 12) = 1; } // Multicast hash table starts empty. Receive only our unicast MAC and broadcast. for (uint offset = 0x5200; offset < 0x5400; offset += 4) { CheckInitializationBudget(); Write(offset, 0); } ProgramRing(0x2800, rxRing.PhysicalAddress, ReceiveDescriptors * 16); ProgramRing(0x3800, txRing.PhysicalAddress, TransmitDescriptors * 16); Write(0x2820, 0); Write(0x282c, 0); Write(0x3820, 0); Write(0x382c, 0); Write(0x0410, 10 | (8u << 10) | (6u << 20)); Thread.MemoryBarrier(); Write(0x2818, ReceiveDescriptors - 1); Write(0x3818, 0); Write(Tctl, (1u << 1) | (1u << 3) | (16u << 4) | (64u << 12)); Write(Rctl, (1u << 1) | (1u << 15) | (1u << 26)); // 2048-byte buffers, strip FCS. enabled = true; while ((Read(Status) & 2) == 0) { CheckInitializationBudget(); platform.Wait(1); } CheckInitializationBudget(); platform.Trace("XFE_NETWORK_DEVICE|Driver|e1000|Pci|" + bus + ":" + slot + ":" + function + "|Vendor|8086|Device|100e|Mac|" + Convert.ToHexString(mac) + "|Link|1|Dma|1|Polled|1\n"); } catch { Dispose(); throw; } } bool Discover() { for (uint b = 0; b < 256; b++) for (uint s = 0; s < 32; s++) { uint first = ReadConfiguration(b, s, 0, 0); if ((first & 65535) == 65535) continue; uint count = (ReadConfiguration(b, s, 0, 0x0c) & 0x00800000) != 0 ? 8u : 1u; for (uint f = 0; f < count; f++) if ((f == 0 ? first : ReadConfiguration(b, s, f, 0)) == 0x100e8086) { bus = b; slot = s; function = f; return true; } } return false; } void CheckInitializationBudget() => Ipv4Network.CheckTimeout(platform.Milliseconds, initializationDeadline, "e1000 initialization"); uint ReadConfiguration(uint b, uint s, uint f, uint offset) { CheckInitializationBudget(); uint result = hardware.ReadPci(b, s, f, offset); CheckInitializationBudget(); return result; } DmaMemory Allocate(uint pages) { CheckInitializationBudget(); DmaMemory memory = hardware.AllocateDma(pages); if (memory.Address == 0 || memory.PhysicalAddress == 0 || memory.Pages != pages || (memory.Address & 4095) != 0 || (memory.PhysicalAddress & 4095) != 0) throw new IOException("e1000 physical DMA allocation failed."); new Span((void*)memory.Address, checked((int)pages * 4096)).Clear(); return memory; } void ProgramRing(uint offset, ulong address, uint bytes) { Write(offset, (uint)address); Write(offset + 4, (uint)(address >> 32)); Write(offset + 8, bytes); Write(offset + 16, 0); } public bool Send(ReadOnlySpan frame, long deadlineMilliseconds) { ObjectDisposedException.ThrowIf(disposed, this); if (!enabled || frame.Length is < 14 or > 1514 || platform.Milliseconds >= deadlineMilliseconds) return false; byte* descriptor = (byte*)txRing.Address + txIndex * 16; while ((Volatile.Read(ref descriptor[12]) & 1) == 0) { if (platform.Milliseconds >= deadlineMilliseconds) return false; platform.Wait(1); } if (platform.Milliseconds >= deadlineMilliseconds) return false; frame.CopyTo(new Span((void*)(txBuffers.Address + (nuint)(txIndex * BufferBytes)), frame.Length)); *(ushort*)(descriptor + 8) = (ushort)frame.Length; descriptor[10] = 0; descriptor[11] = 0x0b; descriptor[12] = descriptor[13] = 0; *(ushort*)(descriptor + 14) = 0; Thread.MemoryBarrier(); txIndex = (txIndex + 1) % TransmitDescriptors; Write(0x3818, (uint)txIndex); while ((Volatile.Read(ref descriptor[12]) & 1) == 0) { if (platform.Milliseconds >= deadlineMilliseconds) return false; platform.Wait(1); } Thread.MemoryBarrier(); return platform.Milliseconds < deadlineMilliseconds; } public int Receive(Span frame) { ObjectDisposedException.ThrowIf(disposed, this); byte* descriptor = (byte*)rxRing.Address + rxIndex * 16; byte status = Volatile.Read(ref descriptor[12]); if ((status & 1) == 0) return 0; Thread.MemoryBarrier(); int length = *(ushort*)(descriptor + 8); bool valid = (status & 3) == 3 && descriptor[13] == 0 && length >= 14 && length <= 1514 && length <= frame.Length; if (valid) new ReadOnlySpan((void*)(rxBuffers.Address + (nuint)(rxIndex * BufferBytes)), length).CopyTo(frame); descriptor[12] = 0; Thread.MemoryBarrier(); Write(0x2818, (uint)rxIndex); rxIndex = (rxIndex + 1) % ReceiveDescriptors; return valid ? length : -1; } public void Dispose() { if (disposed) return; disposed = true; if (registers != 0) { Write(Imc, uint.MaxValue); Write(Rctl, 0); Write(Tctl, 0); _ = Read(Status); } // This also covers a failed MMIO mapping after COMMAND was enabled. // Disable bus mastering before any buffer ownership is returned. if (pciEnabled) { hardware.WritePci(bus, slot, function, 4, savedCommand & ~4u); _ = hardware.ReadPci(bus, slot, function, 4); } foreach (DmaMemory memory in new[] { rxRing, txRing, rxBuffers, txBuffers }) if (memory.Address != 0) hardware.FreeDma(memory); enabled = false; } }