Own the sensor rail, and run the init chain against it
The daemon now powers the sensor and initialises the trustlet against it. On
the phone, every step of the chain returning rc=0:
gpiochip 'f100000.pinctrl' is /dev/gpiochip5 (168 lines)
sensor powered, reset released, irq=1
CMD 0x1006 INIT_SPI rc=0
CMD 0x100a PROBE_DEVICE rc=0
CMD 0x100b INIT_DEVICE rc=0
CMD 0x1004 TA_INIT rc=0
CMD 0x1020 WORK_MODE rc=0
CMD 0x100e SYNC_STATISTICS rc=0
GPIO v2 chardev ioctls directly rather than libgpiod, which is on neither the
phone nor the sysroot and would be a dependency for three lines.
The chip is found by label, and the label is not what the device tree calls it:
the node is pinctrl@f100000 so the chardev advertises "f100000.pinctrl", while
every DT reference says "tlmm". Matching on "tlmm" finds nothing, which is how
the first run failed. There is a second check on the line count, because this
SoC has another pinctrl with 23 lines and driving line 75 of the wrong
controller is not something you recover from over ssh.
The XPU guard is enforced where the line is actually opened, not only asserted
in the core. gpio8-11 are the fingerprint SPI pads and touching one is an
immediate SError with the phone rebooting where it stands, so a refusal has to
sit in front of the ioctl.
Owning the rail is what makes the session recoverable at all: one reset buys
exactly one trustlet init and a second answers -205, so a failed session needs
the rail cycled rather than the chain retried. The harness split these across
two processes and every run began by restarting the one holding the rail.
CAPTURE_IMAGE answers -201 here and that is correct, not a regression: it needs
a shared memory region whose address QTEE patches into the payload, and none is
supplied yet. That is the next piece.
This commit is contained in:
parent
2648e46d43
commit
1fb57cd1be
5 changed files with 357 additions and 3 deletions
106
interfaces/Fingerprintd-Sensor.cppm
Normal file
106
interfaces/Fingerprintd-Sensor.cppm
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
// lint-disable-file fixed-width-types
|
||||
/*
|
||||
Fingerprintd:Sensor — which pins the sensor hangs off, and which must never be
|
||||
touched.
|
||||
|
||||
The FT9391's SPI bus belongs to TrustZone. The normal world drives only the
|
||||
sideband: a load-switch enable, a reset, and an interrupt. That is the same
|
||||
division of labour the downstream driver uses — it owns power, reset and the
|
||||
IRQ while the trustlet owns SPI.
|
||||
|
||||
Pin numbers and the pad configuration come from the stock device tree, which
|
||||
names this node `focalfp_ft9362` even though the part is an FT9391 (vendor
|
||||
copy-paste; the trustlet registers four chip drivers and selects ft9391).
|
||||
|
||||
No I/O here: the line numbers, the timings, and the guard. The shell opens the
|
||||
chip.
|
||||
*/
|
||||
|
||||
export module Fingerprintd:Sensor;
|
||||
import std;
|
||||
|
||||
export namespace fingerprintd::sensor {
|
||||
|
||||
// The TLMM pin controller. Resolved BY LABEL, never by /dev/gpiochipN --
|
||||
// the index is not stable across kernels and getting it wrong means
|
||||
// driving someone else's pins.
|
||||
//
|
||||
// The label is the DT node's unit address, not the driver's name: the node
|
||||
// is pinctrl@f100000, so the chardev reports "f100000.pinctrl". "tlmm" is
|
||||
// what the binding and every DT reference call it and it is NOT what the
|
||||
// chip advertises -- measured on the phone, where the seven chips are five
|
||||
// PMIC gpio banks plus this one and the 23-line 3440000.pinctrl.
|
||||
inline constexpr std::string_view ChipLabel = "f100000.pinctrl";
|
||||
|
||||
// A second, independent check on the chip: TLMM has 168 lines, and the
|
||||
// other pinctrl on this SoC has 23. Matching the label alone would be
|
||||
// enough today, but a chip that cannot even contain our highest line is
|
||||
// never the right one, and driving line 75 of the wrong controller is not
|
||||
// recoverable over ssh.
|
||||
inline constexpr unsigned MinChipLines = 76; // must contain IrqLine
|
||||
|
||||
// From the stock DT node: vdd-gpio = <&tlmm 29>, reset-gpio = <&tlmm 74>,
|
||||
// irq-gpio = <&tlmm 75>, interrupts = <75 1> = IRQ_TYPE_EDGE_RISING.
|
||||
// No clocks, no supplies, no SPI phandle -- pure GPIO plus an interrupt.
|
||||
inline constexpr unsigned PowerLine = 29; // FP_3P3_EN, an ETA5053 load switch
|
||||
inline constexpr unsigned ResetLine = 74; // FPS_RESET_N
|
||||
inline constexpr unsigned IrqLine = 75; // FPS_INT_N
|
||||
|
||||
// Despite the _N in the vendor's name, the line IDLES LOW and pulses high:
|
||||
// the DT says edge-rising. A reader expecting active-low sees the finger
|
||||
// backwards.
|
||||
inline constexpr bool IrqIsActiveHigh = true;
|
||||
|
||||
// ---- The hazard -------------------------------------------------------
|
||||
//
|
||||
// gpio8-11 are the fingerprint SPI pads and they are XPU-protected.
|
||||
// Touching one from the normal world is not an error return: it is an
|
||||
// immediate SError and the phone reboots on the spot. That was paid for
|
||||
// once, with a module load that took the machine down mid-insmod.
|
||||
//
|
||||
// Mainline's DTS reserves them (`gpio-reserved-ranges = <8 4>`), but a
|
||||
// userspace chardev request does not consult that, so the guard has to be
|
||||
// ours and it has to be checked on every line we open.
|
||||
inline constexpr unsigned ReservedSpiFirst = 8;
|
||||
inline constexpr unsigned ReservedSpiCount = 4;
|
||||
|
||||
inline constexpr bool IsReserved(unsigned line) {
|
||||
return line >= ReservedSpiFirst && line < ReservedSpiFirst + ReservedSpiCount;
|
||||
}
|
||||
inline constexpr bool IsSafeLine(unsigned line) { return !IsReserved(line); }
|
||||
|
||||
// The only lines this daemon ever opens.
|
||||
inline constexpr std::array<unsigned, 3> OwnedLines = { PowerLine, ResetLine, IrqLine };
|
||||
static_assert(IsSafeLine(PowerLine) && IsSafeLine(ResetLine) && IsSafeLine(IrqLine),
|
||||
"a line this daemon opens is XPU-protected");
|
||||
|
||||
// ---- Power sequencing -------------------------------------------------
|
||||
//
|
||||
// Rail up, settle, release reset, settle. The delays are the ones the
|
||||
// working reference used; the sensor answers its ready pulse about a
|
||||
// millisecond after reset is released.
|
||||
inline constexpr std::chrono::milliseconds PowerSettle{250};
|
||||
inline constexpr std::chrono::milliseconds ResetSettle{50};
|
||||
|
||||
// Both lines are driven low before the rail comes up, so a warm restart
|
||||
// starts from the same state as a cold one.
|
||||
inline constexpr bool AssertResetBeforePower = true;
|
||||
|
||||
// ---- Why the daemon owns this ----------------------------------------
|
||||
//
|
||||
// One sensor reset buys exactly ONE trustlet init. A second init in the
|
||||
// same power cycle answers -205 "Device not found". So a session that
|
||||
// fails cannot be recovered by re-running the init chain: the rail has to
|
||||
// go down and come back up first, and that means the process holding the
|
||||
// session must also be the process holding the rail.
|
||||
//
|
||||
// The research harness split them -- a Python script held the rail for a
|
||||
// fixed number of seconds while a separate binary drove the trustlet -- and
|
||||
// every run had to restart the holder first or fail.
|
||||
inline constexpr int RcDeviceNotFound = -205;
|
||||
|
||||
enum class Power { Off, On };
|
||||
}
|
||||
|
|
@ -18,3 +18,4 @@ export import :Ta;
|
|||
export import :Engine;
|
||||
export import :Store;
|
||||
export import :Tee;
|
||||
export import :Sensor;
|
||||
|
|
|
|||
Loading…
Reference in a new issue