Compare commits
2
Commits
203528c8a7
...
48b9ed4001
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48b9ed4001 | ||
|
|
081ba1d74e |
@@ -284,12 +284,18 @@ pub fn build(b: *std.Build) void {
|
|||||||
const pci_class_module = b.addModule("pci-class", .{
|
const pci_class_module = b.addModule("pci-class", .{
|
||||||
.root_source_file = b.path("library/device/pci/pci-class.zig"),
|
.root_source_file = b.path("library/device/pci/pci-class.zig"),
|
||||||
});
|
});
|
||||||
|
// Shared CSV helpers (comment stripping, field iteration) for the /etc/*.csv
|
||||||
|
// config files — the device registry and the init service list both parse them.
|
||||||
|
const csv_module = b.addModule("csv", .{
|
||||||
|
.root_source_file = b.path("library/csv/csv.zig"),
|
||||||
|
});
|
||||||
// The device registry: parse /etc/devices.csv into match rules and bind a
|
// The device registry: parse /etc/devices.csv into match rules and bind a
|
||||||
// reported device to a driver — the data-driven, authoritative replacement for
|
// reported device to a driver — the data-driven, authoritative replacement for
|
||||||
// the manager's hand-written switch tables. Pure logic (no hardware, no
|
// the manager's hand-written switch tables. Pure logic (no hardware, no
|
||||||
// syscalls), so it unit-tests with plain `zig test`; the manager imports it.
|
// syscalls), so it unit-tests on the host; the manager imports it.
|
||||||
const device_registry_module = b.addModule("device-registry", .{
|
const device_registry_module = b.addModule("device-registry", .{
|
||||||
.root_source_file = b.path("library/device/registry/device-registry.zig"),
|
.root_source_file = b.path("library/device/registry/device-registry.zig"),
|
||||||
|
.imports = &.{.{ .name = "csv", .module = csv_module }},
|
||||||
});
|
});
|
||||||
// ACPI/PnP hardware-ID (_HID) names — the flat analog of pci-class for acpi_device
|
// ACPI/PnP hardware-ID (_HID) names — the flat analog of pci-class for acpi_device
|
||||||
// nodes. Also shared reference data.
|
// nodes. Also shared reference data.
|
||||||
@@ -646,6 +652,8 @@ pub fn build(b: *std.Build) void {
|
|||||||
// started in ring 3 by the kernel's user-ELF loader.
|
// started in ring 3 by the kernel's user-ELF loader.
|
||||||
const init_exe = addUserBinary(b, kernel_target, &default_imports, "init", "system/services/init/init.zig");
|
const init_exe = addUserBinary(b, kernel_target, &default_imports, "init", "system/services/init/init.zig");
|
||||||
programModule(init_exe).addImport("power-protocol", power_protocol_module);
|
programModule(init_exe).addImport("power-protocol", power_protocol_module);
|
||||||
|
// init parses its boot service list from /etc/init.csv with the shared csv helpers.
|
||||||
|
programModule(init_exe).addImport("csv", csv_module);
|
||||||
// init reads the same `serial` flag the kernel does: its liveness heartbeat is a
|
// init reads the same `serial` flag the kernel does: its liveness heartbeat is a
|
||||||
// serial/test-build diagnostic (the QEMU harness's init tests assert on it, and
|
// serial/test-build diagnostic (the QEMU harness's init tests assert on it, and
|
||||||
// -Dserial images emit it), so a flashable image runs a purely event-driven PID 1
|
// -Dserial images emit it), so a flashable image runs a purely event-driven PID 1
|
||||||
@@ -653,7 +661,8 @@ pub fn build(b: *std.Build) void {
|
|||||||
// the heartbeat stays present under test.
|
// the heartbeat stays present under test.
|
||||||
const init_options = b.addOptions();
|
const init_options = b.addOptions();
|
||||||
init_options.addOption(bool, "serial", serial);
|
init_options.addOption(bool, "serial", serial);
|
||||||
init_options.addOption(bool, "diagnose", diagnose);
|
// Which services init starts is no longer a comptime option: it reads /etc/init.csv,
|
||||||
|
// and -Ddiagnose selects which init.csv is bundled (see the `bundled` list below).
|
||||||
programModule(init_exe).addImport("build_options", init_options.createModule());
|
programModule(init_exe).addImport("build_options", init_options.createModule());
|
||||||
|
|
||||||
// --- the rest of the boot tree: /system services and drivers, /test fixtures ---
|
// --- the rest of the boot tree: /system services and drivers, /test fixtures ---
|
||||||
@@ -760,6 +769,10 @@ pub fn build(b: *std.Build) void {
|
|||||||
// structure is the single source of truth. Entry names (and hence argv[0] and
|
// structure is the single source of truth. Entry names (and hence argv[0] and
|
||||||
// task names) are these paths with a leading slash. Test fixtures mirror their
|
// task names) are these paths with a leading slash. Test fixtures mirror their
|
||||||
// repo home: test/system/services/<name> in the source tree IS the boot path.
|
// repo home: test/system/services/<name> in the source tree IS the boot path.
|
||||||
|
// init's boot service list is data (/etc/init.csv). -Ddiagnose selects the
|
||||||
|
// variant that omits the display stack (so the kernel's boot transcript stays
|
||||||
|
// on screen); both are bundled at the same /etc/init.csv path.
|
||||||
|
const init_csv_source = if (diagnose) "etc/init-diagnose.csv" else "etc/init.csv";
|
||||||
const bundled = [_]BundledBinary{
|
const bundled = [_]BundledBinary{
|
||||||
.{ .path = "system/services/init", .binary = init_exe.getEmittedBin() },
|
.{ .path = "system/services/init", .binary = init_exe.getEmittedBin() },
|
||||||
.{ .path = "system/services/fat", .binary = fat_exe.getEmittedBin() },
|
.{ .path = "system/services/fat", .binary = fat_exe.getEmittedBin() },
|
||||||
@@ -774,6 +787,8 @@ pub fn build(b: *std.Build) void {
|
|||||||
// initrd tree (system/kernel/vfs.zig setInitialRamdisk), so the manager can
|
// initrd tree (system/kernel/vfs.zig setInitialRamdisk), so the manager can
|
||||||
// fs.open("/etc/devices.csv") with no filesystem service running.
|
// fs.open("/etc/devices.csv") with no filesystem service running.
|
||||||
.{ .path = "etc/devices.csv", .binary = b.path("etc/devices.csv") },
|
.{ .path = "etc/devices.csv", .binary = b.path("etc/devices.csv") },
|
||||||
|
// init's service list, likewise read from the kernel-served initrd /etc.
|
||||||
|
.{ .path = "etc/init.csv", .binary = b.path(init_csv_source) },
|
||||||
.{ .path = "system/drivers/ps2-bus", .binary = ps2_bus_exe.getEmittedBin() },
|
.{ .path = "system/drivers/ps2-bus", .binary = ps2_bus_exe.getEmittedBin() },
|
||||||
.{ .path = "system/drivers/ps2-keyboard", .binary = ps2_keyboard_exe.getEmittedBin() },
|
.{ .path = "system/drivers/ps2-keyboard", .binary = ps2_keyboard_exe.getEmittedBin() },
|
||||||
.{ .path = "system/drivers/ps2-mouse", .binary = ps2_mouse_exe.getEmittedBin() },
|
.{ .path = "system/drivers/ps2-mouse", .binary = ps2_mouse_exe.getEmittedBin() },
|
||||||
@@ -1088,7 +1103,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
"library/device/acpi/aml/aml.zig", // AML parse + interpret, incl. Notify dispatch (M21)
|
"library/device/acpi/aml/aml.zig", // AML parse + interpret, incl. Notify dispatch (M21)
|
||||||
"library/device/usb/usb-abi.zig", // wire sizes + bit packings + set-up packet encodings
|
"library/device/usb/usb-abi.zig", // wire sizes + bit packings + set-up packet encodings
|
||||||
"library/device/usb/usb-ids.zig", // class/subclass/protocol code assignments
|
"library/device/usb/usb-ids.zig", // class/subclass/protocol code assignments
|
||||||
"library/device/registry/device-registry.zig", // /etc/devices.csv parse + most-specific driver match
|
"library/csv/csv.zig", // shared /etc/*.csv comment-strip + field-split helpers
|
||||||
"library/device/mmio/mmio.zig", // barriers assemble + registers round-trip
|
"library/device/mmio/mmio.zig", // barriers assemble + registers round-trip
|
||||||
"system/drivers/ps2-bus/scancode.zig", // set-2 decode + keyboard state machine
|
"system/drivers/ps2-bus/scancode.zig", // set-2 decode + keyboard state machine
|
||||||
"system/drivers/ps2-bus/mouse-packet.zig", // 3-byte mouse packet assembly
|
"system/drivers/ps2-bus/mouse-packet.zig", // 3-byte mouse packet assembly
|
||||||
@@ -1113,6 +1128,19 @@ pub fn build(b: *std.Build) void {
|
|||||||
test_step.dependOn(&b.addRunArtifact(mod_tests).step);
|
test_step.dependOn(&b.addRunArtifact(mod_tests).step);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The device registry imports the shared `csv` module, so its tests need that
|
||||||
|
// import wired and don't fit the plain loop above. These prove the /etc/devices.csv
|
||||||
|
// parse + most-specific driver match (incl. virtio 1AF4:1050 beating a class rule).
|
||||||
|
const device_registry_tests = b.addTest(.{
|
||||||
|
.root_module = b.createModule(.{
|
||||||
|
.root_source_file = b.path("library/device/registry/device-registry.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.imports = &.{.{ .name = "csv", .module = csv_module }},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
test_step.dependOn(&b.addRunArtifact(device_registry_tests).step);
|
||||||
|
|
||||||
// The xkeyboard-config keymap tests need its generated `layouts` import wired, so they
|
// The xkeyboard-config keymap tests need its generated `layouts` import wired, so they
|
||||||
// don't fit the plain loop above. Its keycode->character assertions are the end-to-end
|
// don't fit the plain loop above. Its keycode->character assertions are the end-to-end
|
||||||
// proof that the xkb-data -> generator -> Zig-lookup pipeline is correct.
|
// proof that the xkb-data -> generator -> Zig-lookup pipeline is correct.
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# /etc/init.csv — diagnose variant (-Ddiagnose), bundled at /etc/init.csv.
|
||||||
|
#
|
||||||
|
# The display stack (display, display-demo) is omitted so the kernel's timestamped
|
||||||
|
# on-screen boot transcript is never suppressed — the bring-up timeline (USB,
|
||||||
|
# storage, logger) stays readable on real hardware with no serial. See etc/init.csv
|
||||||
|
# for the format; this file must otherwise track it.
|
||||||
|
#
|
||||||
|
# service args...
|
||||||
|
/system/services/input
|
||||||
|
/system/services/device-manager
|
||||||
|
/system/services/fat
|
||||||
|
/system/services/logger
|
||||||
|
@@ -0,0 +1,20 @@
|
|||||||
|
# /etc/init.csv — the services init (PID 1) starts at boot, in order.
|
||||||
|
#
|
||||||
|
# init reads this at startup and spawns each service supervised (restarting it on
|
||||||
|
# a crash, up to a cap). Startup order is top->bottom; shutdown is the reverse, so
|
||||||
|
# the logger (last) goes down first and its final drain still has the fat server
|
||||||
|
# and the whole storage chain alive underneath it. It is AUTHORITATIVE — there is
|
||||||
|
# no hardcoded fallback list; a missing file means no services are started.
|
||||||
|
#
|
||||||
|
# '#' starts a comment (whole-line or trailing); blank lines are ignored. The
|
||||||
|
# first field is the service binary path; any fields after it are the service's
|
||||||
|
# argv. Drivers are absent on purpose — the device manager discovers hardware and
|
||||||
|
# spawns those (see /etc/devices.csv).
|
||||||
|
#
|
||||||
|
# service args...
|
||||||
|
/system/services/input
|
||||||
|
/system/services/device-manager
|
||||||
|
/system/services/fat
|
||||||
|
/system/services/display
|
||||||
|
/system/services/display-demo
|
||||||
|
/system/services/logger
|
||||||
|
@@ -0,0 +1,56 @@
|
|||||||
|
//! Minimal CSV helpers shared by the `/etc/*.csv` config files — the device
|
||||||
|
//! registry (`/etc/devices.csv`) and the init service list (`/etc/init.csv`).
|
||||||
|
//! Freestanding, no allocator: returned fields are slices into the source line,
|
||||||
|
//! so the source must outlive them. `#` starts a comment (whole-line or trailing);
|
||||||
|
//! whitespace around a field is trimmed, so columns may be padded for alignment.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// Strip a trailing `#` comment and surrounding whitespace from one raw line.
|
||||||
|
/// A blank or comment-only line returns "" (length 0) — the caller's skip signal.
|
||||||
|
pub fn stripComment(raw: []const u8) []const u8 {
|
||||||
|
const body = if (std.mem.indexOfScalar(u8, raw, '#')) |hash| raw[0..hash] else raw;
|
||||||
|
return std.mem.trim(u8, body, " \t\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate the comma-separated fields of a line body, each trimmed of spaces and
|
||||||
|
/// tabs. Build it from a `stripComment`ed body.
|
||||||
|
pub const Fields = struct {
|
||||||
|
inner: std.mem.SplitIterator(u8, .scalar),
|
||||||
|
|
||||||
|
/// The next field, trimmed, or null when the row is exhausted.
|
||||||
|
pub fn next(self: *Fields) ?[]const u8 {
|
||||||
|
const field = self.inner.next() orelse return null;
|
||||||
|
return std.mem.trim(u8, field, " \t");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn fields(body: []const u8) Fields {
|
||||||
|
return .{ .inner = std.mem.splitScalar(u8, body, ',') };
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- tests -------------------------------------------------------------------
|
||||||
|
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
test "stripComment trims and drops comments" {
|
||||||
|
try testing.expectEqualStrings("a, b", stripComment(" a, b # trailing\r\n"));
|
||||||
|
try testing.expectEqualStrings("", stripComment(" # whole-line comment"));
|
||||||
|
try testing.expectEqualStrings("", stripComment(" \t "));
|
||||||
|
try testing.expectEqualStrings("x", stripComment("x"));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "fields splits and trims each column" {
|
||||||
|
var it = fields(stripComment("pci, 03 , 80 , /system/drivers/x # note"));
|
||||||
|
try testing.expectEqualStrings("pci", it.next().?);
|
||||||
|
try testing.expectEqualStrings("03", it.next().?);
|
||||||
|
try testing.expectEqualStrings("80", it.next().?);
|
||||||
|
try testing.expectEqualStrings("/system/drivers/x", it.next().?);
|
||||||
|
try testing.expect(it.next() == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a single field yields one column then null" {
|
||||||
|
var it = fields(stripComment("/system/services/input"));
|
||||||
|
try testing.expectEqualStrings("/system/services/input", it.next().?);
|
||||||
|
try testing.expect(it.next() == null);
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
//! (e.g. "PNP0303") with the triple left blank. `driver` is a full ramdisk path.
|
//! (e.g. "PNP0303") with the triple left blank. `driver` is a full ramdisk path.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const csv = @import("csv");
|
||||||
|
|
||||||
/// Which bus a rule or a reported device belongs to. `unknown` is what an
|
/// Which bus a rule or a reported device belongs to. `unknown` is what an
|
||||||
/// unrecognised `bus` token parses to — such a rule never matches (its bus
|
/// unrecognised `bus` token parses to — such a rule never matches (its bus
|
||||||
@@ -168,12 +169,6 @@ pub const ParseResult = struct {
|
|||||||
truncated: bool,
|
truncated: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Strip a trailing `#` comment and surrounding whitespace from one raw line.
|
|
||||||
fn stripComment(raw: []const u8) []const u8 {
|
|
||||||
const body = if (std.mem.indexOfScalar(u8, raw, '#')) |hash| raw[0..hash] else raw;
|
|
||||||
return std.mem.trim(u8, body, " \t\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse one hex field into `T`, honouring `*`/empty as a wildcard (`null`) and
|
/// Parse one hex field into `T`, honouring `*`/empty as a wildcard (`null`) and
|
||||||
/// an optional `0x` prefix. Returns an error only for a genuinely unparsable
|
/// an optional `0x` prefix. Returns an error only for a genuinely unparsable
|
||||||
/// non-wildcard token, so the caller can mark the whole line malformed.
|
/// non-wildcard token, so the caller can mark the whole line malformed.
|
||||||
@@ -197,36 +192,36 @@ fn parseStringField(field: []const u8) ?[]const u8 {
|
|||||||
/// Classify and (if a rule) parse one line. Split out from `parse` so it can be
|
/// Classify and (if a rule) parse one line. Split out from `parse` so it can be
|
||||||
/// unit-tested directly. `line` is the raw line including no newline.
|
/// unit-tested directly. `line` is the raw line including no newline.
|
||||||
fn parseLine(line: []const u8) Line {
|
fn parseLine(line: []const u8) Line {
|
||||||
const body = stripComment(line);
|
const body = csv.stripComment(line);
|
||||||
if (body.len == 0) return .ignorable;
|
if (body.len == 0) return .ignorable;
|
||||||
|
|
||||||
// Nine comma-separated fields: bus, base, class, prog_if, vendor, device,
|
// Nine comma-separated fields (csv.fields trims each): bus, base, class,
|
||||||
// subsystem, hid, driver.
|
// prog_if, vendor, device, subsystem, hid, driver.
|
||||||
var fields: [9][]const u8 = undefined;
|
var cols: [9][]const u8 = undefined;
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
var it = std.mem.splitScalar(u8, body, ',');
|
var it = csv.fields(body);
|
||||||
while (it.next()) |field| {
|
while (it.next()) |field| {
|
||||||
if (count >= fields.len) return .malformed; // too many columns
|
if (count >= cols.len) return .malformed; // too many columns
|
||||||
fields[count] = field;
|
cols[count] = field;
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
if (count != fields.len) return .malformed; // too few columns
|
if (count != cols.len) return .malformed; // too few columns
|
||||||
|
|
||||||
const bus = Bus.fromToken(std.mem.trim(u8, fields[0], " \t"));
|
const bus = Bus.fromToken(cols[0]);
|
||||||
if (bus == .unknown) return .malformed;
|
if (bus == .unknown) return .malformed;
|
||||||
|
|
||||||
const driver = std.mem.trim(u8, fields[8], " \t");
|
const driver = cols[8];
|
||||||
if (driver.len == 0) return .malformed;
|
if (driver.len == 0) return .malformed;
|
||||||
|
|
||||||
return .{ .rule = .{
|
return .{ .rule = .{
|
||||||
.bus = bus,
|
.bus = bus,
|
||||||
.base = parseHexField(u8, fields[1]) catch return .malformed,
|
.base = parseHexField(u8, cols[1]) catch return .malformed,
|
||||||
.subclass = parseHexField(u8, fields[2]) catch return .malformed,
|
.subclass = parseHexField(u8, cols[2]) catch return .malformed,
|
||||||
.prog_if = parseHexField(u8, fields[3]) catch return .malformed,
|
.prog_if = parseHexField(u8, cols[3]) catch return .malformed,
|
||||||
.vendor = parseHexField(u16, fields[4]) catch return .malformed,
|
.vendor = parseHexField(u16, cols[4]) catch return .malformed,
|
||||||
.device = parseHexField(u16, fields[5]) catch return .malformed,
|
.device = parseHexField(u16, cols[5]) catch return .malformed,
|
||||||
.subsystem = parseHexField(u32, fields[6]) catch return .malformed,
|
.subsystem = parseHexField(u32, cols[6]) catch return .malformed,
|
||||||
.hid = parseStringField(fields[7]),
|
.hid = parseStringField(cols[7]),
|
||||||
.driver = driver,
|
.driver = driver,
|
||||||
} };
|
} };
|
||||||
}
|
}
|
||||||
@@ -262,13 +257,13 @@ const testing = std.testing;
|
|||||||
// the specific one must win. And a plain VGA adapter still falls to the generic
|
// the specific one must win. And a plain VGA adapter still falls to the generic
|
||||||
// rule. This is the whole point of widening the ABI to carry vendor/device.
|
// rule. This is the whole point of widening the ABI to carry vendor/device.
|
||||||
test "virtio device rule beats the generic display rule" {
|
test "virtio device rule beats the generic display rule" {
|
||||||
const csv =
|
const text =
|
||||||
\\# bus, base, class, prog_if, vendor, device, subsystem, hid, driver
|
\\# bus, base, class, prog_if, vendor, device, subsystem, hid, driver
|
||||||
\\pci, 03, 00, 00, *, *, *, *, /system/drivers/display
|
\\pci, 03, 00, 00, *, *, *, *, /system/drivers/display
|
||||||
\\pci, 03, 80, *, 1AF4, 1050, *, *, /system/drivers/virtio-gpu
|
\\pci, 03, 80, *, 1AF4, 1050, *, *, /system/drivers/virtio-gpu
|
||||||
;
|
;
|
||||||
var rules: [8]Rule = undefined;
|
var rules: [8]Rule = undefined;
|
||||||
const parsed = parse(csv, &rules);
|
const parsed = parse(text, &rules);
|
||||||
try testing.expectEqual(@as(usize, 2), parsed.count);
|
try testing.expectEqual(@as(usize, 2), parsed.count);
|
||||||
try testing.expectEqual(@as(usize, 0), parsed.malformed);
|
try testing.expectEqual(@as(usize, 0), parsed.malformed);
|
||||||
|
|
||||||
@@ -289,9 +284,9 @@ test "virtio device rule beats the generic display rule" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "no matching row leaves the device unbound" {
|
test "no matching row leaves the device unbound" {
|
||||||
const csv = "pci, 0C, 03, 30, *, *, *, *, /system/drivers/usb-xhci-bus\n";
|
const text = "pci, 0C, 03, 30, *, *, *, *, /system/drivers/usb-xhci-bus\n";
|
||||||
var rules: [8]Rule = undefined;
|
var rules: [8]Rule = undefined;
|
||||||
const parsed = parse(csv, &rules);
|
const parsed = parse(text, &rules);
|
||||||
try testing.expectEqual(@as(usize, 1), parsed.count);
|
try testing.expectEqual(@as(usize, 1), parsed.count);
|
||||||
|
|
||||||
// An AHCI controller (mass storage / SATA / AHCI) has no row — unbound.
|
// An AHCI controller (mass storage / SATA / AHCI) has no row — unbound.
|
||||||
@@ -302,12 +297,12 @@ test "no matching row leaves the device unbound" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "acpi rows match on hid" {
|
test "acpi rows match on hid" {
|
||||||
const csv =
|
const text =
|
||||||
\\acpi, *, *, *, *, *, *, PNP0303, /system/drivers/ps2-bus
|
\\acpi, *, *, *, *, *, *, PNP0303, /system/drivers/ps2-bus
|
||||||
\\acpi, *, *, *, *, *, *, PNP0F13, /system/drivers/ps2-bus
|
\\acpi, *, *, *, *, *, *, PNP0F13, /system/drivers/ps2-bus
|
||||||
;
|
;
|
||||||
var rules: [8]Rule = undefined;
|
var rules: [8]Rule = undefined;
|
||||||
const parsed = parse(csv, &rules);
|
const parsed = parse(text, &rules);
|
||||||
try testing.expectEqual(@as(usize, 2), parsed.count);
|
try testing.expectEqual(@as(usize, 2), parsed.count);
|
||||||
|
|
||||||
const keyboard = matchDriver(rules[0..parsed.count], .{ .bus = .acpi, .hid = "PNP0303" }).?;
|
const keyboard = matchDriver(rules[0..parsed.count], .{ .bus = .acpi, .hid = "PNP0303" }).?;
|
||||||
@@ -317,12 +312,12 @@ test "acpi rows match on hid" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "equally specific rules flag ambiguity" {
|
test "equally specific rules flag ambiguity" {
|
||||||
const csv =
|
const text =
|
||||||
\\pci, 03, 00, 00, *, *, *, *, /system/drivers/display-a
|
\\pci, 03, 00, 00, *, *, *, *, /system/drivers/display-a
|
||||||
\\pci, 03, 00, 00, *, *, *, *, /system/drivers/display-b
|
\\pci, 03, 00, 00, *, *, *, *, /system/drivers/display-b
|
||||||
;
|
;
|
||||||
var rules: [8]Rule = undefined;
|
var rules: [8]Rule = undefined;
|
||||||
const parsed = parse(csv, &rules);
|
const parsed = parse(text, &rules);
|
||||||
const hit = matchDriver(rules[0..parsed.count], .{
|
const hit = matchDriver(rules[0..parsed.count], .{
|
||||||
.bus = .pci, .base = 0x03, .subclass = 0x00, .prog_if = 0x00,
|
.bus = .pci, .base = 0x03, .subclass = 0x00, .prog_if = 0x00,
|
||||||
}).?;
|
}).?;
|
||||||
@@ -331,7 +326,7 @@ test "equally specific rules flag ambiguity" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "comments, blanks, and malformed lines" {
|
test "comments, blanks, and malformed lines" {
|
||||||
const csv =
|
const text =
|
||||||
\\# a header comment
|
\\# a header comment
|
||||||
\\
|
\\
|
||||||
\\pci, 0C, 03, 30, *, *, *, *, /system/drivers/usb-xhci-bus # trailing comment
|
\\pci, 0C, 03, 30, *, *, *, *, /system/drivers/usb-xhci-bus # trailing comment
|
||||||
@@ -340,7 +335,7 @@ test "comments, blanks, and malformed lines" {
|
|||||||
\\bogus-bus, *, *, *, *, *, *, *, /system/drivers/x
|
\\bogus-bus, *, *, *, *, *, *, *, /system/drivers/x
|
||||||
;
|
;
|
||||||
var rules: [8]Rule = undefined;
|
var rules: [8]Rule = undefined;
|
||||||
const parsed = parse(csv, &rules);
|
const parsed = parse(text, &rules);
|
||||||
try testing.expectEqual(@as(usize, 1), parsed.count); // only the xhci row is valid
|
try testing.expectEqual(@as(usize, 1), parsed.count); // only the xhci row is valid
|
||||||
try testing.expectEqual(@as(usize, 3), parsed.malformed); // bad hex, empty driver, bad bus
|
try testing.expectEqual(@as(usize, 3), parsed.malformed); // bad hex, empty driver, bad bus
|
||||||
try testing.expectEqualStrings("/system/drivers/usb-xhci-bus", rules[0].driver);
|
try testing.expectEqualStrings("/system/drivers/usb-xhci-bus", rules[0].driver);
|
||||||
|
|||||||
@@ -21,19 +21,20 @@ const logging = @import("logging");
|
|||||||
const device_manager_protocol = @import("device-manager-protocol");
|
const device_manager_protocol = @import("device-manager-protocol");
|
||||||
const pci_class = @import("pci-class");
|
const pci_class = @import("pci-class");
|
||||||
|
|
||||||
/// Log a discovered function with its (class / subclass / prog-IF) triple decoded
|
/// Log a discovered function as its would-be /etc/devices.csv columns (bus, base,
|
||||||
/// to human names — the boot-log breadcrumb that says *what* the hardware is, so
|
/// class, prog_if, vendor, device, subsystem) followed by the human-readable
|
||||||
/// "class 0x01 (Mass Storage Controller) subclass 0x06 (Serial ATA Controller)
|
/// class/subclass/prog-IF names — so a row for a new driver reads straight off the
|
||||||
/// progif 0x01 (AHCI 1.0)" reads straight off the log when writing a new driver.
|
/// boot log. `subsystem` prints as `*` when the function has none, matching the CSV
|
||||||
/// A dedicated wider buffer than `writeLine`'s, since the decoded names are long.
|
/// wildcard. All read unclaimed, through the bridge's ECAM: the enumerator never
|
||||||
fn logFunction(bus: u64, dev: u64, function: u64, class_triple: u32) void {
|
/// claims the functions it probes (pci.zig's header — the device-owned pci.Function
|
||||||
|
/// view is what needs a claim, not this one). A wide buffer: the names are long.
|
||||||
|
fn logFunction(bus: u64, dev: u64, function: u64, class_triple: u32, vendor_id: u16, product_id: u16, subsystem: u32) void {
|
||||||
const cc = pci_class.ClassCode.unpack(@truncate(class_triple));
|
const cc = pci_class.ClassCode.unpack(@truncate(class_triple));
|
||||||
const pif = pci_class.progIfName(cc.base, cc.subclass, cc.prog_if);
|
const pif = pci_class.progIfName(cc.base, cc.subclass, cc.prog_if);
|
||||||
var line: [200]u8 = undefined;
|
var sub_buffer: [8]u8 = undefined;
|
||||||
const text = if (pif.len != 0)
|
const sub = if (subsystem == 0) "*" else std.fmt.bufPrint(&sub_buffer, "{X:0>8}", .{subsystem}) catch "*";
|
||||||
std.fmt.bufPrint(&line, "/system/drivers/pci-bus: {d}:{d}.{d} class 0x{x:0>2} ({s}) subclass 0x{x:0>2} ({s}) progif 0x{x:0>2} ({s})\n", .{ bus, dev, function, cc.base, pci_class.className(cc.base), cc.subclass, pci_class.subclassName(cc.base, cc.subclass), cc.prog_if, pif }) catch return
|
var line: [320]u8 = undefined;
|
||||||
else
|
const text = std.fmt.bufPrint(&line, "/system/drivers/pci-bus: {d}:{d}.{d} bus=pci base={X:0>2} class={X:0>2} prog_if={X:0>2} vendor={X:0>4} device={X:0>4} subsystem={s} — {s} / {s}{s}{s}\n", .{ bus, dev, function, cc.base, cc.subclass, cc.prog_if, vendor_id, product_id, sub, pci_class.className(cc.base), pci_class.subclassName(cc.base, cc.subclass), if (pif.len != 0) " / " else "", pif }) catch return;
|
||||||
std.fmt.bufPrint(&line, "/system/drivers/pci-bus: {d}:{d}.{d} class 0x{x:0>2} ({s}) subclass 0x{x:0>2} ({s}) progif 0x{x:0>2}\n", .{ bus, dev, function, cc.base, pci_class.className(cc.base), cc.subclass, pci_class.subclassName(cc.base, cc.subclass), cc.prog_if }) catch return;
|
|
||||||
_ = logging.write(text);
|
_ = logging.write(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +137,6 @@ fn scan() void {
|
|||||||
if (vendor_device & 0xFFFF == 0xFFFF) continue;
|
if (vendor_device & 0xFFFF == 0xFFFF) continue;
|
||||||
const class_revision = configRead(bus, dev, function, 0x08);
|
const class_revision = configRead(bus, dev, function, 0x08);
|
||||||
found += 1;
|
found += 1;
|
||||||
logFunction(bus, dev, function, class_revision >> 8);
|
|
||||||
registerAndReport(bus, dev, function, class_revision >> 8);
|
registerAndReport(bus, dev, function, class_revision >> 8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,6 +221,10 @@ fn registerAndReport(bus: u64, dev: u64, function: u64, class_triple: u32) void
|
|||||||
configWrite16(bus, dev, function, 0x04, command);
|
configWrite16(bus, dev, function, 0x04, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The devices.csv-column + friendly-name breadcrumb, now that vendor/device/
|
||||||
|
// subsystem are read. Every discovered function is logged, matched or not.
|
||||||
|
logFunction(bus, dev, function, class_triple, descriptor.vendor, descriptor.device, descriptor.subsystem);
|
||||||
|
|
||||||
const registered = device.register(bridge_id, &descriptor) orelse {
|
const registered = device.register(bridge_id, &descriptor) orelse {
|
||||||
std.log.info("register refused for {d}:{d}.{d}", .{ bus, dev, function });
|
std.log.info("register refused for {d}:{d}.{d}", .{ bus, dev, function });
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -390,13 +390,16 @@ fn reportInterface(manager: ipc.Handle, port: u32, interface: library.InterfaceI
|
|||||||
std.log.info("child report for port {d} interface {d} failed", .{ port, interface.number });
|
std.log.info("child report for port {d} interface {d} failed", .{ port, interface.number });
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
std.log.info("port {d} interface {d}: {s} ({d}/{d}/{d}) registered as device {d}", .{
|
// The devices.csv columns (bus=usb, and the class triple as base/class/prog_if)
|
||||||
|
// then the human-readable interface name — a would-be /etc/devices.csv row read
|
||||||
|
// straight off the boot log.
|
||||||
|
std.log.info("port {d} interface {d} bus=usb base={X:0>2} class={X:0>2} prog_if={X:0>2} — {s} registered as device {d}", .{
|
||||||
port,
|
port,
|
||||||
interface.number,
|
interface.number,
|
||||||
usb_ids.interfaceName(interface.class, interface.subclass, interface.protocol),
|
|
||||||
interface.class,
|
interface.class,
|
||||||
interface.subclass,
|
interface.subclass,
|
||||||
interface.protocol,
|
interface.protocol,
|
||||||
|
usb_ids.interfaceName(interface.class, interface.subclass, interface.protocol),
|
||||||
registered,
|
registered,
|
||||||
});
|
});
|
||||||
return registered;
|
return registered;
|
||||||
|
|||||||
@@ -214,11 +214,13 @@ fn onInit(endpoint: ipc.Handle) bool {
|
|||||||
while (i < registered_count) : (i += 1) {
|
while (i < registered_count) : (i += 1) {
|
||||||
const entry = registered[i];
|
const entry = registered[i];
|
||||||
const hid = entry.hid[0..entry.hid_len];
|
const hid = entry.hid[0..entry.hid_len];
|
||||||
|
// The devices.csv columns (bus=acpi, hid) then the human-readable name — a
|
||||||
|
// would-be /etc/devices.csv row read straight off the boot log.
|
||||||
const desc = acpi_ids.description(hid);
|
const desc = acpi_ids.description(hid);
|
||||||
if (desc.len != 0)
|
if (desc.len != 0)
|
||||||
std.log.info("reported {s} (device {d}, {d} resources) — {s}", .{ hid, entry.device_id, entry.resource_count, desc })
|
std.log.info("device {d} bus=acpi hid={s} — {s} ({d} resources)", .{ entry.device_id, hid, desc, entry.resource_count })
|
||||||
else
|
else
|
||||||
std.log.info("reported {s} (device {d}, {d} resources)", .{ hid, entry.device_id, entry.resource_count });
|
std.log.info("device {d} bus=acpi hid={s} ({d} resources)", .{ entry.device_id, hid, entry.resource_count });
|
||||||
if (manager) |h| {
|
if (manager) |h| {
|
||||||
var report = device_manager_protocol.ChildAdded{ .bus = @intFromEnum(device_manager_protocol.BusKind.acpi), .parent = node_id, .bus_address = entry.device_id, .identity = 0, .device_id = entry.device_id };
|
var report = device_manager_protocol.ChildAdded{ .bus = @intFromEnum(device_manager_protocol.BusKind.acpi), .parent = node_id, .bus_address = entry.device_id, .identity = 0, .device_id = entry.device_id };
|
||||||
@memcpy(report.hid[0..entry.hid_len], entry.hid[0..entry.hid_len]);
|
@memcpy(report.hid[0..entry.hid_len], entry.hid[0..entry.hid_len]);
|
||||||
|
|||||||
@@ -25,42 +25,88 @@ const memory = @import("memory");
|
|||||||
const logging = @import("logging");
|
const logging = @import("logging");
|
||||||
const power_protocol = @import("power-protocol");
|
const power_protocol = @import("power-protocol");
|
||||||
const build_options = @import("build_options");
|
const build_options = @import("build_options");
|
||||||
|
const fs = @import("file-system");
|
||||||
|
const csv = @import("csv");
|
||||||
|
|
||||||
/// The system services init brings up at boot, in order, by binary path. This is
|
/// The system services init brings up at boot are init's policy, not the kernel's —
|
||||||
/// init's policy — the microkernel keeps such choices in user space, not the
|
/// and that policy is now data: `/etc/init.csv` (see `loadServices`), read at
|
||||||
/// kernel. Drivers are absent on purpose: the device manager owns those. (A
|
/// startup instead of a hardcoded list. Drivers are absent on purpose: the device
|
||||||
/// future init reads this from a manifest under /system/services instead of a
|
/// manager owns those.
|
||||||
/// hardcoded list.)
|
///
|
||||||
const boot_services = if (build_options.diagnose) [_][]const u8{
|
/// The most services `/etc/init.csv` can list, and the most argv entries (beyond the
|
||||||
// The diagnose boot: no display service, so the kernel's on-screen boot
|
/// path) each may carry. Fixed caps because init parses the list into static storage —
|
||||||
// transcript is never suppressed — the timestamped timeline (USB bring-up,
|
/// the freestanding, no-allocator counterpart to the device manager's registry table.
|
||||||
// storage, logger) stays readable on real hardware with no serial.
|
const max_services = 16;
|
||||||
"/system/services/input",
|
const max_service_args = 4;
|
||||||
"/system/services/device-manager",
|
|
||||||
"/system/services/fat",
|
/// One service init starts, parsed from a row of `/etc/init.csv`: its binary path
|
||||||
"/system/services/logger",
|
/// and argv, both slices into `init_csv` (held for the life of the process).
|
||||||
} else [_][]const u8{
|
const Service = struct {
|
||||||
"/system/services/input",
|
path: []const u8 = "",
|
||||||
"/system/services/device-manager",
|
arg_buffer: [max_service_args][]const u8 = undefined,
|
||||||
"/system/services/fat",
|
arg_count: usize = 0,
|
||||||
"/system/services/display",
|
fn arguments(self: *const Service) []const []const u8 {
|
||||||
"/system/services/display-demo",
|
return self.arg_buffer[0..self.arg_count];
|
||||||
// Last: at shutdown children stop in reverse order, so the logger goes down
|
}
|
||||||
// FIRST — its final drain still has the fat server (and the whole storage
|
|
||||||
// chain) alive underneath it.
|
|
||||||
"/system/services/logger",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The live process id of each boot service (0 = not running), indexed by its position
|
/// The `/etc/init.csv` bytes, held because the parsed services slice into them.
|
||||||
/// in `boot_services`, plus how many times init has restarted it. init supervises these:
|
var init_csv: [4096]u8 = undefined;
|
||||||
/// it spawns them against `supervision_endpoint` and, on a child's death, restarts it (up
|
var services: [max_services]Service = .{Service{}} ** max_services;
|
||||||
/// to `maximum_restarts`) — the reincarnation half of resilience (docs/resilience.md), the
|
var service_count: usize = 0;
|
||||||
/// service-level counterpart to the device manager's driver restarts.
|
|
||||||
var child_ids: [boot_services.len]u32 = .{0} ** boot_services.len;
|
/// The live process id of each service (0 = not running) and its restart count,
|
||||||
var restart_counts: [boot_services.len]u32 = .{0} ** boot_services.len;
|
/// indexed by position in `services`. init supervises these: it spawns them against
|
||||||
|
/// `supervision_endpoint` and, on a child's death, restarts it (up to
|
||||||
|
/// `maximum_restarts`) — the reincarnation half of resilience (docs/resilience.md),
|
||||||
|
/// the service-level counterpart to the device manager's driver restarts.
|
||||||
|
var child_ids: [max_services]u32 = .{0} ** max_services;
|
||||||
|
var restart_counts: [max_services]u32 = .{0} ** max_services;
|
||||||
var shutting_down = false;
|
var shutting_down = false;
|
||||||
var supervision_endpoint: ipc.Handle = 0;
|
var supervision_endpoint: ipc.Handle = 0;
|
||||||
|
|
||||||
|
/// Parse `/etc/init.csv` into `services`, in file order (startup order; shutdown is
|
||||||
|
/// the reverse). Each row is a binary path followed by its argv, comma-separated;
|
||||||
|
/// `#` comments and blank lines are ignored. The file lives in the initial ramdisk,
|
||||||
|
/// which the kernel serves directly, so init — PID 1, running before any filesystem
|
||||||
|
/// service — reads it with a plain fs.open, the same mechanism the device manager
|
||||||
|
/// uses for /etc/devices.csv. A missing file means no services (the no-ramdisk
|
||||||
|
/// isolation test): loud, but not fatal.
|
||||||
|
fn loadServices() void {
|
||||||
|
var file = fs.open("/etc/init.csv", .{}) orelse {
|
||||||
|
_ = logging.write("/system/services/init: /etc/init.csv missing — no services started\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
defer file.close();
|
||||||
|
var used: usize = 0;
|
||||||
|
while (used < init_csv.len) {
|
||||||
|
const n = file.read(init_csv[used..]) orelse break;
|
||||||
|
if (n == 0) break;
|
||||||
|
used += n;
|
||||||
|
}
|
||||||
|
var lines = std.mem.splitScalar(u8, init_csv[0..used], '\n');
|
||||||
|
while (lines.next()) |line| {
|
||||||
|
const body = csv.stripComment(line);
|
||||||
|
if (body.len == 0) continue;
|
||||||
|
if (service_count >= services.len) {
|
||||||
|
_ = logging.write("/system/services/init: /etc/init.csv has more services than the table holds\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
var it = csv.fields(body);
|
||||||
|
const path = it.next() orelse continue;
|
||||||
|
if (path.len == 0) continue;
|
||||||
|
var service: Service = .{ .path = path };
|
||||||
|
while (it.next()) |argument| {
|
||||||
|
if (argument.len == 0) continue; // padding, or a trailing comma
|
||||||
|
if (service.arg_count >= max_service_args) break;
|
||||||
|
service.arg_buffer[service.arg_count] = argument;
|
||||||
|
service.arg_count += 1;
|
||||||
|
}
|
||||||
|
services[service_count] = service;
|
||||||
|
service_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Give up restarting a service after this many crashes — a crash-loop cap, so a service
|
/// Give up restarting a service after this many crashes — a crash-loop cap, so a service
|
||||||
/// that faults immediately on every spawn doesn't respawn forever.
|
/// that faults immediately on every spawn doesn't respawn forever.
|
||||||
const maximum_restarts = 3;
|
const maximum_restarts = 3;
|
||||||
@@ -89,11 +135,12 @@ pub fn main() void {
|
|||||||
};
|
};
|
||||||
_ = process.bindSignals(supervision_endpoint);
|
_ = process.bindSignals(supervision_endpoint);
|
||||||
|
|
||||||
// Bring up the boot services, supervised so init can stop them cleanly.
|
// Load the service list, then bring each up supervised so init can stop them
|
||||||
// Best-effort and silent: each service announces its own readiness, and in
|
// cleanly. Best-effort and silent: each service announces its own readiness,
|
||||||
// an isolation test with no initial-ramdisk the spawns simply no-op.
|
// and with no /etc/init.csv (an isolation test) the loop starts nothing.
|
||||||
for (boot_services, 0..) |service, i| {
|
loadServices();
|
||||||
if (process.spawnSupervised(service, &.{}, supervision_endpoint)) |id| child_ids[i] = id;
|
for (services[0..service_count], 0..) |*service, i| {
|
||||||
|
if (process.spawnSupervised(service.path, service.arguments(), supervision_endpoint)) |id| child_ids[i] = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscribe to power events (retry: the power service registers well after
|
// Subscribe to power events (retry: the power service registers well after
|
||||||
@@ -141,22 +188,22 @@ pub fn main() void {
|
|||||||
/// iron rule 1); init only decides whether to bring it back.
|
/// iron rule 1); init only decides whether to bring it back.
|
||||||
fn restartChild(id: u32) void {
|
fn restartChild(id: u32) void {
|
||||||
if (shutting_down) return; // deaths during the stop sequence are expected, not crashes
|
if (shutting_down) return; // deaths during the stop sequence are expected, not crashes
|
||||||
for (boot_services, 0..) |service, i| {
|
for (services[0..service_count], 0..) |*service, i| {
|
||||||
if (child_ids[i] != id) continue;
|
if (child_ids[i] != id) continue;
|
||||||
child_ids[i] = 0;
|
child_ids[i] = 0;
|
||||||
// An unknown reason (the record aged out) is treated as a crash worth restarting.
|
// An unknown reason (the record aged out) is treated as a crash worth restarting.
|
||||||
const reason = process.exitReason(id) orelse .fault;
|
const reason = process.exitReason(id) orelse .fault;
|
||||||
if (reason == .exited) {
|
if (reason == .exited) {
|
||||||
std.log.info("{s} exited cleanly; not restarting", .{service});
|
std.log.info("{s} exited cleanly; not restarting", .{service.path});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
restart_counts[i] += 1;
|
restart_counts[i] += 1;
|
||||||
if (restart_counts[i] > maximum_restarts) {
|
if (restart_counts[i] > maximum_restarts) {
|
||||||
std.log.info("{s} keeps crashing; giving up after {d} restarts", .{ service, maximum_restarts });
|
std.log.info("{s} keeps crashing; giving up after {d} restarts", .{ service.path, maximum_restarts });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std.log.info("{s} died ({s}); restarting ({d}/{d})", .{ service, @tagName(reason), restart_counts[i], maximum_restarts });
|
std.log.info("{s} died ({s}); restarting ({d}/{d})", .{ service.path, @tagName(reason), restart_counts[i], maximum_restarts });
|
||||||
if (process.spawnSupervised(service, &.{}, supervision_endpoint)) |new_id| child_ids[i] = new_id;
|
if (process.spawnSupervised(service.path, service.arguments(), supervision_endpoint)) |new_id| child_ids[i] = new_id;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// An untracked child (e.g. the log-flush one-shot): nothing to restart.
|
// An untracked child (e.g. the log-flush one-shot): nothing to restart.
|
||||||
@@ -190,7 +237,7 @@ fn shutDown() void {
|
|||||||
// Log persistence is the logger service's job: it is the LAST boot service,
|
// Log persistence is the logger service's job: it is the LAST boot service,
|
||||||
// so the reverse-order stop below terminates it first and its final drain
|
// so the reverse-order stop below terminates it first and its final drain
|
||||||
// runs while the whole storage chain is still alive.
|
// runs while the whole storage chain is still alive.
|
||||||
var i = boot_services.len;
|
var i = service_count;
|
||||||
while (i > 0) {
|
while (i > 0) {
|
||||||
i -= 1;
|
i -= 1;
|
||||||
if (child_ids[i] != 0) process.stop(child_ids[i], 2000, supervision_endpoint);
|
if (child_ids[i] != 0) process.stop(child_ids[i], 2000, supervision_endpoint);
|
||||||
|
|||||||
+3
-3
@@ -628,7 +628,7 @@ CASES = [
|
|||||||
{"name": "acpi-ps2",
|
{"name": "acpi-ps2",
|
||||||
"smp": 4,
|
"smp": 4,
|
||||||
"timeout": 150,
|
"timeout": 150,
|
||||||
"expect": r"discovery: reported PNP0303[\s\S]*"
|
"expect": r"discovery: device \d+\s+bus=acpi hid=PNP0303[\s\S]*"
|
||||||
r"device-manager: spawned \S*ps2-bus[\s\S]*"
|
r"device-manager: spawned \S*ps2-bus[\s\S]*"
|
||||||
r"ps2-bus: keyboard driver attached",
|
r"ps2-bus: keyboard driver attached",
|
||||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||||
@@ -675,8 +675,8 @@ CASES = [
|
|||||||
{"name": "acpi-report",
|
{"name": "acpi-report",
|
||||||
"smp": 4,
|
"smp": 4,
|
||||||
"timeout": 150,
|
"timeout": 150,
|
||||||
"expect": r"discovery: reported PNP0303 \(device \d+, 3 resources\)[\s\S]*"
|
"expect": r"discovery: device \d+\s+bus=acpi hid=PNP0303[^\n]*\(3 resources\)[\s\S]*"
|
||||||
r"discovery: reported PNP0F13 \(device \d+, 1 resources\)",
|
r"discovery: device \d+\s+bus=acpi hid=PNP0F13[^\n]*\(1 resources\)",
|
||||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||||
# M19.1/M19.3: the ring-3 PCI scan. pci-bus walks the ECAM through its mmio_map
|
# M19.1/M19.3: the ring-3 PCI scan. pci-bus walks the ECAM through its mmio_map
|
||||||
# grant and registers every function it finds; the kernel's own walk retired, so
|
# grant and registers every function it finds; the kernel's own walk retired, so
|
||||||
|
|||||||
Reference in New Issue
Block a user