A host IRQ resource is not necessarily an LPC SIRQ selector. Let the frontend configure the route: ACPI uses the allocated _CRS interrupt and _SRS when present, preserving the firmware selector, while ISA supplies a separate, range-checked SIRQ. Match trigger and polarity to the host route and TPM capabilities, including opaque INTRNG resource mappings. Pass the firmware descriptor unchanged to _SRS; apply the x86 ISA polarity override only when computing the TPM mode. ISA chips without rising-edge support now poll instead of substituting a level mode for the host route. For both FIFO drivers, require a complete GetRandom transport response and a fresh DATA_AVAIL interrupt before selecting interrupt waits. Perform the test with polling before publishing the device, and repeat it after resume. During cold boot, defer the test and publication to an interrupt configuration hook so the IRQ grace wait can sleep and the handler can run. Drain the hook before teardown; failed deferred initialization remains unpublished and can be detached safely. Missing or failed routes release the handler and IRQ resource; TPM 1.2 handler-setup failure now also falls back to polling. A host IRQ resource is not necessarily an LPC SIRQ selector. Let the frontend configure the route: ACPI uses the allocated _CRS interrupt and _SRS when present, preserving the firmware selector, while ISA supplies a separate, range-checked SIRQ. Match trigger and polarity to the host route and TPM capabilities, including opaque INTRNG resource mappings. Pass the firmware descriptor unchanged to _SRS; apply the x86 ISA polarity override only when computing the TPM mode. ISA chips without rising-edge support now poll instead of substituting a level mode for the host route. For both FIFO drivers, require a complete GetRandom transport response and a fresh DATA_AVAIL interrupt before selecting interrupt waits. Perform the test with polling before publishing the device, and repeat it after resume. During cold boot, defer the test and publication to an interrupt configuration hook so the IRQ grace wait can sleep and the handler can run. Drain the hook before teardown; failed deferred initialization remains unpublished and can be detached safely. Missing or failed routes release the handler and IRQ resource; TPM 1.2 handler-setup failure now also falls back to polling. Use monotonic deadlines for TPM 1.2 locality, status and burst polling. Keep lifecycle locality waits uninterruptible, propagate command signal errors, and terminate transfers when the burst wait expires instead of retrying a zero burst indefinitely. Check VALID with the requested status bits and retain bounded polling response retries. Interrupt waits retain their original single-timeout budget and error return. Arm only DATA_AVAIL before GO. Mask delivery on the first interrupt and before relinquishing locality, preventing idle and repeated-source storms. Bound interrupt sleeps so lost delivery can recover the current command through polling. Defer unexpected-source cleanup to a task that can acquire locality, and drain it before destroying driver locks. Preserve an unread TPM 1.2 response when quiescing an already owned locality. If the ithread cannot mask the TPM, yield for a tick with the host source masked so a level storm cannot starve the command owner or cleanup worker. Register INTR_SLEEPABLE | INTR_EXCL for both drivers, and reject firmware shared routes before installing a handler. Such routes now poll: FreeBSD does not allow sleepable shared handlers. Drain maskable events during firmware programming without letting them validate or reject the IRQ test. Retain the initial hardware-quiesce guarantee, including after _SRS: initialization fails when delivery cannot be disabled, and a later unrecoverable quiesce failure suspends command access. Known TPM 1.2 NOINTS quirks and the TPM 2.0 polling hint remain effective. Frontends without an explicit route method use polling.
Details
Validated with 187 FIFO/IRQ/ACPI mock scenarios under ASan/UBSan and warnings-as-errors tpm.ko builds for amd64 GENERIC/AURORA, i386 and arm64. The new cold-attach and stalled-burst cases reject controls with each fix removed. The separate cdevpriv follow-up also passes its 11 scenarios.
Diff Detail
- Lint
Lint Skipped - Unit
Tests Skipped
Event Timeline
without D_TRACKCLOSE, the callback is skipped and the exclusive-open flag stays set forever.
That sounds like a bug in devfs. I guess devfs_open() should bump the usecount only after a successful d_open, though that might not be quite right, the usecount is used for ttys as well and maybe that depends on the existing behaviour. I wrote D59725, could you please check whether that fixes the problem without D_TRACKCLOSE?
It probably makes sense for this to ditch d_close entirely in favor of devfs_set_cdevpriv(9). This is how we typically get reliable semantics. @kib has a writeup of why d_close is shit for this somewhere, but I always have to go trolling through mail history to find it
open/(D_TRACKCLOSE + close) are lockless and racy by definition. Indeed if you need to count consumers (why?) then cdevpriv is the way to do it.
As requested, I tested D59653 and D59657 on top of 943686575d81, the commit that fixed the cold-boot panic. The test machine is the one that panicked after 4e0f283fb97a: an Intel Core i3-6100T desktop with a Winbond WEC WPCT200 rev 0x2 (TPM 1.2, TIS 1.2, no interrupt resource) attached via ACPI PNP0C31.
Everything seems fine.
% uname -v FreeBSD 16.0-CURRENT #4 gitarc-D59657-n289644-290443bbae42: Sun Sep 27 14:07:56 ADT 2026 jrm@asn.ftfl.ca:/usr/obj/usr/src/amd64.amd64/sys/GENERIC
% sysctl -n kern.bootfile /boot/kernel.D59657/kernel
% dmesg | grep -i tpm tpm0: <Trusted Platform Module> iomem 0xfed40000-0xfed44fff on acpi0 tpm: WEC WPCT200 rev 0x2