library: the harness keeps the subscribers, and an id belongs to whoever opened it
Three services had each written the same thing and got it three different ways: input polled the process list to notice a dead subscriber, and only when someone else subscribed; the power service never noticed at all; the device manager noticed drivers but not subscribers. The harness owns the table now, driven by the events a protocol declares — it registers on the reserved verb, frames each event once, posts to everyone interested without waiting on any of them, and reclaims a slot when the kernel says its owner died. Interest masks moved to the envelope, so a subscriber that wants only mice asks the same way everywhere. Two consequences the plan had not foreseen. The device manager now hears a supervised child's death twice, once as its supervisor and once as a subscriber, so restart backoff counted every crash twice and gave up after half as many; it retires the id before counting. And the kernel's published exit table had eight slots for what is now six subscriptions in a plain boot, so it holds sixteen. The other half is a hole the design named early and left standing: a backend handed out a small integer and then honoured it from anyone. A process that guessed a file's node id read another client's file; a display layer had no owner at all, so any client could reconfigure or destroy any layer; a USB device token was never checked against the client that opened it. Each is now bound to the task that opened it, and a wrong owner gets exactly what an unknown id gets — the refusal must not become the oracle the identical answers elsewhere were designed to remove. Closing a file changed with it: it used to succeed unconditionally, which would have told a caller which ids existed. Suite 111/111, with a new case in which one process holds a file and a layer, hands both ids to a second process, and finds them untouched after that process has tried everything with them.
This commit is contained in:
@@ -60,15 +60,14 @@ const pwrbtn_bit: u16 = 1 << 8;
|
||||
const sci_en_bit: u32 = 1 << 0;
|
||||
const slp_en: u32 = 1 << 13;
|
||||
|
||||
// The `.power` subscribers: endpoints handed over as capabilities, each
|
||||
// receiving events as buffered messages. Dropped on a failed send. The
|
||||
// subscriber's task id is kept too — a shutdown request is honored only from a
|
||||
// subscriber (init subscribes; a stray process does not), the soft gate that
|
||||
// stands in for "only the system supervisor may power off" without hardcoding
|
||||
// a pid the kernel's idle tasks would have taken.
|
||||
const maximum_subscribers = 8;
|
||||
var subscribers: [maximum_subscribers]?ipc.Handle = .{null} ** maximum_subscribers;
|
||||
var subscriber_tasks: [maximum_subscribers]u32 = .{0} ** maximum_subscribers;
|
||||
/// The `.power` subscribers, kept by the service harness (P4c): endpoints handed
|
||||
/// over as capabilities, each receiving events as buffered messages, each swept
|
||||
/// when its task dies. The table remembers which task subscribed, which is what
|
||||
/// the shutdown gate below asks — a shutdown request is honored only from a
|
||||
/// subscriber (init subscribes; a stray process does not), the soft gate that
|
||||
/// stands in for "only the system supervisor may power off" without hardcoding a
|
||||
/// pid the kernel's idle tasks would have taken.
|
||||
const Subscriptions = service.Subscribers(power_protocol.Protocol, void);
|
||||
|
||||
// Pass-1 registration record (see main): what pass 2 reports.
|
||||
const Registered = struct { hid: [8]u8 = .{0} ** 8, hid_len: usize = 0, device_id: u64 = 0, resource_count: u64 = 0 };
|
||||
@@ -201,6 +200,7 @@ pub fn main(init: process.Init) void {
|
||||
.init = onInit,
|
||||
.on_message = onMessage,
|
||||
.on_notification = onNotification,
|
||||
.subscribers = Subscriptions.hooks,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -407,13 +407,13 @@ fn publishNotify(node: *aml.Node, code: u64) void {
|
||||
const notice = power_protocol.Notice{ .code = @truncate(code), .hid = hid };
|
||||
std.log.info("power: notify {s} code {d}", .{ hid[0..7], code });
|
||||
if (std.mem.eql(u8, hid[0..7], "PNP0C0A")) {
|
||||
publish(.battery, notice);
|
||||
Subscriptions.publish(.battery, 0, notice);
|
||||
} else if (std.mem.eql(u8, hid[0..7], "ACPI0003")) {
|
||||
publish(.ac, notice);
|
||||
Subscriptions.publish(.ac, 0, notice);
|
||||
} else if (std.mem.eql(u8, hid[0..7], "PNP0C0D")) {
|
||||
publish(.lid, notice);
|
||||
Subscriptions.publish(.lid, 0, notice);
|
||||
} else {
|
||||
publish(.notify, notice);
|
||||
Subscriptions.publish(.notify, 0, notice);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,28 +425,10 @@ fn writeHex2(out: []u8, n: u32) void {
|
||||
}
|
||||
|
||||
fn publishButton() void {
|
||||
publish(.power_button, .{});
|
||||
}
|
||||
|
||||
/// Push one event to every subscriber. The kind is the packet's operation, so
|
||||
/// this is framed once, outside the loop — every subscriber gets identical
|
||||
/// bytes. A subscriber whose endpoint stops accepting (it died) is dropped on
|
||||
/// the failed send, so a dead one can never stall the rest.
|
||||
fn publish(comptime kind: power_protocol.Event, notice: power_protocol.Notice) void {
|
||||
var packet: [envelope.post_maximum]u8 = undefined;
|
||||
const framed = power_protocol.Protocol.encodeEvent(kind, 0, notice, &packet) orelse return;
|
||||
for (&subscribers) |*slot| {
|
||||
if (slot.*) |handle| {
|
||||
if (!ipc.send(handle, framed)) slot.* = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn isSubscriber(task: u32) bool {
|
||||
for (&subscribers, 0..) |*slot, si| {
|
||||
if (slot.* != null and subscriber_tasks[si] == task) return true;
|
||||
}
|
||||
return false;
|
||||
// The kind is the packet's operation, so the harness frames it once and pushes
|
||||
// the same bytes to every subscriber. There is no class here: a power event
|
||||
// goes to everyone who asked for power events.
|
||||
Subscriptions.publish(.power_button, 0, .{});
|
||||
}
|
||||
|
||||
/// Enter S5 (soft off): write SLP_TYP|SLP_EN to the PM1 control register(s).
|
||||
@@ -468,57 +450,37 @@ fn enterS5() void {
|
||||
// --- harness callbacks --------------------------------------------------------
|
||||
|
||||
fn onNotification(badge: u64) void {
|
||||
// The only notification the service binds is the SCI (an IRQ badge).
|
||||
_ = badge;
|
||||
// Two kinds of notification reach this loop now. The SCI is the one this
|
||||
// service binds; the published process exits are the harness's, which it has
|
||||
// already used to sweep the subscriber table before calling here. Everything
|
||||
// that is not a bare IRQ badge must therefore be ignored — treating a death
|
||||
// as an interrupt would clear PM1 status the firmware never set.
|
||||
if (badge & (ipc.notify_exit_bit | ipc.notify_timer_bit | ipc.notify_message_bit | ipc.notify_signal_bit) != 0) return;
|
||||
onSci();
|
||||
}
|
||||
|
||||
/// The generated power dispatch. One provider per system, so the handler context
|
||||
/// is empty and the subscriber table stays in this file's globals.
|
||||
const Serve = power_protocol.Protocol.Provider(void);
|
||||
|
||||
const Invocation = envelope.Invocation;
|
||||
const Answer = envelope.Answer;
|
||||
|
||||
/// Set by `onSubscribe` when the subscriber table has taken the capability the
|
||||
/// call carried, and read by `onMessage`, where the turn's `Arrival` lives.
|
||||
var capability_claimed = false;
|
||||
|
||||
/// The power contract: the reserved `subscribe` (the subscriber's endpoint as
|
||||
/// the call's capability) and `shutdown` (subscribers only). Device discovery
|
||||
/// uses a different endpoint — the device manager's — so nothing here handles a
|
||||
/// tree report.
|
||||
/// the call's capability, answered by the harness) and `shutdown` (subscribers
|
||||
/// only). Device discovery uses a different endpoint — the device manager's — so
|
||||
/// nothing here handles a tree report.
|
||||
fn onMessage(message: []const u8, reply: []u8, sender: u32, arrived: *ipc.Arrival) usize {
|
||||
capability_claimed = false;
|
||||
const written = Serve.dispatch({}, handlers, message, sender, arrived.peek(), reply);
|
||||
if (capability_claimed) _ = arrived.take();
|
||||
return written;
|
||||
return Subscriptions.dispatch({}, handlers, message, sender, arrived, reply);
|
||||
}
|
||||
|
||||
const handlers = Serve.Handlers{ .shutdown = onShutdown, .subscribe = onSubscribe };
|
||||
|
||||
/// The subscriber's endpoint is claimed only when a slot takes it; a full table
|
||||
/// refuses and the turn closes what arrived.
|
||||
fn onSubscribe(_: void, invocation: Invocation(void), _: Answer(void)) isize {
|
||||
const endpoint = invocation.capability orelse return -envelope.EPROTO;
|
||||
for (&subscribers, 0..) |*slot, index| {
|
||||
if (slot.* == null) {
|
||||
slot.* = endpoint;
|
||||
subscriber_tasks[index] = invocation.sender;
|
||||
capability_claimed = true;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -envelope.ENOSPC;
|
||||
}
|
||||
/// `subscribe` and `unsubscribe` are absent on purpose: the harness answers both.
|
||||
const handlers = Subscriptions.Handlers{ .shutdown = onShutdown };
|
||||
|
||||
/// Honored only from a power subscriber — init, which has already run the stop
|
||||
/// sequence over everything else. The power service is mechanism (write S5);
|
||||
/// deciding *when* to shut down and stopping the rest of the system first is
|
||||
/// init's policy. The badge is the whole gate: it is kernel-stamped, so nothing
|
||||
/// in the packet can claim to be init.
|
||||
/// in the packet can claim to be init. The subscriber table moved into the
|
||||
/// harness; the question it answers has not changed.
|
||||
fn onShutdown(_: void, invocation: Invocation(void), _: Answer(void)) isize {
|
||||
if (!isSubscriber(invocation.sender)) return -envelope.EPERM;
|
||||
if (!Subscriptions.has(invocation.sender)) return -envelope.EPERM;
|
||||
enterS5();
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user