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
|
|
@ -32,7 +32,9 @@ extern "C" {
|
|||
#include <qcomtee_errno.h>
|
||||
}
|
||||
|
||||
#include <linux/gpio.h>
|
||||
#include <pthread.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
|
@ -215,6 +217,111 @@ qcomtee_object* OpenService(qcomtee_object* env, std::uint32_t uid) {
|
|||
return p[1].object;
|
||||
}
|
||||
|
||||
// ---- The sensor rail
|
||||
//
|
||||
// GPIO v2 chardev ioctls directly: libgpiod is not on the phone and this is
|
||||
// three lines. The chip is found by LABEL, never by index -- /dev/gpiochipN
|
||||
// ordering is not stable and driving the wrong controller's pins is the kind
|
||||
// of mistake that is not recoverable over ssh.
|
||||
class Sensor {
|
||||
public:
|
||||
~Sensor() { PowerOff(); }
|
||||
|
||||
bool Open() {
|
||||
namespace sn = fingerprintd::sensor;
|
||||
chip_ = FindChip(sn::ChipLabel);
|
||||
if (chip_ < 0) {
|
||||
std::println(std::cerr, "no gpiochip labelled '{}'", sn::ChipLabel);
|
||||
return false;
|
||||
}
|
||||
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");
|
||||
return power_ >= 0 && reset_ >= 0 && irq_ >= 0;
|
||||
}
|
||||
|
||||
// Rail up, settle, release reset, settle. Both lines are driven low first
|
||||
// so a warm restart starts where a cold one does.
|
||||
bool PowerOn() {
|
||||
namespace sn = fingerprintd::sensor;
|
||||
if (!Set(power_, 0) || !Set(reset_, 0)) return false;
|
||||
if (!Set(power_, 1)) return false;
|
||||
std::this_thread::sleep_for(sn::PowerSettle);
|
||||
if (!Set(reset_, 1)) return false;
|
||||
std::this_thread::sleep_for(sn::ResetSettle);
|
||||
on_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PowerOff() {
|
||||
if (!on_) return;
|
||||
Set(reset_, 0);
|
||||
Set(power_, 0);
|
||||
on_ = false;
|
||||
}
|
||||
|
||||
std::optional<int> ReadIrq() const { return Get(irq_); }
|
||||
|
||||
private:
|
||||
static int FindChip(std::string_view label) {
|
||||
for (int i = 0; i < 32; i++) {
|
||||
std::string path = std::format("/dev/gpiochip{}", i);
|
||||
int fd = ::open(path.c_str(), O_RDWR | O_CLOEXEC);
|
||||
if (fd < 0) continue;
|
||||
gpiochip_info info{};
|
||||
if (::ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info) == 0
|
||||
&& label == info.label
|
||||
&& info.lines >= fingerprintd::sensor::MinChipLines) {
|
||||
std::println("gpiochip '{}' is {} ({} lines)", info.label, path,
|
||||
info.lines);
|
||||
return fd;
|
||||
}
|
||||
::close(fd);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int RequestLine(unsigned line, std::uint64_t flags, const char* consumer) {
|
||||
// The guard, enforced where the line is actually opened rather than
|
||||
// only asserted in the core. gpio8-11 are XPU-protected and touching
|
||||
// one is an immediate SError, not an error return.
|
||||
if (!fingerprintd::sensor::IsSafeLine(line)) {
|
||||
std::println(std::cerr,
|
||||
"REFUSING to open gpio{}: XPU-protected fingerprint SPI", line);
|
||||
return -1;
|
||||
}
|
||||
gpio_v2_line_request req{};
|
||||
req.offsets[0] = line;
|
||||
req.num_lines = 1;
|
||||
req.config.flags = flags;
|
||||
std::snprintf(req.consumer, sizeof(req.consumer), "%s", consumer);
|
||||
if (::ioctl(chip_, GPIO_V2_GET_LINE_IOCTL, &req) < 0) {
|
||||
std::println(std::cerr, "gpio{} request failed: {}", line, ::strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
return req.fd;
|
||||
}
|
||||
|
||||
static bool Set(int fd, int v) {
|
||||
if (fd < 0) return false;
|
||||
gpio_v2_line_values vals{};
|
||||
vals.mask = 1;
|
||||
vals.bits = v ? 1 : 0;
|
||||
return ::ioctl(fd, GPIO_V2_LINE_SET_VALUES_IOCTL, &vals) == 0;
|
||||
}
|
||||
|
||||
static std::optional<int> Get(int fd) {
|
||||
if (fd < 0) return std::nullopt;
|
||||
gpio_v2_line_values vals{};
|
||||
vals.mask = 1;
|
||||
if (::ioctl(fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &vals) < 0) return std::nullopt;
|
||||
return static_cast<int>(vals.bits & 1);
|
||||
}
|
||||
|
||||
int chip_ = -1, power_ = -1, reset_ = -1, irq_ = -1;
|
||||
bool on_ = false;
|
||||
};
|
||||
|
||||
// ---- The trustlet
|
||||
//
|
||||
// The loader is IQSEEComCompatAppLoader (UID 122): op 1 loadFromBuffer, op 2
|
||||
|
|
@ -388,11 +495,67 @@ int Probe() {
|
|||
}
|
||||
|
||||
// A storage read needs no sensor. It exercises the whole SFS listener path
|
||||
// if listeners are registered, and answers -2 when they are not.
|
||||
// if listeners are registered, and answers with no templates when they are
|
||||
// not.
|
||||
auto e = SendCommand(app, fingerprintd::ta::Cmd::Enumerate, {});
|
||||
Report(fingerprintd::ta::Cmd::Enumerate, e);
|
||||
|
||||
std::println("\ntrustlet is up and configured. Sensor not powered yet.");
|
||||
// ---- The sensor, and the init chain that needs it powered
|
||||
Sensor sensor;
|
||||
if (!sensor.Open()) {
|
||||
std::println(std::cerr, "sensor lines unavailable; stopping before init");
|
||||
return 1;
|
||||
}
|
||||
if (!sensor.PowerOn()) {
|
||||
std::println(std::cerr, "sensor power-up failed");
|
||||
return 1;
|
||||
}
|
||||
auto irq = sensor.ReadIrq();
|
||||
std::println("sensor powered, reset released, irq={}",
|
||||
irq ? std::to_string(*irq) : std::string("?"));
|
||||
|
||||
// The chain, in order. Every step answers rc=0 on a healthy sensor and the
|
||||
// last one is not optional: without SYNC_STATISTICS the trustlet's
|
||||
// g_statistics stays NULL and the first enrol frame that gets far enough
|
||||
// writes through it.
|
||||
//
|
||||
// One reset buys one init. If this fails, the rail has to go down and come
|
||||
// back up -- re-running the chain answers -205.
|
||||
bool ok = true;
|
||||
for (fingerprintd::ta::Cmd c : fingerprintd::ta::InitChain) {
|
||||
std::vector<std::byte> payload;
|
||||
if (c == fingerprintd::ta::Cmd::WorkMode) {
|
||||
// WORK_MODE takes a u32 mode; 1 = WAIT_TOUCH.
|
||||
payload.assign(0x10, std::byte{0});
|
||||
payload[0] = static_cast<std::byte>(
|
||||
static_cast<std::uint32_t>(fingerprintd::ta::WorkMode::WaitTouch));
|
||||
} else if (c == fingerprintd::ta::Cmd::SyncStatistics) {
|
||||
payload.assign(fingerprintd::ta::SyncStatisticsPayloadSize, std::byte{0});
|
||||
}
|
||||
auto ir = SendCommand(app, c, payload);
|
||||
Report(c, ir);
|
||||
if (!ir.invoked || ir.result != 0 || ir.rc != 0) {
|
||||
ok = false;
|
||||
if (ir.rc == fingerprintd::sensor::RcDeviceNotFound)
|
||||
std::println(std::cerr,
|
||||
" -205: a second init in one power cycle. "
|
||||
"Power-cycle the rail, do not retry.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
std::println(std::cerr, "init chain did not complete");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// With the sensor initialised, a capture returns a real metric. No finger
|
||||
// is needed to see the idle floor.
|
||||
std::vector<std::byte> cap(fingerprintd::ta::CaptureDeclaredLen, std::byte{0});
|
||||
auto c1 = SendCommand(app, fingerprintd::ta::Cmd::CaptureImage, cap);
|
||||
Report(fingerprintd::ta::Cmd::CaptureImage, c1);
|
||||
std::println(" idle capture metric = {}", c1.metric);
|
||||
|
||||
std::println("\ntrustlet initialised against a powered sensor.");
|
||||
pthread_cancel(th);
|
||||
pthread_join(th, nullptr);
|
||||
return 0;
|
||||
|
|
|
|||
Loading…
Reference in a new issue