diff --git a/implementations/main.cpp b/implementations/main.cpp index 5115340..789b6b9 100644 --- a/implementations/main.cpp +++ b/implementations/main.cpp @@ -91,6 +91,7 @@ int g_rescan = -1; // -1 = leave the config's value alone // arise (every frame is terminal), so the knob only matters with a budget. bool g_undecidedIsNoMatch = false; bool g_irqObserve = false; +bool g_edgeWake = 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 @@ -718,6 +719,10 @@ public: // The line fd, for poll(): readable when an edge event is queued. int IrqFd() const { return irq_; } + // Discard anything already queued, so a wait sees only NEW edges. Without + // this the burst from the previous press wakes the next wait instantly. + void DrainEdges() { ReadEdges(); } + // 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; }; @@ -735,6 +740,13 @@ public: return out; } + // Block up to timeoutMs for an edge, then drain. Empty on timeout. This is + // what turns the capture loop from a fixed-cadence poll into a + // wake-on-contact: the line is silent at idle and pulses within + // milliseconds of a finger landing, hundreds of milliseconds before a + // polled capture notices. + std::vector WaitEdges(int timeoutMs); + // Rail up, settle, release reset, settle. Both lines are driven low first // so a warm restart starts where a cold one does. bool PowerOn() { @@ -820,6 +832,17 @@ private: bool on_ = false; }; +std::vector Sensor::WaitEdges(int timeoutMs) { + if (irq_ < 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(timeoutMs)); + return {}; + } + pollfd pfd{ irq_, POLLIN, 0 }; + int r = ::poll(&pfd, 1, timeoutMs); + if (r <= 0) return {}; + return ReadEdges(); +} + // ---- The trustlet // // The loader is IQSEEComCompatAppLoader (UID 122): op 1 loadFromBuffer, op 2 @@ -1284,6 +1307,9 @@ public: auto a = SendCommand(app_, ta::Cmd::Authenticate, au); Report(ta::Cmd::Authenticate, a); if (!a.Ok()) return out; + // The previous press's burst is still queued; drop it so the first + // wait of this session cannot be woken by an old finger. + if (g_edgeWake) sensor_.DrainEdges(); en::TouchTracker tracker; bool inPress = false, pressMatched = false, pressRejected = false; @@ -1362,15 +1388,24 @@ public: } SendCommand(app_, ta::Cmd::QueryEventStatus, q); if (g_verbose) { - // The IRQ line alongside the metric: if it tracks the finger - // under an armed session, lift detection can become an edge - // wait instead of a poll. auto irq = sensor_.ReadIrq(); std::println(" frame {:3} @{:5}ms: metric={:<4}{} irq={}{}", i + 1, msSince(t0), c.metric, finger ? " FINGER" : " ", irq ? std::to_string(*irq) : "?", note); } - std::this_thread::sleep_for(std::chrono::milliseconds(g_frameGapMs)); + // WAKE ON CONTACT. Measured: gpio75 is silent at idle under + // WAIT_TOUCH (0 edges in 60 s) and bursts within milliseconds of a + // finger landing -- and the burst arrives HUNDREDS of ms before a + // fixed-cadence capture notices, which is why a quick tap only + // ever yielded one frame. Waiting on the edge instead of sleeping + // means the first capture of a press happens at contact. + // + // While a finger is DOWN the sensor keeps pulsing, so the wait + // returns immediately and the loop runs as fast as QTEE allows -- + // exactly what a press wants. The timeout is the idle fallback, so + // a release is still noticed promptly. + if (g_edgeWake) sensor_.WaitEdges(g_frameGapMs); + else std::this_thread::sleep_for(std::chrono::milliseconds(g_frameGapMs)); } fingerPresent_.store(false); std::println(" verify loop: {} frames in {} ms ({} ms/frame incl. {} ms gap)", @@ -1534,7 +1569,8 @@ private: return; } PostEvent(std::make_unique(Event{ .kind = Event::Kind::Ready })); - if (g_irqObserve) session_.StartIrqObserver(); + if (g_irqObserve && !g_edgeWake) session_.StartIrqObserver(); + else if (g_irqObserve) std::println("--irq-observe ignored: --edge-wake owns the line fd"); for (;;) { Job j; @@ -2147,6 +2183,9 @@ int main(int argc, char** argv) { 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; + // The observer thread and the loop would both drain the same fd, so + // the diagnostic and the wake are mutually exclusive. + if (a == "--edge-wake") g_edgeWake = 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)));