ASELSANMicrokernel
S549 · SOURCE-BOUND GATE EVIDENCE

S549 · R1 ekran: RP1 DSI host register haritası ve D-PHY zamanlama modeli

tam S549 implementation modülü → Operations --test hedefi ile bağlı tam focused test → ayrı Operations kaydı Bu sayfa yalnız S549 kapısına aittir; komşu kapıların kaynakları bu kabulün içine katılmaz.

S549Focused kod testiOperations id exactsource SHA exacttest target exact

operation: g8l-s549-r1-rp1-dsi-host-register-map-dphy-timing-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–L871
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s549_r1_rp1_dsi_host_register_map_dphy_timing_model.rs::S549 r1 rp1 dsi host register map dphy timing model implementation
#![allow(unexpected_cfgs)]

//! S549 models the RP1 (Raspberry Pi 5 southbridge) MIPI DSI host bring-up as
//! data: a symbolic register map, a checked D-PHY timing calculator, and a
//! bounded register program encoder for two R1 panel profiles.
//!
//! What this gate does NOT claim: no panel, DSI host, D-PHY, board, UART, SD
//! card, power transition or runtime observation exists for S549.  The
//! register offsets, widths, reset values and access classes are model
//! placeholders that must be bound to the RP1 datasheet later; they are NOT
//! hardware-validated.  Nothing here is wired into a boot, IRQ, scheduler or
//! driver path.  The module performs no device operation, emits no UART
//! text, does not rerun S540 or S543, and cannot promote either physical RED.
//!
//! Predecessor: S548 (R1 stage 2).  Next gate: S550.

use alloc::vec::Vec;

pub const S549_SEQUENCE: usize = 549;
pub const S549_EXPECTED_PREDECESSOR: usize = 548;
pub const S549_R1_STAGE: u8 = 2;
pub const S549_R1_RANGE_FIRST: usize = 536;
pub const S549_R1_RANGE_LAST: usize = 568;
pub const S549_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0;
pub const S549_PHYSICAL_OBSERVATIONS: usize = 0;
pub const S549_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0;
pub const S549_SD_WRITES: usize = 0;
pub const S549_UART_OPENS: usize = 0;
pub const S549_POWER_TRANSITIONS: usize = 0;
pub const S549_NEW_IMMUTABLE_RAW_CAPTURES: usize = 0;
pub const S549_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S549_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S549_AUTOMATIC_PROMOTION: bool = false;
pub const S549_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false;
pub const S549_HARDWARE_PRESENT: bool = false;
pub const S549_R1_ACCEPTANCE_COMPLETE: bool = false;
pub const RUNBOOK_EXECUTED_IN_S549: bool = false;

/// Register offsets below are symbolic model placeholders (not RP1-validated).
pub const S549_REGISTER_OFFSETS_HARDWARE_VALIDATED: bool = false;
pub const S549_REGISTER_WIDTH_BITS: u8 = 32;
pub const S549_REGISTER_COUNT: usize = 17;
pub const S549_DPHY_SPEC_RANGE_COUNT: usize = 10;
pub const S549_SUPPORTED_LANE_COUNTS: [u8; 3] = [1, 2, 4];
pub const S549_SUPPORTED_BITS_PER_PIXEL: [u8; 3] = [16, 18, 24];
pub const S549_UI_PER_BYTE_CLOCK: u64 = 8;
pub const S549_MIN_HS_BIT_RATE_BPS: u64 = 80_000_000;
pub const S549_MAX_HS_BIT_RATE_BPS: u64 = 1_500_000_000;
pub const S549_MAX_ACTIVE_PIXELS: u32 = 4096;
pub const S549_MAX_BLANKING_PIXELS: u32 = 1023;
pub const S549_MAX_PROGRAM_WRITES: usize = 16;
pub const S549_PROGRAM_WRITE_COUNT: usize = 13;
pub const S549_PICOSECONDS_PER_SECOND: u64 = 1_000_000_000_000;
pub const S549_NANOSECONDS_PER_SECOND: u64 = 1_000_000_000;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS549RegisterAccess {
    ReadOnly,
    ReadWrite,
    WriteOnly,
    Write1Clear,
}

impl G8lS549RegisterAccess {
    pub const fn is_writable(self) -> bool {
        !matches!(self, Self::ReadOnly)
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS549DsiRegister {
    pub name: &'static str,
    pub offset: u32,
    pub width_bits: u8,
    pub reset_value: u32,
    pub access: G8lS549RegisterAccess,
}

const fn reg(
    name: &'static str,
    offset: u32,
    reset_value: u32,
    access: G8lS549RegisterAccess,
) -> G8lS549DsiRegister {
    G8lS549DsiRegister {
        name,
        offset,
        width_bits: S549_REGISTER_WIDTH_BITS,
        reset_value,
        access,
    }
}

/// Symbolic RP1 DSI host register map.  Offsets are placeholders to be bound
/// to the RP1 datasheet later; they are NOT hardware-validated.
pub const S549_DSI_REGISTER_MAP: [G8lS549DsiRegister; S549_REGISTER_COUNT] = [
    reg("DSI_CTRL", 0x000, 0, G8lS549RegisterAccess::ReadWrite),
    reg("DSI_STATUS", 0x004, 0, G8lS549RegisterAccess::ReadOnly),
    reg(
        "DSI_INT_STATUS",
        0x008,
        0,
        G8lS549RegisterAccess::Write1Clear,
    ),
    reg("DSI_INT_ENABLE", 0x00C, 0, G8lS549RegisterAccess::ReadWrite),
    reg("DSI_HS_CLK", 0x010, 0, G8lS549RegisterAccess::ReadWrite),
    reg("DSI_LANE_CFG", 0x014, 0, G8lS549RegisterAccess::ReadWrite),
    reg(
        "DSI_PHY_TIMING_HS",
        0x018,
        0,
        G8lS549RegisterAccess::ReadWrite,
    ),
    reg(
        "DSI_PHY_TIMING_CLK",
        0x01C,
        0,
        G8lS549RegisterAccess::ReadWrite,
    ),
    reg(
        "DSI_PHY_TIMING_CLK2",
        0x020,
        0,
        G8lS549RegisterAccess::ReadWrite,
    ),
    reg("DSI_PKT_HDR", 0x024, 0, G8lS549RegisterAccess::WriteOnly),
    reg(
        "DSI_PKT_PAYLOAD",
        0x028,
        0,
        G8lS549RegisterAccess::WriteOnly,
    ),
    reg(
        "DSI_VID_TIMING_HACTIVE",
        0x030,
        0,
        G8lS549RegisterAccess::ReadWrite,
    ),
    reg(
        "DSI_VID_TIMING_HBLANK",
        0x034,
        0,
        G8lS549RegisterAccess::ReadWrite,
    ),
    reg(
        "DSI_VID_TIMING_VACTIVE",
        0x038,
        0,
        G8lS549RegisterAccess::ReadWrite,
    ),
    reg(
        "DSI_VID_TIMING_VBLANK",
        0x03C,
        0,
        G8lS549RegisterAccess::ReadWrite,
    ),
    reg(
        "DSI_VID_MODE_CFG",
        0x040,
        0,
        G8lS549RegisterAccess::ReadWrite,
    ),
    reg(
        "DSI_ID",
        0x0FC,
        0x4453_4931,
        G8lS549RegisterAccess::ReadOnly,
    ),
];

pub const S549_CTRL_ENABLE: u32 = 1 << 0;
pub const S549_CTRL_VIDEO_MODE: u32 = 1 << 1;
pub const S549_INT_ENABLE_PANEL_ERROR: u32 = 1 << 0;
pub const S549_VID_MODE_CFG_BURST: u32 = 1 << 0;

/// One MIPI D-PHY global-operation timing range.  Minimum = `min_ns` +
/// `min_ui` unit intervals; maximum (when the spec bounds it) = `max_ns` +
/// `max_ui`.  Composite rows bound the sum of two parameters.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS549DphySpecRange {
    pub name: &'static str,
    pub min_ns: u64,
    pub min_ui: u64,
    pub max: Option<(u64, u64)>,
}

const fn range(
    name: &'static str,
    min_ns: u64,
    min_ui: u64,
    max: Option<(u64, u64)>,
) -> G8lS549DphySpecRange {
    G8lS549DphySpecRange {
        name,
        min_ns,
        min_ui,
        max,
    }
}

pub const S549_DPHY_SPEC_RANGES: [G8lS549DphySpecRange; S549_DPHY_SPEC_RANGE_COUNT] = [
    range("T_LPX", 50, 0, None),
    range("T_HS_PREPARE", 40, 4, Some((85, 6))),
    range("T_HS_PREPARE_PLUS_ZERO", 145, 10, None),
    range("T_HS_TRAIL", 60, 4, None),
    range("T_HS_EXIT", 100, 0, None),
    range("T_CLK_PREPARE", 38, 0, Some((95, 0))),
    range("T_CLK_PREPARE_PLUS_ZERO", 300, 0, None),
    range("T_CLK_PRE", 0, 8, None),
    range("T_CLK_POST", 60, 52, None),
    range("T_CLK_TRAIL", 60, 0, None),
];

const SPEC_LPX: usize = 0;
const SPEC_HS_PREPARE: usize = 1;
const SPEC_HS_PREPARE_PLUS_ZERO: usize = 2;
const SPEC_HS_TRAIL: usize = 3;
const SPEC_HS_EXIT: usize = 4;
const SPEC_CLK_PREPARE: usize = 5;
const SPEC_CLK_PREPARE_PLUS_ZERO: usize = 6;
const SPEC_CLK_PRE: usize = 7;
const SPEC_CLK_POST: usize = 8;
const SPEC_CLK_TRAIL: usize = 9;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS549PanelProfile {
    Phone720x1280Lanes2,
    Tablet1200x1920Lanes4,
}

impl G8lS549PanelProfile {
    pub const fn id(self) -> u8 {
        match self {
            Self::Phone720x1280Lanes2 => 1,
            Self::Tablet1200x1920Lanes4 => 2,
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS549PanelMode {
    pub hactive: u32,
    pub hfp: u32,
    pub hbp: u32,
    pub hsync: u32,
    pub vactive: u32,
    pub vfp: u32,
    pub vbp: u32,
    pub vsync: u32,
    pub refresh_hz: u64,
    pub lanes: u8,
    pub bits_per_pixel: u8,
}

pub const fn canonical_s549_panel_mode(profile: G8lS549PanelProfile) -> G8lS549PanelMode {
    match profile {
        G8lS549PanelProfile::Phone720x1280Lanes2 => G8lS549PanelMode {
            hactive: 720,
            hfp: 40,
            hbp: 40,
            hsync: 10,
            vactive: 1280,
            vfp: 10,
            vbp: 10,
            vsync: 4,
            refresh_hz: 60,
            lanes: 2,
            bits_per_pixel: 24,
        },
        G8lS549PanelProfile::Tablet1200x1920Lanes4 => G8lS549PanelMode {
            hactive: 1200,
            hfp: 60,
            hbp: 60,
            hsync: 20,
            vactive: 1920,
            vfp: 20,
            vbp: 20,
            vsync: 4,
            refresh_hz: 60,
            lanes: 4,
            bits_per_pixel: 24,
        },
    }
}

/// D-PHY timings in unit intervals (UI = one HS bit period).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS549DphyTimingUi {
    pub t_lpx: u32,
    pub t_hs_prepare: u32,
    pub t_hs_zero: u32,
    pub t_hs_trail: u32,
    pub t_hs_exit: u32,
    pub t_clk_prepare: u32,
    pub t_clk_zero: u32,
    pub t_clk_pre: u32,
    pub t_clk_post: u32,
    pub t_clk_trail: u32,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS549DsiHostReceipt {
    pub sequence: usize,
    pub predecessor_sequence: usize,
    pub r1_stage: u8,
    pub profile_id: u8,
    pub mode: G8lS549PanelMode,
    pub htotal: u32,
    pub vtotal: u32,
    pub pixel_clock_hz: u64,
    pub hs_bit_rate_bps: u64,
    pub byte_clock_hz: u64,
    pub ui_ps: u64,
    pub timing: G8lS549DphyTimingUi,
    pub program_write_count: usize,
    pub program_checksum: u64,
    pub register_offsets_hardware_validated: bool,
    pub s540_physical_verdict_retained_red: bool,
    pub s543_physical_verdict_retained_red: bool,
    pub automatic_promotion: bool,
    pub hardware_present: bool,
    pub supported_profile_runtime_observations: usize,
    pub physical_observations: usize,
    pub runbook_executed: bool,
}

#[derive(Debug)]
pub struct G8lS549DsiHostState {
    receipt: Option<G8lS549DsiHostReceipt>,
}

impl G8lS549DsiHostState {
    pub const fn new() -> Self {
        Self { receipt: None }
    }

    pub const fn receipt(&self) -> Option<G8lS549DsiHostReceipt> {
        self.receipt
    }
}

impl Default for G8lS549DsiHostState {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS549DsiHostOutcome {
    Published(G8lS549DsiHostReceipt),
    Retained(G8lS549DsiHostReceipt),
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS549DsiHostError {
    UnsupportedLaneCount,
    UnsupportedBitsPerPixel,
    ZeroDimension,
    ActiveOutOfRange,
    BlankingOutOfRange,
    ArithmeticOverflow,
    HsBitRateNotLaneDivisible,
    HsBitRateBelowMinimum,
    HsBitRateAboveMaximum,
    TimingBelowSpecMinimum,
    TimingAboveSpecMaximum,
    TimingNotByteClockAligned,
    RegisterMapMalformed,
    RegisterUnknown,
    RegisterNotWritable,
    RegisterValueExceedsWidth,
    RegisterFieldOverflow,
    ProgramBufferOverflow,
    PublishedStateDrift,
}

impl G8lS549DsiHostError {
    pub const fn diagnostic_code(self) -> u64 {
        match self {
            Self::UnsupportedLaneCount => 1,
            Self::UnsupportedBitsPerPixel => 2,
            Self::ZeroDimension => 3,
            Self::ActiveOutOfRange => 4,
            Self::BlankingOutOfRange => 5,
            Self::ArithmeticOverflow => 6,
            Self::HsBitRateNotLaneDivisible => 7,
            Self::HsBitRateBelowMinimum => 8,
            Self::HsBitRateAboveMaximum => 9,
            Self::TimingBelowSpecMinimum => 10,
            Self::TimingAboveSpecMaximum => 11,
            Self::TimingNotByteClockAligned => 12,
            Self::RegisterMapMalformed => 13,
            Self::RegisterUnknown => 14,
            Self::RegisterNotWritable => 15,
            Self::RegisterValueExceedsWidth => 16,
            Self::RegisterFieldOverflow => 17,
            Self::ProgramBufferOverflow => 18,
            Self::PublishedStateDrift => 19,
        }
    }
}

type S549Result<T> = Result<T, G8lS549DsiHostError>;

pub fn validate_s549_register_map(map: &[G8lS549DsiRegister]) -> S549Result<()> {
    if map.is_empty() {
        return Err(G8lS549DsiHostError::RegisterMapMalformed);
    }
    for (index, register) in map.iter().enumerate() {
        if register.name.is_empty()
            || register.width_bits != S549_REGISTER_WIDTH_BITS
            || register.offset % 4 != 0
        {
            return Err(G8lS549DsiHostError::RegisterMapMalformed);
        }
        if index > 0 {
            let previous = map[index - 1];
            // Strictly increasing offsets with no overlap of the previous word.
            if register.offset < previous.offset.saturating_add(4) {
                return Err(G8lS549DsiHostError::RegisterMapMalformed);
            }
        }
        if map[..index].iter().any(|other| other.name == register.name) {
            return Err(G8lS549DsiHostError::RegisterMapMalformed);
        }
    }
    Ok(())
}

pub fn lookup_s549_register(
    map: &[G8lS549DsiRegister],
    name: &str,
) -> S549Result<G8lS549DsiRegister> {
    map.iter()
        .copied()
        .find(|register| register.name == name)
        .ok_or(G8lS549DsiHostError::RegisterUnknown)
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS549RegisterWrite {
    pub offset: u32,
    pub value: u32,
}

/// Bounded register program (at most `S549_MAX_PROGRAM_WRITES` writes).
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct G8lS549RegisterProgram {
    writes: Vec<G8lS549RegisterWrite>,
}

impl G8lS549RegisterProgram {
    pub fn new() -> Self {
        Self { writes: Vec::new() }
    }

    pub fn writes(&self) -> &[G8lS549RegisterWrite] {
        &self.writes
    }

    pub fn push(
        &mut self,
        map: &[G8lS549DsiRegister],
        name: &str,
        value: u32,
    ) -> S549Result<G8lS549RegisterWrite> {
        let register = lookup_s549_register(map, name)?;
        if !register.access.is_writable() {
            return Err(G8lS549DsiHostError::RegisterNotWritable);
        }
        if register.width_bits < 32 && value >> register.width_bits != 0 {
            return Err(G8lS549DsiHostError::RegisterValueExceedsWidth);
        }
        if self.writes.len() >= S549_MAX_PROGRAM_WRITES {
            return Err(G8lS549DsiHostError::ProgramBufferOverflow);
        }
        let write = G8lS549RegisterWrite {
            offset: register.offset,
            value,
        };
        self.writes.push(write);
        Ok(write)
    }

    /// FNV-1a over (offset, value) words; wrapping arithmetic is intentional
    /// for the digest and never feeds a timing computation.
    pub fn checksum(&self) -> u64 {
        let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
        for write in &self.writes {
            for word in [write.offset, write.value] {
                for byte in word.to_le_bytes() {
                    hash ^= u64::from(byte);
                    hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
                }
            }
        }
        hash
    }
}

fn checked_mul(a: u64, b: u64) -> S549Result<u64> {
    a.checked_mul(b)
        .ok_or(G8lS549DsiHostError::ArithmeticOverflow)
}

fn checked_add(a: u64, b: u64) -> S549Result<u64> {
    a.checked_add(b)
        .ok_or(G8lS549DsiHostError::ArithmeticOverflow)
}

fn ceil_div(numerator: u64, denominator: u64) -> S549Result<u64> {
    if denominator == 0 {
        return Err(G8lS549DsiHostError::ArithmeticOverflow);
    }
    checked_add(numerator, denominator - 1).map(|sum| sum / denominator)
}

pub fn validate_s549_panel_mode(mode: G8lS549PanelMode) -> S549Result<()> {
    if !S549_SUPPORTED_LANE_COUNTS.contains(&mode.lanes) {
        return Err(G8lS549DsiHostError::UnsupportedLaneCount);
    }
    if !S549_SUPPORTED_BITS_PER_PIXEL.contains(&mode.bits_per_pixel) {
        return Err(G8lS549DsiHostError::UnsupportedBitsPerPixel);
    }
    if mode.hactive == 0 || mode.vactive == 0 || mode.refresh_hz == 0 {
        return Err(G8lS549DsiHostError::ZeroDimension);
    }
    if mode.hactive > S549_MAX_ACTIVE_PIXELS || mode.vactive > S549_MAX_ACTIVE_PIXELS {
        return Err(G8lS549DsiHostError::ActiveOutOfRange);
    }
    for blanking in [
        mode.hfp, mode.hbp, mode.hsync, mode.vfp, mode.vbp, mode.vsync,
    ] {
        if blanking == 0 || blanking > S549_MAX_BLANKING_PIXELS {
            return Err(G8lS549DsiHostError::BlankingOutOfRange);
        }
    }
    Ok(())
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS549LinkClocks {
    pub htotal: u32,
    pub vtotal: u32,
    pub pixel_clock_hz: u64,
    pub hs_bit_rate_bps: u64,
    pub byte_clock_hz: u64,
    pub ui_ps: u64,
}

/// pixel clock = htotal * vtotal * refresh; HS bit rate per lane =
/// pixel clock * bpp / lanes; byte clock = HS bit rate / 8.  All checked.
pub fn compute_s549_link_clocks(mode: G8lS549PanelMode) -> S549Result<G8lS549LinkClocks> {
    validate_s549_panel_mode(mode)?;
    let htotal = mode.hactive + mode.hfp + mode.hbp + mode.hsync;
    let vtotal = mode.vactive + mode.vfp + mode.vbp + mode.vsync;
    let pixel_clock_hz = checked_mul(
        checked_mul(u64::from(htotal), u64::from(vtotal))?,
        mode.refresh_hz,
    )?;
    let link_bits_per_second = checked_mul(pixel_clock_hz, u64::from(mode.bits_per_pixel))?;
    if link_bits_per_second % u64::from(mode.lanes) != 0 {
        return Err(G8lS549DsiHostError::HsBitRateNotLaneDivisible);
    }
    let hs_bit_rate_bps = link_bits_per_second / u64::from(mode.lanes);
    if hs_bit_rate_bps < S549_MIN_HS_BIT_RATE_BPS {
        return Err(G8lS549DsiHostError::HsBitRateBelowMinimum);
    }
    if hs_bit_rate_bps > S549_MAX_HS_BIT_RATE_BPS {
        return Err(G8lS549DsiHostError::HsBitRateAboveMaximum);
    }
    Ok(G8lS549LinkClocks {
        htotal,
        vtotal,
        pixel_clock_hz,
        hs_bit_rate_bps,
        byte_clock_hz: hs_bit_rate_bps / S549_UI_PER_BYTE_CLOCK,
        ui_ps: S549_PICOSECONDS_PER_SECOND / hs_bit_rate_bps,
    })
}

/// Minimum UI count for a spec row: ceil(min_ns * bit_rate / 1e9) + min_ui.
pub fn s549_spec_min_ui(range: G8lS549DphySpecRange, hs_bit_rate_bps: u64) -> S549Result<u64> {
    let ns_part = ceil_div(
        checked_mul(range.min_ns, hs_bit_rate_bps)?,
        S549_NANOSECONDS_PER_SECOND,
    )?;
    checked_add(ns_part, range.min_ui)
}

/// Maximum UI count for a bounded row: floor(max_ns * bit_rate / 1e9) + max_ui.
pub fn s549_spec_max_ui(
    range: G8lS549DphySpecRange,
    hs_bit_rate_bps: u64,
) -> S549Result<Option<u64>> {
    match range.max {
        None => Ok(None),
        Some((max_ns, max_ui)) => {
            let ns_part = checked_mul(max_ns, hs_bit_rate_bps)? / S549_NANOSECONDS_PER_SECOND;
            checked_add(ns_part, max_ui).map(Some)
        }
    }
}

fn round_up_to_byte_clock(ui: u64) -> S549Result<u64> {
    ceil_div(ui, S549_UI_PER_BYTE_CLOCK)
        .and_then(|bytes| checked_mul(bytes, S549_UI_PER_BYTE_CLOCK))
}

fn to_u32(value: u64) -> S549Result<u32> {
    u32::try_from(value).map_err(|_| G8lS549DsiHostError::ArithmeticOverflow)
}

/// Picks the spec minimum rounded up to byte-clock granularity (model
/// policy: the host programs D-PHY timings in byte clocks).  A rounded value
/// above the spec maximum fails closed instead of being silently clamped.
fn derive_single(index: usize, hs_bit_rate_bps: u64, already_ui: u64) -> S549Result<u32> {
    let spec = S549_DPHY_SPEC_RANGES[index];
    let min = s549_spec_min_ui(spec, hs_bit_rate_bps)?;
    let remaining = min.saturating_sub(already_ui);
    let chosen = round_up_to_byte_clock(remaining)?;
    if let Some(max) = s549_spec_max_ui(spec, hs_bit_rate_bps)? {
        if checked_add(chosen, already_ui)? > max {
            return Err(G8lS549DsiHostError::TimingAboveSpecMaximum);
        }
    }
    to_u32(chosen)
}

pub fn derive_s549_dphy_timing(hs_bit_rate_bps: u64) -> S549Result<G8lS549DphyTimingUi> {
    let t_lpx = derive_single(SPEC_LPX, hs_bit_rate_bps, 0)?;
    let t_hs_prepare = derive_single(SPEC_HS_PREPARE, hs_bit_rate_bps, 0)?;
    let t_hs_zero = derive_single(
        SPEC_HS_PREPARE_PLUS_ZERO,
        hs_bit_rate_bps,
        u64::from(t_hs_prepare),
    )?;
    let t_hs_trail = derive_single(SPEC_HS_TRAIL, hs_bit_rate_bps, 0)?;
    let t_hs_exit = derive_single(SPEC_HS_EXIT, hs_bit_rate_bps, 0)?;
    let t_clk_prepare = derive_single(SPEC_CLK_PREPARE, hs_bit_rate_bps, 0)?;
    let t_clk_zero = derive_single(
        SPEC_CLK_PREPARE_PLUS_ZERO,
        hs_bit_rate_bps,
        u64::from(t_clk_prepare),
    )?;
    let t_clk_pre = derive_single(SPEC_CLK_PRE, hs_bit_rate_bps, 0)?;
    let t_clk_post = derive_single(SPEC_CLK_POST, hs_bit_rate_bps, 0)?;
    let t_clk_trail = derive_single(SPEC_CLK_TRAIL, hs_bit_rate_bps, 0)?;
    let timing = G8lS549DphyTimingUi {
        t_lpx,
        t_hs_prepare,
        t_hs_zero,
        t_hs_trail,
        t_hs_exit,
        t_clk_prepare,
        t_clk_zero,
        t_clk_pre,
        t_clk_post,
        t_clk_trail,
    };
    validate_s549_dphy_timing(timing, hs_bit_rate_bps)?;
    Ok(timing)
}

fn check_range(index: usize, value_ui: u64, hs_bit_rate_bps: u64) -> S549Result<()> {
    let spec = S549_DPHY_SPEC_RANGES[index];
    if value_ui < s549_spec_min_ui(spec, hs_bit_rate_bps)? {
        return Err(G8lS549DsiHostError::TimingBelowSpecMinimum);
    }
    if let Some(max) = s549_spec_max_ui(spec, hs_bit_rate_bps)? {
        if value_ui > max {
            return Err(G8lS549DsiHostError::TimingAboveSpecMaximum);
        }
    }
    Ok(())
}

/// Re-checks every single and composite spec row for an arbitrary timing.
pub fn validate_s549_dphy_timing(
    timing: G8lS549DphyTimingUi,
    hs_bit_rate_bps: u64,
) -> S549Result<()> {
    let t = timing;
    let singles = [
        t.t_lpx,
        t.t_hs_prepare,
        t.t_hs_zero,
        t.t_hs_trail,
        t.t_hs_exit,
        t.t_clk_prepare,
        t.t_clk_zero,
        t.t_clk_pre,
        t.t_clk_post,
        t.t_clk_trail,
    ];
    if singles
        .iter()
        .any(|value| u64::from(*value) % S549_UI_PER_BYTE_CLOCK != 0)
    {
        return Err(G8lS549DsiHostError::TimingNotByteClockAligned);
    }
    check_range(SPEC_LPX, u64::from(t.t_lpx), hs_bit_rate_bps)?;
    check_range(SPEC_HS_PREPARE, u64::from(t.t_hs_prepare), hs_bit_rate_bps)?;
    check_range(
        SPEC_HS_PREPARE_PLUS_ZERO,
        checked_add(u64::from(t.t_hs_prepare), u64::from(t.t_hs_zero))?,
        hs_bit_rate_bps,
    )?;
    check_range(SPEC_HS_TRAIL, u64::from(t.t_hs_trail), hs_bit_rate_bps)?;
    check_range(SPEC_HS_EXIT, u64::from(t.t_hs_exit), hs_bit_rate_bps)?;
    check_range(
        SPEC_CLK_PREPARE,
        u64::from(t.t_clk_prepare),
        hs_bit_rate_bps,
    )?;
    check_range(
        SPEC_CLK_PREPARE_PLUS_ZERO,
        checked_add(u64::from(t.t_clk_prepare), u64::from(t.t_clk_zero))?,
        hs_bit_rate_bps,
    )?;
    check_range(SPEC_CLK_PRE, u64::from(t.t_clk_pre), hs_bit_rate_bps)?;
    check_range(SPEC_CLK_POST, u64::from(t.t_clk_post), hs_bit_rate_bps)?;
    check_range(SPEC_CLK_TRAIL, u64::from(t.t_clk_trail), hs_bit_rate_bps)
}

fn byte_clocks_field(ui: u32) -> S549Result<u32> {
    let bytes = u64::from(ui) / S549_UI_PER_BYTE_CLOCK;
    if bytes > 0xFF {
        return Err(G8lS549DsiHostError::RegisterFieldOverflow);
    }
    Ok(bytes as u32)
}

fn pack_bytes(fields: [u32; 4]) -> S549Result<u32> {
    let mut word = 0u32;
    for (shift, field) in [0u32, 8, 16, 24].into_iter().zip(fields) {
        word |= byte_clocks_field(field)? << shift;
    }
    Ok(word)
}

fn pack_blanking(sync: u32, back: u32, front: u32) -> S549Result<u32> {
    for field in [sync, back, front] {
        if field > S549_MAX_BLANKING_PIXELS {
            return Err(G8lS549DsiHostError::RegisterFieldOverflow);
        }
    }
    Ok(sync | (back << 10) | (front << 20))
}

fn bpp_code(bits_per_pixel: u8) -> S549Result<u32> {
    match bits_per_pixel {
        16 => Ok(0),
        18 => Ok(1),
        24 => Ok(2),
        _ => Err(G8lS549DsiHostError::UnsupportedBitsPerPixel),
    }
}

/// Encodes the full bring-up program: disable, lane/clock/PHY/video timing
/// configuration, interrupt enable, then enable in video mode.
pub fn encode_s549_register_program(
    map: &[G8lS549DsiRegister],
    mode: G8lS549PanelMode,
    clocks: G8lS549LinkClocks,
    timing: G8lS549DphyTimingUi,
) -> S549Result<G8lS549RegisterProgram> {
    validate_s549_register_map(map)?;
    let hs_clk_mhz = to_u32(clocks.hs_bit_rate_bps / 1_000_000)?;
    if hs_clk_mhz > 0xFFFF || mode.hactive > 0xFFFF || mode.vactive > 0xFFFF {
        return Err(G8lS549DsiHostError::RegisterFieldOverflow);
    }
    let lane_cfg = u32::from(mode.lanes - 1) | (bpp_code(mode.bits_per_pixel)? << 4);
    let mut program = G8lS549RegisterProgram::new();
    program.push(map, "DSI_CTRL", 0)?;
    program.push(map, "DSI_LANE_CFG", lane_cfg)?;
    program.push(map, "DSI_HS_CLK", hs_clk_mhz)?;
    program.push(
        map,
        "DSI_PHY_TIMING_HS",
        pack_bytes([
            timing.t_lpx,
            timing.t_hs_prepare,
            timing.t_hs_zero,
            timing.t_hs_trail,
        ])?,
    )?;
    program.push(
        map,
        "DSI_PHY_TIMING_CLK",
        pack_bytes([
            timing.t_clk_prepare,
            timing.t_clk_zero,
            timing.t_clk_pre,
            timing.t_clk_post,
        ])?,
    )?;
    program.push(
        map,
        "DSI_PHY_TIMING_CLK2",
        pack_bytes([timing.t_clk_trail, timing.t_hs_exit, 0, 0])?,
    )?;
    program.push(map, "DSI_VID_TIMING_HACTIVE", mode.hactive)?;
    program.push(
        map,
        "DSI_VID_TIMING_HBLANK",
        pack_blanking(mode.hsync, mode.hbp, mode.hfp)?,
    )?;
    program.push(map, "DSI_VID_TIMING_VACTIVE", mode.vactive)?;
    program.push(
        map,
        "DSI_VID_TIMING_VBLANK",
        pack_blanking(mode.vsync, mode.vbp, mode.vfp)?,
    )?;
    program.push(map, "DSI_INT_ENABLE", S549_INT_ENABLE_PANEL_ERROR)?;
    program.push(map, "DSI_VID_MODE_CFG", S549_VID_MODE_CFG_BURST)?;
    // Ordering is a contract: the first write disables the host and the last
    // write enables it in video mode, so no timing register changes while the
    // (modelled) link is active.
    program.push(map, "DSI_CTRL", S549_CTRL_ENABLE | S549_CTRL_VIDEO_MODE)?;
    if program.writes().len() != S549_PROGRAM_WRITE_COUNT {
        return Err(G8lS549DsiHostError::ProgramBufferOverflow);
    }
    Ok(program)
}

/// Pure model service: validates the mode, derives clocks and D-PHY timing,
/// encodes the bounded register program, and publishes or retains a receipt.
pub fn service_s549_model_dsi_host_bring_up(
    state: &mut G8lS549DsiHostState,
    profile: G8lS549PanelProfile,
    mode: G8lS549PanelMode,
) -> Result<G8lS549DsiHostOutcome, G8lS549DsiHostError> {
    validate_s549_register_map(&S549_DSI_REGISTER_MAP)?;
    let clocks = compute_s549_link_clocks(mode)?;
    let timing = derive_s549_dphy_timing(clocks.hs_bit_rate_bps)?;
    let program = encode_s549_register_program(&S549_DSI_REGISTER_MAP, mode, clocks, timing)?;
    let receipt = G8lS549DsiHostReceipt {
        sequence: S549_SEQUENCE,
        predecessor_sequence: S549_EXPECTED_PREDECESSOR,
        r1_stage: S549_R1_STAGE,
        profile_id: profile.id(),
        mode,
        htotal: clocks.htotal,
        vtotal: clocks.vtotal,
        pixel_clock_hz: clocks.pixel_clock_hz,
        hs_bit_rate_bps: clocks.hs_bit_rate_bps,
        byte_clock_hz: clocks.byte_clock_hz,
        ui_ps: clocks.ui_ps,
        timing,
        program_write_count: program.writes().len(),
        program_checksum: program.checksum(),
        register_offsets_hardware_validated: S549_REGISTER_OFFSETS_HARDWARE_VALIDATED,
        s540_physical_verdict_retained_red: S549_S540_PHYSICAL_VERDICT_RETAINED_RED,
        s543_physical_verdict_retained_red: S549_S543_PHYSICAL_VERDICT_RETAINED_RED,
        automatic_promotion: S549_AUTOMATIC_PROMOTION,
        hardware_present: S549_HARDWARE_PRESENT,
        supported_profile_runtime_observations: S549_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS,
        physical_observations: S549_PHYSICAL_OBSERVATIONS,
        runbook_executed: RUNBOOK_EXECUTED_IN_S549,
    };
    if let Some(published) = state.receipt {
        if published != receipt {
            return Err(G8lS549DsiHostError::PublishedStateDrift);
        }
        return Ok(G8lS549DsiHostOutcome::Retained(published));
    }
    state.receipt = Some(receipt);
    Ok(G8lS549DsiHostOutcome::Published(receipt))
}
snippet sha256: 5588fe963747file sha256: 5588fe963747
02 · Doğrulayan test kodu

Operations komutuna bağlı focused test

tam dosyaL1–L624
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s549_r1_rp1_dsi_host_register_map_dphy_timing_model.rs::S549 r1 rp1 dsi host register map dphy timing model focused tests
use aselsan_microkernel_simulation::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s549_r1_rp1_dsi_host_register_map_dphy_timing_model::*;
use std::collections::BTreeSet;

const SOURCE: &str = include_str!(
    "../../kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s549_r1_rp1_dsi_host_register_map_dphy_timing_model.rs"
);
const MAIN: &str = include_str!("../../kernel/src/main.rs");
const SIMULATION_LIB: &str = include_str!("../src/lib.rs");

const PHONE: G8lS549PanelProfile = G8lS549PanelProfile::Phone720x1280Lanes2;
const TABLET: G8lS549PanelProfile = G8lS549PanelProfile::Tablet1200x1920Lanes4;

const PHONE_PIXEL_CLOCK_HZ: u64 = 810 * 1304 * 60;
const PHONE_HS_BIT_RATE_BPS: u64 = PHONE_PIXEL_CLOCK_HZ * 24 / 2;
const TABLET_PIXEL_CLOCK_HZ: u64 = 1340 * 1964 * 60;
const TABLET_HS_BIT_RATE_BPS: u64 = TABLET_PIXEL_CLOCK_HZ * 24 / 4;

fn publish(
    state: &mut G8lS549DsiHostState,
    profile: G8lS549PanelProfile,
) -> Result<G8lS549DsiHostOutcome, G8lS549DsiHostError> {
    service_s549_model_dsi_host_bring_up(state, profile, canonical_s549_panel_mode(profile))
}

fn phone_mode() -> G8lS549PanelMode {
    canonical_s549_panel_mode(PHONE)
}

fn low_rate_mode() -> G8lS549PanelMode {
    G8lS549PanelMode {
        hactive: 320,
        hfp: 8,
        hbp: 8,
        hsync: 4,
        vactive: 240,
        vfp: 4,
        vbp: 4,
        vsync: 2,
        refresh_hz: 60,
        lanes: 1,
        bits_per_pixel: 16,
    }
}

#[test]
fn sequence_scope_and_nonpromotion_are_exact() {
    assert_eq!(S549_SEQUENCE, 549);
    assert_eq!(S549_EXPECTED_PREDECESSOR, 548);
    assert_eq!(S549_R1_STAGE, 2);
    assert_eq!(S549_R1_RANGE_FIRST, 536);
    assert_eq!(S549_R1_RANGE_LAST, 568);
    assert_eq!(S549_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS, 0);
    assert_eq!(S549_PHYSICAL_OBSERVATIONS, 0);
    assert_eq!(S549_PHYSICAL_OR_DEVICE_OPERATIONS, 0);
    assert_eq!(S549_SD_WRITES, 0);
    assert_eq!(S549_UART_OPENS, 0);
    assert_eq!(S549_POWER_TRANSITIONS, 0);
    assert_eq!(S549_NEW_IMMUTABLE_RAW_CAPTURES, 0);
    assert!(S549_S540_PHYSICAL_VERDICT_RETAINED_RED);
    assert!(S549_S543_PHYSICAL_VERDICT_RETAINED_RED);
    assert!(!S549_AUTOMATIC_PROMOTION);
    assert!(!S549_BOOT_TO_UI_PHYSICALLY_OBSERVED);
    assert!(!S549_HARDWARE_PRESENT);
    assert!(!S549_R1_ACCEPTANCE_COMPLETE);
    assert!(!RUNBOOK_EXECUTED_IN_S549);
    assert!(!S549_REGISTER_OFFSETS_HARDWARE_VALIDATED);
    assert_eq!(S549_SUPPORTED_LANE_COUNTS, [1, 2, 4]);
    assert_eq!(S549_PROGRAM_WRITE_COUNT, 13);
    assert!(S549_PROGRAM_WRITE_COUNT <= S549_MAX_PROGRAM_WRITES);
}

#[test]
fn module_is_registered_in_kernel_and_simulation() {
    let module = "g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s549_r1_rp1_dsi_host_register_map_dphy_timing_model";
    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",
        "#[no_mangle]",
        "spin::",
        "std::",
        "crate::kprintln!",
    ] {
        assert!(!SOURCE.contains(forbidden), "forbidden token: {forbidden}");
    }
    assert!(SOURCE.contains("NOT hardware-validated"));
    assert!(SOURCE.contains("performs no device operation"));
    assert!(SOURCE.contains("does not rerun S540 or S543"));
}

#[test]
fn diagnostic_codes_are_nonzero_and_unique() {
    let errors = [
        G8lS549DsiHostError::UnsupportedLaneCount,
        G8lS549DsiHostError::UnsupportedBitsPerPixel,
        G8lS549DsiHostError::ZeroDimension,
        G8lS549DsiHostError::ActiveOutOfRange,
        G8lS549DsiHostError::BlankingOutOfRange,
        G8lS549DsiHostError::ArithmeticOverflow,
        G8lS549DsiHostError::HsBitRateNotLaneDivisible,
        G8lS549DsiHostError::HsBitRateBelowMinimum,
        G8lS549DsiHostError::HsBitRateAboveMaximum,
        G8lS549DsiHostError::TimingBelowSpecMinimum,
        G8lS549DsiHostError::TimingAboveSpecMaximum,
        G8lS549DsiHostError::TimingNotByteClockAligned,
        G8lS549DsiHostError::RegisterMapMalformed,
        G8lS549DsiHostError::RegisterUnknown,
        G8lS549DsiHostError::RegisterNotWritable,
        G8lS549DsiHostError::RegisterValueExceedsWidth,
        G8lS549DsiHostError::RegisterFieldOverflow,
        G8lS549DsiHostError::ProgramBufferOverflow,
        G8lS549DsiHostError::PublishedStateDrift,
    ];
    let codes: BTreeSet<_> = errors
        .into_iter()
        .map(G8lS549DsiHostError::diagnostic_code)
        .collect();
    assert_eq!(codes.len(), errors.len());
    assert!(!codes.contains(&0));
}

#[test]
fn exact_replay_retains_the_same_receipt() {
    let mut state = G8lS549DsiHostState::new();
    let G8lS549DsiHostOutcome::Published(receipt) = publish(&mut state, PHONE).unwrap() else {
        panic!("first publication missing")
    };
    assert_eq!(state.receipt(), Some(receipt));
    assert_eq!(
        publish(&mut state, PHONE),
        Ok(G8lS549DsiHostOutcome::Retained(receipt))
    );
}

#[test]
fn divergent_input_after_publication_fails_closed() {
    let mut state = G8lS549DsiHostState::new();
    publish(&mut state, PHONE).unwrap();
    assert_eq!(
        publish(&mut state, TABLET),
        Err(G8lS549DsiHostError::PublishedStateDrift)
    );
    let mut drifted = phone_mode();
    drifted.hfp += 1;
    assert_eq!(
        service_s549_model_dsi_host_bring_up(&mut state, PHONE, drifted),
        Err(G8lS549DsiHostError::PublishedStateDrift)
    );
}

#[test]
fn phone_profile_computes_expected_clocks_and_receipt() {
    let mut state = G8lS549DsiHostState::default();
    let G8lS549DsiHostOutcome::Published(receipt) = publish(&mut state, PHONE).unwrap() else {
        panic!("phone publication missing")
    };
    assert_eq!(receipt.sequence, 549);
    assert_eq!(receipt.predecessor_sequence, 548);
    assert_eq!(receipt.r1_stage, 2);
    assert_eq!(receipt.profile_id, 1);
    assert_eq!(receipt.htotal, 810);
    assert_eq!(receipt.vtotal, 1304);
    assert_eq!(receipt.pixel_clock_hz, 63_374_400);
    assert_eq!(receipt.pixel_clock_hz, PHONE_PIXEL_CLOCK_HZ);
    assert_eq!(receipt.hs_bit_rate_bps, 760_492_800);
    assert_eq!(receipt.hs_bit_rate_bps, PHONE_HS_BIT_RATE_BPS);
    assert_eq!(receipt.byte_clock_hz, 95_061_600);
    assert_eq!(receipt.ui_ps, 1314);
    assert_eq!(receipt.program_write_count, 13);
    assert!(!receipt.register_offsets_hardware_validated);
    assert!(receipt.s540_physical_verdict_retained_red);
    assert!(receipt.s543_physical_verdict_retained_red);
    assert!(!receipt.automatic_promotion);
    assert!(!receipt.hardware_present);
    assert_eq!(receipt.supported_profile_runtime_observations, 0);
    assert_eq!(receipt.physical_observations, 0);
    assert!(!receipt.runbook_executed);
}

#[test]
fn phone_profile_derives_expected_dphy_timing_in_ui() {
    let timing = derive_s549_dphy_timing(PHONE_HS_BIT_RATE_BPS).unwrap();
    assert_eq!(
        timing,
        G8lS549DphyTimingUi {
            t_lpx: 40,
            t_hs_prepare: 40,
            t_hs_zero: 88,
            t_hs_trail: 56,
            t_hs_exit: 80,
            t_clk_prepare: 32,
            t_clk_zero: 200,
            t_clk_pre: 8,
            t_clk_post: 104,
            t_clk_trail: 48,
        }
    );
    assert_eq!(validate_s549_dphy_timing(timing, PHONE_HS_BIT_RATE_BPS), Ok(()));
}

#[test]
fn tablet_profile_uses_four_lanes_within_spec() {
    let mut state = G8lS549DsiHostState::new();
    let G8lS549DsiHostOutcome::Published(receipt) = publish(&mut state, TABLET).unwrap() else {
        panic!("tablet publication missing")
    };
    assert_eq!(receipt.profile_id, 2);
    assert_eq!(receipt.mode.lanes, 4);
    assert_eq!(receipt.htotal, 1340);
    assert_eq!(receipt.vtotal, 1964);
    assert_eq!(receipt.pixel_clock_hz, TABLET_PIXEL_CLOCK_HZ);
    assert_eq!(receipt.hs_bit_rate_bps, 947_433_600);
    assert_eq!(receipt.hs_bit_rate_bps, TABLET_HS_BIT_RATE_BPS);
    assert_eq!(receipt.byte_clock_hz, 118_429_200);
    assert_eq!(receipt.ui_ps, 1055);
    assert!(receipt.hs_bit_rate_bps <= S549_MAX_HS_BIT_RATE_BPS);
    assert_eq!(
        validate_s549_dphy_timing(receipt.timing, receipt.hs_bit_rate_bps),
        Ok(())
    );
}

#[test]
fn timing_values_are_byte_clock_aligned_and_within_spec_for_all_lane_counts() {
    for lanes in S549_SUPPORTED_LANE_COUNTS {
        let mut mode = phone_mode();
        mode.lanes = lanes;
        mode.bits_per_pixel = if lanes == 1 { 16 } else { 24 };
        let clocks = compute_s549_link_clocks(mode).unwrap();
        let timing = derive_s549_dphy_timing(clocks.hs_bit_rate_bps).unwrap();
        for value in [
            timing.t_lpx,
            timing.t_hs_prepare,
            timing.t_hs_zero,
            timing.t_hs_trail,
            timing.t_hs_exit,
            timing.t_clk_prepare,
            timing.t_clk_zero,
            timing.t_clk_pre,
            timing.t_clk_post,
            timing.t_clk_trail,
        ] {
            assert_eq!(u64::from(value) % S549_UI_PER_BYTE_CLOCK, 0);
            assert!(value > 0);
        }
        assert_eq!(validate_s549_dphy_timing(timing, clocks.hs_bit_rate_bps), Ok(()));
    }
}

#[test]
fn one_lane_phone_mode_exceeds_hs_bit_rate_ceiling() {
    let mut mode = phone_mode();
    mode.lanes = 1;
    assert_eq!(
        compute_s549_link_clocks(mode),
        Err(G8lS549DsiHostError::HsBitRateAboveMaximum)
    );
    let mut state = G8lS549DsiHostState::new();
    assert_eq!(
        service_s549_model_dsi_host_bring_up(&mut state, PHONE, mode),
        Err(G8lS549DsiHostError::HsBitRateAboveMaximum)
    );
    assert_eq!(state.receipt(), None);
}

#[test]
fn unsupported_lane_counts_and_pixel_depths_are_rejected() {
    for lanes in [0, 3, 5, 8] {
        let mut mode = phone_mode();
        mode.lanes = lanes;
        assert_eq!(
            validate_s549_panel_mode(mode),
            Err(G8lS549DsiHostError::UnsupportedLaneCount),
            "lanes {lanes}"
        );
    }
    for bpp in [0, 12, 30, 32] {
        let mut mode = phone_mode();
        mode.bits_per_pixel = bpp;
        assert_eq!(
            validate_s549_panel_mode(mode),
            Err(G8lS549DsiHostError::UnsupportedBitsPerPixel),
            "bpp {bpp}"
        );
    }
}

#[test]
fn zero_or_out_of_range_geometry_is_rejected() {
    let mut zero = phone_mode();
    zero.vactive = 0;
    assert_eq!(
        validate_s549_panel_mode(zero),
        Err(G8lS549DsiHostError::ZeroDimension)
    );
    let mut no_refresh = phone_mode();
    no_refresh.refresh_hz = 0;
    assert_eq!(
        validate_s549_panel_mode(no_refresh),
        Err(G8lS549DsiHostError::ZeroDimension)
    );
    let mut wide = phone_mode();
    wide.hactive = S549_MAX_ACTIVE_PIXELS + 1;
    assert_eq!(
        validate_s549_panel_mode(wide),
        Err(G8lS549DsiHostError::ActiveOutOfRange)
    );
    let mut blank = phone_mode();
    blank.vbp = S549_MAX_BLANKING_PIXELS + 1;
    assert_eq!(
        validate_s549_panel_mode(blank),
        Err(G8lS549DsiHostError::BlankingOutOfRange)
    );
    let mut zero_blank = phone_mode();
    zero_blank.hsync = 0;
    assert_eq!(
        validate_s549_panel_mode(zero_blank),
        Err(G8lS549DsiHostError::BlankingOutOfRange)
    );
}

#[test]
fn pixel_clock_overflow_fails_closed() {
    let mut mode = phone_mode();
    mode.refresh_hz = u64::MAX;
    assert_eq!(
        compute_s549_link_clocks(mode),
        Err(G8lS549DsiHostError::ArithmeticOverflow)
    );
    mode.refresh_hz = u64::MAX / (810 * 1304);
    assert_eq!(
        compute_s549_link_clocks(mode),
        Err(G8lS549DsiHostError::ArithmeticOverflow)
    );
}

#[test]
fn lane_indivisible_link_rate_and_low_rate_are_rejected() {
    let odd = G8lS549PanelMode {
        hactive: 101,
        hfp: 1,
        hbp: 1,
        hsync: 2,
        vactive: 101,
        vfp: 1,
        vbp: 1,
        vsync: 2,
        refresh_hz: 61,
        lanes: 4,
        bits_per_pixel: 18,
    };
    assert_eq!(
        compute_s549_link_clocks(odd),
        Err(G8lS549DsiHostError::HsBitRateNotLaneDivisible)
    );
    let mut slow = low_rate_mode();
    slow.refresh_hz = 30;
    assert_eq!(
        compute_s549_link_clocks(slow),
        Err(G8lS549DsiHostError::HsBitRateBelowMinimum)
    );
}

#[test]
fn low_rate_mode_violates_clk_prepare_maximum() {
    let clocks = compute_s549_link_clocks(low_rate_mode()).unwrap();
    assert_eq!(clocks.hs_bit_rate_bps, 81_600_000);
    assert_eq!(
        derive_s549_dphy_timing(clocks.hs_bit_rate_bps),
        Err(G8lS549DsiHostError::TimingAboveSpecMaximum)
    );
    let mut state = G8lS549DsiHostState::new();
    assert_eq!(
        service_s549_model_dsi_host_bring_up(&mut state, PHONE, low_rate_mode()),
        Err(G8lS549DsiHostError::TimingAboveSpecMaximum)
    );
    assert_eq!(state.receipt(), None);
}

#[test]
fn handcrafted_timing_outside_spec_is_rejected() {
    let good = derive_s549_dphy_timing(PHONE_HS_BIT_RATE_BPS).unwrap();
    let mut short_lpx = good;
    short_lpx.t_lpx = 32;
    assert_eq!(
        validate_s549_dphy_timing(short_lpx, PHONE_HS_BIT_RATE_BPS),
        Err(G8lS549DsiHostError::TimingBelowSpecMinimum)
    );
    let mut long_prepare = good;
    long_prepare.t_hs_prepare = 72;
    assert_eq!(
        validate_s549_dphy_timing(long_prepare, PHONE_HS_BIT_RATE_BPS),
        Err(G8lS549DsiHostError::TimingAboveSpecMaximum)
    );
    let mut short_composite = good;
    short_composite.t_hs_zero = 72;
    assert_eq!(
        validate_s549_dphy_timing(short_composite, PHONE_HS_BIT_RATE_BPS),
        Err(G8lS549DsiHostError::TimingBelowSpecMinimum)
    );
    let mut unaligned = good;
    unaligned.t_clk_trail = 49;
    assert_eq!(
        validate_s549_dphy_timing(unaligned, PHONE_HS_BIT_RATE_BPS),
        Err(G8lS549DsiHostError::TimingNotByteClockAligned)
    );
}

#[test]
fn dphy_spec_table_encodes_consistent_min_max_ranges() {
    assert_eq!(S549_DPHY_SPEC_RANGES.len(), S549_DPHY_SPEC_RANGE_COUNT);
    let names: BTreeSet<_> = S549_DPHY_SPEC_RANGES.iter().map(|r| r.name).collect();
    assert_eq!(names.len(), S549_DPHY_SPEC_RANGE_COUNT);
    for range in S549_DPHY_SPEC_RANGES {
        for rate in [S549_MIN_HS_BIT_RATE_BPS, PHONE_HS_BIT_RATE_BPS, S549_MAX_HS_BIT_RATE_BPS] {
            let min = s549_spec_min_ui(range, rate).unwrap();
            if let Some(max) = s549_spec_max_ui(range, rate).unwrap() {
                assert!(min <= max, "{} at {rate}", range.name);
            }
        }
    }
    let prepare = S549_DPHY_SPEC_RANGES[1];
    assert_eq!(prepare.name, "T_HS_PREPARE");
    assert_eq!(s549_spec_min_ui(prepare, PHONE_HS_BIT_RATE_BPS), Ok(35));
    assert_eq!(s549_spec_max_ui(prepare, PHONE_HS_BIT_RATE_BPS), Ok(Some(70)));
    assert_eq!(s549_spec_min_ui(prepare, u64::MAX), Err(G8lS549DsiHostError::ArithmeticOverflow));
}

#[test]
fn register_map_is_sorted_aligned_unique_and_symbolic() {
    assert_eq!(validate_s549_register_map(&S549_DSI_REGISTER_MAP), Ok(()));
    assert_eq!(S549_DSI_REGISTER_MAP.len(), S549_REGISTER_COUNT);
    for name in [
        "DSI_CTRL",
        "DSI_HS_CLK",
        "DSI_LANE_CFG",
        "DSI_PKT_HDR",
        "DSI_VID_TIMING_HACTIVE",
        "DSI_VID_TIMING_HBLANK",
        "DSI_VID_TIMING_VACTIVE",
        "DSI_VID_TIMING_VBLANK",
    ] {
        let register = lookup_s549_register(&S549_DSI_REGISTER_MAP, name).unwrap();
        assert_eq!(register.width_bits, 32);
        assert_eq!(register.offset % 4, 0);
    }
    assert_eq!(
        lookup_s549_register(&S549_DSI_REGISTER_MAP, "DSI_STATUS").unwrap().access,
        G8lS549RegisterAccess::ReadOnly
    );
    assert_eq!(
        lookup_s549_register(&S549_DSI_REGISTER_MAP, "DSI_ID").unwrap().reset_value,
        0x4453_4931
    );
    assert_eq!(
        lookup_s549_register(&S549_DSI_REGISTER_MAP, "DSI_MISSING"),
        Err(G8lS549DsiHostError::RegisterUnknown)
    );
}

#[test]
fn malformed_register_maps_are_rejected() {
    assert_eq!(
        validate_s549_register_map(&[]),
        Err(G8lS549DsiHostError::RegisterMapMalformed)
    );
    let mut unaligned = S549_DSI_REGISTER_MAP;
    unaligned[4].offset = 0x012;
    assert_eq!(
        validate_s549_register_map(&unaligned),
        Err(G8lS549DsiHostError::RegisterMapMalformed)
    );
    let mut overlapping = S549_DSI_REGISTER_MAP;
    overlapping[2].offset = 0x004;
    assert_eq!(
        validate_s549_register_map(&overlapping),
        Err(G8lS549DsiHostError::RegisterMapMalformed)
    );
    let mut duplicate = S549_DSI_REGISTER_MAP;
    duplicate[3].name = "DSI_CTRL";
    assert_eq!(
        validate_s549_register_map(&duplicate),
        Err(G8lS549DsiHostError::RegisterMapMalformed)
    );
    let mut narrow = S549_DSI_REGISTER_MAP;
    narrow[0].width_bits = 16;
    assert_eq!(
        validate_s549_register_map(&narrow),
        Err(G8lS549DsiHostError::RegisterMapMalformed)
    );
}

#[test]
fn register_program_rejects_unknown_readonly_and_oversized_writes() {
    let mut program = G8lS549RegisterProgram::new();
    assert_eq!(
        program.push(&S549_DSI_REGISTER_MAP, "DSI_NOPE", 1),
        Err(G8lS549DsiHostError::RegisterUnknown)
    );
    assert_eq!(
        program.push(&S549_DSI_REGISTER_MAP, "DSI_STATUS", 1),
        Err(G8lS549DsiHostError::RegisterNotWritable)
    );
    assert_eq!(
        program.push(&S549_DSI_REGISTER_MAP, "DSI_ID", 1),
        Err(G8lS549DsiHostError::RegisterNotWritable)
    );
    let mut narrow = S549_DSI_REGISTER_MAP;
    narrow[0].width_bits = 8;
    assert_eq!(
        program.push(&narrow, "DSI_CTRL", 0x100),
        Err(G8lS549DsiHostError::RegisterValueExceedsWidth)
    );
    assert_eq!(program.writes().len(), 0);
    assert_eq!(
        program.push(&S549_DSI_REGISTER_MAP, "DSI_INT_STATUS", 0xFFFF_FFFF),
        Ok(G8lS549RegisterWrite {
            offset: 0x008,
            value: 0xFFFF_FFFF
        })
    );
}

#[test]
fn register_program_buffer_is_bounded() {
    let mut program = G8lS549RegisterProgram::new();
    for _ in 0..S549_MAX_PROGRAM_WRITES {
        program.push(&S549_DSI_REGISTER_MAP, "DSI_PKT_PAYLOAD", 0xAB).unwrap();
    }
    assert_eq!(program.writes().len(), S549_MAX_PROGRAM_WRITES);
    assert_eq!(
        program.push(&S549_DSI_REGISTER_MAP, "DSI_PKT_PAYLOAD", 0xAB),
        Err(G8lS549DsiHostError::ProgramBufferOverflow)
    );
    assert_eq!(program.writes().len(), S549_MAX_PROGRAM_WRITES);
}

#[test]
fn encoded_program_disables_first_configures_then_enables_video_mode() {
    let mode = phone_mode();
    let clocks = compute_s549_link_clocks(mode).unwrap();
    let timing = derive_s549_dphy_timing(clocks.hs_bit_rate_bps).unwrap();
    let program = encode_s549_register_program(&S549_DSI_REGISTER_MAP, mode, clocks, timing).unwrap();
    let writes = program.writes();
    assert_eq!(writes.len(), S549_PROGRAM_WRITE_COUNT);
    assert_eq!(writes[0], G8lS549RegisterWrite { offset: 0x000, value: 0 });
    assert_eq!(writes[1], G8lS549RegisterWrite { offset: 0x014, value: 0x21 });
    assert_eq!(writes[2], G8lS549RegisterWrite { offset: 0x010, value: 760 });
    // lpx=40/8=5, prepare=5, zero=11, trail=7 byte clocks.
    assert_eq!(writes[3], G8lS549RegisterWrite { offset: 0x018, value: 0x070B_0505 });
    // clk_prepare=4, clk_zero=25, clk_pre=1, clk_post=13.
    assert_eq!(writes[4], G8lS549RegisterWrite { offset: 0x01C, value: 0x0D01_1904 });
    // clk_trail=6, hs_exit=10.
    assert_eq!(writes[5], G8lS549RegisterWrite { offset: 0x020, value: 0x0000_0A06 });
    assert_eq!(writes[6], G8lS549RegisterWrite { offset: 0x030, value: 720 });
    assert_eq!(
        writes[7],
        G8lS549RegisterWrite { offset: 0x034, value: 10 | (40 << 10) | (40 << 20) }
    );
    assert_eq!(writes[8], G8lS549RegisterWrite { offset: 0x038, value: 1280 });
    assert_eq!(
        writes[9],
        G8lS549RegisterWrite { offset: 0x03C, value: 4 | (10 << 10) | (10 << 20) }
    );
    assert_eq!(writes[10], G8lS549RegisterWrite { offset: 0x00C, value: 1 });
    assert_eq!(writes[11], G8lS549RegisterWrite { offset: 0x040, value: 1 });
    assert_eq!(writes[12], G8lS549RegisterWrite { offset: 0x000, value: 0b11 });
    let mut state = G8lS549DsiHostState::new();
    let G8lS549DsiHostOutcome::Published(receipt) = publish(&mut state, PHONE).unwrap() else {
        panic!("phone publication missing")
    };
    assert_eq!(receipt.program_checksum, program.checksum());
    assert_ne!(receipt.program_checksum, 0);
    let mut other = G8lS549DsiHostState::new();
    let G8lS549DsiHostOutcome::Published(tablet) = publish(&mut other, TABLET).unwrap() else {
        panic!("tablet publication missing")
    };
    assert_ne!(tablet.program_checksum, receipt.program_checksum);
}

#[test]
fn encoder_rejects_register_field_overflow_and_malformed_map() {
    let mode = phone_mode();
    let clocks = compute_s549_link_clocks(mode).unwrap();
    let timing = derive_s549_dphy_timing(clocks.hs_bit_rate_bps).unwrap();
    let mut oversized = timing;
    oversized.t_clk_zero = 256 * 8;
    assert_eq!(
        encode_s549_register_program(&S549_DSI_REGISTER_MAP, mode, clocks, oversized),
        Err(G8lS549DsiHostError::RegisterFieldOverflow)
    );
    let mut broken = S549_DSI_REGISTER_MAP;
    broken[1].offset = 0x000;
    assert_eq!(
        encode_s549_register_program(&broken, mode, clocks, timing),
        Err(G8lS549DsiHostError::RegisterMapMalformed)
    );
    let mut missing = S549_DSI_REGISTER_MAP;
    missing[6].name = "DSI_RENAMED";
    assert_eq!(
        encode_s549_register_program(&missing, mode, clocks, timing),
        Err(G8lS549DsiHostError::RegisterUnknown)
    );
}

#[test]
fn source_only_gate_keeps_runtime_physical_and_r1_claims_zero() {
    assert!(SOURCE.contains("S549_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0"));
    assert!(SOURCE.contains("S549_PHYSICAL_OBSERVATIONS: usize = 0"));
    assert!(SOURCE.contains("S549_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0"));
    assert!(SOURCE.contains("S549_HARDWARE_PRESENT: bool = false"));
    assert!(SOURCE.contains("S549_R1_ACCEPTANCE_COMPLETE: bool = false"));
    assert!(SOURCE.contains("RUNBOOK_EXECUTED_IN_S549: bool = false"));
    assert!(SOURCE.contains("S549_REGISTER_OFFSETS_HARDWARE_VALIDATED: bool = false"));
}
snippet sha256: 85b605285628file sha256: 85b605285628
03 · Kapı kimlik kaydı

Operations sıra, kimlik ve başlık bağı

tam Operations kaydıL2962–L3026
website/src/lib/operations.ts::g8l-s549-r1-rp1-dsi-host-register-map-dphy-timing-model
  {
    id: "g8l-s549-r1-rp1-dsi-host-register-map-dphy-timing-model",
    date: "2026-08-30",
    sequence: 549,
    status: "passed",
    umbrella_status: "partial",
    title: "S549 · R1 ekran: RP1 DSI host register haritası ve D-PHY zamanlama modeli",
    summary:
      "S549 kaynak kapısı PASS'tir: RP1 (Raspberry Pi 5 southbridge) MIPI DSI host bring-up'ı veri olarak modellendi; 17 girişli sembolik register haritası (DSI_CTRL, DSI_HS_CLK, DSI_LANE_CFG, DSI_PKT_HDR, DSI_VID_TIMING_*), piksel saati / lane / bpp'den checked aritmetikle byte clock ve HS bit hızı üreten hesaplayıcı, min/max kodlanmış D-PHY spec tablosundan UI biriminde T_LPX/T_HS_*/T_CLK_* türetimi ve 16 yazımla sınırlı register programı encoder'ı tek fail-closed modülde toplandı. 720x1280@60 RGB888 2-lane telefon profili 760492800 bps/lane, 1200x1920@60 RGB888 4-lane 10.1 inç tablet profili 947433600 bps/lane verir; 1-lane telefon modu 1.5 Gbps tavanını aştığı için, düşük hızlı 81.6 Mbps modu ise T_CLK_PREPARE maksimumunu ihlal ettiği için reddedilir. Register offset'leri RP1 datasheet'ine sonradan bağlanacak model placeholder'larıdır ve donanımda doğrulanmamıştır. Focused 25/25 PASS'tir. S540 ve S543 fiziksel RED immutable kalır, hiçbir panel/DSI host/board yoktur, physical observation=0 ve RUNBOOK_EXECUTED_IN_S549=NO'dur. S550 aynı kaynak/host sınırında R1 aşama 2'yi sürdüren bir sonraki kapıdır.",
    evidence: [
      "S549, S548'den ayrı kaynak model modülü, 25-test focused binary, proof, status manifest, Operations kaydı ve complete Code kartına sahiptir; hiçbir üretim çağrı noktasına, boot/IRQ/scheduler/driver yoluna bağlanmamıştır.",
      "Dar S549 source-model status=PASS; R1 umbrella=PARTIAL, S540 ve S543 physical gate status=RED olarak ayrı tutulur ve S546 üçüncü fiziksel koşunun kararı varsayılmaz.",
      "Register haritası 17 sembolik 32-bit girişten oluşur: DSI_CTRL 0x000 RW, DSI_STATUS 0x004 RO, DSI_INT_STATUS 0x008 W1C, DSI_INT_ENABLE 0x00C RW, DSI_HS_CLK 0x010 RW, DSI_LANE_CFG 0x014 RW, DSI_PHY_TIMING_HS/CLK/CLK2 0x018-0x020 RW, DSI_PKT_HDR 0x024 WO, DSI_PKT_PAYLOAD 0x028 WO, DSI_VID_TIMING_HACTIVE/HBLANK/VACTIVE/VBLANK 0x030-0x03C RW, DSI_VID_MODE_CFG 0x040 RW ve DSI_ID 0x0FC RO (reset 0x44534931).",
      "Offset, genişlik, reset ve erişim sınıfları RP1 datasheet'ine sonradan bağlanacak model placeholder'larıdır; donanımda doğrulanmamıştır ve S549_REGISTER_OFFSETS_HARDWARE_VALIDATED=false her receipt'te taşınır.",
      "Harita doğrulayıcı boş tablo, 32-bit dışı genişlik, 4-byte hizasız veya artmayan/çakışan offset ve tekrarlanan isimleri RegisterMapMalformed ile reddeder.",
      "Saat hesaplayıcı pixel_clock=htotal*vtotal*refresh, hs_bit_rate=pixel_clock*bpp/lanes (tam bölünme zorunlu), byte_clock=hs_bit_rate/8 ve ui_ps=1e12/hs_bit_rate değerlerini yalnız checked u64 aritmetikle üretir; taşma ArithmeticOverflow ile fail-closed döner.",
      "Desteklenen lane sayıları 1/2/4, bpp 16/18/24, HS bit hızı aralığı 80 Mbps-1.5 Gbps, aktif piksel ≤4096 ve blanking 1..=1023'tür; bunların dışı UnsupportedLaneCount, UnsupportedBitsPerPixel, ZeroDimension, ActiveOutOfRange, BlankingOutOfRange, HsBitRateBelowMinimum veya HsBitRateAboveMaximum ile reddedilir.",
      "Telefon profili 720x1280@60 RGB888 2-lane (htotal 810, vtotal 1304) pixel clock 63374400 Hz, HS bit hızı 760492800 bps/lane, byte clock 95061600 Hz ve UI 1314 ps verir.",
      "Tablet profili 1200x1920@60 RGB888 4-lane (htotal 1340, vtotal 1964) pixel clock 157905600 Hz, HS bit hızı 947433600 bps/lane, byte clock 118429200 Hz ve UI 1055 ps verir.",
      "D-PHY spec tablosu 10 satırda min (ns+UI) ve varsa max (ns+UI) kodlar: T_LPX≥50ns, T_HS_PREPARE 40ns+4UI..85ns+6UI, T_HS_PREPARE+T_HS_ZERO≥145ns+10UI, T_HS_TRAIL≥60ns+4UI, T_HS_EXIT≥100ns, T_CLK_PREPARE 38..95ns, T_CLK_PREPARE+T_CLK_ZERO≥300ns, T_CLK_PRE≥8UI, T_CLK_POST≥60ns+52UI, T_CLK_TRAIL≥60ns.",
      "Türetilen zamanlamalar spec minimumunun byte-clock (8 UI) katına yuvarlanmasıdır; yuvarlanmış değer bir spec maksimumunu aşarsa kırpılmaz, TimingAboveSpecMaximum ile reddedilir. Telefon profili T_LPX=40, T_HS_PREPARE=40, T_HS_ZERO=88, T_HS_TRAIL=56, T_HS_EXIT=80, T_CLK_PREPARE=32, T_CLK_ZERO=200, T_CLK_PRE=8, T_CLK_POST=104, T_CLK_TRAIL=48 UI üretir.",
      "Bağımsız doğrulayıcı elle kurulan zamanlamalarda spec minimum altını, maksimum üstünü, bileşik toplam ihlalini ve 8 UI hizasız değerleri ayrı diagnostic kodlarıyla reddeder.",
      "Register programı en fazla 16 yazımla sınırlıdır ve kanonik program 13 yazımdır: önce DSI_CTRL=0, sonra lane/bpp, HS clock (MHz), üç paketlenmiş PHY timing sözcüğü, dört video timing sözcüğü, INT_ENABLE, VID_MODE_CFG ve en son DSI_CTRL=ENABLE|VIDEO_MODE.",
      "Bilinmeyen register, salt-okunur register yazımı, genişliği aşan değer, 8-bit/10-bit alan taşması ve sınırı aşan program uzunluğu RegisterUnknown, RegisterNotWritable, RegisterValueExceedsWidth, RegisterFieldOverflow ve ProgramBufferOverflow ile fail-closed döner.",
      "1-lane telefon modu 1520985600 bps ile 1.5 Gbps tavanını aştığından, 320x240 16-bpp 1-lane 81.6 Mbps modu ise byte-clock yuvarlanmış T_CLK_PREPARE değeri 95 ns maksimumunu aştığından reddedilir; her iki durumda state receipt'i boş kalır.",
      "Exact replay aynı receipt ile Retained döner; yayından sonra farklı profil veya tek alanı değişmiş mode PublishedStateDrift ile reddedilir. 19 diagnostic kodu sıfırdan farklı ve benzersizdir.",
      "Kaynakta unsafe, asm!, write_volatile, crate::uart, crate::arch, #[no_mangle], spin:: ve std:: yoktur; focused test bunu kaynak metni üzerinden doğrular.",
      "Focused target 1 grup / 25 passed / 0 failed / 0 ignored / 0 filtered verdi.",
      "Implementation 28348 B / 5588fe963747f3f560d92284aadb040a0d76bb414f9d84a51d493d2b528b5e15; focused test 22477 B / 85b60528562819fb4b6456cdbc1ebd36dac8c5bdaf61657032c60d99ae25533c SHA-256'dır.",
      "Proof 6624 B'dir.",
      "S540 immutable raw 20525 B ve S543 immutable raw 20509 B fiziksel RED olarak byte-exact korunur; automatic promotion=false ve S543 rerun=false'dur.",
      "S549 sırasında panel, DSI host, D-PHY, board, SD write/read-back/eject, UART open/capture, power transition, fiziksel koşu veya yeni immutable raw üretimi yapılmadı.",
      "RUNBOOK_EXECUTED_IN_S549=NO; supported-profile runtime observations=0, physical observations=0, hardware present=false, Boot-to-UI physically observed=false ve R1 acceptance=false'dur.",
      "S550 aynı kaynak/host sınırında R1 aşama 2'yi sürdürür; register offset'lerinin RP1 datasheet'ine bağlanması ayrı ve açıkça belgelenecek bir adımdır, 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_s549_r1_rp1_dsi_host_register_map_dphy_timing_model -- --test-threads=1",
    ],
    terminalSessions: [
      {
        id: "s549-focused",
        title: "S549 RP1 DSI host register map ve D-PHY timing model focused",
        commandLines: [
          "CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s549_r1_rp1_dsi_host_register_map_dphy_timing_model -- --test-threads=1",
        ],
        outputLines: [
          "test result: ok. 25 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s",
          "S549 focused=1 group / 25 passed / 0 failed",
          "hardware=none physical=0 runbook=NO",
        ],
        exitCode: 0,
        outputMode: "complete",
      },
    ],
    terminalSessionsNote:
      "S549 kaynak/host model PASS'tir; supported-profile runtime veya fiziksel PASS değildir. S540 ve S543 RED raw ve kararları değişmez.",
    limitations: [
      "S549 yalnız kaynak/host modelidir; hiçbir donanım/panel/modem/board gözlemi yoktur ve modül hiçbir üretim boot/IRQ/scheduler/driver yoluna bağlanmamıştır.",
      "Register offset, genişlik, reset ve erişim sınıfları RP1 datasheet'ine bağlanmamış model placeholder'larıdır; donanımda doğrulanmamıştır.",
      "D-PHY spec aralıkları modelde kodlanmış tablo değerleridir; gerçek D-PHY veya panel zamanlaması ölçülmemiştir.",
      "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.",
      "S550 aynı kaynak/host sınırında R1 aşama 2'yi sürdürür; yeni SD/UART/power koşusu ayrı kapı, fresh target revalidation, açık operatör yetkisi ve yeni immutable raw ister.",
    ],
  },
snippet sha256: 19ebdefd7075file 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_s549_r1_rp1_dsi_host_register_map_dphy_timing_model -- --test-threads=1
proof: docs/M8.1-RPi5-G8l-S549-R1-RP1-DSI-Host-Register-Map-DPhy-Timing-Model-Proof.md
Registry schema v5 · generator website/scripts/generate-code-gates.mjs · Tam SHA-256: 3050638b71a684d8f8f947a8a6faa237a17fa8db5dc0db04fb207b668b462af9