XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
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;
    }
}