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.
This commit is contained in:
Jorijn van der Graaf 2026-09-02 23:56:23 +02:00
commit 479f653e65

View file

@ -54,6 +54,7 @@ extern "C" {
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <poll.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
@ -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<Edge> ReadEdges() {
std::vector<Edge> 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<std::size_t>(n) / sizeof(ev[0]); i++)
out.push_back({ ev[i].timestamp_ns, ev[i].id == GPIO_V2_LINE_EVENT_RISING_EDGE });
if (static_cast<std::size_t>(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<bool> fingerPresent_{false};
std::thread irqThread_;
std::atomic<bool> irqQuit_{false};
};
// =============================================================================
@ -1468,6 +1534,7 @@ private:
return;
}
PostEvent(std::make_unique<Event>(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)));