Page MenuHomeFreeBSD

D59657.1793589514.diff
No OneTemporary

Authored By
Unknown
Size
63 KB
Referenced Files
None
Subscribers
None

D59657.1793589514.diff

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

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)

Event Timeline