105 lines
5.7 KiB
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);
|
|
}
|