S550 · SOURCE-BOUND GATE EVIDENCE
S550 · R1 ekran: ILI9881 panel DCS init dizisi modeli
tam S550 implementation modülü → Operations --test hedefi ile bağlı tam focused test → ayrı Operations kaydı Bu sayfa yalnız S550 kapısına aittir; komşu kapıların kaynakları bu kabulün içine katılmaz.
S550Focused kod testiOperations id exactsource SHA exacttest target exact
operation: g8l-s550-r1-ili9881-panel-dcs-init-sequence-model
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–L936
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s550_r1_ili9881_panel_dcs_init_sequence_model.rs::S550 r1 ili9881 panel dcs init sequence model implementation
#![allow(unexpected_cfgs)]
//! S550 models an ILI9881C-class panel initialisation program for the
//! Raspberry Pi Touch Display 2 (5" and 7" profiles, both 720x1280 over two
//! MIPI DSI lanes) as a pure host/source model.
//!
//! The model owns three pieces: a table of DCS/generic packets and delays per
//! panel profile, an encoder that turns each packet into raw DSI packet bytes
//! (24-bit header + 6-bit Hamming ECC for every packet, 16-bit reflected
//! CRC-16 with polynomial `0x8408` for long packets), and a fail-closed
//! validator that enforces the ordered phases page select -> register writes
//! -> sleep-out (`0x11`) -> delay >= 120 ms -> display-on (`0x29`), a maximum
//! payload size and a closed set of data types (`0x05`, `0x15`, `0x29`,
//! `0x39`).
//!
//! S550 does NOT claim anything physical: no panel, DSI host, RP1, touch
//! controller, board, UART, SD card or power transition exists for this gate
//! (`S550_PHYSICAL_OBSERVATIONS = 0`, `RUNBOOK_EXECUTED_IN_S550 = false`).
//! The module is not wired into any boot, IRQ, scheduler or driver path; only
//! the focused simulation test drives it. The S540 and S543 physical verdicts
//! remain immutable RED. The register subset in the profile tables is a
//! representative model subset, not a vendor-complete init dump.
//!
//! Predecessor: S549 (RP1 DSI host register map / D-PHY timing model).
//! Next gate: S551.
pub const S550_SEQUENCE: usize = 550;
pub const S550_EXPECTED_PREDECESSOR: usize = 549;
pub const S550_R1_STAGE: u8 = 2;
pub const S550_R1_RANGE_FIRST: usize = 536;
pub const S550_R1_RANGE_LAST: usize = 568;
pub const S550_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0;
pub const S550_PHYSICAL_OBSERVATIONS: usize = 0;
pub const S550_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0;
pub const S550_SD_WRITES: usize = 0;
pub const S550_UART_OPENS: usize = 0;
pub const S550_POWER_TRANSITIONS: usize = 0;
pub const S550_NEW_IMMUTABLE_RAW_CAPTURES: usize = 0;
pub const S550_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S550_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S550_AUTOMATIC_PROMOTION: bool = false;
pub const S550_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false;
pub const S550_HARDWARE_PRESENT: bool = false;
pub const S550_R1_ACCEPTANCE_COMPLETE: bool = false;
pub const RUNBOOK_EXECUTED_IN_S550: bool = false;
/// Panel geometry shared by both Touch Display 2 profiles.
pub const S550_PANEL_WIDTH: u16 = 720;
pub const S550_PANEL_HEIGHT: u16 = 1280;
pub const S550_DSI_LANES: u8 = 2;
/// Virtual channel encoded into bits 7:6 of the data identifier byte.
pub const S550_VIRTUAL_CHANNEL: u8 = 0;
/// DSI data types accepted by the model.
pub const S550_DT_DCS_SHORT_WRITE_0: u8 = 0x05;
pub const S550_DT_DCS_SHORT_WRITE_1: u8 = 0x15;
pub const S550_DT_GENERIC_LONG_WRITE: u8 = 0x29;
pub const S550_DT_DCS_LONG_WRITE: u8 = 0x39;
/// DCS commands with ordering significance.
pub const S550_DCS_SLEEP_OUT: u8 = 0x11;
pub const S550_DCS_DISPLAY_ON: u8 = 0x29;
/// ILI9881C page-select prefix: `FF 98 81 <page>`.
pub const S550_PAGE_SELECT_PREFIX: [u8; 3] = [0xFF, 0x98, 0x81];
pub const S550_PAGE_MAX: u8 = 4;
/// Bounded buffers: payload bytes per packet and steps per program.
pub const S550_MAX_PAYLOAD_BYTES: usize = 32;
pub const S550_MAX_ENCODED_BYTES: usize = 4 + S550_MAX_PAYLOAD_BYTES + 2;
pub const S550_MAX_PROGRAM_STEPS: usize = 128;
pub const S550_MIN_SLEEP_OUT_DELAY_MS: u32 = 120;
pub const S550_MAX_SINGLE_DELAY_MS: u32 = 1_000;
pub const S550_MAX_TOTAL_DELAY_MS: u32 = 5_000;
/// CRC-16 used for DSI long packets: `x^16 + x^12 + x^5 + 1`, reflected
/// form `0x8408`, initial value `0xFFFF`, no final XOR, LSB-first.
pub const S550_CRC16_POLY_REFLECTED: u16 = 0x8408;
pub const S550_CRC16_INIT: u16 = 0xFFFF;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS550PanelProfile {
TouchDisplay2Inch5,
TouchDisplay2Inch7,
}
impl G8lS550PanelProfile {
pub const fn id(self) -> u8 {
match self {
Self::TouchDisplay2Inch5 => 5,
Self::TouchDisplay2Inch7 => 7,
}
}
}
/// Modelled video timing per profile. Host table values; not vendor
/// validated and not observed on any panel.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS550PanelTiming {
pub width: u16,
pub height: u16,
pub lanes: u8,
pub hfront_porch: u16,
pub hsync: u16,
pub hback_porch: u16,
pub vfront_porch: u16,
pub vsync: u16,
pub vback_porch: u16,
pub pixel_clock_khz: u32,
pub sleep_out_delay_ms: u32,
pub display_on_delay_ms: u32,
}
impl G8lS550PanelTiming {
pub const fn htotal(self) -> u32 {
self.width as u32 + self.hfront_porch as u32 + self.hsync as u32 + self.hback_porch as u32
}
pub const fn vtotal(self) -> u32 {
self.height as u32 + self.vfront_porch as u32 + self.vsync as u32 + self.vback_porch as u32
}
/// Frame rate in millihertz, checked; `None` when the timing is empty
/// or the result does not fit `u32`.
pub fn refresh_mhz(self) -> Option<u32> {
let total = (self.htotal() as u64).checked_mul(self.vtotal() as u64)?;
if total == 0 {
return None;
}
let millihertz = (self.pixel_clock_khz as u64).checked_mul(1_000_000)? / total;
u32::try_from(millihertz).ok()
}
}
pub const S550_TIMING_INCH5: G8lS550PanelTiming = G8lS550PanelTiming {
width: S550_PANEL_WIDTH,
height: S550_PANEL_HEIGHT,
lanes: S550_DSI_LANES,
hfront_porch: 80,
hsync: 20,
hback_porch: 80,
vfront_porch: 12,
vsync: 4,
vback_porch: 20,
pixel_clock_khz: 71_000,
sleep_out_delay_ms: 120,
display_on_delay_ms: 20,
};
pub const S550_TIMING_INCH7: G8lS550PanelTiming = G8lS550PanelTiming {
width: S550_PANEL_WIDTH,
height: S550_PANEL_HEIGHT,
lanes: S550_DSI_LANES,
hfront_porch: 239,
hsync: 33,
hback_porch: 50,
vfront_porch: 20,
vsync: 2,
vback_porch: 30,
pixel_clock_khz: 83_330,
sleep_out_delay_ms: 120,
display_on_delay_ms: 20,
};
pub const fn s550_timing_for(profile: G8lS550PanelProfile) -> G8lS550PanelTiming {
match profile {
G8lS550PanelProfile::TouchDisplay2Inch5 => S550_TIMING_INCH5,
G8lS550PanelProfile::TouchDisplay2Inch7 => S550_TIMING_INCH7,
}
}
/// One init program step: a DSI write packet or a host-side delay.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS550Step<'a> {
Write { data_type: u8, payload: &'a [u8] },
DelayMs(u32),
}
const fn page(p: u8) -> G8lS550Step<'static> {
// Page select is a 4-byte generic long write in the ILI9881C convention.
match p {
0 => G8lS550Step::Write {
data_type: 0x29,
payload: &[0xFF, 0x98, 0x81, 0x00],
},
1 => G8lS550Step::Write {
data_type: 0x29,
payload: &[0xFF, 0x98, 0x81, 0x01],
},
2 => G8lS550Step::Write {
data_type: 0x29,
payload: &[0xFF, 0x98, 0x81, 0x02],
},
3 => G8lS550Step::Write {
data_type: 0x29,
payload: &[0xFF, 0x98, 0x81, 0x03],
},
_ => G8lS550Step::Write {
data_type: 0x29,
payload: &[0xFF, 0x98, 0x81, 0x04],
},
}
}
/// Representative 7" Touch Display 2 init program (model subset).
pub const S550_INIT_PROGRAM_INCH7: &[G8lS550Step<'static>] = &[
page(3),
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x01, 0x00],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x02, 0x00],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x03, 0x73],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x04, 0x00],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x05, 0x00],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x06, 0x0A],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x07, 0x00],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x08, 0x00],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x09, 0x01],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x0A, 0x00],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x0B, 0x00],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x0C, 0x01],
},
page(4),
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x6C, 0x15],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x6E, 0x2A],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x6F, 0x33],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x3A, 0x94],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x8D, 0x14],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x87, 0xBA],
},
page(1),
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x22, 0x0A],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x31, 0x00],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x53, 0x8A],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x55, 0x8A],
},
G8lS550Step::Write {
data_type: 0x39,
payload: &[0xA0, 0x00, 0x1D, 0x2A, 0x13],
},
page(0),
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x36, 0x00],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x3A, 0x77],
},
G8lS550Step::Write {
data_type: 0x05,
payload: &[0x11],
},
G8lS550Step::DelayMs(120),
G8lS550Step::Write {
data_type: 0x05,
payload: &[0x29],
},
G8lS550Step::DelayMs(20),
];
/// Representative 5" Touch Display 2 init program (model subset).
pub const S550_INIT_PROGRAM_INCH5: &[G8lS550Step<'static>] = &[
page(3),
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x01, 0x00],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x02, 0x00],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x03, 0x53],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x04, 0x53],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x05, 0x13],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x06, 0x04],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x07, 0x02],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x08, 0x02],
},
page(4),
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x6C, 0x15],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x6E, 0x30],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x6F, 0x37],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x3A, 0xA4],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x8D, 0x1A],
},
page(1),
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x22, 0x0A],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x53, 0x72],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x55, 0x77],
},
G8lS550Step::Write {
data_type: 0x39,
payload: &[0xA0, 0x00, 0x10, 0x1C, 0x0F],
},
page(0),
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x36, 0x00],
},
G8lS550Step::Write {
data_type: 0x15,
payload: &[0x3A, 0x77],
},
G8lS550Step::Write {
data_type: 0x05,
payload: &[0x11],
},
G8lS550Step::DelayMs(120),
G8lS550Step::Write {
data_type: 0x05,
payload: &[0x29],
},
G8lS550Step::DelayMs(20),
];
pub const fn s550_canonical_program(
profile: G8lS550PanelProfile,
) -> &'static [G8lS550Step<'static>] {
match profile {
G8lS550PanelProfile::TouchDisplay2Inch5 => S550_INIT_PROGRAM_INCH5,
G8lS550PanelProfile::TouchDisplay2Inch7 => S550_INIT_PROGRAM_INCH7,
}
}
/// 6-bit MIPI DSI/CSI-2 header ECC over the 24-bit header
/// (`D0..D7` = data identifier, `D8..D15` = second byte, `D16..D23` = third).
pub const fn s550_header_ecc(data_id: u8, byte1: u8, byte2: u8) -> u8 {
const ROWS: [u32; 6] = [
// P0..P5 bit masks over D0..D23 (MIPI DSI Hamming SEC-DED code).
(1 << 0)
| (1 << 1)
| (1 << 2)
| (1 << 4)
| (1 << 5)
| (1 << 7)
| (1 << 10)
| (1 << 11)
| (1 << 13)
| (1 << 16)
| (1 << 20)
| (1 << 21)
| (1 << 22)
| (1 << 23),
(1 << 0)
| (1 << 1)
| (1 << 3)
| (1 << 4)
| (1 << 6)
| (1 << 8)
| (1 << 10)
| (1 << 12)
| (1 << 14)
| (1 << 17)
| (1 << 20)
| (1 << 21)
| (1 << 22)
| (1 << 23),
(1 << 0)
| (1 << 2)
| (1 << 3)
| (1 << 5)
| (1 << 6)
| (1 << 9)
| (1 << 11)
| (1 << 12)
| (1 << 15)
| (1 << 18)
| (1 << 20)
| (1 << 21)
| (1 << 22),
(1 << 1)
| (1 << 2)
| (1 << 3)
| (1 << 7)
| (1 << 8)
| (1 << 9)
| (1 << 13)
| (1 << 14)
| (1 << 15)
| (1 << 19)
| (1 << 20)
| (1 << 21)
| (1 << 23),
(1 << 4)
| (1 << 5)
| (1 << 6)
| (1 << 7)
| (1 << 8)
| (1 << 9)
| (1 << 16)
| (1 << 17)
| (1 << 18)
| (1 << 19)
| (1 << 20)
| (1 << 22)
| (1 << 23),
(1 << 10)
| (1 << 11)
| (1 << 12)
| (1 << 13)
| (1 << 14)
| (1 << 15)
| (1 << 16)
| (1 << 17)
| (1 << 18)
| (1 << 19)
| (1 << 21)
| (1 << 22)
| (1 << 23),
];
let header = data_id as u32 | ((byte1 as u32) << 8) | ((byte2 as u32) << 16);
let mut ecc = 0u8;
let mut i = 0;
while i < 6 {
let parity = (header & ROWS[i]).count_ones() & 1;
ecc |= (parity as u8) << i;
i += 1;
}
ecc
}
/// Reflected CRC-16 (`0x8408`, init `0xFFFF`) over a long-packet payload.
pub fn s550_payload_crc16(payload: &[u8]) -> u16 {
let mut crc = S550_CRC16_INIT;
for &byte in payload {
crc ^= byte as u16;
for _ in 0..8 {
crc = if crc & 1 != 0 {
(crc >> 1) ^ S550_CRC16_POLY_REFLECTED
} else {
crc >> 1
};
}
}
crc
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS550EncodedPacket {
pub bytes: [u8; S550_MAX_ENCODED_BYTES],
pub len: usize,
pub ecc: u8,
pub crc: Option<u16>,
}
impl G8lS550EncodedPacket {
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len]
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS550PanelInitReceipt {
pub sequence: usize,
pub predecessor_sequence: usize,
pub r1_stage: u8,
pub profile: G8lS550PanelProfile,
pub width: u16,
pub height: u16,
pub lanes: u8,
pub step_count: usize,
pub packet_count: usize,
pub short_packet_count: usize,
pub long_packet_count: usize,
pub page_select_count: usize,
pub register_write_count: usize,
pub total_delay_ms: u32,
pub sleep_out_delay_ms: u32,
pub encoded_bytes: usize,
pub sleep_out_ecc: u8,
pub display_on_ecc: u8,
pub program_digest: 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 runbook_executed: bool,
}
#[derive(Debug)]
pub struct G8lS550PanelInitState {
receipt: Option<G8lS550PanelInitReceipt>,
}
impl G8lS550PanelInitState {
pub const fn new() -> Self {
Self { receipt: None }
}
pub const fn receipt(&self) -> Option<G8lS550PanelInitReceipt> {
self.receipt
}
}
impl Default for G8lS550PanelInitState {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS550PanelInitOutcome {
Published(G8lS550PanelInitReceipt),
Retained(G8lS550PanelInitReceipt),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS550PanelInitError {
WrongPredecessor,
EmptyProgram,
ProgramTooLong,
UnknownDataType,
PayloadTooLong,
ShortPacketLengthMismatch,
EmptyLongPayload,
MalformedPageSelect,
PageOutOfRange,
FirstStepNotPageSelect,
SleepOutOutsidePageZero,
DisplayOnBeforeSleepOut,
InsufficientSleepOutDelay,
DuplicateSleepOut,
PacketAfterDisplayOn,
DelayTooLong,
DelayOverflow,
IncompleteProgram,
RegisterWriteAfterSleepOut,
PublishedStateDrift,
}
impl G8lS550PanelInitError {
pub const fn diagnostic_code(self) -> u64 {
match self {
Self::WrongPredecessor => 1,
Self::EmptyProgram => 2,
Self::ProgramTooLong => 3,
Self::UnknownDataType => 4,
Self::PayloadTooLong => 5,
Self::ShortPacketLengthMismatch => 6,
Self::EmptyLongPayload => 7,
Self::MalformedPageSelect => 8,
Self::PageOutOfRange => 9,
Self::FirstStepNotPageSelect => 10,
Self::SleepOutOutsidePageZero => 11,
Self::DisplayOnBeforeSleepOut => 12,
Self::InsufficientSleepOutDelay => 13,
Self::DuplicateSleepOut => 14,
Self::PacketAfterDisplayOn => 15,
Self::DelayTooLong => 16,
Self::DelayOverflow => 17,
Self::IncompleteProgram => 18,
Self::RegisterWriteAfterSleepOut => 19,
Self::PublishedStateDrift => 20,
}
}
}
pub const fn s550_data_type_is_long(data_type: u8) -> Option<bool> {
match data_type {
S550_DT_DCS_SHORT_WRITE_0 | S550_DT_DCS_SHORT_WRITE_1 => Some(false),
S550_DT_GENERIC_LONG_WRITE | S550_DT_DCS_LONG_WRITE => Some(true),
_ => None,
}
}
/// Encodes one write packet into raw DSI bytes. Short packets are
/// `[DI, D0, D1, ECC]`; long packets are
/// `[DI, WC.lo, WC.hi, ECC, payload..., CRC.lo, CRC.hi]`.
pub fn s550_encode_packet(
data_type: u8,
payload: &[u8],
) -> Result<G8lS550EncodedPacket, G8lS550PanelInitError> {
let long = s550_data_type_is_long(data_type).ok_or(G8lS550PanelInitError::UnknownDataType)?;
if payload.len() > S550_MAX_PAYLOAD_BYTES {
return Err(G8lS550PanelInitError::PayloadTooLong);
}
let data_id = (S550_VIRTUAL_CHANNEL << 6) | (data_type & 0x3F);
let mut packet = G8lS550EncodedPacket {
bytes: [0; S550_MAX_ENCODED_BYTES],
len: 0,
ecc: 0,
crc: None,
};
if !long {
let expected = if data_type == S550_DT_DCS_SHORT_WRITE_0 {
1
} else {
2
};
if payload.len() != expected {
return Err(G8lS550PanelInitError::ShortPacketLengthMismatch);
}
let d1 = if payload.len() == 2 { payload[1] } else { 0 };
let ecc = s550_header_ecc(data_id, payload[0], d1);
packet.bytes[0] = data_id;
packet.bytes[1] = payload[0];
packet.bytes[2] = d1;
packet.bytes[3] = ecc;
packet.len = 4;
packet.ecc = ecc;
return Ok(packet);
}
if payload.is_empty() {
return Err(G8lS550PanelInitError::EmptyLongPayload);
}
let word_count = payload.len() as u16;
let ecc = s550_header_ecc(data_id, word_count as u8, (word_count >> 8) as u8);
let crc = s550_payload_crc16(payload);
packet.bytes[0] = data_id;
packet.bytes[1] = word_count as u8;
packet.bytes[2] = (word_count >> 8) as u8;
packet.bytes[3] = ecc;
packet.bytes[4..4 + payload.len()].copy_from_slice(payload);
packet.bytes[4 + payload.len()] = crc as u8;
packet.bytes[5 + payload.len()] = (crc >> 8) as u8;
packet.len = 6 + payload.len();
packet.ecc = ecc;
packet.crc = Some(crc);
Ok(packet)
}
/// Returns `Some(page)` when the packet is an ILI9881C page select.
pub fn s550_page_select(
data_type: u8,
payload: &[u8],
) -> Result<Option<u8>, G8lS550PanelInitError> {
if payload.first() != Some(&S550_PAGE_SELECT_PREFIX[0]) {
return Ok(None);
}
if data_type != S550_DT_GENERIC_LONG_WRITE
|| payload.len() != 4
|| payload[1] != S550_PAGE_SELECT_PREFIX[1]
|| payload[2] != S550_PAGE_SELECT_PREFIX[2]
{
return Err(G8lS550PanelInitError::MalformedPageSelect);
}
if payload[3] > S550_PAGE_MAX {
return Err(G8lS550PanelInitError::PageOutOfRange);
}
Ok(Some(payload[3]))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Phase {
AwaitingPageSelect,
RegisterWrites,
SleepOutDelay,
Complete,
}
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
fn digest_bytes(mut digest: u64, bytes: &[u8]) -> u64 {
for &byte in bytes {
digest ^= byte as u64;
digest = digest.wrapping_mul(FNV_PRIME);
}
digest
}
/// Validates the ordered phases of an init program and encodes every packet.
/// Every violation fails closed with a distinct error.
pub fn s550_validate_program(
profile: G8lS550PanelProfile,
program: &[G8lS550Step<'_>],
) -> Result<G8lS550PanelInitReceipt, G8lS550PanelInitError> {
if program.is_empty() {
return Err(G8lS550PanelInitError::EmptyProgram);
}
if program.len() > S550_MAX_PROGRAM_STEPS {
return Err(G8lS550PanelInitError::ProgramTooLong);
}
let timing = s550_timing_for(profile);
let mut phase = Phase::AwaitingPageSelect;
let mut current_page: Option<u8> = None;
let mut packet_count = 0usize;
let mut short_packet_count = 0usize;
let mut long_packet_count = 0usize;
let mut page_select_count = 0usize;
let mut register_write_count = 0usize;
let mut total_delay_ms = 0u32;
let mut sleep_out_delay_ms = 0u32;
let mut encoded_bytes = 0usize;
let mut sleep_out_ecc = 0u8;
let mut display_on_ecc = 0u8;
let mut digest = digest_bytes(FNV_OFFSET, &[profile.id(), timing.lanes]);
for step in program {
match *step {
G8lS550Step::DelayMs(ms) => {
if ms > S550_MAX_SINGLE_DELAY_MS {
return Err(G8lS550PanelInitError::DelayTooLong);
}
total_delay_ms = total_delay_ms
.checked_add(ms)
.filter(|&total| total <= S550_MAX_TOTAL_DELAY_MS)
.ok_or(G8lS550PanelInitError::DelayOverflow)?;
if phase == Phase::SleepOutDelay {
sleep_out_delay_ms = sleep_out_delay_ms
.checked_add(ms)
.ok_or(G8lS550PanelInitError::DelayOverflow)?;
}
digest = digest_bytes(
digest,
&[
0xD0,
ms as u8,
(ms >> 8) as u8,
(ms >> 16) as u8,
(ms >> 24) as u8,
],
);
}
G8lS550Step::Write { data_type, payload } => {
if phase == Phase::Complete {
return Err(G8lS550PanelInitError::PacketAfterDisplayOn);
}
let encoded = s550_encode_packet(data_type, payload)?;
let page = s550_page_select(data_type, payload)?;
let long = s550_data_type_is_long(data_type) == Some(true);
let dcs = if data_type == S550_DT_DCS_SHORT_WRITE_0 {
Some(payload[0])
} else {
None
};
match phase {
Phase::AwaitingPageSelect => {
if page.is_none() {
return Err(G8lS550PanelInitError::FirstStepNotPageSelect);
}
phase = Phase::RegisterWrites;
}
Phase::RegisterWrites => {}
Phase::SleepOutDelay => {
if dcs == Some(S550_DCS_SLEEP_OUT) {
return Err(G8lS550PanelInitError::DuplicateSleepOut);
}
if dcs != Some(S550_DCS_DISPLAY_ON) {
return Err(G8lS550PanelInitError::RegisterWriteAfterSleepOut);
}
}
Phase::Complete => unreachable!(),
}
if let Some(p) = page {
current_page = Some(p);
page_select_count += 1;
} else if dcs == Some(S550_DCS_SLEEP_OUT) {
if current_page != Some(0) {
return Err(G8lS550PanelInitError::SleepOutOutsidePageZero);
}
phase = Phase::SleepOutDelay;
sleep_out_ecc = encoded.ecc;
} else if dcs == Some(S550_DCS_DISPLAY_ON) {
if phase != Phase::SleepOutDelay {
return Err(G8lS550PanelInitError::DisplayOnBeforeSleepOut);
}
if sleep_out_delay_ms < S550_MIN_SLEEP_OUT_DELAY_MS
|| sleep_out_delay_ms < timing.sleep_out_delay_ms
{
return Err(G8lS550PanelInitError::InsufficientSleepOutDelay);
}
phase = Phase::Complete;
display_on_ecc = encoded.ecc;
} else {
register_write_count += 1;
}
packet_count += 1;
if long {
long_packet_count += 1;
} else {
short_packet_count += 1;
}
encoded_bytes += encoded.len;
digest = digest_bytes(digest, encoded.as_bytes());
}
}
}
if phase != Phase::Complete {
return Err(G8lS550PanelInitError::IncompleteProgram);
}
Ok(G8lS550PanelInitReceipt {
sequence: S550_SEQUENCE,
predecessor_sequence: S550_EXPECTED_PREDECESSOR,
r1_stage: S550_R1_STAGE,
profile,
width: timing.width,
height: timing.height,
lanes: timing.lanes,
step_count: program.len(),
packet_count,
short_packet_count,
long_packet_count,
page_select_count,
register_write_count,
total_delay_ms,
sleep_out_delay_ms,
encoded_bytes,
sleep_out_ecc,
display_on_ecc,
program_digest: digest,
hardware_present: S550_HARDWARE_PRESENT,
s540_physical_verdict_retained_red: S550_S540_PHYSICAL_VERDICT_RETAINED_RED,
s543_physical_verdict_retained_red: S550_S543_PHYSICAL_VERDICT_RETAINED_RED,
automatic_promotion: S550_AUTOMATIC_PROMOTION,
supported_profile_runtime_observations: S550_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS,
physical_observations: S550_PHYSICAL_OBSERVATIONS,
runbook_executed: RUNBOOK_EXECUTED_IN_S550,
})
}
/// Fail-closed publication of a validated init program. An exact replay
/// returns `Retained` with the same receipt; any divergence after publication
/// fails with `PublishedStateDrift`.
pub fn service_s550_model_panel_init_program(
state: &mut G8lS550PanelInitState,
predecessor_sequence: usize,
profile: G8lS550PanelProfile,
program: &[G8lS550Step<'_>],
) -> Result<G8lS550PanelInitOutcome, G8lS550PanelInitError> {
if predecessor_sequence != S550_EXPECTED_PREDECESSOR {
return Err(G8lS550PanelInitError::WrongPredecessor);
}
let receipt = s550_validate_program(profile, program)?;
if let Some(published) = state.receipt {
if published != receipt {
return Err(G8lS550PanelInitError::PublishedStateDrift);
}
return Ok(G8lS550PanelInitOutcome::Retained(published));
}
state.receipt = Some(receipt);
Ok(G8lS550PanelInitOutcome::Published(receipt))
}
snippet sha256: cdc545f16057…file sha256: cdc545f16057…
02 · Doğrulayan test kodu
Operations komutuna bağlı focused test
tam dosyaL1–L645
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s550_r1_ili9881_panel_dcs_init_sequence_model.rs::S550 r1 ili9881 panel dcs init sequence model focused tests
use aselsan_microkernel_simulation::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s550_r1_ili9881_panel_dcs_init_sequence_model::*;
use std::collections::BTreeSet;
const SOURCE: &str = include_str!(
"../../kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s550_r1_ili9881_panel_dcs_init_sequence_model.rs"
);
const MAIN: &str = include_str!("../../kernel/src/main.rs");
const SIMULATION_LIB: &str = include_str!("../src/lib.rs");
const PAGE0: G8lS550Step<'static> = G8lS550Step::Write {
data_type: 0x29,
payload: &[0xFF, 0x98, 0x81, 0x00],
};
const PAGE3: G8lS550Step<'static> = G8lS550Step::Write {
data_type: 0x29,
payload: &[0xFF, 0x98, 0x81, 0x03],
};
const REG: G8lS550Step<'static> = G8lS550Step::Write {
data_type: 0x15,
payload: &[0x36, 0x00],
};
const SLEEP_OUT: G8lS550Step<'static> = G8lS550Step::Write {
data_type: 0x05,
payload: &[0x11],
};
const DISPLAY_ON: G8lS550Step<'static> = G8lS550Step::Write {
data_type: 0x05,
payload: &[0x29],
};
fn minimal_program() -> Vec<G8lS550Step<'static>> {
vec![
PAGE3,
REG,
PAGE0,
SLEEP_OUT,
G8lS550Step::DelayMs(120),
DISPLAY_ON,
]
}
fn publish(
state: &mut G8lS550PanelInitState,
profile: G8lS550PanelProfile,
program: &[G8lS550Step<'_>],
) -> Result<G8lS550PanelInitOutcome, G8lS550PanelInitError> {
service_s550_model_panel_init_program(state, S550_EXPECTED_PREDECESSOR, profile, program)
}
fn validate(program: &[G8lS550Step<'_>]) -> Result<G8lS550PanelInitReceipt, G8lS550PanelInitError> {
s550_validate_program(G8lS550PanelProfile::TouchDisplay2Inch7, program)
}
#[test]
fn sequence_scope_and_nonpromotion_are_exact() {
assert_eq!(S550_SEQUENCE, 550);
assert_eq!(S550_EXPECTED_PREDECESSOR, 549);
assert_eq!(S550_R1_STAGE, 2);
assert_eq!(S550_R1_RANGE_FIRST, 536);
assert_eq!(S550_R1_RANGE_LAST, 568);
assert_eq!(S550_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS, 0);
assert_eq!(S550_PHYSICAL_OBSERVATIONS, 0);
assert_eq!(S550_PHYSICAL_OR_DEVICE_OPERATIONS, 0);
assert_eq!(S550_SD_WRITES, 0);
assert_eq!(S550_UART_OPENS, 0);
assert_eq!(S550_POWER_TRANSITIONS, 0);
assert_eq!(S550_NEW_IMMUTABLE_RAW_CAPTURES, 0);
assert!(S550_S540_PHYSICAL_VERDICT_RETAINED_RED);
assert!(S550_S543_PHYSICAL_VERDICT_RETAINED_RED);
assert!(!S550_AUTOMATIC_PROMOTION);
assert!(!S550_BOOT_TO_UI_PHYSICALLY_OBSERVED);
assert!(!S550_HARDWARE_PRESENT);
assert!(!S550_R1_ACCEPTANCE_COMPLETE);
assert!(!RUNBOOK_EXECUTED_IN_S550);
}
#[test]
fn module_is_registered_in_kernel_and_simulation() {
let module = "g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s550_r1_ili9881_panel_dcs_init_sequence_model";
// Line-anchored match (equivalent to the regexes `^mod <module>;$` and
// `^pub mod <module>;$`).
assert!(MAIN.lines().any(|line| line == format!("mod {module};")));
assert!(SIMULATION_LIB
.lines()
.any(|line| line == format!("pub mod {module};")));
assert!(SIMULATION_LIB.contains(&format!("#[path = \"../../kernel/src/{module}.rs\"]")));
}
#[test]
fn source_has_no_device_execution_or_uart_emission_surface() {
for forbidden in [
"unsafe",
"asm!",
"write_volatile",
"crate::uart",
"crate::arch",
"#[no_mangle]",
"spin::",
"std::",
"/dev/",
"kprintln!",
] {
assert!(!SOURCE.contains(forbidden), "forbidden token: {forbidden}");
}
assert!(SOURCE.contains("does NOT claim anything physical"));
assert!(SOURCE.contains("not wired into any boot, IRQ, scheduler or driver path"));
}
#[test]
fn diagnostic_codes_are_nonzero_and_unique() {
let errors = [
G8lS550PanelInitError::WrongPredecessor,
G8lS550PanelInitError::EmptyProgram,
G8lS550PanelInitError::ProgramTooLong,
G8lS550PanelInitError::UnknownDataType,
G8lS550PanelInitError::PayloadTooLong,
G8lS550PanelInitError::ShortPacketLengthMismatch,
G8lS550PanelInitError::EmptyLongPayload,
G8lS550PanelInitError::MalformedPageSelect,
G8lS550PanelInitError::PageOutOfRange,
G8lS550PanelInitError::FirstStepNotPageSelect,
G8lS550PanelInitError::SleepOutOutsidePageZero,
G8lS550PanelInitError::DisplayOnBeforeSleepOut,
G8lS550PanelInitError::InsufficientSleepOutDelay,
G8lS550PanelInitError::DuplicateSleepOut,
G8lS550PanelInitError::PacketAfterDisplayOn,
G8lS550PanelInitError::DelayTooLong,
G8lS550PanelInitError::DelayOverflow,
G8lS550PanelInitError::IncompleteProgram,
G8lS550PanelInitError::RegisterWriteAfterSleepOut,
G8lS550PanelInitError::PublishedStateDrift,
];
let codes: BTreeSet<_> = errors
.into_iter()
.map(G8lS550PanelInitError::diagnostic_code)
.collect();
assert_eq!(codes.len(), errors.len());
assert_eq!(codes.len(), 20);
assert!(!codes.contains(&0));
}
#[test]
fn exact_replay_retains_the_same_receipt() {
let mut state = G8lS550PanelInitState::new();
let profile = G8lS550PanelProfile::TouchDisplay2Inch7;
let program = s550_canonical_program(profile);
let G8lS550PanelInitOutcome::Published(receipt) =
publish(&mut state, profile, program).unwrap()
else {
panic!("first S550 publication missing")
};
assert_eq!(state.receipt(), Some(receipt));
assert_eq!(
publish(&mut state, profile, program),
Ok(G8lS550PanelInitOutcome::Retained(receipt))
);
assert_eq!(
publish(&mut state, profile, program),
Ok(G8lS550PanelInitOutcome::Retained(receipt))
);
}
#[test]
fn divergent_input_after_publication_fails_closed() {
let mut state = G8lS550PanelInitState::new();
let program = s550_canonical_program(G8lS550PanelProfile::TouchDisplay2Inch7);
let G8lS550PanelInitOutcome::Published(receipt) =
publish(&mut state, G8lS550PanelProfile::TouchDisplay2Inch7, program).unwrap()
else {
panic!("first S550 publication missing")
};
// Other profile with a valid program: drift.
assert_eq!(
publish(
&mut state,
G8lS550PanelProfile::TouchDisplay2Inch5,
s550_canonical_program(G8lS550PanelProfile::TouchDisplay2Inch5),
),
Err(G8lS550PanelInitError::PublishedStateDrift)
);
// Same profile, one register value changed: drift.
let mut mutated = program.to_vec();
mutated[1] = G8lS550Step::Write { data_type: 0x15, payload: &[0x01, 0x01] };
assert_eq!(
publish(&mut state, G8lS550PanelProfile::TouchDisplay2Inch7, &mutated),
Err(G8lS550PanelInitError::PublishedStateDrift)
);
// Same profile, longer post sleep-out delay: drift.
let mut longer = program.to_vec();
longer[program.len() - 3] = G8lS550Step::DelayMs(150);
assert_eq!(
publish(&mut state, G8lS550PanelProfile::TouchDisplay2Inch7, &longer),
Err(G8lS550PanelInitError::PublishedStateDrift)
);
// Invalid programs still fail with their own error, not drift.
assert_eq!(
publish(&mut state, G8lS550PanelProfile::TouchDisplay2Inch7, &[]),
Err(G8lS550PanelInitError::EmptyProgram)
);
assert_eq!(state.receipt(), Some(receipt));
}
#[test]
fn wrong_predecessor_is_rejected_before_validation() {
let mut state = G8lS550PanelInitState::new();
let program = s550_canonical_program(G8lS550PanelProfile::TouchDisplay2Inch7);
for predecessor in [0, 548, 550, 551] {
assert_eq!(
service_s550_model_panel_init_program(
&mut state,
predecessor,
G8lS550PanelProfile::TouchDisplay2Inch7,
program,
),
Err(G8lS550PanelInitError::WrongPredecessor)
);
}
assert_eq!(
service_s550_model_panel_init_program(
&mut state,
548,
G8lS550PanelProfile::TouchDisplay2Inch7,
&[],
),
Err(G8lS550PanelInitError::WrongPredecessor)
);
assert_eq!(state.receipt(), None);
}
#[test]
fn header_ecc_matches_pinned_vectors() {
// (DI, D0, D1) -> ECC for the MIPI DSI 24-bit header Hamming code.
for ((di, d0, d1), ecc) in [
((0x00, 0x00, 0x00), 0x00),
((0x05, 0x11, 0x00), 0x36), // DCS sleep-out short packet
((0x05, 0x29, 0x00), 0x1C), // DCS display-on short packet
((0x05, 0x10, 0x00), 0x2C), // DCS sleep-in
((0x15, 0x36, 0x00), 0x29), // MADCTL = 0
((0x15, 0x53, 0x24), 0x08),
((0x29, 0x04, 0x00), 0x3F), // generic long, WC = 4 (page select)
((0x29, 0x03, 0x00), 0x1A),
((0x39, 0x05, 0x00), 0x36), // DCS long, WC = 5
((0xFF, 0xFF, 0xFF), 0x3C),
] {
assert_eq!(s550_header_ecc(di, d0, d1), ecc, "ecc({di:#04x},{d0:#04x},{d1:#04x})");
assert!(s550_header_ecc(di, d0, d1) < 0x40);
}
}
#[test]
fn header_ecc_is_a_single_error_correcting_hamming_code() {
// Linear code: the syndrome of a single flipped header bit is the XOR of
// the two ECC values. All 24 syndromes must be distinct, nonzero and of
// odd weight >= 3 so that single-bit errors are correctable and
// distinguishable from a flipped parity bit.
let base = (0x39u8, 0x21u8, 0x84u8);
let ecc0 = s550_header_ecc(base.0, base.1, base.2);
let mut syndromes = BTreeSet::new();
for bit in 0..24u32 {
let flipped = ((base.0 as u32) | ((base.1 as u32) << 8) | ((base.2 as u32) << 16)) ^ (1 << bit);
let ecc = s550_header_ecc(flipped as u8, (flipped >> 8) as u8, (flipped >> 16) as u8);
let syndrome = ecc ^ ecc0;
assert!(syndrome != 0);
assert!(syndrome.count_ones() >= 3 && syndrome.count_ones() % 2 == 1);
syndromes.insert(syndrome);
}
assert_eq!(syndromes.len(), 24);
}
#[test]
fn payload_crc16_matches_pinned_vectors() {
assert_eq!(S550_CRC16_POLY_REFLECTED, 0x8408);
assert_eq!(S550_CRC16_INIT, 0xFFFF);
assert_eq!(s550_payload_crc16(&[]), 0xFFFF);
// MIPI CSI-2/DSI specification checksum example (24-byte payload).
let spec_example: [u8; 24] = [
0xFF, 0x00, 0x00, 0x00, 0x1E, 0xF0, 0x1E, 0xC7, 0x4F, 0x82, 0x78, 0xC5, 0x82, 0xE0,
0x8C, 0x70, 0xD2, 0x3C, 0x78, 0xE9, 0xFF, 0x00, 0x00, 0x01,
];
assert_eq!(s550_payload_crc16(&spec_example), 0xE569);
assert_eq!(s550_payload_crc16(&[0xFF, 0x98, 0x81, 0x03]), 0x2EC7);
assert_eq!(s550_payload_crc16(&[0xFF, 0x98, 0x81, 0x00]), 0x1C5C);
assert_eq!(s550_payload_crc16(&[0x11]), 0x0E8F);
assert_eq!(s550_payload_crc16(&[0x29, 0x05, 0x01]), 0xC927);
// Single-byte change changes the CRC.
assert_ne!(
s550_payload_crc16(&[0xFF, 0x98, 0x81, 0x03]),
s550_payload_crc16(&[0xFF, 0x98, 0x81, 0x04])
);
}
#[test]
fn short_packets_encode_to_four_bytes_with_ecc() {
let sleep_out = s550_encode_packet(0x05, &[0x11]).unwrap();
assert_eq!(sleep_out.as_bytes(), &[0x05, 0x11, 0x00, 0x36]);
assert_eq!(sleep_out.ecc, 0x36);
assert_eq!(sleep_out.crc, None);
let display_on = s550_encode_packet(0x05, &[0x29]).unwrap();
assert_eq!(display_on.as_bytes(), &[0x05, 0x29, 0x00, 0x1C]);
let madctl = s550_encode_packet(0x15, &[0x36, 0x00]).unwrap();
assert_eq!(madctl.as_bytes(), &[0x15, 0x36, 0x00, 0x29]);
let two = s550_encode_packet(0x15, &[0x53, 0x24]).unwrap();
assert_eq!(two.as_bytes(), &[0x15, 0x53, 0x24, 0x08]);
assert_eq!(two.len, 4);
}
#[test]
fn long_packets_encode_word_count_ecc_payload_and_crc() {
let page3 = s550_encode_packet(0x29, &[0xFF, 0x98, 0x81, 0x03]).unwrap();
assert_eq!(
page3.as_bytes(),
&[0x29, 0x04, 0x00, 0x3F, 0xFF, 0x98, 0x81, 0x03, 0xC7, 0x2E]
);
assert_eq!(page3.ecc, 0x3F);
assert_eq!(page3.crc, Some(0x2EC7));
assert_eq!(page3.len, 10);
let dcs_long = s550_encode_packet(0x39, &[0xA0, 0x00, 0x1D, 0x2A, 0x13]).unwrap();
assert_eq!(dcs_long.len, 11);
assert_eq!(&dcs_long.as_bytes()[..4], &[0x39, 0x05, 0x00, 0x36]);
assert_eq!(&dcs_long.as_bytes()[4..9], &[0xA0, 0x00, 0x1D, 0x2A, 0x13]);
let crc = s550_payload_crc16(&[0xA0, 0x00, 0x1D, 0x2A, 0x13]);
assert_eq!(&dcs_long.as_bytes()[9..], &[crc as u8, (crc >> 8) as u8]);
// Maximum payload is encodable and fills the bounded buffer exactly.
let max = [0x5Au8; S550_MAX_PAYLOAD_BYTES];
let encoded = s550_encode_packet(0x39, &max).unwrap();
assert_eq!(encoded.len, S550_MAX_ENCODED_BYTES);
assert_eq!(encoded.bytes[1], S550_MAX_PAYLOAD_BYTES as u8);
assert_eq!(encoded.bytes[2], 0);
}
#[test]
fn unknown_data_types_and_oversized_payloads_are_rejected() {
for data_type in [0x00, 0x01, 0x03, 0x06, 0x13, 0x14, 0x23, 0x28, 0x32, 0x3E, 0x49, 0x7F, 0xFF] {
assert_eq!(
s550_encode_packet(data_type, &[0x00, 0x00]),
Err(G8lS550PanelInitError::UnknownDataType),
"data type {data_type:#04x}"
);
assert_eq!(s550_data_type_is_long(data_type), None);
}
let oversized = [0u8; S550_MAX_PAYLOAD_BYTES + 1];
assert_eq!(
s550_encode_packet(0x39, &oversized),
Err(G8lS550PanelInitError::PayloadTooLong)
);
assert_eq!(
s550_encode_packet(0x29, &oversized),
Err(G8lS550PanelInitError::PayloadTooLong)
);
// Unknown data types are also rejected inside a program.
let program = [PAGE3, G8lS550Step::Write { data_type: 0x23, payload: &[0x01, 0x02] }];
assert_eq!(validate(&program), Err(G8lS550PanelInitError::UnknownDataType));
}
#[test]
fn short_packet_length_mismatch_and_empty_long_payload_fail() {
assert_eq!(
s550_encode_packet(0x05, &[]),
Err(G8lS550PanelInitError::ShortPacketLengthMismatch)
);
assert_eq!(
s550_encode_packet(0x05, &[0x11, 0x00]),
Err(G8lS550PanelInitError::ShortPacketLengthMismatch)
);
assert_eq!(
s550_encode_packet(0x15, &[0x36]),
Err(G8lS550PanelInitError::ShortPacketLengthMismatch)
);
assert_eq!(
s550_encode_packet(0x15, &[0x36, 0x00, 0x01]),
Err(G8lS550PanelInitError::ShortPacketLengthMismatch)
);
assert_eq!(
s550_encode_packet(0x29, &[]),
Err(G8lS550PanelInitError::EmptyLongPayload)
);
assert_eq!(
s550_encode_packet(0x39, &[]),
Err(G8lS550PanelInitError::EmptyLongPayload)
);
}
#[test]
fn canonical_profiles_validate_and_publish_distinct_receipts() {
let mut receipts = Vec::new();
for profile in [
G8lS550PanelProfile::TouchDisplay2Inch5,
G8lS550PanelProfile::TouchDisplay2Inch7,
] {
let program = s550_canonical_program(profile);
let mut state = G8lS550PanelInitState::new();
let G8lS550PanelInitOutcome::Published(receipt) =
publish(&mut state, profile, program).unwrap()
else {
panic!("canonical program must publish")
};
assert_eq!(receipt.sequence, 550);
assert_eq!(receipt.predecessor_sequence, 549);
assert_eq!(receipt.r1_stage, 2);
assert_eq!(receipt.profile, profile);
assert_eq!((receipt.width, receipt.height, receipt.lanes), (720, 1280, 2));
assert_eq!(receipt.step_count, program.len());
assert_eq!(receipt.packet_count, program.len() - 2);
assert_eq!(
receipt.short_packet_count + receipt.long_packet_count,
receipt.packet_count
);
assert_eq!(receipt.page_select_count, 4);
assert_eq!(
receipt.register_write_count,
receipt.packet_count - receipt.page_select_count - 2
);
assert_eq!(receipt.total_delay_ms, 140);
assert_eq!(receipt.sleep_out_delay_ms, 120);
assert_eq!(receipt.sleep_out_ecc, 0x36);
assert_eq!(receipt.display_on_ecc, 0x1C);
// 4 page selects (10 B) + 1 DCS long (11 B) + short packets (4 B).
assert_eq!(
receipt.encoded_bytes,
4 * 10 + 11 + 4 * receipt.short_packet_count
);
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.runbook_executed);
receipts.push(receipt);
}
assert_ne!(receipts[0], receipts[1]);
assert_ne!(receipts[0].program_digest, receipts[1].program_digest);
assert_eq!(receipts[0].step_count, 27);
assert_eq!(receipts[1].step_count, 33);
}
#[test]
fn phase_ordering_violations_fail_closed() {
assert_eq!(validate(&[]), Err(G8lS550PanelInitError::EmptyProgram));
assert_eq!(
validate(&[REG, PAGE0, SLEEP_OUT, G8lS550Step::DelayMs(120), DISPLAY_ON]),
Err(G8lS550PanelInitError::FirstStepNotPageSelect)
);
assert_eq!(
validate(&[G8lS550Step::DelayMs(10), PAGE0]),
Err(G8lS550PanelInitError::IncompleteProgram)
);
assert_eq!(
validate(&[PAGE0, REG, DISPLAY_ON]),
Err(G8lS550PanelInitError::DisplayOnBeforeSleepOut)
);
assert_eq!(
validate(&[PAGE3, REG, SLEEP_OUT, G8lS550Step::DelayMs(120), DISPLAY_ON]),
Err(G8lS550PanelInitError::SleepOutOutsidePageZero)
);
assert_eq!(
validate(&[PAGE0, SLEEP_OUT, G8lS550Step::DelayMs(120), REG, DISPLAY_ON]),
Err(G8lS550PanelInitError::RegisterWriteAfterSleepOut)
);
assert_eq!(
validate(&[PAGE0, SLEEP_OUT, G8lS550Step::DelayMs(120), PAGE3, DISPLAY_ON]),
Err(G8lS550PanelInitError::RegisterWriteAfterSleepOut)
);
assert_eq!(
validate(&[PAGE0, SLEEP_OUT, G8lS550Step::DelayMs(120), SLEEP_OUT, DISPLAY_ON]),
Err(G8lS550PanelInitError::DuplicateSleepOut)
);
assert_eq!(
validate(&[PAGE0, SLEEP_OUT, G8lS550Step::DelayMs(120), DISPLAY_ON, REG]),
Err(G8lS550PanelInitError::PacketAfterDisplayOn)
);
assert_eq!(
validate(&[PAGE0, SLEEP_OUT, G8lS550Step::DelayMs(120), DISPLAY_ON, DISPLAY_ON]),
Err(G8lS550PanelInitError::PacketAfterDisplayOn)
);
assert_eq!(
validate(&[PAGE0, SLEEP_OUT, G8lS550Step::DelayMs(120)]),
Err(G8lS550PanelInitError::IncompleteProgram)
);
// A trailing delay after display-on is allowed; a trailing packet is not.
assert!(validate(&[PAGE0, SLEEP_OUT, G8lS550Step::DelayMs(120), DISPLAY_ON, G8lS550Step::DelayMs(20)]).is_ok());
assert!(validate(&minimal_program()).is_ok());
}
#[test]
fn sleep_out_delay_below_120ms_is_rejected_and_accumulates() {
for delay in [0, 1, 50, 100, 119] {
assert_eq!(
validate(&[PAGE0, SLEEP_OUT, G8lS550Step::DelayMs(delay), DISPLAY_ON]),
Err(G8lS550PanelInitError::InsufficientSleepOutDelay),
"delay {delay}"
);
}
assert_eq!(
validate(&[PAGE0, SLEEP_OUT, DISPLAY_ON]),
Err(G8lS550PanelInitError::InsufficientSleepOutDelay)
);
let split = validate(&[
PAGE0,
SLEEP_OUT,
G8lS550Step::DelayMs(60),
G8lS550Step::DelayMs(60),
DISPLAY_ON,
])
.unwrap();
assert_eq!(split.sleep_out_delay_ms, 120);
assert_eq!(split.total_delay_ms, 120);
// Delay before sleep-out does not count towards the sleep-out wait.
assert_eq!(
validate(&[
PAGE0,
G8lS550Step::DelayMs(200),
SLEEP_OUT,
G8lS550Step::DelayMs(100),
DISPLAY_ON,
]),
Err(G8lS550PanelInitError::InsufficientSleepOutDelay)
);
let exact = validate(&[PAGE0, SLEEP_OUT, G8lS550Step::DelayMs(120), DISPLAY_ON]).unwrap();
assert_eq!(exact.sleep_out_delay_ms, S550_MIN_SLEEP_OUT_DELAY_MS);
}
#[test]
fn delay_bounds_and_overflow_fail_closed() {
assert_eq!(
validate(&[PAGE0, SLEEP_OUT, G8lS550Step::DelayMs(1_001), DISPLAY_ON]),
Err(G8lS550PanelInitError::DelayTooLong)
);
assert_eq!(
validate(&[PAGE0, SLEEP_OUT, G8lS550Step::DelayMs(u32::MAX), DISPLAY_ON]),
Err(G8lS550PanelInitError::DelayTooLong)
);
let mut program = vec![PAGE0];
for _ in 0..5 {
program.push(G8lS550Step::DelayMs(1_000));
}
program.extend([SLEEP_OUT, G8lS550Step::DelayMs(120), DISPLAY_ON]);
assert_eq!(validate(&program), Err(G8lS550PanelInitError::DelayOverflow));
let mut ok = vec![PAGE0];
for _ in 0..4 {
ok.push(G8lS550Step::DelayMs(1_000));
}
ok.extend([SLEEP_OUT, G8lS550Step::DelayMs(1_000), DISPLAY_ON]);
assert_eq!(validate(&ok).unwrap().total_delay_ms, S550_MAX_TOTAL_DELAY_MS);
// Step-count bound.
let mut too_long = vec![PAGE0; S550_MAX_PROGRAM_STEPS + 1];
too_long.push(SLEEP_OUT);
assert_eq!(validate(&too_long), Err(G8lS550PanelInitError::ProgramTooLong));
let mut at_bound = vec![PAGE0; S550_MAX_PROGRAM_STEPS - 3];
at_bound.extend([SLEEP_OUT, G8lS550Step::DelayMs(120), DISPLAY_ON]);
assert_eq!(at_bound.len(), S550_MAX_PROGRAM_STEPS);
assert_eq!(
validate(&at_bound).unwrap().page_select_count,
S550_MAX_PROGRAM_STEPS - 3
);
}
#[test]
fn page_select_malformed_and_out_of_range_fail() {
assert_eq!(s550_page_select(0x29, &[0xFF, 0x98, 0x81, 0x03]), Ok(Some(3)));
assert_eq!(s550_page_select(0x15, &[0x36, 0x00]), Ok(None));
assert_eq!(
s550_page_select(0x39, &[0xFF, 0x98, 0x81, 0x03]),
Err(G8lS550PanelInitError::MalformedPageSelect)
);
assert_eq!(
s550_page_select(0x29, &[0xFF, 0x98, 0x81]),
Err(G8lS550PanelInitError::MalformedPageSelect)
);
assert_eq!(
s550_page_select(0x29, &[0xFF, 0x98, 0x80, 0x03]),
Err(G8lS550PanelInitError::MalformedPageSelect)
);
assert_eq!(
s550_page_select(0x29, &[0xFF, 0x99, 0x81, 0x03]),
Err(G8lS550PanelInitError::MalformedPageSelect)
);
assert_eq!(
s550_page_select(0x29, &[0xFF, 0x98, 0x81, 0x05]),
Err(G8lS550PanelInitError::PageOutOfRange)
);
assert_eq!(
s550_page_select(0x29, &[0xFF, 0x98, 0x81, 0xFF]),
Err(G8lS550PanelInitError::PageOutOfRange)
);
let bad_page = [G8lS550Step::Write { data_type: 0x29, payload: &[0xFF, 0x98, 0x81, 0x09] }];
assert_eq!(validate(&bad_page), Err(G8lS550PanelInitError::PageOutOfRange));
let short_ff = [G8lS550Step::Write { data_type: 0x15, payload: &[0xFF, 0x03] }];
assert_eq!(validate(&short_ff), Err(G8lS550PanelInitError::MalformedPageSelect));
}
#[test]
fn timing_tables_are_720x1280_two_lane_and_refresh_is_checked() {
for profile in [
G8lS550PanelProfile::TouchDisplay2Inch5,
G8lS550PanelProfile::TouchDisplay2Inch7,
] {
let timing = s550_timing_for(profile);
assert_eq!((timing.width, timing.height, timing.lanes), (720, 1280, 2));
assert_eq!(timing.sleep_out_delay_ms, 120);
assert_eq!(timing.display_on_delay_ms, 20);
let refresh = timing.refresh_mhz().unwrap();
assert!((50_000..=70_000).contains(&refresh), "refresh {refresh} mHz");
}
assert_eq!(S550_TIMING_INCH7.htotal(), 1042);
assert_eq!(S550_TIMING_INCH7.vtotal(), 1332);
assert_eq!(S550_TIMING_INCH5.htotal(), 900);
assert_eq!(S550_TIMING_INCH5.vtotal(), 1316);
assert_ne!(S550_TIMING_INCH5, S550_TIMING_INCH7);
let overflow = G8lS550PanelTiming {
width: 1,
height: 1,
hfront_porch: 0,
hsync: 0,
hback_porch: 0,
vfront_porch: 0,
vsync: 0,
vback_porch: 0,
pixel_clock_khz: u32::MAX,
..S550_TIMING_INCH7
};
assert_eq!(overflow.refresh_mhz(), None);
let zero = G8lS550PanelTiming {
width: 0,
hfront_porch: 0,
hsync: 0,
hback_porch: 0,
..S550_TIMING_INCH7
};
assert_eq!(zero.refresh_mhz(), None);
}
#[test]
fn source_only_gate_keeps_runtime_physical_and_r1_claims_zero() {
assert!(SOURCE.contains("S550_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0"));
assert!(SOURCE.contains("S550_PHYSICAL_OBSERVATIONS: usize = 0"));
assert!(SOURCE.contains("S550_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0"));
assert!(SOURCE.contains("S550_HARDWARE_PRESENT: bool = false"));
assert!(SOURCE.contains("S550_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false"));
assert!(SOURCE.contains("S550_R1_ACCEPTANCE_COMPLETE: bool = false"));
assert!(SOURCE.contains("RUNBOOK_EXECUTED_IN_S550: bool = false"));
assert!(SOURCE.contains("S550_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true"));
assert!(SOURCE.contains("S550_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true"));
}
snippet sha256: 8a49e5bdc6e1…file sha256: 8a49e5bdc6e1…
03 · Kapı kimlik kaydı
Operations sıra, kimlik ve başlık bağı
tam Operations kaydıL2902–L2961
website/src/lib/operations.ts::g8l-s550-r1-ili9881-panel-dcs-init-sequence-model
{
id: "g8l-s550-r1-ili9881-panel-dcs-init-sequence-model",
date: "2026-08-30",
sequence: 550,
status: "passed",
umbrella_status: "partial",
title: "S550 · R1 ekran: ILI9881 panel DCS init dizisi modeli",
summary:
"S550 kaynak/host model kapısı PASS'tir: Raspberry Pi Touch Display 2'nin 5 inç ve 7 inç ILI9881C profilleri için (ikisi de 720x1280, 2 lane, ayrı timing ve init tabloları) DCS/generic paket ve gecikme adımlarından oluşan init programı, 24-bit başlık üzerinde 6-bit MIPI DSI Hamming ECC ve long paketler için reflected CRC-16 (0x8408, init 0xFFFF) üreten ham DSI byte encoder'ı ve page select → register write → sleep-out (0x11) → ≥120 ms → display-on (0x29) faz sırasını, 32 B payload sınırını ve 0x05/0x15/0x29/0x39 dışındaki data type'ları fail-closed reddeden validator modellenmiştir. Focused 21/21 PASS'tir; ECC/CRC değerleri bilinen vektörlerle pinlenmiştir. Hiçbir panel, DSI host, dokunmatik, board, UART, SD veya güç işlemi yoktur; S540 ve S543 fiziksel RED immutable kalır, physical observation=0, RUNBOOK_EXECUTED_IN_S550=NO, Boot-to-UI=false ve R1 acceptance=false'dur. S551 R1 2. aşamanın bir sonraki host-only ekran/dokunma/UI model kapısıdır.",
evidence: [
"S550, S549'dan ayrı kernel model modülü, 21-test focused binary, proof, status bloğu, Operations kaydı ve complete Code kartına sahiptir; modül kernel main.rs ve simulation lib.rs'te kayıtlıdır fakat hiçbir boot, IRQ, scheduler veya sürücü yoluna bağlanmamıştır.",
"Dar S550 source/host model status=PASS; R1 umbrella=PARTIAL, S540 ve S543 physical gate status=RED olarak ayrı tutulur.",
"Model iki panel profili taşır: TouchDisplay2Inch5 (hfp/hs/hbp 80/20/80, vfp/vs/vbp 12/4/20, 71000 kHz, htotal 900, vtotal 1316) ve TouchDisplay2Inch7 (239/33/50, 20/2/30, 83330 kHz, htotal 1042, vtotal 1332); ikisi de 720x1280 ve 2 lane'dir. Timing değerleri host tablosudur, vendor doğrulaması veya panel gözlemi yoktur.",
"Kabul edilen DSI data type kümesi kapalıdır: 0x05 DCS short (0 parametre), 0x15 DCS short (1 parametre), 0x29 generic long ve 0x39 DCS long; diğer tüm data type'lar UnknownDataType ile reddedilir.",
"Encoder short paketleri [DI, D0, D1, ECC], long paketleri [DI, WC.lo, WC.hi, ECC, payload, CRC.lo, CRC.hi] olarak üretir; payload sınırı 32 B, kodlanmış paket sınırı 38 B'dir.",
"6-bit ECC MIPI DSI Hamming kodudur; pinlenen vektörler ECC(05 11 00)=0x36, ECC(05 29 00)=0x1C, ECC(29 04 00)=0x3F, ECC(15 36 00)=0x29, ECC(39 05 00)=0x36, ECC(FF FF FF)=0x3C'dir ve 24 tek-bit sendromunun ayrık, sıfırdan farklı ve tek ağırlıklı (≥3) olduğu test edilir.",
"CRC-16 reflected polinom 0x8408, init 0xFFFF, final XOR yoktur; boş payload 0xFFFF, MIPI spesifikasyonu 24-byte checksum örneği 0xE569, FF 98 81 03 için 0x2EC7 ve FF 98 81 00 için 0x1C5C pinlenmiştir; page-3 select 29 04 00 3F FF 98 81 03 C7 2E olarak kodlanır.",
"Validator faz sırasını zorlar: ilk adım FF 98 81 <page> page select (page 0..=4), register write'lar, page 0 üzerinde sleep-out 0x11, birikimli ≥120 ms gecikme, display-on 0x29 ve yalnız kuyruk gecikmesi; sleep-out sonrası register write, çift sleep-out, display-on sonrası paket ve eksik program reddedilir.",
"Sınırlar checked aritmetikle korunur: program ≤128 adım, tek gecikme ≤1000 ms, toplam gecikme ≤5000 ms; aşımlar DelayTooLong/DelayOverflow/ProgramTooLong ile fail-closed döner.",
"20 hata kodu sıfırdan farklı ve benzersizdir; yanlış predecessor (≠549) doğrulamadan önce reddedilir.",
"Canonical 5 inç programı 27 adım / 25 paket, 7 inç programı 33 adım / 31 paket üretir; ikisi de 4 page select, 120 ms sleep-out beklemesi ve 140 ms toplam gecikme taşır ve farklı FNV-1a program digest'i verir.",
"service_s550_model_panel_init_program exact replay'de aynı receipt ile Retained döner; publish sonrası farklı profil, register değeri veya gecikme PublishedStateDrift ile reddedilir.",
"Focused target 1 grup / 21 passed / 0 failed / 0 ignored / 0 filtered verdi.",
"Implementation 29042 B / cdc545f16057b821e86b6df183a6773a6052e6ba2dd6d5b002675f5bfc938169; focused test 24916 B / 8a49e5bdc6e190c2aa75ce475858783a933e11b2fbf721e722702cc6881b0692 SHA-256'dır.",
"Proof 5688 B'dir.",
"Register tabloları temsili model alt kümesidir; vendor'un tam init dökümü değildir ve hiçbir panel üzerinde doğrulanmamıştır.",
"S550 sırasında panel, DSI host, RP1, dokunmatik, board, SD write/read-back/eject, UART open/capture, power transition, physical retry veya yeni immutable raw üretimi yapılmadı; S540 ve S543 raw/verdict byte-exact RED korunur, automatic promotion=false ve S546 kararı varsayılmaz.",
"RUNBOOK_EXECUTED_IN_S550=NO; supported-profile runtime observations=0, physical observations=0, hardware present=false, production callsite wired=false, Boot-to-UI physically observed=false ve R1 acceptance=false'dur.",
"S551 R1 2. aşamada bir sonraki host-only ekran/dokunma/UI model kapısıdır; panel, DSI, SD, UART, güç 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_s550_r1_ili9881_panel_dcs_init_sequence_model -- --test-threads=1",
],
terminalSessions: [
{
id: "s550-focused",
title: "S550 ILI9881 panel DCS init dizisi modeli focused acceptance",
commandLines: [
"CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s550_r1_ili9881_panel_dcs_init_sequence_model -- --test-threads=1",
],
outputLines: [
"test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s",
"S550 focused=1 group / 21 passed / 0 failed",
"hardware=none physical=0 runbook=NO",
],
exitCode: 0,
outputMode: "complete",
},
],
terminalSessionsNote:
"S550 kaynak/host model PASS'tir; supported-profile runtime veya fiziksel PASS değildir. S540 ve S543 RED raw ve kararları değişmez.",
limitations: [
"S550 yalnız host üzerinde derlenen ve focused testle sürülen bir modeldir; hiçbir donanım/panel/modem/board gözlemi yoktur.",
"Timing tabloları ve register alt kümesi vendor doğrulaması taşımaz; gerçek ILI9881C init dökümüyle byte-exact eşleşme iddiası yoktur.",
"Modül hiçbir boot, IRQ, scheduler veya sürücü yoluna bağlanmamıştır; production callsite yoktur.",
"S540 ve S543 fiziksel RED immutable kalır; S546 üçüncü fiziksel koşunun kararı bu kapıda varsayılmaz ve otomatik yükseltme yoktur.",
"BOOT_TO_UI_READY gerçek UART'ta görülmedi; Boot-to-UI ve R1 acceptance false kalır.",
"S551 R1 2. aşamanın bir sonraki host-only model kapısıdır; yeni SD/UART/power/panel koşusu ayrı kapı, fresh target revalidation, açık operatör yetkisi ve yeni immutable raw ister.",
],
},snippet sha256: 2a90e5f04bdc…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_s550_r1_ili9881_panel_dcs_init_sequence_model -- --test-threads=1proof: docs/M8.1-RPi5-G8l-S550-R1-ILI9881-Panel-DCS-Init-Sequence-Model-Proof.md
Registry schema v5 · generator
website/scripts/generate-code-gates.mjs · Tam SHA-256: 3050638b71a684d8f8f947a8a6faa237a17fa8db5dc0db04fb207b668b462af9