/* $MirOS: src/sys/dev/ic/tpm.c,v 1.5 2010/07/25 16:36:18 tg Exp $ */ /*- * Copyright (c) 2009, 2010 * Thorsten Glaser * Copyright (c) 2003 * Rick Wash * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * TCPA TPM driver, bus independent code */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* #define TPM_DEBUG */ /* TPM addresses */ #define TPM_ADDR 0x4E #define TPM_DATA 0x4F /* write status bits */ #define STATUS_BIT_ABORT 0x01 #define STATUS_BIT_LASTBYTE 0x04 /* read status bits */ #define STATUS_BIT_BUSY 0x01 #define STATUS_BIT_DATA_AVAIL 0x02 #define STATUS_BIT_REWRITE 0x04 /* Timeout Numbers */ #define TPM_DELAY 50 /* in milliseconds */ #define TPM_TIMEOUT ((2*2*60*60) / TPM_DELAY) #define TPM_TSLEEP_TIMEOUT (2*60*hz) #define TPM_CALLOUT_TIMEOUT TPM_DELAY int tpm_init(struct tpm_softc *sc, pci_chipset_tag_t pa_pc, pcitag_t pa_tag); int tpm_recv(struct tpm_softc *sc, int count); int tpm_send(struct tpm_softc *sc, int count); void tpm_intr(void *data); void tpm_read_data(struct tpm_softc *sc); void tpm_send_data(struct tpm_softc *sc); struct cfdriver tpm_cd = { NULL, "tpm", DV_DULL }; struct tpm_softc *tpm_sc; #ifndef SMALL_KERNEL /* must match enum tpm_chip_type */ static const char *tpm_chip_names[] = { "", "Intel ICH2 LPC", "Intel ICH3 LPC-M", "Intel ICH4 LPC", "Intel ICH4 LPC-M" }; #endif int tpmopen(dev_t dev, int flag, int mode, struct proc *p) { #ifdef TPM_DEBUG printf("in tpmopen\n"); #endif /* TPM_DEBUG */ /* Make sure it has been initialised */ if (!tpm_sc || !tpm_sc->initialised) return (ENODEV); /* Only can be opened by one process at a time */ /* XXX: concurrency hack */ if (tpm_sc->open != 0) return (EBUSY); tpm_sc->open = 1; return (0); } int tpmclose(dev_t dev, int flag, int mode, struct proc *p) { #ifdef TPM_DEBUG printf("in tpmclose\n"); #endif /* TPM_DEBUG */ tpm_sc->open = 0; return (0); } int tpmwrite(dev_t dev, struct uio *uio, int flags) { size_t len; int rv; if (!tpm_sc || !tpm_sc->initialised) return (ENODEV); else if (!tpm_sc->open) return (EPERM); else if (tpm_sc->state != TPM_STATE_READY) return (EBUSY); len = min(uio->uio_resid, TPM_BUFFER_LEN); uiomove(tpm_sc->buffer, len, uio); tpm_sc->len = len; tpm_sc->state = TPM_STATE_WRITE_START; timeout_add(&tpm_sc->tmo, 1); while ((tpm_sc->state != TPM_STATE_WRITE_DONE) && (tpm_sc->state != TPM_STATE_ERROR)) tsleep(tpm_sc->buffer, PRIBIO, "tpmwrite", TPM_TSLEEP_TIMEOUT); rv = tpm_sc->retval; tpm_sc->retval = 0; tpm_sc->state = TPM_STATE_READY; #ifdef TPM_DEBUG printf("in tpmwrite\n"); #endif /* TPM_DEBUG */ return (rv); } int tpmread(dev_t dev, struct uio *uio, int flags) { size_t len; int rv; if (!tpm_sc || !tpm_sc->initialised) return (ENODEV); else if (!tpm_sc->open) return (EPERM); else if (tpm_sc->state != TPM_STATE_READY) return (EBUSY); #ifdef TPM_DEBUG printf("in tpmread\n"); #endif /* TPM_DEBUG */ len = min(uio->uio_resid, TPM_BUFFER_LEN); if (len < 6) return (EIO); tpm_sc->len = len; tpm_sc->state = TPM_STATE_READ_START; timeout_add(&tpm_sc->tmo, 1); while ((tpm_sc->state != TPM_STATE_READ_DONE) && (tpm_sc->state != TPM_STATE_ERROR)) tsleep(tpm_sc->buffer, PRIBIO, "tpmread", TPM_TSLEEP_TIMEOUT); rv = tpm_sc->retval; tpm_sc->retval = 0; if (tpm_sc->len > 0) uiomove(tpm_sc->buffer, tpm_sc->len, uio); tpm_sc->state = TPM_STATE_READY; return (rv); } int tpmioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p) { return (ENODEV); } int tpm_match(struct device *parent, void *match, void *aux) { struct tpm_attach_args *ta = aux; if (ta == NULL || strcmp(ta->busname, "tpm") || ta->chiptype == TPM_INVALID_CHIP) return (0); if (tpm_sc && tpm_sc->initialised) { printf("tpm: second %sch ignored\n", "mat"); return (0); } return (1); } void tpm_attach(struct device *parent, struct device *self, void *aux) { struct tpm_attach_args *ta = aux; int notattach; if (tpm_sc && tpm_sc->initialised) { printf("tpm: second %sch ignored\n", "atta"); return; } tpm_sc = (void *)self; tpm_sc->type = ta->chiptype; outb(TPM_ADDR, 0x08); /* base addr lo */ tpm_sc->base = inb(TPM_DATA); outb(TPM_ADDR, 0x09); /* base addr hi */ tpm_sc->base |= inb(TPM_DATA) << 8; /* Query the chip for its version */ outb(TPM_ADDR, 0x00); tpm_sc->version[0] = inb(TPM_DATA); outb(TPM_ADDR, 0x01); tpm_sc->version[1] = inb(TPM_DATA); outb(TPM_ADDR, 0x02); tpm_sc->version[2] = inb(TPM_DATA); outb(TPM_ADDR, 0x03); tpm_sc->version[3] = inb(TPM_DATA); /* Query the chip for its vendor */ outb(TPM_ADDR, 0x04); tpm_sc->vendor[0] = inb(TPM_DATA); outb(TPM_ADDR, 0x05); tpm_sc->vendor[1] = inb(TPM_DATA); outb(TPM_ADDR, 0x06); tpm_sc->vendor[2] = inb(TPM_DATA); outb(TPM_ADDR, 0x07); tpm_sc->vendor[3] = inb(TPM_DATA); tpm_sc->vendor[4] = '\0'; notattach = (tpm_sc->vendor[0] == 0xFF) ? 1 : 0; printf(" port 0x%X (%s), version ", tpm_sc->base, tpm_chip_names[tpm_sc->type]); if (tpm_sc->version[0] == 0xFF) { notattach |= 2; printf("failed"); } else printf("%d.%d.%d.%d", tpm_sc->version[0], tpm_sc->version[1], tpm_sc->version[2], tpm_sc->version[3]); printf(", vendor %s, %sattached\n", (notattach & 1) ? "failed" : (const char *)tpm_sc->vendor, notattach ? "not " : ""); if (notattach) return; timeout_set(&tpm_sc->tmo, tpm_intr, tpm_sc); timeout_add(&tpm_sc->tmo, 1); tpm_sc->initialised = 1; } /* Lower half of the device driver. This does the actual polling of the device * Uses NetBSD's callout to delay, so the kernel stays useful while polling */ void tpm_intr(void *data) { struct tpm_softc *sc = data; int status; #ifdef __OpenBSD__ int s; s = splnet(); #endif switch (sc->state) { case TPM_STATE_READY: /* XXX: Should this be here? */ #ifdef TPM_DEBUG printf("tpm: state ready\n"); #endif /* TPM_DEBUG */ timeout_add(&sc->tmo, TPM_CALLOUT_TIMEOUT); break; case TPM_STATE_READ_START: /* Wait till the chip is not busy */ #ifdef TPM_DEBUG printf("tpm: state read start\n"); #endif /* TPM_DEBUG */ sc->wait_timeout++; if (sc->wait_timeout > TPM_TIMEOUT) { printf("tpm: timeout on read\n"); sc->state = TPM_STATE_ERROR; sc->retval = EIO; wakeup(sc->buffer); break; } status = inb(sc->base + 1); if ((status & STATUS_BIT_BUSY) != 0) { timeout_add(&sc->tmo, TPM_CALLOUT_TIMEOUT); break; } sc->state = TPM_STATE_WAIT_FOR_DATA; sc->wait_timeout = 0; /* fallthrough */ case TPM_STATE_WAIT_FOR_DATA: /* Wait till there is data available */ #ifdef TPM_DEBUG printf("tpm: state wait for data\n"); #endif /* TPM_DEBUG */ sc->wait_timeout++; if (sc->wait_timeout > TPM_TIMEOUT) { printf("tpm: timeout waiting for data\n"); sc->state = TPM_STATE_ERROR; sc->retval = EIO; wakeup(sc->buffer); break; } status = inb(sc->base + 1); status &= (STATUS_BIT_BUSY | STATUS_BIT_DATA_AVAIL); if ( status != STATUS_BIT_DATA_AVAIL ) { timeout_add(&sc->tmo, TPM_CALLOUT_TIMEOUT); break; } /* Read Data */ sc->wait_timeout = 0; sc->retval = 0; sc->state = TPM_STATE_READ_DONE; tpm_read_data(sc); wakeup(sc->buffer); break; case TPM_STATE_WRITE_START: /* Wait till the chip is not busy */ outb(sc->base+1, STATUS_BIT_ABORT); #ifdef TPM_DEBUG printf("tpm: state write start\n"); #endif /* TPM_DEBUG */ sc->state = TPM_STATE_WRITE_BUSY; case TPM_STATE_WRITE_BUSY: sc->wait_timeout++; #ifdef TPM_DEBUG printf("tpm:state write busy\n"); #endif /* TPM_DEBUG */ if (sc->wait_timeout > TPM_TIMEOUT) { printf("tpm: timeout waiting to write\n"); sc->state = TPM_STATE_ERROR; sc->retval = EIO; wakeup(sc->buffer); break; } status = 0; status = inb(sc->base + 1); #ifdef TPM_DEBUG printf("tpm: base: %08x, status bit: %08x\n", sc->base, status); #endif /* TPM_DEBUG */ if ( (status & STATUS_BIT_BUSY) != 0) { timeout_add(&sc->tmo, TPM_CALLOUT_TIMEOUT); break; } sc->wait_timeout = 0; sc->retval = 0; sc->state = TPM_STATE_WRITE_WORKING; /* Send Data */ tpm_send_data(sc); /* fallthrough */ case TPM_STATE_WRITE_WORKING: /* Wait till the chip is working, done, or an error */ #ifdef TPM_DEBUG printf("tpm: state write working\n"); #endif /* TPM_DEBUG */ sc->wait_timeout++; if (sc->wait_timeout > TPM_TIMEOUT) { printf("tpm: timeout after writing\n"); sc->state = TPM_STATE_ERROR; sc->retval = EIO; wakeup(sc->buffer); break; } status = inb(sc->base + 1); if ( (status & (STATUS_BIT_BUSY | STATUS_BIT_DATA_AVAIL | STATUS_BIT_REWRITE)) == 0) { timeout_add(&sc->tmo, TPM_CALLOUT_TIMEOUT); break; } sc->state = TPM_STATE_WRITE_DONE; if ( (status & STATUS_BIT_REWRITE) != 0) { sc->retval = EIO; sc->state = TPM_STATE_ERROR; } wakeup(sc->buffer); break; default: printf("tpm: error in interrupt\n"); sc->state = TPM_STATE_ERROR; sc->retval = EIO; wakeup(sc->buffer); } #ifdef __OpenBSD__ splx(s); #endif } void tpm_read_data(struct tpm_softc *sc) { int i; int status; char *buffer = sc->buffer; u_int32_t size; for (i=0; i<6; i++) { status = inb(sc->base+1); if ( (status & STATUS_BIT_DATA_AVAIL) == 0) { printf("tpm: error reading header\n"); sc->len = 0; sc->retval = EIO; sc->state = TPM_STATE_ERROR;; return; } *buffer++ = inb(sc->base); } #ifdef __OpenBSD__ size = betoh32(*(u_int32_t *)(sc->buffer + 2)); #else size = be32toh(*(u_int32_t *)(sc->buffer + 2)); #endif if (sc->len < size) { sc->len = 0; sc->retval = EIO; sc->state = TPM_STATE_ERROR; return; } for (; i < size; i++) { status = inb(sc->base+1); if ( (status & STATUS_BIT_DATA_AVAIL) == 0) { printf("tpm: error reading data\n"); sc->len = 0; sc->retval = EIO; sc->state = TPM_STATE_ERROR; return; } *buffer++ = inb(sc->base); } /* Make sure data available is gone */ status = inb(sc->base+1); if ( (status & STATUS_BIT_DATA_AVAIL) != 0) { printf("tpm: data avail is stuck\n"); sc->len = 0; sc->retval = EIO; sc->state = TPM_STATE_ERROR; return; } /* Send an abort */ outb(sc->base+1, STATUS_BIT_ABORT); sc->len = size; /* TODO: crc check? */ } void tpm_send_data(struct tpm_softc *sc) { int i; /* Write everything but the last byte */ for (i = 0; i < sc->len; i++) { outb(sc->base, sc->buffer[i]); } /* Send last byte */ outb(sc->base+1, STATUS_BIT_LASTBYTE); /* Send last byte */ outb(sc->base, sc->buffer[i]); }