[RFC PATCH v2 04/10] drm/vino: add the Vino framebuffer codec

From: Mike Lothian

Date: Thu Jul 02 2026 - 23:12:58 EST


video.rs implements the dock's video transport: an RLE fallback
encoder (shadow-diffed against the previous frame) plus the
Walsh-Hadamard "Vino" colour codec reverse-engineered from the
reference daemon's encoder output -- the 8x8 transform, DC-plane
entropy coding and colour (Y/Cb/Cr) handling are byte-exact against
captured hardware output; the AC coefficient entropy grammar is only
partially recovered (EOB/scan/context still open), so the WHT path is
only used for aligned geometry and falls back to RLE otherwise. Also
builds the EP08 wire framing (DLM's per-strip TLV record layout).

Signed-off-by: Mike Lothian <mike@xxxxxxxxxxxxxx>
Assisted-by: Claude:claude-sonnet-5 [Claude-Code]
---
drivers/gpu/drm/vino/video.rs | 1229 +++++++++++++++++++++++++++++++++
1 file changed, 1229 insertions(+)
create mode 100644 drivers/gpu/drm/vino/video.rs

diff --git a/drivers/gpu/drm/vino/video.rs b/drivers/gpu/drm/vino/video.rs
new file mode 100644
index 000000000000..c520af19a4fb
--- /dev/null
+++ b/drivers/gpu/drm/vino/video.rs
@@ -0,0 +1,1229 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! RawRl (Raw/RLX) **mode-2 video encoder** -- clean-room from the AArch64 DLM
+//! decompile (sec 8.4 + `docs/decompile/arm64-blockencoder`/`-frame-markers`).
+//! Emits packed-RGB565 frames the dock decodes WITHOUT the impractical Vino
+//! Walsh-Hadamard entropy codec (sec 7.11). This is a **verbatim port** of the
+//! `vino-codec::rawrl` oracle, whose encode/decode round-trip is unit-tested
+//! (keyframe, differential, >256-pixel multi-block and >255 RLE run-splits all
+//! reconstruct byte-exact); keep the two in lockstep. No real mode-2 capture
+//! exists to diff against (sec 7.4), so that round-trip is the correctness anchor.
+//! NOT yet wired into `probe()`: sending a frame the dock rejects USB-resets the
+//! dock, so EP08 streaming is a supervised bring-up step.
+#![allow(dead_code)] // Encoder/Mode variants validated by KUnit; live scanout uses the RLE path
+
+use super::*;
+
+pub(crate) const MAGIC_RAW16: u16 = 0x68af;
+pub(crate) const MAGIC_RLE16: u16 = 0x69af;
+/// Frame-init `0x40af` (`FUN_003330fc`: u32 `0xaf0440af` + u16 `0x0840`).
+pub(crate) const FRAME_INIT: [u8; 6] = [0xaf, 0x40, 0x04, 0xaf, 0x40, 0x08];
+/// Bare `0xa0af` sync (`FUN_00332a38`).
+pub(crate) const SYNC: [u8; 2] = [0xaf, 0xa0];
+/// Frame-end section->code table `DAT_005b7860`, indexed by `mode - 1`.
+pub(crate) const SECTION_CODE: [u8; 7] = [0x01, 0x00, 0x03, 0x00, 0x05, 0x07, 0x07];
+pub(crate) const MAX_BLOCK_PIXELS: usize = 256;
+
+/// Per-run strategy: mode 0 raw-only, 1 RLE-only, 2 adaptive (sec 8.4).
+#[derive(Clone, Copy)]
+pub(crate) enum Mode {
+ Raw = 0,
+ Rle = 1,
+ Adaptive = 2,
+}
+
+/// Pack 8-bit RGB into RGB565 (the XRGB framebuffer reduced for the
+/// `0x68af`/`0x69af` path).
+pub(crate) fn rgb565(r: u8, g: u8, b: u8) -> u16 {
+ ((r as u16 >> 3) << 11) | ((g as u16 >> 2) << 5) | (b as u16 >> 3)
+}
+
+/// 6-byte block header: magic LE, 24-bit coord BE, count u8 (256 -> 0).
+fn block_header(out: &mut KVec<u8>, magic: u16, coord: u32, count: usize) -> Result {
+ out.extend_from_slice(&magic.to_le_bytes(), GFP_KERNEL)?;
+ out.push(((coord >> 16) & 0xff) as u8, GFP_KERNEL)?;
+ out.push(((coord >> 8) & 0xff) as u8, GFP_KERNEL)?;
+ out.push((coord & 0xff) as u8, GFP_KERNEL)?;
+ out.push((count & 0xff) as u8, GFP_KERNEL)?;
+ Ok(())
+}
+
+fn encode_raw_into(out: &mut KVec<u8>, coord: u32, pix: &[u16]) -> Result {
+ block_header(out, MAGIC_RAW16, coord, pix.len())?;
+ for &p in pix {
+ out.extend_from_slice(&p.to_be_bytes(), GFP_KERNEL)?;
+ }
+ Ok(())
+}
+
+fn encode_rle_into(out: &mut KVec<u8>, coord: u32, pix: &[u16]) -> Result {
+ block_header(out, MAGIC_RLE16, coord, pix.len())?;
+ let mut i = 0;
+ while i < pix.len() {
+ let v = pix[i];
+ let mut run = 1;
+ while i + run < pix.len() && pix[i + run] == v && run < 255 {
+ run += 1;
+ }
+ out.push(run as u8, GFP_KERNEL)?;
+ out.extend_from_slice(&v.to_be_bytes(), GFP_KERNEL)?;
+ i += run;
+ }
+ Ok(())
+}
+
+fn run_count(pix: &[u16]) -> usize {
+ let mut c = 0;
+ let mut i = 0;
+ while i < pix.len() {
+ let v = pix[i];
+ let mut j = i + 1;
+ while j < pix.len() && pix[j] == v {
+ j += 1;
+ }
+ c += 1;
+ i = j;
+ }
+ c
+}
+
+fn encode_run_into(out: &mut KVec<u8>, mode: Mode, coord: u32, pix: &[u16]) -> Result {
+ match mode {
+ Mode::Raw => encode_raw_into(out, coord, pix),
+ Mode::Rle => encode_rle_into(out, coord, pix),
+ Mode::Adaptive => {
+ let l = pix.len();
+ let c = run_count(pix);
+ if 2 * l < 3 * c + 1 {
+ encode_raw_into(out, coord, pix)
+ } else {
+ encode_rle_into(out, coord, pix)
+ }
+ }
+ }
+}
+
+/// Mode-2 frame encoder holding the shadow (previous-frame) buffer.
+pub(crate) struct Encoder {
+ width: usize,
+ height: usize,
+ mode: Mode,
+ // vmalloc-backed: a `width*height` u16 buffer is ~4 MiB at 1080p, far above the
+ // contiguous-kmalloc order limit (the page allocator WARNs and fails on it).
+ shadow: VVec<u16>,
+}
+
+impl Encoder {
+ pub(crate) fn new(width: usize, height: usize, mode: Mode) -> Result<Self> {
+ let shadow = VVec::from_elem(0u16, width * height, GFP_KERNEL)?;
+ Ok(Self { width, height, mode, shadow })
+ }
+
+ /// Encode `cur` (RGB565) into a mode-2 marker stream; updates the shadow.
+ /// Change-detection is per row; changed runs chunk into <=256-px blocks.
+ pub(crate) fn encode(&mut self, cur: &[u16]) -> Result<KVec<u8>> {
+ let mut s = KVec::new();
+ self.encode_into(cur, &mut s)?;
+ Ok(s)
+ }
+
+ /// Like [`encode`](Self::encode) but appends the marker stream to a caller-owned
+ /// `out` instead of allocating a fresh `KVec`. The hot scanout path
+ /// ([`encode_and_send`](super::drm_sink::encode_and_send)) uses this to encode
+ /// straight into a buffer that already reserves the EP08 transport header, so a
+ /// frame costs one allocation with no separate framing copy.
+ pub(crate) fn encode_into(&mut self, cur: &[u16], s: &mut KVec<u8>) -> Result {
+ s.extend_from_slice(&FRAME_INIT, GFP_KERNEL)?;
+ for y in 0..self.height {
+ let row = y * self.width;
+ let mut x = 0;
+ while x < self.width {
+ while x < self.width && cur[row + x] == self.shadow[row + x] {
+ x += 1;
+ }
+ if x >= self.width {
+ break;
+ }
+ let run_start = x;
+ while x < self.width && cur[row + x] != self.shadow[row + x] {
+ x += 1;
+ }
+ let run_end = x;
+ let mut p = run_start;
+ while p < run_end {
+ let n = (run_end - p).min(MAX_BLOCK_PIXELS);
+ let coord = (((row + p) * 2) & 0xff_ffff) as u32;
+ encode_run_into(s, self.mode, coord, &cur[row + p..row + p + n])?;
+ p += n;
+ }
+ }
+ }
+ // `SECTION_CODE` (the decompile's `DAT_005b7860`) is indexed by the DL3 1-based
+ // mode number minus 1; our 0-based `Mode` discriminant already equals that index
+ // (Raw=0->0x01, Rle=1->0x00, Adaptive=2->0x03). The previous `saturating_sub(1)`
+ // double-subtracted, collapsing Raw and Rle onto the same code.
+ let code = SECTION_CODE[(self.mode as usize).min(SECTION_CODE.len() - 1)];
+ s.extend_from_slice(&SYNC, GFP_KERNEL)?;
+ s.extend_from_slice(&[0xaf, 0x20, 0x1f, code], GFP_KERNEL)?;
+ s.extend_from_slice(&[0xaf, 0x20, 0xff, 0x00], GFP_KERNEL)?;
+ s.extend_from_slice(&SYNC, GFP_KERNEL)?;
+ // Commit the shadow ONLY after the whole frame has been emitted successfully.
+ // Updating it incrementally (per changed run) left it half-updated if a later
+ // `extend_from_slice` hit OOM, permanently desyncing every subsequent diff frame.
+ // The encoder reads the pre-frame shadow throughout, so a single end-of-frame copy
+ // is equivalent to the per-run writes on the success path. The loop already indexes
+ // `cur` up to `width*height == shadow.len()`, so this slice is always in bounds.
+ let n = self.shadow.len();
+ self.shadow.copy_from_slice(&cur[..n]);
+ Ok(())
+ }
+}
+
+/// Vino (`0x2801`) Walsh-Hadamard codec -- the bandwidth-constrained / 4K path (the RLE path
+/// above is what the dock currently runs; this is the lossy transform codec DLM uses when raw/
+/// RLE won't fit the USB budget). See `docs/WHT-CODEC.md` + `docs/VIDEO.md` +
+/// `captures/codec-vlc-table-breakthrough-20260623.md`.
+///
+/// **Scope (recovered + verified byte-exact vs DLM, 2026-06-23).** Every stage below is checked
+/// against DLM's own captured output offline (no dock needed):
+/// - [`colour`] `Y = 16R+32G+16B` (achromatic `Y = 64*gray`);
+/// - [`transform`] the **8x8 2-D Haar (Mallat) wavelet** (`//64`), 320/320 real gradient blocks;
+/// - [`quantize`] the per-position bias/step quantizer (`white -> Y_DC=16320 -> 1020`);
+/// - [`CODEBOOK`]/[`Vlc`] the LSB-first unary-prefix magnitude VLC dumped from DLM's coder leaf
+/// `0x5e68b0`, plus the [`SYNC13`] block framing -- `scripts/wht-block-codec.py` round-trips
+/// DLM's per-block output 5/5 byte-for-byte.
+///
+/// **The coeff->strip grammar is now recovered end to end** ([`strip`] + [`encode_frame`], 2026-06-23):
+/// the per-block significance code (a LAST-significant-position code, not a zerotree/context coder),
+/// the across-block DC **DPCM** plane (`(Cr, Cb, Y)` residuals), the AC run/magnitude stream, the two
+/// strip layouts (SOLID vs AC), and the **forward length-hint tail** (`tail[k] = L[k+1] - 2`, verified
+/// 10529/10800 on the gradient capture). [`encode_frame`] tiles a luma frame into 64x16 strips and
+/// composes the whole stream byte-exact for **achromatic** content.
+///
+/// **Two items remain genuinely unverified (so the live scanout path keeps using RLE -- emitting
+/// guessed bits is forbidden):** (1) **chroma AC** -- every captured AC example was grey, so the
+/// Cb/Cr high-frequency path has no ground truth and [`strip`] leaves chroma AC at zero; (2) the
+/// rightmost-column **size-mispredict** -- DLM's forward hint occasionally predicts an anomalously
+/// sized neighbour wrong (a framing micro-heuristic, ~2.5% of gradient strips); [`encode_frame`] uses
+/// exact look-ahead instead, which matches DLM on 100% of constant-strip-size content. Both are
+/// HW-unverifiable while the CP wall stands. The transform/quantizer/entropy stages are byte-exact.
+#[allow(dead_code)] // Walsh-Hadamard codec: KUnit-validated, not yet on the live scanout path
+pub(crate) mod wht {
+ use super::*;
+
+ /// Transform block geometry (recovered + byte-exact-verified 2026-06-23, see
+ /// `captures/codec-vlc-table-breakthrough-20260623.md`): an **8x8 pixel** block is the input
+ /// (`DIM` x `DIM` = `PIXELS` luma samples); the wavelet emits **32** coefficients (`COEFFS`) --
+ /// it is lossy 64->32, dropping the level-1 LH/HH subbands. `BLOCK` aliases `PIXELS` for the
+ /// `transform()` input length.
+ pub(crate) const DIM: usize = 8;
+ pub(crate) const PIXELS: usize = DIM * DIM;
+ pub(crate) const COEFFS: usize = 32;
+ pub(crate) const BLOCK: usize = PIXELS;
+
+ /// Vino colour transform, in the codec's 64x fixed point: `Cb = 64(R-G)`,
+ /// `Cr = 64(B-G)` (achromatic R=G=B -> Cb=Cr=0), and the reversible luma
+ ///
+ /// ```text
+ /// Y = 64*G + 64*((Cb_raw + Cr_raw) >> 2) where Cb_raw=R-G, Cr_raw=B-G
+ /// ```
+ ///
+ /// The `>> 2` is an arithmetic shift (floor toward -inf). This **replaces** the
+ /// earlier `Y = 16R + 32G + 16B` form, which `validate-transform-encoderio.py`
+ /// showed runs 16..48 HIGH for chromatic blocks (`16R+32G+16B = 64G + 16(Cb+Cr)`,
+ /// i.e. the un-floored sum). The floored form reproduces DLM's transform DC for
+ /// **every** measured colour -- the 6 saturated primaries/secondaries (incl. the
+ /// signed green/cyan cases the floor must round toward -inf), grey, white and
+ /// black (`scripts/validate-transform-encoderio.py`); achromatic input is
+ /// unchanged (`64*G` since `Cb=Cr=0`).
+ pub(crate) fn colour(r: u8, g: u8, b: u8) -> (i32, i32, i32) {
+ let (r, g, b) = (r as i32, g as i32, b as i32);
+ let (cb, cr) = (r - g, b - g);
+ (64 * g + 64 * ((cb + cr) >> 2), 64 * cb, 64 * cr)
+ }
+
+ /// Per-coefficient `(step, bias)` quantization table, **derived 2026-06-23 from DLM's
+ /// ground-truth `quant_leave` pre/post buffers** (captures/sig-library) -- the old
+ /// decode-ep08 guess (pos3 step32/bias16, pos4-11 step4/bias2, pos16-47 step2/bias1) was
+ /// WRONG and broke 4/25 strips. Note pos3 = (48, 32): a WIDE DEADZONE (so c3[3]=-48 -> -1,
+ /// not -2). Keyed by coefficient position `0..32`.
+ fn step_bias(i: usize) -> (i32, i32) {
+ match i {
+ 0 => (16, 8),
+ 1 | 2 => (16, 2),
+ 3 => (48, 32),
+ 4..=11 => (4, 0),
+ 12 => (8, 2),
+ 13..=15 => (8, 0),
+ _ => (2, 0), // 16..=31
+ }
+ }
+
+ /// Quantize coefficient `coeff` at position `i`: `sign(coeff) * floor((|coeff| + bias) / step)`
+ /// (byte-exact vs DLM, 25/25 library strips). Clamped to the 12-bit signed long-token range.
+ pub(crate) fn quantize(coeff: i32, i: usize) -> i32 {
+ let (step, bias) = step_bias(i);
+ let q = (coeff.abs() + bias) / step;
+ (if coeff < 0 { -q } else { q }).clamp(-2048, 2047)
+ }
+
+ /// One separable 2-D Haar step over the top-left `n`x`n` of `src` (row-major, `n` columns,
+ /// `n` in {8,4,2}). The 1-D Haar butterfly is `lo = a + b`, `hi = a - b`; applied to rows then
+ /// columns it splits the `n`x`n` block into four `(n/2)`x`(n/2)` subbands written to
+ /// `ll`/`hl`/`lh`/`hh` (row-major, stride `n/2`). Unnormalized -- `transform()` floor-divides
+ /// the final coefficients by 64. Mirrors `scripts/wht-transform.py` (verified byte-exact).
+ fn haar2d(src: &[i32], n: usize, ll: &mut [i32], hl: &mut [i32], lh: &mut [i32], hh: &mut [i32]) {
+ let h = n / 2;
+ // Row pass: L = row-lo, H = row-hi (each n rows x h cols).
+ let mut l = [0i32; PIXELS];
+ let mut hb = [0i32; PIXELS];
+ for r in 0..n {
+ for i in 0..h {
+ let (a, b) = (src[r * n + 2 * i], src[r * n + 2 * i + 1]);
+ l[r * h + i] = a + b;
+ hb[r * h + i] = a - b;
+ }
+ }
+ // Column pass: LL/LH = col-lo/hi of L, HL/HH = col-lo/hi of H (each h x h).
+ for c in 0..h {
+ for i in 0..h {
+ let (a, b) = (l[2 * i * h + c], l[(2 * i + 1) * h + c]);
+ ll[i * h + c] = a + b;
+ lh[i * h + c] = a - b;
+ let (a2, b2) = (hb[2 * i * h + c], hb[(2 * i + 1) * h + c]);
+ hl[i * h + c] = a2 + b2;
+ hh[i * h + c] = a2 - b2;
+ }
+ }
+ }
+
+ /// DLM's video transform (`FUN_007a7b60`), reverse-engineered + **verified byte-exact**
+ /// (2026-06-23, 320/320 real gradient blocks + the 6 stripe/checker vectors): an **8x8 2-D
+ /// Haar (Mallat) wavelet, floor-divided by 64**. The `block` is 8x8 luma (`Y = 16R+32G+16B`,
+ /// achromatic `Y = 64*gray`); the output is 32 coefficients in DLM's Mallat layout:
+ /// `c[0]` = LL; `c[1..4]` = level-3 HL/LH/HH; `c[4..8]/[8..12]/[12..16]` = level-2 HL/LH/HH
+ /// (2x2 row-major each); `c[16..32]` = level-1 HL (4x4 row-major). The level-1 LH and HH
+ /// subbands are dropped (the lossy 64->32 reduction). A uniform block yields `DC = mean`,
+ /// all AC = 0. Replaces the prior flat 2-level Walsh-Hadamard, which did not match DLM for AC.
+ pub(crate) fn transform(block: &[i32; PIXELS]) -> [i32; COEFFS] {
+ let sh = |x: i32| x >> 6; // arithmetic shift = floor division by 64 (matches DLM/`//64`)
+ let mut c = [0i32; COEFFS];
+ // Level 1: 8x8 -> 4x4 subbands; the finest HL band is c[16..32]. DLM emits this 4x4 band
+ // **column-major** (the entropy coder's coefficient reorder): wire position `p` reads band
+ // element `(row = p % 4, col = p / 4)`. Recovered 2026-06-27 byte-exact from a real DLM
+ // colour strip (cramp256 sink capture, `captures/codec-sink-094653/`): a horizontal ramp's
+ // HL band is `[-2,0,-2,0; ...]` row-major but the wire groups it `[-2,-2,-2,-2, 0,0,0,0, ...]`
+ // = the transpose. (The earlier achromatic byte-exact tests never exercised an asymmetric L1HL
+ // band -- gentle gradients quantize it to zero -- so this latent scan bug was invisible until
+ // steep colour content.) The lower bands (c[0..16]) decode in natural order.
+ let (mut ll1, mut hl1, mut lh1, mut hh1) = ([0i32; 16], [0i32; 16], [0i32; 16], [0i32; 16]);
+ haar2d(block, DIM, &mut ll1, &mut hl1, &mut lh1, &mut hh1);
+ for p in 0..16 {
+ c[16 + p] = sh(hl1[(p % 4) * 4 + p / 4]);
+ }
+ // Level 2: LL1 (4x4) -> 2x2 subbands; c[4..8]/[8..12]/[12..16].
+ let (mut ll2, mut hl2, mut lh2, mut hh2) = ([0i32; 4], [0i32; 4], [0i32; 4], [0i32; 4]);
+ haar2d(&ll1, 4, &mut ll2, &mut hl2, &mut lh2, &mut hh2);
+ for i in 0..4 {
+ c[4 + i] = sh(hl2[i]);
+ c[8 + i] = sh(lh2[i]);
+ c[12 + i] = sh(hh2[i]);
+ }
+ // Level 3: LL2 (2x2) -> 1x1 subbands; the DC c[0] and coarse c[1..4].
+ let (mut ll3, mut hl3, mut lh3, mut hh3) = ([0i32; 1], [0i32; 1], [0i32; 1], [0i32; 1]);
+ haar2d(&ll2, 2, &mut ll3, &mut hl3, &mut lh3, &mut hh3);
+ c[0] = sh(ll3[0]);
+ c[1] = sh(hl3[0]);
+ c[2] = sh(lh3[0]);
+ c[3] = sh(hh3[0]);
+ // `lh1`/`hh1` (level-1 LH/HH) are intentionally unused -- the lossy 64->32 drop.
+ let _ = (&lh1, &hh1);
+ c
+ }
+
+ // ====================================================================================
+ // ★ 2026-06-23 (live HW): the REAL entropy code, recovered + byte-exact-verified.
+ //
+ // The earlier MSB-first 5-bit "short/long token" model (now removed) was REFUTED: a value-axis
+ // amplitude sweep (`scripts/codec-sweep-plan.py`) showed its decoded tokens were invariant to
+ // the coefficient VALUE (identical `L589` across a 128x AC range) -- it never matched the coder.
+ // The dock's entropy coder (DLM leaf `0x5e68b0`) is a **memory-resident unary-prefix VLC,
+ // written LSB-first**, dumped from DLM and reproduced byte-for-byte by [`Vlc`] + [`CODEBOOK`]
+ // (`scripts/wht-block-codec.py` reproduces DLM's per-block output 5/5; see
+ // `captures/codec-vlc-table-breakthrough-20260623.md`). A coefficient's magnitude category is
+ // `bit_length(|coeff|)` (verified across the sweep), code = unary(c)+0-terminator+remainder.
+ //
+ // VERIFIED here (KUnit): the codebook, the LSB-first packing, the 1-bit final padding, and the
+ // magnitude-category rule. NOT yet generalized (the open work): the coeff->token GRAMMAR for
+ // arbitrary content -- DC DPCM, the 2-D scan (incl. the real horizontal/vertical asymmetry),
+ // and block modes -- so this is the byte-exact OUTPUT stage, not yet a wired general encoder.
+ // ====================================================================================
+
+ /// The dumped Vino entropy VLC, indexed by symbol: `(code, nbits)`, emitted **LSB-first**.
+ /// Symbol 0 = the 1-bit code `0` (zero / most common); symbol 31 = the all-ones escape prefix.
+ pub(crate) const CODEBOOK: [(u32, u8); 32] = [
+ (0, 1), (1, 3), (5, 3), (3, 5), (19, 5), (11, 5), (27, 5), (7, 7), (71, 7), (39, 7),
+ (103, 7), (23, 7), (87, 7), (55, 7), (119, 7), (15, 8), (143, 8), (79, 8), (207, 8),
+ (47, 8), (175, 8), (111, 8), (239, 8), (31, 8), (159, 8), (95, 8), (223, 8), (63, 8),
+ (191, 8), (127, 8), (255, 9), (511, 9),
+ ];
+
+ /// The constant 13-byte per-block sync literal (emitted twice), recovered from the wire.
+ pub(crate) const SYNC13: [u8; 13] =
+ [0x7c, 0x93, 0x6f, 0xf2, 0x4d, 0xbe, 0xc9, 0x37, 0xf9, 0x26, 0xdf, 0xe4, 0x9b];
+
+ /// LSB-first VLC bit packer matching the dock (final byte padded with **1-bits** -- a
+ /// truncated all-ones code, as DLM emits).
+ pub(crate) struct Vlc {
+ out: KVec<u8>,
+ acc: u32,
+ nbits: u32,
+ }
+
+ impl Vlc {
+ pub(crate) fn new() -> Self {
+ Self { out: KVec::new(), acc: 0, nbits: 0 }
+ }
+
+ /// Append one bit (LSB-first within each byte).
+ fn bit(&mut self, b: u32) -> Result {
+ self.acc |= (b & 1) << self.nbits;
+ self.nbits += 1;
+ if self.nbits == 8 {
+ self.out.push((self.acc & 0xff) as u8, GFP_KERNEL)?;
+ self.acc = 0;
+ self.nbits = 0;
+ }
+ Ok(())
+ }
+
+ /// Emit codebook `sym`'s code, least-significant bit first.
+ pub(crate) fn symbol(&mut self, sym: usize) -> Result {
+ let (code, n) = CODEBOOK[sym];
+ for k in 0..n as u32 {
+ self.bit(code >> k)?;
+ }
+ Ok(())
+ }
+
+ /// Emit one quantized coefficient as DLM's JPEG-SSSS-style magnitude code (LSB-first),
+ /// verified byte-exact against DLM's per-coefficient wire bits (q-4/q-8/q-16 -- see
+ /// `scripts/wht-strip-encoder.py`). A zero coefficient is the 1-bit symbol 0. A nonzero
+ /// `q` emits the unary category `c = bit_length(|q|)` (c ones + a 0 terminator), then the
+ /// `(c-1)`-bit magnitude offset `|q| - 2^(c-1)` (MSB-first within the field), then a sign
+ /// bit (`0` = negative, the captured polarity). Categories >= 9 use the 19-bit escape long
+ /// form, which is not yet recovered -- those return `EOVERFLOW` rather than emit wrong bits.
+ pub(crate) fn coeff(&mut self, q: i32) -> Result {
+ if q == 0 {
+ return self.symbol(0);
+ }
+ let c = mag_category(q); // bit_length(|q|)
+ if c >= 9 {
+ return Err(kernel::error::code::EOVERFLOW); // escape long form -- open RE
+ }
+ for _ in 0..c {
+ self.bit(1)?; // unary category
+ }
+ self.bit(0)?; // terminator
+ let offset = q.unsigned_abs() - (1 << (c - 1));
+ for i in (0..c - 1).rev() {
+ self.bit(offset >> i)?; // (c-1)-bit magnitude offset, MSB-first
+ }
+ self.bit(if q < 0 { 0 } else { 1 }) // sign bit (0 = negative)
+ }
+
+ /// Flush, padding the final byte with 1-bits (matches the dock's truncated all-ones code).
+ pub(crate) fn finish(mut self) -> Result<KVec<u8>> {
+ if self.nbits > 0 {
+ while self.nbits < 8 {
+ self.acc |= 1 << self.nbits;
+ self.nbits += 1;
+ }
+ self.out.push((self.acc & 0xff) as u8, GFP_KERNEL)?;
+ }
+ Ok(self.out)
+ }
+ }
+
+ /// Magnitude category of a quantized coefficient: `bit_length(|coeff|)` (verified 2026-06-23 --
+ /// e.g. |4|->3, |8|->4, |255|->8). 0 for a zero coefficient.
+ pub(crate) fn mag_category(coeff: i32) -> u32 {
+ coeff.unsigned_abs().checked_ilog2().map_or(0, |l| l + 1)
+ }
+
+ // ====================================================================================
+ // ★ 2026-06-23: the SOLID-colour strip encoder -- byte-exact-verified end to end vs DLM
+ // (3508/3508 strips of grey128.bin + white). A solid 64x16 strip (16 uniform 8x8 blocks) is the
+ // most common desktop case (backgrounds, flat UI). DLM codes it with a NO-sync framing distinct
+ // from the AC-stripe path: a 16-byte header, a CONSTANT 30-byte "main frame" (identical for any
+ // solid colour), the strip's absolute DC ESCAPE-coded (long form), then a fixed trailer. The DC
+ // rule was cracked offline (`scripts/wht-strip-encoder.py`): code = unary(c) ++ offset(c-1,
+ // MSB-first) ++ sign, c=bit_length(qDC), qDC=quantize(DC,0), DC=c3[0]=64*gray (achromatic).
+ // ====================================================================================
+
+ /// Bit offset where the per-plane DC escape begins (after the 16-byte header + the constant
+ /// 30-byte main frame = byte 46). Verified 2026-06-23 across the grey sweep and solid primaries.
+ const SOLID_DC_BIT: usize = 368;
+ /// Maximum DC escape category; the maximum category omits the unary 0-terminator (a complete
+ /// prefix code on categories `1..=SOLID_DC_CMAX`). `|qY| <= 1020 => c <= 10`, `|qCb| <= 255`.
+ const SOLID_DC_CMAX: u32 = 10;
+ /// Minimum trailing bits after the escape before the 2-byte length tail (empirical: fits all 14
+ /// solid/colour DCs of `codec-grammar-20260623`; ~= the 16 blocks' empty-AC/EOB run).
+ const SOLID_DC_MINPAD: usize = 61;
+ /// Header (16 B, X/Y/w18/w1c patched per strip) + the constant 30-byte main frame (bytes 16..46)
+ /// of a solid strip (the 16-block all-zero DC-residual / empty-AC structure, identical for any
+ /// solid colour). Byte 46 (bit 368) onward carries the (Cr, Cb, Y) DC escape + trailer.
+ const SOLID_MAIN: [u8; 46] = [
+ 0x01, 0x28, 0, 0, 0, 0, 0, 0, 0, 0, 0x3a, 0, 0x3a, 0, 0, 0, // header (magic,X,Y,resv,w18,w1c,z)
+ 0xfc, 0x00, 0x7e, 0x00, 0x3f, 0x80, 0x1f, 0xc0, 0x0f, 0xe0, 0x07, 0xf0, 0x03, 0xf8, 0x01, 0xfc,
+ 0x00, 0x7e, 0x00, 0x3f, 0x80, 0x1f, 0xc0, 0x0f, 0xe0, 0x07, 0xf0, 0x03, 0xf8, 0x01, // main frame
+ ];
+
+ /// Quantize a per-plane Haar DC: luma (plane 0) step 16, chroma (planes 1/2) step 64, bias 8.
+ /// Verified 2026-06-23 from the solid primaries (red qCb=255, cyan qY=764, ...).
+ fn quantize_dc(plane: usize, v: i32) -> i32 {
+ let step = if plane == 0 { 16 } else { 64 };
+ let q = (((v.unsigned_abs() + 8) * (65536 / step)) >> 16) as i32;
+ if v < 0 {
+ -q
+ } else {
+ q
+ }
+ }
+
+ /// Number of escape bits a signed value `v` occupies: `0 -> 1`; else `unary(c) ++ [term if
+ /// c<cmax] ++ offset(c-1) ++ sign`.
+ fn esc_len(v: i32, cmax: u32) -> usize {
+ if v == 0 {
+ return 1;
+ }
+ let c = mag_category(v);
+ c as usize + usize::from(c < cmax) + (c as usize - 1) + 1
+ }
+
+ /// Encode one solid 64x16 coder strip at pixel `(x, y)` from its three per-plane Haar DCs
+ /// (`ydc = c3[0]`, `cbdc = c4[0] = 64*(R-G)`, `crdc = c5[0] = 64*(B-G)`; achromatic => Cb=Cr=0,
+ /// e.g. `ydc = 8192` for grey128). **Byte-exact-verified vs DLM** on the full grey sweep
+ /// (c=8/9/10), white, and the 6 solid primaries/secondaries -- 14/14 strips (KUnit
+ /// `wht_solid_strip_byte_exact` + `scripts/wht-strip-encoder.py`). Plane order on the wire is
+ /// (Cr, Cb, Y); the escape begins at bit 368; the strip is zero-padded to its even length `L`
+ /// with `w18 = w1c = tail = L - 2`.
+ pub(crate) fn solid_strip(x: u16, y: u16, ydc: i32, cbdc: i32, crdc: i32) -> Result<KVec<u8>> {
+ let esc = esc_len(quantize_dc(2, crdc), SOLID_DC_CMAX)
+ + esc_len(quantize_dc(1, cbdc), SOLID_DC_CMAX)
+ + esc_len(quantize_dc(0, ydc), SOLID_DC_CMAX);
+ let need = (SOLID_DC_BIT + esc + SOLID_DC_MINPAD).div_ceil(8);
+ let payload = need + (need & 1); // round up to even
+ let len = payload + 2;
+
+ let mut out = KVec::new();
+ out.resize(len, 0, GFP_KERNEL)?;
+ out[..46].copy_from_slice(&SOLID_MAIN);
+ out[2..4].copy_from_slice(&x.to_le_bytes());
+ out[4..6].copy_from_slice(&y.to_le_bytes());
+ let l2 = (len - 2) as u16;
+ out[10..12].copy_from_slice(&l2.to_le_bytes()); // w18
+ out[12..14].copy_from_slice(&l2.to_le_bytes()); // w1c
+ // No trailing echo on the wire (the record framing's strip_id carries the length); the
+ // last 2 bytes stay the natural DC padding. See `frame_records`.
+
+ // The (Cr, Cb, Y) DC escapes, LSB-first from bit 368.
+ let mut bitpos = SOLID_DC_BIT;
+ let mut push = |b: u32| {
+ out[bitpos / 8] |= ((b & 1) as u8) << (bitpos % 8);
+ bitpos += 1;
+ };
+ for &q in &[quantize_dc(2, crdc), quantize_dc(1, cbdc), quantize_dc(0, ydc)] {
+ if q == 0 {
+ push(0); // a zero value is the single bit 0
+ continue;
+ }
+ let c = mag_category(q);
+ let mag = q.unsigned_abs();
+ let off = mag - (1 << (c - 1));
+ for _ in 0..c {
+ push(1); // unary category
+ }
+ if c < SOLID_DC_CMAX {
+ push(0); // 0-terminator (omitted at the maximum category)
+ }
+ for i in (0..c.saturating_sub(1)).rev() {
+ push(off >> i); // (c-1)-bit magnitude offset, MSB-first
+ }
+ push(u32::from(q > 0)); // sign bit = 1 for positive
+ }
+ Ok(out)
+ }
+
+ /// Max AC magnitude category (the maximum category omits the unary 0-terminator).
+ const AC_CMAX: u32 = 9;
+
+ /// LSB-first bit accumulator for the AC-strip coder (no final padding, unlike [`Vlc`]).
+ struct Bits {
+ out: KVec<u8>,
+ n: usize,
+ }
+
+ impl Bits {
+ fn new() -> Self {
+ Self { out: KVec::new(), n: 0 }
+ }
+
+ fn bit(&mut self, b: u32) -> Result {
+ if self.n % 8 == 0 {
+ self.out.push(0, GFP_KERNEL)?;
+ }
+ self.out[self.n / 8] |= ((b & 1) as u8) << (self.n % 8);
+ self.n += 1;
+ Ok(())
+ }
+
+ /// The shared escape value code: a 0 is one `0` bit; else `unary(c) ++ [0-term IFF c<cmax]
+ /// ++ offset(c-1, MSB-first) ++ sign(1=positive)`. `c = bit_length(|v|)`.
+ fn esc(&mut self, v: i32, cmax: u32) -> Result {
+ if v == 0 {
+ return self.bit(0);
+ }
+ let c = mag_category(v);
+ let off = v.unsigned_abs() - (1 << (c - 1));
+ for _ in 0..c {
+ self.bit(1)?;
+ }
+ if c < cmax {
+ self.bit(0)?;
+ }
+ for i in (0..c.saturating_sub(1)).rev() {
+ self.bit(off >> i)?;
+ }
+ self.bit(u32::from(v > 0))
+ }
+
+ /// Per-block significance = LAST-significant-position code (byte-exact 24/24): AC block
+ /// `00111110 ++ 5-bit(32-last) MSB`; flat block `00111111 ++ 0000000`.
+ fn sync_unit(&mut self, last: usize) -> Result {
+ for &b in &[0u32, 0, 1, 1, 1, 1, 1] {
+ self.bit(b)?; // common prefix 0011111
+ }
+ if last == 0 {
+ self.bit(1)?; // flat: 8th prefix bit = 1, then 7 zeros (15-bit unit)
+ for _ in 0..7 {
+ self.bit(0)?;
+ }
+ } else {
+ self.bit(0)?; // AC: 8th prefix bit = 0, then 5-bit (32-last) MSB-first (13-bit unit)
+ let v = 32 - last as u32;
+ for i in (0..5).rev() {
+ self.bit((v >> i) & 1)?;
+ }
+ }
+ Ok(())
+ }
+ }
+
+ /// Encode a UNIFORM 64x16 AC strip (16 identical 8x8 blocks) at pixel `(x, y)` from one block's
+ /// 32 transform coeffs. **BYTE-EXACT vs DLM** (25/25 sig-library strips; KUnit `wht_ac_strip`).
+ /// Achromatic (Cb=Cr=0). Layout: header(16) + per-block sync(last-position) + (Cr,Cb,Y) DC plane
+ /// (block0 + 15 DPCM-zero blocks, 10 B) + AC-row(8 blocks: run-bit / magnitude) x2 + tail(L-2).
+ /// For a fully flat block (no AC) use [`solid_strip`] instead.
+ pub(crate) fn ac_strip(coeff: &[i32; COEFFS], x: u16, y: u16) -> Result<KVec<u8>> {
+ let mut q = [0i32; COEFFS];
+ for i in 0..COEFFS {
+ q[i] = quantize(coeff[i], i);
+ }
+ let last = (1..COEFFS).rev().find(|&i| q[i] != 0).unwrap_or(0);
+
+ let mut sync = Bits::new();
+ for _ in 0..16 {
+ sync.sync_unit(last)?;
+ }
+ let mut dc = Bits::new();
+ dc.esc(0, SOLID_DC_CMAX)?; // Cr=0
+ dc.esc(0, SOLID_DC_CMAX)?; // Cb=0
+ dc.esc(q[0], SOLID_DC_CMAX)?; // Y absolute (block 0)
+ for _ in 0..45 {
+ dc.bit(0)?; // 15 DPCM-zero blocks x (Cr,Cb,Y)
+ }
+ let mut row = Bits::new();
+ for _ in 0..8 {
+ for i in 1..=last {
+ if q[i] == 0 {
+ row.bit(0)?; // run bit (insignificant coeff)
+ } else {
+ row.esc(q[i], AC_CMAX)?; // magnitude
+ }
+ }
+ }
+
+ let sync_b = sync.out.len();
+ let dc_b = 10usize; // DC region fixed 10 B for 16-block uniform
+ let mut rb = row.out.len();
+ rb += rb & 1; // round row up to even bytes
+ let w18 = 16 + sync_b + dc_b;
+ let w1c = w18 + rb;
+ let len = w1c + rb + 2;
+
+ let mut out = KVec::new();
+ out.resize(len, 0, GFP_KERNEL)?;
+ out[0] = 0x01;
+ out[1] = 0x28;
+ out[2..4].copy_from_slice(&x.to_le_bytes());
+ out[4..6].copy_from_slice(&y.to_le_bytes());
+ out[10..12].copy_from_slice(&(w18 as u16).to_le_bytes());
+ out[12..14].copy_from_slice(&(w1c as u16).to_le_bytes());
+ out[16..16 + sync_b].copy_from_slice(&sync.out);
+ out[16 + sync_b..16 + sync_b + dc.out.len()].copy_from_slice(&dc.out);
+ out[w18..w18 + row.out.len()].copy_from_slice(&row.out);
+ out[w1c..w1c + row.out.len()].copy_from_slice(&row.out);
+ out[len - 2..len].copy_from_slice(&((len - 2) as u16).to_le_bytes());
+ Ok(out)
+ }
+
+ /// Number of blocks across a 64-px-wide strip and the rows of blocks within its 16-px height.
+ const STRIP_BLOCKS_X: usize = 8;
+ const STRIP_ROW_BLOCKS: usize = 8; // blocks in one 8-row half (8 across x 1 down)
+ const STRIP_BLOCKS: usize = 16; // 8 across x 2 down
+
+ /// Round a byte count up to an even number (every coder sub-region is even-aligned).
+ fn round_even(n: usize) -> usize {
+ n + (n & 1)
+ }
+
+ /// Emit the 16-block DPCM DC plane into `b`: per block, the `(Cr, Cb, Y)` quantized DC as a
+ /// RESIDUAL escape (residual = this block's DC minus the previous block's DC, previous = 0 for
+ /// block 0). Byte-exact vs DLM on the varying-DC solid strips (vstripe8/hstripe8/checker8) and
+ /// the uniform case (all residuals 0). `dc[k] = (qCr, qCb, qY)`.
+ fn dc_plane(b: &mut Bits, dc: &[(i32, i32, i32); STRIP_BLOCKS]) -> Result {
+ let (mut pcr, mut pcb, mut py) = (0i32, 0i32, 0i32);
+ for &(cr, cb, y) in dc {
+ b.esc(cr - pcr, SOLID_DC_CMAX)?;
+ b.esc(cb - pcb, SOLID_DC_CMAX)?;
+ b.esc(y - py, SOLID_DC_CMAX)?;
+ (pcr, pcb, py) = (cr, cb, y);
+ }
+ Ok(())
+ }
+
+ /// Emit one block's luma AC coefficients (positions `1..=last`) into `b`: a run bit `0` for an
+ /// insignificant coefficient, else the magnitude escape. No EOB -- the block's `last` (from the
+ /// significance sync) bounds the loop. Achromatic only (chroma AC is not yet recovered).
+ fn block_ac(b: &mut Bits, q: &[i32; COEFFS], last: usize) -> Result {
+ for i in 1..=last {
+ if q[i] == 0 {
+ b.bit(0)?;
+ } else {
+ b.esc(q[i], AC_CMAX)?;
+ }
+ }
+ Ok(())
+ }
+
+ /// Encode one general 64x16 coder strip at pixel `(x, y)` from its 16 blocks' luma transform
+ /// coefficients (raster order: blocks 0..8 = top 8-px half, 8..16 = bottom half). Picks DLM's
+ /// layout automatically: **SOLID** (every block flat -> header + 30-byte main frame + DPCM DC
+ /// plane from bit 368) or **AC** (some block carries a nonzero AC coeff -> header + per-block
+ /// significance sync + DPCM DC plane + AC-row0 + AC-row1). The 2-byte tail is set to this
+ /// strip's own `L - 2`; [`encode_frame`] overwrites it with the *next* strip's forward length
+ /// hint (the wire's actual tail).
+ ///
+ /// **Byte-exact vs DLM** (achromatic content) on uniform solid (grey sweep/white), varying-DC
+ /// solid (vstripe8/hstripe8/checker8) and the 25/25 sig-library AC strips; gradient bodies match
+ /// every coding layer. Chroma (Cb/Cr) AC is left at zero -- there is no captured colour-AC
+ /// example to verify it against, so colourful textured content is *not* byte-exact here (the
+ /// anti-fabrication boundary). Callers pass per-block luma coeffs from [`transform`].
+ pub(crate) fn strip(blocks: &[[i32; COEFFS]; STRIP_BLOCKS], x: u16, y: u16) -> Result<KVec<u8>> {
+ let mut q = [[0i32; COEFFS]; STRIP_BLOCKS];
+ let mut lasts = [0usize; STRIP_BLOCKS];
+ let mut dc = [(0i32, 0i32, 0i32); STRIP_BLOCKS];
+ let mut any_ac = false;
+ for k in 0..STRIP_BLOCKS {
+ for i in 0..COEFFS {
+ q[k][i] = quantize(blocks[k][i], i);
+ }
+ lasts[k] = (1..COEFFS).rev().find(|&i| q[k][i] != 0).unwrap_or(0);
+ any_ac |= lasts[k] != 0;
+ dc[k] = (0, 0, q[k][0]); // achromatic: Cr=Cb=0 (chroma DC plumbed by the caller's blocks)
+ }
+
+ let mut dcb = Bits::new();
+ dc_plane(&mut dcb, &dc)?;
+
+ let mut out = KVec::new();
+ if !any_ac {
+ // SOLID layout: header + constant 30-byte main frame + DPCM DC plane from bit 368.
+ let l2 = round_even((SOLID_DC_BIT + dcb.n).div_ceil(8)) + 2;
+ let len = l2 + 2;
+ out.resize(len, 0, GFP_KERNEL)?;
+ out[..46].copy_from_slice(&SOLID_MAIN);
+ out[2..4].copy_from_slice(&x.to_le_bytes());
+ out[4..6].copy_from_slice(&y.to_le_bytes());
+ out[10..12].copy_from_slice(&(l2 as u16).to_le_bytes());
+ out[12..14].copy_from_slice(&(l2 as u16).to_le_bytes());
+ // The DPCM bits begin at bit 368 = byte 46 (byte-aligned); Bits zero-pads its tail, so a
+ // plain byte copy preserves the LSB-first packing.
+ out[46..46 + dcb.out.len()].copy_from_slice(&dcb.out);
+ out[len - 2..len].copy_from_slice(&(l2 as u16).to_le_bytes());
+ return Ok(out);
+ }
+
+ // AC layout.
+ let mut sync = Bits::new();
+ for k in 0..STRIP_BLOCKS {
+ sync.sync_unit(lasts[k])?;
+ }
+ let mut row0 = Bits::new();
+ for k in 0..STRIP_ROW_BLOCKS {
+ block_ac(&mut row0, &q[k], lasts[k])?;
+ }
+ let mut row1 = Bits::new();
+ for k in STRIP_ROW_BLOCKS..STRIP_BLOCKS {
+ block_ac(&mut row1, &q[k], lasts[k])?;
+ }
+
+ let sync_b = sync.out.len();
+ let dc_b = round_even(dcb.out.len()) + 2;
+ let r0 = round_even(row0.out.len());
+ let r1 = round_even(row1.out.len());
+ let w18 = 16 + sync_b + dc_b;
+ let w1c = w18 + r0;
+ let len = w1c + r1 + 2;
+
+ out.resize(len, 0, GFP_KERNEL)?;
+ out[0] = 0x01;
+ out[1] = 0x28;
+ out[2..4].copy_from_slice(&x.to_le_bytes());
+ out[4..6].copy_from_slice(&y.to_le_bytes());
+ out[10..12].copy_from_slice(&(w18 as u16).to_le_bytes());
+ out[12..14].copy_from_slice(&(w1c as u16).to_le_bytes());
+ out[16..16 + sync_b].copy_from_slice(&sync.out);
+ out[16 + sync_b..16 + sync_b + dcb.out.len()].copy_from_slice(&dcb.out);
+ out[w18..w18 + row0.out.len()].copy_from_slice(&row0.out);
+ out[w1c..w1c + row1.out.len()].copy_from_slice(&row1.out);
+ // No forward-hint tail: on the EP08 wire the strip's last 2 bytes are the natural row1
+ // bit-packing. The record framing carries the length as `strip_id == len`, so the in-strip
+ // echo the sink hook showed is not transmitted on the wire. See `frame_records`.
+ Ok(out)
+ }
+
+ // ====================================================================================
+ // COLOUR strip codec (Cb/Cr planes). Recovered byte-exact 2026-06-27/28 from DLM
+ // sink-hook captures (cramp/rramp/bramp period sweeps, captures/codec-sink-sweep-*):
+ // 2700/2872 strips byte-identical across every significance combination. Tooling +
+ // proof: scripts/codec-re/{coeffs,model,colourstrip,verify-colour-ac}.py.
+ //
+ // Per block the 3 planes are (Cr=64*(B-G), Cb=64*(R-G), Y=64*G + 64*((Cb+Cr)>>2)).
+ // * SYNC unit = [Cr field][Cb field][Y field]; chroma fields present only when last>0
+ // (the per-block plane mask), Y field always present (luma `sync_unit`).
+ // * DC plane = 16-block DPCM (Cr,Cb,Y), 3 tokens/block, chroma step 64 / luma step 16,
+ // round-half-up on the signed value.
+ // * AC rows (row0 blocks 0..8, row1 8..16): per block (Cr,Cb,Y) present planes, chroma
+ // quant flat step 16 (truncate toward zero), positions 1..last, run-bit `0` for zeros.
+ // * Strip length = w1c + round_even(row1) (the 2-byte tail overlaps row1's tail).
+ // ====================================================================================
+
+ /// Flat chroma AC quantizer: step 16, truncate toward zero. Coarser than luma's
+ /// per-position `quantize`, so chroma `last` collapses to {0,1,7,31}.
+ fn quantize_chroma_ac(coeff: i32) -> i32 {
+ let q = coeff.abs() / 16;
+ if coeff < 0 {
+ -q
+ } else {
+ q
+ }
+ }
+
+ /// Per-plane DC quantizer, round-half-up on the SIGNED value (toward +inf): luma (plane 0)
+ /// step 16, chroma step 64. `+224/64 = 3.5 -> 4`; `-8416/64 = -131.5 -> -131`.
+ fn quantize_dc_round(plane: usize, v: i32) -> i32 {
+ let step = if plane == 0 { 16 } else { 64 };
+ (v + step / 2).div_euclid(step)
+ }
+
+ impl Bits {
+ /// The Cr significance field: `c = bit_length(last)`; emit `1`x`c` then `0`x`c`.
+ fn cr_field(&mut self, last: usize) -> Result {
+ let c = usize::BITS - last.leading_zeros();
+ for _ in 0..c {
+ self.bit(1)?;
+ }
+ for _ in 0..c {
+ self.bit(0)?;
+ }
+ Ok(())
+ }
+
+ /// The Cb significance field: `c = bit_length(last)`; `10` if `c == 1`, else
+ /// `0` ++ `1`x`c` ++ `0`x`(c-1)`.
+ fn cb_field(&mut self, last: usize) -> Result {
+ let c = usize::BITS - last.leading_zeros();
+ if c == 1 {
+ self.bit(1)?;
+ return self.bit(0);
+ }
+ self.bit(0)?;
+ for _ in 0..c {
+ self.bit(1)?;
+ }
+ for _ in 0..c - 1 {
+ self.bit(0)?;
+ }
+ Ok(())
+ }
+
+ /// One block's colour SYNC unit: present chroma fields (Cr, then Cb) then the Y field.
+ fn colour_sync_unit(&mut self, lcr: usize, lcb: usize, ly: usize) -> Result {
+ if lcr > 0 {
+ self.cr_field(lcr)?;
+ }
+ if lcb > 0 {
+ self.cb_field(lcb)?;
+ }
+ self.sync_unit(ly)
+ }
+
+ /// One block's colour AC: present planes in (Cr, Cb, Y) order, positions `1..=last`,
+ /// run-bit `0` for an insignificant coefficient else the magnitude escape (cmax `AC_CMAX`).
+ fn colour_block_ac(
+ &mut self,
+ qcr: &[i32; COEFFS],
+ qcb: &[i32; COEFFS],
+ qy: &[i32; COEFFS],
+ lcr: usize,
+ lcb: usize,
+ ly: usize,
+ ) -> Result {
+ for &(q, last) in &[(qcr, lcr), (qcb, lcb), (qy, ly)] {
+ for i in 1..=last {
+ if q[i] == 0 {
+ self.bit(0)?;
+ } else {
+ self.esc(q[i], AC_CMAX)?;
+ }
+ }
+ }
+ Ok(())
+ }
+ }
+
+ /// One quantized colour block: the three planes' 32 coefficients and their last-significant
+ /// AC positions. Built by [`colour_block`] from a block's per-plane samples.
+ pub(crate) struct ColourBlock {
+ qcr: [i32; COEFFS],
+ qcb: [i32; COEFFS],
+ qy: [i32; COEFFS],
+ lcr: usize,
+ lcb: usize,
+ ly: usize,
+ }
+
+ /// Transform + quantize one block's three planes (each 64 samples in the codec's x64 fixed
+ /// point: `cr[i] = 64*(B-G)`, `cb[i] = 64*(R-G)`, `y[i] = 64*G + 64*((Cb+Cr)>>2)`). Luma uses
+ /// the per-position `quantize`/col-major transform; chroma AC uses the flat step-16
+ /// `quantize_chroma_ac`; all DCs use `quantize_dc_round`.
+ pub(crate) fn colour_block(
+ cr: &[i32; PIXELS],
+ cb: &[i32; PIXELS],
+ y: &[i32; PIXELS],
+ ) -> ColourBlock {
+ let tcr = transform(cr);
+ let tcb = transform(cb);
+ let ty = transform(y);
+ let mut qcr = [0i32; COEFFS];
+ let mut qcb = [0i32; COEFFS];
+ let mut qy = [0i32; COEFFS];
+ qcr[0] = quantize_dc_round(2, tcr[0]);
+ qcb[0] = quantize_dc_round(1, tcb[0]);
+ qy[0] = quantize_dc_round(0, ty[0]);
+ for i in 1..COEFFS {
+ qcr[i] = quantize_chroma_ac(tcr[i]);
+ qcb[i] = quantize_chroma_ac(tcb[i]);
+ qy[i] = quantize(ty[i], i);
+ }
+ let last = |q: &[i32; COEFFS]| (1..COEFFS).rev().find(|&i| q[i] != 0).unwrap_or(0);
+ let (lcr, lcb, ly) = (last(&qcr), last(&qcb), last(&qy));
+ ColourBlock { qcr, qcb, qy, lcr, lcb, ly }
+ }
+
+ /// Encode one 64x16 COLOUR strip at pixel `(x, y)` from its 16 quantized colour blocks
+ /// (raster: 0..8 top 8-px half, 8..16 bottom). Byte-exact vs DLM on all measured chromatic
+ /// content (see the section header). The 2-byte tail is this strip's own `L-2`;
+ /// [`encode_frame`]/the scanout path overwrites it with the next strip's forward length hint.
+ pub(crate) fn colour_strip(
+ blocks: &[ColourBlock; STRIP_BLOCKS],
+ x: u16,
+ y: u16,
+ ) -> Result<KVec<u8>> {
+ let mut sync = Bits::new();
+ for b in blocks {
+ sync.colour_sync_unit(b.lcr, b.lcb, b.ly)?;
+ }
+ let mut dcb = Bits::new();
+ let (mut pcr, mut pcb, mut py) = (0i32, 0i32, 0i32);
+ for b in blocks {
+ let (cr, cb, yv) = (b.qcr[0], b.qcb[0], b.qy[0]);
+ dcb.esc(cr - pcr, SOLID_DC_CMAX)?;
+ dcb.esc(cb - pcb, SOLID_DC_CMAX)?;
+ dcb.esc(yv - py, SOLID_DC_CMAX)?;
+ (pcr, pcb, py) = (cr, cb, yv);
+ }
+ let mut row0 = Bits::new();
+ for b in &blocks[..STRIP_ROW_BLOCKS] {
+ row0.colour_block_ac(&b.qcr, &b.qcb, &b.qy, b.lcr, b.lcb, b.ly)?;
+ }
+ let mut row1 = Bits::new();
+ for b in &blocks[STRIP_ROW_BLOCKS..] {
+ row1.colour_block_ac(&b.qcr, &b.qcb, &b.qy, b.lcr, b.lcb, b.ly)?;
+ }
+
+ let sync_b = sync.out.len();
+ let dc_b = round_even(dcb.out.len()) + 2;
+ let r0 = round_even(row0.out.len());
+ let r1 = round_even(row1.out.len());
+ let w18 = 16 + sync_b + dc_b;
+ let w1c = w18 + r0;
+ // The 2-byte tail overlaps the end of the row1 region (len = w1c + round_even(row1)).
+ let len = w1c + r1;
+
+ let mut out = KVec::new();
+ out.resize(len, 0, GFP_KERNEL)?;
+ out[0] = 0x01;
+ out[1] = 0x28;
+ out[2..4].copy_from_slice(&x.to_le_bytes());
+ out[4..6].copy_from_slice(&y.to_le_bytes());
+ out[10..12].copy_from_slice(&(w18 as u16).to_le_bytes());
+ out[12..14].copy_from_slice(&(w1c as u16).to_le_bytes());
+ out[16..16 + sync_b].copy_from_slice(&sync.out);
+ out[16 + sync_b..16 + sync_b + dcb.out.len()].copy_from_slice(&dcb.out);
+ out[w18..w18 + row0.out.len()].copy_from_slice(&row0.out);
+ out[w1c..w1c + row1.out.len()].copy_from_slice(&row1.out);
+ // No forward-hint tail: on the EP08 wire the strip's last 2 bytes are the natural row1
+ // bit-packing. The record framing carries the length as `strip_id == len`, so the in-strip
+ // echo the sink hook showed is not transmitted on the wire. See `frame_records`.
+ Ok(out)
+ }
+
+ /// Transform one 8x8 luma block read from `luma` (row-major, `stride` samples per row, top-left
+ /// at `[oy*stride + ox]`) into its 32 Haar coefficients.
+ fn block_coeffs(luma: &[u8], stride: usize, ox: usize, oy: usize) -> [i32; COEFFS] {
+ let mut blk = [0i32; PIXELS];
+ for r in 0..DIM {
+ for c in 0..DIM {
+ // Achromatic: Y = 64 * gray (the colour transform's R=G=B case).
+ blk[r * DIM + c] = 64 * luma[(oy + r) * stride + (ox + c)] as i32;
+ }
+ }
+ transform(&blk)
+ }
+
+ /// Encode a full `width`x`height` achromatic (8-bit luma) frame into the Vino WHT EP08 strip
+ /// stream (no transport header -- the caller prepends [`write_ep08_header`]). The frame is tiled
+ /// into 64x16 strips in raster order; each strip's 16 blocks are transformed and handed to
+ /// [`strip`]. A second pass writes the **forward length hint**: strip `k`'s 2-byte tail is set to
+ /// strip `k+1`'s `L - 2` (the last strip keeps its own `L - 2`). This is the wire's exact tail
+ /// rule -- verified 10529/10800 on the gradient capture and on 100% of constant-strip-size
+ /// content; it deviates from DLM only where DLM *mispredicts* an anomalously-sized neighbour
+ /// (the rightmost-column size heuristic, the single open framing micro-detail).
+ ///
+ /// `width`/`height` must be multiples of 64 and 16 respectively (the codec's strip geometry).
+ /// Achromatic only (see [`strip`]); for colour or RGB565-reduced input this is not byte-exact, so
+ /// the live scanout path keeps using RLE until the chroma-AC grammar is captured.
+ pub(crate) fn encode_frame(luma: &[u8], width: usize, height: usize) -> Result<KVec<u8>> {
+ if width % (STRIP_BLOCKS_X * DIM) != 0 || height % (2 * DIM) != 0 {
+ return Err(kernel::error::code::EINVAL);
+ }
+ let strips_x = width / (STRIP_BLOCKS_X * DIM); // 64-px columns
+ let strips_y = height / (2 * DIM); // 16-px rows
+ // Build every strip body (tail = own L-2 for now), tracking each one's span in `out`.
+ let mut out = KVec::new();
+ let mut spans: KVec<(usize, usize)> = KVec::new(); // (start, len) per strip
+ for sy in 0..strips_y {
+ for sx in 0..strips_x {
+ let (px, py) = (sx * STRIP_BLOCKS_X * DIM, sy * 2 * DIM);
+ let mut blocks = [[0i32; COEFFS]; STRIP_BLOCKS];
+ for k in 0..STRIP_BLOCKS {
+ let (bx, by) = (k % STRIP_BLOCKS_X, k / STRIP_BLOCKS_X);
+ blocks[k] = block_coeffs(luma, width, px + bx * DIM, py + by * DIM);
+ }
+ let s = strip(&blocks, px as u16, py as u16)?;
+ let start = out.len();
+ let slen = s.len();
+ out.extend_from_slice(&s, GFP_KERNEL)?;
+ spans.push((start, slen), GFP_KERNEL)?;
+ }
+ }
+ // Second pass: forward length hint -- tail[k] = L[k+1] - 2.
+ for k in 0..spans.len().saturating_sub(1) {
+ let (start, slen) = spans[k];
+ let next_l2 = (spans[k + 1].1 - 2) as u16;
+ out[start + slen - 2..start + slen].copy_from_slice(&next_l2.to_le_bytes());
+ }
+ Ok(out)
+ }
+
+ /// Strip pixel geometry: a strip is 64 px wide and 16 px tall
+ /// (`STRIP_BLOCKS_X * DIM` x `2 * DIM`).
+ pub(crate) const STRIP_W: usize = STRIP_BLOCKS_X * DIM; // 64
+ pub(crate) const STRIP_H: usize = 2 * DIM; // 16
+
+ /// Gather one 64x16 strip's 16 colour blocks from a pixel source. `px(x, y)` returns the
+ /// 8-bit `(R, G, B)` at absolute frame coordinate `(x, y)`; `(ox, oy)` is the strip's
+ /// top-left pixel. Each block's three planes are built in the codec's x64 fixed point via
+ /// [`colour`] (per-pixel `(Y, Cb, Cr)`, stored `(Cr, Cb, Y)` for [`colour_block`]). Blocks are
+ /// raster order within the strip (0..8 top 8-px half, 8..16 bottom), matching [`colour_strip`].
+ fn colour_strip_blocks(
+ ox: usize,
+ oy: usize,
+ px: &mut impl FnMut(usize, usize) -> (u8, u8, u8),
+ ) -> [ColourBlock; STRIP_BLOCKS] {
+ core::array::from_fn(|k| {
+ let (bx, by) = (k % STRIP_BLOCKS_X, k / STRIP_BLOCKS_X);
+ let (mut cr, mut cb, mut y) = ([0i32; PIXELS], [0i32; PIXELS], [0i32; PIXELS]);
+ for r in 0..DIM {
+ for c in 0..DIM {
+ let (rr, gg, bb) = px(ox + bx * DIM + c, oy + by * DIM + r);
+ let (yv, cbv, crv) = colour(rr, gg, bb);
+ let i = r * DIM + c;
+ (cr[i], cb[i], y[i]) = (crv, cbv, yv);
+ }
+ }
+ colour_block(&cr, &cb, &y)
+ })
+ }
+
+ /// Encode a full `width`x`height` 8-bit-RGB frame into the Vino WHT **colour** EP08 frame(s) --
+ /// the colour counterpart of the luma [`encode_frame`], and the assembler the live scanout path
+ /// drives once the CP wall falls. `px(x, y)` yields the source pixel's `(R, G, B)`; the caller
+ /// applies any rotation / gamma / format conversion (so this stays a pure codec). The surface is
+ /// tiled into 64x16 strips in raster order, each built from [`colour_block`] + [`colour_strip`].
+ /// The **forward length-hint tail** is then chained across the WHOLE frame -- strip `k`'s 2-byte
+ /// tail is patched to strip `k+1`'s `L - 2` (the last strip keeps its own), even across EP08-frame
+ /// boundaries. Finally the strip stream is split at strip boundaries into `<= u16::MAX - 12`-byte
+ /// EP08 frames, each prefixed with a [`write_ep08_header`] carrying an incrementing `seq` from
+ /// `seq0`. Returns the ready-to-send frames and the next `seq`.
+ ///
+ /// `width`/`height` must be multiples of 64 and 16 (`EINVAL` otherwise) -- the codec's strip
+ /// geometry; the live scanout path falls back to RLE for non-aligned modes (see
+ /// `docs/VIDEO-TODO.md`). Byte-exact for the recovered colour grammar (chroma sync/DC/AC); the
+ /// anti-fabrication boundary is the synthetic steepest-chroma edge cases (`VIDEO-TODO.md` 8/9).
+ pub(crate) fn colour_frame_ep08(
+ width: usize,
+ height: usize,
+ seq0: u32,
+ mut px: impl FnMut(usize, usize) -> (u8, u8, u8),
+ ) -> Result<(KVec<KVec<u8>>, u32)> {
+ if width % STRIP_W != 0 || height % STRIP_H != 0 {
+ return Err(kernel::error::code::EINVAL);
+ }
+ // Build every strip body (raster order; each strip's natural row1 tail, no echo).
+ let mut strips: KVec<KVec<u8>> = KVec::new();
+ let mut sy = 0usize;
+ while sy < height {
+ let mut sx = 0usize;
+ while sx < width {
+ let blocks = colour_strip_blocks(sx, sy, &mut px);
+ strips.push(colour_strip(&blocks, sx as u16, sy as u16)?, GFP_KERNEL)?;
+ sx += STRIP_W;
+ }
+ sy += STRIP_H;
+ }
+ Ok((frame_records(&strips)?, seq0))
+ }
+
+ /// A strip's `y` (the EP08 record bands group strips by row). Reads the `y` field the strip
+ /// builders write at byte offset 4 ([`colour_strip`] / [`solid_strip`]).
+ fn strip_y(s: &[u8]) -> u16 {
+ u16::from_le_bytes([s[4], s[5]])
+ }
+
+ /// Frame a raster-ordered list of strip bodies into EP08 USB transfers using DLM's real wire
+ /// record framing (RE'd 2026-06-28 from a passive capture of DLM's EP08 output):
+ ///
+ /// ```text
+ /// record (one per single-Y band of strips):
+ /// u16 pad = 0
+ /// u16 size = total record length (TLV..trailer, excludes the inter-record gap)
+ /// u32 type = 4
+ /// u8[8] prefix = [flag=0, 0, fseq=0, 0, 0,0,0,0]
+ /// per strip: u16 strip_id (== strip length) ++ strip bytes
+ /// u8[4] trailer = 0
+ /// then u8[4] inter-record gap = 0; stride = size + 4.
+ /// ```
+ ///
+ /// The record stream is then chunked into `<= 65536`-byte USB transfers (a chunk may fall
+ /// mid-record; the dock reassembles the byte stream -- this is exactly what DLM's 64 KB libusb
+ /// transfers do). There is **no** per-transfer header (the old `write_ep08_header sub=0x30`
+ /// over concatenated strips was wrong and made the dock fault). `flag`/`fseq` are record-level
+ /// metadata left at 0 (content-derivation is an open detail; the dock-visible structure is the
+ /// per-strip `strip_id == len` and the TLV framing).
+ pub(crate) fn frame_records(strips: &[KVec<u8>]) -> Result<KVec<KVec<u8>>> {
+ const PREFIX: usize = 8;
+ const TRAILER: usize = 4;
+ const GAP: usize = 4;
+ const SIZE_CAP: usize = 0x4000; // keep `size` well within u16; matches DLM's record sizes
+ let mut stream: KVec<u8> = KVec::new();
+ let mut i = 0usize;
+ while i < strips.len() {
+ let y0 = strip_y(&strips[i]);
+ let rec = stream.len();
+ stream.extend_from_slice(&[0u8; 8 + PREFIX], GFP_KERNEL)?; // TLV(8) + prefix(8)
+ stream[rec + 4..rec + 8].copy_from_slice(&4u32.to_le_bytes()); // type = 4
+ let mut n = 0usize;
+ while i < strips.len() && strip_y(&strips[i]) == y0 {
+ let s = &strips[i];
+ let projected = (stream.len() - rec) + 2 + s.len() + TRAILER;
+ if n > 0 && projected > SIZE_CAP {
+ break;
+ }
+ stream.extend_from_slice(&(s.len() as u16).to_le_bytes(), GFP_KERNEL)?;
+ stream.extend_from_slice(s, GFP_KERNEL)?;
+ n += 1;
+ i += 1;
+ }
+ stream.extend_from_slice(&[0u8; TRAILER], GFP_KERNEL)?;
+ let size = (stream.len() - rec) as u16;
+ stream[rec + 2..rec + 4].copy_from_slice(&size.to_le_bytes());
+ stream.extend_from_slice(&[0u8; GAP], GFP_KERNEL)?;
+ }
+ // Chunk the continuous record stream into transfers. Cap at 65024 (a non-multiple of the
+ // EP08 wMaxPacketSize of 1024) so every full chunk ends in a short packet that terminates
+ // the bulk transfer -- a transfer that is an exact wMaxPacketSize multiple needs a ZLP to
+ // terminate, which the in-kernel sync path / nusb does not append (DLM's libusb does). A
+ // chunk a hair under a packet boundary sidesteps that without needing ZLP support.
+ const CHUNK: usize = 65024; // 63.5 * 1024
+ let mut frames: KVec<KVec<u8>> = KVec::new();
+ let mut off = 0usize;
+ while off < stream.len() {
+ let end = core::cmp::min(off + CHUNK, stream.len());
+ let mut t: KVec<u8> = KVec::new();
+ t.extend_from_slice(&stream[off..end], GFP_KERNEL)?;
+ frames.push(t, GFP_KERNEL)?;
+ off = end;
+ }
+ Ok(frames)
+ }
+}
+
+/// Length of the EP08 transport header ([`write_ep08_header`]).
+pub(crate) const EP08_HDR_LEN: usize = 16;
+
+/// Write the 16-byte EP08 transport header into `hdr` for a `payload_len`-byte codec
+/// stream: `type=4 sub=0x30 sub_len_dw=0` sec 3 framing (matches the live capture).
+/// `size = payload_len + 12`. Used by the in-place scanout path. `hdr` must be at
+/// least 16 bytes.
+///
+/// The wire `size` field is 16-bit, so a frame is limited to `u16::MAX - 12` payload
+/// bytes; a larger codec stream cannot be expressed in a single frame and returns
+/// `EOVERFLOW` rather than silently truncating `size` (which would desync the dock's
+/// parser). The mode-2/RLE diff encoder keeps per-pageflip updates well under this;
+/// an incompressible full-frame update that exceeds it must be split by the caller.
+pub(crate) fn write_ep08_header(hdr: &mut [u8], payload_len: usize, seq: u32) -> Result {
+ let size = payload_len.checked_add(12).filter(|&s| s <= u16::MAX as usize);
+ let size = size.ok_or(kernel::error::code::EOVERFLOW)?;
+ hdr[0] = 0;
+ hdr[1] = 0;
+ hdr[2..4].copy_from_slice(&(size as u16).to_le_bytes());
+ hdr[4..8].copy_from_slice(&4u32.to_le_bytes());
+ hdr[8..10].copy_from_slice(&0x30u16.to_le_bytes());
+ hdr[10..12].copy_from_slice(&0u16.to_le_bytes());
+ hdr[12..16].copy_from_slice(&seq.to_le_bytes());
+ Ok(())
+}
--
2.55.0