Skip to content

Resume() holds stale USB handle after S3 suspend — intermittent, existing retry doesn't reopen the device #281

Open

Description

@Tora-Bora

Environment

  • Hardware: Lenovo ThinkPad X1 Yoga Gen 3
  • Sensor: 06cb:009a Synaptics, Inc. Metallica MIS Touch Fingerprint Reader
  • Distro: Ubuntu 24.04, kernel 6.14.0-36-generic
  • python3-validity 0.15~ppa2, open-fprintd 0.7~ppa2 (from PPA)

Summary

After deep (S3) suspend, Resume() frequently fails with
usb.core.USBError: [Errno 19] No such device — roughly half the
time on this machine, depending on how long the sensor was suspended
for. When the kernel re-enumerates the sensor as a new USB device
number, the service still holds the file descriptor from the
pre-suspend device; neither the primary open_common() call nor
the existing except-clause retry reopens the USB device.

When this happens, recovery requires systemctl restart python3-validity
(~1 second), which is long enough for GDM/gnome-shell to poll fprintd,
get an error, and fall back to password prompt — so the sensor never
lights up on lid-open. The rest of the time (short suspends where
USB is not re-enumerated) Resume() works fine.

The current Resume() already tries twice

def Resume(self):
    logging.debug('In Resume')
    tls.reset()
    try:
        init.open_common()
    except:
        init.open_common()   # same stale usb.dev — retry is a no-op

Both invocations issue commands through the same usb.dev object.
open_common() runs init_flash() → usb.send_init() → writes over
self.dev — and there is no path in the resume flow that calls
usb.open() again. usb.open() is only invoked at process startup
via init.open().

Timeline of a failed resume (representative)

11:06:08  kernel:      PM: suspend exit
11:06:08  open-fprintd: (from resume.py) Manager.Resume
11:06:08  python3-validity: In Resume        <- old fd
11:06:09  gdm-fingerprint: gkr-pam: no password is available for user
          (GDM has already polled fprintd, got an error, gave up)
11:06:09  dbus-service: USBError [Errno 19]  x2 (initial + internal retry)
11:06:09  systemd: Stopped / Started python3-validity   (external restart)
11:06:10  python3-validity: Manager is back online, registering
          (too late — GDM has moved on)

Suggested fix

Reopen the USB device in the except-clause of Resume():

def Resume(self):
    logging.debug('In Resume')
    tls.reset()
    try:
        init.open_common()
    except Exception:
        # USB device likely re-enumerated across S3; fd is stale.
        from validitysensor.usb import usb
        usb.close()
        usb.open()
        init.open_common()

This eliminates the need for the external process restart and closes
the race window with the desktop's fingerprint poll.

s2idle resume is unaffected because the USB device is not
re-enumerated — the sensor keeps the same device number, and the
existing retry succeeds. It's specifically the S3 case that breaks.

Related open-fprintd change

Locally I patched open-fprintd's resume.py to (a) cancel any
pending VerifyStop/Release before calling Resume(), and
(b) fall back to systemctl restart python3-validity when
Resume() throws. Happy to open a separate PR to
uunicorn/open-fprintd if that would be useful — but the
process-restart workaround only matters until Resume() handles
stale handles itself.

Reproducibility

Intermittent — approximately 50/50 in a recent 10-resume sample on
this hardware. Longer suspends (multiple hours) fail more often;
short suspends often succeed. The failure mode itself is consistent
whenever it triggers: same USBError from the same code path.

Related issues

Activity

  1. peterzgn commented on Oct 7, 2026

    @peterzgn

    I can reproduce a closely related failure on a ThinkPad T480s with the same 06cb:009a sensor, and I did some controlled testing that may help narrow this down.

    Environment:

    • Lenovo ThinkPad T480s
    • Synaptics Metallica MIS Touch Fingerprint Reader: 06cb:009a
    • Linux Mint 22.3 (Zena)
    • Kernel: 7.0.0-38-generic
    • Python: 3.12.3
    • PyUSB: 1.2.1-2
    • libusb: 1.0.27-1
    • python3-validity: 0.15~ppa2
    • open-fprintd: 0.7~ppa2
    • fprintd-clients: 1.90.1-1ubuntu5
    • Suspend mode: deep (S3)

    Cold boot works normally. The sensor is detected and fingerprint verification works.

    After S3 resume, the kernel consistently reports:

    xhci_hcd 0000:3c:00.0: xHC error in resume, USBSTS 0x401, Reinit
    usb 1-9: reset full-speed USB device ...
    

    With only open-fprintd-suspend enabled and no custom restart workaround, open-fprintd remains in:

    The service is suspended / offline, delay the call
    

    and fingerprint authentication is unavailable.

    I also tested the restart-after-resume workaround similar to #254. Restarting python3-validity/open-fprintd can bring the D-Bus device back, but I found that D-Bus readiness does not necessarily mean that the sensor is actually ready for a biometric operation.

    In one controlled test, after the services had restarted and the device had successfully registered with open-fprintd, the first fprintd-verify produced:

    Verify result: verify-no-match
    VerifyStop failed: Message recipient disconnected from message bus without replying
    

    The kernel and python-validity logs at exactly that time showed:

    usb 1-9: USB disconnect, device number 5
    usb.core.USBError: [Errno 19] No such device (it may have been disconnected)
    

    python3-validity then went offline.

    Immediately afterward the kernel physically re-enumerated the same sensor:

    usb 1-9: new full-speed USB device number 7 using xhci_hcd
    usb 1-9: New USB device found, idVendor=06cb, idProduct=009a
    

    udev/systemd automatically started python3-validity again and the device registered with open-fprintd.

    After this real disconnect/re-enumeration, subsequent fprintd-verify sessions completed normally. One was a normal no-match and the next was verify-match, with no additional USB disconnect or Errno 19.

    I also tested manually setting the device's sysfs authorized state from 0 -> 1 before starting the services. That did NOT reproduce the recovery produced by the real USB disconnect/re-enumeration. The device remained on the old USB device number, and the first Verify still caused the disconnect/Errno 19/re-enumeration sequence.

    I also enabled power/persist=1 for 06cb:009a. The setting survives suspend and appears to reduce some re-enumeration behavior during resume, but it does not eliminate this first-Verify failure.

    One additional observation: a D-Bus readiness check based on Manager.GetDevices() can return one device even when the backend is not truly usable. I observed GetDevices() reporting the sensor while python3-validity had already failed during another recovery experiment.

    This seems consistent with the stale USB/device-state theory described in this issue. In particular, there appears to be a difference between merely restarting/re-registering the userspace services and obtaining a genuinely fresh USB device after kernel re-enumeration.

    I have kept the full journal logs from these tests and can provide the relevant sections if useful. I would also be happy to test a small Resume() patch (for example explicitly closing/reopening the USB device before open_common()) on this T480s rather than changing the entire installation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions