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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
namespace XFESystem.Gpu.Nvidia;

/// <summary>
/// Publishes real Blackwell copy commands into caller-owned native mappings.
/// Prerequisites: GSP/RM initialized the device/channel, GPU MMU mapped these
/// ranges, and RM supplied GPPut and the work-submission register/token. This
/// class does not perform PCI discovery, map BARs, or invent a doorbell offset.
/// The current XFESystem runtime has no such resource provider and does not
/// construct this class. Host tests use private unmanaged memory, not a GPU.
/// </summary>
public sealed unsafe class NvidiaMappedCopyChannel : IDisposable
{
    private readonly object gate = new();
    private readonly NvidiaMappedRegion commands, fifo, fence;
    private readonly nuint gpPut, workSubmission, drainBar1;
    private readonly uint workToken, copyClass;
    private readonly int entryCount;
    private int producer;
    private uint payload;
    private bool inFlight, disposed;

    public bool InFlight => Volatile.Read(ref inFlight);

    public NvidiaMappedCopyChannel(uint copyClass, NvidiaMappedRegion commands, NvidiaMappedRegion fifo,
        int entryCount, NvidiaMappedRegion fence, nuint gpPut, nuint workSubmission,
        uint workToken, int initialProducer = 0, nuint drainBar1 = 0)
    {
        if (copyClass != NvidiaBlackwellCommands.CopyClassA && copyClass != NvidiaBlackwellCommands.CopyClassB)
            throw new ArgumentOutOfRangeException(nameof(copyClass));
        if (commands.Length < NvidiaBlackwellCommands.Fill32CommandBytes || entryCount < 4 ||
            entryCount > 65536 || (entryCount & (entryCount - 1)) != 0 ||
            fifo.Length < (long)entryCount * NvidiaBlackwellCommands.GpfifoEntryBytes || fence.Length != 4 ||
            initialProducer < 0 || initialProducer >= entryCount)
            throw new ArgumentException("Invalid command, FIFO, fence or producer geometry.");
        fifo.Gpu.RequireAlignment(8);
        if ((fifo.CpuAddress & 7) != 0) throw new ArgumentException("GPFIFO CPU mapping must be eight-byte aligned.");
        if (commands.Gpu.Overlaps(fifo.Gpu) || commands.Gpu.Overlaps(fence.Gpu) || fifo.Gpu.Overlaps(fence.Gpu) ||
            commands.CpuOverlaps(fifo) || commands.CpuOverlaps(fence) || fifo.CpuOverlaps(fence))
            throw new ArgumentException("Channel memory ranges overlap.");
        RequireRegister(gpPut); RequireRegister(workSubmission);
        if (drainBar1 != 0) RequireRegister(drainBar1);
        if (gpPut == workSubmission) throw new ArgumentException("GPPut and the work-submission register alias.");
        foreach (var memory in new[] { commands, fifo, fence })
            if (Contains(memory, gpPut) || Contains(memory, workSubmission) || (drainBar1 != 0 && Contains(memory, drainBar1)))
                throw new ArgumentException("A control register aliases command, FIFO or fence memory.");
        // Validate the complete pushbuffer range before exposing an instance.
        Span<byte> scratch = stackalloc byte[NvidiaBlackwellCommands.GpfifoEntryBytes];
        NvidiaBlackwellCommands.WriteGpfifoEntry(scratch, commands.Gpu, NvidiaBlackwellCommands.Fill32CommandBytes);
        this.copyClass = copyClass; this.commands = commands; this.fifo = fifo; this.fence = fence;
        this.entryCount = entryCount; this.gpPut = gpPut; this.workSubmission = workSubmission;
        this.workToken = workToken; this.drainBar1 = drainBar1; producer = initialProducer;
    }

    public uint SubmitFill32(NvidiaGpuRange target, uint pixels, uint color)
    {
        Monitor.Enter(gate);
        uint result;
        // The native runtime does not unwind arbitrary finally handlers. Release
        // explicitly on both paths; compiler-generated lock/finally is unsafe here.
        try { result = SubmitFill32Core(target, pixels, color); }
        catch { Monitor.Exit(gate); throw; }
        Monitor.Exit(gate);
        return result;
    }

    private uint SubmitFill32Core(NvidiaGpuRange target, uint pixels, uint color)
    {
            ObjectDisposedException.ThrowIf(disposed, this);
            if (inFlight) throw new InvalidOperationException("The prior GPU fence has not completed; its mappings cannot be reused.");
            if (payload == uint.MaxValue) throw new InvalidOperationException("Fence payload space is exhausted; recreate the channel after draining it.");
            if (target.Overlaps(commands.Gpu) || target.Overlaps(fifo.Gpu) || target.Overlaps(fence.Gpu))
                throw new ArgumentException("Fill target aliases live channel memory.");
            uint next = payload + 1;
            // Validate/encode privately first. Failed validation leaves all DMA
            // mappings and registers untouched, including the previous fence.
            Span<byte> packet = stackalloc byte[NvidiaBlackwellCommands.Fill32CommandBytes];
            NvidiaBlackwellCommands.WriteFill32(packet, copyClass, target, pixels, color, fence.Gpu, next);
            Span<byte> entries = stackalloc byte[NvidiaBlackwellCommands.GpfifoEntryBytes * 2];
            NvidiaBlackwellCommands.WriteSegmentBase(entries, commands.Gpu);
            NvidiaBlackwellCommands.WriteGpfifoEntry(entries[8..], commands.Gpu, packet.Length);
            // From here onwards, conservatively retain ownership even if a
            // mapping faults while publishing. Never recycle a partial submit.
            payload = next; inFlight = true;
            Volatile.Write(ref *(uint*)fence.CpuAddress, 0);
            packet.CopyTo(new Span<byte>((void*)commands.CpuAddress, packet.Length));
            int second = (producer + 1) & (entryCount - 1);
            entries[..8].CopyTo(new Span<byte>((void*)(fifo.CpuAddress + (nuint)(producer * 8)), 8));
            entries[8..].CopyTo(new Span<byte>((void*)(fifo.CpuAddress + (nuint)(second * 8)), 8));
            Thread.MemoryBarrier();
            if (drainBar1 != 0) _ = Volatile.Read(ref *(uint*)drainBar1);
            producer = (producer + 2) & (entryCount - 1);
            Volatile.Write(ref *(uint*)gpPut, (uint)producer);
            Thread.MemoryBarrier();
            Volatile.Write(ref *(uint*)workSubmission, workToken);
            return payload;
    }

    /// <summary>
    /// Reads the mapped GPU-written fence. A timeout never implies completion and no
    /// CPU-side simulator writes this word. Callers must retain mappings on timeout.
    /// </summary>
    public bool TryRetire()
    {
        Monitor.Enter(gate);
        bool result;
        try { result = TryRetireCore(); }
        catch { Monitor.Exit(gate); throw; }
        Monitor.Exit(gate);
        return result;
    }

    private bool TryRetireCore()
    {
            ObjectDisposedException.ThrowIf(disposed, this);
            if (!inFlight) return true;
            if (Volatile.Read(ref *(uint*)fence.CpuAddress) != payload) return false;
            Thread.MemoryBarrier(); inFlight = false;
            return true;
    }

    public void Dispose()
    {
        Monitor.Enter(gate);
        try { DisposeCore(); }
        catch { Monitor.Exit(gate); throw; }
        Monitor.Exit(gate);
    }

    private void DisposeCore()
    {
            if (disposed) return;
            if (inFlight) throw new InvalidOperationException("Wait for the GPU fence or reset the owning channel before unmapping memory.");
            disposed = true;
    }

    private static bool Contains(NvidiaMappedRegion memory, nuint word) =>
        word < memory.CpuAddress + (nuint)memory.Length && memory.CpuAddress < word + 4;
    private static void RequireRegister(nuint word)
    {
        if (word == 0 || (word & 3) != 0 || word > nuint.MaxValue - 4)
            throw new ArgumentException("A mapped control register is null, unaligned or overflows.");
    }
}