Skip to content

Internal driver engine and PCI driver binding - #3260

Closed
valentinbreiz wants to merge 5 commits into
feature/driver-kit-test-axesfrom
feature/driver-kit-engine
Closed

valentinbreiz wants to merge 5 commits into
feature/driver-kit-test-axesfrom
feature/driver-kit-engine

Conversation

@valentinbreiz

Copy link
Copy Markdown
Member

Stacked on #3259 (base feature/driver-kit-test-axes), itself on #3258 and #3257. Retarget to gen3 once those merge.

Fourth step towards letting a user kernel register its own PCI and USB drivers. This PR adds the driver engine and PCI binding. Everything stays internal: each type already has its final name, namespace and signature, so the PR that opens the seam only makes them public and marks them experimental (COSMOS0003). No public API or PublicAPI.*.txt change. No USB binding yet.

Problem

  • No way to bind a driver that is not built in. Every driver binds during HAL init, from a hard-coded list. A kernel has no registration point, and nothing hands a driver a PCI function, a BAR, DMA memory or an interrupt.
  • Nothing tears a failed bind down. A built-in that fails leaves its function as it found it by accident, not by design: decoding, bus mastering, MSI-X vectors and DMA pages are never put back.
  • Devices cannot reach System's managers after boot. Built-in mice and NICs are pulled once by System's initializer. NetworkManager.RegisterDevice also published the count before the slot (s_devices[s_deviceCount++] = device), so a reader walking up to the count could see an empty slot.

Fix

Registration and the pass (Cosmos.Kernel.HAL.Drivers.Engine.DriverCore):

  • A kernel registers PciDriverRegistrations (name, factory, PciMatches) from its constructor. Register returns false for a taken name (built-in owner names included) and throws after the pass, or from inside a factory, a Probe or a work item.
  • Global.StartKernel runs one pass after enabling interrupts and before USB hot-plug starts, behind PCIEnabled alone so a kernel without PCI trims the engine. Registration closes as the pass starts.
  • The pass walks functions in bus/device/function order and skips any function with an owner (the gop reservation included) and any bridge. Each function goes to its matching registrations, most specific first (Device > Class(b,s,p) > Class(b,s), then registration order), until one returns Bound, which claims it under the registration's name. Every decision is logged, e.g. [Drivers] pci/0000:00:02.0 -> rtl8139 (class match).
  • With nothing registered the pass touches no device and logs [Drivers] No driver registered.
  • The pass then builds an internal device list (path, owner, IDs, class) for every function.

Per-attempt context (PciDeviceContext, MmioRegion, PortRegion, DmaBuffer):

  • Before Probe: saves the Command register, sizes every BAR once with decoding off, turns bus mastering off and INTx off.
  • TryMapBar maps every 2 MiB block through EnsureMmioMapped and then enables memory decoding. Accesses are bounds- and alignment-checked, with a DMA write barrier before each write and a read barrier after each read.
  • TryMapIoBar is false where the platform has no port I/O (ARM64).
  • TryAllocateDma(length, maximumDeviceAddress) returns zeroed pages, or false when they would end above the limit.
  • Config writes below 0x40 are refused. Probe-only members throw outside Probe.

Interrupts, work items and events:

  • TryRequestInterrupts wraps the handler in a trampoline that stays disarmed until Bound.
    • MSI-X when the function and the platform support it: entry 0 is programmed masked, then armed, bus mastering on and the entry unmasked after Bound.
    • Otherwise the handler is polled from a recurring timer: about 55 ms on x64, 10 ms on ARM64.
    • False when neither is possible. Whether the timer ticks is measured once, before the first probe, and only when something is registered.
  • Work items run on a driver-work thread started on first use through KernelThread.TryStart. One scheduled during Probe runs only after Bound.
  • DeviceEvent.Wait throws inside Probe and returns false once the binding is gone. IrqSafeLock wraps the IRQ-safe spinlock.

Teardown of a declined or failed attempt, in a fixed order:

  1. Disarm the trampoline, mask the MSI-X entry, stop the poll timer.
  2. Drop work items and publications, cancel events.
  3. Write back the Command register with bus mastering off, and read it back.
  4. Free the DMA buffers.
  5. MsiX.Disable, then release the vectors (UnbindEntry and ReleaseDevice from Groundwork for user drivers: thread-safe allocation and mapping, MSI-X teardown, DMA barriers #3257).
  6. Invalidate the regions.

Publishing:

  • PublishMouse and PublishNetworkLink queue a publication during Probe, delivered after Bound, dropped on failure. Adapters PublishedMouse : MouseDevice and PublishedNetworkDevice : NetworkDevice reach the managers through sinks System installs in its initializer, inside the Mouse and Network guards. Built-in devices register first and stay primary.
  • NetworkLink.Deliver copies each frame into a fresh array and runs the stack with interrupts masked.
  • NetworkManager.RegisterDevice stores the slot, then publishes the count with Volatile.Write.

Other changes: MsiX.TryGetTable (where the table lives, so it can be mapped before Enable), and PciOwner.IsBuiltIn.

Verification

Build. make setup: 0 errors, no warnings in the files this PR adds or changes.

Drivers suite. 47 tests per cell. It now builds with Storage off, so the built-in NVMe driver leaves the controller free for the new nvme cell.

Cells x64 arm64
Total (tests / failed / skipped) 188 / 0 / 116 (4 cells) 705 / 0 / 453 (15 cells)
edu 35 passed 35 passed ×3
nvme 15 passed 15 passed ×3
rtl8139 14 passed 14 passed ×3
e1000e-arm64 n/a 12 passed ×3
usb-mouse 8 passed 8 passed ×3

Every skip is a cell gate. What the new cells check:

  • Registration: taken names, invalid registrations, Register after the pass or from a driver callback.
  • Ranking: device beats class; a failed or declined candidate falls through to the next.
  • The device list: it matches every enumerated function.
  • edu: identification, liveness and factorial through the mapped BAR. DMA round-trips on x64. On ARM64 the 2^28 limit fails, and the test checks the allocator really had no page below it.
  • Polled interrupts: edu raises one through register 0x60. The handler never runs before Bound or after teardown.
  • nvme: a first driver requests interrupts and fails. A second one gets MSI-X again on x64 and ARM64 GICv3 (polled on the other ARM64 cells) and receives the completion interrupt for an Identify.
  • Teardown: Command register restored with bus mastering off, regions and buffers invalidated, work items and publications dropped.
  • e1000e on ARM64: MSI-X through the ITS on GICv3, polled otherwise.
  • rtl8139: a test driver built only on the seam binds on both arches, publishes its link and gets a DHCP lease: [NetworkStack] Configured IP 10.0.2.15 on device rtl8139 pci/0000:00:02.0.

After the review fixes, the fixer removed each fix in turn (bus-master clear, poll-timer stop, re-entrancy flag, work-item drop): exactly the cell written for it failed.

Other suites, QEMU. Same totals as #3259, test by test:

  • x64: HelloWorld, Memory, GarbageCollector, Threading, Timer, Interrupts, Pci, Virtio, Storage (6 cells), Network (2), Graphic (3).
  • arm64: HelloWorld, Memory, Threading, Interrupts (3), Pci, Virtio (2), Storage (18), Network (2).
  • The one exception is below.

GarbageCollector on arm64: GC_InteriorPointerRoot fails (46/47).

  • The GC has a bug that predates this PR. GCSegment.Enumerator.MoveNext computes Current + size on a GCObject*, so the size is scaled by sizeof(GCObject), and the heap walk stops early (GCSegment.cs:135). An array whose only root is an interior pointer is then never marked and gets swept.
  • This PR only shifts boot allocations enough to put the test's array where the walk stops.
  • The fix belongs in a separate GC PR; this one stays red on that cell until it lands.

Real hardware. The runs above are QEMU only. The pass does nothing unless a kernel registers a driver.

Known gaps.

  • A bridge is never offered to a driver, but no profile has a PCI-to-PCI bridge, so no cell exercises that check.
  • No cell checks any more that a declined attempt puts back the INTx-disable bit, because the RTL8139 cell now binds.
  • No cell drives an exception out of an interrupt handler (it halts the kernel) or out of a work item.
  • The e1000e cell checks MSI-X entry state but raises no real e1000e interrupt; the nvme cells prove real MSI-X delivery.
  • The driver-work thread gets the default stack: KernelThread.TryStart takes no stack size.
  • MouseManager.UnregisterMouse, NetworkManager.UnregisterDevice and a generation stamp on NetworkAdapter come with USB unplug, which is their first caller.

Found here but not changed:

  • PIT.RegisterTimer reprograms channel 0 after leaving its masked scope. A tick between the LSB and MSB writes can stop the timer for good. The engine registers with interrupts masked.
  • MsiX.Enable ignores EnsureMmioMapped's result, so a table that fails to map would fault on ARM64 in the built-ins. The engine maps the table first.
  • The patcher fails to write back a kernel assembly that declares a const of an enum type from another kernel assembly (AssemblyResolutionException in MetadataBuilder.GetConstantType). A user driver declaring const MouseButtons would hit it once the seam is public.
  • With two NICs, DHCP would call AddressMap.Add(0.0.0.0, ...) once per device and hit a duplicate key, and it gives the lease to device 0 whichever NIC answered. This comes from reading the code; it was not reproduced.
  • The Storage suite's ExpectedTestCount is below the number of tests it runs. This was already the case before this PR.

RegisterDevice incremented the count before it stored the device, so a
reader walking the table up to the count could see an empty slot. Store
the slot and the primary index first, then publish the count with a
Volatile.Write that DeviceCount and GetAdapter read back. Registration
can now run after boot, from the driver pass.
A kernel registers PciDriverRegistrations from its constructor through
the internal DriverCore. Global.StartKernel runs one pass after enabling
interrupts and before USB hot-plug: each PCI function no driver owns,
bridges excepted, is offered to its matching registrations, most
specific match first, until one returns Bound and claims it.

Each attempt gets a PciDeviceContext:
- BARs sized once with decoding off, mapped as bounds-checked regions;
- DMA buffers under an address ceiling;
- MSI-X programmed masked and unmasked only after Bound, otherwise the
  handler polled from the timer;
- work items on a lazily started driver-work thread, and events;
- mice and network links published to System's managers after Bound.
A declined or failed attempt is torn down in a fixed order, which puts
back the Command register with bus mastering off, frees its DMA and
releases its MSI-X vectors.

Every type stays internal in its final shape until the seam opens.
The suite registers its own drivers and checks, per cell:
- registration rules, ranking and the per-function device list;
- edu: identification, factorial, DMA below 2^28, a polled interrupt
  and a work item;
- nvme, free because the suite now builds without Storage: a failed
  attempt releases its interrupts, then a second driver gets MSI-X
  again and completes an Identify;
- e1000e on arm64: MSI-X through the GICv3 ITS;
- rtl8139: a driver-published link obtains a DHCP lease.
Core grants the suite temporary internals access to count vectors.
@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

🧪 Drivers Tests

Cell x64 arm64
all ⚠️ 72/188, 116 skip ⚠️ 252/705, 453 skip
e1000e-arm64 n/a ⚠️ 12/47, 35 skip
e1000e-arm64+gicv2 n/a ⚠️ 12/47, 35 skip
e1000e-arm64+gicv3 n/a ⚠️ 12/47, 35 skip
edu ✅ 35/47, 12 skip ✅ 35/47, 12 skip
edu+gicv2 n/a ✅ 35/47, 12 skip
edu+gicv3 n/a ✅ 35/47, 12 skip
nvme ⚠️ 15/47, 32 skip ⚠️ 15/47, 32 skip
nvme+gicv2 n/a ⚠️ 15/47, 32 skip
nvme+gicv3 n/a ⚠️ 15/47, 32 skip
rtl8139 ⚠️ 14/47, 33 skip ⚠️ 14/47, 33 skip
rtl8139+gicv2 n/a ⚠️ 14/47, 33 skip
rtl8139+gicv3 n/a ⚠️ 14/47, 33 skip
usb-mouse ⚠️ 8/47, 39 skip ⚠️ 8/47, 39 skip
usb-mouse+gicv2 n/a ⚠️ 8/47, 39 skip
usb-mouse+gicv3 n/a ⚠️ 8/47, 39 skip

Skipped

  • x64: 116 skipped: this cell attaches no edu device (72), this cell attaches no NVMe controller (12), this cell attaches no USB device (9), this cell attaches no RTL8139 (9), this cell attaches no unclaimed PCI function (5), this cell attaches no unclaimed 82574L (4), this cell attaches no unclaimed Ethernet function (3), this cell attaches neither edu nor an NVMe controller (2)
  • arm64: 453 skipped: this cell attaches no edu device (288), this cell attaches no NVMe controller (48), this cell attaches no USB device (36), this cell attaches no RTL8139 (36), this cell attaches no unclaimed PCI function (15), this cell attaches no unclaimed 82574L (12), this cell attaches no unclaimed Ethernet function (9), this cell attaches neither edu nor an NVMe controller (9)

📎 Artifacts

Architecture Test Results UART Log Kernel ISO
x64 XML Log ISO
arm64 XML Log ISO

📋 View full test summary | 📄 Kernel.cs

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

🧪 Fat Tests

Cell x64 arm64
all ✅ 42/42 ✅ 42/42

📎 Artifacts

Architecture Test Results UART Log Kernel ISO
x64 XML Log ISO
arm64 XML Log ISO

📋 View full test summary | 📄 Kernel.cs

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

🧪 File Tests

Cell x64 arm64
all ✅ 37/37 ✅ 37/37

📎 Artifacts

Architecture Test Results UART Log Kernel ISO
x64 XML Log ISO
arm64 XML Log ISO

📋 View full test summary | 📄 Kernel.cs

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

🧪 GarbageCollector Tests

Cell x64 arm64
all ✅ 50/50 ✅ 50/50

📎 Artifacts

Architecture Test Results UART Log Kernel ISO
x64 XML Log ISO
arm64 XML Log ISO

📋 View full test summary | 📄 Kernel.cs

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

🧪 Graphic Tests

Cell x64 arm64
all ✅ 53/81, 28 skip ✅ 27/54, 27 skip
bare ⚠️ 13/27, 14 skip ⚠️ 13/27, 14 skip
virtio-gpu ✅ 14/27, 13 skip ✅ 14/27, 13 skip
vmware-svga ✅ 26/27, 1 skip n/a

Skipped

  • x64: 28 skipped: VMware SVGA II adapter not present, needs the vmware-svga profile (26), virtio-gpu not attached, needs the virtio-gpu profile (2)
  • arm64: 27 skipped: VMware SVGA II adapter not present, needs the vmware-svga profile (26), virtio-gpu not attached, needs the virtio-gpu profile (1)

📎 Artifacts

Architecture Test Results UART Log Kernel ISO
x64 XML Log ISO
arm64 XML Log ISO

📋 View full test summary | 📄 Kernel.cs

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

🧪 HelloWorld Tests

Cell x64 arm64
all ✅ 3/3 ✅ 3/3

📎 Artifacts

Architecture Test Results UART Log Kernel ISO
x64 XML Log ISO
arm64 XML Log ISO

📋 View full test summary | 📄 Kernel.cs

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

🧪 Math Tests

Cell x64 arm64
all ✅ 30/30 ✅ 30/30

📎 Artifacts

Architecture Test Results UART Log Kernel ISO
x64 XML Log ISO
arm64 XML Log ISO

📋 View full test summary | 📄 Kernel.cs

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

🧪 Memory Tests

Cell x64 arm64
all ✅ 71/71 ✅ 71/71

📎 Artifacts

Architecture Test Results UART Log Kernel ISO
x64 XML Log ISO
arm64 XML Log ISO

📋 View full test summary | 📄 Kernel.cs

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

🧪 Network Tests

Cell x64 arm64
all ✅ 48/48 ✅ 48/48
e1000e ✅ 24/24 n/a
virtio-net-mmio n/a ✅ 24/24
virtio-net-pci ✅ 24/24 ✅ 24/24

📎 Artifacts

Architecture Test Results UART Log Kernel ISO
x64 XML Log ISO
arm64 XML Log ISO

📋 View full test summary | 📄 Kernel.cs

@github-actions

Copy link
Copy Markdown

🧪 Pci Tests

Cell x64 arm64
all ✅ 10/10 ✅ 9/10, 1 skip

Skipped

  • arm64: 1 skipped: no Intel Ethernet function on this machine (1)

📎 Artifacts

Architecture Test Results UART Log Kernel ISO
x64 XML Log ISO
arm64 XML Log ISO

📋 View full test summary | 📄 Kernel.cs

@github-actions

Copy link
Copy Markdown

🧪 Power Tests

Cell x64 arm64
all ✅ 4/4 ✅ 4/4

📎 Artifacts

Architecture Test Results UART Log Kernel ISO
x64 XML Log ISO
arm64 XML Log ISO

📋 View full test summary | 📄 Kernel.cs

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

🧪 Storage Tests

Cell x64 arm64
all ✅ 434/480, 46 skip ⚠️ 683/1440, 757 skip
ahci ✅ 70/80, 10 skip ✅ 70/80, 10 skip
ahci+acpi-off ✅ 70/80, 10 skip ⚠️ 3/80, 77 skip
ahci+gicv2 n/a ✅ 70/80, 10 skip
ahci+gicv2+acpi-off n/a ⚠️ 3/80, 77 skip
ahci+gicv3 n/a ✅ 70/80, 10 skip
ahci+gicv3+acpi-off n/a ⚠️ 3/80, 77 skip
nvme ✅ 74/80, 6 skip ✅ 71/80, 9 skip
nvme+acpi-off ✅ 73/80, 7 skip ⚠️ 3/80, 77 skip
nvme+gicv2 n/a ✅ 72/80, 8 skip
nvme+gicv2+acpi-off n/a ⚠️ 3/80, 77 skip
nvme+gicv3 n/a ✅ 72/80, 8 skip
nvme+gicv3+acpi-off n/a ⚠️ 3/80, 77 skip
usb ✅ 76/80, 4 skip ✅ 76/80, 4 skip
usb+acpi-off ✅ 71/80, 9 skip ⚠️ 4/80, 76 skip
usb+gicv2 n/a ✅ 76/80, 4 skip
usb+gicv2+acpi-off n/a ⚠️ 4/80, 76 skip
usb+gicv3 n/a ✅ 76/80, 4 skip
usb+gicv3+acpi-off n/a ⚠️ 4/80, 76 skip

Skipped

  • x64: 46 skipped: not a USB profile (24), 64-bit BAR relocation probe is nvme-profile only (8), USB hot-plug thread not running (scheduler timer not ticking) (5), not an NVMe profile (4), NVMe controller API is nvme-profile only (4), acpi-off has no MSI routing to pin (1)
  • arm64: 757 skipped: no block device bound for partition-table tests (441), no block device bound for this profile (180), not a USB profile (72), x64 mapper cell; arm64 installs Device mappings via DeviceMapper (18), BAR relocation probe is x64-only (same harness as the mapper cell) (18), NVMe controller API is nvme-profile only (15), not an NVMe profile (12), interrupt mode not pinned by this cell (1)

📎 Artifacts

Architecture Test Results UART Log Kernel ISO
x64 XML Log ISO
arm64 XML Log ISO

📋 View full test summary | 📄 Kernel.cs

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

🧪 Threading Tests

Cell x64 arm64
all ✅ 78/78 ✅ 78/78

📎 Artifacts

Architecture Test Results UART Log Kernel ISO
x64 XML Log ISO
arm64 XML Log ISO

📋 View full test summary | 📄 Kernel.cs

@github-actions

Copy link
Copy Markdown

🧪 TypeCasting Tests

Cell x64 arm64
all ✅ 17/17 ✅ 17/17

📎 Artifacts

Architecture Test Results UART Log Kernel ISO
x64 XML Log ISO
arm64 XML Log ISO

📋 View full test summary | 📄 Kernel.cs

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

🧪 Virtio Tests

Cell x64 arm64
all ✅ 12/12 ✅ 19/24, 5 skip
virtio-mmio n/a ✅ 7/12, 5 skip
virtio-pci ✅ 12/12 ✅ 12/12

Skipped

  • arm64: 5 skipped: this cell presents virtio over MMIO (5)

📎 Artifacts

Architecture Test Results UART Log Kernel ISO
x64 XML Log ISO
arm64 XML Log ISO

📋 View full test summary | 📄 Kernel.cs

@github-actions

Copy link
Copy Markdown

🧪 Interrupts Tests

Cell x64 arm64
all ✅ 18/18 ✅ 31/54, 23 skip
bare ✅ 18/18 ⚠️ 8/18, 10 skip
bare+gicv2 n/a ✅ 10/18, 8 skip
bare+gicv3 n/a ✅ 13/18, 5 skip

Skipped

  • arm64: 23 skipped: no MSI routing in this cell (GICv2 has no ITS) (6), needs MSI routing and an MSI-X function no driver has enabled (arm64: virtio-net owns the only one) (6), self-IPI harness is x64-only (3), LAPIC MSI address contract is x64-only (3), LAPIC timer vector is x64-only (3), gic-version not pinned by this cell (2)

📎 Artifacts

Architecture Test Results UART Log Kernel ISO
x64 XML Log ISO
arm64 XML Log ISO

📋 View full test summary | 📄 Kernel.cs

@github-actions

Copy link
Copy Markdown

🧪 Runtime Tests

Cell x64 arm64
all ✅ 103/103 ✅ 103/103

📎 Artifacts

Architecture Test Results UART Log Kernel ISO
x64 XML Log ISO
arm64 XML Log ISO

📋 View full test summary | 📄 Kernel.cs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant