106 lines
5.1 KiB
Text
106 lines
5.1 KiB
Text
|
|
// 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 };
|
||
|
|
}
|