Add 0x969/0xd51 no-template-found handling b[0]=4/5 (Wrong Finger handling) - #5
Open
Karloss1234 wants to merge 18 commits into
Open
Add 0x969/0xd51 no-template-found handling b[0]=4/5 (Wrong Finger handling)#5Karloss1234 wants to merge 18 commits into
Karloss1234 wants to merge 18 commits into
Conversation
The 0xd51 chip does not emit the b[0]=2 "finger detected" interrupt during capture, so the wait-finger loop in Sensor.capture() hangs forever. Accept b[0]=3 as a substitute (gated on the real device type so existing chips are unaffected) and pass the interrupt through to the wait-capture-complete loop. Also wires the two known USB IDs through SupportedDevices, blobs, firmware_tables, the udev rule, and aliases sensor type 0xd51 to the 0x199 profile so SensorTypeInfo / SensorCaptureProg lookups succeed (no native profile exists yet). Closes uunicorn#181 Closes uunicorn#225 Closes uunicorn#238
Without these entries `validity-sensors-firmware` crashes with a KeyError for users on 138a:00ab and 06cb:00b7 who follow the README's standard install flow. The 0xd51-family chips ship with firmware pre-loaded so the downloader is only needed for factory-reset chips, but the script should not crash. Both PIDs point at HP softpaq sp135736.exe (the same blob extracted to 6_07f_hp_cmit_mis_qm.xpfwext in FIRMWARE_NAMES). sha512 verified against the canonical ftp.hp.com URL. Reported by a 06cb:00b7 user on PR uunicorn#256.
The chip's database rejects creating a second finger record with the same subtype for the same user, which previously caused fprintd-enroll to fail at the final stage with enroll-failed after all per-stage captures had passed. Detect and delete any pre-existing record with the same subtype right before db.new_finger. Placing the delete here (inside do_create_finger, after all captures have completed) rather than at EnrollStart matters: pre-deleting before the enrollment session starts left the chip in a state where every subsequent capture returned retry-scan indefinitely until the daemon was restarted. By the time do_create_finger runs the captures are done and the chip is ready to accept the save, so deletion at this point doesn't disrupt session state.
Lenovo softpaqs (the previously supported devices) ship as Inno Setup installers, which is what innoextract handles. HP softpaqs (sp135736.exe used for the new DEV_AB and DEV_B7 entries) are CAB-wrapped self- extracting exes — innoextract rejects them with "Not a supported Inno Setup installer!" and the postinst surfaces a noisy Python traceback. Try innoextract first; on CalledProcessError or FileNotFoundError, fall back to cabextract. The standalone tool-availability check accepts either extractor so installations on either family work out of the box. Note: dropped the `-F <fwname>` filter from cabextract — its pattern matches the full path inside the cab (e.g. src/driver/INF/x64/6_07f_... xpfwext) not just the basename, so -F would silently skip the target. Extract everything; the existing `find` afterward locates the file regardless of the subdirectory it landed in.
debian/python3-validity.postinst: run pam-auth-update --package --enable fprintd so sudo / screen-unlock / GNOME Settings fingerprint flows work immediately after install, without the user having to know about pam-auth-update. debian/control: recommend libpam-fprintd (the PAM module our postinst enables) and cabextract (the fallback extractor used by the new validity-sensors-firmware path for HP softpaqs). Bumped changelog through 0.16~hp1/hp2/hp3, with hp3 documenting the same-finger re-enroll fix shipped this release.
pam_fprintd re-prints "Place your finger on the reader" for every verify-retry-scan signal it receives. On the 0xd51 chip the capture loop is chatty — a single 10-second verify window can fire the signal 20+ times, flooding the terminal during sudo authentication. Suppress all but the first verify-retry-scan per VerifyStart so the user sees one initial prompt and one early "place again" hint, then silence until match or timeout. Enrollment behavior is unchanged because per-stage retry hints are useful there (each stage is a discrete user action where lift-and-retry feedback matters).
debian/source/options: tar-ignore .pybuild, build, *.egg-info, __pycache__, *.pyc. 3.0 (native) source packages bundle the working tree verbatim, so any leftover pybuild cache from prior local debuild runs gets shipped to Launchpad. pybuild's cached state embeds the developer's absolute paths (e.g. /home/<dev>/.../python-validity/.pybuild/...), which the build chroot can't write to. The first per-series build succeeded because pybuild created the cache fresh; subsequent series builds inherited the dirty cache and failed with Permission denied trying to write into the baked-in path. This is a packaging-only change; no source code differs.
sensor.py: log resolved capture geometry once at chip open. Useful when investigating whether the 0x199 spoof produces appropriate dimensions for whatever chip the daemon is talking to. The log line includes real_type vs spoofed_type, lines_2d from the capture program, computed lines_per_frame, bytes_per_line, line_width, and lines_per_calibration_data — everything needed to recognize a profile mismatch without re-instrumenting. dbus-service: in VerifyStart, log every internal chip retry-scan to the journal independent of the D-Bus signal throttle. Lets users and support tickets quantify capture quality without manual instrumentation — `journalctl -u python3-validity.service | grep 'Chip capture retry-scan'` gives the raw count immediately. Both changes are INFO-level log lines, no functional impact. About 1 log per chip open + 0-3 lines per verify in normal use.
Multiple users reported the 0xd51-family chips (138a:00ab, 06cb:00b7)
getting stuck after cold boot, unclean exit, or suspend/resume — the
chip accepts the bulk-OUT but never responds on bulk-IN, so the first
cleartext cmd (cmd 3e get_flash_info) times out and the daemon
restart-loops every 15 seconds. The workaround users were running
manually is a USB-level reset:
sudo systemctl stop python3-validity open-fprintd
sudo udevadm trigger --attr-match=idVendor=138a --attr-match=idProduct=00ab
sudo systemctl start python3-validity open-fprintd
The reset call here is the in-driver equivalent. The chip's USB
address can shift after reset, so we re-find by vid/pid.
Reported by Killersparrow1 (issue uunicorn#238, Fedora 44, sensor vanishes
on reboot) and a separate Arch / ZBook G5 user (USBTimeoutError on
cmd 3e). The patch matches what the project memory has flagged for
the past two sessions as "kept local and not in PR" — turns out it
was the actually-load-bearing piece.
Locally confirmed: clean daemon restart, sudo matches in 1 retry,
no traceback, no "USB reset failed" warning.
The 138a:00ab USB ID is shared across HP machines but maps to different sensor silicon. The EliteBook 840 G5 reports sensor type 0xd51; the ZBook Studio x360 G5 reports 0x969. Both lack native SensorTypeInfo / SensorCaptureProg entries, and empirically both work when aliased to the 0x199 capture profile -- the on-chip matcher accepts the resulting images. Mirror the existing 0xd51 handling for 0x969: - add 0x969 to line_update_type1_devices - alias 0x969 -> 0x199 in open() - accept the b[0]==3 finger interrupt when real_device_type is 0x969 Verified on an HP ZBook Studio x360 G5: init + calibration + enroll (5 stages, enroll-completed) + verify (verify-match).
After suspend/resume, 0x969 chips re-enumerate reporting sensor type 0x199 directly rather than 0x969. Without intervention Sensor.open() skips the alias-to-0x199 block and capture()'s b[0]==3 interrupt fix (which keys off real_device_type), so verify hangs indefinitely post-resume. Detect via the device *name*, which is stable across boot and resume: FM-3439-xxx and FM- 154-xxx are the two known 0x969 model families (HP ZBook 17 G6, ZBook Studio G5, ProBook G6). Genuine 0x199 chips (FM-3367-xxx, FM-3380-xxx, FM-155-xxx) don't match either pattern. Diagnosed and tested by @Karloss1234 on Kubuntu 26.04 with an HP ZBook 17 G6 (06cb:00b7). Reported at Point 4 of the review on uunicorn#256. 0xd51 users: if verify hangs after resume, please report; we may need the same treatment for 'FM-154-xxx' (no space).
Both @bcoutts (fresh factory 0xd51 on ProBook 445R G6) and @ntoyiakhona06-creator (Windows-Hello-paired 0xd51 on EliteBook 840 G5) hit the same failure: usb.cmd(reset_blob) returns status 0404 — from opposite chip states (factory-fresh vs. previously paired). reset_blob is byte-identical across blobs_97 / blobs_9a / blobs_9d (shared "Prometheus" blob), and was originally extracted from Windows drivers for 0x199-class chips. The failure is not blob content or selection — it's that 0xd51 / 0x969 silicon does not accept this reset primitive at all. Until someone extracts a working reset_blob for the newer chip family, init_flash on a factory-fresh chip and factory_reset for unpair-from-Windows are both unsupported on this hardware. Catch 0404 at both call sites and raise a specific, actionable error (pointing at the tracking PR and telling users what to attach when reporting) instead of the bare "Failed: 0404" that assert_status produces. Also add a "known issue" section to the README. References: uunicorn#256 (bcoutts, ntoyiakhona06-creator comments).
Two related flash-management issues found by @Karloss1234 while tracing 04b5 errors during enroll on an HP ZBook 17 G6 (Point 6 of the review on uunicorn#256). 1. del_record was sending 0x48 without the db_write_enable prefix that new_record and every write in flash.py use. On 0x969 chips this returns error 04b6 (silent) and the record stays on flash — deletes never actually free space. Mirror new_record's shape: db_write_enable + try/finally: call_cleanups(). 2. Even with (1) fixed, the chip does not automatically compact the database partition. Windows only triggers compaction as part of its own enroll flow after a TPM reset, so a long-running Linux- only setup accumulates uncompacted dead records until enroll fails at new_finger with the chip's opaque 04b5. new_record was already fetching db_info() with a TODO to actually check the numbers — actually check them. If the payload plus a small header estimate doesn't fit in Info.free, raise a clear error that names the recovery path (erase_flash(4) + reboot), instead of letting the on-wire 04b5 bubble up.
…ewers Three additions from the discussion on uunicorn#256: - Template competition (Windows Hello vs. Linux fprintd sharing the on-chip database) with the "enroll different fingers per OS" and "erase partition 4" workarounds. From @Karloss1234's Point 2. - Kubuntu / KDE lock-screen PAM setup — three /etc/pam.d files and a pam_permit.so → pam_unix.so sddm-greeter fix. Not needed on GNOME. From @Karloss1234's Point 5. - Known issue: 0404 on reset_blob for 0xd51/0x969 silicon (factory init and factory_reset are unsupported on this chip family). Tells users what to attach when reporting. Code changes for the same items are in prior commits; this is docs.
0xd51 / 0x969 chips can enter a "wedged capture-quality gate" state after long uptime + heavy dev cycles: every captured frame is rejected by the on-chip quality gate and no capture-complete interrupt is ever emitted, so sensor.identify() loops in wait_int() indefinitely. Nothing the daemon can do from userspace clears this — even sensor.cancel() only unblocks wait_int; the wedge itself survives systemctl restart. Recovery is a real cold power cycle (shutdown, unplug charger, hold power ~15s, boot). Historically the daemon-side identify() thread would keep running past pam_fprintd's 10s timeout, and diagnosing the state took ~30 minutes of guessing because the journal just showed retry-scan repetitions with no summary. Add a retry-count watchdog to VerifyStart's update_cb: after 25 consecutive retries with no capture-complete (typically 12-15s), log a specific warning naming the wedge condition and the required recovery path, and call sensor.cancel() to unblock the identify() thread. The outer except in run() then emits verify-no-match cleanly. 25 is empirically past the tail of a healthy verify (~2 retries typical, p95 = 2 in the task uunicorn#17 benchmark) with comfortable margin. Enrollment path is left alone — its retry-scans are per-stage user actions, not the same signature. Diagnosed on the maintainer's own 840 G5 (0xd51) after 4 days of uptime + a diagnostic init.open() that didn't clean up.
Rolls up: - 0x969 support (ggiesen SimpleX-T#2, Karloss1234) - post-resume 0x969 recovery (Karloss1234) - 0404 friendlier error at reset_blob (bcoutts, ntoyiakhona06) - del_record db_write_enable fix + flash-full pre-check (Karloss1234) - VerifyStart watchdog for wedged-chip state - README caveats for 0xd51/0x969 Full context in uunicorn#256.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds improved handling for HP 0xd51 and 0x969 fingerprint sensors. Much of the code was taken with thanks from the @kelexine fork (now closed).
The current SimpleX-T fork seems to successfully treat b[0] = 4 or 5 interrupts as a general b[0] != 3 failure.
Commit 1: Add clean no-matching-template handling
Some sensors return a distinct interrupt when a fingerprint image is captured successfully but no enrolled template matches.
This commit:
Commit 2: Add retry handling for clean no-match results
This commit builds on Commit 1.
It adds limited retry handling in dbus-service for clean no-match results before returning a final verification failure. Sometimes the correct finger is pressed and a high quality scan is achieved and an incorrect No-Match is reported. For such false negative instances a retry is allowed.
The retry policy remains outside sensor.py so the sensor layer only reports the result and does not control authentication behaviour.
Testing
Tested on:
Verified:
Granular Detail of Chip Interrupts
Known interrupt codes observed for 0x969 / 0xd51 sensors:
glow_start_scan() is called to switch on sensor.
capture() is called to request the following chip interrupt returns:
0000000000 b[0] = 0
Chip Started / Capture initialisation / waiting for finger.
0340010000 b[0] = 3
Object detected on sensor. Finger/object acquisition has started.
capture() remains in its capture-complete loop until the chip returns
an image quality result:
0320070000 - capture rejected
0342040040 - capture accepted
0320070000 b[0] = 3 and b[2] & 4
Capture completed but image was rejected by the sensor.
Possible causes:
capture() raises an exception which returns control to identify().
identify() calls dbus-service.update_cb(e), allowing another capture
attempt. Subsequent attempts loop every second or so.
0342040040 b[0] = 3 and b[2] & 4
Capture completed successfully.
A valid fingerprint image is available for matching.
At this stage capture().get_prg_status2() seeks a detailed chip response:
For 06cb:00b7 0x969 sensor:
identify() then calls match_finger() which requests chip to return Image Match result:
03000100db b[0] = 3
Match result: matching fingerprint template found.
dbus-service.run() then determines:
04000100db b[0] = 4 (for 0x969 only)
Match result: no matching fingerprint template found. No Match - Retry three times.
Raised as FingerNotMatchedException by kelexine fork so dbus-service.run() can allow retries.
05000100db b[0] = 5 (for 0xd51 only)
Match result: no matching fingerprint template found. No Match - Retry three times.
Raised as FingerNotMatchedException by kelexine so dbus-service.run() can allow retries.