Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F176897753
D59657.1793589514.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
63 KB
Referenced Files
None
Subscribers
None
D59657.1793589514.diff
View Options
diff --git a/sys/conf/files b/sys/conf/files
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -3314,6 +3314,7 @@
dev/tdfx/tdfx_pci.c optional tdfx pci
dev/ti/if_ti.c optional ti pci
dev/tpm/tpm20.c optional tpm
+dev/tpm/tpm_acpi_subr.c optional tpm acpi
dev/tpm/tpm_bus.c optional tpm acpi
dev/tpm/tpm_if.m optional tpm
dev/tpm/tpm_spibus.c optional tpm spibus fdt
diff --git a/sys/dev/tpm/tpm.c b/sys/dev/tpm/tpm.c
--- a/sys/dev/tpm/tpm.c
+++ b/sys/dev/tpm/tpm.c
@@ -39,6 +39,9 @@
#include <isa/isareg.h>
#include <isa/isavar.h>
#include <dev/tpm/tpmvar.h>
+#include <dev/tpm/tpm_irq.h>
+
+#include "tpm_if.h"
MALLOC_DEFINE(M_TPM, "tpm12_buffer", "buffer for TPM 1.2 responses");
@@ -56,8 +59,6 @@
#define TPM_SAVESTATE_RETRIES 50
#define TPM_SAVESTATE_RETRY_MS 100
-#define IRQUNK -1
-
#define TPM_ACCESS 0x0000 /* access register */
#define TPM_ACCESS_ESTABLISHMENT 0x01 /* establishment */
#define TPM_ACCESS_REQUEST_USE 0x02 /* request using locality */
@@ -170,8 +171,8 @@
{ 0, "", TPM_DEV_NOINTS },
};
-int tpm_tis12_irqinit(struct tpm_softc *, int, int);
-int tpm_tis12_init(struct tpm_softc *, int, const char *);
+int tpm_tis12_irqinit(struct tpm_softc *, int);
+int tpm_tis12_init(struct tpm_softc *, const char *);
int tpm_tis12_start(struct tpm_softc *, int);
int tpm_tis12_read(struct tpm_softc *, void *, int, size_t *, int);
int tpm_tis12_write(struct tpm_softc *, void *, int);
@@ -179,21 +180,22 @@
void tpm_intr(void *);
-int tpm_waitfor_poll(struct tpm_softc *, u_int8_t, int, void *);
-int tpm_waitfor_int(struct tpm_softc *, u_int8_t, int, int);
-int tpm_waitfor(struct tpm_softc *, u_int8_t, int, void *);
+int tpm_waitfor_poll(struct tpm_softc *, u_int8_t, sbintime_t);
+int tpm_waitfor_int(struct tpm_softc *, u_int8_t, sbintime_t);
+int tpm_waitfor(struct tpm_softc *, u_int8_t, int);
int tpm_request_locality(struct tpm_softc *, int);
-int tpm_getburst(struct tpm_softc *);
+int tpm_getburst(struct tpm_softc *, int *);
u_int8_t tpm_status(struct tpm_softc *);
-int tpm_tmotohz(int);
int tpm_legacy_probe(bus_space_tag_t, bus_addr_t);
-int tpm_legacy_init(struct tpm_softc *, int, const char *);
+int tpm_legacy_init(struct tpm_softc *, const char *);
int tpm_legacy_start(struct tpm_softc *, int);
int tpm_legacy_read(struct tpm_softc *, void *, int, size_t *, int);
int tpm_legacy_write(struct tpm_softc *, void *, int);
int tpm_legacy_end(struct tpm_softc *, int, int);
+static int tpm_attach_finish(struct tpm_softc *);
+static void tpm_delayed_attach(void *);
static int tpm_transmit_header(struct tpm_softc *, uint32_t, uint32_t *);
static void tpm_tis12_abort(struct tpm_softc *);
static int tpm_tis12_devid_index(uint32_t);
@@ -202,6 +204,13 @@
static void tpm_tis12_relinquish_locality(struct tpm_softc *);
static int tpm_tis12_request_locality(struct tpm_softc *, int);
static int tpm_tis12_resume(struct tpm_softc *);
+static int tpm_tis12_setup_intr(struct tpm_softc *);
+static int tpm_tis12_fallback(struct tpm_softc *);
+static void tpm_tis12_intr_task(void *, int);
+static void tpm_tis12_intr_failed(struct tpm_softc *);
+static bool tpm_tis12_mask_intr(struct tpm_softc *);
+static void tpm_tis12_arm_intr(struct tpm_softc *);
+static void tpm_tis12_finish_intr(struct tpm_softc *);
/*
@@ -218,26 +227,34 @@
int
tpm_attach(device_t dev)
{
- struct make_dev_args args;
struct tpm_softc *sc;
- rman_res_t irq_start;
void *intr_cookie;
- int error, irq;
+ int error;
sc = device_get_softc(dev);
+ sc->sc_dev = dev;
+ sc->sc_intr_hook.ich_state = ICHS_DONE;
sx_init(&sc->sc_lock, "TPM driver lock");
mtx_init(&sc->sc_intr_lock, "TPM interrupt lock", NULL, MTX_DEF);
cv_init(&sc->sc_intr_cv, "tpm_intr");
sc->intr_cookie = NULL;
sc->irq_res = NULL;
sc->sc_cdev = NULL;
- sc->sc_vector = IRQUNK;
sc->sc_capabilities = 0;
sc->sc_flags = 0;
sc->sc_suspend = 0;
sc->sc_dying = false;
sc->sc_locality = false;
sc->sc_command_pending = false;
+ sc->sc_irq_usable = false;
+ sc->sc_interrupts = false;
+ sc->sc_intr_testing = false;
+ sc->sc_intr_configuring = false;
+ sc->sc_intr_fault = false;
+ sc->sc_intr_armed = false;
+ sc->sc_intr_received = false;
+ sc->sc_irq_config.valid = false;
+ TASK_INIT(&sc->sc_intr_task, 0, tpm_tis12_intr_task, sc);
sc->mem_rid = 0;
sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid,
@@ -252,7 +269,7 @@
sc->irq_rid = 0;
sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irq_rid,
- RF_ACTIVE | RF_SHAREABLE);
+ RF_ACTIVE);
if (tpm_legacy_probe(sc->sc_bt, sc->sc_bh)) {
sc->sc_init = tpm_legacy_init;
sc->sc_start = tpm_legacy_start;
@@ -267,69 +284,108 @@
sc->sc_end = tpm_tis12_end;
}
- irq = IRQUNK;
- if (sc->irq_res != NULL) {
- irq_start = rman_get_start(sc->irq_res);
- /*
- * The TIS path uses the resource IRQ as the LPC SIRQ selector.
- * Parent IRQ numbers need not map directly to SIRQ channels.
- * Validate the full resource value before narrowing and use
- * polling for routes outside the supported range.
- */
- if (sc->sc_init == tpm_tis12_init &&
- (irq_start == 0 || irq_start > 15))
- device_printf(dev,
- "cannot use IRQ %ju; using polling\n",
- (uintmax_t)irq_start);
- else
- irq = irq_start;
- }
-
printf("%s", device_get_name(dev));
sx_xlock(&sc->sc_lock);
- error = sc->sc_init(sc, irq, "tpm");
- sx_xunlock(&sc->sc_lock);
+ error = sc->sc_init(sc, "tpm");
if (error != 0) {
+ sx_xunlock(&sc->sc_lock);
error = ENXIO;
goto fail;
}
if (sc->sc_init == tpm_tis12_init && sc->irq_res != NULL) {
- if (sc->sc_vector == IRQUNK) {
- if (bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid,
- sc->irq_res) == 0)
- sc->irq_res = NULL;
- } else {
+ if (sc->sc_irq_usable && (TPM_CONFIGURE_IRQ(dev, sc->irq_res,
+ &sc->sc_irq_config, false) != 0 ||
+ !tpm_irq_supported(sc->sc_capabilities,
+ &sc->sc_irq_config))) {
+ sc->sc_irq_usable = false;
+ device_printf(dev,
+ "IRQ route unavailable; using polling\n");
+ }
+ if (sc->sc_irq_usable) {
+ /* Recovery sleeps with an exclusive source masked. */
/* Failed setup may leave a stale output cookie. */
intr_cookie = NULL;
error = bus_setup_intr(dev, sc->irq_res,
- INTR_TYPE_TTY | INTR_MPSAFE, NULL, tpm_intr, sc,
- &intr_cookie);
- if (error != 0) {
- printf(": cannot establish interrupt\n");
- error = ENXIO;
- goto fail;
- }
- sc->intr_cookie = intr_cookie;
+ INTR_TYPE_TTY | INTR_MPSAFE | INTR_EXCL |
+ INTR_SLEEPABLE, NULL, tpm_intr, sc, &intr_cookie);
+ if (error == 0)
+ sc->intr_cookie = intr_cookie;
+ else
+ device_printf(dev,
+ "IRQ setup failed; using polling\n");
+ }
+ if (sc->intr_cookie == NULL)
+ error = tpm_tis12_fallback(sc);
+ if (error != 0) {
+ sx_xunlock(&sc->sc_lock);
+ error = ENXIO;
+ goto fail;
}
}
+ sx_xunlock(&sc->sc_lock);
+
+ /* IRQ validation needs timers and a runnable interrupt thread. */
+ if (cold && sc->intr_cookie != NULL) {
+ sc->sc_intr_hook.ich_func = tpm_delayed_attach;
+ sc->sc_intr_hook.ich_arg = sc;
+ if (config_intrhook_establish(&sc->sc_intr_hook) == 0)
+ return (0);
+ sc->sc_intr_hook.ich_state = ICHS_DONE;
+ error = ENXIO;
+ goto fail;
+ }
+ sx_xlock(&sc->sc_lock);
+ error = tpm_attach_finish(sc);
+ sx_xunlock(&sc->sc_lock);
+ if (error != 0)
+ goto fail;
+ return (0);
+
+fail:
+ tpm_detach(dev);
+ return (error);
+}
+
+/* Publish the device only after the route test has finished. */
+static int
+tpm_attach_finish(struct tpm_softc *sc)
+{
+ struct make_dev_args args;
+ int error;
+ sx_assert(&sc->sc_lock, SA_XLOCKED);
+ if (sc->intr_cookie != NULL) {
+ error = tpm_tis12_setup_intr(sc);
+ if (error != 0)
+ return (ENXIO);
+ }
make_dev_args_init(&args);
args.mda_devsw = &tpm_cdevsw;
- args.mda_unit = device_get_unit(dev);
+ args.mda_unit = device_get_unit(sc->sc_dev);
args.mda_uid = UID_ROOT;
args.mda_gid = GID_WHEEL;
args.mda_mode = 0600;
args.mda_si_drv1 = sc;
- error = make_dev_s(&args, &sc->sc_cdev, "tpm");
- if (error != 0)
- goto fail;
+ return (make_dev_s(&args, &sc->sc_cdev, "tpm"));
+}
- return (0);
+static void
+tpm_delayed_attach(void *arg)
+{
+ struct tpm_softc *sc = arg;
+ int error;
-fail:
- tpm_detach(dev);
- return (error);
+ sx_xlock(&sc->sc_lock);
+ error = tpm_attach_finish(sc);
+ if (error != 0) {
+ mtx_lock(&sc->sc_intr_lock);
+ sc->sc_dying = true;
+ mtx_unlock(&sc->sc_intr_lock);
+ device_printf(sc->sc_dev, "deferred attach failed: %d\n", error);
+ }
+ sx_xunlock(&sc->sc_lock);
+ config_intrhook_disestablish(&sc->sc_intr_hook);
}
int
@@ -338,9 +394,13 @@
struct tpm_softc *sc;
sc = device_get_softc(dev);
+ config_intrhook_drain(&sc->sc_intr_hook);
sx_xlock(&sc->sc_lock);
+ mtx_lock(&sc->sc_intr_lock);
sc->sc_dying = true;
+ mtx_unlock(&sc->sc_intr_lock);
sx_xunlock(&sc->sc_lock);
+ taskqueue_drain(taskqueue_thread, &sc->sc_intr_task);
/*
* Prevent new methods from touching the transport. Do not hold the
@@ -455,31 +515,18 @@
}
mtx_lock(&sc->sc_intr_lock);
- reg = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE);
- bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE,
- reg & ~TPM_GLOBAL_INT_ENABLE);
- bus_space_barrier(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE, 4,
- BUS_SPACE_BARRIER_WRITE);
- reg = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE);
- error = (reg & TPM_GLOBAL_INT_ENABLE) != 0 ? EIO : 0;
- bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_INT_STATUS,
- bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_INT_STATUS));
- bus_space_barrier(sc->sc_bt, sc->sc_bh, TPM_INT_STATUS, 4,
- BUS_SPACE_BARRIER_WRITE);
+ error = tpm_tis12_mask_intr(sc) ? 0 : EIO;
+ sc->sc_intr_armed = false;
mtx_unlock(&sc->sc_intr_lock);
if (release)
tpm_tis12_relinquish_locality(sc);
return (error);
}
-/*
- * Setup the interrupt vector if one is provided and interrupts are known
- * to work on that particular chip. The caller must hold locality zero.
- */
+/* Quiesce before handler installation; keep the known-device quirks. */
int
-tpm_tis12_irqinit(struct tpm_softc *sc, int irq, int idx)
+tpm_tis12_irqinit(struct tpm_softc *sc, int idx)
{
- u_int32_t r;
int error;
sx_assert(&sc->sc_lock, SA_XLOCKED);
@@ -487,28 +534,8 @@
error = tpm_tis12_quiesce(sc);
if (error != 0)
return (error);
-
- mtx_lock(&sc->sc_intr_lock);
- if (irq < 1 || irq > 15 || (tpm_devs[idx].flags & TPM_DEV_NOINTS)) {
- sc->sc_vector = IRQUNK;
- mtx_unlock(&sc->sc_intr_lock);
- return (0);
- }
-
- /* Program interrupt vector. */
- bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_INT_VECTOR, irq);
- sc->sc_vector = irq;
-
- /* Program interrupt type. */
- if (sc->sc_capabilities & TPM_INTF_INT_EDGE_RISING)
- r = TPM_INT_EDGE_RISING;
- else if (sc->sc_capabilities & TPM_INTF_INT_LEVEL_HIGH)
- r = TPM_INT_LEVEL_HIGH;
- else
- r = TPM_INT_LEVEL_LOW;
- bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE, r);
-
- mtx_unlock(&sc->sc_intr_lock);
+ sc->sc_irq_usable = sc->irq_res != NULL &&
+ (tpm_devs[idx].flags & TPM_DEV_NOINTS) == 0;
return (0);
}
@@ -523,9 +550,218 @@
return (i);
}
+/* Called with sc_intr_lock held. Never take sc_lock in the handler. */
+static void
+tpm_tis12_intr_failed(struct tpm_softc *sc)
+{
+
+ sc->sc_interrupts = false;
+ if (!sc->sc_intr_fault && !sc->sc_dying) {
+ sc->sc_intr_fault = true;
+ taskqueue_enqueue(taskqueue_thread, &sc->sc_intr_task);
+ }
+ cv_broadcast(&sc->sc_intr_cv);
+}
+
+static bool
+tpm_tis12_mask_intr(struct tpm_softc *sc)
+{
+ uint32_t reg;
+ bool disabled;
+
+ mtx_assert(&sc->sc_intr_lock, MA_OWNED);
+ reg = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE);
+ bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE,
+ reg & ~(TPM_GLOBAL_INT_ENABLE | TPM_IRQ_EVENTS));
+ bus_space_barrier(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE, 4,
+ BUS_SPACE_BARRIER_WRITE);
+ disabled = (bus_space_read_4(sc->sc_bt, sc->sc_bh,
+ TPM_INTERRUPT_ENABLE) & TPM_GLOBAL_INT_ENABLE) == 0;
+ bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_INT_STATUS,
+ bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_INT_STATUS));
+ bus_space_barrier(sc->sc_bt, sc->sc_bh, TPM_INT_STATUS, 4,
+ BUS_SPACE_BARRIER_WRITE);
+ return (disabled);
+}
+
+static void
+tpm_tis12_arm_intr(struct tpm_softc *sc)
+{
+
+ mtx_lock(&sc->sc_intr_lock);
+ sc->sc_intr_received = false;
+ if ((sc->sc_interrupts || sc->sc_intr_testing) && !sc->sc_intr_fault) {
+ if (tpm_tis12_mask_intr(sc) && (bus_space_read_4(sc->sc_bt,
+ sc->sc_bh, TPM_INT_STATUS) & TPM_DATA_AVAIL_INT) == 0) {
+ sc->sc_intr_armed = true;
+ bus_space_write_4(sc->sc_bt, sc->sc_bh,
+ TPM_INTERRUPT_ENABLE, sc->sc_intr_mode |
+ TPM_DATA_AVAIL_INT | TPM_GLOBAL_INT_ENABLE);
+ bus_space_barrier(sc->sc_bt, sc->sc_bh,
+ TPM_INTERRUPT_ENABLE, 4, BUS_SPACE_BARRIER_WRITE);
+ } else {
+ tpm_tis12_intr_failed(sc);
+ }
+ }
+ mtx_unlock(&sc->sc_intr_lock);
+}
+
+static void
+tpm_tis12_finish_intr(struct tpm_softc *sc)
+{
+ sbintime_t deadline;
+
+ deadline = sbinuptime() + mstosbt(TPM_IRQ_GRACE_MS);
+ mtx_lock(&sc->sc_intr_lock);
+ while (sc->sc_intr_armed && !sc->sc_intr_received &&
+ !sc->sc_intr_fault) {
+ if (cv_timedwait_sbt(&sc->sc_intr_cv, &sc->sc_intr_lock,
+ deadline, 0, C_ABSOLUTE | C_HARDCLOCK) == EWOULDBLOCK) {
+ tpm_tis12_intr_failed(sc);
+ break;
+ }
+ }
+ mtx_unlock(&sc->sc_intr_lock);
+}
+
+static int
+tpm_tis12_fallback(struct tpm_softc *sc)
+{
+ int error;
+
+ sx_assert(&sc->sc_lock, SA_XLOCKED);
+ mtx_lock(&sc->sc_intr_lock);
+ sc->sc_interrupts = false;
+ sc->sc_intr_testing = false;
+ sc->sc_intr_armed = false;
+ mtx_unlock(&sc->sc_intr_lock);
+ error = tpm_tis12_quiesce(sc);
+ if (error != 0) {
+ sc->sc_suspend = 1;
+ device_printf(sc->sc_dev,
+ "failed to quiesce TPM interrupts; suspending device\n");
+ }
+ if (sc->intr_cookie != NULL) {
+ bus_teardown_intr(sc->sc_dev, sc->irq_res, sc->intr_cookie);
+ sc->intr_cookie = NULL;
+ if (error == 0)
+ device_printf(sc->sc_dev,
+ "interrupts unavailable; using polling\n");
+ }
+ if (sc->irq_res != NULL && bus_release_resource(sc->sc_dev, SYS_RES_IRQ,
+ sc->irq_rid, sc->irq_res) == 0)
+ sc->irq_res = NULL;
+ sc->sc_irq_usable = false;
+ return (error);
+}
+
+static void
+tpm_tis12_intr_task(void *arg, int pending)
+{
+ struct tpm_softc *sc = arg;
+
+ sx_xlock(&sc->sc_lock);
+ if (!sc->sc_dying)
+ tpm_tis12_fallback(sc);
+ sx_xunlock(&sc->sc_lock);
+}
+
+static int
+tpm_tis12_setup_intr(struct tpm_softc *sc)
+{
+ struct tpm_irq_config config;
+ uint8_t response[15];
+ uint32_t length;
+ bool working;
+ int error, quiesce_error;
+ static const uint8_t cmd[] = {
+ 0x00, 0xc1, /* TPM_TAG_RQU_COMMAND */
+ 0x00, 0x00, 0x00, 0x0e, /* Command length */
+ 0x00, 0x00, 0x00, 0x46, /* TPM_ORD_GetRandom */
+ 0x00, 0x00, 0x00, 0x01 /* One byte */
+ };
+
+ sx_assert(&sc->sc_lock, SA_XLOCKED);
+ mtx_lock(&sc->sc_intr_lock);
+ sc->sc_interrupts = false;
+ working = !sc->sc_intr_fault && !sc->sc_dying && sc->sc_irq_usable;
+ mtx_unlock(&sc->sc_intr_lock);
+ if (!working || tpm_tis12_request_locality(sc, 0) != 0)
+ goto fallback;
+ config = sc->sc_irq_config;
+ mtx_lock(&sc->sc_intr_lock);
+ sc->sc_intr_configuring = true;
+ mtx_unlock(&sc->sc_intr_lock);
+ error = TPM_CONFIGURE_IRQ(sc->sc_dev, sc->irq_res, &config, true);
+ /* _SRS may alter the enable register, including on failure. */
+ quiesce_error = tpm_tis12_quiesce(sc);
+ mtx_lock(&sc->sc_intr_lock);
+ sc->sc_intr_configuring = false;
+ mtx_unlock(&sc->sc_intr_lock);
+ if (quiesce_error != 0 || error != 0 ||
+ !tpm_irq_supported(sc->sc_capabilities, &config)) {
+ tpm_tis12_relinquish_locality(sc);
+ goto fallback;
+ }
+ if (config.sirq != TPM_IRQ_SIRQ_UNCHANGED)
+ bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_INT_VECTOR,
+ config.sirq);
+ sc->sc_intr_mode = config.mode;
+ sc->sc_irq_config = config;
+ bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE,
+ config.mode);
+ bus_space_barrier(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE, 4,
+ BUS_SPACE_BARRIER_WRITE);
+ working = (bus_space_read_4(sc->sc_bt, sc->sc_bh,
+ TPM_INTERRUPT_ENABLE) & (TPM_GLOBAL_INT_ENABLE |
+ TPM_IRQ_MODE_MASK)) == config.mode;
+ tpm_tis12_relinquish_locality(sc);
+ if (!working)
+ goto fallback;
+
+ mtx_lock(&sc->sc_intr_lock);
+ sc->sc_intr_testing = true;
+ sc->sc_intr_received = false;
+ mtx_unlock(&sc->sc_intr_lock);
+ error = tpm_tis12_start(sc, UIO_WRITE);
+ if (error == 0)
+ error = tpm_tis12_write(sc, __DECONST(void *, cmd),
+ sizeof(cmd));
+ if (error == 0)
+ error = tpm_tis12_end(sc, UIO_WRITE, 0);
+ if (error == 0)
+ error = tpm_tis12_start(sc, UIO_READ);
+ if (error == 0)
+ error = tpm_tis12_read(sc, response, 10, NULL, TPM_PARAM_SIZE);
+ if (error == 0) {
+ length = be32dec(response + 2);
+ if (be16dec(response) != TPM_TAG_RSP_COMMAND ||
+ length < 10 || length > sizeof(response))
+ error = EIO;
+ else
+ error = tpm_tis12_read(sc, response + 10, length - 10,
+ NULL, TPM_PARAM_SIZE);
+ }
+ if (error == 0)
+ error = tpm_tis12_end(sc, UIO_READ, 0);
+ else
+ tpm_tis12_abort(sc);
+ mtx_lock(&sc->sc_intr_lock);
+ sc->sc_intr_testing = false;
+ working = error == 0 && sc->sc_intr_received && !sc->sc_intr_fault;
+ sc->sc_interrupts = working;
+ mtx_unlock(&sc->sc_intr_lock);
+ if (working) {
+ device_printf(sc->sc_dev, "using verified interrupts\n");
+ return (0);
+ }
+fallback:
+ return (tpm_tis12_fallback(sc));
+}
+
/* Setup TPM using TIS 1.2 interface. */
int
-tpm_tis12_init(struct tpm_softc *sc, int irq, const char *name)
+tpm_tis12_init(struct tpm_softc *sc, const char *name)
{
u_int32_t r;
int error, i;
@@ -556,7 +792,7 @@
if (error != 0)
return 1;
- error = tpm_tis12_irqinit(sc, irq, i);
+ error = tpm_tis12_irqinit(sc, i);
if (error != 0) {
printf("%s: failed to quiesce interrupts during attach: %d\n",
name, error);
@@ -565,8 +801,7 @@
/* Abort whatever it thought it was doing. */
bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_STS, TPM_STS_CMD_READY);
- error = tpm_waitfor(sc, TPM_STS_CMD_READY, TPM_READY_TMO,
- sc->sc_write);
+ error = tpm_waitfor(sc, TPM_STS_CMD_READY, TPM_READY_TMO);
out:
tpm_tis12_relinquish_locality(sc);
@@ -578,9 +813,15 @@
tpm_tis12_resume(struct tpm_softc *sc)
{
uint32_t capabilities, devid;
- int error, i, irq;
+ int error, i;
sx_assert(&sc->sc_lock, SA_XLOCKED);
+ mtx_lock(&sc->sc_intr_lock);
+ sc->sc_interrupts = false;
+ mtx_unlock(&sc->sc_intr_lock);
+ error = tpm_tis12_quiesce(sc);
+ if (error != 0)
+ return (error);
capabilities = bus_space_read_4(sc->sc_bt, sc->sc_bh,
TPM_INTF_CAPABILITIES);
if ((capabilities & TPM_CAPSREQ) != TPM_CAPSREQ ||
@@ -593,21 +834,21 @@
sc->sc_capabilities = capabilities;
i = tpm_tis12_devid_index(devid);
- irq = sc->sc_vector;
error = tpm_tis12_request_locality(sc, 0);
if (error != 0)
return (error);
- error = tpm_tis12_irqinit(sc, irq, i);
+ error = tpm_tis12_irqinit(sc, i);
if (error != 0)
goto out;
/* Abort firmware residue and leave the command FIFO ready. */
bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_STS,
TPM_STS_CMD_READY);
- error = tpm_waitfor(sc, TPM_STS_CMD_READY, TPM_READY_TMO,
- sc->sc_write);
+ error = tpm_waitfor(sc, TPM_STS_CMD_READY, TPM_READY_TMO);
out:
tpm_tis12_relinquish_locality(sc);
+ if (error == 0 && sc->intr_cookie != NULL)
+ error = tpm_tis12_setup_intr(sc);
return (error);
}
@@ -625,8 +866,9 @@
static int
tpm_tis12_request_locality(struct tpm_softc *sc, int flags)
{
+ sbintime_t deadline;
u_int32_t r;
- int to, rv;
+ int rv;
sx_assert(&sc->sc_lock, SA_XLOCKED);
KASSERT(!sc->sc_locality, ("%s: locality already owned", __func__));
@@ -634,21 +876,27 @@
if ((bus_space_read_1(sc->sc_bt, sc->sc_bh, TPM_ACCESS) &
(TPM_ACCESS_VALID | TPM_ACCESS_ACTIVE_LOCALITY)) ==
(TPM_ACCESS_VALID | TPM_ACCESS_ACTIVE_LOCALITY)) {
+ mtx_lock(&sc->sc_intr_lock);
sc->sc_locality = true;
+ mtx_unlock(&sc->sc_intr_lock);
return 0;
}
+ mtx_lock(&sc->sc_intr_lock);
sc->sc_locality = false;
+ mtx_unlock(&sc->sc_intr_lock);
bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_ACCESS,
TPM_ACCESS_REQUEST_USE);
bus_space_barrier(sc->sc_bt, sc->sc_bh, TPM_ACCESS, 1,
BUS_SPACE_BARRIER_WRITE);
- to = tpm_tmotohz(TPM_ACCESS_TMO);
+ deadline = sbinuptime() + mstosbt(TPM_ACCESS_TMO);
while ((r = bus_space_read_1(sc->sc_bt, sc->sc_bh, TPM_ACCESS) &
(TPM_ACCESS_VALID | TPM_ACCESS_ACTIVE_LOCALITY)) !=
- (TPM_ACCESS_VALID | TPM_ACCESS_ACTIVE_LOCALITY) && to--) {
+ (TPM_ACCESS_VALID | TPM_ACCESS_ACTIVE_LOCALITY)) {
+ if (sbinuptime() >= deadline)
+ break;
rv = pause_sbt("tpm_locality", tick_sbt, 0,
C_HARDCLOCK | ((flags & PCATCH) != 0 ? C_CATCH : 0));
if (rv && rv != EWOULDBLOCK) {
@@ -669,7 +917,9 @@
return EBUSY;
}
+ mtx_lock(&sc->sc_intr_lock);
sc->sc_locality = true;
+ mtx_unlock(&sc->sc_intr_lock);
return 0;
}
@@ -691,8 +941,13 @@
sc->sc_command_pending = false;
if (!sc->sc_locality)
return;
+ mtx_lock(&sc->sc_intr_lock);
+ if (!tpm_tis12_mask_intr(sc))
+ tpm_tis12_intr_failed(sc);
+ sc->sc_intr_armed = false;
tpm_tis12_release_locality_hw(sc->sc_bt, sc->sc_bh);
sc->sc_locality = false;
+ mtx_unlock(&sc->sc_intr_lock);
}
static void
@@ -707,36 +962,26 @@
}
int
-tpm_getburst(struct tpm_softc *sc)
+tpm_getburst(struct tpm_softc *sc, int *burst)
{
- int burst, to, rv;
+ sbintime_t deadline;
+ int rv;
sx_assert(&sc->sc_lock, SA_XLOCKED);
- to = tpm_tmotohz(TPM_BURST_TMO);
-
- burst = 0;
- while (burst == 0 && to--) {
- /*
- * Burst count has to be read from bits 8 to 23 without
- * touching any other bits, eg. the actual status bits 0
- * to 7.
- */
- burst = bus_space_read_1(sc->sc_bt, sc->sc_bh, TPM_STS + 1);
- burst |= bus_space_read_1(sc->sc_bt, sc->sc_bh, TPM_STS + 2)
+ deadline = sbinuptime() + mstosbt(TPM_BURST_TMO);
+ for (;;) {
+ /* Read bits 8 to 23 without touching the status byte. */
+ *burst = bus_space_read_1(sc->sc_bt, sc->sc_bh, TPM_STS + 1);
+ *burst |= bus_space_read_1(sc->sc_bt, sc->sc_bh, TPM_STS + 2)
<< 8;
-#ifdef TPM_DEBUG
- printf("tpm_getburst: read %d\n", burst);
-#endif
- if (burst)
- return burst;
-
+ if (*burst != 0)
+ return (0);
+ if (sbinuptime() >= deadline)
+ return (EIO);
rv = pause_sig("tpm_getburst", 1);
- if (rv && rv != EWOULDBLOCK) {
- return 0;
- }
+ if (rv != 0 && rv != EWOULDBLOCK)
+ return (rv);
}
-
- return 0;
}
u_int8_t
@@ -750,16 +995,6 @@
return status;
}
-int
-tpm_tmotohz(int tmo)
-{
- struct timeval tv;
-
- tv.tv_sec = tmo / 1000;
- tv.tv_usec = 1000 * (tmo % 1000);
-
- return tvtohz(&tv);
-}
/*
* Transmit a command with no parameters and consume its header-only reply.
@@ -889,95 +1124,48 @@
/* Wait for given status bits using polling. */
int
-tpm_waitfor_poll(struct tpm_softc *sc, u_int8_t mask, int tmo, void *c)
+tpm_waitfor_poll(struct tpm_softc *sc, u_int8_t mask, sbintime_t deadline)
{
int rv;
sx_assert(&sc->sc_lock, SA_XLOCKED);
- /*
- * Poll until either the requested condition or a time out is
- * met.
- */
- while (((sc->sc_stat = tpm_status(sc)) & mask) != mask && tmo--) {
+ for (;;) {
+ sc->sc_stat = tpm_status(sc);
+ if ((sc->sc_stat & mask) == mask)
+ return (0);
+ if (sbinuptime() >= deadline)
+ return (EWOULDBLOCK);
rv = pause_sig("tpm_poll", 1);
- if (rv && rv != EWOULDBLOCK) {
-#ifdef TPM_DEBUG
- printf("tpm_waitfor_poll: interrupted %d\n", rv);
-#endif
- return rv;
- }
+ if (rv != 0 && rv != EWOULDBLOCK)
+ return (rv);
}
-
- return 0;
}
-/* Wait for given status bits using interrupts. */
+/* Use bounded sleeps so a lost interrupt still permits polling progress. */
int
-tpm_waitfor_int(struct tpm_softc *sc, u_int8_t mask, int tmo, int inttype)
+tpm_waitfor_int(struct tpm_softc *sc, u_int8_t mask, sbintime_t deadline)
{
- sbintime_t deadline;
int rv;
sx_assert(&sc->sc_lock, SA_XLOCKED);
mtx_lock(&sc->sc_intr_lock);
-
- /* Poll and return when condition is already met. */
- sc->sc_stat = tpm_status(sc);
- if ((sc->sc_stat & mask) == mask) {
- rv = 0;
- goto out;
- }
-
- /*
- * The handler takes sc_intr_lock before acknowledging and waking us,
- * so an event cannot be lost between the status check and CV wait.
- */
- bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE,
- bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE) |
- inttype);
- bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE,
- bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE) |
- TPM_GLOBAL_INT_ENABLE);
-
- /*
- * Poll once more to remedy the race between previous polling
- * and enabling interrupts on the tpm chip.
- */
- sc->sc_stat = tpm_status(sc);
- if ((sc->sc_stat & mask) == mask) {
- rv = 0;
- goto disable;
- }
-
- deadline = sbinuptime() + mstosbt(tmo);
-#ifdef TPM_DEBUG
- printf("tpm_waitfor_int: sleeping for %d ms\n", tmo);
-#endif
- do {
- rv = cv_timedwait_sig_sbt(&sc->sc_intr_cv,
- &sc->sc_intr_lock, deadline, 0,
- C_ABSOLUTE | C_HARDCLOCK);
+ for (;;) {
sc->sc_stat = tpm_status(sc);
if ((sc->sc_stat & mask) == mask) {
rv = 0;
break;
}
- } while (rv == 0);
-#ifdef TPM_DEBUG
- printf("tpm_waitfor_int: woke up with rv %d stat %b\n", rv,
- sc->sc_stat, TPM_STS_BITS);
-#endif
-
- /* Disable interrupts on tpm chip again. */
-disable:
- bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE,
- bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE) &
- ~TPM_GLOBAL_INT_ENABLE);
- bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE,
- bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE) &
- ~inttype);
-
-out:
+ if (sbinuptime() >= deadline) {
+ tpm_tis12_intr_failed(sc);
+ rv = EWOULDBLOCK;
+ break;
+ }
+ rv = cv_timedwait_sig_sbt(&sc->sc_intr_cv, &sc->sc_intr_lock,
+ MIN(deadline, sbinuptime() + mstosbt(TPM_IRQ_POLL_MS)), 0,
+ C_ABSOLUTE | C_HARDCLOCK);
+ if (rv != 0 && rv != EWOULDBLOCK)
+ break;
+ }
mtx_unlock(&sc->sc_intr_lock);
return (rv);
}
@@ -986,96 +1174,32 @@
* Wait on given status bits, uses interrupts where possible, otherwise polls.
*/
int
-tpm_waitfor(struct tpm_softc *sc, u_int8_t b0, int tmo, void *c)
+tpm_waitfor(struct tpm_softc *sc, u_int8_t mask, int tmo)
{
- u_int8_t b;
- int re, to, rv;
+ sbintime_t deadline;
+ bool interrupts;
+ int retries, rv;
-#ifdef TPM_DEBUG
- printf("tpm_waitfor: b0 %b\n", b0, TPM_STS_BITS);
-#endif
sx_assert(&sc->sc_lock, SA_XLOCKED);
-
- /*
- * If possible, use interrupts, otherwise poll.
- *
- * We use interrupts for TPM_STS_VALID and TPM_STS_DATA_AVAIL (if
- * the tpm chips supports them) as waiting for those can take
- * really long. The other TPM_STS* are not needed very often
- * so we do not support them.
- */
- if (sc->sc_vector != IRQUNK && sc->intr_cookie != NULL) {
- b = b0;
-
- /*
- * Wait for data ready. This interrupt only occurs
- * when both TPM_STS_VALID and TPM_STS_DATA_AVAIL are asserted.
- * Thus we don't have to bother with TPM_STS_VALID
- * separately and can just return.
- *
- * This only holds for interrupts! When using polling
- * both flags have to be waited for, see below.
- */
- if ((b & TPM_STS_DATA_AVAIL) && (sc->sc_capabilities &
- TPM_INTF_DATA_AVAIL_INT))
- return tpm_waitfor_int(sc, b, tmo,
- TPM_DATA_AVAIL_INT);
-
- /* Wait for status valid bit. */
- if ((b & TPM_STS_VALID) && (sc->sc_capabilities &
- TPM_INTF_STS_VALID_INT)) {
- rv = tpm_waitfor_int(sc, b, tmo, TPM_STS_VALID_INT);
- if (rv != 0)
- return rv;
- else
- b = b0 & ~TPM_STS_VALID;
- }
-
- /*
- * When all flags are taken care of, return. Otherwise
- * use polling for eg. TPM_STS_CMD_READY.
- */
- if (b == 0)
- return 0;
- }
-
- re = 3;
-restart:
- /*
- * If requested wait for TPM_STS_VALID before dealing with
- * any other flag. Eg. when both TPM_STS_DATA_AVAIL and TPM_STS_VALID
- * are requested, wait for the latter first.
- */
- b = b0;
- if (b0 & TPM_STS_VALID)
- b = TPM_STS_VALID;
-
- to = tpm_tmotohz(tmo);
-again:
- if ((rv = tpm_waitfor_poll(sc, b, to, c)) != 0)
- return rv;
-
- if ((b & sc->sc_stat) == TPM_STS_VALID) {
- /* Now wait for other flags. */
- b = b0 & ~TPM_STS_VALID;
- to++;
- goto again;
+ mtx_lock(&sc->sc_intr_lock);
+ interrupts = sc->sc_interrupts;
+ mtx_unlock(&sc->sc_intr_lock);
+ /* Keep interrupt timeouts out of the polling response-retry loop. */
+ if (interrupts && (mask & TPM_STS_DATA_AVAIL) != 0) {
+ deadline = sbinuptime() + mstosbt(tmo);
+ return (tpm_waitfor_int(sc, mask, deadline));
}
-
- if ((sc->sc_stat & b) != b) {
-#ifdef TPM_DEBUG
- printf("tpm_waitfor: timeout: stat=%b b=%b\n",
- sc->sc_stat, TPM_STS_BITS, b, TPM_STS_BITS);
-#endif
- if (re-- && (b0 & TPM_STS_VALID)) {
- bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_STS,
- TPM_STS_RESP_RETRY);
- goto restart;
- }
- return EIO;
+ for (retries = 3;; retries--) {
+ deadline = sbinuptime() + mstosbt(tmo);
+ rv = tpm_waitfor_poll(sc, mask, deadline);
+ if (rv != EWOULDBLOCK)
+ return (rv);
+ if (retries == 0 || (mask & TPM_STS_VALID) == 0)
+ return (EIO);
+ /* Retry the complete response, including its VALID indication. */
+ bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_STS,
+ TPM_STS_RESP_RETRY);
}
-
- return 0;
}
/* Start transaction. */
@@ -1091,9 +1215,11 @@
return (EIO);
}
rv = tpm_waitfor(sc, TPM_STS_DATA_AVAIL | TPM_STS_VALID,
- TPM_READ_TMO, sc->sc_read);
+ TPM_READ_TMO);
if (rv != 0)
tpm_tis12_abort(sc);
+ else
+ tpm_tis12_finish_intr(sc);
return (rv);
}
@@ -1119,8 +1245,7 @@
/* Abort previous and restart. */
bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_STS, TPM_STS_CMD_READY);
- if ((rv = tpm_waitfor(sc, TPM_STS_CMD_READY, TPM_READY_TMO,
- sc->sc_write))) {
+ if ((rv = tpm_waitfor(sc, TPM_STS_CMD_READY, TPM_READY_TMO))) {
#ifdef TPM_DEBUG
printf("tpm_tis12_start: UIO_WRITE readying failed %d\n", rv);
#endif
@@ -1150,10 +1275,12 @@
cnt = 0;
while (len > 0) {
if ((rv = tpm_waitfor(sc, TPM_STS_DATA_AVAIL | TPM_STS_VALID,
- TPM_READ_TMO, sc->sc_read)))
+ TPM_READ_TMO)))
return rv;
- bcnt = tpm_getburst(sc);
+ rv = tpm_getburst(sc, &bcnt);
+ if (rv != 0)
+ return (rv);
n = MIN(len, bcnt);
#ifdef TPM_DEBUG
printf("tpm_tis12_read: fetching %d, burst is %d\n", n, bcnt);
@@ -1193,11 +1320,14 @@
cnt = 0;
while (cnt < len - 1) {
- for (r = tpm_getburst(sc); r > 0 && cnt < len - 1; r--) {
+ rv = tpm_getburst(sc, &r);
+ if (rv != 0)
+ return (rv);
+ for (; r > 0 && cnt < len - 1; r--) {
bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_DATA, *p++);
cnt++;
}
- if ((rv = tpm_waitfor(sc, TPM_STS_VALID, TPM_READ_TMO, sc))) {
+ if ((rv = tpm_waitfor(sc, TPM_STS_VALID, TPM_READ_TMO))) {
#ifdef TPM_DEBUG
printf("tpm_tis12_write: failed burst rv %d\n", rv);
#endif
@@ -1216,7 +1346,7 @@
bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_DATA, *p++);
cnt++;
- if ((rv = tpm_waitfor(sc, TPM_STS_VALID, TPM_READ_TMO, sc))) {
+ if ((rv = tpm_waitfor(sc, TPM_STS_VALID, TPM_READ_TMO))) {
#ifdef TPM_DEBUG
printf("tpm_tis12_write: failed last byte rv %d\n", rv);
#endif
@@ -1250,8 +1380,7 @@
return (err != 0 ? 0 : EIO);
}
if (err == 0)
- rv = tpm_waitfor(sc, TPM_STS_VALID, TPM_READ_TMO,
- sc->sc_read);
+ rv = tpm_waitfor(sc, TPM_STS_VALID, TPM_READ_TMO);
/* Still more data? */
if (err == 0 && rv == 0)
@@ -1283,6 +1412,7 @@
if (err != 0 || rv != 0)
tpm_tis12_abort(sc);
else {
+ tpm_tis12_arm_intr(sc);
bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_STS,
TPM_STS_GO);
sc->sc_command_pending = true;
@@ -1296,31 +1426,30 @@
tpm_intr(void *v)
{
struct tpm_softc *sc = v;
- u_int32_t r;
-#ifdef TPM_DEBUG
- static int cnt = 0;
-#endif
+ uint32_t status, enable;
+ bool disabled;
mtx_lock(&sc->sc_intr_lock);
- r = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_INT_STATUS);
-#ifdef TPM_DEBUG
- if (r != 0)
- printf("tpm_intr: int=%b (%d)\n", r, TPM_INTERRUPT_ENABLE_BITS,
- cnt);
- else
- cnt++;
-#endif
- if (!(r & (TPM_CMD_READY_INT | TPM_LOCALITY_CHANGE_INT |
- TPM_STS_VALID_INT | TPM_DATA_AVAIL_INT))) {
+ status = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_INT_STATUS);
+ enable = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE);
+ if ((enable & TPM_GLOBAL_INT_ENABLE) == 0 ||
+ (status & enable & TPM_IRQ_EVENTS) == 0) {
mtx_unlock(&sc->sc_intr_lock);
return;
}
-
- bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_INT_STATUS, r);
+ /* Mask on the first event before acknowledging a stuck source. */
+ disabled = sc->sc_locality && tpm_tis12_mask_intr(sc);
+ if (disabled && sc->sc_intr_armed &&
+ (status & TPM_DATA_AVAIL_INT) != 0)
+ sc->sc_intr_received = true;
+ else if (!disabled || !sc->sc_intr_configuring)
+ tpm_tis12_intr_failed(sc);
+ sc->sc_intr_armed = false;
cv_broadcast(&sc->sc_intr_cv);
mtx_unlock(&sc->sc_intr_lock);
-
- return;
+ /* Keep the host source masked while command and cleanup threads run. */
+ if (!disabled)
+ pause("tpmirq", 1);
}
/* Read single byte using legacy interface. */
@@ -1388,7 +1517,7 @@
/* Setup TPM using legacy interface. */
int
-tpm_legacy_init(struct tpm_softc *sc, int irq, const char *name)
+tpm_legacy_init(struct tpm_softc *sc, const char *name)
{
char id[8];
int i;
diff --git a/sys/dev/tpm/tpm20.h b/sys/dev/tpm/tpm20.h
--- a/sys/dev/tpm/tpm20.h
+++ b/sys/dev/tpm/tpm20.h
@@ -61,6 +61,7 @@
#include "opt_tpm.h"
#include "tpm_if.h"
+#include "tpm_irq.h"
#define BIT(x) (1U << (x))
@@ -128,13 +129,23 @@
/* Serialize commands and lifecycle; intr_lock nests inside. */
struct sx dev_lock;
+ /* Serialize TIS IRQ I/O with locality release; SPI access may sleep. */
+ struct sx intr_io_lock;
struct mtx intr_lock;
struct cv intr_cv;
void *intr_cookie;
- int intr_type; /* Current event type */
uint32_t intr_generation;
uint32_t intr_mask; /* Saved TIS interrupt configuration */
+ struct tpm_irq_config irq_config;
+ struct task intr_task;
+ struct intr_config_hook intr_hook;
+ bool intr_testing;
+ bool intr_configuring;
+ bool intr_fault;
+ bool intr_armed;
+ bool intr_received;
+ bool locality; /* Protected by intr_io_lock */
bool interrupts;
bool common_initialized;
bool dying;
diff --git a/sys/dev/tpm/tpm_acpi.c b/sys/dev/tpm/tpm_acpi.c
--- a/sys/dev/tpm/tpm_acpi.c
+++ b/sys/dev/tpm/tpm_acpi.c
@@ -36,6 +36,8 @@
#include <isa/isareg.h>
#include <isa/isavar.h>
#include "tpmvar.h"
+#include "tpm_irq.h"
+#include "tpm_if.h"
#include "opt_acpi.h"
#include <contrib/dev/acpica/include/acpi.h>
@@ -68,6 +70,7 @@
DEVMETHOD(device_detach, tpm_detach),
DEVMETHOD(device_suspend, tpm_suspend),
DEVMETHOD(device_resume, tpm_resume),
+ DEVMETHOD(tpm_configure_irq, tpm_acpi_configure_irq),
DEVMETHOD_END
};
diff --git a/sys/dev/tpm/tpm_acpi_subr.c b/sys/dev/tpm/tpm_acpi_subr.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/tpm/tpm_acpi_subr.c
@@ -0,0 +1,122 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Kevin Bowling <kbowling@FreeBSD.org>
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+#ifdef INTRNG
+#include <sys/intr.h>
+#endif
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <dev/acpica/acpivar.h>
+#include <dev/tpm/tpm_irq.h>
+
+int
+tpm_acpi_configure_irq(device_t dev, struct resource *irq,
+ struct tpm_irq_config *config, bool program)
+{
+ ACPI_BUFFER buf;
+ ACPI_HANDLE handle, method;
+ ACPI_RESOURCE *res;
+ ACPI_STATUS status;
+ rman_res_t allocated;
+ uint32_t gsi, mode;
+ uint8_t trigger, polarity;
+ int error, count;
+#ifdef INTRNG
+ struct intr_map_data_acpi *map;
+
+ /* rman_get_start() is an opaque mapping index on INTRNG systems. */
+ map = rman_get_virtual(irq);
+ if (map == NULL || map->hdr.type != INTR_MAP_DATA_ACPI)
+ return (ENXIO);
+ allocated = map->irq;
+#else
+ allocated = rman_get_start(irq);
+#endif
+ handle = acpi_get_handle(dev);
+ buf.Length = ACPI_ALLOCATE_BUFFER;
+ buf.Pointer = NULL;
+ status = AcpiGetCurrentResources(handle, &buf);
+ if (ACPI_FAILURE(status))
+ return (ENXIO);
+
+ error = ENXIO;
+ count = 0;
+ for (res = buf.Pointer; res->Type != ACPI_RESOURCE_TYPE_END_TAG;
+ res = ACPI_NEXT_RESOURCE(res)) {
+ switch (res->Type) {
+ case ACPI_RESOURCE_TYPE_IRQ:
+ if (res->Data.Irq.InterruptCount != 1 ||
+ res->Data.Irq.Shareable != ACPI_EXCLUSIVE)
+ goto out;
+ gsi = res->Data.Irq.Interrupts[0];
+ trigger = res->Data.Irq.Triggering;
+ polarity = res->Data.Irq.Polarity;
+ break;
+ case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
+ if (res->Data.ExtendedIrq.ProducerConsumer !=
+ ACPI_CONSUMER ||
+ res->Data.ExtendedIrq.InterruptCount != 1 ||
+ res->Data.ExtendedIrq.Shareable != ACPI_EXCLUSIVE)
+ goto out;
+ gsi = res->Data.ExtendedIrq.Interrupts[0];
+ trigger = res->Data.ExtendedIrq.Triggering;
+ polarity = res->Data.ExtendedIrq.Polarity;
+ break;
+ default:
+ continue;
+ }
+ /* Reject ambiguous resources or a changed route on resume. */
+ if (++count != 1 || gsi != allocated ||
+ (trigger != ACPI_EDGE_SENSITIVE &&
+ trigger != ACPI_LEVEL_SENSITIVE) ||
+ (polarity != ACPI_ACTIVE_HIGH &&
+ polarity != ACPI_ACTIVE_LOW))
+ goto out;
+#ifdef INTRNG
+ if (map->trig != (trigger == ACPI_EDGE_SENSITIVE ?
+ INTR_TRIGGER_EDGE : INTR_TRIGGER_LEVEL) ||
+ map->pol != (polarity == ACPI_ACTIVE_HIGH ?
+ INTR_POLARITY_HIGH : INTR_POLARITY_LOW))
+ goto out;
+#elif defined(__amd64__) || defined(__i386__)
+ /* Override only the local mode; keep _SRS input unchanged. */
+ if (gsi < 16 && trigger == ACPI_EDGE_SENSITIVE &&
+ polarity == ACPI_ACTIVE_LOW &&
+ acpi_override_isa_irq_polarity)
+ polarity = ACPI_ACTIVE_HIGH;
+#endif
+ mode = (trigger == ACPI_EDGE_SENSITIVE ?
+ TPM_IRQ_EDGE_RISING : TPM_IRQ_LEVEL_HIGH) |
+ (polarity == ACPI_ACTIVE_LOW ? TPM_IRQ_LEVEL_LOW : 0);
+ if (config->valid && config->mode != mode)
+ goto out;
+ }
+ if (count != 1)
+ goto out;
+
+ status = AE_OK;
+ if (program) {
+ /* _SRS can enable IRQs; locality and a handler must exist. */
+ status = AcpiGetHandle(handle, "_SRS", &method);
+ if (status == AE_NOT_FOUND)
+ status = AE_OK;
+ else if (ACPI_SUCCESS(status))
+ status = AcpiSetCurrentResources(handle, &buf);
+ }
+ if (ACPI_SUCCESS(status)) {
+ config->sirq = TPM_IRQ_SIRQ_UNCHANGED;
+ config->mode = mode;
+ config->valid = true;
+ error = 0;
+ }
+out:
+ AcpiOsFree(buf.Pointer);
+ return (error);
+}
diff --git a/sys/dev/tpm/tpm_if.m b/sys/dev/tpm/tpm_if.m
--- a/sys/dev/tpm/tpm_if.m
+++ b/sys/dev/tpm/tpm_if.m
@@ -30,10 +30,24 @@
HEADER {
struct tpm_priv;
+ struct tpm_irq_config;
};
INTERFACE tpm;
+#
+# Describe an exclusive interrupt route without touching hardware when
+# program is false. With program true, configure it while locality zero
+# is owned and a handler is installed. Return a separate, optional LPC
+# SIRQ selector and TPM trigger/polarity bits. Unsupported routes poll.
+#
+METHOD int configure_irq {
+ device_t dev;
+ struct resource *irq;
+ struct tpm_irq_config *config;
+ bool program;
+};
+
#
# Transfer data to the TPM data buffer
#
diff --git a/sys/dev/tpm/tpm_irq.h b/sys/dev/tpm/tpm_irq.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/tpm/tpm_irq.h
@@ -0,0 +1,42 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Kevin Bowling <kbowling@FreeBSD.org>
+ */
+
+#ifndef _DEV_TPM_IRQ_H_
+#define _DEV_TPM_IRQ_H_
+
+/* TPM_INT_VECTOR selects an LPC SIRQ, not a host interrupt resource. */
+#define TPM_IRQ_SIRQ_UNCHANGED (-1)
+#define TPM_IRQ_EVENTS 0x87
+#define TPM_IRQ_MODE_MASK 0x18
+#define TPM_IRQ_LEVEL_HIGH 0x00
+#define TPM_IRQ_LEVEL_LOW 0x08
+#define TPM_IRQ_EDGE_RISING 0x10
+#define TPM_IRQ_EDGE_FALLING 0x18
+
+/* Bound a lost IRQ's latency and allow a pending handler to be scheduled. */
+#define TPM_IRQ_POLL_MS 100
+#define TPM_IRQ_GRACE_MS 20
+
+struct tpm_irq_config {
+ int sirq;
+ uint32_t mode; /* TPM_INT_ENABLE trigger and polarity bits */
+ bool valid; /* Preserve the allocated route across resume */
+};
+
+static inline bool
+tpm_irq_supported(uint32_t caps, const struct tpm_irq_config *config)
+{
+
+ return ((config->sirq == TPM_IRQ_SIRQ_UNCHANGED ||
+ (config->sirq >= 1 && config->sirq <= 15)) &&
+ (config->mode & ~TPM_IRQ_MODE_MASK) == 0 &&
+ (caps & 1) != 0 && (caps & (8U << (config->mode >> 3))) != 0);
+}
+
+int tpm_acpi_configure_irq(device_t, struct resource *,
+ struct tpm_irq_config *, bool);
+
+#endif
diff --git a/sys/dev/tpm/tpm_isa.c b/sys/dev/tpm/tpm_isa.c
--- a/sys/dev/tpm/tpm_isa.c
+++ b/sys/dev/tpm/tpm_isa.c
@@ -48,6 +48,23 @@
#include <dev/isa/isavar.h>
#endif
#include "tpmvar.h"
+#include "tpm_irq.h"
+#include "tpm_if.h"
+
+static int
+tpm_isa_configure_irq(device_t dev, struct resource *irq,
+ struct tpm_irq_config *config, bool program)
+{
+ rman_res_t sirq;
+
+ sirq = rman_get_start(irq);
+ if (sirq < 1 || sirq > 15)
+ return (ENXIO);
+ config->sirq = sirq;
+ config->mode = TPM_IRQ_EDGE_RISING;
+ config->valid = true;
+ return (0);
+}
static int
tpm_isa_probe(device_t dev)
@@ -81,6 +98,7 @@
DEVMETHOD(device_detach, tpm_detach),
DEVMETHOD(device_suspend, tpm_suspend),
DEVMETHOD(device_resume, tpm_resume),
+ DEVMETHOD(tpm_configure_irq, tpm_isa_configure_irq),
DEVMETHOD_END
};
diff --git a/sys/dev/tpm/tpm_tis_acpi.c b/sys/dev/tpm/tpm_tis_acpi.c
--- a/sys/dev/tpm/tpm_tis_acpi.c
+++ b/sys/dev/tpm/tpm_tis_acpi.c
@@ -28,6 +28,7 @@
#include <sys/cdefs.h>
#include "tpm20.h"
+#include "tpm_irq.h"
#include "tpm_if.h"
static int tpmtis_acpi_probe(device_t dev);
@@ -77,6 +78,7 @@
static device_method_t tpmtis_methods[] = {
DEVMETHOD(device_attach, tpmtis_acpi_attach),
DEVMETHOD(device_probe, tpmtis_acpi_probe),
+ DEVMETHOD(tpm_configure_irq, tpm_acpi_configure_irq),
DEVMETHOD_END
};
diff --git a/sys/dev/tpm/tpm_tis_core.c b/sys/dev/tpm/tpm_tis_core.c
--- a/sys/dev/tpm/tpm_tis_core.c
+++ b/sys/dev/tpm/tpm_tis_core.c
@@ -31,6 +31,7 @@
#include <machine/atomic.h>
#include "tpm20.h"
+#include "tpm_irq.h"
#include "tpm_if.h"
/*
@@ -79,12 +80,20 @@
static int tpmtis_transmit(device_t dev, struct tpm_priv *priv, size_t length);
+static int tpmtis_attach_finish(struct tpm_sc *sc);
+static void tpmtis_delayed_attach(void *arg);
static int tpmtis_detach(device_t dev);
+static int tpmtis_suspend(device_t dev);
static void tpmtis_intr_handler(void *arg);
+static void tpmtis_intr_task(void *arg, int pending);
+static int tpmtis_fallback(struct tpm_sc *sc);
+static void tpmtis_arm_intr(struct tpm_sc *sc);
+static bool tpmtis_mask_intr(struct tpm_sc *sc);
+static void tpmtis_finish_intr(struct tpm_sc *sc);
static bool tpmtis_program_intr(struct tpm_sc *sc, bool enable);
-static void tpmtis_setup_intr(struct tpm_sc *sc);
+static int tpmtis_setup_intr(struct tpm_sc *sc);
static int tpmtis_resume(device_t dev);
static bool tpmtis_read_bytes(struct tpm_sc *sc, size_t count, uint8_t *buf);
@@ -103,7 +112,6 @@
tpmtis_attach(device_t dev)
{
struct tpm_sc *sc;
- rman_res_t irq;
void *intr_cookie;
bool configured;
int result;
@@ -111,13 +119,23 @@
sc = device_get_softc(dev);
sc->dev = dev;
- sc->intr_type = -1;
+ sc->intr_hook.ich_state = ICHS_DONE;
sc->intr_generation = 0;
sc->interrupts = false;
sc->intr_cookie = NULL;
sc->irq_res = NULL;
+ sc->intr_testing = false;
+ sc->intr_configuring = false;
+ sc->intr_fault = false;
+ sc->intr_armed = false;
+ sc->intr_received = false;
+ sc->locality = false;
+ sc->irq_config.valid = false;
+ sc->dying = false;
+ TASK_INIT(&sc->intr_task, 0, tpmtis_intr_task, sc);
sx_init(&sc->dev_lock, "TPM driver lock");
+ sx_init(&sc->intr_io_lock, "TPM interrupt I/O lock");
mtx_init(&sc->intr_lock, "TPM interrupt lock", NULL, MTX_DEF);
cv_init(&sc->intr_cv, "tpmtis_intr");
@@ -128,8 +146,8 @@
*/
sx_xlock(&sc->dev_lock);
configured = tpmtis_program_intr(sc, false);
- sx_xunlock(&sc->dev_lock);
if (!configured) {
+ sx_xunlock(&sc->dev_lock);
device_printf(dev, "failed to quiesce TPM interrupts\n");
tpmtis_detach(dev);
return (ENXIO);
@@ -144,61 +162,108 @@
sc->irq_rid = 0;
sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irq_rid,
- RF_ACTIVE | RF_SHAREABLE);
- if (sc->irq_res == NULL)
+ RF_ACTIVE);
+ if (sc->irq_res == NULL) {
+ device_printf(dev, "IRQ resource unavailable; using polling\n");
goto skip_irq;
-
- /*
- * This driver uses the resource IRQ directly as the LPC SIRQ selector.
- * Parent IRQ numbers need not map directly to SIRQ channels. Preserve
- * the existing 1..15 limit and use polling for other routes without
- * installing a handler.
- */
- irq = bus_get_resource_start(dev, SYS_RES_IRQ, sc->irq_rid);
- if (irq == 0 || irq > 0xf) {
- device_printf(dev, "cannot use IRQ %ju; using polling\n",
- (uintmax_t)irq);
- goto release_irq;
}
+ if (TPM_CONFIGURE_IRQ(dev, sc->irq_res, &sc->irq_config, false) != 0 ||
+ !tpm_irq_supported(TPM_READ_4(dev, TPM_INTF_CAPS), &sc->irq_config))
+ goto release_irq;
+ /* Recovery can sleep with the host source masked; sharing is unsafe. */
/* Failed setup may leave a stale cookie after removing its handler. */
intr_cookie = NULL;
- result = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
+ result = bus_setup_intr(dev, sc->irq_res,
+ INTR_TYPE_MISC | INTR_MPSAFE | INTR_EXCL | INTR_SLEEPABLE,
NULL, tpmtis_intr_handler, sc, &intr_cookie);
if (result == 0) {
sc->intr_cookie = intr_cookie;
goto skip_irq;
}
release_irq:
+ device_printf(dev, "IRQ route unavailable; using polling\n");
if (bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid,
sc->irq_res) == 0)
sc->irq_res = NULL;
skip_irq:
- result = tpm20_init(sc);
+ /* IRQ validation needs timers and a runnable interrupt thread. */
+ if (cold && sc->intr_cookie != NULL) {
+ sx_xunlock(&sc->dev_lock);
+ sc->intr_hook.ich_func = tpmtis_delayed_attach;
+ sc->intr_hook.ich_arg = sc;
+ if (config_intrhook_establish(&sc->intr_hook) == 0)
+ return (0);
+ sc->intr_hook.ich_state = ICHS_DONE;
+ result = ENXIO;
+ } else {
+ result = tpmtis_attach_finish(sc);
+ sx_xunlock(&sc->dev_lock);
+ }
if (result != 0) {
tpmtis_detach(dev);
return (result);
}
- if (sc->intr_cookie != NULL)
- tpmtis_setup_intr(sc);
-
return (0);
}
+static int
+tpmtis_attach_finish(struct tpm_sc *sc)
+{
+
+ sx_assert(&sc->dev_lock, SA_XLOCKED);
+ /* Keep fault cleanup out until validation and publication finish. */
+ if (tpmtis_setup_intr(sc) != 0)
+ return (ENXIO);
+ return (tpm20_init(sc));
+}
+
+static void
+tpmtis_delayed_attach(void *arg)
+{
+ struct tpm_sc *sc = arg;
+ int error;
+
+ sx_xlock(&sc->dev_lock);
+ error = tpmtis_attach_finish(sc);
+ if (error != 0) {
+ mtx_lock(&sc->intr_lock);
+ atomic_store_bool(&sc->dying, true);
+ mtx_unlock(&sc->intr_lock);
+ device_printf(sc->dev, "deferred attach failed: %d\n", error);
+ }
+ sx_xunlock(&sc->dev_lock);
+ config_intrhook_disestablish(&sc->intr_hook);
+}
+
+/* Deferred initialization can fail before the harvester is initialized. */
+static int
+tpmtis_suspend(device_t dev)
+{
+ struct tpm_sc *sc = device_get_softc(dev);
+
+ if (!sc->common_initialized)
+ return (0);
+ return (tpm20_suspend(dev));
+}
+
static int
tpmtis_detach(device_t dev)
{
struct tpm_sc *sc;
sc = device_get_softc(dev);
+ config_intrhook_drain(&sc->intr_hook);
/*
* Stop new commands before quiescing hardware with the lock still live.
*/
+ mtx_lock(&sc->intr_lock);
atomic_store_bool(&sc->dying, true);
+ mtx_unlock(&sc->intr_lock);
+ taskqueue_drain(taskqueue_thread, &sc->intr_task);
sx_xlock(&sc->dev_lock);
mtx_lock(&sc->intr_lock);
sc->interrupts = false;
- sc->intr_type = -1;
mtx_unlock(&sc->intr_lock);
if (!tpmtis_program_intr(sc, false))
device_printf(dev,
@@ -210,6 +275,7 @@
bus_teardown_intr(dev, sc->irq_res, sc->intr_cookie);
cv_destroy(&sc->intr_cv);
mtx_destroy(&sc->intr_lock);
+ sx_destroy(&sc->intr_io_lock);
if (sc->irq_res != NULL)
bus_release_resource(dev, SYS_RES_IRQ,
@@ -222,151 +288,290 @@
return (0);
}
-/*
- * Test if the advertisted interrupt actually works.
- * This sends a simple command. (GetRandom)
- * Interrupts are then enabled in the handler.
- */
+/* Called with intr_lock held; the worker may wait for the command lock. */
static void
-tpmtis_test_intr(struct tpm_sc *sc)
+tpmtis_intr_failed(struct tpm_sc *sc)
{
- struct tpm_priv *priv;
- uint8_t cmd[] = {
- 0x80, 0x01, /* TPM_ST_NO_SESSIONS tag*/
- 0x00, 0x00, 0x00, 0x0c, /* cmd length */
- 0x00, 0x00, 0x01, 0x7b, /* cmd TPM_CC_GetRandom */
- 0x00, 0x01 /* number of bytes requested */
- };
- sx_xlock(&sc->dev_lock);
- priv = sc->internal_priv;
- memcpy(priv->buf, cmd, sizeof(cmd));
- tpmtis_transmit(sc->dev, priv, sizeof(cmd));
- sx_xunlock(&sc->dev_lock);
+ sc->interrupts = false;
+ if (!sc->intr_fault && !sc->dying) {
+ sc->intr_fault = true;
+ taskqueue_enqueue(taskqueue_thread, &sc->intr_task);
+ }
+ cv_broadcast(&sc->intr_cv);
}
+/* The caller owns locality and serializes register I/O with the handler. */
static bool
-tpmtis_program_intr(struct tpm_sc *sc, bool enable)
+tpmtis_mask_intr(struct tpm_sc *sc)
{
- rman_res_t irq;
uint32_t reg;
+ bool disabled;
- sx_assert(&sc->dev_lock, SA_XLOCKED);
+ sx_assert(&sc->intr_io_lock, SA_XLOCKED);
+ reg = TPM_READ_4(sc->dev, TPM_INT_ENABLE);
+ TPM_WRITE_4(sc->dev, TPM_INT_ENABLE,
+ reg & ~(TPM_INT_ENABLE_GLOBAL_ENABLE | TPM_IRQ_EVENTS));
+ TPM_WRITE_BARRIER(sc->dev, TPM_INT_ENABLE, 4);
+ disabled = (TPM_READ_4(sc->dev, TPM_INT_ENABLE) &
+ TPM_INT_ENABLE_GLOBAL_ENABLE) == 0;
+ reg = TPM_READ_4(sc->dev, TPM_INT_STS);
+ TPM_WRITE_4(sc->dev, TPM_INT_STS, reg);
+ TPM_WRITE_BARRIER(sc->dev, TPM_INT_STS, 4);
+ return (disabled);
+}
+static bool
+tpmtis_program_intr(struct tpm_sc *sc, bool enable)
+{
+ struct tpm_irq_config config;
+ bool configured;
+ int error;
+
+ sx_assert(&sc->dev_lock, SA_XLOCKED);
if (!tpmtis_request_locality(sc, 0)) {
/* Interrupt enable can be read without owning locality. */
return (!enable && (TPM_READ_4(sc->dev, TPM_INT_ENABLE) &
TPM_INT_ENABLE_GLOBAL_ENABLE) == 0);
}
- /* Disable delivery before acknowledging or reconfiguring interrupts. */
- reg = TPM_READ_4(sc->dev, TPM_INT_ENABLE);
- reg &= ~TPM_INT_ENABLE_GLOBAL_ENABLE;
- TPM_WRITE_4(sc->dev, TPM_INT_ENABLE, reg);
- TPM_WRITE_BARRIER(sc->dev, TPM_INT_ENABLE, 4);
- if ((TPM_READ_4(sc->dev, TPM_INT_ENABLE) &
- TPM_INT_ENABLE_GLOBAL_ENABLE) != 0) {
- tpmtis_relinquish_locality(sc);
- return (false);
- }
-
- /* Clear all pending interrupts. */
- reg = TPM_READ_4(sc->dev, TPM_INT_STS);
- TPM_WRITE_4(sc->dev, TPM_INT_STS, reg);
-
- if (enable) {
+ sx_xlock(&sc->intr_io_lock);
+ configured = tpmtis_mask_intr(sc);
+ if (configured && enable) {
KASSERT(sc->intr_cookie != NULL,
- ("TPM interrupts enabled without a handler"));
- irq = bus_get_resource_start(sc->dev, SYS_RES_IRQ,
- sc->irq_rid);
- if (irq == 0 || irq > 0xf) {
- tpmtis_relinquish_locality(sc);
- return (false);
- }
- TPM_WRITE_1(sc->dev, TPM_INT_VECTOR, (uint8_t)irq);
-
- if (sc->intr_mask == 0) {
- reg = TPM_READ_4(sc->dev, TPM_INT_ENABLE);
- reg |= TPM_INT_ENABLE_DATA_AVAIL |
- TPM_INT_ENABLE_LOC_CHANGE |
- TPM_INT_ENABLE_CMD_RDY |
- TPM_INT_ENABLE_STS_VALID;
- reg &= ~TPM_INT_ENABLE_GLOBAL_ENABLE;
- sc->intr_mask = reg;
+ ("TPM interrupts configured without a handler"));
+ config = sc->irq_config;
+ mtx_lock(&sc->intr_lock);
+ sc->intr_configuring = true;
+ mtx_unlock(&sc->intr_lock);
+ error = TPM_CONFIGURE_IRQ(sc->dev, sc->irq_res, &config, true);
+ /* Firmware may have changed interrupt enables in _SRS. */
+ configured = tpmtis_mask_intr(sc);
+ mtx_lock(&sc->intr_lock);
+ sc->intr_configuring = false;
+ mtx_unlock(&sc->intr_lock);
+ if (error != 0 || !tpm_irq_supported(
+ TPM_READ_4(sc->dev, TPM_INTF_CAPS), &config))
+ configured = false;
+ if (configured) {
+ sc->irq_config = config;
+ if (config.sirq != TPM_IRQ_SIRQ_UNCHANGED)
+ TPM_WRITE_1(sc->dev, TPM_INT_VECTOR,
+ config.sirq);
+ sc->intr_mask = config.mode | TPM_INT_ENABLE_DATA_AVAIL;
+ TPM_WRITE_4(sc->dev, TPM_INT_ENABLE, config.mode);
+ TPM_WRITE_BARRIER(sc->dev, TPM_INT_ENABLE, 4);
+ configured = (TPM_READ_4(sc->dev, TPM_INT_ENABLE) &
+ (TPM_INT_ENABLE_GLOBAL_ENABLE |
+ TPM_IRQ_MODE_MASK)) == config.mode;
}
- TPM_WRITE_4(sc->dev, TPM_INT_ENABLE,
- sc->intr_mask | TPM_INT_ENABLE_GLOBAL_ENABLE);
}
-
+ sx_xunlock(&sc->intr_io_lock);
tpmtis_relinquish_locality(sc);
- return (true);
+ return (configured);
+}
+
+static int
+tpmtis_fallback(struct tpm_sc *sc)
+{
+ bool quiesced;
+
+ sx_assert(&sc->dev_lock, SA_XLOCKED);
+ mtx_lock(&sc->intr_lock);
+ sc->interrupts = false;
+ sc->intr_testing = false;
+ sc->intr_armed = false;
+ mtx_unlock(&sc->intr_lock);
+ quiesced = tpmtis_program_intr(sc, false);
+ if (!quiesced) {
+ sc->suspended = true;
+ device_printf(sc->dev,
+ "failed to quiesce TPM interrupts; suspending device\n");
+ }
+ if (sc->intr_cookie != NULL) {
+ bus_teardown_intr(sc->dev, sc->irq_res, sc->intr_cookie);
+ sc->intr_cookie = NULL;
+ if (quiesced)
+ device_printf(sc->dev,
+ "interrupts unavailable; using polling\n");
+ }
+ if (sc->irq_res != NULL && bus_release_resource(sc->dev, SYS_RES_IRQ,
+ sc->irq_rid, sc->irq_res) == 0)
+ sc->irq_res = NULL;
+ return (quiesced ? 0 : EIO);
}
static void
-tpmtis_setup_intr(struct tpm_sc *sc)
+tpmtis_intr_task(void *arg, int pending)
{
- bool configured, enable;
+ struct tpm_sc *sc = arg;
- enable = sc->intr_cookie != NULL;
sx_xlock(&sc->dev_lock);
+ if (!sc->dying)
+ tpmtis_fallback(sc);
+ sx_xunlock(&sc->dev_lock);
+}
+
+/* Require a complete tagged response and a fresh DATA_AVAIL interrupt. */
+static int
+tpmtis_setup_intr(struct tpm_sc *sc)
+{
+ struct tpm_priv *priv;
+ bool working;
+ int error;
+ static const uint8_t cmd[] = {
+ 0x80, 0x01, /* TPM_ST_NO_SESSIONS */
+ 0x00, 0x00, 0x00, 0x0c, /* Command length */
+ 0x00, 0x00, 0x01, 0x7b, /* TPM_CC_GetRandom */
+ 0x00, 0x01 /* One byte */
+ };
+
+ sx_assert(&sc->dev_lock, SA_XLOCKED);
+ if (sc->intr_cookie == NULL)
+ return (0);
+ priv = malloc(sizeof(*priv), M_TPM20, M_WAITOK | M_ZERO);
mtx_lock(&sc->intr_lock);
sc->interrupts = false;
- sc->intr_type = -1;
+ working = !sc->intr_fault && !sc->dying;
mtx_unlock(&sc->intr_lock);
- configured = tpmtis_program_intr(sc, enable);
- sx_xunlock(&sc->dev_lock);
- if (!configured || !enable)
- return;
- tpmtis_test_intr(sc);
+ if (!working || !tpmtis_program_intr(sc, true))
+ goto fallback;
+ mtx_lock(&sc->intr_lock);
+ sc->intr_testing = true;
+ sc->intr_received = false;
+ mtx_unlock(&sc->intr_lock);
+ memcpy(priv->buf, cmd, sizeof(cmd));
+ error = tpmtis_transmit(sc->dev, priv, sizeof(cmd));
+ mtx_lock(&sc->intr_lock);
+ sc->intr_testing = false;
+ working = error == 0 && be16dec(priv->buf) == 0x8001 &&
+ sc->intr_received && !sc->intr_fault;
+ sc->interrupts = working;
+ mtx_unlock(&sc->intr_lock);
+ if (working) {
+ device_printf(sc->dev, "using verified interrupts\n");
+ error = 0;
+ goto out;
+ }
+fallback:
+ error = tpmtis_fallback(sc);
+out:
+ free(priv, M_TPM20);
+ return (error);
}
static int
tpmtis_resume(device_t dev)
{
struct tpm_sc *sc;
- bool restore_intr;
+ bool quiesced;
+ int error;
sc = device_get_softc(dev);
+ if (!sc->common_initialized)
+ return (ENXIO);
sx_xlock(&sc->dev_lock);
mtx_lock(&sc->intr_lock);
- restore_intr = sc->interrupts;
-
- /*
- * Interrupt routing and enable state may be lost across suspend. Keep
- * the transport in polling mode until a restored interrupt is actually
- * observed by the handler.
- */
sc->interrupts = false;
- sc->intr_type = -1;
mtx_unlock(&sc->intr_lock);
- if (!tpmtis_program_intr(sc, restore_intr))
- device_printf(dev,
- "failed to %s interrupts; using polling\n",
- restore_intr ? "restore" : "disable");
+ quiesced = tpmtis_program_intr(sc, false);
sx_xunlock(&sc->dev_lock);
+ if (!quiesced) {
+ device_printf(dev,
+ "failed to quiesce interrupts during resume\n");
+ return (EIO);
+ }
+ /* Complete Startup through polling before testing the route again. */
+ error = tpm20_resume(dev);
+ if (error == 0) {
+ sx_xlock(&sc->dev_lock);
+ error = tpmtis_setup_intr(sc);
+ sx_xunlock(&sc->dev_lock);
+ }
+ return (error);
+}
+
+static void
+tpmtis_arm_intr(struct tpm_sc *sc)
+{
+ bool enable;
+
+ sx_xlock(&sc->intr_io_lock);
+ mtx_lock(&sc->intr_lock);
+ enable = (sc->interrupts || sc->intr_testing) && !sc->intr_fault;
+ sc->intr_received = false;
+ mtx_unlock(&sc->intr_lock);
+ if (enable) {
+ /* Stale response status must not validate this command. */
+ if (tpmtis_mask_intr(sc) && (TPM_READ_4(sc->dev, TPM_INT_STS) &
+ TPM_INT_STS_DATA_AVAIL) == 0) {
+ mtx_lock(&sc->intr_lock);
+ sc->intr_armed = true;
+ mtx_unlock(&sc->intr_lock);
+ TPM_WRITE_4(sc->dev, TPM_INT_ENABLE,
+ sc->intr_mask | TPM_INT_ENABLE_GLOBAL_ENABLE);
+ TPM_WRITE_BARRIER(sc->dev, TPM_INT_ENABLE, 4);
+ } else {
+ mtx_lock(&sc->intr_lock);
+ tpmtis_intr_failed(sc);
+ mtx_unlock(&sc->intr_lock);
+ }
+ }
+ sx_xunlock(&sc->intr_io_lock);
+}
+
+/* Give an already pending handler time to run before consuming its response. */
+static void
+tpmtis_finish_intr(struct tpm_sc *sc)
+{
+ sbintime_t deadline;
- return (tpm20_resume(dev));
+ deadline = sbinuptime() + mstosbt(TPM_IRQ_GRACE_MS);
+ mtx_lock(&sc->intr_lock);
+ while (sc->intr_armed && !sc->intr_received && !sc->intr_fault) {
+ if (cv_timedwait_sbt(&sc->intr_cv, &sc->intr_lock,
+ deadline, 0, C_ABSOLUTE | C_HARDCLOCK) == EWOULDBLOCK) {
+ tpmtis_intr_failed(sc);
+ break;
+ }
+ }
+ mtx_unlock(&sc->intr_lock);
}
static void
tpmtis_intr_handler(void *arg)
{
- struct tpm_sc *sc;
- uint32_t status;
+ struct tpm_sc *sc = arg;
+ uint32_t status, enable;
+ bool disabled;
- sc = (struct tpm_sc *)arg;
+ /* Register access may sleep on SPI; do not hold intr_lock across it. */
+ sx_xlock(&sc->intr_io_lock);
status = TPM_READ_4(sc->dev, TPM_INT_STS);
+ enable = TPM_READ_4(sc->dev, TPM_INT_ENABLE);
+ if ((enable & TPM_INT_ENABLE_GLOBAL_ENABLE) == 0 ||
+ (status & enable & TPM_IRQ_EVENTS) == 0) {
+ sx_xunlock(&sc->intr_io_lock);
+ return;
+ }
- TPM_WRITE_4(sc->dev, TPM_INT_STS, status);
-
+ /* Mask on the first event so a stuck source cannot retrigger. */
+ disabled = sc->locality && tpmtis_mask_intr(sc);
mtx_lock(&sc->intr_lock);
- /* Check for stray interrupts. */
- if (sc->intr_type != -1 && (sc->intr_type & status) != 0) {
- sc->interrupts = true;
+ if (disabled && sc->intr_armed &&
+ (status & TPM_INT_STS_DATA_AVAIL) != 0) {
+ sc->intr_received = true;
sc->intr_generation++;
- cv_broadcast(&sc->intr_cv);
+ } else if (!disabled || !sc->intr_configuring) {
+ tpmtis_intr_failed(sc);
}
+ sc->intr_armed = false;
+ cv_broadcast(&sc->intr_cv);
mtx_unlock(&sc->intr_lock);
+ sx_xunlock(&sc->intr_io_lock);
+ /* Keep the host source masked while command and cleanup threads run. */
+ if (!disabled)
+ pause("tpmirq", 1);
}
static uint32_t
@@ -382,16 +587,15 @@
tpm_wait_for_reg(struct tpm_sc *sc, bus_size_t off, uint32_t mask,
uint32_t val, int32_t timeout, int intr_type, bool reg32)
{
- sbintime_t deadline;
+ sbintime_t deadline, wakeup;
uint32_t generation;
bool interrupts, result;
sx_assert(&sc->dev_lock, SA_XLOCKED);
deadline = sbinuptime() + ustosbt(timeout);
mtx_lock(&sc->intr_lock);
- sc->intr_type = intr_type;
generation = sc->intr_generation;
- interrupts = sc->interrupts;
+ interrupts = sc->interrupts && intr_type == TPM_INT_STS_DATA_AVAIL;
mtx_unlock(&sc->intr_lock);
for (;;) {
@@ -404,7 +608,8 @@
pause("TPM POLLING", 1);
mtx_lock(&sc->intr_lock);
generation = sc->intr_generation;
- interrupts = sc->interrupts;
+ interrupts = sc->interrupts &&
+ intr_type == TPM_INT_STS_DATA_AVAIL;
mtx_unlock(&sc->intr_lock);
continue;
}
@@ -415,16 +620,19 @@
* resulting window before cv_timedwait_sbt() atomically sleeps.
*/
mtx_lock(&sc->intr_lock);
+ wakeup = MIN(deadline, sbinuptime() + mstosbt(TPM_IRQ_POLL_MS));
if (generation == sc->intr_generation)
(void)cv_timedwait_sbt(&sc->intr_cv, &sc->intr_lock,
- deadline, 0, C_ABSOLUTE | C_HARDCLOCK);
+ wakeup, 0, C_ABSOLUTE | C_HARDCLOCK);
generation = sc->intr_generation;
- interrupts = sc->interrupts;
+ interrupts = sc->interrupts &&
+ intr_type == TPM_INT_STS_DATA_AVAIL;
mtx_unlock(&sc->intr_lock);
}
mtx_lock(&sc->intr_lock);
- sc->intr_type = -1;
+ if (!result && intr_type == TPM_INT_STS_DATA_AVAIL && sc->intr_armed)
+ tpmtis_intr_failed(sc);
mtx_unlock(&sc->intr_lock);
return (result);
}
@@ -503,8 +711,12 @@
TPM_WRITE_1(sc->dev, TPM_ACCESS, TPM_ACCESS_LOC_REQ);
TPM_WRITE_BARRIER(sc->dev, TPM_ACCESS, 1);
if (tpm_wait_for_reg(sc, TPM_ACCESS, mask, mask, TPM_TIMEOUT_A,
- TPM_INT_STS_LOC_CHANGE, false))
+ TPM_INT_STS_LOC_CHANGE, false)) {
+ sx_xlock(&sc->intr_io_lock);
+ sc->locality = true;
+ sx_xunlock(&sc->intr_io_lock);
return (true);
+ }
/* Cancel the request so a later grant cannot leave locality owned. */
TPM_WRITE_1(sc->dev, TPM_ACCESS, TPM_ACCESS_LOC_RELINQUISH);
@@ -515,21 +727,21 @@
static void
tpmtis_relinquish_locality(struct tpm_sc *sc)
{
- bool interrupts;
+ bool disabled;
sx_assert(&sc->dev_lock, SA_XLOCKED);
- /*
- * Interrupts can only be cleared when a locality is active.
- * Clear them now in case interrupt handler didn't make it in time.
- */
+ /* No delivery or handler register writes after releasing locality. */
+ sx_xlock(&sc->intr_io_lock);
+ disabled = tpmtis_mask_intr(sc);
mtx_lock(&sc->intr_lock);
- interrupts = sc->interrupts;
+ sc->intr_armed = false;
+ if (!disabled)
+ tpmtis_intr_failed(sc);
mtx_unlock(&sc->intr_lock);
- if (interrupts)
- TPM_WRITE_4(sc->dev, TPM_INT_STS,
- TPM_READ_4(sc->dev, TPM_INT_STS));
-
- OR1(sc, TPM_ACCESS, TPM_ACCESS_LOC_RELINQUISH);
+ TPM_WRITE_1(sc->dev, TPM_ACCESS, TPM_ACCESS_LOC_RELINQUISH);
+ TPM_WRITE_BARRIER(sc->dev, TPM_ACCESS, 1);
+ sc->locality = false;
+ sx_xunlock(&sc->intr_io_lock);
}
static bool
@@ -603,6 +815,7 @@
curr_cmd = be32toh(*(uint32_t *) (&priv->buf[6]));
timeout = tpm20_get_timeout(curr_cmd);
+ tpmtis_arm_intr(sc);
TPM_WRITE_4(dev, TPM_STS, TPM_STS_CMD_START);
TPM_WRITE_BARRIER(dev, TPM_STS, 4);
@@ -630,6 +843,7 @@
goto out;
}
}
+ tpmtis_finish_intr(sc);
/* Read response header. Length is passed in bytes 2 - 6. */
if (!tpmtis_read_bytes(sc, TPM_HEADER_SIZE, priv->buf)) {
device_printf(dev,
@@ -678,7 +892,7 @@
DEVMETHOD(device_attach, tpmtis_attach),
DEVMETHOD(device_detach, tpmtis_detach),
DEVMETHOD(device_shutdown, tpm20_shutdown),
- DEVMETHOD(device_suspend, tpm20_suspend),
+ DEVMETHOD(device_suspend, tpmtis_suspend),
DEVMETHOD(device_resume, tpmtis_resume),
DEVMETHOD(tpm_transmit, tpmtis_transmit),
DEVMETHOD_END
diff --git a/sys/dev/tpm/tpmvar.h b/sys/dev/tpm/tpmvar.h
--- a/sys/dev/tpm/tpmvar.h
+++ b/sys/dev/tpm/tpmvar.h
@@ -21,18 +21,23 @@
#ifdef __FreeBSD__
#include <sys/condvar.h>
+#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/sx.h>
+#include <sys/taskqueue.h>
+#include <dev/tpm/tpm_irq.h>
#endif
struct tpm_softc {
-#ifndef __FreeBSD__
+#ifdef __FreeBSD__
+ device_t sc_dev;
+#else
struct device sc_dev;
#endif
void *sc_ih;
- int (*sc_init)(struct tpm_softc *, int, const char *);
+ int (*sc_init)(struct tpm_softc *, const char *);
int (*sc_start)(struct tpm_softc *, int);
int (*sc_read)(struct tpm_softc *, void *, int, size_t *, int);
int (*sc_write)(struct tpm_softc *, void *, int);
@@ -49,7 +54,6 @@
int sc_flags;
#define TPM_OPEN 0x0001
- int sc_vector;
#ifdef __FreeBSD__
void *intr_cookie;
int mem_rid, irq_rid;
@@ -59,6 +63,17 @@
struct sx sc_lock;
struct mtx sc_intr_lock;
struct cv sc_intr_cv;
+ struct task sc_intr_task;
+ struct intr_config_hook sc_intr_hook;
+ uint32_t sc_intr_mode;
+ struct tpm_irq_config sc_irq_config;
+ bool sc_irq_usable;
+ bool sc_interrupts;
+ bool sc_intr_testing;
+ bool sc_intr_configuring;
+ bool sc_intr_fault;
+ bool sc_intr_armed;
+ bool sc_intr_received;
bool sc_dying;
bool sc_locality;
bool sc_command_pending;
diff --git a/sys/modules/tpm/Makefile b/sys/modules/tpm/Makefile
--- a/sys/modules/tpm/Makefile
+++ b/sys/modules/tpm/Makefile
@@ -20,6 +20,7 @@
.if ${MACHINE_ARCH:Namd64:Ni386:Naarch64} == ""
SRCS+= acpi_if.h
SRCS+= tpm_tis_acpi.c
+SRCS+= tpm_acpi_subr.c
SRCS+= tpm_crb.c
SRCS+= tpm_bus.c
CFLAGS+=-DDEV_ACPI
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Nov 2, 3:18 AM (5 h, 30 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41303339
Default Alt Text
D59657.1793589514.diff (63 KB)
Attached To
Mode
D59657: tpm: Validate firmware interrupt routes and fall back cleanly
Attached
Detach File
Event Timeline