S548 · SOURCE-BOUND GATE EVIDENCE
S548 · R1 ekran: test deseni ve FramebufferCap bağlama modeli
tam S548 implementation modülü → Operations --test hedefi ile bağlı tam focused test → ayrı Operations kaydı Bu sayfa yalnız S548 kapısına aittir; komşu kapıların kaynakları bu kabulün içine katılmaz.
S548Focused kod testiOperations id exactsource SHA exacttest target exact
operation: g8l-s548-r1-display-test-pattern-framebuffer-capability-binding
uygulama/model · focused test · Operations · 3 exact excerpt
sequence-bound=true · implementation-bound=true
01 · Yürütme / doğrulama kodu
Kapının gerçek repository sözleşmesi
tam dosyaL1–L592
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s548_r1_display_test_pattern_framebuffer_capability_binding.rs::S548 r1 display test pattern framebuffer capability binding implementation
#![allow(unexpected_cfgs)]
//! S548 models a deterministic display test-pattern renderer bound to the
//! framebuffer capability model (R1 stage 2: display, touch and basic UI).
//!
//! The model mirrors the M4.4 `PixelFormat` / `FramebufferRights` shapes so it
//! compiles identically in the `no_std` kernel and the host simulation. A
//! `G8lS548FramebufferBinding` carries the capability geometry (width, height,
//! byte pitch, pixel format) and the rights actually granted to the renderer.
//! Four patterns are rendered into an `alloc::vec::Vec<u32>` word buffer with
//! explicit pitch handling (the pitch may exceed `width * bytes_per_pixel`;
//! padding words stay zero and are covered by the checksum), BGRA8888 and
//! RGB565 packing, bounds-checked pixel writes and an FNV-1a 64-bit checksum
//! of the produced buffer so tests can pin exact expected values.
//!
//! The renderer requires the WRITE right and fails closed without it; VSYNC
//! and DMA are not required. Every malformed geometry, oversized buffer,
//! unaligned pitch, invalid checkerboard cell or out-of-range pixel access is
//! rejected before any buffer is produced.
//!
//! What this gate does NOT claim: there is no panel, no MMIO, no mailbox
//! transaction, no real framebuffer mapping and no production callsite. No
//! hardware exists for this gate; `physical observations = 0`,
//! `RUNBOOK_EXECUTED_IN_S548=NO`, Boot-to-UI physically observed = false and
//! R1 acceptance complete = false. S540 and S543 remain immutable physical
//! RED verdicts. Predecessor: S547 (mailbox framebuffer allocation contract).
//! Next: S549 (RP1 DSI host register map and D-PHY timing model).
use alloc::vec::Vec;
pub const S548_SEQUENCE: usize = 548;
pub const S548_EXPECTED_PREDECESSOR: usize = 547;
pub const S548_R1_STAGE: u8 = 2;
pub const S548_R1_RANGE_FIRST: usize = 536;
pub const S548_R1_RANGE_LAST: usize = 568;
pub const S548_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0;
pub const S548_PHYSICAL_OBSERVATIONS: usize = 0;
pub const S548_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0;
pub const S548_SD_WRITES: usize = 0;
pub const S548_UART_OPENS: usize = 0;
pub const S548_POWER_TRANSITIONS: usize = 0;
pub const S548_NEW_IMMUTABLE_RAW_CAPTURES: usize = 0;
pub const S548_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S548_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S548_AUTOMATIC_PROMOTION: bool = false;
pub const S548_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false;
pub const S548_HARDWARE_PRESENT: bool = false;
pub const S548_R1_ACCEPTANCE_COMPLETE: bool = false;
pub const RUNBOOK_EXECUTED_IN_S548: bool = false;
/// Number of vertical colour bars in the colour-bar pattern.
pub const S548_COLOUR_BAR_COUNT: u32 = 8;
/// Reference phone panel geometry used by the pinned checksum cases.
pub const S548_PHONE_WIDTH: u32 = 720;
pub const S548_PHONE_HEIGHT: u32 = 1280;
/// Small reference geometry used by the pinned checksum cases.
pub const S548_SMALL_WIDTH: u32 = 64;
pub const S548_SMALL_HEIGHT: u32 = 64;
/// Hard geometry limits of the model (fail-closed above them).
pub const S548_MAX_DIMENSION: u32 = 4096;
pub const S548_MAX_PITCH_BYTES: u32 = S548_MAX_DIMENSION * 4;
pub const S548_MAX_BUFFER_BYTES: u64 = 64 * 1024 * 1024;
/// Border thickness of the border+crosshair pattern in pixels.
pub const S548_BORDER_THICKNESS_PX: u32 = 4;
/// FNV-1a 64-bit parameters.
pub const S548_FNV1A_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
pub const S548_FNV1A_PRIME: u64 = 0x0000_0100_0000_01b3;
/// Mirror of the M4.4 `ui::framebuffer::PixelFormat` (kept local so the model
/// compiles in both the kernel and the host simulation).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS548PixelFormat {
Bgra8888,
Rgb565,
}
impl G8lS548PixelFormat {
pub const fn bytes_per_pixel(self) -> u32 {
match self {
Self::Bgra8888 => 4,
Self::Rgb565 => 2,
}
}
pub const fn format_code(self) -> u8 {
match self {
Self::Bgra8888 => 1,
Self::Rgb565 => 2,
}
}
}
/// Mirror of the M4.4 `ui::framebuffer::FramebufferRights` (bitmask).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct G8lS548FramebufferRights(u8);
impl G8lS548FramebufferRights {
pub const READ: Self = Self(0b0001);
pub const WRITE: Self = Self(0b0010);
pub const VSYNC: Self = Self(0b0100);
pub const DMA: Self = Self(0b1000);
pub const FULL: Self = Self(Self::READ.0 | Self::WRITE.0 | Self::VSYNC.0 | Self::DMA.0);
pub const fn empty() -> Self {
Self(0)
}
pub const fn from_bits(bits: u8) -> Self {
Self(bits & Self::FULL.0)
}
pub const fn contains(self, other: Self) -> bool {
(self.0 & other.0) == other.0
}
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub const fn as_u8(self) -> u8 {
self.0
}
}
/// An 8-bit-per-channel colour used by the pattern tables.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS548Rgb {
pub r: u8,
pub g: u8,
pub b: u8,
}
impl G8lS548Rgb {
pub const fn new(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b }
}
}
pub const S548_WHITE: G8lS548Rgb = G8lS548Rgb::new(0xff, 0xff, 0xff);
pub const S548_BLACK: G8lS548Rgb = G8lS548Rgb::new(0x00, 0x00, 0x00);
pub const S548_RED: G8lS548Rgb = G8lS548Rgb::new(0xff, 0x00, 0x00);
pub const S548_BACKGROUND_GREY: G8lS548Rgb = G8lS548Rgb::new(0x20, 0x20, 0x20);
/// Full-intensity colour bars, left to right: white, yellow, cyan, green,
/// magenta, red, blue, black.
pub const S548_COLOUR_BARS: [G8lS548Rgb; S548_COLOUR_BAR_COUNT as usize] = [
G8lS548Rgb::new(0xff, 0xff, 0xff),
G8lS548Rgb::new(0xff, 0xff, 0x00),
G8lS548Rgb::new(0x00, 0xff, 0xff),
G8lS548Rgb::new(0x00, 0xff, 0x00),
G8lS548Rgb::new(0xff, 0x00, 0xff),
G8lS548Rgb::new(0xff, 0x00, 0x00),
G8lS548Rgb::new(0x00, 0x00, 0xff),
G8lS548Rgb::new(0x00, 0x00, 0x00),
];
/// The deterministic patterns the renderer can produce.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS548TestPattern {
ColourBars,
Checkerboard { cell: u32 },
Gradient,
BorderCrosshair,
}
impl G8lS548TestPattern {
pub const fn pattern_code(self) -> u8 {
match self {
Self::ColourBars => 1,
Self::Checkerboard { .. } => 2,
Self::Gradient => 3,
Self::BorderCrosshair => 4,
}
}
pub const fn parameter(self) -> u32 {
match self {
Self::Checkerboard { cell } => cell,
_ => 0,
}
}
}
/// Framebuffer capability geometry plus the rights granted to the renderer.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS548FramebufferBinding {
pub cap_id: u64,
pub width: u32,
pub height: u32,
pub pitch_bytes: u32,
pub format: G8lS548PixelFormat,
pub cap_max_rights: G8lS548FramebufferRights,
pub granted_rights: G8lS548FramebufferRights,
}
impl G8lS548FramebufferBinding {
/// Tight-pitch binding with the WRITE right granted from a FULL cap.
pub const fn tight(cap_id: u64, width: u32, height: u32, format: G8lS548PixelFormat) -> Self {
Self {
cap_id,
width,
height,
pitch_bytes: width * format.bytes_per_pixel(),
format,
cap_max_rights: G8lS548FramebufferRights::FULL,
granted_rights: G8lS548FramebufferRights::WRITE,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS548DisplayTestPatternReceipt {
pub sequence: usize,
pub predecessor_sequence: usize,
pub r1_stage: u8,
pub cap_id: u64,
pub width: u32,
pub height: u32,
pub pitch_bytes: u32,
pub format_code: u8,
pub granted_rights: u8,
pub pattern_code: u8,
pub pattern_parameter: u32,
pub pixels_written: u64,
pub padding_bytes_per_row: u32,
pub buffer_words: usize,
pub checksum: u64,
pub hardware_present: bool,
pub s540_physical_verdict_retained_red: bool,
pub s543_physical_verdict_retained_red: bool,
pub automatic_promotion: bool,
pub supported_profile_runtime_observations: usize,
pub physical_observations: usize,
pub boot_to_ui_physically_observed: bool,
pub r1_acceptance_complete: bool,
pub runbook_executed: bool,
}
#[derive(Debug)]
pub struct G8lS548DisplayTestPatternState {
receipt: Option<G8lS548DisplayTestPatternReceipt>,
}
impl G8lS548DisplayTestPatternState {
pub const fn new() -> Self {
Self { receipt: None }
}
pub const fn receipt(&self) -> Option<G8lS548DisplayTestPatternReceipt> {
self.receipt
}
}
impl Default for G8lS548DisplayTestPatternState {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS548DisplayTestPatternOutcome {
Rendered(G8lS548DisplayTestPatternReceipt),
Retained(G8lS548DisplayTestPatternReceipt),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS548DisplayTestPatternError {
WriteRightMissing,
GrantExceedsCapability,
ZeroDimension,
DimensionTooLarge,
PitchNotWordAligned,
PitchBelowRowBytes,
PitchTooLarge,
BufferSizeOverflow,
WidthBelowBarCount,
CheckerboardCellZero,
CheckerboardCellExceedsFrame,
FrameTooSmallForBorderCrosshair,
PixelOutOfBounds,
BufferIndexOutOfRange,
PublishedStateDrift,
}
impl G8lS548DisplayTestPatternError {
pub const fn diagnostic_code(self) -> u64 {
match self {
Self::WriteRightMissing => 1,
Self::GrantExceedsCapability => 2,
Self::ZeroDimension => 3,
Self::DimensionTooLarge => 4,
Self::PitchNotWordAligned => 5,
Self::PitchBelowRowBytes => 6,
Self::PitchTooLarge => 7,
Self::BufferSizeOverflow => 8,
Self::WidthBelowBarCount => 9,
Self::CheckerboardCellZero => 10,
Self::CheckerboardCellExceedsFrame => 11,
Self::FrameTooSmallForBorderCrosshair => 12,
Self::PixelOutOfBounds => 13,
Self::BufferIndexOutOfRange => 14,
Self::PublishedStateDrift => 15,
}
}
}
/// A rendered frame: the word buffer plus the receipt describing it.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct G8lS548RenderedFrame {
pub receipt: G8lS548DisplayTestPatternReceipt,
pub buffer: Vec<u32>,
}
/// FNV-1a 64-bit over a byte slice.
pub fn s548_fnv1a_64(bytes: &[u8]) -> u64 {
let mut hash = S548_FNV1A_OFFSET_BASIS;
for byte in bytes {
hash ^= u64::from(*byte);
hash = hash.wrapping_mul(S548_FNV1A_PRIME);
}
hash
}
/// FNV-1a 64-bit over the little-endian byte image of a word buffer.
pub fn s548_checksum_words(words: &[u32]) -> u64 {
let mut hash = S548_FNV1A_OFFSET_BASIS;
for word in words {
for byte in word.to_le_bytes() {
hash ^= u64::from(byte);
hash = hash.wrapping_mul(S548_FNV1A_PRIME);
}
}
hash
}
/// Packs a colour into the given format. BGRA8888 is the little-endian
/// word `A<<24 | R<<16 | G<<8 | B` (memory order B, G, R, A) with A = 0xff.
/// RGB565 is `R5<<11 | G6<<5 | B5` in the low 16 bits.
pub const fn s548_pack_pixel(format: G8lS548PixelFormat, colour: G8lS548Rgb) -> u32 {
match format {
G8lS548PixelFormat::Bgra8888 => {
0xff00_0000 | ((colour.r as u32) << 16) | ((colour.g as u32) << 8) | (colour.b as u32)
}
G8lS548PixelFormat::Rgb565 => {
(((colour.r as u32) >> 3) << 11)
| (((colour.g as u32) >> 2) << 5)
| ((colour.b as u32) >> 3)
}
}
}
/// Validates the binding geometry and rights; returns the buffer word count.
pub fn s548_validate_binding(
binding: &G8lS548FramebufferBinding,
) -> Result<usize, G8lS548DisplayTestPatternError> {
if !binding.cap_max_rights.contains(binding.granted_rights) {
return Err(G8lS548DisplayTestPatternError::GrantExceedsCapability);
}
if !binding
.granted_rights
.contains(G8lS548FramebufferRights::WRITE)
{
return Err(G8lS548DisplayTestPatternError::WriteRightMissing);
}
if binding.width == 0 || binding.height == 0 {
return Err(G8lS548DisplayTestPatternError::ZeroDimension);
}
if binding.width > S548_MAX_DIMENSION || binding.height > S548_MAX_DIMENSION {
return Err(G8lS548DisplayTestPatternError::DimensionTooLarge);
}
if binding.pitch_bytes % 4 != 0 {
return Err(G8lS548DisplayTestPatternError::PitchNotWordAligned);
}
let row_bytes = binding
.width
.checked_mul(binding.format.bytes_per_pixel())
.ok_or(G8lS548DisplayTestPatternError::BufferSizeOverflow)?;
if binding.pitch_bytes < row_bytes {
return Err(G8lS548DisplayTestPatternError::PitchBelowRowBytes);
}
if binding.pitch_bytes > S548_MAX_PITCH_BYTES {
return Err(G8lS548DisplayTestPatternError::PitchTooLarge);
}
let total_bytes = u64::from(binding.pitch_bytes)
.checked_mul(u64::from(binding.height))
.ok_or(G8lS548DisplayTestPatternError::BufferSizeOverflow)?;
if total_bytes > S548_MAX_BUFFER_BYTES {
return Err(G8lS548DisplayTestPatternError::BufferSizeOverflow);
}
usize::try_from(total_bytes / 4).map_err(|_| G8lS548DisplayTestPatternError::BufferSizeOverflow)
}
/// Validates pattern parameters against the binding geometry.
pub fn s548_validate_pattern(
binding: &G8lS548FramebufferBinding,
pattern: G8lS548TestPattern,
) -> Result<(), G8lS548DisplayTestPatternError> {
match pattern {
G8lS548TestPattern::ColourBars => {
if binding.width < S548_COLOUR_BAR_COUNT {
return Err(G8lS548DisplayTestPatternError::WidthBelowBarCount);
}
}
G8lS548TestPattern::Checkerboard { cell } => {
if cell == 0 {
return Err(G8lS548DisplayTestPatternError::CheckerboardCellZero);
}
if cell > binding.width || cell > binding.height {
return Err(G8lS548DisplayTestPatternError::CheckerboardCellExceedsFrame);
}
}
G8lS548TestPattern::Gradient => {}
G8lS548TestPattern::BorderCrosshair => {
let minimum = S548_BORDER_THICKNESS_PX * 2 + 1;
if binding.width < minimum || binding.height < minimum {
return Err(G8lS548DisplayTestPatternError::FrameTooSmallForBorderCrosshair);
}
}
}
Ok(())
}
/// Word index and bit shift of pixel `(x, y)` inside the pitch-addressed
/// buffer. Bounds-checked against the binding geometry.
pub fn s548_pixel_location(
binding: &G8lS548FramebufferBinding,
x: u32,
y: u32,
) -> Result<(usize, u32), G8lS548DisplayTestPatternError> {
if x >= binding.width || y >= binding.height {
return Err(G8lS548DisplayTestPatternError::PixelOutOfBounds);
}
let byte_offset = u64::from(y)
.checked_mul(u64::from(binding.pitch_bytes))
.and_then(|row| row.checked_add(u64::from(x) * u64::from(binding.format.bytes_per_pixel())))
.ok_or(G8lS548DisplayTestPatternError::BufferSizeOverflow)?;
let word = usize::try_from(byte_offset / 4)
.map_err(|_| G8lS548DisplayTestPatternError::BufferSizeOverflow)?;
let shift = ((byte_offset % 4) as u32) * 8;
Ok((word, shift))
}
/// Bounds-checked pixel write.
pub fn s548_write_pixel(
buffer: &mut [u32],
binding: &G8lS548FramebufferBinding,
x: u32,
y: u32,
packed: u32,
) -> Result<(), G8lS548DisplayTestPatternError> {
let (word, shift) = s548_pixel_location(binding, x, y)?;
let slot = buffer
.get_mut(word)
.ok_or(G8lS548DisplayTestPatternError::BufferIndexOutOfRange)?;
match binding.format {
G8lS548PixelFormat::Bgra8888 => *slot = packed,
G8lS548PixelFormat::Rgb565 => {
let mask = 0xffff_u32 << shift;
*slot = (*slot & !mask) | ((packed & 0xffff) << shift);
}
}
Ok(())
}
/// Bounds-checked pixel read (returns the packed value).
pub fn s548_read_pixel(
buffer: &[u32],
binding: &G8lS548FramebufferBinding,
x: u32,
y: u32,
) -> Result<u32, G8lS548DisplayTestPatternError> {
let (word, shift) = s548_pixel_location(binding, x, y)?;
let slot = buffer
.get(word)
.ok_or(G8lS548DisplayTestPatternError::BufferIndexOutOfRange)?;
Ok(match binding.format {
G8lS548PixelFormat::Bgra8888 => *slot,
G8lS548PixelFormat::Rgb565 => (*slot >> shift) & 0xffff,
})
}
/// The colour of pixel `(x, y)` for a pattern on a validated binding.
pub fn s548_pattern_colour(
binding: &G8lS548FramebufferBinding,
pattern: G8lS548TestPattern,
x: u32,
y: u32,
) -> G8lS548Rgb {
match pattern {
G8lS548TestPattern::ColourBars => {
let bar = (u64::from(x) * u64::from(S548_COLOUR_BAR_COUNT)) / u64::from(binding.width);
S548_COLOUR_BARS[(bar as usize).min(S548_COLOUR_BARS.len() - 1)]
}
G8lS548TestPattern::Checkerboard { cell } => {
if ((x / cell) + (y / cell)) % 2 == 0 {
S548_WHITE
} else {
S548_BLACK
}
}
G8lS548TestPattern::Gradient => {
let span_x = u64::from(binding.width.saturating_sub(1)).max(1);
let span_y = u64::from(binding.height.saturating_sub(1)).max(1);
let r = (u64::from(x) * 255 / span_x) as u8;
let g = (u64::from(y) * 255 / span_y) as u8;
G8lS548Rgb::new(r, g, 255 - r)
}
G8lS548TestPattern::BorderCrosshair => {
let t = S548_BORDER_THICKNESS_PX;
if x < t || y < t || x >= binding.width - t || y >= binding.height - t {
S548_WHITE
} else if x == binding.width / 2 || y == binding.height / 2 {
S548_RED
} else {
S548_BACKGROUND_GREY
}
}
}
}
/// Renders a pattern into a fresh word buffer and returns it with a receipt.
pub fn render_s548_test_pattern(
binding: &G8lS548FramebufferBinding,
pattern: G8lS548TestPattern,
) -> Result<G8lS548RenderedFrame, G8lS548DisplayTestPatternError> {
let buffer_words = s548_validate_binding(binding)?;
s548_validate_pattern(binding, pattern)?;
let mut buffer: Vec<u32> = Vec::new();
buffer.resize(buffer_words, 0);
let mut pixels_written: u64 = 0;
for y in 0..binding.height {
for x in 0..binding.width {
let colour = s548_pattern_colour(binding, pattern, x, y);
let packed = s548_pack_pixel(binding.format, colour);
s548_write_pixel(&mut buffer, binding, x, y, packed)?;
pixels_written = pixels_written
.checked_add(1)
.ok_or(G8lS548DisplayTestPatternError::BufferSizeOverflow)?;
}
}
let checksum = s548_checksum_words(&buffer);
let receipt = G8lS548DisplayTestPatternReceipt {
sequence: S548_SEQUENCE,
predecessor_sequence: S548_EXPECTED_PREDECESSOR,
r1_stage: S548_R1_STAGE,
cap_id: binding.cap_id,
width: binding.width,
height: binding.height,
pitch_bytes: binding.pitch_bytes,
format_code: binding.format.format_code(),
granted_rights: binding.granted_rights.as_u8(),
pattern_code: pattern.pattern_code(),
pattern_parameter: pattern.parameter(),
pixels_written,
padding_bytes_per_row: binding.pitch_bytes
- binding.width * binding.format.bytes_per_pixel(),
buffer_words,
checksum,
hardware_present: S548_HARDWARE_PRESENT,
s540_physical_verdict_retained_red: S548_S540_PHYSICAL_VERDICT_RETAINED_RED,
s543_physical_verdict_retained_red: S548_S543_PHYSICAL_VERDICT_RETAINED_RED,
automatic_promotion: S548_AUTOMATIC_PROMOTION,
supported_profile_runtime_observations: S548_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS,
physical_observations: S548_PHYSICAL_OBSERVATIONS,
boot_to_ui_physically_observed: S548_BOOT_TO_UI_PHYSICALLY_OBSERVED,
r1_acceptance_complete: S548_R1_ACCEPTANCE_COMPLETE,
runbook_executed: RUNBOOK_EXECUTED_IN_S548,
};
Ok(G8lS548RenderedFrame { receipt, buffer })
}
/// Fail-closed, idempotent render service. The first successful render
/// publishes its receipt; an exact replay is retained; any divergent binding
/// or pattern after publication is rejected. The buffer itself is not kept in
/// the state (the receipt's checksum identifies it).
pub fn service_s548_model_render_test_pattern(
state: &mut G8lS548DisplayTestPatternState,
binding: &G8lS548FramebufferBinding,
pattern: G8lS548TestPattern,
) -> Result<G8lS548DisplayTestPatternOutcome, G8lS548DisplayTestPatternError> {
let frame = render_s548_test_pattern(binding, pattern)?;
let receipt = frame.receipt;
if let Some(published) = state.receipt {
if published != receipt {
return Err(G8lS548DisplayTestPatternError::PublishedStateDrift);
}
return Ok(G8lS548DisplayTestPatternOutcome::Retained(published));
}
state.receipt = Some(receipt);
Ok(G8lS548DisplayTestPatternOutcome::Rendered(receipt))
}
snippet sha256: eee6f8da27a9…file sha256: eee6f8da27a9…
02 · Doğrulayan test kodu
Operations komutuna bağlı focused test
tam dosyaL1–L605
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s548_r1_display_test_pattern_framebuffer_capability_binding.rs::S548 r1 display test pattern framebuffer capability binding focused tests
use aselsan_microkernel_simulation::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s548_r1_display_test_pattern_framebuffer_capability_binding::*;
use std::collections::BTreeSet;
const SOURCE: &str = include_str!(
"../../kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s548_r1_display_test_pattern_framebuffer_capability_binding.rs"
);
const MAIN: &str = include_str!("../../kernel/src/main.rs");
const SIMULATION_LIB: &str = include_str!("../src/lib.rs");
/// Expected FNV-1a 64 checksums computed by an independent reference
/// implementation of the same pattern/packing/pitch rules.
const PHONE_BARS_BGRA8888: u64 = 0x7c54_4deb_8157_9325;
const PHONE_GRADIENT_RGB565: u64 = 0x2c3b_4455_9487_9b71;
const PHONE_CHECKER40_BGRA8888: u64 = 0xbb4f_86ef_b3eb_0325;
const SMALL_CHECKER8_BGRA8888: u64 = 0xa546_9e63_bfa7_0325;
const SMALL_BARS_RGB565: u64 = 0x8e56_2bac_1128_4b25;
const SMALL_BORDER_BGRA8888: u64 = 0xf748_ab56_fde2_f1b2;
const SMALL_GRADIENT_BGRA8888: u64 = 0x10b5_b99e_81f4_aef5;
const SMALL_BARS_BGRA8888_PITCH320: u64 = 0xe645_3365_e450_3325;
const SMALL_BARS_RGB565_PITCH192: u64 = 0xde43_4cdc_99b5_8b25;
fn phone(format: G8lS548PixelFormat) -> G8lS548FramebufferBinding {
G8lS548FramebufferBinding::tight(0x5480, S548_PHONE_WIDTH, S548_PHONE_HEIGHT, format)
}
fn small(format: G8lS548PixelFormat) -> G8lS548FramebufferBinding {
G8lS548FramebufferBinding::tight(0x5481, S548_SMALL_WIDTH, S548_SMALL_HEIGHT, format)
}
fn render(
binding: &G8lS548FramebufferBinding,
pattern: G8lS548TestPattern,
) -> G8lS548RenderedFrame {
render_s548_test_pattern(binding, pattern).unwrap()
}
fn bgra(r: u8, g: u8, b: u8) -> u32 {
0xff00_0000 | (u32::from(r) << 16) | (u32::from(g) << 8) | u32::from(b)
}
#[test]
fn sequence_scope_and_nonpromotion_are_exact() {
assert_eq!(S548_SEQUENCE, 548);
assert_eq!(S548_EXPECTED_PREDECESSOR, 547);
assert_eq!(S548_R1_STAGE, 2);
assert_eq!(S548_R1_RANGE_FIRST, 536);
assert_eq!(S548_R1_RANGE_LAST, 568);
assert_eq!(S548_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS, 0);
assert_eq!(S548_PHYSICAL_OBSERVATIONS, 0);
assert_eq!(S548_PHYSICAL_OR_DEVICE_OPERATIONS, 0);
assert_eq!(S548_SD_WRITES, 0);
assert_eq!(S548_UART_OPENS, 0);
assert_eq!(S548_POWER_TRANSITIONS, 0);
assert_eq!(S548_NEW_IMMUTABLE_RAW_CAPTURES, 0);
assert!(S548_S540_PHYSICAL_VERDICT_RETAINED_RED);
assert!(S548_S543_PHYSICAL_VERDICT_RETAINED_RED);
assert!(!S548_AUTOMATIC_PROMOTION);
assert!(!S548_BOOT_TO_UI_PHYSICALLY_OBSERVED);
assert!(!S548_HARDWARE_PRESENT);
assert!(!S548_R1_ACCEPTANCE_COMPLETE);
assert!(!RUNBOOK_EXECUTED_IN_S548);
assert_eq!(S548_COLOUR_BAR_COUNT, 8);
assert_eq!(S548_COLOUR_BARS.len(), 8);
assert_eq!((S548_PHONE_WIDTH, S548_PHONE_HEIGHT), (720, 1280));
assert_eq!((S548_SMALL_WIDTH, S548_SMALL_HEIGHT), (64, 64));
assert_eq!(S548_BORDER_THICKNESS_PX, 4);
assert_eq!(S548_FNV1A_OFFSET_BASIS, 0xcbf2_9ce4_8422_2325);
assert_eq!(S548_FNV1A_PRIME, 0x0000_0100_0000_01b3);
}
#[test]
fn module_is_registered_in_kernel_and_simulation() {
let module = "g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s548_r1_display_test_pattern_framebuffer_capability_binding";
assert!(MAIN.contains(&format!("mod {module};")));
assert!(SIMULATION_LIB.contains(&format!("pub mod {module};")));
}
#[test]
fn source_has_no_device_execution_or_uart_emission_surface() {
for forbidden in [
"unsafe",
"asm!",
"write_volatile",
"crate::uart",
"crate::arch",
"crate::mm",
"crate::ui",
"#[no_mangle]",
"spin::",
"std::",
"kprintln!",
] {
assert!(!SOURCE.contains(forbidden), "forbidden token: {forbidden}");
}
assert!(SOURCE.contains("no panel, no MMIO, no mailbox"));
assert!(SOURCE.contains("no production callsite"));
assert!(SOURCE.contains("use alloc::vec::Vec;"));
}
#[test]
fn diagnostic_codes_are_nonzero_and_unique() {
let errors = [
G8lS548DisplayTestPatternError::WriteRightMissing,
G8lS548DisplayTestPatternError::GrantExceedsCapability,
G8lS548DisplayTestPatternError::ZeroDimension,
G8lS548DisplayTestPatternError::DimensionTooLarge,
G8lS548DisplayTestPatternError::PitchNotWordAligned,
G8lS548DisplayTestPatternError::PitchBelowRowBytes,
G8lS548DisplayTestPatternError::PitchTooLarge,
G8lS548DisplayTestPatternError::BufferSizeOverflow,
G8lS548DisplayTestPatternError::WidthBelowBarCount,
G8lS548DisplayTestPatternError::CheckerboardCellZero,
G8lS548DisplayTestPatternError::CheckerboardCellExceedsFrame,
G8lS548DisplayTestPatternError::FrameTooSmallForBorderCrosshair,
G8lS548DisplayTestPatternError::PixelOutOfBounds,
G8lS548DisplayTestPatternError::BufferIndexOutOfRange,
G8lS548DisplayTestPatternError::PublishedStateDrift,
];
let codes: BTreeSet<_> = errors
.into_iter()
.map(G8lS548DisplayTestPatternError::diagnostic_code)
.collect();
assert_eq!(codes.len(), errors.len());
assert!(!codes.contains(&0));
}
#[test]
fn exact_replay_retains_the_same_receipt() {
let mut state = G8lS548DisplayTestPatternState::new();
let binding = phone(G8lS548PixelFormat::Bgra8888);
let G8lS548DisplayTestPatternOutcome::Rendered(receipt) =
service_s548_model_render_test_pattern(
&mut state,
&binding,
G8lS548TestPattern::ColourBars,
)
.unwrap()
else {
panic!("first S548 render missing")
};
assert_eq!(state.receipt(), Some(receipt));
assert_eq!(receipt.checksum, PHONE_BARS_BGRA8888);
assert_eq!(
service_s548_model_render_test_pattern(
&mut state,
&binding,
G8lS548TestPattern::ColourBars,
),
Ok(G8lS548DisplayTestPatternOutcome::Retained(receipt))
);
assert_eq!(state.receipt(), Some(receipt));
}
#[test]
fn divergent_input_after_publication_fails_closed() {
let mut state = G8lS548DisplayTestPatternState::new();
let binding = small(G8lS548PixelFormat::Bgra8888);
service_s548_model_render_test_pattern(
&mut state,
&binding,
G8lS548TestPattern::Checkerboard { cell: 8 },
)
.unwrap();
let published = state.receipt().unwrap();
assert_eq!(
service_s548_model_render_test_pattern(
&mut state,
&binding,
G8lS548TestPattern::Checkerboard { cell: 16 },
),
Err(G8lS548DisplayTestPatternError::PublishedStateDrift)
);
let mut other_cap = binding;
other_cap.cap_id ^= 1;
assert_eq!(
service_s548_model_render_test_pattern(
&mut state,
&other_cap,
G8lS548TestPattern::Checkerboard { cell: 8 },
),
Err(G8lS548DisplayTestPatternError::PublishedStateDrift)
);
let mut more_rights = binding;
more_rights.granted_rights = G8lS548FramebufferRights::FULL;
assert_eq!(
service_s548_model_render_test_pattern(
&mut state,
&more_rights,
G8lS548TestPattern::Checkerboard { cell: 8 },
),
Err(G8lS548DisplayTestPatternError::PublishedStateDrift)
);
assert_eq!(state.receipt(), Some(published));
}
#[test]
fn colour_bars_720x1280_bgra8888_pins_exact_checksum_and_bar_edges() {
let binding = phone(G8lS548PixelFormat::Bgra8888);
let frame = render(&binding, G8lS548TestPattern::ColourBars);
assert_eq!(frame.buffer.len(), 720 * 1280);
assert_eq!(frame.receipt.buffer_words, 720 * 1280);
assert_eq!(frame.receipt.pixels_written, 720 * 1280);
assert_eq!(frame.receipt.padding_bytes_per_row, 0);
assert_eq!(frame.receipt.pitch_bytes, 2880);
assert_eq!(frame.receipt.format_code, 1);
assert_eq!(frame.receipt.pattern_code, 1);
assert_eq!(frame.receipt.checksum, PHONE_BARS_BGRA8888);
assert_eq!(s548_checksum_words(&frame.buffer), PHONE_BARS_BGRA8888);
// Each bar is exactly 90 pixels wide.
for bar in 0..8u32 {
let expected = s548_pack_pixel(G8lS548PixelFormat::Bgra8888, S548_COLOUR_BARS[bar as usize]);
assert_eq!(s548_read_pixel(&frame.buffer, &binding, bar * 90, 0), Ok(expected));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, bar * 90 + 89, 1279), Ok(expected));
}
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 0, 640), Ok(bgra(0xff, 0xff, 0xff)));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 719, 640), Ok(bgra(0, 0, 0)));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 450, 0), Ok(bgra(0xff, 0, 0)));
}
#[test]
fn gradient_and_checkerboard_720x1280_pin_exact_checksums() {
let rgb565 = phone(G8lS548PixelFormat::Rgb565);
let gradient = render(&rgb565, G8lS548TestPattern::Gradient);
assert_eq!(gradient.buffer.len(), 720 * 1280 / 2);
assert_eq!(gradient.receipt.pitch_bytes, 1440);
assert_eq!(gradient.receipt.format_code, 2);
assert_eq!(gradient.receipt.pattern_code, 3);
assert_eq!(gradient.receipt.checksum, PHONE_GRADIENT_RGB565);
let bgra8888 = phone(G8lS548PixelFormat::Bgra8888);
let checker = render(&bgra8888, G8lS548TestPattern::Checkerboard { cell: 40 });
assert_eq!(checker.receipt.pattern_code, 2);
assert_eq!(checker.receipt.pattern_parameter, 40);
assert_eq!(checker.receipt.checksum, PHONE_CHECKER40_BGRA8888);
assert_ne!(checker.receipt.checksum, gradient.receipt.checksum);
}
#[test]
fn checkerboard_64x64_cell8_alternates_and_pins_exact_checksum() {
let binding = small(G8lS548PixelFormat::Bgra8888);
let frame = render(&binding, G8lS548TestPattern::Checkerboard { cell: 8 });
assert_eq!(frame.buffer.len(), 64 * 64);
assert_eq!(frame.receipt.checksum, SMALL_CHECKER8_BGRA8888);
let white = bgra(0xff, 0xff, 0xff);
let black = bgra(0, 0, 0);
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 0, 0), Ok(white));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 7, 7), Ok(white));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 8, 0), Ok(black));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 0, 8), Ok(black));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 8, 8), Ok(white));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 63, 63), Ok(white));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 63, 0), Ok(black));
let whites = frame.buffer.iter().filter(|word| **word == white).count();
let blacks = frame.buffer.iter().filter(|word| **word == black).count();
assert_eq!((whites, blacks), (2048, 2048));
// A different cell size changes the checksum.
let cell16 = render(&binding, G8lS548TestPattern::Checkerboard { cell: 16 });
assert_ne!(cell16.receipt.checksum, frame.receipt.checksum);
}
#[test]
fn colour_bars_64x64_rgb565_packs_two_pixels_per_word_and_pins_checksum() {
let binding = small(G8lS548PixelFormat::Rgb565);
let frame = render(&binding, G8lS548TestPattern::ColourBars);
assert_eq!(frame.buffer.len(), 64 * 64 / 2);
assert_eq!(frame.receipt.pitch_bytes, 128);
assert_eq!(frame.receipt.checksum, SMALL_BARS_RGB565);
// White = 0xffff, yellow = 0xffe0, cyan = 0x07ff, green = 0x07e0,
// magenta = 0xf81f, red = 0xf800, blue = 0x001f, black = 0x0000.
let expected = [0xffff, 0xffe0, 0x07ff, 0x07e0, 0xf81f, 0xf800, 0x001f, 0x0000];
for (bar, packed) in expected.into_iter().enumerate() {
assert_eq!(
s548_pack_pixel(G8lS548PixelFormat::Rgb565, S548_COLOUR_BARS[bar]),
packed
);
assert_eq!(
s548_read_pixel(&frame.buffer, &binding, bar as u32 * 8, 3),
Ok(packed)
);
}
// Word 0 holds pixels (0,0) low half and (1,0) high half, both white.
assert_eq!(frame.buffer[0], 0xffff_ffff);
// Word 4 holds pixels (8,0) and (9,0): yellow twice.
assert_eq!(frame.buffer[4], 0xffe0_ffe0);
assert_eq!(s548_pixel_location(&binding, 9, 0), Ok((4, 16)));
assert_eq!(s548_pixel_location(&binding, 0, 1), Ok((32, 0)));
}
#[test]
fn gradient_64x64_corners_are_exact() {
let binding = small(G8lS548PixelFormat::Bgra8888);
let frame = render(&binding, G8lS548TestPattern::Gradient);
assert_eq!(frame.receipt.checksum, SMALL_GRADIENT_BGRA8888);
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 0, 0), Ok(bgra(0, 0, 255)));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 63, 0), Ok(bgra(255, 0, 0)));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 0, 63), Ok(bgra(0, 255, 255)));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 63, 63), Ok(bgra(255, 255, 0)));
// Monotonic red ramp along the top row.
let mut previous = 0u32;
for x in 0..64 {
let red = (s548_read_pixel(&frame.buffer, &binding, x, 0).unwrap() >> 16) & 0xff;
assert!(red >= previous);
previous = red;
}
// A 1x1 gradient is valid and degenerates to a single blue pixel.
let one = G8lS548FramebufferBinding::tight(1, 1, 1, G8lS548PixelFormat::Bgra8888);
let dot = render(&one, G8lS548TestPattern::Gradient);
assert_eq!(dot.buffer, vec![bgra(0, 0, 255)]);
}
#[test]
fn border_crosshair_64x64_pixels_are_exact() {
let binding = small(G8lS548PixelFormat::Bgra8888);
let frame = render(&binding, G8lS548TestPattern::BorderCrosshair);
assert_eq!(frame.receipt.pattern_code, 4);
assert_eq!(frame.receipt.checksum, SMALL_BORDER_BGRA8888);
let white = bgra(0xff, 0xff, 0xff);
let red = bgra(0xff, 0, 0);
let grey = bgra(0x20, 0x20, 0x20);
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 0, 0), Ok(white));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 3, 30), Ok(white));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 60, 30), Ok(white));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 30, 3), Ok(white));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 30, 60), Ok(white));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 4, 30), Ok(grey));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 59, 30), Ok(grey));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 32, 10), Ok(red));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 10, 32), Ok(red));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 32, 32), Ok(red));
assert_eq!(s548_read_pixel(&frame.buffer, &binding, 31, 31), Ok(grey));
let reds = frame.buffer.iter().filter(|word| **word == red).count();
// 56 interior pixels on each crosshair arm, centre counted once.
assert_eq!(reds, 56 + 56 - 1);
let whites = frame.buffer.iter().filter(|word| **word == white).count();
assert_eq!(whites, 64 * 64 - 56 * 56);
}
#[test]
fn pitch_padding_stays_zero_and_changes_the_checksum() {
let mut padded = small(G8lS548PixelFormat::Bgra8888);
padded.pitch_bytes = 320;
let frame = render(&padded, G8lS548TestPattern::ColourBars);
assert_eq!(frame.buffer.len(), 320 / 4 * 64);
assert_eq!(frame.receipt.padding_bytes_per_row, 64);
assert_eq!(frame.receipt.pixels_written, 64 * 64);
assert_eq!(frame.receipt.checksum, SMALL_BARS_BGRA8888_PITCH320);
let tight = render(&small(G8lS548PixelFormat::Bgra8888), G8lS548TestPattern::ColourBars);
assert_ne!(frame.receipt.checksum, tight.receipt.checksum);
for y in 0..64usize {
let row = &frame.buffer[y * 80..(y + 1) * 80];
assert_eq!(&row[..64], &tight.buffer[y * 64..(y + 1) * 64]);
assert!(row[64..].iter().all(|word| *word == 0));
}
assert_eq!(s548_pixel_location(&padded, 0, 1), Ok((80, 0)));
let mut padded565 = small(G8lS548PixelFormat::Rgb565);
padded565.pitch_bytes = 192;
let frame565 = render(&padded565, G8lS548TestPattern::ColourBars);
assert_eq!(frame565.buffer.len(), 192 / 4 * 64);
assert_eq!(frame565.receipt.padding_bytes_per_row, 64);
assert_eq!(frame565.receipt.checksum, SMALL_BARS_RGB565_PITCH192);
assert_eq!(s548_pixel_location(&padded565, 1, 1), Ok((48, 16)));
}
#[test]
fn write_right_is_required_but_vsync_and_dma_are_not() {
let mut state = G8lS548DisplayTestPatternState::new();
for rights in [
G8lS548FramebufferRights::empty(),
G8lS548FramebufferRights::READ,
G8lS548FramebufferRights::VSYNC,
G8lS548FramebufferRights::DMA,
G8lS548FramebufferRights::READ
.union(G8lS548FramebufferRights::VSYNC)
.union(G8lS548FramebufferRights::DMA),
] {
let mut binding = small(G8lS548PixelFormat::Bgra8888);
binding.granted_rights = rights;
assert_eq!(
service_s548_model_render_test_pattern(
&mut state,
&binding,
G8lS548TestPattern::ColourBars
),
Err(G8lS548DisplayTestPatternError::WriteRightMissing),
"rights {:#06b}",
rights.as_u8()
);
assert_eq!(state.receipt(), None);
}
for rights in [
G8lS548FramebufferRights::WRITE,
G8lS548FramebufferRights::READ.union(G8lS548FramebufferRights::WRITE),
G8lS548FramebufferRights::FULL,
] {
let mut binding = small(G8lS548PixelFormat::Bgra8888);
binding.granted_rights = rights;
let frame = render(&binding, G8lS548TestPattern::ColourBars);
assert_eq!(frame.receipt.granted_rights, rights.as_u8());
assert!(frame.receipt.granted_rights & G8lS548FramebufferRights::WRITE.as_u8() != 0);
}
assert_eq!(G8lS548FramebufferRights::FULL.as_u8(), 0b1111);
assert_eq!(G8lS548FramebufferRights::from_bits(0xff), G8lS548FramebufferRights::FULL);
}
#[test]
fn grant_exceeding_capability_fails_closed_before_rights_check() {
let mut state = G8lS548DisplayTestPatternState::new();
let mut binding = small(G8lS548PixelFormat::Bgra8888);
binding.cap_max_rights = G8lS548FramebufferRights::READ;
binding.granted_rights = G8lS548FramebufferRights::WRITE;
assert_eq!(
service_s548_model_render_test_pattern(&mut state, &binding, G8lS548TestPattern::Gradient),
Err(G8lS548DisplayTestPatternError::GrantExceedsCapability)
);
binding.cap_max_rights = G8lS548FramebufferRights::WRITE;
binding.granted_rights = G8lS548FramebufferRights::WRITE.union(G8lS548FramebufferRights::DMA);
assert_eq!(
service_s548_model_render_test_pattern(&mut state, &binding, G8lS548TestPattern::Gradient),
Err(G8lS548DisplayTestPatternError::GrantExceedsCapability)
);
binding.cap_max_rights = G8lS548FramebufferRights::empty();
binding.granted_rights = G8lS548FramebufferRights::empty();
assert_eq!(
service_s548_model_render_test_pattern(&mut state, &binding, G8lS548TestPattern::Gradient),
Err(G8lS548DisplayTestPatternError::WriteRightMissing)
);
assert_eq!(state.receipt(), None);
}
#[test]
fn malformed_geometry_fails_closed() {
let base = small(G8lS548PixelFormat::Bgra8888);
let cases: [(fn(&mut G8lS548FramebufferBinding), G8lS548DisplayTestPatternError); 8] = [
(|b| b.width = 0, G8lS548DisplayTestPatternError::ZeroDimension),
(|b| b.height = 0, G8lS548DisplayTestPatternError::ZeroDimension),
(
|b| b.width = S548_MAX_DIMENSION + 1,
G8lS548DisplayTestPatternError::DimensionTooLarge,
),
(
|b| b.height = S548_MAX_DIMENSION + 1,
G8lS548DisplayTestPatternError::DimensionTooLarge,
),
(|b| b.pitch_bytes = 258, G8lS548DisplayTestPatternError::PitchNotWordAligned),
(|b| b.pitch_bytes = 252, G8lS548DisplayTestPatternError::PitchBelowRowBytes),
(
|b| b.pitch_bytes = S548_MAX_PITCH_BYTES + 4,
G8lS548DisplayTestPatternError::PitchTooLarge,
),
(
|b| {
b.width = 4096;
b.height = 4096;
b.pitch_bytes = 4096 * 4 + 4;
},
G8lS548DisplayTestPatternError::PitchTooLarge,
),
];
for (mutate, expected) in cases {
let mut binding = base;
mutate(&mut binding);
assert_eq!(render_s548_test_pattern(&binding, G8lS548TestPattern::Gradient), Err(expected));
}
// Largest legal geometry (4096 x 4096 x 4 = 64 MiB) validates exactly at the limit.
let mut maximal = base;
maximal.width = 4096;
maximal.height = 4096;
maximal.pitch_bytes = 4096 * 4;
assert_eq!(s548_validate_binding(&maximal), Ok(4096 * 4096));
// Any RGB565 frame with a 4-aligned pitch one word above the row is legal.
let mut odd565 = small(G8lS548PixelFormat::Rgb565);
odd565.pitch_bytes = 132;
assert_eq!(s548_validate_binding(&odd565), Ok(132 / 4 * 64));
}
#[test]
fn pattern_parameter_boundaries_fail_closed() {
let binding = small(G8lS548PixelFormat::Bgra8888);
assert_eq!(
render_s548_test_pattern(&binding, G8lS548TestPattern::Checkerboard { cell: 0 }),
Err(G8lS548DisplayTestPatternError::CheckerboardCellZero)
);
assert_eq!(
render_s548_test_pattern(&binding, G8lS548TestPattern::Checkerboard { cell: 65 }),
Err(G8lS548DisplayTestPatternError::CheckerboardCellExceedsFrame)
);
assert!(render_s548_test_pattern(&binding, G8lS548TestPattern::Checkerboard { cell: 64 }).is_ok());
assert!(render_s548_test_pattern(&binding, G8lS548TestPattern::Checkerboard { cell: 1 }).is_ok());
let narrow = G8lS548FramebufferBinding::tight(2, 7, 16, G8lS548PixelFormat::Bgra8888);
assert_eq!(
render_s548_test_pattern(&narrow, G8lS548TestPattern::ColourBars),
Err(G8lS548DisplayTestPatternError::WidthBelowBarCount)
);
let eight = G8lS548FramebufferBinding::tight(3, 8, 1, G8lS548PixelFormat::Bgra8888);
let bars = render(&eight, G8lS548TestPattern::ColourBars);
for bar in 0..8usize {
assert_eq!(bars.buffer[bar], s548_pack_pixel(G8lS548PixelFormat::Bgra8888, S548_COLOUR_BARS[bar]));
}
let tiny = G8lS548FramebufferBinding::tight(4, 8, 9, G8lS548PixelFormat::Bgra8888);
assert_eq!(
render_s548_test_pattern(&tiny, G8lS548TestPattern::BorderCrosshair),
Err(G8lS548DisplayTestPatternError::FrameTooSmallForBorderCrosshair)
);
let minimal = G8lS548FramebufferBinding::tight(5, 9, 9, G8lS548PixelFormat::Bgra8888);
let cross = render(&minimal, G8lS548TestPattern::BorderCrosshair);
assert_eq!(cross.buffer[4 * 9 + 4], bgra(0xff, 0, 0));
assert_eq!(cross.buffer.iter().filter(|w| **w == bgra(0x20, 0x20, 0x20)).count(), 0);
}
#[test]
fn bounds_checked_pixel_access_rejects_out_of_range() {
let binding = small(G8lS548PixelFormat::Bgra8888);
let mut buffer = vec![0u32; 64 * 64];
assert_eq!(
s548_write_pixel(&mut buffer, &binding, 64, 0, 1),
Err(G8lS548DisplayTestPatternError::PixelOutOfBounds)
);
assert_eq!(
s548_write_pixel(&mut buffer, &binding, 0, 64, 1),
Err(G8lS548DisplayTestPatternError::PixelOutOfBounds)
);
assert_eq!(
s548_read_pixel(&buffer, &binding, u32::MAX, u32::MAX),
Err(G8lS548DisplayTestPatternError::PixelOutOfBounds)
);
assert!(buffer.iter().all(|word| *word == 0));
let mut short = vec![0u32; 64 * 63];
assert_eq!(
s548_write_pixel(&mut short, &binding, 0, 63, 1),
Err(G8lS548DisplayTestPatternError::BufferIndexOutOfRange)
);
assert_eq!(
s548_read_pixel(&short, &binding, 63, 63),
Err(G8lS548DisplayTestPatternError::BufferIndexOutOfRange)
);
assert_eq!(s548_write_pixel(&mut short, &binding, 63, 62, 0xdead_beef), Ok(()));
assert_eq!(short[64 * 62 + 63], 0xdead_beef);
// RGB565 writes touch only their own half-word.
let binding565 = small(G8lS548PixelFormat::Rgb565);
let mut words = vec![0u32; 64 * 64 / 2];
assert_eq!(s548_write_pixel(&mut words, &binding565, 0, 0, 0x1_f800), Ok(()));
assert_eq!(s548_write_pixel(&mut words, &binding565, 1, 0, 0x001f), Ok(()));
assert_eq!(words[0], 0x001f_f800);
assert_eq!(s548_read_pixel(&words, &binding565, 0, 0), Ok(0xf800));
assert_eq!(s548_read_pixel(&words, &binding565, 1, 0), Ok(0x001f));
}
#[test]
fn fnv1a_64_matches_reference_vectors_and_word_byte_order() {
assert_eq!(s548_fnv1a_64(b""), S548_FNV1A_OFFSET_BASIS);
assert_eq!(s548_fnv1a_64(b"a"), 0xaf63_dc4c_8601_ec8c);
assert_eq!(s548_fnv1a_64(b"foobar"), 0x8594_4171_f739_67e8);
assert_eq!(s548_checksum_words(&[]), S548_FNV1A_OFFSET_BASIS);
assert_eq!(
s548_checksum_words(&[0x6162_6364]),
s548_fnv1a_64(&[0x64, 0x63, 0x62, 0x61])
);
assert_eq!(
s548_checksum_words(&[0x0403_0201, 0x0807_0605]),
s548_fnv1a_64(&[1, 2, 3, 4, 5, 6, 7, 8])
);
assert_ne!(s548_checksum_words(&[1, 0]), s548_checksum_words(&[0, 1]));
}
#[test]
fn receipt_carries_binding_identity_and_zero_claims() {
let binding = phone(G8lS548PixelFormat::Rgb565);
let frame = render(&binding, G8lS548TestPattern::BorderCrosshair);
let receipt = frame.receipt;
assert_eq!(receipt.sequence, S548_SEQUENCE);
assert_eq!(receipt.predecessor_sequence, S548_EXPECTED_PREDECESSOR);
assert_eq!(receipt.r1_stage, S548_R1_STAGE);
assert_eq!(receipt.cap_id, 0x5480);
assert_eq!((receipt.width, receipt.height, receipt.pitch_bytes), (720, 1280, 1440));
assert_eq!(receipt.format_code, 2);
assert_eq!(receipt.granted_rights, G8lS548FramebufferRights::WRITE.as_u8());
assert_eq!(receipt.pattern_code, 4);
assert_eq!(receipt.pattern_parameter, 0);
assert_eq!(receipt.pixels_written, 720 * 1280);
assert_eq!(receipt.buffer_words, 720 * 1280 / 2);
assert_eq!(receipt.checksum, s548_checksum_words(&frame.buffer));
assert!(!receipt.hardware_present);
assert!(receipt.s540_physical_verdict_retained_red);
assert!(receipt.s543_physical_verdict_retained_red);
assert!(!receipt.automatic_promotion);
assert_eq!(receipt.supported_profile_runtime_observations, 0);
assert_eq!(receipt.physical_observations, 0);
assert!(!receipt.boot_to_ui_physically_observed);
assert!(!receipt.r1_acceptance_complete);
assert!(!receipt.runbook_executed);
assert_eq!(G8lS548DisplayTestPatternState::default().receipt(), None);
}
#[test]
fn source_only_gate_keeps_runtime_physical_and_r1_claims_zero() {
assert!(SOURCE.contains("S548_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0"));
assert!(SOURCE.contains("S548_PHYSICAL_OBSERVATIONS: usize = 0"));
assert!(SOURCE.contains("S548_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0"));
assert!(SOURCE.contains("S548_HARDWARE_PRESENT: bool = false"));
assert!(SOURCE.contains("S548_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false"));
assert!(SOURCE.contains("S548_R1_ACCEPTANCE_COMPLETE: bool = false"));
assert!(SOURCE.contains("RUNBOOK_EXECUTED_IN_S548: bool = false"));
assert!(SOURCE.contains("S548_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true"));
assert!(SOURCE.contains("S548_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true"));
}
snippet sha256: 5a50432d0342…file sha256: 5a50432d0342…
03 · Kapı kimlik kaydı
Operations sıra, kimlik ve başlık bağı
tam Operations kaydıL3027–L3088
website/src/lib/operations.ts::g8l-s548-r1-display-test-pattern-framebuffer-capability-binding
{
id: "g8l-s548-r1-display-test-pattern-framebuffer-capability-binding",
date: "2026-08-30",
sequence: 548,
status: "passed",
umbrella_status: "partial",
title: "S548 · R1 ekran: test deseni ve FramebufferCap bağlama modeli",
summary:
"S548 kaynak/host model kapısı PASS'tir: M4.4 FramebufferCap modelinin PixelFormat ve FramebufferRights şekillerine bağlı deterministik bir ekran test deseni renderer'ı modellenmiştir. Renk barları (8 bar), dama tahtası (ayarlanabilir hücre), gradyan ve çerçeve+artı desenleri; açık pitch işleme (pitch, genişlik×bpp'yi aşabilir), BGRA8888 ve RGB565 paketleme, sınır denetimli piksel yazımı ve üretilen tamponun FNV-1a 64 checksum'ı ile alloc::vec::Vec<u32> içine çizilir; 720x1280 ve 64x64 durumları için exact checksum'lar sabitlenmiştir. Grant hak denetimi fail-closed'dur: WRITE zorunludur, VSYNC/DMA gerekli değildir ve cap sınırını aşan grant reddedilir. Focused 21/21 PASS'tir; hiçbir MMIO, mailbox, gerçek framebuffer eşlemesi veya production çağrı noktası yoktur. S540 ve S543 fiziksel raw/verdict değişmez RED kalır; physical observation=0, RUNBOOK_EXECUTED_IN_S548=NO, Boot-to-UI=false ve R1 acceptance=false'dur. S549, S548 geometri/format şekillerini tüketen host-only RP1 DSI host register haritası ve D-PHY zamanlama modeli kapısıdır.",
evidence: [
"S548, S547'den ayrı kaynak modülü, 21-test focused binary, proof, status manifest, Operations kaydı ve complete Code kartına sahiptir; production callsite eklenmemiştir.",
"Dar S548 source/host status=PASS; R1 umbrella=PARTIAL, R1 aşaması 2 (ekran, dokunma ve temel UI); S540 ve S543 physical gate status=RED olarak ayrı tutulur.",
"Model M4.4 PixelFormat (Bgra8888=4 B/px, Rgb565=2 B/px) ve FramebufferRights (READ/WRITE/VSYNC/DMA bitmask) şekillerini kernel no_std ve host simülasyonda aynı derlenecek biçimde yansıtır.",
"G8lS548FramebufferBinding cap_id, width, height, pitch_bytes, format, cap_max_rights ve granted_rights taşır; render pitch_bytes×height/4 kelimelik Vec<u32> üretir.",
"Piksel (x,y) byte ofseti y×pitch+x×bpp'dir; RGB565 için yarım-kelime kayması (ofset%4)×8'dir; pitch dolgu kelimeleri hiç yazılmaz, sıfır kalır ve checksum'a dahildir.",
"BGRA8888 paketi 0xff<<24|R<<16|G<<8|B (bellek sırası B,G,R,A); RGB565 paketi R5<<11|G6<<5|B5'tir.",
"Dört desen tablo güdümlüdür: renk barları (bar=x×8/width, beyaz-sarı-camgöbeği-yeşil-macenta-kırmızı-mavi-siyah), dama tahtası ((x/cell+y/cell) çift ise beyaz), gradyan (r=x×255/(w-1), g=y×255/(h-1), b=255-r), çerçeve+artı (4 px beyaz çerçeve, kırmızı orta sütun/satır, 0x202020 zemin).",
"Sabitlenen FNV-1a 64 checksum'lar bağımsız referans uygulamasıyla çapraz doğrulanmıştır: 720x1280 BGRA8888 renk barları 0x7c544deb81579325, 720x1280 RGB565 gradyan 0x2c3b445594879b71, 720x1280 BGRA8888 dama(40) 0xbb4f86efb3eb0325, 64x64 BGRA8888 dama(8) 0xa5469e63bfa70325, 64x64 RGB565 renk barları 0x8e562bac11284b25, 64x64 BGRA8888 çerçeve+artı 0xf748ab56fde2f1b2, 64x64 BGRA8888 gradyan 0x10b5b99e81f4aef5.",
"Pitch dolgulu durumlar da sabitlenmiştir: 64x64 BGRA8888 pitch 320 renk barları 0xe6453365e4503325 ve 64x64 RGB565 pitch 192 renk barları 0xde434cdc99b58b25; dolgu checksum'ı değiştirir, piksel içeriğini değiştirmez.",
"Hak denetimi fail-closed'dur: granted_rights cap_max_rights'ı aşarsa GrantExceedsCapability, WRITE yoksa WriteRightMissing döner; READ, VSYNC veya DMA tek başına veya birlikte WRITE'ın yerini tutmaz; VSYNC ve DMA gerekli değildir.",
"Geometri denetimi fail-closed'dur: sıfır boyut, 4096 üstü boyut, 4'e hizasız pitch, satır byte'ının altındaki pitch, 16384 üstü pitch ve 64 MiB üstü veya checked çarpım taşması reddedilir; 4096x4096x4 tam sınırda kabul edilir.",
"Desen parametreleri fail-closed'dur: 8 genişliğin altında renk barları, 0 veya kareyi aşan dama hücresi ve 9x9 altı çerçeve+artı reddedilir.",
"Piksel okuma/yazma sınır denetimlidir: geometri dışı koordinat PixelOutOfBounds, tampon uzunluğunu aşan kelime BufferIndexOutOfRange döner; RGB565 yazımı yalnız kendi yarım-kelimesine dokunur.",
"On beş hata kodu sıfırdan farklı ve benzersizdir; exact tekrar aynı receipt ile Retained döner, yayın sonrası farklı desen, cap_id veya hak kümesi PublishedStateDrift ile reddedilir.",
"Focused target 1 grup / 21 passed / 0 failed / 0 ignored / 0 filtered verdi.",
"Implementation 21461 B / eee6f8da27a9fb74c9590c8de21ea6c9409cdb2a2353ea1327d5a1dbee4baceb; focused test 27193 B / 5a50432d03423c286321f049818dda3fb5ca74234e2d7d54408e9749888f9f53 SHA-256'dır.",
"Proof 5612 B'dir.",
"S540 immutable raw 20525 B ve S543 immutable raw 20509 B byte-exact korunur; her ikisi RED, automatic promotion=false ve rerun=false'dur.",
"S548 sırasında panel, DSI host, mailbox, MMIO, gerçek framebuffer eşlemesi, SD write/read-back/eject, UART open/capture, power transition veya yeni immutable raw üretimi yapılmadı.",
"RUNBOOK_EXECUTED_IN_S548=NO; supported-profile runtime observations=0, physical observations=0, hardware present=false, Boot-to-UI physically observed=false ve R1 acceptance=false'dur.",
"S549 yalnız host üzerinde RP1 DSI host register haritasını ve D-PHY zamanlama modelini S548 geometri/format şekilleri üzerinden modelleyecektir; aygıt veya fiziksel koşu yetkisi değildir.",
],
commands: [
"CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s548_r1_display_test_pattern_framebuffer_capability_binding -- --test-threads=1",
],
terminalSessions: [
{
id: "s548-focused",
title: "S548 ekran test deseni ve FramebufferCap bağlama focused kabulü",
commandLines: [
"CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s548_r1_display_test_pattern_framebuffer_capability_binding -- --test-threads=1",
],
outputLines: [
"test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s",
"S548 focused=1 group / 21 passed / 0 failed",
"hardware=none physical=0 runbook=NO",
],
exitCode: 0,
outputMode: "complete",
},
],
terminalSessionsNote:
"S548 kaynak/host model PASS'tir; supported-profile runtime veya fiziksel PASS değildir. S540 ve S543 RED raw ve kararları değişmez.",
limitations: [
"S548 yalnız host üzerinde derlenen ve focused testle sürülen bir modeldir; hiçbir donanım/panel/modem/board gözlemi yoktur.",
"Renderer hiçbir MMIO, mailbox veya gerçek framebuffer eşlemesine dokunmaz; üretilen Vec<u32> tamponu yalnız checksum ve piksel assertion'ları ile doğrulanır.",
"Modül hiçbir boot, IRQ, scheduler veya driver yoluna bağlanmamıştır; production callsite wired=false'dur.",
"S540 ve S543 fiziksel RED immutable kalır; S546 üçüncü fiziksel koşu ayrı bir kapıdır ve kararı burada varsayılmaz.",
"Boot-to-UI fiziksel olarak gözlenmedi; Boot-to-UI ve R1 acceptance false kalır.",
"S549 host-only RP1 DSI host register haritası ve D-PHY zamanlama modeli tamamlanmadan panel yolu için yeni bir fiziksel aday yoktur.",
],
},snippet sha256: f1a6c5b66334…file sha256: 9726dbf00f84…
Focused test komutu
CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s548_r1_display_test_pattern_framebuffer_capability_binding -- --test-threads=1proof: docs/M8.1-RPi5-G8l-S548-R1-Display-Test-Pattern-Framebuffer-Capability-Binding-Proof.md
Registry schema v5 · generator
website/scripts/generate-code-gates.mjs · Tam SHA-256: 3050638b71a684d8f8f947a8a6faa237a17fa8db5dc0db04fb207b668b462af9