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.
87 lines
4.6 KiB
Zig
87 lines
4.6 KiB
Zig
//! system/services/input — the user-space input service. Shipped in the initial_ramdisk,
|
|
//! spawned as a ring-3 process, and bound at `/protocol/input`. It
|
|
//! is the fan-out point between **sources** (keyboard, mouse, and joystick/gamepad drivers)
|
|
//! and **subscribers** (any program that wants input): a source `publish`es an
|
|
//! `InputEvent`, and the service pushes it to every subscriber whose interest mask includes
|
|
//! that event's device class (keyboard / mouse / joystick).
|
|
//!
|
|
//! The delivery discipline is the whole design (see docs/input.md). The kernel's IPC is a
|
|
//! synchronous rendezvous: a server holds one pending reply, so it cannot park N
|
|
//! subscribers blocked in a "wait for next event" call. Broadcasting therefore has to be
|
|
//! *push* — the service delivering to subscribers. But a synchronous push (`ipc_call`)
|
|
//! would let one dead or wedged subscriber hang the whole broadcast, since the kernel
|
|
//! never wakes a sender parked on a dead peer's endpoint. So delivery uses the
|
|
//! asynchronous `ipc.send`: it posts the event to each subscriber's endpoint queue and
|
|
//! returns at once, and can never block on a subscriber. That primitive exists for exactly
|
|
//! this ([ipc.md](../../../docs/ipc.md), "asynchronous / buffered send").
|
|
//!
|
|
//! A subscriber registers by handing the service its own endpoint as a capability (M13
|
|
//! capability passing — this service is its first real consumer). The service keeps that
|
|
//! handle and `ipc.send`s each event to it. That is the envelope's reserved `subscribe`
|
|
//! verb, which this protocol adopts rather than defining its own.
|
|
//!
|
|
//! P4a moved this service onto the shared harness (library/kernel/service.zig) — it was the
|
|
//! last hand-rolled receive loop in the tree, and the one service that answered neither the
|
|
//! universal ping nor a `terminate` signal. P4c finished the job: the subscriber table, the
|
|
//! fan-out, and the dead-subscriber sweep are the harness's now
|
|
//! (`service.Subscribers`), so what is left here is what is actually about input — which
|
|
//! device class an event belongs to, and which classes a subscriber asked for. The sweep
|
|
//! that replaced the old prune is the one idiom the system uses everywhere: published
|
|
//! process-exit notifications, not a poll of the process list on every subscribe.
|
|
|
|
const ipc = @import("ipc");
|
|
const service = @import("service");
|
|
const logging = @import("logging");
|
|
const input_protocol = @import("input-protocol");
|
|
const envelope = @import("envelope");
|
|
|
|
/// The subscriber side of the input contract: the table, the reserved `subscribe`
|
|
/// verb, the exit sweep, and the fan-out. One fan-out point per process, so the
|
|
/// handler context is empty.
|
|
const Subscriptions = service.Subscribers(input_protocol.Protocol, void);
|
|
|
|
const Invocation = envelope.Invocation;
|
|
const Answer = envelope.Answer;
|
|
|
|
/// Push `event` to every subscriber whose interest mask includes its device class. The
|
|
/// class is the packet's operation, so this picks *which event* to publish and the harness
|
|
/// frames it once for the whole fan-out — the mapping from device to class is the only part
|
|
/// of a broadcast that is this service's own.
|
|
fn broadcast(event: input_protocol.InputEvent) void {
|
|
const class = input_protocol.deviceBit(event.device);
|
|
switch (input_protocol.eventOfDevice(event.device) orelse return) { // no class wants it
|
|
.keyboard => Subscriptions.publishClass(.keyboard, 0, event.asKeyboard() orelse return, class),
|
|
.mouse => Subscriptions.publishClass(.mouse, 0, event.asMouse() orelse return, class),
|
|
.joystick => Subscriptions.publishClass(.joystick, 0, event.asJoystick() orelse return, class),
|
|
}
|
|
}
|
|
|
|
fn onPublish(_: void, invocation: Invocation(input_protocol.InputEvent), _: Answer(void)) isize {
|
|
broadcast(invocation.request);
|
|
return 0;
|
|
}
|
|
|
|
/// `subscribe` and `unsubscribe` are absent on purpose: the harness answers both.
|
|
const handlers = Subscriptions.Handlers{ .publish = onPublish };
|
|
|
|
fn onMessage(message: []const u8, out: []u8, sender: u32, arrived: *ipc.Arrival) usize {
|
|
return Subscriptions.dispatch({}, handlers, message, sender, arrived, out);
|
|
}
|
|
|
|
fn initialise(_: ipc.Handle) bool {
|
|
// The harness has already bound `/protocol/input` by the time this runs, so
|
|
// "ready" still means what it always meant: the name is claimed and the loop
|
|
// is about to serve it.
|
|
_ = logging.write("/system/services/input: ready\n");
|
|
return true;
|
|
}
|
|
|
|
pub fn main() void {
|
|
service.run(input_protocol.message_maximum, .{
|
|
.service = "input",
|
|
.init = initialise,
|
|
.on_message = onMessage,
|
|
.subscribers = Subscriptions.hooks,
|
|
});
|
|
}
|