Skip to content

fix(display): support the V2 board's CST3530 touch controller - #138

Open
Babel36acl wants to merge 2 commits into
TheMaxMur:mainfrom
Babel36acl:cst3530-touch
Open

Babel36acl wants to merge 2 commits into
TheMaxMur:mainfrom
Babel36acl:cst3530-touch

Conversation

@Babel36acl

Copy link
Copy Markdown

display: support the V2 board's CST3530 touch controller

What happens

On a V2 Waveshare RP2350-Touch-LCD-2.8 the display flavor draws, shows the
first-run Set a PIN? prompt — and no tap ever registers. The key is not
degraded, it is unusable: on this flavor the panel is the user-presence gate,
so every FIDO operation waits on a touch that can never arrive.

Why

Waveshare shipped two revisions of the board and the difference is the touch
part (their product page lists it per revision):

revision touch IC 7-bit addr register addresses
V1 CST328 0x1A 2 bytes (0xD000 report)
V2 (2026-10-14) CST3530 0x58 4 bytes (0xD0070000 report)

firmware/src/display.rs talks to the CST328, so on a V2 board every transfer
NAKs and Touch::read returns None forever. The panel pins, the reset line
and the reset pulse are identical between revisions — only the controller is
different, and neither address can be probed for at runtime (the wrong one just
fails), so this has to be a compile-time choice.

What this does

  • rsk-ui::touch::parse_cst3530 — pure parser beside parse_cst328, under host
    tests: point count in [3]'s low nibble, x/y low bytes in [4]/[5], the
    strength, the two high nibbles packed into [7], finger event in [8]'s high
    nibble (a lift frame carries the last coordinate and must not count as a tap).
  • firmware/src/display.rs — read_report() per chip behind
    cfg(display_tp = …): the CST3530 reads nine bytes at 0xD0070000 and acks by
    writing the bare 0xD00002AB address; the CST328 path is unchanged, including
    its 0xD109 normal-mode write (not a CST3530 register, so it is CST328-only).
  • firmware/build.rs — DISPLAY_TP_CHIP=cst328|cst3530 (default cst328, also
    settable as display.tp_chip in a board file), refused loudly on any other
    value.
  • nix/firmware.nix — tpChip knob + a firmware-display-v2 package.
  • docs/guides/display.md — the revision table, the symptom, and how to pick.
  • docs/assurance-matrix.md + assurance/configurations.toml — regenerated with
    scripts/matrix_gate.py --write; the new column arrives as gap cells with a
    settling question (settled_by = "evidence": which check.sh rows have to run
    on this image, and whether any of them reaches a presence gesture at all — a
    suite that presses BOOTSEL proves nothing when presence is a panel read). I
    deliberately did not invent an equivalent disposition: the closure is
    firmware-display's, not the default build's, and the gate refuses that basis.

Testing

Rebuilt from source on x86_64 Linux with rustup (thumbv8m.main-none-eabihf),
arm-none-eabi-gcc 12.2.1, flip-link, and picotool 2.3.1 built from source:

  • cargo test -p rsk-ui — green, including five new CST3530 parser cases.
  • LED_KIND=none FLASH_SIZE=16M DISPLAY_TP_CHIP=cst3530 cargo build --release -p firmware --features display
    — builds; the build script's cfg log confirms display_tp="cst3530".
  • scripts/pt.sh fences the store (0x00e7f000..0x00fff000, NSBOOT denied) and
    picotool uf2 convert produces a 2.4 MB UF2.
  • The default (CST328) display build and the non-display build both still build.

One warning shows up on both configurations and is not from this change: the
linker prints address (0x1000013c) of section .text is not a multiple of alignment (8) — identical address on the untouched CST328 build, so it belongs
to the toolchain/linker-script pair rather than to the touch driver. The
rsk-rsa "incorrect section attributes" assembler warnings are likewise from the
distro arm-none-eabi-gcc instead of the pinned gcc-arm-embedded.

Hardware: verified on a V2 board (16 MB flash). DISPLAY_TP_CHIP=cst3530
image flashed over BOOTSEL; the panel's touch responds and the on-screen flow is
usable, where the same image with the CST328 driver drew the screen and ignored
every tap. No change was needed to the report layout or the axis mapping.

Waveshare shipped two revisions of the reference board and the difference is
the touch controller: V1 has a CST328 (7-bit 0x1A, two-byte register
addresses, the report block at 0xD000), the V2 of 2026-10-14 a CST3530
(7-bit 0x58, four-byte register addresses, the report block at 0xD0070000).

The display flavor hard-codes the CST328, so on a V2 board every read NAKs and
`Touch::read` answers "no finger" forever: the panel draws, the first-run
"Set a PIN?" prompt appears, and nothing on it responds — the key is unusable
rather than merely degraded, because on this flavor the panel IS the presence
gate. Neither address can be probed for at runtime (the wrong one simply
fails), so the choice is compile-time: `DISPLAY_TP_CHIP=cst328|cst3530`,
defaulting to the V1 part, plus a `firmware-display-v2` package that sets it.

The bit-twiddling goes in `rsk-ui::touch::parse_cst3530` beside the CST328
parser, under host tests, for the same reason that one lives there: a wrong
reconstruction maps a tap to the wrong button. The CST3530 reports nine bytes
— point count in `[3]`'s low nibble, then x/y low bytes, strength, the two
high nibbles packed into `[7]`, and the finger event in `[8]`'s high nibble —
and every read has to write the bare 0xD00002AB address to ack the frame. The
reset pulse and the panel pins are identical between revisions, so nothing
else changes: no mode write (0xD109 is not a CST3530 register), same I2C bus,
same axes.

Verified on the NAS with a rustup toolchain (thumbv8m.main-none-eabihf,
picotool 2.3.1 for the partition table and the UF2): `cargo test -p rsk-ui`
green including five new CST3530 parser cases, `DISPLAY_TP_CHIP=cst3530`
builds and produces a UF2 with the store fenced by scripts/pt.sh, the default
CST328 path still builds, and docs/assurance-matrix.md is regenerated (a new
`firmware-display-v2` column, its settling question recorded in
assurance/configurations.toml).
cargo fmt --all -- --check flagged the blocking_write_read call: one line over
the width. No behaviour change; check.sh's first row is fmt, so this is what
turned CI red before anything else could run.
@Babel36acl

Copy link
Copy Markdown
Author

Filed #139 for the hardware-revision side of this (symptom + how to tell V1 from V2 + what I'm asking about flavor policy), so the design discussion stays here and the tracker has an entry other V2 owners can find.

@Babel36acl

Copy link
Copy Markdown
Author

CI note, so the red does not get read as this patch's doing: 39 of 40 checks pass, including all fourteen flavors — display and board-waveshare-touch-lcd among them — and the sole failure is the check job's cargo-audit (SCA) row.

Two vulnerabilities landed in the advisory database on 2026-10-23, both already in Cargo.lock on main:

  • RUSTSEC-2026-0387 — crypto-bigint 0.7.5, timing variability (fix: >= 0.7.6)
  • RUSTSEC-2026-0385 — ctutils 0.4.2, non-constant-time mask generation (fix: >= 0.4.4)

This diff does not touch Cargo.lock or Cargo.toml, and main's last ci run was 2026-10-20 (green, run 39331018007) — i.e. the advisory drift predates this branch and would fail any run made after the 23rd. Nothing in the CST3530 change can reach it.

A lock-only fix exists and is small (cargo update -p crypto-bigint --precise 0.7.6 pulls ctutils to 0.4.7; scoped, no source change). I have not included it here — dependency moves are your trust base and this branch is one topic. Say the word and I will send it as its own PR so this one can go green on its own merits.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant