library: five protocols speak the envelope
The folded header stops being a rule in a document and becomes the layout on the wire. Verbs number from sixteen, leaving describe, enumerate, subscribe and unsubscribe reserved and answered the same way by every provider — none of them writes a line to do it. What each protocol used to carry in a field of its own now travels in the header: a vfs node and a display layer are the packet's target, and a reply opens with a status the envelope stamps rather than one each protocol spelled for itself. Display gains the most. One forty-byte request had served eleven verbs, so attach_scanout smuggled stride through x, refresh through y and format through colour, and every coordinate crossed as a bitcast. Per-operation structs end all three: the fields have their own names and their own signs, and the tile payload grows to 224 bytes because the prefix shrank. Scanout loses a message maximum of 64 it had no business declaring — it answers calls, and the floor for a call is 256 — and virtio-gpu stops hard-coding that number at its harness. Two changes are semantic rather than notational. A directory now ends at an entry with no name, because the fixed part of a reply always travels and a zero-length reply no longer exists to mean anything. And input joins the service harness, the last loop in the tree that answered no ping and heard no terminate; its subscriber table, its pruning and its fan-out are the same code, and a shutdown now asks it to stop instead of killing it. A new conformance case reads the registry's own listing and asks every protocol it finds for its name, its version and its verb count, then offers a verb nobody defines and requires -ENOSYS — the envelope's promise, checked against providers rather than against itself. What it cannot reach in that boot it names on the serial line instead of passing quietly. Suite 110/110.
This commit is contained in:
@@ -10,8 +10,8 @@ pub fn build(b: *std.Build) void {
|
||||
.name = "virtio-gpu",
|
||||
.root_source_file = b.path("virtio-gpu.zig"),
|
||||
.imports = &.{
|
||||
"channel", "display-protocol", "driver", "ipc", "logging", "memory", "mmio", "pci",
|
||||
"process", "scanout-protocol", "service", "time",
|
||||
"channel", "display-protocol", "driver", "envelope", "ipc", "logging", "memory",
|
||||
"mmio", "pci", "process", "scanout-protocol", "service", "time",
|
||||
},
|
||||
});
|
||||
b.installArtifact(exe);
|
||||
|
||||
@@ -25,11 +25,19 @@ const memory = @import("memory");
|
||||
const logging = @import("logging");
|
||||
const mmio = @import("mmio");
|
||||
const pci = @import("pci");
|
||||
const envelope = @import("envelope");
|
||||
const display_protocol = @import("display-protocol");
|
||||
const scanout_protocol = @import("scanout-protocol");
|
||||
const vp = @import("virtio-pci.zig");
|
||||
const vg = @import("virtio-gpu-protocol.zig");
|
||||
|
||||
/// The generated scanout dispatch. One scanout per driver instance, so the
|
||||
/// handler context is empty and the mode stays in this file's globals.
|
||||
const Serve = scanout_protocol.Protocol.Provider(void);
|
||||
|
||||
const Invocation = envelope.Invocation;
|
||||
const Answer = envelope.Answer;
|
||||
|
||||
/// The DisplayFormat (device-abi) our B8G8R8X8 scanout resource presents: bgrx = 1. Handed to
|
||||
/// the compositor in the announce so it packs colours in the surface's byte order.
|
||||
const display_format_bgrx: u32 = 1;
|
||||
@@ -482,60 +490,60 @@ fn announce() void {
|
||||
std.log.info("no display service to announce to (scanout-only)", .{});
|
||||
return;
|
||||
};
|
||||
var request = display_protocol.Request{
|
||||
.operation = @intFromEnum(display_protocol.Operation.attach_scanout),
|
||||
.x = max_width, // the shared surface's row stride in pixels (it is sized to the max mode)
|
||||
.y = edid_refresh_hz, // the panel refresh from EDID (0 = unknown) — the frame-clock seed
|
||||
var packet: [display_protocol.message_maximum]u8 = undefined;
|
||||
const framed = display_protocol.Protocol.encodeRequest(.attach_scanout, 0, .{
|
||||
.stride = max_width, // the shared surface's row stride in pixels (it is sized to the max mode)
|
||||
.width = current_width,
|
||||
.height = current_height,
|
||||
.colour = display_format_bgrx,
|
||||
};
|
||||
var reply: [display_protocol.reply_size]u8 = undefined;
|
||||
_ = ipc.callCap(display, std.mem.asBytes(&request), &reply, surface.handle) catch {
|
||||
.format = display_format_bgrx,
|
||||
.refresh_hz = edid_refresh_hz, // from EDID (0 = unknown) — the frame-clock seed
|
||||
}, &.{}, &packet) orelse return;
|
||||
var reply: [display_protocol.message_maximum]u8 = undefined;
|
||||
_ = ipc.callCap(display, framed, &reply, surface.handle) catch {
|
||||
std.log.info("announce to display failed", .{});
|
||||
return;
|
||||
};
|
||||
std.log.info("announced scanout to display", .{});
|
||||
}
|
||||
|
||||
/// A `scanout_protocol.Reply{status}` written into `reply`.
|
||||
fn scanoutStatus(reply: []u8, ok: bool) usize {
|
||||
const response = scanout_protocol.Reply{ .status = if (ok) 0 else -1 };
|
||||
@memcpy(reply[0..scanout_protocol.reply_size], std.mem.asBytes(&response));
|
||||
return scanout_protocol.reply_size;
|
||||
// --- serving the scanout protocol -------------------------------------------
|
||||
//
|
||||
// The compositor drives present / mode queries here. The pixels are already in
|
||||
// the shared surface, so a present is a transfer-to-host + fenced flush; a mode
|
||||
// change just re-points the scanout rectangle (the surface is sized to the
|
||||
// largest mode). One scanout, so `Header.target` is always 0.
|
||||
|
||||
/// The device did not take the frame, or the mode asked for is not one this
|
||||
/// scanout offers.
|
||||
const refused: isize = -envelope.ENOENT;
|
||||
|
||||
fn onPresent(_: void, _: Invocation(scanout_protocol.Present), _: Answer(void)) isize {
|
||||
return if (presentFull()) 0 else refused;
|
||||
}
|
||||
|
||||
/// The `.scanout` service: the compositor drives present / mode queries here. The pixels are
|
||||
/// already in the shared surface, so a present is a transfer-to-host + fenced flush; a mode
|
||||
/// change just re-points the scanout rectangle (the surface is sized to the largest mode).
|
||||
fn onMessage(message: []const u8, reply: []u8, sender: u32, arrived: *ipc.Arrival) usize {
|
||||
_ = sender;
|
||||
_ = arrived; // nothing here takes a capability: the harness closes what arrives
|
||||
if (message.len < scanout_protocol.request_size) return 0;
|
||||
const request = std.mem.bytesToValue(scanout_protocol.Request, message[0..scanout_protocol.request_size]);
|
||||
switch (request.operation) {
|
||||
@intFromEnum(scanout_protocol.Operation.present) => return scanoutStatus(reply, presentFull()),
|
||||
@intFromEnum(scanout_protocol.Operation.get_modes) => {
|
||||
var response = scanout_protocol.ModesReply{ .status = 0, .count = offered_modes.len, .modes = undefined };
|
||||
for (0..scanout_protocol.max_modes) |i| {
|
||||
response.modes[i] = if (i < offered_modes.len)
|
||||
.{ .width = offered_modes[i].width, .height = offered_modes[i].height }
|
||||
else
|
||||
.{ .width = 0, .height = 0 };
|
||||
}
|
||||
@memcpy(reply[0..scanout_protocol.modes_reply_size], std.mem.asBytes(&response));
|
||||
return scanout_protocol.modes_reply_size;
|
||||
},
|
||||
@intFromEnum(scanout_protocol.Operation.set_mode) => {
|
||||
const w = request.width;
|
||||
const h = request.height;
|
||||
if (w == 0 or h == 0 or w > max_width or h > max_height) return scanoutStatus(reply, false);
|
||||
current_width = w;
|
||||
current_height = h;
|
||||
return scanoutStatus(reply, setScanoutRect());
|
||||
},
|
||||
else => return 0,
|
||||
fn onGetModes(_: void, _: Invocation(void), answer: Answer(scanout_protocol.Modes)) isize {
|
||||
var offered = scanout_protocol.Modes{ .count = offered_modes.len };
|
||||
for (0..@min(offered_modes.len, scanout_protocol.max_modes)) |i| {
|
||||
offered.modes[i] = .{ .width = offered_modes[i].width, .height = offered_modes[i].height };
|
||||
}
|
||||
answer.set(offered);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn onSetMode(_: void, invocation: Invocation(scanout_protocol.SetMode), _: Answer(void)) isize {
|
||||
const w = invocation.request.width;
|
||||
const h = invocation.request.height;
|
||||
if (w == 0 or h == 0 or w > max_width or h > max_height) return refused;
|
||||
current_width = w;
|
||||
current_height = h;
|
||||
return if (setScanoutRect()) 0 else refused;
|
||||
}
|
||||
|
||||
const handlers = Serve.Handlers{ .present = onPresent, .get_modes = onGetModes, .set_mode = onSetMode };
|
||||
|
||||
fn onMessage(message: []const u8, reply: []u8, sender: u32, arrived: *ipc.Arrival) usize {
|
||||
_ = arrived; // nothing here takes a capability: the harness closes what arrives
|
||||
return Serve.dispatch({}, handlers, message, sender, null, reply);
|
||||
}
|
||||
|
||||
pub fn main(init: process.Init) void {
|
||||
@@ -547,7 +555,7 @@ pub fn main(init: process.Init) void {
|
||||
std.log.info("malformed device id '{s}'", .{argument});
|
||||
return;
|
||||
};
|
||||
service.run(256, .{
|
||||
service.run(scanout_protocol.message_maximum, .{
|
||||
.service = "scanout",
|
||||
.init = initialise,
|
||||
.on_message = onMessage,
|
||||
|
||||
Reference in New Issue
Block a user