@@ -195,13 +195,16 @@ confirm_gpg_card() {
195195
196196 # setup the USB so we can reach the USB Security dongle's OpenPGP smartcard
197197 enable_usb
198+ # Wait for USB enumeration before accessing GPG card to avoid race condition
199+ wait_for_usb_devices
198200
199201 echo -e " \nVerifying presence of GPG card...\n"
200202 # ensure we don't exit without retrying
201203 errexit=$( set -o | grep errexit | awk ' {print $2}' )
202204 set +e
203- gpg_output=$( gpg --card-status 2>&1 )
204- if [ $? -ne 0 ]; then
205+ DEBUG " Attempting gpg card detection (bounded wait)"
206+ if ! wait_for_gpg_card; then
207+ DEBUG " GPG card access failed with output: $gpg_output "
205208 # prompt for reinsertion and try a second time
206209 read -n1 -r -p \
207210 " Can't access GPG key; remove and reinsert, then press Enter to retry. " \
@@ -211,8 +214,10 @@ confirm_gpg_card() {
211214 set -e
212215 fi
213216 # retry card status
214- gpg_output=$( gpg --card-status 2>&1 ) ||
217+ DEBUG " Retrying gpg --card-status after reinsertion (bounded wait)"
218+ wait_for_gpg_card ||
215219 die " gpg card read failed"
220+ DEBUG " Retry succeeded"
216221 fi
217222
218223 # Extract and display GPG PIN retry counters
@@ -419,7 +424,89 @@ enable_usb() {
419424 insmod /lib/modules/ehci-pci.ko || die " ehci_pci: module load failed"
420425 insmod /lib/modules/xhci-hcd.ko || die " xhci_hcd: module load failed"
421426 insmod /lib/modules/xhci-pci.ko || die " xhci_pci: module load failed"
427+ }
428+
429+ # Wait for USB bus enumeration to complete after enable_usb() loads modules.
430+ # Uses time-bounded polling (max 2s) to avoid race conditions where device
431+ # nodes haven't been created yet. No hardcoded sleep - checks actual readiness.
432+ # Waits for actual USB peripheral devices (e.g., 1-1, 5-3), not just hubs/controllers.
433+ wait_for_usb_devices () {
434+ TRACE_FUNC
435+ if [ ! -d /sys/bus/usb/devices ] || [ ! -r /proc/uptime ]; then
436+ DEBUG " USB sysfs or uptime not available, skipping wait"
437+ return
438+ fi
439+
440+ local start now elapsed
441+ start=$( awk ' {print $1}' /proc/uptime)
442+ DEBUG " Waiting for USB peripheral devices (not just hubs) - max 2s timeout"
443+
444+ local iteration=0
445+ while : ; do
446+ iteration=$(( iteration + 1 ))
447+
448+ # Check for actual USB peripheral devices (format: bus-port like 1-1, 5-3)
449+ # Root hubs are named usb1, usb2, etc. - we want devices downstream from them
450+ # Pattern: /sys/bus/usb/devices/[0-9]*-[0-9]*/idVendor (e.g., 1-1, 5-3.2)
451+ local peripheral_count=0
452+ if [ -d /sys/bus/usb/devices ]; then
453+ # Count devices matching bus-port pattern (not usb* root hubs)
454+ for dev in /sys/bus/usb/devices/* -* /idVendor; do
455+ if [ -r " $dev " ]; then
456+ peripheral_count=$(( peripheral_count + 1 ))
457+ fi
458+ done
459+ fi
460+
461+ now=$( awk ' {print $1}' /proc/uptime)
462+ elapsed=$( awk -v s=" $start " -v n=" $now " ' BEGIN{printf "%.3f", n - s}' )
463+
464+ if [ $peripheral_count -gt 0 ]; then
465+ DEBUG " USB peripheral devices ready after ${elapsed} s (iteration $iteration ): found $peripheral_count device(s)"
466+ return
467+ fi
468+
469+ # Timeout after 2 seconds
470+ if awk -v s=" $start " -v n=" $now " ' BEGIN{exit (n - s > 2.0) ? 0 : 1}' ; then
471+ DEBUG " USB wait timeout at ${elapsed} s (iter $iteration ): only found $peripheral_count peripheral device(s)"
472+ return
473+ fi
474+ done
475+ }
422476
477+ # Wait for gpg --card-status to succeed (bounded, no sleep).
478+ # Sets global gpg_output with the last command output.
479+ wait_for_gpg_card () {
480+ TRACE_FUNC
481+ if [ ! -r /proc/uptime ]; then
482+ gpg_output=$( gpg --card-status 2>&1 )
483+ return $?
484+ fi
485+
486+ local start now elapsed
487+ start=$( awk ' {print $1}' /proc/uptime)
488+ local attempt=0
489+ while : ; do
490+ attempt=$(( attempt + 1 ))
491+ gpg_output=$( gpg --card-status 2>&1 )
492+ if [ $? -eq 0 ]; then
493+ now=$( awk ' {print $1}' /proc/uptime)
494+ elapsed=$( awk -v s=" $start " -v n=" $now " ' BEGIN{printf "%.3f", n - s}' )
495+ DEBUG " gpg --card-status succeeded after ${elapsed} s (attempt $attempt )"
496+ return 0
497+ fi
498+
499+ now=$( awk ' {print $1}' /proc/uptime)
500+ elapsed=$( awk -v s=" $start " -v n=" $now " ' BEGIN{printf "%.3f", n - s}' )
501+ if awk -v s=" $start " -v n=" $now " ' BEGIN{exit (n - s > 2.0) ? 0 : 1}' ; then
502+ DEBUG " gpg --card-status timeout at ${elapsed} s (attempt $attempt )"
503+ return 1
504+ fi
505+ done
506+ }
507+
508+ enable_usb_keyboard () {
509+ TRACE_FUNC
423510 # For resiliency, test CONFIG_USB_KEYBOARD_REQUIRED explicitly rather
424511 # than having it imply CONFIG_USER_USB_KEYBOARD at build time.
425512 # Otherwise, if a user got CONFIG_USER_USB_KEYBOARD=n in their
0 commit comments