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);
}
/// <summary>82540EM (8086:100e), legacy descriptor rings, polled DMA. Not an e1000e or physical NIC acceptance claim.</summary>
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<byte> 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<byte>((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<byte> 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<byte>((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<byte> 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<byte>((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;
}
}
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);
}
/// <summary>82540EM (8086:100e), legacy descriptor rings, polled DMA. Not an e1000e or physical NIC acceptance claim.</summary>
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<byte> 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<byte>((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<byte> 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<byte>((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<byte> 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<byte>((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;
}
}