//! The power protocol (docs/os-development/power.md): system power's //! domain-named surface, bound at `/protocol/power`. On x86 the acpi service //! provides it; on ARM a PSCI/mailbox service will bind the same name — //! subscribers never learn which firmware they are on (docs/discovery.md — //! firmware neutrality), which is the whole point of naming the contract rather //! than the provider (docs/os-development/protocol-namespace.md). //! //! Defined through the envelope, so every packet begins with the folded //! `Header`. Three shapes ride the channel, and the envelope names all three: //! //! - **subscribe** is the *reserved* verb, not one of this protocol's own: a //! synchronous call whose attached capability is the subscriber's endpoint is //! exactly what `envelope.operation_subscribe` means everywhere. //! - **shutdown** is this protocol's one verb — the only operation that *does* //! something irreversible, and the reason the provider gates it by badge. //! - **the events** are pushes: the service `ipc_send`s each one to every //! subscriber, no reply owed, so a slow or dead subscriber can never wedge the //! source. **The kind is the packet's operation** — one declared event per //! named kind, exactly as the input protocol delivers one per device class — //! so a subscriber reads *what happened* out of the header instead of a tag //! inside the payload. That is what the old `EventMessage`'s two leading bytes //! (an operation byte saying "this is an event", then the kind) fold into. //! //! `Header.target` is unused (0) in both directions: the provider is the only //! object either side addresses. And no packet carries a version any more — the //! reserved `describe` verb is the version handshake, asked once at connect time //! rather than re-carried out of every packet's budget. const std = @import("std"); const envelope = @import("envelope"); /// What a published event carries beyond its kind. The kind is the packet's /// operation, so nothing here repeats it; `power_button`, `lid`, `ac` and /// `battery` leave both fields zero and are fully described by the verb alone. pub const Notice = extern struct { /// The device notification code (ACPI `Notify`'s second argument), or 0. code: u32 = 0, /// The notifying device's hardware id (EISA-decoded), or all zero. hid: [8]u8 = .{0} ** 8, }; pub const Protocol = envelope.Define(.{ .name = "power", .version = 1, .operations = &.{ // Orderly shutdown's last step: enter S5. Honored only from a // subscriber — init, the process that has already run the stop sequence // over everything else (docs/os-development/power.md, "authority, not // information"). Nothing to say and nothing to answer, so the verb and // the reply's `Status` are the whole exchange. .{ .name = "shutdown" }, }, .events = &.{ // The vocabulary is hardware-neutral: a lid is a lid whether ACPI or a // PSCI mailbox reported it. One event per kind, each carrying the same // `Notice`, because what differs between them is which thing happened — // and that is the header's job now. .{ .name = "power_button", .payload = Notice }, .{ .name = "lid", .payload = Notice }, .{ .name = "ac", .payload = Notice }, .{ .name = "battery", .payload = Notice }, // A device notification that maps to none of the named events — the // `code` and `hid` say which device and what happened. .{ .name = "notify", .payload = Notice }, }, }); pub const Operation = Protocol.Operation; /// What happened. The event *is* the kind: this is the generated event /// enumeration, re-exported under the name this protocol has always called its /// vocabulary, with the same members it has always had. pub const Event = Protocol.Event; /// What a provider and a subscriber size their buffers to. This module used to /// declare 64 — the *push* floor — which was simply wrong for a protocol whose /// requests ride `ipc_call`: a provider sizing its receive buffer to 64 refuses /// any caller that sends up to the floor it is entitled to. pub const message_maximum: usize = Protocol.message_maximum; test "the kind is the verb, and an event fits the push floor" { try std.testing.expectEqual(@as(u32, 16), @intFromEnum(Operation.shutdown)); try std.testing.expectEqual(@as(u32, 16), @intFromEnum(Event.power_button)); try std.testing.expectEqual(@as(u32, 17), @intFromEnum(Event.lid)); try std.testing.expectEqual(@as(u32, 18), @intFromEnum(Event.ac)); try std.testing.expectEqual(@as(u32, 19), @intFromEnum(Event.battery)); try std.testing.expectEqual(@as(u32, 20), @intFromEnum(Event.notify)); // subscribe is the RESERVED verb, below the protocol range entirely. try std.testing.expectEqual(@as(u32, 2), envelope.operation_subscribe); try std.testing.expectEqual(envelope.prefix_size + @sizeOf(Notice), Protocol.event_maximum); try std.testing.expect(Protocol.event_maximum <= envelope.post_maximum); // The call floor, not the push floor: `shutdown` is a synchronous call. try std.testing.expectEqual(envelope.packet_maximum, message_maximum); } test "a pushed event names its kind in the header" { var buffer: [envelope.post_maximum]u8 = undefined; const packet = Protocol.encodeEvent(.power_button, 0, .{}, &buffer).?; try std.testing.expectEqual(Event.power_button, Protocol.eventOf(packet).?); const notified = Protocol.encodeEvent(.notify, 0, .{ .code = 0x80, .hid = "PNP0C0A\x00".* }, &buffer).?; try std.testing.expectEqual(Event.notify, Protocol.eventOf(notified).?); try std.testing.expectEqual(@as(u32, 0x80), Protocol.decodeEvent(.notify, notified).?.code); }