S551 · SOURCE-BOUND GATE EVIDENCE
S551 · R1 ekran: Goodix GT911 dokunmatik I2C rapor çözücü modeli
tam S551 implementation modülü → Operations --test hedefi ile bağlı tam focused test → ayrı Operations kaydı Bu sayfa yalnız S551 kapısına aittir; komşu kapıların kaynakları bu kabulün içine katılmaz.
S551Focused kod testiOperations id exactsource SHA exacttest target exact
operation: g8l-s551-r1-gt911-touch-i2c-report-decoder-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–L544
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s551_r1_gt911_touch_i2c_report_decoder_model.rs::S551 r1 gt911 touch i2c report decoder model implementation
#![allow(dead_code)]
//! S551 models the Goodix GT911 capacitive touch controller report protocol
//! as pure decode logic for the R1 720x1280 panel.
//!
//! The model covers the I2C slave addresses `0x5D`/`0x14`, the coordinate
//! status register `0x814E` (buffer-ready bit 7, large-detect bit 6, touch
//! count bits 0..3), the eight-byte per-point records starting at `0x814F`
//! (track id, x lo/hi, y lo/hi, size lo/hi, reserved), the configuration
//! block `0x8047..=0x80FE` with its 8-bit sum-complement checksum at `0x80FF`,
//! the ASCII product id at `0x8140` (`"911"`), an axis swap/invert transform
//! and the status write-back that clears `0x814E` to `0` after a frame is
//! consumed. The decoder produces a `G8lS551TouchFrame` from a byte slice
//! and fails closed on a missing buffer-ready bit, a touch count above five,
//! out-of-range coordinates, a bad configuration checksum, a truncated or
//! oversized frame, a non-zero reserved byte, and unordered track ids.
//!
//! This gate is a source/host model only. No I2C controller, no touch panel
//! and no board exist for it; nothing here performs a device operation, is
//! wired into a boot, IRQ, scheduler or driver path, or claims a physical
//! observation. S540 and S543 physical verdicts remain immutable RED.
//!
//! Predecessor: S550 (ILI9881 panel DCS init sequence model).
//! Next gate: S552 (input focus and touch event routing model).
use alloc::vec::Vec;
pub const S551_SEQUENCE: usize = 551;
pub const S551_EXPECTED_PREDECESSOR: usize = 550;
pub const S551_R1_STAGE: u8 = 2;
pub const S551_R1_RANGE_FIRST: usize = 536;
pub const S551_R1_RANGE_LAST: usize = 568;
pub const S551_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0;
pub const S551_PHYSICAL_OBSERVATIONS: usize = 0;
pub const S551_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0;
pub const S551_SD_WRITES: usize = 0;
pub const S551_UART_OPENS: usize = 0;
pub const S551_POWER_TRANSITIONS: usize = 0;
pub const S551_NEW_IMMUTABLE_RAW_CAPTURES: usize = 0;
pub const S551_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S551_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S551_AUTOMATIC_PROMOTION: bool = false;
pub const S551_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false;
pub const S551_HARDWARE_PRESENT: bool = false;
pub const S551_R1_ACCEPTANCE_COMPLETE: bool = false;
pub const RUNBOOK_EXECUTED_IN_S551: bool = false;
/// GT911 7-bit I2C slave addresses selected by the INT pin level at reset.
pub const S551_GT911_I2C_ADDRESS_PRIMARY: u8 = 0x5D;
pub const S551_GT911_I2C_ADDRESS_SECONDARY: u8 = 0x14;
/// Coordinate status register: bit 7 buffer ready, bit 6 large detect,
/// bits 0..3 touch count.
pub const S551_GT911_STATUS_REGISTER: u16 = 0x814E;
pub const S551_GT911_STATUS_BUFFER_READY_BIT: u8 = 0x80;
pub const S551_GT911_STATUS_LARGE_DETECT_BIT: u8 = 0x40;
pub const S551_GT911_STATUS_TOUCH_COUNT_MASK: u8 = 0x0F;
/// Value written back to the status register after a frame is consumed.
pub const S551_GT911_STATUS_WRITE_BACK_VALUE: u8 = 0x00;
/// First per-point record register; records are eight bytes each.
pub const S551_GT911_POINT_BASE_REGISTER: u16 = 0x814F;
pub const S551_GT911_POINT_RECORD_BYTES: usize = 8;
pub const S551_GT911_MAX_POINTS: usize = 5;
pub const S551_GT911_MAX_TRACK_ID: u8 = 9;
/// Largest report the decoder accepts: status byte plus five records.
pub const S551_GT911_MAX_REPORT_BYTES: usize =
1 + S551_GT911_MAX_POINTS * S551_GT911_POINT_RECORD_BYTES;
/// Configuration block and checksum registers.
pub const S551_GT911_CONFIG_FIRST_REGISTER: u16 = 0x8047;
pub const S551_GT911_CONFIG_LAST_REGISTER: u16 = 0x80FE;
pub const S551_GT911_CONFIG_CHECKSUM_REGISTER: u16 = 0x80FF;
pub const S551_GT911_CONFIG_BYTES: usize =
(S551_GT911_CONFIG_LAST_REGISTER - S551_GT911_CONFIG_FIRST_REGISTER + 1) as usize;
/// Offsets inside the configuration block (relative to `0x8047`).
pub const S551_GT911_CONFIG_OFFSET_VERSION: usize = 0x00;
pub const S551_GT911_CONFIG_OFFSET_X_MAX_LO: usize = 0x01;
pub const S551_GT911_CONFIG_OFFSET_X_MAX_HI: usize = 0x02;
pub const S551_GT911_CONFIG_OFFSET_Y_MAX_LO: usize = 0x03;
pub const S551_GT911_CONFIG_OFFSET_Y_MAX_HI: usize = 0x04;
pub const S551_GT911_CONFIG_OFFSET_TOUCH_NUMBER: usize = 0x05;
pub const S551_GT911_CONFIG_OFFSET_MODULE_SWITCH1: usize = 0x06;
/// Module_Switch1 bit 3 selects the controller-side X/Y exchange.
pub const S551_GT911_MODULE_SWITCH1_X2Y_BIT: u8 = 0x08;
pub const S551_GT911_CONFIG_VERSION: u8 = 0x41;
/// Product id registers `0x8140..=0x8143` hold ASCII `"911"` plus NUL.
pub const S551_GT911_PRODUCT_ID_REGISTER: u16 = 0x8140;
pub const S551_GT911_PRODUCT_ID: [u8; 4] = *b"911\0";
/// R1 panel geometry and the inclusive coordinate limits.
pub const S551_PANEL_WIDTH: u16 = 720;
pub const S551_PANEL_HEIGHT: u16 = 1280;
pub const S551_MAX_X: u16 = S551_PANEL_WIDTH - 1;
pub const S551_MAX_Y: u16 = S551_PANEL_HEIGHT - 1;
/// Raw point exactly as the controller records it (before axis transform).
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct G8lS551RawTouchPoint {
pub track_id: u8,
pub x: u16,
pub y: u16,
pub size: u16,
}
/// Decoded point in panel coordinates (after swap/invert).
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct G8lS551TouchPoint {
pub track_id: u8,
pub x: u16,
pub y: u16,
pub size: u16,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct G8lS551AxisTransform {
pub swap_xy: bool,
pub invert_x: bool,
pub invert_y: bool,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct G8lS551TouchFrame {
pub points: [G8lS551TouchPoint; S551_GT911_MAX_POINTS],
pub count: u8,
pub large_detect: bool,
}
impl G8lS551TouchFrame {
pub fn active_points(&self) -> &[G8lS551TouchPoint] {
let count = (self.count as usize).min(S551_GT911_MAX_POINTS);
&self.points[..count]
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS551TouchI2cReportDecoderReceipt {
pub sequence: usize,
pub predecessor_sequence: usize,
pub r1_stage: u8,
pub i2c_address: u8,
pub product_id: [u8; 4],
pub config_bytes: usize,
pub config_checksum: u8,
pub config_x_max: u16,
pub config_y_max: u16,
pub config_touch_number: u8,
pub transform: G8lS551AxisTransform,
pub status_register: u16,
pub status_before: u8,
pub status_after_write_back: u8,
pub report_bytes: usize,
pub frame: G8lS551TouchFrame,
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 G8lS551TouchI2cReportDecoderState {
receipt: Option<G8lS551TouchI2cReportDecoderReceipt>,
status_shadow: u8,
write_backs: usize,
}
impl G8lS551TouchI2cReportDecoderState {
pub const fn new() -> Self {
Self {
receipt: None,
status_shadow: S551_GT911_STATUS_WRITE_BACK_VALUE,
write_backs: 0,
}
}
pub const fn receipt(&self) -> Option<G8lS551TouchI2cReportDecoderReceipt> {
self.receipt
}
/// Modelled content of `0x814E` after the last service call.
pub const fn status_register_shadow(&self) -> u8 {
self.status_shadow
}
/// Number of modelled write-backs to `0x814E`.
pub const fn write_backs(&self) -> usize {
self.write_backs
}
}
impl Default for G8lS551TouchI2cReportDecoderState {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS551TouchI2cReportDecoderOutcome {
FrameDecoded(G8lS551TouchI2cReportDecoderReceipt),
FrameRetained(G8lS551TouchI2cReportDecoderReceipt),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS551TouchI2cReportDecoderError {
InvalidI2cAddress,
ProductIdMismatch,
ConfigLengthMismatch,
ConfigChecksumMismatch,
ConfigVersionMismatch,
ConfigResolutionMismatch,
ConfigTouchNumberMismatch,
AxisSwapConfigMismatch,
TruncatedFrame,
FrameLengthMismatch,
BufferNotReady,
TouchCountOverflow,
TrackIdOutOfRange,
TrackIdNotAscending,
ReservedByteNonZero,
CoordinateOutOfRange,
ArithmeticOverflow,
PublishedStateDrift,
}
impl G8lS551TouchI2cReportDecoderError {
pub const fn diagnostic_code(self) -> u64 {
match self {
Self::InvalidI2cAddress => 1,
Self::ProductIdMismatch => 2,
Self::ConfigLengthMismatch => 3,
Self::ConfigChecksumMismatch => 4,
Self::ConfigVersionMismatch => 5,
Self::ConfigResolutionMismatch => 6,
Self::ConfigTouchNumberMismatch => 7,
Self::AxisSwapConfigMismatch => 8,
Self::TruncatedFrame => 9,
Self::FrameLengthMismatch => 10,
Self::BufferNotReady => 11,
Self::TouchCountOverflow => 12,
Self::TrackIdOutOfRange => 13,
Self::TrackIdNotAscending => 14,
Self::ReservedByteNonZero => 15,
Self::CoordinateOutOfRange => 16,
Self::ArithmeticOverflow => 17,
Self::PublishedStateDrift => 18,
}
}
}
pub const fn gt911_i2c_address_is_valid(address: u8) -> bool {
address == S551_GT911_I2C_ADDRESS_PRIMARY || address == S551_GT911_I2C_ADDRESS_SECONDARY
}
/// 8-bit sum-complement over the `0x8047..=0x80FE` block: the byte that makes
/// `sum(config) + checksum == 0 (mod 256)`.
pub fn compute_gt911_config_checksum(
config: &[u8],
) -> Result<u8, G8lS551TouchI2cReportDecoderError> {
if config.len() != S551_GT911_CONFIG_BYTES {
return Err(G8lS551TouchI2cReportDecoderError::ConfigLengthMismatch);
}
let mut sum: u32 = 0;
for byte in config {
sum = sum
.checked_add(*byte as u32)
.ok_or(G8lS551TouchI2cReportDecoderError::ArithmeticOverflow)?;
}
let low = sum & 0xFF;
Ok(((0x100 - low) & 0xFF) as u8)
}
/// Canonical R1 configuration block: version `0x41`, 720x1280 output range,
/// five touch points, Module_Switch1 with the X2Y bit chosen by `swap_xy`,
/// remaining bytes zero.
pub fn canonical_s551_config(swap_xy: bool) -> [u8; S551_GT911_CONFIG_BYTES] {
let mut config = [0u8; S551_GT911_CONFIG_BYTES];
config[S551_GT911_CONFIG_OFFSET_VERSION] = S551_GT911_CONFIG_VERSION;
let [x_lo, x_hi] = S551_PANEL_WIDTH.to_le_bytes();
let [y_lo, y_hi] = S551_PANEL_HEIGHT.to_le_bytes();
config[S551_GT911_CONFIG_OFFSET_X_MAX_LO] = x_lo;
config[S551_GT911_CONFIG_OFFSET_X_MAX_HI] = x_hi;
config[S551_GT911_CONFIG_OFFSET_Y_MAX_LO] = y_lo;
config[S551_GT911_CONFIG_OFFSET_Y_MAX_HI] = y_hi;
config[S551_GT911_CONFIG_OFFSET_TOUCH_NUMBER] = S551_GT911_MAX_POINTS as u8;
config[S551_GT911_CONFIG_OFFSET_MODULE_SWITCH1] = if swap_xy {
S551_GT911_MODULE_SWITCH1_X2Y_BIT
} else {
0
};
config
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS551ConfigSummary {
pub checksum: u8,
pub x_max: u16,
pub y_max: u16,
pub touch_number: u8,
pub swap_xy: bool,
}
/// Validates the configuration block against the R1 panel and the checksum
/// byte read from `0x80FF`.
pub fn validate_gt911_config(
config: &[u8],
checksum_register: u8,
) -> Result<G8lS551ConfigSummary, G8lS551TouchI2cReportDecoderError> {
let checksum = compute_gt911_config_checksum(config)?;
if checksum != checksum_register {
return Err(G8lS551TouchI2cReportDecoderError::ConfigChecksumMismatch);
}
if config[S551_GT911_CONFIG_OFFSET_VERSION] != S551_GT911_CONFIG_VERSION {
return Err(G8lS551TouchI2cReportDecoderError::ConfigVersionMismatch);
}
let x_max = u16::from_le_bytes([
config[S551_GT911_CONFIG_OFFSET_X_MAX_LO],
config[S551_GT911_CONFIG_OFFSET_X_MAX_HI],
]);
let y_max = u16::from_le_bytes([
config[S551_GT911_CONFIG_OFFSET_Y_MAX_LO],
config[S551_GT911_CONFIG_OFFSET_Y_MAX_HI],
]);
if x_max != S551_PANEL_WIDTH || y_max != S551_PANEL_HEIGHT {
return Err(G8lS551TouchI2cReportDecoderError::ConfigResolutionMismatch);
}
let touch_number = config[S551_GT911_CONFIG_OFFSET_TOUCH_NUMBER];
if touch_number as usize != S551_GT911_MAX_POINTS {
return Err(G8lS551TouchI2cReportDecoderError::ConfigTouchNumberMismatch);
}
let swap_xy =
config[S551_GT911_CONFIG_OFFSET_MODULE_SWITCH1] & S551_GT911_MODULE_SWITCH1_X2Y_BIT != 0;
Ok(G8lS551ConfigSummary {
checksum,
x_max,
y_max,
touch_number,
swap_xy,
})
}
/// Encodes one raw point into the eight-byte GT911 record layout.
pub const fn encode_gt911_point(
point: G8lS551RawTouchPoint,
) -> [u8; S551_GT911_POINT_RECORD_BYTES] {
let x = point.x.to_le_bytes();
let y = point.y.to_le_bytes();
let size = point.size.to_le_bytes();
[
point.track_id,
x[0],
x[1],
y[0],
y[1],
size[0],
size[1],
0x00,
]
}
/// Encodes a status byte plus records as the controller would present them
/// from `0x814E` onwards. Used by the focused test to build wire frames.
pub fn encode_gt911_report(
points: &[G8lS551RawTouchPoint],
buffer_ready: bool,
large_detect: bool,
) -> Result<Vec<u8>, G8lS551TouchI2cReportDecoderError> {
if points.len() > S551_GT911_MAX_POINTS {
return Err(G8lS551TouchI2cReportDecoderError::TouchCountOverflow);
}
let mut status = points.len() as u8 & S551_GT911_STATUS_TOUCH_COUNT_MASK;
if buffer_ready {
status |= S551_GT911_STATUS_BUFFER_READY_BIT;
}
if large_detect {
status |= S551_GT911_STATUS_LARGE_DETECT_BIT;
}
let mut report = Vec::with_capacity(S551_GT911_MAX_REPORT_BYTES);
report.push(status);
for point in points {
report.extend_from_slice(&encode_gt911_point(*point));
}
Ok(report)
}
/// Applies the controller-native to panel transform and range-checks.
pub fn transform_gt911_point(
raw: G8lS551RawTouchPoint,
transform: G8lS551AxisTransform,
) -> Result<G8lS551TouchPoint, G8lS551TouchI2cReportDecoderError> {
let (mut x, mut y) = if transform.swap_xy {
(raw.y, raw.x)
} else {
(raw.x, raw.y)
};
if x > S551_MAX_X || y > S551_MAX_Y {
return Err(G8lS551TouchI2cReportDecoderError::CoordinateOutOfRange);
}
if transform.invert_x {
x = S551_MAX_X
.checked_sub(x)
.ok_or(G8lS551TouchI2cReportDecoderError::ArithmeticOverflow)?;
}
if transform.invert_y {
y = S551_MAX_Y
.checked_sub(y)
.ok_or(G8lS551TouchI2cReportDecoderError::ArithmeticOverflow)?;
}
Ok(G8lS551TouchPoint {
track_id: raw.track_id,
x,
y,
size: raw.size,
})
}
/// Pure decoder: `report[0]` is the status register, followed by
/// `count` eight-byte records. The slice must be exactly
/// `1 + count * 8` bytes long.
pub fn decode_gt911_report(
report: &[u8],
transform: G8lS551AxisTransform,
) -> Result<G8lS551TouchFrame, G8lS551TouchI2cReportDecoderError> {
let status = *report
.first()
.ok_or(G8lS551TouchI2cReportDecoderError::TruncatedFrame)?;
if status & S551_GT911_STATUS_BUFFER_READY_BIT == 0 {
return Err(G8lS551TouchI2cReportDecoderError::BufferNotReady);
}
let count = status & S551_GT911_STATUS_TOUCH_COUNT_MASK;
if count as usize > S551_GT911_MAX_POINTS {
return Err(G8lS551TouchI2cReportDecoderError::TouchCountOverflow);
}
let expected_len = (count as usize)
.checked_mul(S551_GT911_POINT_RECORD_BYTES)
.and_then(|records| records.checked_add(1))
.ok_or(G8lS551TouchI2cReportDecoderError::ArithmeticOverflow)?;
if report.len() < expected_len {
return Err(G8lS551TouchI2cReportDecoderError::TruncatedFrame);
}
if report.len() != expected_len {
return Err(G8lS551TouchI2cReportDecoderError::FrameLengthMismatch);
}
let mut frame = G8lS551TouchFrame {
points: [G8lS551TouchPoint::default(); S551_GT911_MAX_POINTS],
count,
large_detect: status & S551_GT911_STATUS_LARGE_DETECT_BIT != 0,
};
let mut previous_track: Option<u8> = None;
for (index, record) in report[1..]
.chunks_exact(S551_GT911_POINT_RECORD_BYTES)
.enumerate()
{
let track_id = record[0];
if track_id > S551_GT911_MAX_TRACK_ID {
return Err(G8lS551TouchI2cReportDecoderError::TrackIdOutOfRange);
}
if let Some(previous) = previous_track {
if track_id <= previous {
return Err(G8lS551TouchI2cReportDecoderError::TrackIdNotAscending);
}
}
if record[7] != 0 {
return Err(G8lS551TouchI2cReportDecoderError::ReservedByteNonZero);
}
let raw = G8lS551RawTouchPoint {
track_id,
x: u16::from_le_bytes([record[1], record[2]]),
y: u16::from_le_bytes([record[3], record[4]]),
size: u16::from_le_bytes([record[5], record[6]]),
};
frame.points[index] = transform_gt911_point(raw, transform)?;
previous_track = Some(track_id);
}
Ok(frame)
}
/// Full model service: validates the controller identity and configuration,
/// decodes one report, models the `0x814E` write-back to `0`, and retains an
/// exact receipt. Exact replay is retained; any divergence after the first
/// publication fails closed.
pub fn service_s551_model_decode_touch_report(
state: &mut G8lS551TouchI2cReportDecoderState,
i2c_address: u8,
product_id: [u8; 4],
config: &[u8],
config_checksum_register: u8,
transform: G8lS551AxisTransform,
report: &[u8],
) -> Result<G8lS551TouchI2cReportDecoderOutcome, G8lS551TouchI2cReportDecoderError> {
if !gt911_i2c_address_is_valid(i2c_address) {
return Err(G8lS551TouchI2cReportDecoderError::InvalidI2cAddress);
}
if product_id != S551_GT911_PRODUCT_ID {
return Err(G8lS551TouchI2cReportDecoderError::ProductIdMismatch);
}
let summary = validate_gt911_config(config, config_checksum_register)?;
if summary.swap_xy != transform.swap_xy {
return Err(G8lS551TouchI2cReportDecoderError::AxisSwapConfigMismatch);
}
let frame = decode_gt911_report(report, transform)?;
let status_before = report[0];
let receipt = G8lS551TouchI2cReportDecoderReceipt {
sequence: S551_SEQUENCE,
predecessor_sequence: S551_EXPECTED_PREDECESSOR,
r1_stage: S551_R1_STAGE,
i2c_address,
product_id,
config_bytes: config.len(),
config_checksum: summary.checksum,
config_x_max: summary.x_max,
config_y_max: summary.y_max,
config_touch_number: summary.touch_number,
transform,
status_register: S551_GT911_STATUS_REGISTER,
status_before,
status_after_write_back: S551_GT911_STATUS_WRITE_BACK_VALUE,
report_bytes: report.len(),
frame,
hardware_present: S551_HARDWARE_PRESENT,
s540_physical_verdict_retained_red: S551_S540_PHYSICAL_VERDICT_RETAINED_RED,
s543_physical_verdict_retained_red: S551_S543_PHYSICAL_VERDICT_RETAINED_RED,
automatic_promotion: S551_AUTOMATIC_PROMOTION,
supported_profile_runtime_observations: S551_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS,
physical_observations: S551_PHYSICAL_OBSERVATIONS,
runbook_executed: RUNBOOK_EXECUTED_IN_S551,
};
if let Some(published) = state.receipt {
if published != receipt {
return Err(G8lS551TouchI2cReportDecoderError::PublishedStateDrift);
}
return Ok(G8lS551TouchI2cReportDecoderOutcome::FrameRetained(
published,
));
}
// Write-back model: the consumed status byte is replaced by `0x00` so the
// controller may publish the next buffer.
state.status_shadow = S551_GT911_STATUS_WRITE_BACK_VALUE;
state.write_backs = state
.write_backs
.checked_add(1)
.ok_or(G8lS551TouchI2cReportDecoderError::ArithmeticOverflow)?;
state.receipt = Some(receipt);
Ok(G8lS551TouchI2cReportDecoderOutcome::FrameDecoded(receipt))
}
snippet sha256: f55ae2ccc19c…file sha256: f55ae2ccc19c…
02 · Doğrulayan test kodu
Operations komutuna bağlı focused test
tam dosyaL1–L759
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s551_r1_gt911_touch_i2c_report_decoder_model.rs::S551 r1 gt911 touch i2c report decoder model focused tests
use aselsan_microkernel_simulation::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s551_r1_gt911_touch_i2c_report_decoder_model::*;
use std::collections::BTreeSet;
const SOURCE: &str = include_str!(
"../../kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s551_r1_gt911_touch_i2c_report_decoder_model.rs"
);
const MAIN: &str = include_str!("../../kernel/src/main.rs");
const SIMULATION_LIB: &str = include_str!("../src/lib.rs");
const IDENTITY: G8lS551AxisTransform = G8lS551AxisTransform {
swap_xy: false,
invert_x: false,
invert_y: false,
};
fn raw(track_id: u8, x: u16, y: u16, size: u16) -> G8lS551RawTouchPoint {
G8lS551RawTouchPoint {
track_id,
x,
y,
size,
}
}
fn report(points: &[G8lS551RawTouchPoint]) -> Vec<u8> {
encode_gt911_report(points, true, false).unwrap()
}
fn config_and_checksum(swap_xy: bool) -> ([u8; S551_GT911_CONFIG_BYTES], u8) {
let config = canonical_s551_config(swap_xy);
let checksum = compute_gt911_config_checksum(&config).unwrap();
(config, checksum)
}
fn decode(
state: &mut G8lS551TouchI2cReportDecoderState,
transform: G8lS551AxisTransform,
report: &[u8],
) -> Result<G8lS551TouchI2cReportDecoderOutcome, G8lS551TouchI2cReportDecoderError> {
let (config, checksum) = config_and_checksum(transform.swap_xy);
service_s551_model_decode_touch_report(
state,
S551_GT911_I2C_ADDRESS_PRIMARY,
S551_GT911_PRODUCT_ID,
&config,
checksum,
transform,
report,
)
}
fn single_point_report() -> Vec<u8> {
report(&[raw(0, 100, 200, 12)])
}
#[test]
fn sequence_scope_and_nonpromotion_are_exact() {
assert_eq!(S551_SEQUENCE, 551);
assert_eq!(S551_EXPECTED_PREDECESSOR, 550);
assert_eq!(S551_R1_STAGE, 2);
assert_eq!(S551_R1_RANGE_FIRST, 536);
assert_eq!(S551_R1_RANGE_LAST, 568);
assert_eq!(S551_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS, 0);
assert_eq!(S551_PHYSICAL_OBSERVATIONS, 0);
assert_eq!(S551_PHYSICAL_OR_DEVICE_OPERATIONS, 0);
assert_eq!(S551_SD_WRITES, 0);
assert_eq!(S551_UART_OPENS, 0);
assert_eq!(S551_POWER_TRANSITIONS, 0);
assert_eq!(S551_NEW_IMMUTABLE_RAW_CAPTURES, 0);
assert!(S551_S540_PHYSICAL_VERDICT_RETAINED_RED);
assert!(S551_S543_PHYSICAL_VERDICT_RETAINED_RED);
assert!(!S551_AUTOMATIC_PROMOTION);
assert!(!S551_BOOT_TO_UI_PHYSICALLY_OBSERVED);
assert!(!S551_HARDWARE_PRESENT);
assert!(!S551_R1_ACCEPTANCE_COMPLETE);
assert!(!RUNBOOK_EXECUTED_IN_S551);
}
#[test]
fn protocol_constants_match_the_gt911_register_map() {
assert_eq!(S551_GT911_I2C_ADDRESS_PRIMARY, 0x5D);
assert_eq!(S551_GT911_I2C_ADDRESS_SECONDARY, 0x14);
assert_eq!(S551_GT911_STATUS_REGISTER, 0x814E);
assert_eq!(S551_GT911_POINT_BASE_REGISTER, 0x814F);
assert_eq!(S551_GT911_POINT_BASE_REGISTER, S551_GT911_STATUS_REGISTER + 1);
assert_eq!(S551_GT911_STATUS_BUFFER_READY_BIT, 0x80);
assert_eq!(S551_GT911_STATUS_LARGE_DETECT_BIT, 0x40);
assert_eq!(S551_GT911_STATUS_TOUCH_COUNT_MASK, 0x0F);
assert_eq!(S551_GT911_STATUS_WRITE_BACK_VALUE, 0x00);
assert_eq!(S551_GT911_POINT_RECORD_BYTES, 8);
assert_eq!(S551_GT911_MAX_POINTS, 5);
assert_eq!(S551_GT911_MAX_REPORT_BYTES, 41);
assert_eq!(S551_GT911_CONFIG_FIRST_REGISTER, 0x8047);
assert_eq!(S551_GT911_CONFIG_LAST_REGISTER, 0x80FE);
assert_eq!(S551_GT911_CONFIG_CHECKSUM_REGISTER, 0x80FF);
assert_eq!(S551_GT911_CONFIG_BYTES, 184);
assert_eq!(S551_GT911_PRODUCT_ID_REGISTER, 0x8140);
assert_eq!(&S551_GT911_PRODUCT_ID[..3], b"911");
assert_eq!(S551_GT911_PRODUCT_ID[3], 0);
assert_eq!(S551_PANEL_WIDTH, 720);
assert_eq!(S551_PANEL_HEIGHT, 1280);
assert_eq!(S551_MAX_X, 719);
assert_eq!(S551_MAX_Y, 1279);
assert!(gt911_i2c_address_is_valid(0x5D));
assert!(gt911_i2c_address_is_valid(0x14));
assert!(!gt911_i2c_address_is_valid(0x5E));
}
#[test]
fn canonical_config_checksum_is_an_eight_bit_sum_complement() {
for swap_xy in [false, true] {
let (config, checksum) = config_and_checksum(swap_xy);
let sum: u32 = config.iter().map(|byte| *byte as u32).sum::<u32>() + checksum as u32;
assert_eq!(sum % 256, 0);
assert_eq!(config[0], 0x41);
assert_eq!(u16::from_le_bytes([config[1], config[2]]), 720);
assert_eq!(u16::from_le_bytes([config[3], config[4]]), 1280);
assert_eq!(config[5], 5);
assert_eq!(config[6] & 0x08 != 0, swap_xy);
let summary = validate_gt911_config(&config, checksum).unwrap();
assert_eq!(summary.checksum, checksum);
assert_eq!(summary.x_max, 720);
assert_eq!(summary.y_max, 1280);
assert_eq!(summary.touch_number, 5);
assert_eq!(summary.swap_xy, swap_xy);
}
// Table-driven spot checks of the complement arithmetic.
let zeros = [0u8; S551_GT911_CONFIG_BYTES];
assert_eq!(compute_gt911_config_checksum(&zeros), Ok(0x00));
let mut one = zeros;
one[0] = 0x01;
assert_eq!(compute_gt911_config_checksum(&one), Ok(0xFF));
let all_ff = [0xFFu8; S551_GT911_CONFIG_BYTES];
// 184 * 255 = 46920 = 0xB748 -> low byte 0x48 -> complement 0xB8.
assert_eq!(compute_gt911_config_checksum(&all_ff), Ok(0xB8));
assert_eq!(
compute_gt911_config_checksum(&zeros[..183]),
Err(G8lS551TouchI2cReportDecoderError::ConfigLengthMismatch)
);
}
#[test]
fn single_point_report_decodes_and_write_back_clears_status() {
let mut state = G8lS551TouchI2cReportDecoderState::new();
assert_eq!(state.status_register_shadow(), 0);
assert_eq!(state.write_backs(), 0);
let wire = single_point_report();
assert_eq!(wire.len(), 9);
assert_eq!(wire[0], 0x81);
let G8lS551TouchI2cReportDecoderOutcome::FrameDecoded(receipt) =
decode(&mut state, IDENTITY, &wire).unwrap()
else {
panic!("first S551 frame must be decoded")
};
assert_eq!(state.receipt(), Some(receipt));
assert_eq!(state.status_register_shadow(), 0x00);
assert_eq!(state.write_backs(), 1);
assert_eq!(receipt.sequence, 551);
assert_eq!(receipt.predecessor_sequence, 550);
assert_eq!(receipt.r1_stage, 2);
assert_eq!(receipt.i2c_address, 0x5D);
assert_eq!(receipt.product_id, *b"911\0");
assert_eq!(receipt.config_bytes, 184);
assert_eq!(receipt.config_x_max, 720);
assert_eq!(receipt.config_y_max, 1280);
assert_eq!(receipt.config_touch_number, 5);
assert_eq!(receipt.status_register, 0x814E);
assert_eq!(receipt.status_before, 0x81);
assert_eq!(receipt.status_after_write_back, 0x00);
assert_eq!(receipt.report_bytes, 9);
assert_eq!(receipt.frame.count, 1);
assert!(!receipt.frame.large_detect);
assert_eq!(
receipt.frame.active_points(),
&[G8lS551TouchPoint {
track_id: 0,
x: 100,
y: 200,
size: 12
}]
);
assert_eq!(receipt.frame.points[1], G8lS551TouchPoint::default());
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);
}
#[test]
fn five_point_report_decodes_all_records_in_track_order() {
let points = [
raw(0, 0, 0, 1),
raw(1, 719, 0, 2),
raw(2, 0, 1279, 3),
raw(5, 719, 1279, 4),
raw(9, 360, 640, 0xFFFF),
];
let wire = report(&points);
assert_eq!(wire.len(), 41);
assert_eq!(wire[0], 0x85);
let frame = decode_gt911_report(&wire, IDENTITY).unwrap();
assert_eq!(frame.count, 5);
assert_eq!(frame.active_points().len(), 5);
for (index, point) in points.iter().enumerate() {
let decoded = frame.points[index];
assert_eq!(decoded.track_id, point.track_id);
assert_eq!(decoded.x, point.x);
assert_eq!(decoded.y, point.y);
assert_eq!(decoded.size, point.size);
}
let mut state = G8lS551TouchI2cReportDecoderState::new();
let G8lS551TouchI2cReportDecoderOutcome::FrameDecoded(receipt) =
decode(&mut state, IDENTITY, &wire).unwrap()
else {
panic!("five-point frame must be decoded")
};
assert_eq!(receipt.frame, frame);
assert_eq!(receipt.report_bytes, 41);
}
#[test]
fn zero_point_release_report_decodes_an_empty_frame() {
let wire = report(&[]);
assert_eq!(wire, vec![0x80]);
let frame = decode_gt911_report(&wire, IDENTITY).unwrap();
assert_eq!(frame.count, 0);
assert!(frame.active_points().is_empty());
assert_eq!(frame, G8lS551TouchFrame::default());
let mut state = G8lS551TouchI2cReportDecoderState::new();
assert!(matches!(
decode(&mut state, IDENTITY, &wire),
Ok(G8lS551TouchI2cReportDecoderOutcome::FrameDecoded(_))
));
assert_eq!(state.status_register_shadow(), 0);
}
#[test]
fn exact_replay_retains_the_same_receipt() {
let mut state = G8lS551TouchI2cReportDecoderState::new();
let wire = single_point_report();
let G8lS551TouchI2cReportDecoderOutcome::FrameDecoded(receipt) =
decode(&mut state, IDENTITY, &wire).unwrap()
else {
panic!("first publication missing")
};
assert_eq!(
decode(&mut state, IDENTITY, &wire),
Ok(G8lS551TouchI2cReportDecoderOutcome::FrameRetained(receipt))
);
assert_eq!(state.write_backs(), 1);
assert_eq!(state.receipt(), Some(receipt));
}
#[test]
fn divergent_input_after_publication_fails_closed() {
let mut state = G8lS551TouchI2cReportDecoderState::new();
decode(&mut state, IDENTITY, &single_point_report()).unwrap();
let moved = report(&[raw(0, 101, 200, 12)]);
assert_eq!(
decode(&mut state, IDENTITY, &moved),
Err(G8lS551TouchI2cReportDecoderError::PublishedStateDrift)
);
let inverted = G8lS551AxisTransform {
invert_x: true,
..IDENTITY
};
assert_eq!(
decode(&mut state, inverted, &single_point_report()),
Err(G8lS551TouchI2cReportDecoderError::PublishedStateDrift)
);
let (config, checksum) = config_and_checksum(false);
assert_eq!(
service_s551_model_decode_touch_report(
&mut state,
S551_GT911_I2C_ADDRESS_SECONDARY,
S551_GT911_PRODUCT_ID,
&config,
checksum,
IDENTITY,
&single_point_report(),
),
Err(G8lS551TouchI2cReportDecoderError::PublishedStateDrift)
);
assert_eq!(state.write_backs(), 1);
}
#[test]
fn buffer_not_ready_status_is_rejected() {
let not_ready = encode_gt911_report(&[raw(0, 1, 1, 1)], false, false).unwrap();
assert_eq!(not_ready[0], 0x01);
assert_eq!(
decode_gt911_report(¬_ready, IDENTITY),
Err(G8lS551TouchI2cReportDecoderError::BufferNotReady)
);
assert_eq!(
decode_gt911_report(&[0x00], IDENTITY),
Err(G8lS551TouchI2cReportDecoderError::BufferNotReady)
);
let mut state = G8lS551TouchI2cReportDecoderState::new();
assert_eq!(
decode(&mut state, IDENTITY, ¬_ready),
Err(G8lS551TouchI2cReportDecoderError::BufferNotReady)
);
assert_eq!(state.receipt(), None);
assert_eq!(state.write_backs(), 0);
}
#[test]
fn touch_count_above_five_is_rejected() {
for count in 6u8..=15 {
let mut wire = vec![0x80 | count];
wire.extend(std::iter::repeat(0u8).take(count as usize * 8));
assert_eq!(
decode_gt911_report(&wire, IDENTITY),
Err(G8lS551TouchI2cReportDecoderError::TouchCountOverflow),
"count {count}"
);
}
let six = [raw(0, 0, 0, 0); 6];
assert_eq!(
encode_gt911_report(&six, true, false),
Err(G8lS551TouchI2cReportDecoderError::TouchCountOverflow)
);
}
#[test]
fn truncated_and_oversized_frames_are_rejected() {
assert_eq!(
decode_gt911_report(&[], IDENTITY),
Err(G8lS551TouchI2cReportDecoderError::TruncatedFrame)
);
let two = report(&[raw(0, 1, 2, 3), raw(1, 4, 5, 6)]);
assert_eq!(two.len(), 17);
for cut in 1..two.len() {
assert_eq!(
decode_gt911_report(&two[..cut], IDENTITY),
Err(G8lS551TouchI2cReportDecoderError::TruncatedFrame),
"cut {cut}"
);
}
let mut oversized = two.clone();
oversized.push(0x00);
assert_eq!(
decode_gt911_report(&oversized, IDENTITY),
Err(G8lS551TouchI2cReportDecoderError::FrameLengthMismatch)
);
let mut full_block = two.clone();
full_block.resize(S551_GT911_MAX_REPORT_BYTES, 0);
assert_eq!(
decode_gt911_report(&full_block, IDENTITY),
Err(G8lS551TouchI2cReportDecoderError::FrameLengthMismatch)
);
let mut state = G8lS551TouchI2cReportDecoderState::new();
assert_eq!(
decode(&mut state, IDENTITY, &two[..9]),
Err(G8lS551TouchI2cReportDecoderError::TruncatedFrame)
);
assert_eq!(state.receipt(), None);
}
#[test]
fn out_of_range_coordinates_are_rejected_at_the_exact_boundary() {
assert!(decode_gt911_report(&report(&[raw(0, 719, 1279, 1)]), IDENTITY).is_ok());
for bad in [
raw(0, 720, 0, 1),
raw(0, 0, 1280, 1),
raw(0, 720, 1280, 1),
raw(0, 0xFFFF, 0, 1),
raw(0, 0, 0xFFFF, 1),
] {
assert_eq!(
decode_gt911_report(&report(&[bad]), IDENTITY),
Err(G8lS551TouchI2cReportDecoderError::CoordinateOutOfRange),
"{bad:?}"
);
}
// A later bad record still fails the whole frame.
let mixed = report(&[raw(0, 1, 1, 1), raw(1, 2, 2, 2), raw(2, 719, 1280, 3)]);
assert_eq!(
decode_gt911_report(&mixed, IDENTITY),
Err(G8lS551TouchI2cReportDecoderError::CoordinateOutOfRange)
);
let mut state = G8lS551TouchI2cReportDecoderState::new();
assert_eq!(
decode(&mut state, IDENTITY, &report(&[raw(0, 720, 0, 1)])),
Err(G8lS551TouchI2cReportDecoderError::CoordinateOutOfRange)
);
assert_eq!(state.receipt(), None);
}
#[test]
fn axis_swap_and_invert_transform_coordinates() {
let swap = G8lS551AxisTransform {
swap_xy: true,
invert_x: false,
invert_y: false,
};
// Controller-native (x=1279, y=719) becomes panel (719, 1279).
let swapped = transform_gt911_point(raw(3, 1279, 719, 7), swap).unwrap();
assert_eq!((swapped.x, swapped.y, swapped.track_id, swapped.size), (719, 1279, 3, 7));
// Without the swap the same raw point is out of range.
assert_eq!(
transform_gt911_point(raw(3, 1279, 719, 7), IDENTITY),
Err(G8lS551TouchI2cReportDecoderError::CoordinateOutOfRange)
);
// Range is checked after the swap, before inversion.
assert_eq!(
transform_gt911_point(raw(0, 1280, 0, 0), swap),
Err(G8lS551TouchI2cReportDecoderError::CoordinateOutOfRange)
);
let invert_both = G8lS551AxisTransform {
swap_xy: false,
invert_x: true,
invert_y: true,
};
let inverted = transform_gt911_point(raw(0, 0, 0, 0), invert_both).unwrap();
assert_eq!((inverted.x, inverted.y), (719, 1279));
let inverted = transform_gt911_point(raw(0, 719, 1279, 0), invert_both).unwrap();
assert_eq!((inverted.x, inverted.y), (0, 0));
let inverted = transform_gt911_point(raw(0, 100, 200, 0), invert_both).unwrap();
assert_eq!((inverted.x, inverted.y), (619, 1079));
let swap_invert_x = G8lS551AxisTransform {
swap_xy: true,
invert_x: true,
invert_y: false,
};
let point = transform_gt911_point(raw(0, 1000, 20, 0), swap_invert_x).unwrap();
assert_eq!((point.x, point.y), (699, 1000));
// The full service accepts the swap only when the config X2Y bit agrees.
let mut state = G8lS551TouchI2cReportDecoderState::new();
let G8lS551TouchI2cReportDecoderOutcome::FrameDecoded(receipt) =
decode(&mut state, swap, &report(&[raw(0, 1279, 719, 1)])).unwrap()
else {
panic!("swapped frame must decode")
};
assert_eq!(receipt.transform, swap);
assert_eq!((receipt.frame.points[0].x, receipt.frame.points[0].y), (719, 1279));
}
#[test]
fn axis_swap_must_match_the_config_x2y_bit() {
let swap = G8lS551AxisTransform {
swap_xy: true,
invert_x: false,
invert_y: false,
};
let (config_no_swap, checksum_no_swap) = config_and_checksum(false);
let (config_swap, checksum_swap) = config_and_checksum(true);
assert_ne!(checksum_no_swap, checksum_swap);
let mut state = G8lS551TouchI2cReportDecoderState::new();
assert_eq!(
service_s551_model_decode_touch_report(
&mut state,
0x5D,
S551_GT911_PRODUCT_ID,
&config_no_swap,
checksum_no_swap,
swap,
&single_point_report(),
),
Err(G8lS551TouchI2cReportDecoderError::AxisSwapConfigMismatch)
);
assert_eq!(
service_s551_model_decode_touch_report(
&mut state,
0x5D,
S551_GT911_PRODUCT_ID,
&config_swap,
checksum_swap,
IDENTITY,
&single_point_report(),
),
Err(G8lS551TouchI2cReportDecoderError::AxisSwapConfigMismatch)
);
assert_eq!(state.receipt(), None);
}
#[test]
fn bad_config_checksum_length_version_resolution_and_touch_number_are_rejected() {
let (config, checksum) = config_and_checksum(false);
let mut state = G8lS551TouchI2cReportDecoderState::new();
let wire = single_point_report();
let attempt = |state: &mut G8lS551TouchI2cReportDecoderState, config: &[u8], checksum: u8| {
service_s551_model_decode_touch_report(
state,
0x5D,
S551_GT911_PRODUCT_ID,
config,
checksum,
IDENTITY,
&wire,
)
};
for wrong in [checksum.wrapping_add(1), checksum ^ 0x80, 0x00] {
if wrong == checksum {
continue;
}
assert_eq!(
attempt(&mut state, &config, wrong),
Err(G8lS551TouchI2cReportDecoderError::ConfigChecksumMismatch)
);
}
let mut corrupted = config;
corrupted[0x40] ^= 0x01;
assert_eq!(
attempt(&mut state, &corrupted, checksum),
Err(G8lS551TouchI2cReportDecoderError::ConfigChecksumMismatch)
);
assert_eq!(
attempt(&mut state, &config[..183], checksum),
Err(G8lS551TouchI2cReportDecoderError::ConfigLengthMismatch)
);
let mut longer = config.to_vec();
longer.push(0);
assert_eq!(
attempt(&mut state, &longer, checksum),
Err(G8lS551TouchI2cReportDecoderError::ConfigLengthMismatch)
);
let mut version = config;
version[S551_GT911_CONFIG_OFFSET_VERSION] = 0x42;
let version_checksum = compute_gt911_config_checksum(&version).unwrap();
assert_eq!(
attempt(&mut state, &version, version_checksum),
Err(G8lS551TouchI2cReportDecoderError::ConfigVersionMismatch)
);
let mut resolution = config;
resolution[S551_GT911_CONFIG_OFFSET_X_MAX_LO] = 0x00;
resolution[S551_GT911_CONFIG_OFFSET_X_MAX_HI] = 0x04;
let resolution_checksum = compute_gt911_config_checksum(&resolution).unwrap();
assert_eq!(
attempt(&mut state, &resolution, resolution_checksum),
Err(G8lS551TouchI2cReportDecoderError::ConfigResolutionMismatch)
);
let mut touches = config;
touches[S551_GT911_CONFIG_OFFSET_TOUCH_NUMBER] = 10;
let touches_checksum = compute_gt911_config_checksum(&touches).unwrap();
assert_eq!(
attempt(&mut state, &touches, touches_checksum),
Err(G8lS551TouchI2cReportDecoderError::ConfigTouchNumberMismatch)
);
assert_eq!(state.receipt(), None);
assert_eq!(state.write_backs(), 0);
}
#[test]
fn wrong_i2c_address_and_product_id_are_rejected() {
let (config, checksum) = config_and_checksum(false);
let wire = single_point_report();
let mut state = G8lS551TouchI2cReportDecoderState::new();
for address in [0x00, 0x15, 0x5C, 0x5E, 0x7F, 0xFF] {
assert_eq!(
service_s551_model_decode_touch_report(
&mut state,
address,
S551_GT911_PRODUCT_ID,
&config,
checksum,
IDENTITY,
&wire,
),
Err(G8lS551TouchI2cReportDecoderError::InvalidI2cAddress),
"address {address:#04x}"
);
}
for product in [*b"912\0", *b"911 ", *b"\0911", *b"9110", *b"\0\0\0\0"] {
assert_eq!(
service_s551_model_decode_touch_report(
&mut state,
0x14,
product,
&config,
checksum,
IDENTITY,
&wire,
),
Err(G8lS551TouchI2cReportDecoderError::ProductIdMismatch),
"{product:?}"
);
}
assert_eq!(state.receipt(), None);
let G8lS551TouchI2cReportDecoderOutcome::FrameDecoded(receipt) =
service_s551_model_decode_touch_report(
&mut state,
S551_GT911_I2C_ADDRESS_SECONDARY,
S551_GT911_PRODUCT_ID,
&config,
checksum,
IDENTITY,
&wire,
)
.unwrap()
else {
panic!("secondary address must decode")
};
assert_eq!(receipt.i2c_address, 0x14);
}
#[test]
fn track_id_ordering_and_range_are_enforced() {
assert!(decode_gt911_report(&report(&[raw(9, 1, 1, 1)]), IDENTITY).is_ok());
assert_eq!(
decode_gt911_report(&report(&[raw(10, 1, 1, 1)]), IDENTITY),
Err(G8lS551TouchI2cReportDecoderError::TrackIdOutOfRange)
);
assert_eq!(
decode_gt911_report(&report(&[raw(0xFF, 1, 1, 1)]), IDENTITY),
Err(G8lS551TouchI2cReportDecoderError::TrackIdOutOfRange)
);
assert_eq!(
decode_gt911_report(&report(&[raw(1, 1, 1, 1), raw(0, 2, 2, 2)]), IDENTITY),
Err(G8lS551TouchI2cReportDecoderError::TrackIdNotAscending)
);
assert_eq!(
decode_gt911_report(&report(&[raw(2, 1, 1, 1), raw(2, 2, 2, 2)]), IDENTITY),
Err(G8lS551TouchI2cReportDecoderError::TrackIdNotAscending)
);
let sparse = decode_gt911_report(
&report(&[raw(1, 1, 1, 1), raw(4, 2, 2, 2), raw(8, 3, 3, 3)]),
IDENTITY,
)
.unwrap();
assert_eq!(
sparse
.active_points()
.iter()
.map(|point| point.track_id)
.collect::<Vec<_>>(),
vec![1, 4, 8]
);
}
#[test]
fn reserved_byte_and_large_detect_bit_are_modelled() {
let mut wire = single_point_report();
wire[8] = 0x01;
assert_eq!(
decode_gt911_report(&wire, IDENTITY),
Err(G8lS551TouchI2cReportDecoderError::ReservedByteNonZero)
);
let large = encode_gt911_report(&[raw(0, 5, 6, 700)], true, true).unwrap();
assert_eq!(large[0], 0xC1);
let frame = decode_gt911_report(&large, IDENTITY).unwrap();
assert!(frame.large_detect);
assert_eq!(frame.count, 1);
assert_eq!(frame.points[0].size, 700);
let mut state = G8lS551TouchI2cReportDecoderState::new();
let G8lS551TouchI2cReportDecoderOutcome::FrameDecoded(receipt) =
decode(&mut state, IDENTITY, &large).unwrap()
else {
panic!("large-detect frame must decode")
};
assert_eq!(receipt.status_before, 0xC1);
assert_eq!(receipt.status_after_write_back, 0x00);
assert!(receipt.frame.large_detect);
// Bits 4 and 5 are not part of the modelled status word and are ignored
// only if they do not alter count/ready/large semantics.
let mut unmodelled = single_point_report();
unmodelled[0] = 0x81 | 0x30;
let frame = decode_gt911_report(&unmodelled, IDENTITY).unwrap();
assert_eq!(frame.count, 1);
assert!(!frame.large_detect);
}
#[test]
fn encoder_and_decoder_round_trip_exactly() {
let point = raw(4, 0x02CF, 0x04FF, 0x1234);
assert_eq!(
encode_gt911_point(point),
[0x04, 0xCF, 0x02, 0xFF, 0x04, 0x34, 0x12, 0x00]
);
let points = [raw(0, 10, 20, 30), raw(3, 400, 1000, 5), raw(7, 719, 1279, 65535)];
let wire = report(&points);
assert_eq!(wire.len(), 1 + 3 * 8);
let frame = decode_gt911_report(&wire, IDENTITY).unwrap();
let decoded: Vec<G8lS551RawTouchPoint> = frame
.active_points()
.iter()
.map(|point| raw(point.track_id, point.x, point.y, point.size))
.collect();
assert_eq!(decoded, points.to_vec());
let re_encoded = encode_gt911_report(&decoded, true, false).unwrap();
assert_eq!(re_encoded, wire);
}
#[test]
fn diagnostic_codes_are_nonzero_and_unique() {
let errors = [
G8lS551TouchI2cReportDecoderError::InvalidI2cAddress,
G8lS551TouchI2cReportDecoderError::ProductIdMismatch,
G8lS551TouchI2cReportDecoderError::ConfigLengthMismatch,
G8lS551TouchI2cReportDecoderError::ConfigChecksumMismatch,
G8lS551TouchI2cReportDecoderError::ConfigVersionMismatch,
G8lS551TouchI2cReportDecoderError::ConfigResolutionMismatch,
G8lS551TouchI2cReportDecoderError::ConfigTouchNumberMismatch,
G8lS551TouchI2cReportDecoderError::AxisSwapConfigMismatch,
G8lS551TouchI2cReportDecoderError::TruncatedFrame,
G8lS551TouchI2cReportDecoderError::FrameLengthMismatch,
G8lS551TouchI2cReportDecoderError::BufferNotReady,
G8lS551TouchI2cReportDecoderError::TouchCountOverflow,
G8lS551TouchI2cReportDecoderError::TrackIdOutOfRange,
G8lS551TouchI2cReportDecoderError::TrackIdNotAscending,
G8lS551TouchI2cReportDecoderError::ReservedByteNonZero,
G8lS551TouchI2cReportDecoderError::CoordinateOutOfRange,
G8lS551TouchI2cReportDecoderError::ArithmeticOverflow,
G8lS551TouchI2cReportDecoderError::PublishedStateDrift,
];
let codes: BTreeSet<_> = errors
.into_iter()
.map(G8lS551TouchI2cReportDecoderError::diagnostic_code)
.collect();
assert_eq!(codes.len(), errors.len());
assert_eq!(codes.len(), 18);
assert!(!codes.contains(&0));
}
#[test]
fn module_is_registered_in_kernel_and_simulation() {
let module = "g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s551_r1_gt911_touch_i2c_report_decoder_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",
"read_volatile",
"crate::uart",
"crate::arch",
"#[no_mangle]",
"spin::",
"std::",
"/dev/",
"kprintln!",
] {
assert!(!SOURCE.contains(forbidden), "forbidden token: {forbidden}");
}
assert!(SOURCE.contains("No I2C controller, no touch panel"));
assert!(SOURCE.contains("nothing here performs a device operation"));
}
#[test]
fn source_only_gate_keeps_runtime_physical_and_r1_claims_zero() {
assert!(SOURCE.contains("S551_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0"));
assert!(SOURCE.contains("S551_PHYSICAL_OBSERVATIONS: usize = 0"));
assert!(SOURCE.contains("S551_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0"));
assert!(SOURCE.contains("S551_HARDWARE_PRESENT: bool = false"));
assert!(SOURCE.contains("S551_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false"));
assert!(SOURCE.contains("S551_R1_ACCEPTANCE_COMPLETE: bool = false"));
assert!(SOURCE.contains("RUNBOOK_EXECUTED_IN_S551: bool = false"));
assert!(SOURCE.contains("S551_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true"));
assert!(SOURCE.contains("S551_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true"));
}
snippet sha256: 82093b5f1e0b…file sha256: 82093b5f1e0b…
03 · Kapı kimlik kaydı
Operations sıra, kimlik ve başlık bağı
tam Operations kaydıL2841–L2901
website/src/lib/operations.ts::g8l-s551-r1-gt911-touch-i2c-report-decoder-model
{
id: "g8l-s551-r1-gt911-touch-i2c-report-decoder-model",
date: "2026-08-30",
sequence: 551,
status: "passed",
umbrella_status: "partial",
title: "S551 · R1 ekran: Goodix GT911 dokunmatik I2C rapor çözücü modeli",
summary:
"S551 kaynak/host model kapısı PASS'tir: Goodix GT911 dokunmatik denetleyicinin I2C rapor protokolü (adres 0x5D/0x14, 0x814E durum yazmacı, 0x814F'ten başlayan 8 baytlık nokta kayıtları, 0x8047–0x80FE konfigürasyon bloğu ve 0x80FF toplam-tümleyen sağlaması, 0x8140 ürün kimliği '911') saf çözücü mantığı olarak modellendi. Çözücü bayt diliminden en fazla 5 noktalı TouchFrame üretir, buffer-ready bitini zorunlu kılar, eksen takas/ters çevirme dönüşümünü uygular ve 0x814E yazma-geri modelini 0'a temizler; count>5, aralık dışı koordinat, bozuk sağlama, kesik veya fazla uzun çerçeve fail-closed reddedilir. Focused 23/23 PASS'tir. S540 ve S543 fiziksel raw/verdict değişmez RED kalır; supported-profile runtime/physical observation=0/0, hiçbir I2C denetleyici, dokunmatik panel veya board mevcut değildir, Boot-to-UI=false ve R1 acceptance=false'dur. RUNBOOK_EXECUTED_IN_S551=NO. S552 çözülmüş dokunma çerçevelerini tüketen input focus ve touch event routing modelidir.",
evidence: [
"S551, S550'den ayrı source model module, 23-test focused binary, proof, status manifest, Operations kaydı ve complete Code kartına sahiptir; hiçbir production callsite'a bağlanmamıştır.",
"Dar S551 source-model status=PASS; R1 umbrella=PARTIAL ve S540/S543 physical gate status=RED olarak ayrı tutulur.",
"Model GT911 I2C 7-bit adreslerini 0x5D (primary) ve 0x14 (secondary) olarak tanımlar; başka her adres InvalidI2cAddress ile reddedilir.",
"0x814E durum yazmacı bit 7 buffer-ready, bit 6 large-detect ve bit 0..3 dokunma sayısı olarak çözülür; buffer-ready biti temizse BufferNotReady döner ve receipt üretilmez.",
"Nokta kayıtları 0x814F'ten itibaren 8 bayttır: track id, x lo/hi, y lo/hi, size lo/hi ve sıfır olması zorunlu reserved bayt; en fazla 5 nokta ve 41 baytlık rapor kabul edilir.",
"Dokunma sayısı 6..15 TouchCountOverflow, çerçeve uzunluğu 1+count*8'den kısa ise TruncatedFrame, uzun ise FrameLengthMismatch ile fail-closed reddedilir.",
"Koordinat aralığı eksen takasından sonra ve ters çevirmeden önce x 0..719 / y 0..1279 olarak denetlenir; 720/1280 ve 0xFFFF değerleri CoordinateOutOfRange döner, 719/1279 sınırı kabul edilir.",
"Eksen dönüşümü swap_xy, invert_x ve invert_y bayraklarını uygular; swap_xy konfigürasyon Module_Switch1 X2Y biti ile uyuşmazsa AxisSwapConfigMismatch döner.",
"Konfigürasyon bloğu 0x8047..0x80FE (184 bayt) üzerinde 8-bit toplam-tümleyen sağlaması checked u32 toplama ile hesaplanır; 0x80FF ile uyuşmazlık, yanlış uzunluk, sürüm, çözünürlük veya touch number ayrı kodlarla reddedilir.",
"Ürün kimliği 0x8140'ta ASCII '911' + NUL olarak zorunludur; track id 0..9 aralığında ve çerçeve içinde kesin artan sırada olmalıdır.",
"Yazma-geri modeli çerçeve tüketildikten sonra 0x814E gölgesini 0x00'a temizler ve write-back sayacını bir artırır; exact replay FrameRetained döner ve ikinci yazma-geri yapmaz.",
"Yayından sonra farklı rapor, dönüşüm veya adres PublishedStateDrift ile reddedilir; 18 hata kodu sıfırdan farklı ve tekildir.",
"Kodlayıcı (encode_gt911_point/encode_gt911_report) ile çözücü byte-exact round-trip verir.",
"Focused target 1 grup / 23 passed / 0 failed / 0 ignored / 0 filtered verdi.",
"Implementation 20474 B / f55ae2ccc19c6c623e246824b1456c9583bc123208930076b477e21959674d01; focused test 28049 B / 82093b5f1e0b89cc8cf7bcfd2d3a59bef25174a0aad843a128800d96ea6f6e43 SHA-256'dır.",
"Proof 4952 B'dır.",
"Kaynakta unsafe, asm!, write_volatile, crate::uart, crate::arch, spin:: veya #[no_mangle] yüzeyi yoktur; modül boot, IRQ, scheduler veya driver yoluna bağlı değildir.",
"S551 sırasında I2C işlemi, dokunmatik panel gözlemi, SD write/read-back/eject, UART open/capture, power transition, physical retry veya yeni immutable raw üretimi yapılmadı.",
"RUNBOOK_EXECUTED_IN_S551=NO; supported-profile runtime observations=0, physical observations=0, hardware present=false, Boot-to-UI physically observed=false ve R1 acceptance=false'dur.",
"S552 yalnız host üzerinde çözülmüş G8lS551TouchFrame değerlerini tüketen input focus ve touch event routing modelidir; I2C, panel 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_s551_r1_gt911_touch_i2c_report_decoder_model -- --test-threads=1",
],
terminalSessions: [
{
id: "s551-focused",
title: "S551 GT911 dokunmatik I2C rapor çözücü modeli focused",
commandLines: [
"CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s551_r1_gt911_touch_i2c_report_decoder_model -- --test-threads=1",
],
outputLines: [
"test result: ok. 23 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s",
"S551 focused=1 group / 23 passed / 0 failed",
"hardware=none physical=0 runbook=NO",
],
exitCode: 0,
outputMode: "complete",
},
],
terminalSessionsNote:
"S551 kaynak/host model PASS'tir; I2C, dokunmatik panel veya fiziksel PASS değildir. S540 ve S543 RED raw ve kararları değişmez.",
limitations: [
"S551 yalnız GT911 rapor protokolünün saf çözücü modelidir; gerçek I2C transferi, INT pini veya dokunmatik panel davranışı gözlenmemiştir.",
"S540 ve S543 fiziksel RED immutable kalır; hiçbir donanım/panel/modem/board gözlemi yoktur.",
"Modül hiçbir production callsite'a, boot, IRQ, scheduler veya driver yoluna bağlı değildir; supported RPi5 runtime invocation yoktur.",
"Konfigürasyon bloğunun yalnız sürüm, çözünürlük, touch number ve X2Y biti anlamlandırılır; kalan baytlar yalnız sağlamaya girer.",
"Boot-to-UI gerçek UART'ta görülmedi; Boot-to-UI ve R1 acceptance false kalır.",
"S552 çözülmüş dokunma çerçevelerini tüketen input focus ve touch event routing modelidir; yeni SD/UART/power/I2C koşusu ayrı kapı, fresh target revalidation, açık operatör yetkisi ve yeni immutable raw ister.",
],
},snippet sha256: 58faf23e0512…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_s551_r1_gt911_touch_i2c_report_decoder_model -- --test-threads=1proof: docs/M8.1-RPi5-G8l-S551-R1-GT911-Touch-I2C-Report-Decoder-Model-Proof.md
Registry schema v5 · generator
website/scripts/generate-code-gates.mjs · Tam SHA-256: 91d38c7b6222f0b4c117be786454853543da55a160e543d9b951057cc20dcc06