Stand up /system/services/display: a ring-3 process that claims the framebuffer D1 seeded, maps it write-combining as the front buffer, allocates a cacheable back buffer, and presents composed frames. The GUI track's compositor, reached by name over ServiceId.display (= 9). - protocol.zig: the display wire protocol (info/create_layer/configure_layer/ destroy_layer/fill_rect/blit_tile/damage/present). `info` and a whole-screen `present` are live; the layer ops fail-stub until D3. - display.zig: enumerate -> claim -> mmio_map(WC) the LFB, mmap a cacheable back buffer, clear it and present it (proving the double-buffer path), then serve. - runtime.display + barrel exports (display, display_protocol): a cached `.display` client with info()/present(), the runtime.block shape. - init spawns "display" in boot_services; build.zig wires the protocol onto the runtime, builds the exe, packs it into the initial-ramdisk, installs it. mmap fix the back buffer forced: systemMmap was capped at 256 pages (1 MiB) by a fixed kernel-stack scratch array. Rewrote it to map page-by-page with rollback (no array) and raised the cap to 8192 pages (32 MiB) — enough for a 4K back buffer. A real limitation met. Gate: `python3 test/qemu_test.py display-service` matches the service's own serial heartbeats (display: online WxH / presented frame 0), printed only after the full claim -> WC-map -> back-buffer -> present chain. Regression-checked usermem, heap, init, and D1's display.
61 lines
2.4 KiB
Zig
61 lines
2.4 KiB
Zig
//! User-space display client: talk to the display service (query the mode, and — from D3
|
|
//! — create layers, draw, and present) without hand-rolling the IPC. The `runtime.block`
|
|
//! shape: a cached `.display` lookup with a boot-race retry, then extern-struct request/
|
|
//! reply marshalling. See system/services/display/ and docs/display.md.
|
|
|
|
const std = @import("std");
|
|
const ipc = @import("ipc.zig");
|
|
const system = @import("system.zig");
|
|
const protocol = @import("display-protocol");
|
|
|
|
/// The display's current mode, as `info()` reports it.
|
|
pub const Info = struct {
|
|
width: u32,
|
|
height: u32,
|
|
pitch: u32, // bytes per row (may exceed width*4; see docs/framebuffer.md)
|
|
format: u32, // a device-abi DisplayFormat value (0 = rgbx, 1 = bgrx)
|
|
};
|
|
|
|
/// The service endpoint, looked up once and cached.
|
|
var handle: ?ipc.Handle = null;
|
|
|
|
/// Look up the display service, retrying while it comes up (a client races its
|
|
/// registration at boot). Returns the endpoint, or null if it never appears.
|
|
fn service() ?ipc.Handle {
|
|
if (handle) |h| return h;
|
|
var attempts: usize = 0;
|
|
while (attempts < 100) : (attempts += 1) {
|
|
if (ipc.lookup(.display)) |h| {
|
|
handle = h;
|
|
return h;
|
|
}
|
|
system.sleep(50);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Send one request, receive its reply; true on a zero status. `out` receives the reply
|
|
/// so callers can read `info`/`layer` fields on success.
|
|
fn transact(request: protocol.Request, out: *protocol.Reply) bool {
|
|
const h = service() orelse return false;
|
|
var req = request;
|
|
var reply: [protocol.reply_size]u8 = undefined;
|
|
const len = ipc.call(h, std.mem.asBytes(&req), &reply) catch return false;
|
|
if (len < protocol.reply_size) return false;
|
|
out.* = std.mem.bytesToValue(protocol.Reply, reply[0..protocol.reply_size]);
|
|
return out.status == 0;
|
|
}
|
|
|
|
/// The display's current mode, or null if the service never came up.
|
|
pub fn info() ?Info {
|
|
var reply: protocol.Reply = undefined;
|
|
if (!transact(.{ .operation = @intFromEnum(protocol.Operation.info) }, &reply)) return null;
|
|
return .{ .width = reply.width, .height = reply.height, .pitch = reply.pitch, .format = reply.format };
|
|
}
|
|
|
|
/// Composite the dirty layers and flush the frame to the screen.
|
|
pub fn present() bool {
|
|
var reply: protocol.Reply = undefined;
|
|
return transact(.{ .operation = @intFromEnum(protocol.Operation.present) }, &reply);
|
|
}
|