From 479f653e65f1e5e282de4d91f7fbcb5a5b6fd818 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Wed, 2 Sep 2026 23:56:23 +0200 Subject: [PATCH] Observe gpio75 edge events, before building anything on them Taps are one frame at ~200 ms per frame just as they were at ~700, so the polling cadence is what catches a tap once and the loop's cost is no longer the limit. The remaining lever is the architecture stock uses: react to the touch edge, then capture as fast as QTEE allows for the length of the press. That rests on a prerequisite worth measuring before a line of it is written. The IRQ line is requested with both edges enabled and an observer thread polls it and logs every event with the kernel timestamp, the interval since the last, and the pulse width. Pure observation -- the matching loop is untouched. Two questions it answers. Whether edges are observable from userspace at all, and how quiet the line is at idle under an armed session: the level poll caught it high on 5 of 136 idle frames, so there are pulses at rest, and if they are frequent a wake-on-edge is dead before it starts. --- implementations/main.cpp | 71 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/implementations/main.cpp b/implementations/main.cpp index 55a79d5..5115340 100644 --- a/implementations/main.cpp +++ b/implementations/main.cpp @@ -54,6 +54,7 @@ extern "C" { #include #include #include +#include #include #include #include @@ -89,6 +90,7 @@ int g_rescan = -1; // -1 = leave the config's value alone // reached a verdict is also reported as no-match. Under rescan=0 this cannot // arise (every frame is terminal), so the knob only matters with a budget. bool g_undecidedIsNoMatch = false; +bool g_irqObserve = false; // The namespace key the trustlet hashes into the SFS group's directory name. // It defaults to Android's because that is what this device's existing store @@ -703,10 +705,36 @@ public: } power_ = RequestLine(sn::PowerLine, GPIO_V2_LINE_FLAG_OUTPUT, "fpd-pwr"); reset_ = RequestLine(sn::ResetLine, GPIO_V2_LINE_FLAG_OUTPUT, "fpd-rst"); - irq_ = RequestLine(sn::IrqLine, GPIO_V2_LINE_FLAG_INPUT, "fpd-irq"); + // Both edges, not just the rising one the DT declares: the line is a + // ~1 ms pulse, so a level read catches it only by luck, and knowing + // the pulse WIDTH distinguishes a touch pulse from a heartbeat. + irq_ = RequestLine(sn::IrqLine, + GPIO_V2_LINE_FLAG_INPUT | GPIO_V2_LINE_FLAG_EDGE_RISING + | GPIO_V2_LINE_FLAG_EDGE_FALLING, + "fpd-irq"); return power_ >= 0 && reset_ >= 0 && irq_ >= 0; } + // The line fd, for poll(): readable when an edge event is queued. + int IrqFd() const { return irq_; } + + // Drain queued edge events. Each is a gpio_v2_line_event with a kernel + // timestamp in ns and RISING/FALLING. + struct Edge { std::uint64_t ns; bool rising; }; + std::vector ReadEdges() { + std::vector out; + if (irq_ < 0) return out; + gpio_v2_line_event ev[16]; + for (;;) { + ssize_t n = ::read(irq_, ev, sizeof(ev)); + if (n <= 0) break; + for (std::size_t i = 0; i < static_cast(n) / sizeof(ev[0]); i++) + out.push_back({ ev[i].timestamp_ns, ev[i].id == GPIO_V2_LINE_EVENT_RISING_EDGE }); + if (static_cast(n) < sizeof(ev)) break; + } + return out; + } + // Rail up, settle, release reset, settle. Both lines are driven low first // so a warm restart starts where a cold one does. bool PowerOn() { @@ -766,6 +794,9 @@ private: std::println(std::cerr, "gpio{} request failed: {}", line, ::strerror(errno)); return -1; } + // Edge events are drained with read(); never block the caller on it. + int fl = ::fcntl(req.fd, F_GETFL); + if (fl >= 0) ::fcntl(req.fd, F_SETFL, fl | O_NONBLOCK); return req.fd; } @@ -1356,6 +1387,39 @@ public: int EnrolStages() const { return g_samples; } qcomtee_object* App() const { return app_; } + // IRQ edge observer: poll() the line and log each edge. This is the + // measurement the IRQ-driven design needs before it is built -- whether + // edges are observable at all, how wide the pulses are, and whether the + // line is quiet at idle. If the sensor pulses at rest, a wake-on-edge is + // useless and the design is dead before it starts. + void StartIrqObserver() { + if (sensor_.IrqFd() < 0) return; + irqThread_ = std::thread([this] { + std::uint64_t last = 0, lastRise = 0; + unsigned edges = 0; + for (;;) { + pollfd pfd{ sensor_.IrqFd(), POLLIN, 0 }; + int r = ::poll(&pfd, 1, 500); + if (irqQuit_.load()) break; + if (r <= 0) continue; + for (auto& e : sensor_.ReadEdges()) { + edges++; + double sinceLast = last ? (e.ns - last) / 1e6 : 0; + if (e.rising) lastRise = e.ns; + std::string width = (!e.rising && lastRise) + ? std::format(" pulse={:.2f}ms", (e.ns - lastRise) / 1e6) : ""; + std::println(" irq edge #{}: {} +{:.1f}ms{}", edges, + e.rising ? "RISE" : "fall", sinceLast, width); + last = e.ns; + } + } + }); + } + void StopIrqObserver() { + irqQuit_.store(true); + if (irqThread_.joinable()) irqThread_.join(); + } + private: bool SyncConfig() { namespace ta = fingerprintd::ta; @@ -1397,6 +1461,8 @@ private: fingerprintd::engine::Baseline baseline_; std::uint32_t gid_ = 0xFFFFFFFF; std::atomic fingerPresent_{false}; + std::thread irqThread_; + std::atomic irqQuit_{false}; }; // ============================================================================= @@ -1468,6 +1534,7 @@ private: return; } PostEvent(std::make_unique(Event{ .kind = Event::Kind::Ready })); + if (g_irqObserve) session_.StartIrqObserver(); for (;;) { Job j; @@ -1532,6 +1599,7 @@ private: } } } + session_.StopIrqObserver(); session_.Stop(); } @@ -2078,6 +2146,7 @@ int main(int argc, char** argv) { if (a.starts_with("--frames=")) frames = std::stoi(std::string(a.substr(9))); if (a.starts_with("--frame-gap=")) g_frameGapMs = std::stoi(std::string(a.substr(12))); if (a == "--undecided=nomatch") g_undecidedIsNoMatch = true; + if (a == "--irq-observe") g_irqObserve = true; if (a.starts_with("--log-dir=")) g_logDir = a.substr(10); if (a.starts_with("--state-dir=")) g_stateDir = a.substr(12); if (a.starts_with("--rescan=")) g_rescan = std::stoi(std::string(a.substr(9)));