On Wed, Apr 15, 2020 at 05:16:05PM -0700, Omar Sandoval wrote: > On Wed, Apr 15, 2020 at 04:51:39PM -0700, James Bottomley wrote: > > On Wed, 2020-04-15 at 15:45 -0700, Omar Sandoval wrote: > > > From: Omar Sandoval <osandov@xxxxxx> > > > > > > We've encountered a particular model of STMicroelectronics TPM that > > > transiently returns a bad value in the status register. This causes > > > the kernel to believe that the TPM is ready to receive a command when > > > it actually isn't, which in turn causes the send to time out in > > > get_burstcount(). In testing, reading the status register one extra > > > time convinces the TPM to return a valid value. > > > > Interesting, I've got a very early upgradeable nuvoton that seems to be > > behaving like this. > > I'll attach the userspace reproducer I used to figure this out. I'd be > interested to see if it times out on your TPM, too. Note that it bangs > on /dev/mem and assumes that the MMIO address is 0xfed40000. That seems > to be the hard-coded address for x86 in the kernel, but just to be safe > you might want to check `grep MSFT0101 /proc/iomem`. Forgot to attach it, of course...
#include <fcntl.h> #include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <time.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> enum tis_access { TPM_ACCESS_VALID = 0x80, TPM_ACCESS_ACTIVE_LOCALITY = 0x20, TPM_ACCESS_REQUEST_PENDING = 0x04, TPM_ACCESS_REQUEST_USE = 0x02, }; enum tis_status { TPM_STS_VALID = 0x80, TPM_STS_COMMAND_READY = 0x40, TPM_STS_GO = 0x20, TPM_STS_DATA_AVAIL = 0x10, TPM_STS_DATA_EXPECT = 0x08, }; #define TPM_ACCESS(l) (0x0000 | ((l) << 12)) #define TPM_STS(l) (0x0018 | ((l) << 12)) int main(void) { int fd; void *map; volatile uint8_t *access; volatile uint8_t *sts; unsigned long long i; fd = open("/dev/mem", O_RDWR | O_DSYNC); if (fd == -1) { perror("open"); return EXIT_FAILURE; } map = mmap(NULL, 0x5000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0xfed40000); if (map == MAP_FAILED) { perror("mmap"); return EXIT_FAILURE; } access = (uint8_t *)map + TPM_ACCESS(0); sts = (uint8_t *)map + TPM_STS(0); i = 0; for (;;) { struct timespec stop, now; uint32_t burstcnt; uint8_t sts_read; *access = TPM_ACCESS_REQUEST_USE; while ((*access & (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) != (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) ; sts_read = *sts; #if 0 if (sts_read == 0xff) sts_read = *sts; #endif if (!(sts_read & TPM_STS_COMMAND_READY)) { *sts = TPM_STS_COMMAND_READY; while (!(*sts & TPM_STS_COMMAND_READY)) ; } clock_gettime(CLOCK_MONOTONIC, &stop); stop.tv_sec += 1; for (;;) { burstcnt = ((*(volatile uint32_t *)sts) >> 8) & 0xffff; if (burstcnt) break; clock_gettime(CLOCK_MONOTONIC, &now); if (now.tv_sec > stop.tv_sec || (now.tv_sec == stop.tv_sec && now.tv_nsec >= stop.tv_nsec)) { fprintf(stderr, "Timed out after %llu iterations\n", i); i = 0; break; } } *access = TPM_ACCESS_ACTIVE_LOCALITY; while ((*access & (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) != TPM_ACCESS_VALID) ; i++; } return EXIT_SUCCESS; }