danos/library/protocol/power/power-protocol.zig

105 lines
5.7 KiB
Zig

//! 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);
}