Discovery-migration prerequisites (M19.0)
The host bridge now carries MMIO apertures derived from the boot memory map's gaps below 4 GiB (largest three, sort-merged; a single after-the- last-region hole dies on OVMF's flash at the top) plus one aperture above the described space — so a user-space device_register of PCI functions with BAR resources can pass containment. The discovery test asserts every PCI memory resource lies inside a bridge window and names any escapee. device_register is idempotent on exact (parent, class, identity, resources) match — a restarted registering bus cannot duplicate its children; proven directly against the broker in the bus test. ChildAdded gains device_id so a report can carry the registered kernel id a matched driver needs as its assignment.
This commit is contained in:
@@ -180,6 +180,26 @@ pub fn register(parent_id: u64, owner: u32, descriptor: *const device_abi.Device
|
||||
if (!ok) return error.NotContained;
|
||||
}
|
||||
|
||||
// Idempotent on exact match (docs/m19-m20-plan.md decision 3): a restarted
|
||||
// registering bus re-registers what it rediscovers, and the table has no
|
||||
// unregister — an identical (class, identity, resources) child under the
|
||||
// same parent returns the existing id instead of appending a duplicate.
|
||||
for (devices[0..count]) |*existing| {
|
||||
if (existing.parent != parent_id) continue;
|
||||
if (existing.class != descriptor.class) continue;
|
||||
if (existing.pci_class != descriptor.pci_class) continue;
|
||||
if (existing.hid_len != descriptor.hid_len) continue;
|
||||
if (!std.mem.eql(u8, existing.hid[0..@intCast(existing.hid_len)], descriptor.hid[0..@intCast(descriptor.hid_len)])) continue;
|
||||
if (existing.resource_count != descriptor.resource_count) continue;
|
||||
var same = true;
|
||||
for (0..@intCast(descriptor.resource_count)) |i| {
|
||||
const a = existing.resources[i];
|
||||
const b = descriptor.resources[i];
|
||||
if (a.kind != b.kind or a.start != b.start or a.len != b.len) same = false;
|
||||
}
|
||||
if (same) return existing.id;
|
||||
}
|
||||
|
||||
var d = std.mem.zeroes(device_abi.DeviceDescriptor);
|
||||
d.id = count;
|
||||
d.parent = parent_id;
|
||||
|
||||
@@ -283,6 +283,34 @@ fn discoveryTest() void {
|
||||
check("PCI functions were enumerated (MCFG/ECAM)", pci_functions >= 1);
|
||||
check("each PCI function exposes its ECAM config space as resource 0", pci_config_ok);
|
||||
|
||||
// M19.0: every PCI memory resource (config slice and BARs alike) must be
|
||||
// contained in one of its parent bridge's windows — the aperture derivation
|
||||
// from the memory map is what makes a future user-space device_register of
|
||||
// these functions pass containment. This is the assert that catches a
|
||||
// too-coarse hole computation before M19.2 would.
|
||||
var bars_contained = true;
|
||||
for (buffer[0..n]) |d| {
|
||||
if (d.class != @intFromEnum(device_abi.DeviceClass.pci_device)) continue;
|
||||
if (d.parent >= n) {
|
||||
bars_contained = false;
|
||||
continue;
|
||||
}
|
||||
const bridge = buffer[@intCast(d.parent)];
|
||||
for (d.resources[0..@intCast(d.resource_count)]) |r| {
|
||||
if (r.kind != @intFromEnum(device_abi.ResourceKind.memory)) continue;
|
||||
var inside = false;
|
||||
for (bridge.resources[0..@intCast(bridge.resource_count)]) |w| {
|
||||
if (w.kind != @intFromEnum(device_abi.ResourceKind.memory)) continue;
|
||||
if (r.start >= w.start and r.start + r.len <= w.start + w.len) inside = true;
|
||||
}
|
||||
if (!inside) {
|
||||
bars_contained = false;
|
||||
log(" escaping BAR: 0x{x}+0x{x} on device {d}\n", .{ r.start, r.len, d.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
check("every PCI BAR lies inside a bridge aperture (M19.0)", bars_contained);
|
||||
|
||||
result();
|
||||
}
|
||||
|
||||
@@ -2085,6 +2113,35 @@ fn hpetGsi() ?u32 {
|
||||
/// land in the device table with the containment invariant intact.
|
||||
fn busTest(boot_information: *const BootInformation) void {
|
||||
log("DANOS-TEST-BEGIN: bus\n", .{});
|
||||
|
||||
// M19.0: device_register is idempotent on exact match — a restarted
|
||||
// registering bus must not duplicate its children. Driven directly against
|
||||
// the broker: claim an unclaimed node, register the same (class, hid,
|
||||
// resourceless) child twice, expect one id and one table entry.
|
||||
{
|
||||
const me = scheduler.currentId();
|
||||
var probe: [1]device_abi.DeviceDescriptor = undefined;
|
||||
const total = devices_broker.enumerate(&probe);
|
||||
check("device tree is seeded for the idempotence check", total >= 1);
|
||||
if (devices_broker.ownerOf(0) == null) {
|
||||
check("claimed device 0 for the idempotence check", devices_broker.claim(0, me));
|
||||
var child = std.mem.zeroes(device_abi.DeviceDescriptor);
|
||||
child.class = @intFromEnum(device_abi.DeviceClass.unknown);
|
||||
child.pci_class = device_abi.no_pci_class;
|
||||
child.hid_len = 4;
|
||||
child.hid[0..4].* = "idem".*;
|
||||
const first = devices_broker.register(0, me, &child) catch 0;
|
||||
check("first register succeeded", first != 0);
|
||||
const before = devices_broker.enumerate(&probe);
|
||||
const second = devices_broker.register(0, me, &child) catch 0;
|
||||
check("re-register returned the same id", second == first);
|
||||
check("re-register grew nothing", devices_broker.enumerate(&probe) == before);
|
||||
devices_broker.releaseAllOwnedBy(me);
|
||||
} else {
|
||||
check("device 0 unexpectedly claimed before the idempotence check", false);
|
||||
}
|
||||
}
|
||||
|
||||
if (boot_information.initial_ramdisk_len == 0) {
|
||||
check("bootloader handed over an initial_ramdisk", false);
|
||||
result();
|
||||
|
||||
Reference in New Issue
Block a user