Message ID | 1282753982-1761-1-git-send-email-andreas.niederl@iaik.tugraz.at |
---|---|
State | New |
Headers | show |
On 08/25/2010 11:33 AM, Andreas Niederl wrote: > This implementation is based on the TPM 1.2 interface for virtualized TPM > devices from the Xen-4.0.0 ioemu-qemu-xen fork. > > A separate thread is used for I/O to the host TPM device because the Linux TPM > driver does not allow for non-blocking I/O. > > Signed-off-by: Andreas Niederl<andreas.niederl@iaik.tugraz.at> > Interesting. A few high level comments. You need to make the device qdev-aware and VMState aware. It would be better to use the generic thread pool series floating around on the list than to introduce a device specific thread. The command line interface should separate our the host passthrough from a future vTPM interface. IOW, something like a -tpm host,path=/dev/tpm0,id=vtpm0 -device tpm,driver=vtpm0 Regards, Anthony Liguori > --- > Makefile.target | 3 + > configure | 9 + > hw/pc_piix.c | 8 + > hw/tpm_tis.c | 831 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 851 insertions(+), 0 deletions(-) > create mode 100644 hw/tpm_tis.c > > diff --git a/Makefile.target b/Makefile.target > index c8281e9..2226c75 100644 > --- a/Makefile.target > +++ b/Makefile.target > @@ -193,6 +193,9 @@ obj-y += e1000.o > # Inter-VM PCI shared memory > obj-$(CONFIG_KVM) += ivshmem.o > > +# TPM passthrough device > +obj-$(CONFIG_TPM) += tpm_tis.o qemu-thread.o > + > # Hardware support > obj-i386-y += vga.o > obj-i386-y += mc146818rtc.o i8259.o pc.o > diff --git a/configure b/configure > index a20371c..e1b55a8 100755 > --- a/configure > +++ b/configure > @@ -315,6 +315,7 @@ pkgversion="" > check_utests="no" > user_pie="no" > zero_malloc="" > +tpm="no" > > # OS specific > if check_define __linux__ ; then > @@ -448,6 +449,7 @@ AIX) > usb="linux" > if [ "$cpu" = "i386" -o "$cpu" = "x86_64" ] ; then > audio_possible_drivers="$audio_possible_drivers fmod" > + tpm="yes" > fi > ;; > esac > @@ -707,6 +709,8 @@ for opt do > ;; > --enable-vhost-net) vhost_net="yes" > ;; > + --disable-tpm) tpm="no" > + ;; > --*dir) > ;; > *) echo "ERROR: unknown option $opt"; show_help="yes" > @@ -895,6 +899,7 @@ echo " --enable-docs enable documentation build" > echo " --disable-docs disable documentation build" > echo " --disable-vhost-net disable vhost-net acceleration support" > echo " --enable-vhost-net enable vhost-net acceleration support" > +echo " --disable-tpm disable tpm passthrough device emulation" > echo "" > echo "NOTE: The object files are built at the place where configure is launched" > exit 1 > @@ -2187,6 +2192,7 @@ echo "preadv support $preadv" > echo "fdatasync $fdatasync" > echo "uuid support $uuid" > echo "vhost-net support $vhost_net" > +echo "tpm support $tpm" > > if test $sdl_too_old = "yes"; then > echo "-> Your SDL version is too old - please upgrade to have SDL support" > @@ -2423,6 +2429,9 @@ fi > if test "$fdatasync" = "yes" ; then > echo "CONFIG_FDATASYNC=y">> $config_host_mak > fi > +if test "$tpm" = "yes" ; then > + echo "CONFIG_TPM=y">> $config_host_mak > +fi > > # XXX: suppress that > if [ "$bsd" = "yes" ] ; then > diff --git a/hw/pc_piix.c b/hw/pc_piix.c > index 32a1057..ebc3478 100644 > --- a/hw/pc_piix.c > +++ b/hw/pc_piix.c > @@ -38,6 +38,10 @@ > > #define MAX_IDE_BUS 2 > > +#ifdef CONFIG_TPM > +void tpm_tis_init(qemu_irq irq); > +#endif > + > static const int ide_iobase[MAX_IDE_BUS] = { 0x1f0, 0x170 }; > static const int ide_iobase2[MAX_IDE_BUS] = { 0x3f6, 0x376 }; > static const int ide_irq[MAX_IDE_BUS] = { 14, 15 }; > @@ -147,6 +151,10 @@ static void pc_init1(ram_addr_t ram_size, > } > } > > +#ifdef CONFIG_TPM > + tpm_tis_init(isa_reserve_irq(11)); > +#endif > + > pc_audio_init(pci_enabled ? pci_bus : NULL, isa_irq); > > pc_cmos_init(below_4g_mem_size, above_4g_mem_size, boot_device, > diff --git a/hw/tpm_tis.c b/hw/tpm_tis.c > new file mode 100644 > index 0000000..e109e1a > --- /dev/null > +++ b/hw/tpm_tis.c > @@ -0,0 +1,831 @@ > +/* > + * tpm_tis.c - QEMU emulator for a 1.2 TPM with TIS interface > + * > + * Copyright (C) 2006 IBM Corporation > + * Copyright (C) 2010 IAIK, Graz University of Technology > + * > + * Author: Stefan Berger<stefanb@us.ibm.com> > + * David Safford<safford@us.ibm.com> > + * > + * Author: Andreas Niederl<andreas.niederl@iaik.tugraz.at> > + * Pass through a TPM device rather than using the emulator > + * Modified to use a separate thread for IO to/from TPM as the Linux TPM driver > + * framework does not allow non-blocking IO > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License as > + * published by the Free Software Foundation, version 2 of the > + * License. > + * > + * > + * Implementation of the TIS interface according to specs at > + * https://www.trustedcomputinggroup.org/groups/pc_client/TCG_PCClientTPMSpecification_1-20_1-00_FINAL.pdf > + * > + */ > + > +#include<sys/types.h> > +#include<sys/stat.h> > +#include<errno.h> > +#include<string.h> > + > +#include "qemu-option.h" > +#include "qemu-config.h" > +#include "hw/hw.h" > +#include "hw/pc.h" > +#include "hw/pci.h" > +#include "hw/pci_ids.h" > +#include "qemu-thread.h" > + > +//#define TPM_DEBUG > + > +#define TPM_MAX_PKT 4096 > + > +#define TIS_ADDR_BASE 0xFED40000 > + > +/* tis registers */ > +#define TPM_REG_ACCESS 0x00 > +#define TPM_REG_INT_ENABLE 0x08 > +#define TPM_REG_INT_VECTOR 0x0c > +#define TPM_REG_INT_STATUS 0x10 > +#define TPM_REG_INTF_CAPABILITY 0x14 > +#define TPM_REG_STS 0x18 > +#define TPM_REG_DATA_FIFO 0x24 > +#define TPM_REG_DID_VID 0xf00 > +#define TPM_REG_RID 0xf04 > + > +#define STS_VALID (1<< 7) > +#define STS_COMMAND_READY (1<< 6) > +#define STS_TPM_GO (1<< 5) > +#define STS_DATA_AVAILABLE (1<< 4) > +#define STS_EXPECT (1<< 3) > +#define STS_RESPONSE_RETRY (1<< 1) > + > +#define ACCESS_TPM_REG_VALID_STS (1<< 7) > +#define ACCESS_ACTIVE_LOCALITY (1<< 5) > +#define ACCESS_BEEN_SEIZED (1<< 4) > +#define ACCESS_SEIZE (1<< 3) > +#define ACCESS_PENDING_REQUEST (1<< 2) > +#define ACCESS_REQUEST_USE (1<< 1) > +#define ACCESS_TPM_ESTABLISHMENT (1<< 0) > + > +#define INT_ENABLED (1<< 31) > +#define INT_DATA_AVAILABLE (1<< 0) > +#define INT_LOCALITY_CHANGED (1<< 2) > +#define INT_COMMAND_READY (1<< 7) > + > +#define INTERRUPTS_SUPPORTED (INT_LOCALITY_CHANGED | \ > + INT_DATA_AVAILABLE | \ > + INT_COMMAND_READY) > +#define CAPABILITIES_SUPPORTED ((1<< 4) | \ > + INTERRUPTS_SUPPORTED) > + > +enum { > + STATE_IDLE = 0, > + STATE_READY, > + STATE_COMPLETION, > + STATE_EXECUTION, > + STATE_RECEPTION > +}; > + > +#define NUM_LOCALITIES 5 > +#define NO_LOCALITY 0xff > + > +#define IS_VALID_LOC(x) ((x)< NUM_LOCALITIES) > + > +#define TPM_DID 0x0001 > +#define TPM_VID 0x0001 > +#define TPM_RID 0x0001 > + > + > +const char *tpm_device = NULL; > + > +/* locality data */ > +typedef struct TPMLocal { > + uint32_t state; > + uint8_t access; > + uint8_t sts; > + uint32_t inte; > + uint32_t ints; > +} tpmLoc; > + > +/* overall state of the TPM interface; 's' marks as save upon suspension */ > +typedef struct TPMState { > + uint8_t transmit; /* boolean value */ > + /* set when tpm transmit in progress */ > + uint8_t aborting; /* boolean value */ > + uint32_t offset; /* s */ > + uint8_t buf[TPM_MAX_PKT]; /* s */ > + int tpmfd; /* s */ > + > + /* path to host tpm device */ > + const char *path; /* s */ > + uint32_t path_len; /* s */ > + > + QemuMutex tpm_thread_lock; > + QemuMutex tpm_thread_abort_lock; > + QemuCond tpm_thread_cond_data_avail; > + QemuThread tpm_thread_id; > + > + uint8_t active_loc; /* s */ > + uint8_t aborting_locty; > + uint8_t next_locty; > + uint8_t irq_pending; /* s */ > + tpmLoc loc[NUM_LOCALITIES]; /* s */ > + qemu_irq_handler set_irq; > + void *irq_opaque; > + int irq; > +} tpmState; > + > + > +/* local prototypes */ > +static void tis_raise_irq(tpmState *s, uint8_t locty, uint32_t irqmask); > +static void tis_abort(tpmState *s); > + > +#ifdef TPM_DEBUG > +static void showBuff(unsigned char *buff, const char *string); > +#endif > + > + > +/********************************************************************** > + helper functions > + *********************************************************************/ > + > +static inline uint32_t tpm_get_size_from_buffer(const uint8_t *buffer) > +{ > + uint32_t len = (buffer[4]<< 8) + buffer[5]; > + return len; > +} > + > +static inline uint8_t locality_from_addr(target_phys_addr_t addr) > +{ > + return (uint8_t)((addr>> 12)& 0x7); > +} > + > + > +static void die2(int err, const char *what) > +{ > + fprintf(stderr, "%s failed: %s\n", what, strerror(err)); > + abort(); > +} > + > +static void die(const char *what) > +{ > + die2(errno, what); > +} > + > + > +/* borrowed from qemu-char.c */ > +static int unix_write(int fd, const uint8_t *buf, uint32_t len1) > +{ > + int ret, len; > + > + len = len1; > + while (len> 0) { > + ret = write(fd, buf, len); > + if (ret< 0) { > + if (errno != EINTR&& errno != EAGAIN) > + return -1; > + } else if (ret == 0) { > + break; > + } else { > + buf += ret; > + len -= ret; > + } > + } > + return len1 - len; > +} > + > +static int unix_tpm_read(int fd, uint8_t *buf) > +{ > + int ret, len, len1; > + uint8_t *buf1; > + > + buf1 = buf; > + len = sizeof(buf) + TPM_MAX_PKT; > + while ((buf1 - buf)< 6) { > + ret = read(fd, buf1, len); > + if (ret< 0) { > + if (errno != EINTR&& errno != EAGAIN) > + return -1; > + } else { > + buf1 += ret; > + } > + } > + > + len1 = tpm_get_size_from_buffer(buf); > + if (len1> TPM_MAX_PKT) { > + fprintf(stderr, "Error: received invalid tpm response size: %d\n", > + len1); > + return -1; > + } > + > + /* response size minus already read data */ > + len = len1 - (buf1 - buf); > + while (len> 0) { > + ret = read(fd, buf, len); > + if (ret< 0) { > + if (errno != EINTR&& errno != EAGAIN) > + return -1; > + } else if (ret == 0) { > + break; > + } else { > + buf += ret; > + len -= ret; > + } > + } > + return len1 - len; > +} > + > +/****************************************************************************/ > +/* Transmit request to TPM and read Response */ > +/****************************************************************************/ > + > +static void *tpm_thread(void *opaque) > +{ > + tpmState *s = opaque; > + sigset_t set; > + uint32_t size = 0; > + /* hardcode locality 0 */ > + uint8_t locty = 0; > +#ifdef TPM_DEBUG > + uint32_t ret; > +#endif > + > + /* block all signals */ > + if (sigfillset(&set)) die("sigfillset"); > + if (sigprocmask(SIG_BLOCK,&set, NULL)) die("sigprocmask"); > + > + qemu_mutex_lock(&s->tpm_thread_lock); > + while(1) { > + if(!s->transmit) > + qemu_cond_wait(&s->tpm_thread_cond_data_avail,&s->tpm_thread_lock); > + qemu_mutex_unlock(&s->tpm_thread_lock); > + > +#ifdef TPM_DEBUG > + showBuff(s->buf, "To TPM"); > +#endif > + size = tpm_get_size_from_buffer(s->buf); > + > + if(unix_write(s->tpmfd, s->buf, size)< 0) { > + fprintf(stderr, "Error: while transmitting data to host tpm" > + ": %s (%i)\n", > + strerror(errno), errno); > + tis_abort(s); > + qemu_mutex_lock(&s->tpm_thread_lock); > + continue; > + } > + > + if(unix_tpm_read(s->tpmfd, s->buf)< 0) { > + fprintf(stderr, "Error: while reading data from host tpm" > + ": %s (%i)\n", > + strerror(errno), errno); > + tis_abort(s); > + qemu_mutex_lock(&s->tpm_thread_lock); > + continue; > + } > + > + qemu_mutex_lock(&s->tpm_thread_abort_lock); > + if(!s->aborting) { > +#ifdef TPM_DEBUG > + showBuff(s->buf, "From TPM"); > + > + ret = (s->buf[8])*256 + s->buf[9]; > + if (ret) > + printf("tpm command failed with error %d\n", ret); > + else > + printf("tpm command succeeded\n"); > +#endif > + > + qemu_mutex_lock(&s->tpm_thread_lock); > + s->loc[locty].sts = STS_VALID | STS_DATA_AVAILABLE; > + s->loc[locty].state = STATE_COMPLETION; > + s->transmit = 0; > + tis_raise_irq(s, locty, INT_DATA_AVAILABLE); > + } else { > + qemu_mutex_lock(&s->tpm_thread_lock); > + tis_abort(s); > +#ifdef TPM_DEBUG > + printf("Abort is complete.\n"); > +#endif > + s->transmit = 0; > + } > + qemu_mutex_unlock(&s->tpm_thread_abort_lock); > + } > + > + return NULL; > +} > + > + > +/**********************************************************************/ > + > +/* > + * read a byte of response data > + */ > +static uint32_t tpm_data_read(tpmState *s, uint8_t locty) > +{ > + uint32_t ret, len; > + > + if (s->loc[locty].state != STATE_COMPLETION) { > +#ifdef TPM_DEBUG > + printf("tpm_data_read with no data available!\n"); > +#endif > + return 0xff; > + } > + > + len = tpm_get_size_from_buffer(s->buf); > + ret = s->buf[s->offset++]; > + if (s->offset>= len) { > + s->loc[locty].sts = STS_VALID ; > + s->offset = 0; > + } > +#ifdef TPM_DEBUG > + printf("tpm_data_read byte x%02x [%d]\n",ret,s->offset-1); > +#endif > + return ret; > +} > + > +/* raise an interrupt if allowed */ > +static void tis_raise_irq(tpmState *s, uint8_t locty, uint32_t irqmask) > +{ > + if (!s->irq_pending&& > + (s->loc[locty].inte& INT_ENABLED)&& > + (s->loc[locty].inte& irqmask)) { > + if ((irqmask& s->loc[locty].ints) == 0) { > +#ifdef TPM_DEBUG > + printf("Raising IRQ for flag %08x\n",irqmask); > +#endif > + s->set_irq(s->irq_opaque, s->irq, 1); > + s->irq_pending = 1; > + s->loc[locty].ints |= irqmask; > + } > + } > +} > + > +/* abort execution of command */ > +static void tis_abort(tpmState *s) > +{ > + s->offset = 0; > + s->active_loc = s->next_locty; > + qemu_mutex_lock(&s->tpm_thread_lock); > + s->transmit = 0; > + qemu_mutex_unlock(&s->tpm_thread_lock); > + > + /* > + * Need to react differently depending on who's aborting now and > + * which locality will become active afterwards. > + */ > + if (s->aborting_locty == s->next_locty) { > + s->loc[s->aborting_locty].state = STATE_READY; > + s->loc[s->aborting_locty].sts = STS_COMMAND_READY; > + tis_raise_irq(s, s->aborting_locty, INT_COMMAND_READY); > + } > + > + /* locality after abort is another one than the current one */ > + if (s->aborting_locty != s->next_locty&& s->next_locty != NO_LOCALITY) { > + s->loc[s->aborting_locty].access&= ~ACCESS_ACTIVE_LOCALITY; > + s->loc[s->next_locty].access |= ACCESS_ACTIVE_LOCALITY; > + tis_raise_irq(s, s->next_locty, INT_LOCALITY_CHANGED); > + } > + > + s->aborting_locty = NO_LOCALITY; /* nobody's aborting a command anymore */ > +} > + > +/* abort current command */ > +static void tis_prep_abort(tpmState *s, uint8_t locty, uint8_t newlocty) > +{ > + s->aborting_locty = locty; /* current locality */ > + s->next_locty = newlocty; /* locality after successful abort */ > + > + s->next_locty = 0; /* only locality 0 available */ > + > + /* > + * only abort a command using an interrupt if currently executing > + * a command AND if there's a valid connection to the vTPM. > + */ > + if (s->loc[locty].state == STATE_EXECUTION) { > + qemu_mutex_lock(&s->tpm_thread_abort_lock); > + s->aborting = 1; > + qemu_mutex_unlock(&s->tpm_thread_abort_lock); > + } else { > + tis_abort(s); > + } > +} > + > + > +/* > + * Read a register of the TIS interface > + * See specs pages 33-63 for description of the registers > + */ > +static uint32_t tis_mem_readl(void *opaque, target_phys_addr_t addr) > +{ > + tpmState *s = (tpmState *)opaque; > + uint16_t offset = addr& 0xffc; > + uint8_t shift = (addr& 0x3) * 8; > + uint32_t val = 0; > + uint8_t locty = locality_from_addr(addr); > + > + if (offset == TPM_REG_ACCESS) { > + if (locty> 0) { > + /* no access to localities other than 0 possible */ > + val = 0; > + } else { > + if (s->active_loc == locty) { > + s->loc[locty].access |= (1<< 5); > + } else { > + s->loc[locty].access&= ~(1<< 5); > + } > + val = s->loc[locty].access; > + } > + } else > + if (locty> 0) { > + /* higher localities deactivated */ > + val = 0xffff; > + } else > + if (offset == TPM_REG_INT_ENABLE) { > + val = s->loc[locty].inte; > + } else > + if (offset == TPM_REG_INT_VECTOR) { > + val = s->irq; > + } else > + if (offset == TPM_REG_INT_STATUS) { > + val = s->loc[locty].ints; > + } else > + if (offset == TPM_REG_INTF_CAPABILITY) { > + val = CAPABILITIES_SUPPORTED; > + } else > + if (offset == TPM_REG_STS) { /* status register */ > + /* ??? - appears to be just some value as we aren't a hardware TPM */ > + val = (sizeof(s->buf) - s->offset)<< 8 | s->loc[locty].sts; > + } else > + if (offset == TPM_REG_DATA_FIFO) { > + val = tpm_data_read(s, locty); > + } else > + if (offset == TPM_REG_DID_VID) { > + val = (TPM_DID<< 16) | TPM_VID; > + } else > + if (offset == TPM_REG_RID) { > + val = TPM_RID; > + } > + > + if (shift&& locty == 0) { > + val>>= shift; > + } > + > +#ifdef TPM_DEBUG > + printf(" read(%08x) = %08x\n", > + (int)addr, > + val); > +#endif > + > + return val; > +} > + > +/* > + * Write a value to a register of the TIS interface > + * See specs pages 33-63 for description of the registers > + */ > +static void tis_mem_writel(void *opaque, target_phys_addr_t addr, uint32_t val) > +{ > + tpmState* s=(tpmState*)opaque; > + uint16_t off = addr& 0xfff; > + uint8_t locty = locality_from_addr(addr); > + uint32_t len; > + > +#ifdef TPM_DEBUG > + printf("write(%08x) = %08x\n", > + (int)addr, > + val); > +#endif > + > + /* linux tpm_tis driver only uses locality 0 */ > + if (locty> 0) { > + fprintf(stderr, "Warning: Write to unavailable locality: %d \n", locty); > + return; > + } > +#ifdef TPM_DEBUG > + if(s->transmit) { > + fprintf(stderr, "Warning: tpm received data while transmit" > + " in progress.\n"); > + printf("WARN: write(%08x) = %08x\n", > + (int)addr, > + val); > + } > +#endif > + > + if (off == TPM_REG_ACCESS) { > + if (val& ACCESS_ACTIVE_LOCALITY) { > + /* give up locality if currently owned */ > + if (s->active_loc == locty) { > + /*uint8_t newlocty = NO_LOCALITY;*/ > + s->loc[locty].access&= ~(ACCESS_PENDING_REQUEST); > + /* anybody wants the locality ? */ > + if (s->loc[locty].access& ACCESS_REQUEST_USE) { > + s->loc[locty].access |= ACCESS_TPM_REG_VALID_STS; > + s->loc[locty].access&= ~ACCESS_REQUEST_USE; > + } > + tis_prep_abort(s, locty, locty); > + } > + } > + if (val& ACCESS_BEEN_SEIZED) { > + /* clear the flag */ > + s->loc[locty].access&= ~ACCESS_BEEN_SEIZED; > + } > + if (val& ACCESS_SEIZE) { > + if (locty> s->active_loc&& IS_VALID_LOC(s->active_loc)) { > + s->loc[s->active_loc].access |= ACCESS_BEEN_SEIZED; > + s->loc[locty].access = ACCESS_TPM_REG_VALID_STS; > + tis_prep_abort(s, s->active_loc, locty); > + } > + } > + if (val& ACCESS_REQUEST_USE) { > + if (IS_VALID_LOC(s->active_loc)) { > + /* locality election */ > + s->loc[s->active_loc].access |= ACCESS_PENDING_REQUEST; > + } else { > + /* no locality active -> make this one active now */ > + s->loc[locty].access |= ACCESS_ACTIVE_LOCALITY; > + s->active_loc = locty; > + tis_raise_irq(s, locty, INT_LOCALITY_CHANGED); > + } > + } > + } else > + if (off == TPM_REG_INT_ENABLE) { > + s->loc[locty].inte = (val& (INT_ENABLED | (0x3<< 3) | > + INTERRUPTS_SUPPORTED)); > + } else > + if (off == TPM_REG_INT_STATUS) { > + /* clearing of interrupt flags */ > + if ((val& INTERRUPTS_SUPPORTED)&& > + (s->loc[locty].ints& INTERRUPTS_SUPPORTED)) { > + s->set_irq(s->irq_opaque, s->irq, 0); > + s->irq_pending = 0; > + } > + s->loc[locty].ints&= ~(val& INTERRUPTS_SUPPORTED); > + } else > + if (off == TPM_REG_STS) { > + if (val& STS_COMMAND_READY) { > + if (s->loc[locty].state == STATE_IDLE) { > + s->loc[locty].sts = STS_COMMAND_READY; > + s->loc[locty].state = STATE_READY; > + tis_raise_irq(s, locty, INT_COMMAND_READY); > + } else if (s->loc[locty].state == STATE_COMPLETION || > + s->loc[locty].state == STATE_EXECUTION || > + s->loc[locty].state == STATE_RECEPTION) { > + /* abort currently running command */ > + tis_prep_abort(s, locty, locty); > + } > + } > + if (val& STS_TPM_GO) { > + s->offset = 0; > + > + if (s->transmit != 1) { > + qemu_mutex_lock(&s->tpm_thread_lock); > + s->transmit = 1; > + s->loc[locty].state = STATE_EXECUTION; > + qemu_mutex_unlock(&s->tpm_thread_lock); > + qemu_cond_signal(&s->tpm_thread_cond_data_avail); > + } > + } > + if (val& STS_RESPONSE_RETRY) { > + s->offset = 0; > + } > + } else if (off == TPM_REG_DATA_FIFO) { > + /* data fifo */ > + if (s->loc[locty].state == STATE_IDLE || > + s->loc[locty].state == STATE_EXECUTION || > + s->loc[locty].state == STATE_COMPLETION) { > + /* drop the byte */ > + } else { > +#ifdef TPM_DEBUG > + printf("Byte to send to TPM: %02x at offset: %03d\n", val, s->offset); > +#endif > + s->loc[locty].state = STATE_RECEPTION; > + s->loc[locty].sts = STS_EXPECT | STS_VALID; > + > + if (s->offset< TPM_MAX_PKT) > + s->buf[s->offset++] = (uint8_t)val; > + > + if (s->offset> 5) { > + /* we have a packet length - see if we have all of it */ > + len = tpm_get_size_from_buffer(s->buf); > + if (s->offset>= len) { > +#ifdef TPM_DEBUG > + printf("We have a complete packet of %u bytes\n", len); > +#endif > + s->offset = 0; > + > + qemu_mutex_lock(&s->tpm_thread_lock); > + s->transmit = 1; > + qemu_mutex_unlock(&s->tpm_thread_lock); > + qemu_cond_signal(&s->tpm_thread_cond_data_avail); > + s->loc[locty].sts = STS_VALID; > + } > + } > + } > + } > +} > + > + > +static CPUReadMemoryFunc *tis_readfn[3]={ > + tis_mem_readl, > + tis_mem_readl, > + tis_mem_readl > +}; > + > +static CPUWriteMemoryFunc *tis_writefn[3]={ > + tis_mem_writel, > + tis_mem_writel, > + tis_mem_writel > +}; > + > +/* > + * Save the internal state of this interface for later resumption. > + * Need to get any outstanding responses from the vTPM back, so > + * this might delay the suspend for a while. > + */ > +static void tpm_save(QEMUFile* f,void* opaque) > +{ > + tpmState* s=(tpmState*)opaque; > + uint8_t locty = s->active_loc; > + int c; > + > + /* need to wait for outstanding requests to complete */ > + if (s->loc[locty].state == STATE_EXECUTION) { > + int repeats = 30; /* 30 seconds; really should be infty */ > + while (repeats> 0&& > + !(s->loc[s->active_loc].sts& STS_DATA_AVAILABLE)) { > + sleep(1); > + } > + } > + > + close(s->tpmfd); > + > + qemu_put_be32s(f,&s->offset); > + qemu_put_buffer(f, s->buf, TPM_MAX_PKT); > + qemu_put_8s(f,&s->active_loc); > + qemu_put_8s(f,&s->irq_pending); > + for (c = 0; c< NUM_LOCALITIES; c++) { > + qemu_put_be32s(f,&s->loc[c].state); > + qemu_put_8s(f,&s->loc[c].access); > + qemu_put_8s(f,&s->loc[c].sts); > + qemu_put_be32s(f,&s->loc[c].inte); > + qemu_put_be32s(f,&s->loc[c].ints); > + } > + qemu_put_buffer(f, (uint8_t*) s->path, s->path_len); > + qemu_put_be32s(f,&s->path_len); > +} > + > + > +/* > + * load TIS interface state > + */ > +static int tpm_load(QEMUFile* f,void* opaque,int version_id) > +{ > + tpmState* s=(tpmState*)opaque; > + int c; > + > + if (version_id != 1) > + return -EINVAL; > + > + qemu_get_be32s(f,&s->offset); > + qemu_get_buffer(f, s->buf, TPM_MAX_PKT); > + qemu_get_8s(f,&s->active_loc); > + qemu_get_8s(f,&s->irq_pending); > + for (c = 0; c< NUM_LOCALITIES; c++) { > + qemu_get_be32s(f,&s->loc[c].state); > + qemu_get_8s(f,&s->loc[c].access); > + qemu_get_8s(f,&s->loc[c].sts); > + qemu_get_be32s(f,&s->loc[c].inte); > + qemu_get_be32s(f,&s->loc[c].ints); > + } > + qemu_get_be32s(f,&s->path_len); > + qemu_get_buffer(f, (uint8_t*) s->path, s->path_len); > + > + s->tpmfd = open(s->path, O_RDWR); > + > + if(s->tpmfd< 0) > + hw_error("Cannot open %s: %s (%i)\n", s->path, strerror(errno), errno); > + > + qemu_mutex_init(&s->tpm_thread_lock); > + qemu_cond_init(&s->tpm_thread_cond_data_avail); > + qemu_mutex_init(&s->tpm_thread_abort_lock); > + > + qemu_thread_create(&s->tpm_thread_id,&tpm_thread, s); > + > + return 0; > +} > + > + > +typedef struct LPCtpmState { > + tpmState tpm; > + int mem; > +} LPCtpmState; > + > + > +/* > + * initialize TIS interface > + */ > + > +struct IRQState { > + qemu_irq_handler handler; > + void *opaque; > + int n; > +}; > + > +void tpm_tis_init(qemu_irq irq); > + > +void tpm_tis_init(qemu_irq irq) > +{ > + LPCtpmState *d; > + tpmState *s; > + int c = 0; > + int tpmfd = -1; > + > + if(tpm_device == NULL) { > + return; > + } > + > + tpmfd = open(tpm_device, O_RDWR); > + if(tpmfd< 0) { > + fprintf(stderr, "Cannot open %s: %s (%i)\n", tpm_device, > + strerror(errno), errno); > + return; > + } > + > + d = qemu_mallocz(sizeof(LPCtpmState)); > + d->mem = cpu_register_io_memory(tis_readfn, tis_writefn, d); > + > + if (d->mem == -1) { > + fprintf(stderr, "Failed to register IO memory TPM TIS\n"); > + return; > + } > + > + cpu_register_physical_memory(TIS_ADDR_BASE, > + 0x1000 * NUM_LOCALITIES, d->mem); > + > + /* initialize tpmState */ > + s =&d->tpm; > + > + s->offset = 0; > + s->transmit = 0; > + s->aborting = 0; > + s->active_loc = NO_LOCALITY; > + > + while (c< NUM_LOCALITIES) { > + s->loc[c].access = (1<< 7); > + s->loc[c].sts = 0; > + s->loc[c].inte = (1<< 3); > + s->loc[c].ints = 0; > + s->loc[c].state = STATE_IDLE; > + c++; > + } > + s->set_irq = irq->handler; > + s->irq_opaque = irq->opaque; > + s->irq = irq->n; > + s->aborting_locty = NO_LOCALITY; > + s->tpmfd = tpmfd; > + s->path_len = strlen(tpm_device); > + s->path = tpm_device; > + > + memset(s->buf,0,sizeof(s->buf)); > + > + qemu_mutex_init(&s->tpm_thread_lock); > + qemu_cond_init(&s->tpm_thread_cond_data_avail); > + qemu_mutex_init(&s->tpm_thread_abort_lock); > + > + qemu_thread_create(&s->tpm_thread_id,&tpm_thread, s); > + > + register_savevm(NULL, "tpm-tis", 0, 1, tpm_save, tpm_load, s); > +} > + > + > +int tpm_init(QemuOpts *opts) { > + if(tpm_device == NULL) { > + tpm_device = qemu_opt_get(opts, "dev"); > + } > + return 0; > +} > + > +/****************************************************************************/ > +/* */ > +/* optional verbose logging of data to/from tpm chip */ > +/* */ > +/****************************************************************************/ > +#ifdef TPM_DEBUG > +static void showBuff(unsigned char *buff, const char *string) > +{ > + uint32_t i, len; > + > + len = tpm_get_size_from_buffer(buff); > + printf("%s length=%d\n", string, len); > + for (i = 0; i< len; i++) { > + if (i&& !(i % 16)) { > + printf("\n"); > + } > + printf("%.2X ", buff[i]); > + } > + printf("\n"); > +} > +#endif > + > + >
On Wed, Aug 25, 2010 at 4:33 PM, Andreas Niederl <andreas.niederl@iaik.tugraz.at> wrote: > This implementation is based on the TPM 1.2 interface for virtualized TPM > devices from the Xen-4.0.0 ioemu-qemu-xen fork. > > A separate thread is used for I/O to the host TPM device because the Linux TPM > driver does not allow for non-blocking I/O. > > Signed-off-by: Andreas Niederl <andreas.niederl@iaik.tugraz.at> In general, please read CODING_STYLE. Ideally, the host and guest devices should be decoupled so that the guest visible device should still be functional without host TPM support. > --- > Makefile.target | 3 + > configure | 9 + > hw/pc_piix.c | 8 + > hw/tpm_tis.c | 831 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 851 insertions(+), 0 deletions(-) > create mode 100644 hw/tpm_tis.c > > diff --git a/Makefile.target b/Makefile.target > index c8281e9..2226c75 100644 > --- a/Makefile.target > +++ b/Makefile.target > @@ -193,6 +193,9 @@ obj-y += e1000.o > # Inter-VM PCI shared memory > obj-$(CONFIG_KVM) += ivshmem.o > > +# TPM passthrough device > +obj-$(CONFIG_TPM) += tpm_tis.o qemu-thread.o The device does not have target dependencies, so please move these lines to Makefile.objs. > + > # Hardware support > obj-i386-y += vga.o > obj-i386-y += mc146818rtc.o i8259.o pc.o > diff --git a/configure b/configure > index a20371c..e1b55a8 100755 > --- a/configure > +++ b/configure > @@ -315,6 +315,7 @@ pkgversion="" > check_utests="no" > user_pie="no" > zero_malloc="" > +tpm="no" > > # OS specific > if check_define __linux__ ; then > @@ -448,6 +449,7 @@ AIX) > usb="linux" > if [ "$cpu" = "i386" -o "$cpu" = "x86_64" ] ; then > audio_possible_drivers="$audio_possible_drivers fmod" > + tpm="yes" > fi > ;; > esac > @@ -707,6 +709,8 @@ for opt do > ;; > --enable-vhost-net) vhost_net="yes" > ;; > + --disable-tpm) tpm="no" > + ;; > --*dir) > ;; > *) echo "ERROR: unknown option $opt"; show_help="yes" > @@ -895,6 +899,7 @@ echo " --enable-docs enable documentation build" > echo " --disable-docs disable documentation build" > echo " --disable-vhost-net disable vhost-net acceleration support" > echo " --enable-vhost-net enable vhost-net acceleration support" > +echo " --disable-tpm disable tpm passthrough device emulation" > echo "" > echo "NOTE: The object files are built at the place where configure is launched" > exit 1 > @@ -2187,6 +2192,7 @@ echo "preadv support $preadv" > echo "fdatasync $fdatasync" > echo "uuid support $uuid" > echo "vhost-net support $vhost_net" > +echo "tpm support $tpm" > > if test $sdl_too_old = "yes"; then > echo "-> Your SDL version is too old - please upgrade to have SDL support" > @@ -2423,6 +2429,9 @@ fi > if test "$fdatasync" = "yes" ; then > echo "CONFIG_FDATASYNC=y" >> $config_host_mak > fi > +if test "$tpm" = "yes" ; then > + echo "CONFIG_TPM=y" >> $config_host_mak > +fi > > # XXX: suppress that > if [ "$bsd" = "yes" ] ; then > diff --git a/hw/pc_piix.c b/hw/pc_piix.c > index 32a1057..ebc3478 100644 > --- a/hw/pc_piix.c > +++ b/hw/pc_piix.c > @@ -38,6 +38,10 @@ > > #define MAX_IDE_BUS 2 > > +#ifdef CONFIG_TPM > +void tpm_tis_init(qemu_irq irq); > +#endif This belongs to a header file. > + > static const int ide_iobase[MAX_IDE_BUS] = { 0x1f0, 0x170 }; > static const int ide_iobase2[MAX_IDE_BUS] = { 0x3f6, 0x376 }; > static const int ide_irq[MAX_IDE_BUS] = { 14, 15 }; > @@ -147,6 +151,10 @@ static void pc_init1(ram_addr_t ram_size, > } > } > > +#ifdef CONFIG_TPM > + tpm_tis_init(isa_reserve_irq(11)); > +#endif > + > pc_audio_init(pci_enabled ? pci_bus : NULL, isa_irq); > > pc_cmos_init(below_4g_mem_size, above_4g_mem_size, boot_device, > diff --git a/hw/tpm_tis.c b/hw/tpm_tis.c > new file mode 100644 > index 0000000..e109e1a > --- /dev/null > +++ b/hw/tpm_tis.c > @@ -0,0 +1,831 @@ > +/* > + * tpm_tis.c - QEMU emulator for a 1.2 TPM with TIS interface > + * > + * Copyright (C) 2006 IBM Corporation > + * Copyright (C) 2010 IAIK, Graz University of Technology > + * > + * Author: Stefan Berger <stefanb@us.ibm.com> > + * David Safford <safford@us.ibm.com> > + * > + * Author: Andreas Niederl <andreas.niederl@iaik.tugraz.at> > + * Pass through a TPM device rather than using the emulator > + * Modified to use a separate thread for IO to/from TPM as the Linux TPM driver > + * framework does not allow non-blocking IO > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License as > + * published by the Free Software Foundation, version 2 of the > + * License. > + * > + * > + * Implementation of the TIS interface according to specs at > + * https://www.trustedcomputinggroup.org/groups/pc_client/TCG_PCClientTPMSpecification_1-20_1-00_FINAL.pdf > + * > + */ > + > +#include <sys/types.h> > +#include <sys/stat.h> > +#include <errno.h> > +#include <string.h> > + > +#include "qemu-option.h" > +#include "qemu-config.h" > +#include "hw/hw.h" > +#include "hw/pc.h" > +#include "hw/pci.h" > +#include "hw/pci_ids.h" > +#include "qemu-thread.h" > + > +//#define TPM_DEBUG > + > +#define TPM_MAX_PKT 4096 > + > +#define TIS_ADDR_BASE 0xFED40000 > + > +/* tis registers */ > +#define TPM_REG_ACCESS 0x00 > +#define TPM_REG_INT_ENABLE 0x08 > +#define TPM_REG_INT_VECTOR 0x0c > +#define TPM_REG_INT_STATUS 0x10 > +#define TPM_REG_INTF_CAPABILITY 0x14 > +#define TPM_REG_STS 0x18 > +#define TPM_REG_DATA_FIFO 0x24 > +#define TPM_REG_DID_VID 0xf00 > +#define TPM_REG_RID 0xf04 > + > +#define STS_VALID (1 << 7) > +#define STS_COMMAND_READY (1 << 6) > +#define STS_TPM_GO (1 << 5) > +#define STS_DATA_AVAILABLE (1 << 4) > +#define STS_EXPECT (1 << 3) > +#define STS_RESPONSE_RETRY (1 << 1) > + > +#define ACCESS_TPM_REG_VALID_STS (1 << 7) > +#define ACCESS_ACTIVE_LOCALITY (1 << 5) > +#define ACCESS_BEEN_SEIZED (1 << 4) > +#define ACCESS_SEIZE (1 << 3) > +#define ACCESS_PENDING_REQUEST (1 << 2) > +#define ACCESS_REQUEST_USE (1 << 1) > +#define ACCESS_TPM_ESTABLISHMENT (1 << 0) > + > +#define INT_ENABLED (1 << 31) > +#define INT_DATA_AVAILABLE (1 << 0) > +#define INT_LOCALITY_CHANGED (1 << 2) > +#define INT_COMMAND_READY (1 << 7) > + > +#define INTERRUPTS_SUPPORTED (INT_LOCALITY_CHANGED | \ > + INT_DATA_AVAILABLE | \ > + INT_COMMAND_READY) > +#define CAPABILITIES_SUPPORTED ((1 << 4) | \ > + INTERRUPTS_SUPPORTED) > + > +enum { > + STATE_IDLE = 0, > + STATE_READY, > + STATE_COMPLETION, > + STATE_EXECUTION, > + STATE_RECEPTION > +}; > + > +#define NUM_LOCALITIES 5 > +#define NO_LOCALITY 0xff > + > +#define IS_VALID_LOC(x) ((x) < NUM_LOCALITIES) > + > +#define TPM_DID 0x0001 > +#define TPM_VID 0x0001 > +#define TPM_RID 0x0001 > + > + > +const char *tpm_device = NULL; This should be part of TPMState, not a global. > + > +/* locality data */ > +typedef struct TPMLocal { > + uint32_t state; > + uint8_t access; > + uint8_t sts; > + uint32_t inte; > + uint32_t ints; > +} tpmLoc; Typedefs should use CamelCase, so this should be named TPMLocal. Why not TPMLocality (for both)? > + > +/* overall state of the TPM interface; 's' marks as save upon suspension */ > +typedef struct TPMState { > + uint8_t transmit; /* boolean value */ > + /* set when tpm transmit in progress */ > + uint8_t aborting; /* boolean value */ > + uint32_t offset; /* s */ > + uint8_t buf[TPM_MAX_PKT]; /* s */ > + int tpmfd; /* s */ > + > + /* path to host tpm device */ > + const char *path; /* s */ > + uint32_t path_len; /* s */ > + > + QemuMutex tpm_thread_lock; > + QemuMutex tpm_thread_abort_lock; > + QemuCond tpm_thread_cond_data_avail; > + QemuThread tpm_thread_id; > + > + uint8_t active_loc; /* s */ > + uint8_t aborting_locty; > + uint8_t next_locty; > + uint8_t irq_pending; /* s */ > + tpmLoc loc[NUM_LOCALITIES]; /* s */ > + qemu_irq_handler set_irq; > + void *irq_opaque; > + int irq; > +} tpmState; Same here, TPMState. > + > + > +/* local prototypes */ > +static void tis_raise_irq(tpmState *s, uint8_t locty, uint32_t irqmask); > +static void tis_abort(tpmState *s); It's usually easy to reorder the code so that forward declarations are not needed. > + > +#ifdef TPM_DEBUG > +static void showBuff(unsigned char *buff, const char *string); Function names should not use CamelCase, so this should be renamed to show_buff(). > +#endif > + > + > +/********************************************************************** > + helper functions > + *********************************************************************/ > + > +static inline uint32_t tpm_get_size_from_buffer(const uint8_t *buffer) > +{ > + uint32_t len = (buffer[4] << 8) + buffer[5]; > + return len; > +} > + > +static inline uint8_t locality_from_addr(target_phys_addr_t addr) > +{ > + return (uint8_t)((addr >> 12) & 0x7); > +} > + > + > +static void die2(int err, const char *what) > +{ > + fprintf(stderr, "%s failed: %s\n", what, strerror(err)); > + abort(); > +} > + > +static void die(const char *what) > +{ > + die2(errno, what); > +} There's hw_error() and errx(), no need to reinvent the wheel. > + > + > +/* borrowed from qemu-char.c */ > +static int unix_write(int fd, const uint8_t *buf, uint32_t len1) > +{ > + int ret, len; > + > + len = len1; > + while (len > 0) { > + ret = write(fd, buf, len); > + if (ret < 0) { > + if (errno != EINTR && errno != EAGAIN) > + return -1; > + } else if (ret == 0) { > + break; > + } else { > + buf += ret; > + len -= ret; > + } > + } > + return len1 - len; > +} > + > +static int unix_tpm_read(int fd, uint8_t *buf) For consistency, this should be named 'unix_read'. > +{ > + int ret, len, len1; > + uint8_t *buf1; > + > + buf1 = buf; > + len = sizeof(buf) + TPM_MAX_PKT; > + while ((buf1 - buf) < 6) { > + ret = read(fd, buf1, len); > + if (ret < 0) { > + if (errno != EINTR && errno != EAGAIN) > + return -1; > + } else { > + buf1 += ret; > + } > + } > + > + len1 = tpm_get_size_from_buffer(buf); > + if (len1 > TPM_MAX_PKT) { > + fprintf(stderr, "Error: received invalid tpm response size: %d\n", > + len1); > + return -1; > + } > + > + /* response size minus already read data */ > + len = len1 - (buf1 - buf); > + while (len > 0) { > + ret = read(fd, buf, len); > + if (ret < 0) { > + if (errno != EINTR && errno != EAGAIN) > + return -1; > + } else if (ret == 0) { > + break; > + } else { > + buf += ret; > + len -= ret; > + } > + } > + return len1 - len; > +} > + > +/****************************************************************************/ > +/* Transmit request to TPM and read Response */ > +/****************************************************************************/ > + > +static void *tpm_thread(void *opaque) > +{ > + tpmState *s = opaque; > + sigset_t set; > + uint32_t size = 0; > + /* hardcode locality 0 */ > + uint8_t locty = 0; > +#ifdef TPM_DEBUG > + uint32_t ret; > +#endif > + > + /* block all signals */ > + if (sigfillset(&set)) die("sigfillset"); die()/errx() must be on a line of its own, also below. > + if (sigprocmask(SIG_BLOCK, &set, NULL)) die("sigprocmask"); > + > + qemu_mutex_lock(&s->tpm_thread_lock); > + while(1) { > + if(!s->transmit) There should be a space between 'if' and '(', also in other places. > + qemu_cond_wait(&s->tpm_thread_cond_data_avail, &s->tpm_thread_lock); > + qemu_mutex_unlock(&s->tpm_thread_lock); > + > +#ifdef TPM_DEBUG > + showBuff(s->buf, "To TPM"); > +#endif > + size = tpm_get_size_from_buffer(s->buf); > + > + if(unix_write(s->tpmfd, s->buf, size) < 0) { > + fprintf(stderr, "Error: while transmitting data to host tpm" > + ": %s (%i)\n", > + strerror(errno), errno); > + tis_abort(s); > + qemu_mutex_lock(&s->tpm_thread_lock); > + continue; > + } > + > + if(unix_tpm_read(s->tpmfd, s->buf) < 0) { > + fprintf(stderr, "Error: while reading data from host tpm" > + ": %s (%i)\n", > + strerror(errno), errno); > + tis_abort(s); > + qemu_mutex_lock(&s->tpm_thread_lock); > + continue; > + } > + > + qemu_mutex_lock(&s->tpm_thread_abort_lock); > + if(!s->aborting) { > +#ifdef TPM_DEBUG > + showBuff(s->buf, "From TPM"); > + > + ret = (s->buf[8])*256 + s->buf[9]; > + if (ret) > + printf("tpm command failed with error %d\n", ret); > + else > + printf("tpm command succeeded\n"); > +#endif > + > + qemu_mutex_lock(&s->tpm_thread_lock); > + s->loc[locty].sts = STS_VALID | STS_DATA_AVAILABLE; > + s->loc[locty].state = STATE_COMPLETION; > + s->transmit = 0; > + tis_raise_irq(s, locty, INT_DATA_AVAILABLE); > + } else { > + qemu_mutex_lock(&s->tpm_thread_lock); > + tis_abort(s); > +#ifdef TPM_DEBUG > + printf("Abort is complete.\n"); > +#endif > + s->transmit = 0; > + } > + qemu_mutex_unlock(&s->tpm_thread_abort_lock); > + } > + > + return NULL; > +} > + > + > +/**********************************************************************/ > + > +/* > + * read a byte of response data > + */ > +static uint32_t tpm_data_read(tpmState *s, uint8_t locty) > +{ > + uint32_t ret, len; > + > + if (s->loc[locty].state != STATE_COMPLETION) { > +#ifdef TPM_DEBUG > + printf("tpm_data_read with no data available!\n"); > +#endif Please introduce a macro DPRINTF instead. See other devices for examples. > + return 0xff; > + } > + > + len = tpm_get_size_from_buffer(s->buf); > + ret = s->buf[s->offset++]; > + if (s->offset >= len) { > + s->loc[locty].sts = STS_VALID ; > + s->offset = 0; > + } > +#ifdef TPM_DEBUG > + printf("tpm_data_read byte x%02x [%d]\n",ret,s->offset-1); > +#endif > + return ret; > +} > + > +/* raise an interrupt if allowed */ > +static void tis_raise_irq(tpmState *s, uint8_t locty, uint32_t irqmask) > +{ > + if (!s->irq_pending && > + (s->loc[locty].inte & INT_ENABLED) && > + (s->loc[locty].inte & irqmask)) { > + if ((irqmask & s->loc[locty].ints) == 0) { > +#ifdef TPM_DEBUG > + printf("Raising IRQ for flag %08x\n",irqmask); > +#endif > + s->set_irq(s->irq_opaque, s->irq, 1); > + s->irq_pending = 1; > + s->loc[locty].ints |= irqmask; > + } > + } > +} > + > +/* abort execution of command */ > +static void tis_abort(tpmState *s) > +{ > + s->offset = 0; > + s->active_loc = s->next_locty; > + qemu_mutex_lock(&s->tpm_thread_lock); > + s->transmit = 0; > + qemu_mutex_unlock(&s->tpm_thread_lock); > + > + /* > + * Need to react differently depending on who's aborting now and > + * which locality will become active afterwards. > + */ > + if (s->aborting_locty == s->next_locty) { > + s->loc[s->aborting_locty].state = STATE_READY; > + s->loc[s->aborting_locty].sts = STS_COMMAND_READY; > + tis_raise_irq(s, s->aborting_locty, INT_COMMAND_READY); > + } > + > + /* locality after abort is another one than the current one */ > + if (s->aborting_locty != s->next_locty && s->next_locty != NO_LOCALITY) { > + s->loc[s->aborting_locty].access &= ~ACCESS_ACTIVE_LOCALITY; > + s->loc[s->next_locty].access |= ACCESS_ACTIVE_LOCALITY; > + tis_raise_irq(s, s->next_locty, INT_LOCALITY_CHANGED); > + } > + > + s->aborting_locty = NO_LOCALITY; /* nobody's aborting a command anymore */ > +} > + > +/* abort current command */ > +static void tis_prep_abort(tpmState *s, uint8_t locty, uint8_t newlocty) > +{ > + s->aborting_locty = locty; /* current locality */ > + s->next_locty = newlocty; /* locality after successful abort */ > + > + s->next_locty = 0; /* only locality 0 available */ > + > + /* > + * only abort a command using an interrupt if currently executing > + * a command AND if there's a valid connection to the vTPM. > + */ > + if (s->loc[locty].state == STATE_EXECUTION) { > + qemu_mutex_lock(&s->tpm_thread_abort_lock); > + s->aborting = 1; > + qemu_mutex_unlock(&s->tpm_thread_abort_lock); > + } else { > + tis_abort(s); > + } > +} > + > + > +/* > + * Read a register of the TIS interface > + * See specs pages 33-63 for description of the registers > + */ > +static uint32_t tis_mem_readl(void *opaque, target_phys_addr_t addr) > +{ > + tpmState *s = (tpmState *)opaque; Useless cast in C. > + uint16_t offset = addr & 0xffc; > + uint8_t shift = (addr & 0x3) * 8; > + uint32_t val = 0; > + uint8_t locty = locality_from_addr(addr); > + > + if (offset == TPM_REG_ACCESS) { Please use switch() instead of this chain of 'if/else if'. > + if (locty > 0) { > + /* no access to localities other than 0 possible */ > + val = 0; > + } else { > + if (s->active_loc == locty) { > + s->loc[locty].access |= (1 << 5); > + } else { > + s->loc[locty].access &= ~(1 << 5); > + } > + val = s->loc[locty].access; > + } > + } else > + if (locty > 0) { > + /* higher localities deactivated */ > + val = 0xffff; > + } else > + if (offset == TPM_REG_INT_ENABLE) { > + val = s->loc[locty].inte; > + } else > + if (offset == TPM_REG_INT_VECTOR) { > + val = s->irq; > + } else > + if (offset == TPM_REG_INT_STATUS) { > + val = s->loc[locty].ints; > + } else > + if (offset == TPM_REG_INTF_CAPABILITY) { > + val = CAPABILITIES_SUPPORTED; > + } else > + if (offset == TPM_REG_STS) { /* status register */ > + /* ??? - appears to be just some value as we aren't a hardware TPM */ > + val = (sizeof(s->buf) - s->offset) << 8 | s->loc[locty].sts; > + } else > + if (offset == TPM_REG_DATA_FIFO) { > + val = tpm_data_read(s, locty); > + } else > + if (offset == TPM_REG_DID_VID) { > + val = (TPM_DID << 16) | TPM_VID; > + } else > + if (offset == TPM_REG_RID) { > + val = TPM_RID; > + } > + > + if (shift && locty == 0) { > + val >>= shift; > + } > + > +#ifdef TPM_DEBUG > + printf(" read(%08x) = %08x\n", > + (int)addr, We have FMT_plx, please use that instead of cast. > + val); > +#endif > + > + return val; > +} > + > +/* > + * Write a value to a register of the TIS interface > + * See specs pages 33-63 for description of the registers > + */ > +static void tis_mem_writel(void *opaque, target_phys_addr_t addr, uint32_t val) > +{ > + tpmState* s=(tpmState*)opaque; > + uint16_t off = addr & 0xfff; > + uint8_t locty = locality_from_addr(addr); > + uint32_t len; > + > +#ifdef TPM_DEBUG > + printf("write(%08x) = %08x\n", > + (int)addr, > + val); > +#endif > + > + /* linux tpm_tis driver only uses locality 0 */ > + if (locty > 0) { > + fprintf(stderr, "Warning: Write to unavailable locality: %d \n", locty); > + return; > + } > +#ifdef TPM_DEBUG > + if(s->transmit) { > + fprintf(stderr, "Warning: tpm received data while transmit" > + " in progress.\n"); > + printf("WARN: write(%08x) = %08x\n", > + (int)addr, > + val); > + } > +#endif > + > + if (off == TPM_REG_ACCESS) { > + if (val & ACCESS_ACTIVE_LOCALITY) { > + /* give up locality if currently owned */ > + if (s->active_loc == locty) { > + /*uint8_t newlocty = NO_LOCALITY;*/ > + s->loc[locty].access &= ~(ACCESS_PENDING_REQUEST); > + /* anybody wants the locality ? */ > + if (s->loc[locty].access & ACCESS_REQUEST_USE) { > + s->loc[locty].access |= ACCESS_TPM_REG_VALID_STS; > + s->loc[locty].access &= ~ACCESS_REQUEST_USE; > + } > + tis_prep_abort(s, locty, locty); > + } > + } > + if (val & ACCESS_BEEN_SEIZED) { > + /* clear the flag */ > + s->loc[locty].access &= ~ACCESS_BEEN_SEIZED; > + } > + if (val & ACCESS_SEIZE) { > + if (locty > s->active_loc && IS_VALID_LOC(s->active_loc)) { > + s->loc[s->active_loc].access |= ACCESS_BEEN_SEIZED; > + s->loc[locty].access = ACCESS_TPM_REG_VALID_STS; > + tis_prep_abort(s, s->active_loc, locty); > + } > + } > + if (val & ACCESS_REQUEST_USE) { > + if (IS_VALID_LOC(s->active_loc)) { > + /* locality election */ > + s->loc[s->active_loc].access |= ACCESS_PENDING_REQUEST; > + } else { > + /* no locality active -> make this one active now */ > + s->loc[locty].access |= ACCESS_ACTIVE_LOCALITY; > + s->active_loc = locty; > + tis_raise_irq(s, locty, INT_LOCALITY_CHANGED); > + } > + } > + } else > + if (off == TPM_REG_INT_ENABLE) { > + s->loc[locty].inte = (val & (INT_ENABLED | (0x3 << 3) | > + INTERRUPTS_SUPPORTED)); > + } else > + if (off == TPM_REG_INT_STATUS) { > + /* clearing of interrupt flags */ > + if ((val & INTERRUPTS_SUPPORTED) && > + (s->loc[locty].ints & INTERRUPTS_SUPPORTED)) { > + s->set_irq(s->irq_opaque, s->irq, 0); > + s->irq_pending = 0; > + } > + s->loc[locty].ints &= ~(val & INTERRUPTS_SUPPORTED); > + } else > + if (off == TPM_REG_STS) { > + if (val & STS_COMMAND_READY) { > + if (s->loc[locty].state == STATE_IDLE) { > + s->loc[locty].sts = STS_COMMAND_READY; > + s->loc[locty].state = STATE_READY; > + tis_raise_irq(s, locty, INT_COMMAND_READY); > + } else if (s->loc[locty].state == STATE_COMPLETION || > + s->loc[locty].state == STATE_EXECUTION || > + s->loc[locty].state == STATE_RECEPTION) { > + /* abort currently running command */ > + tis_prep_abort(s, locty, locty); > + } > + } > + if (val & STS_TPM_GO) { > + s->offset = 0; > + > + if (s->transmit != 1) { > + qemu_mutex_lock(&s->tpm_thread_lock); > + s->transmit = 1; > + s->loc[locty].state = STATE_EXECUTION; > + qemu_mutex_unlock(&s->tpm_thread_lock); > + qemu_cond_signal(&s->tpm_thread_cond_data_avail); > + } > + } > + if (val & STS_RESPONSE_RETRY) { > + s->offset = 0; > + } > + } else if (off == TPM_REG_DATA_FIFO) { > + /* data fifo */ > + if (s->loc[locty].state == STATE_IDLE || > + s->loc[locty].state == STATE_EXECUTION || > + s->loc[locty].state == STATE_COMPLETION) { > + /* drop the byte */ > + } else { > +#ifdef TPM_DEBUG > + printf("Byte to send to TPM: %02x at offset: %03d\n", val, s->offset); > +#endif > + s->loc[locty].state = STATE_RECEPTION; > + s->loc[locty].sts = STS_EXPECT | STS_VALID; > + > + if (s->offset < TPM_MAX_PKT) > + s->buf[s->offset++] = (uint8_t)val; > + > + if (s->offset > 5) { > + /* we have a packet length - see if we have all of it */ > + len = tpm_get_size_from_buffer(s->buf); > + if (s->offset >= len) { > +#ifdef TPM_DEBUG > + printf("We have a complete packet of %u bytes\n", len); > +#endif > + s->offset = 0; > + > + qemu_mutex_lock(&s->tpm_thread_lock); > + s->transmit = 1; > + qemu_mutex_unlock(&s->tpm_thread_lock); > + qemu_cond_signal(&s->tpm_thread_cond_data_avail); > + s->loc[locty].sts = STS_VALID; > + } > + } > + } > + } > +} > + > + > +static CPUReadMemoryFunc *tis_readfn[3]={ 'const' > + tis_mem_readl, > + tis_mem_readl, > + tis_mem_readl > +}; > + > +static CPUWriteMemoryFunc *tis_writefn[3]={ > + tis_mem_writel, > + tis_mem_writel, > + tis_mem_writel > +}; > + > +/* > + * Save the internal state of this interface for later resumption. > + * Need to get any outstanding responses from the vTPM back, so > + * this might delay the suspend for a while. > + */ > +static void tpm_save(QEMUFile* f,void* opaque) Please convert the save and load functions to VMState. > +{ > + tpmState* s=(tpmState*)opaque; > + uint8_t locty = s->active_loc; > + int c; > + > + /* need to wait for outstanding requests to complete */ > + if (s->loc[locty].state == STATE_EXECUTION) { > + int repeats = 30; /* 30 seconds; really should be infty */ > + while (repeats > 0 && > + !(s->loc[s->active_loc].sts & STS_DATA_AVAILABLE)) { > + sleep(1); > + } > + } > + > + close(s->tpmfd); > + > + qemu_put_be32s(f,&s->offset); > + qemu_put_buffer(f, s->buf, TPM_MAX_PKT); > + qemu_put_8s(f, &s->active_loc); > + qemu_put_8s(f, &s->irq_pending); > + for (c = 0; c < NUM_LOCALITIES; c++) { > + qemu_put_be32s(f, &s->loc[c].state); > + qemu_put_8s(f, &s->loc[c].access); > + qemu_put_8s(f, &s->loc[c].sts); > + qemu_put_be32s(f, &s->loc[c].inte); > + qemu_put_be32s(f, &s->loc[c].ints); > + } > + qemu_put_buffer(f, (uint8_t*) s->path, s->path_len); > + qemu_put_be32s(f, &s->path_len); > +} > + > + > +/* > + * load TIS interface state > + */ > +static int tpm_load(QEMUFile* f,void* opaque,int version_id) > +{ > + tpmState* s=(tpmState*)opaque; > + int c; > + > + if (version_id != 1) > + return -EINVAL; > + > + qemu_get_be32s(f,&s->offset); > + qemu_get_buffer(f, s->buf, TPM_MAX_PKT); > + qemu_get_8s(f, &s->active_loc); > + qemu_get_8s(f, &s->irq_pending); > + for (c = 0; c < NUM_LOCALITIES; c++) { > + qemu_get_be32s(f, &s->loc[c].state); > + qemu_get_8s(f, &s->loc[c].access); > + qemu_get_8s(f, &s->loc[c].sts); > + qemu_get_be32s(f, &s->loc[c].inte); > + qemu_get_be32s(f, &s->loc[c].ints); > + } > + qemu_get_be32s(f, &s->path_len); > + qemu_get_buffer(f, (uint8_t*) s->path, s->path_len); > + > + s->tpmfd = open(s->path, O_RDWR); What happens to old s->tpmfd? The user can issue multiple 'loadvm' commands. > + > + if(s->tpmfd < 0) > + hw_error("Cannot open %s: %s (%i)\n", s->path, strerror(errno), errno); > + > + qemu_mutex_init(&s->tpm_thread_lock); > + qemu_cond_init( &s->tpm_thread_cond_data_avail); > + qemu_mutex_init(&s->tpm_thread_abort_lock); > + > + qemu_thread_create(&s->tpm_thread_id, &tpm_thread, s); > + > + return 0; > +} > + > + > +typedef struct LPCtpmState { > + tpmState tpm; > + int mem; > +} LPCtpmState; > + > + > +/* > + * initialize TIS interface > + */ > + > +struct IRQState { > + qemu_irq_handler handler; > + void *opaque; > + int n; > +}; Why is this needed? > + > +void tpm_tis_init(qemu_irq irq); This belongs to a header file somewhere. > + > +void tpm_tis_init(qemu_irq irq) > +{ Please convert to qdev. Some of the initialization code can be refactored to a reset handler as well. > + LPCtpmState *d; > + tpmState *s; > + int c = 0; > + int tpmfd = -1; > + > + if(tpm_device == NULL) { > + return; > + } > + > + tpmfd = open(tpm_device, O_RDWR); > + if(tpmfd < 0) { > + fprintf(stderr, "Cannot open %s: %s (%i)\n", tpm_device, > + strerror(errno), errno); > + return; > + } > + > + d = qemu_mallocz(sizeof(LPCtpmState)); > + d->mem = cpu_register_io_memory(tis_readfn, tis_writefn, d); > + > + if (d->mem == -1) { > + fprintf(stderr, "Failed to register IO memory TPM TIS\n"); > + return; > + } > + > + cpu_register_physical_memory(TIS_ADDR_BASE, > + 0x1000 * NUM_LOCALITIES, d->mem); > + > + /* initialize tpmState */ > + s = &d->tpm; > + > + s->offset = 0; > + s->transmit = 0; > + s->aborting = 0; > + s->active_loc = NO_LOCALITY; > + > + while (c < NUM_LOCALITIES) { > + s->loc[c].access = (1 << 7); > + s->loc[c].sts = 0; > + s->loc[c].inte = (1 << 3); > + s->loc[c].ints = 0; > + s->loc[c].state = STATE_IDLE; > + c++; > + } > + s->set_irq = irq->handler; > + s->irq_opaque = irq->opaque; > + s->irq = irq->n; > + s->aborting_locty = NO_LOCALITY; > + s->tpmfd = tpmfd; > + s->path_len = strlen(tpm_device); > + s->path = tpm_device; > + > + memset(s->buf,0,sizeof(s->buf)); > + > + qemu_mutex_init(&s->tpm_thread_lock); > + qemu_cond_init( &s->tpm_thread_cond_data_avail); > + qemu_mutex_init(&s->tpm_thread_abort_lock); > + > + qemu_thread_create(&s->tpm_thread_id, &tpm_thread, s); > + > + register_savevm(NULL, "tpm-tis", 0, 1, tpm_save, tpm_load, s); > +} > + > + > +int tpm_init(QemuOpts *opts) { > + if(tpm_device == NULL) { > + tpm_device = qemu_opt_get(opts, "dev"); > + } > + return 0; > +} > + > +/****************************************************************************/ > +/* */ > +/* optional verbose logging of data to/from tpm chip */ > +/* */ > +/****************************************************************************/ > +#ifdef TPM_DEBUG > +static void showBuff(unsigned char *buff, const char *string) > +{ > + uint32_t i, len; > + > + len = tpm_get_size_from_buffer(buff); > + printf("%s length=%d\n", string, len); > + for (i = 0; i < len; i++) { > + if (i && !(i % 16)) { > + printf("\n"); > + } > + printf("%.2X ", buff[i]); > + } > + printf("\n"); > +} > +#endif > + > + > -- > 1.7.2.2 > > >
On Wed, 25 Aug 2010, Blue Swirl wrote: > On Wed, Aug 25, 2010 at 4:33 PM, Andreas Niederl > <andreas.niederl@iaik.tugraz.at> wrote: > > This implementation is based on the TPM 1.2 interface for virtualized TPM > > devices from the Xen-4.0.0 ioemu-qemu-xen fork. > > > > A separate thread is used for I/O to the host TPM device because the Linux TPM > > driver does not allow for non-blocking I/O. > > > > Signed-off-by: Andreas Niederl <andreas.niederl@iaik.tugraz.at> > > In general, please read CODING_STYLE. > > Ideally, the host and guest devices should be decoupled so that the > guest visible device should still be functional without host TPM > support. > > > --- [..snip..] > > +static inline uint32_t tpm_get_size_from_buffer(const uint8_t *buffer) > > +{ > > + uint32_t len = (buffer[4] << 8) + buffer[5]; > > + return len; > > +} > > + > > +static inline uint8_t locality_from_addr(target_phys_addr_t addr) > > +{ > > + return (uint8_t)((addr >> 12) & 0x7); > > +} > > + > > + > > +static void die2(int err, const char *what) > > +{ > > + fprintf(stderr, "%s failed: %s\n", what, strerror(err)); > > + abort(); > > +} > > + > > +static void die(const char *what) > > +{ > > + die2(errno, what); > > +} > > There's hw_error() and errx(), no need to reinvent the wheel. Since it looks as if this code was based on mine, i take issues with your analysis. Firstly hw_error is for hardware errors and does not print message corresponding to errno code and lastly there is no errx[1] [..snip..] [1] err(3): CONFORMING TO These functions are non-standard BSD extensions.
On Wed, Aug 25, 2010 at 8:46 PM, malc <av1474@comtv.ru> wrote: > On Wed, 25 Aug 2010, Blue Swirl wrote: > >> On Wed, Aug 25, 2010 at 4:33 PM, Andreas Niederl >> <andreas.niederl@iaik.tugraz.at> wrote: >> > This implementation is based on the TPM 1.2 interface for virtualized TPM >> > devices from the Xen-4.0.0 ioemu-qemu-xen fork. >> > >> > A separate thread is used for I/O to the host TPM device because the Linux TPM >> > driver does not allow for non-blocking I/O. >> > >> > Signed-off-by: Andreas Niederl <andreas.niederl@iaik.tugraz.at> >> >> In general, please read CODING_STYLE. >> >> Ideally, the host and guest devices should be decoupled so that the >> guest visible device should still be functional without host TPM >> support. >> >> > --- > > [..snip..] > >> > +static inline uint32_t tpm_get_size_from_buffer(const uint8_t *buffer) >> > +{ >> > + uint32_t len = (buffer[4] << 8) + buffer[5]; >> > + return len; >> > +} >> > + >> > +static inline uint8_t locality_from_addr(target_phys_addr_t addr) >> > +{ >> > + return (uint8_t)((addr >> 12) & 0x7); >> > +} >> > + >> > + >> > +static void die2(int err, const char *what) >> > +{ >> > + fprintf(stderr, "%s failed: %s\n", what, strerror(err)); >> > + abort(); >> > +} >> > + >> > +static void die(const char *what) >> > +{ >> > + die2(errno, what); >> > +} >> >> There's hw_error() and errx(), no need to reinvent the wheel. > > Since it looks as if this code was based on mine, i take issues with > your analysis. > > Firstly hw_error is for hardware errors and does not print message > corresponding to errno code Right, the correct function should be error_report(). > and lastly there is no errx[1] > > [..snip..] > > [1] err(3): > CONFORMING TO > These functions are non-standard BSD extensions. err() and errx() are already used somewhere. But these should be converted to error_report etc., like other printf based local functions.
On Thu, 26 Aug 2010, Blue Swirl wrote: > On Wed, Aug 25, 2010 at 8:46 PM, malc <av1474@comtv.ru> wrote: > > On Wed, 25 Aug 2010, Blue Swirl wrote: > > > >> On Wed, Aug 25, 2010 at 4:33 PM, Andreas Niederl > >> <andreas.niederl@iaik.tugraz.at> wrote: > >> > This implementation is based on the TPM 1.2 interface for virtualized TPM > >> > devices from the Xen-4.0.0 ioemu-qemu-xen fork. > >> > [..snip..] > >> There's hw_error() and errx(), no need to reinvent the wheel. > > > > Since it looks as if this code was based on mine, i take issues with > > your analysis. > > > > Firstly hw_error is for hardware errors and does not print message > > corresponding to errno code > > Right, the correct function should be error_report(). Which is not at all the same as err/errx or die/die2 (doesn't abort/exit) [FWIW die/die2 predates error_report, furthermore error_report kind of breaks the convention that public functions/helpers have a qemu_ prefix] > > > and lastly there is no errx[1] > > > > [..snip..] > > > > [1] err(3): > > CONFORMING TO > > These functions are non-standard BSD extensions. > > err() and errx() are already used somewhere. But these should be > converted to error_report etc., like other printf based local > functions. > Only in Linux specific nbd code.
On 08/26/2010 04:44 PM, malc wrote: >> >>> and lastly there is no errx[1] >>> >>> [..snip..] >>> >>> [1] err(3): >>> CONFORMING TO >>> These functions are non-standard BSD extensions. >>> >> err() and errx() are already used somewhere. But these should be >> converted to error_report etc., like other printf based local >> functions. >> >> > Only in Linux specific nbd code. > And the nbd stuff is derived from non-QEMU code that was brought into the tree. It's generally a bad example of QEMU coding style. Regards, Anthony Liguori
diff --git a/Makefile.target b/Makefile.target index c8281e9..2226c75 100644 --- a/Makefile.target +++ b/Makefile.target @@ -193,6 +193,9 @@ obj-y += e1000.o # Inter-VM PCI shared memory obj-$(CONFIG_KVM) += ivshmem.o +# TPM passthrough device +obj-$(CONFIG_TPM) += tpm_tis.o qemu-thread.o + # Hardware support obj-i386-y += vga.o obj-i386-y += mc146818rtc.o i8259.o pc.o diff --git a/configure b/configure index a20371c..e1b55a8 100755 --- a/configure +++ b/configure @@ -315,6 +315,7 @@ pkgversion="" check_utests="no" user_pie="no" zero_malloc="" +tpm="no" # OS specific if check_define __linux__ ; then @@ -448,6 +449,7 @@ AIX) usb="linux" if [ "$cpu" = "i386" -o "$cpu" = "x86_64" ] ; then audio_possible_drivers="$audio_possible_drivers fmod" + tpm="yes" fi ;; esac @@ -707,6 +709,8 @@ for opt do ;; --enable-vhost-net) vhost_net="yes" ;; + --disable-tpm) tpm="no" + ;; --*dir) ;; *) echo "ERROR: unknown option $opt"; show_help="yes" @@ -895,6 +899,7 @@ echo " --enable-docs enable documentation build" echo " --disable-docs disable documentation build" echo " --disable-vhost-net disable vhost-net acceleration support" echo " --enable-vhost-net enable vhost-net acceleration support" +echo " --disable-tpm disable tpm passthrough device emulation" echo "" echo "NOTE: The object files are built at the place where configure is launched" exit 1 @@ -2187,6 +2192,7 @@ echo "preadv support $preadv" echo "fdatasync $fdatasync" echo "uuid support $uuid" echo "vhost-net support $vhost_net" +echo "tpm support $tpm" if test $sdl_too_old = "yes"; then echo "-> Your SDL version is too old - please upgrade to have SDL support" @@ -2423,6 +2429,9 @@ fi if test "$fdatasync" = "yes" ; then echo "CONFIG_FDATASYNC=y" >> $config_host_mak fi +if test "$tpm" = "yes" ; then + echo "CONFIG_TPM=y" >> $config_host_mak +fi # XXX: suppress that if [ "$bsd" = "yes" ] ; then diff --git a/hw/pc_piix.c b/hw/pc_piix.c index 32a1057..ebc3478 100644 --- a/hw/pc_piix.c +++ b/hw/pc_piix.c @@ -38,6 +38,10 @@ #define MAX_IDE_BUS 2 +#ifdef CONFIG_TPM +void tpm_tis_init(qemu_irq irq); +#endif + static const int ide_iobase[MAX_IDE_BUS] = { 0x1f0, 0x170 }; static const int ide_iobase2[MAX_IDE_BUS] = { 0x3f6, 0x376 }; static const int ide_irq[MAX_IDE_BUS] = { 14, 15 }; @@ -147,6 +151,10 @@ static void pc_init1(ram_addr_t ram_size, } } +#ifdef CONFIG_TPM + tpm_tis_init(isa_reserve_irq(11)); +#endif + pc_audio_init(pci_enabled ? pci_bus : NULL, isa_irq); pc_cmos_init(below_4g_mem_size, above_4g_mem_size, boot_device, diff --git a/hw/tpm_tis.c b/hw/tpm_tis.c new file mode 100644 index 0000000..e109e1a --- /dev/null +++ b/hw/tpm_tis.c @@ -0,0 +1,831 @@ +/* + * tpm_tis.c - QEMU emulator for a 1.2 TPM with TIS interface + * + * Copyright (C) 2006 IBM Corporation + * Copyright (C) 2010 IAIK, Graz University of Technology + * + * Author: Stefan Berger <stefanb@us.ibm.com> + * David Safford <safford@us.ibm.com> + * + * Author: Andreas Niederl <andreas.niederl@iaik.tugraz.at> + * Pass through a TPM device rather than using the emulator + * Modified to use a separate thread for IO to/from TPM as the Linux TPM driver + * framework does not allow non-blocking IO + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * + * Implementation of the TIS interface according to specs at + * https://www.trustedcomputinggroup.org/groups/pc_client/TCG_PCClientTPMSpecification_1-20_1-00_FINAL.pdf + * + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <string.h> + +#include "qemu-option.h" +#include "qemu-config.h" +#include "hw/hw.h" +#include "hw/pc.h" +#include "hw/pci.h" +#include "hw/pci_ids.h" +#include "qemu-thread.h" + +//#define TPM_DEBUG + +#define TPM_MAX_PKT 4096 + +#define TIS_ADDR_BASE 0xFED40000 + +/* tis registers */ +#define TPM_REG_ACCESS 0x00 +#define TPM_REG_INT_ENABLE 0x08 +#define TPM_REG_INT_VECTOR 0x0c +#define TPM_REG_INT_STATUS 0x10 +#define TPM_REG_INTF_CAPABILITY 0x14 +#define TPM_REG_STS 0x18 +#define TPM_REG_DATA_FIFO 0x24 +#define TPM_REG_DID_VID 0xf00 +#define TPM_REG_RID 0xf04 + +#define STS_VALID (1 << 7) +#define STS_COMMAND_READY (1 << 6) +#define STS_TPM_GO (1 << 5) +#define STS_DATA_AVAILABLE (1 << 4) +#define STS_EXPECT (1 << 3) +#define STS_RESPONSE_RETRY (1 << 1) + +#define ACCESS_TPM_REG_VALID_STS (1 << 7) +#define ACCESS_ACTIVE_LOCALITY (1 << 5) +#define ACCESS_BEEN_SEIZED (1 << 4) +#define ACCESS_SEIZE (1 << 3) +#define ACCESS_PENDING_REQUEST (1 << 2) +#define ACCESS_REQUEST_USE (1 << 1) +#define ACCESS_TPM_ESTABLISHMENT (1 << 0) + +#define INT_ENABLED (1 << 31) +#define INT_DATA_AVAILABLE (1 << 0) +#define INT_LOCALITY_CHANGED (1 << 2) +#define INT_COMMAND_READY (1 << 7) + +#define INTERRUPTS_SUPPORTED (INT_LOCALITY_CHANGED | \ + INT_DATA_AVAILABLE | \ + INT_COMMAND_READY) +#define CAPABILITIES_SUPPORTED ((1 << 4) | \ + INTERRUPTS_SUPPORTED) + +enum { + STATE_IDLE = 0, + STATE_READY, + STATE_COMPLETION, + STATE_EXECUTION, + STATE_RECEPTION +}; + +#define NUM_LOCALITIES 5 +#define NO_LOCALITY 0xff + +#define IS_VALID_LOC(x) ((x) < NUM_LOCALITIES) + +#define TPM_DID 0x0001 +#define TPM_VID 0x0001 +#define TPM_RID 0x0001 + + +const char *tpm_device = NULL; + +/* locality data */ +typedef struct TPMLocal { + uint32_t state; + uint8_t access; + uint8_t sts; + uint32_t inte; + uint32_t ints; +} tpmLoc; + +/* overall state of the TPM interface; 's' marks as save upon suspension */ +typedef struct TPMState { + uint8_t transmit; /* boolean value */ + /* set when tpm transmit in progress */ + uint8_t aborting; /* boolean value */ + uint32_t offset; /* s */ + uint8_t buf[TPM_MAX_PKT]; /* s */ + int tpmfd; /* s */ + + /* path to host tpm device */ + const char *path; /* s */ + uint32_t path_len; /* s */ + + QemuMutex tpm_thread_lock; + QemuMutex tpm_thread_abort_lock; + QemuCond tpm_thread_cond_data_avail; + QemuThread tpm_thread_id; + + uint8_t active_loc; /* s */ + uint8_t aborting_locty; + uint8_t next_locty; + uint8_t irq_pending; /* s */ + tpmLoc loc[NUM_LOCALITIES]; /* s */ + qemu_irq_handler set_irq; + void *irq_opaque; + int irq; +} tpmState; + + +/* local prototypes */ +static void tis_raise_irq(tpmState *s, uint8_t locty, uint32_t irqmask); +static void tis_abort(tpmState *s); + +#ifdef TPM_DEBUG +static void showBuff(unsigned char *buff, const char *string); +#endif + + +/********************************************************************** + helper functions + *********************************************************************/ + +static inline uint32_t tpm_get_size_from_buffer(const uint8_t *buffer) +{ + uint32_t len = (buffer[4] << 8) + buffer[5]; + return len; +} + +static inline uint8_t locality_from_addr(target_phys_addr_t addr) +{ + return (uint8_t)((addr >> 12) & 0x7); +} + + +static void die2(int err, const char *what) +{ + fprintf(stderr, "%s failed: %s\n", what, strerror(err)); + abort(); +} + +static void die(const char *what) +{ + die2(errno, what); +} + + +/* borrowed from qemu-char.c */ +static int unix_write(int fd, const uint8_t *buf, uint32_t len1) +{ + int ret, len; + + len = len1; + while (len > 0) { + ret = write(fd, buf, len); + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN) + return -1; + } else if (ret == 0) { + break; + } else { + buf += ret; + len -= ret; + } + } + return len1 - len; +} + +static int unix_tpm_read(int fd, uint8_t *buf) +{ + int ret, len, len1; + uint8_t *buf1; + + buf1 = buf; + len = sizeof(buf) + TPM_MAX_PKT; + while ((buf1 - buf) < 6) { + ret = read(fd, buf1, len); + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN) + return -1; + } else { + buf1 += ret; + } + } + + len1 = tpm_get_size_from_buffer(buf); + if (len1 > TPM_MAX_PKT) { + fprintf(stderr, "Error: received invalid tpm response size: %d\n", + len1); + return -1; + } + + /* response size minus already read data */ + len = len1 - (buf1 - buf); + while (len > 0) { + ret = read(fd, buf, len); + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN) + return -1; + } else if (ret == 0) { + break; + } else { + buf += ret; + len -= ret; + } + } + return len1 - len; +} + +/****************************************************************************/ +/* Transmit request to TPM and read Response */ +/****************************************************************************/ + +static void *tpm_thread(void *opaque) +{ + tpmState *s = opaque; + sigset_t set; + uint32_t size = 0; + /* hardcode locality 0 */ + uint8_t locty = 0; +#ifdef TPM_DEBUG + uint32_t ret; +#endif + + /* block all signals */ + if (sigfillset(&set)) die("sigfillset"); + if (sigprocmask(SIG_BLOCK, &set, NULL)) die("sigprocmask"); + + qemu_mutex_lock(&s->tpm_thread_lock); + while(1) { + if(!s->transmit) + qemu_cond_wait(&s->tpm_thread_cond_data_avail, &s->tpm_thread_lock); + qemu_mutex_unlock(&s->tpm_thread_lock); + +#ifdef TPM_DEBUG + showBuff(s->buf, "To TPM"); +#endif + size = tpm_get_size_from_buffer(s->buf); + + if(unix_write(s->tpmfd, s->buf, size) < 0) { + fprintf(stderr, "Error: while transmitting data to host tpm" + ": %s (%i)\n", + strerror(errno), errno); + tis_abort(s); + qemu_mutex_lock(&s->tpm_thread_lock); + continue; + } + + if(unix_tpm_read(s->tpmfd, s->buf) < 0) { + fprintf(stderr, "Error: while reading data from host tpm" + ": %s (%i)\n", + strerror(errno), errno); + tis_abort(s); + qemu_mutex_lock(&s->tpm_thread_lock); + continue; + } + + qemu_mutex_lock(&s->tpm_thread_abort_lock); + if(!s->aborting) { +#ifdef TPM_DEBUG + showBuff(s->buf, "From TPM"); + + ret = (s->buf[8])*256 + s->buf[9]; + if (ret) + printf("tpm command failed with error %d\n", ret); + else + printf("tpm command succeeded\n"); +#endif + + qemu_mutex_lock(&s->tpm_thread_lock); + s->loc[locty].sts = STS_VALID | STS_DATA_AVAILABLE; + s->loc[locty].state = STATE_COMPLETION; + s->transmit = 0; + tis_raise_irq(s, locty, INT_DATA_AVAILABLE); + } else { + qemu_mutex_lock(&s->tpm_thread_lock); + tis_abort(s); +#ifdef TPM_DEBUG + printf("Abort is complete.\n"); +#endif + s->transmit = 0; + } + qemu_mutex_unlock(&s->tpm_thread_abort_lock); + } + + return NULL; +} + + +/**********************************************************************/ + +/* + * read a byte of response data + */ +static uint32_t tpm_data_read(tpmState *s, uint8_t locty) +{ + uint32_t ret, len; + + if (s->loc[locty].state != STATE_COMPLETION) { +#ifdef TPM_DEBUG + printf("tpm_data_read with no data available!\n"); +#endif + return 0xff; + } + + len = tpm_get_size_from_buffer(s->buf); + ret = s->buf[s->offset++]; + if (s->offset >= len) { + s->loc[locty].sts = STS_VALID ; + s->offset = 0; + } +#ifdef TPM_DEBUG + printf("tpm_data_read byte x%02x [%d]\n",ret,s->offset-1); +#endif + return ret; +} + +/* raise an interrupt if allowed */ +static void tis_raise_irq(tpmState *s, uint8_t locty, uint32_t irqmask) +{ + if (!s->irq_pending && + (s->loc[locty].inte & INT_ENABLED) && + (s->loc[locty].inte & irqmask)) { + if ((irqmask & s->loc[locty].ints) == 0) { +#ifdef TPM_DEBUG + printf("Raising IRQ for flag %08x\n",irqmask); +#endif + s->set_irq(s->irq_opaque, s->irq, 1); + s->irq_pending = 1; + s->loc[locty].ints |= irqmask; + } + } +} + +/* abort execution of command */ +static void tis_abort(tpmState *s) +{ + s->offset = 0; + s->active_loc = s->next_locty; + qemu_mutex_lock(&s->tpm_thread_lock); + s->transmit = 0; + qemu_mutex_unlock(&s->tpm_thread_lock); + + /* + * Need to react differently depending on who's aborting now and + * which locality will become active afterwards. + */ + if (s->aborting_locty == s->next_locty) { + s->loc[s->aborting_locty].state = STATE_READY; + s->loc[s->aborting_locty].sts = STS_COMMAND_READY; + tis_raise_irq(s, s->aborting_locty, INT_COMMAND_READY); + } + + /* locality after abort is another one than the current one */ + if (s->aborting_locty != s->next_locty && s->next_locty != NO_LOCALITY) { + s->loc[s->aborting_locty].access &= ~ACCESS_ACTIVE_LOCALITY; + s->loc[s->next_locty].access |= ACCESS_ACTIVE_LOCALITY; + tis_raise_irq(s, s->next_locty, INT_LOCALITY_CHANGED); + } + + s->aborting_locty = NO_LOCALITY; /* nobody's aborting a command anymore */ +} + +/* abort current command */ +static void tis_prep_abort(tpmState *s, uint8_t locty, uint8_t newlocty) +{ + s->aborting_locty = locty; /* current locality */ + s->next_locty = newlocty; /* locality after successful abort */ + + s->next_locty = 0; /* only locality 0 available */ + + /* + * only abort a command using an interrupt if currently executing + * a command AND if there's a valid connection to the vTPM. + */ + if (s->loc[locty].state == STATE_EXECUTION) { + qemu_mutex_lock(&s->tpm_thread_abort_lock); + s->aborting = 1; + qemu_mutex_unlock(&s->tpm_thread_abort_lock); + } else { + tis_abort(s); + } +} + + +/* + * Read a register of the TIS interface + * See specs pages 33-63 for description of the registers + */ +static uint32_t tis_mem_readl(void *opaque, target_phys_addr_t addr) +{ + tpmState *s = (tpmState *)opaque; + uint16_t offset = addr & 0xffc; + uint8_t shift = (addr & 0x3) * 8; + uint32_t val = 0; + uint8_t locty = locality_from_addr(addr); + + if (offset == TPM_REG_ACCESS) { + if (locty > 0) { + /* no access to localities other than 0 possible */ + val = 0; + } else { + if (s->active_loc == locty) { + s->loc[locty].access |= (1 << 5); + } else { + s->loc[locty].access &= ~(1 << 5); + } + val = s->loc[locty].access; + } + } else + if (locty > 0) { + /* higher localities deactivated */ + val = 0xffff; + } else + if (offset == TPM_REG_INT_ENABLE) { + val = s->loc[locty].inte; + } else + if (offset == TPM_REG_INT_VECTOR) { + val = s->irq; + } else + if (offset == TPM_REG_INT_STATUS) { + val = s->loc[locty].ints; + } else + if (offset == TPM_REG_INTF_CAPABILITY) { + val = CAPABILITIES_SUPPORTED; + } else + if (offset == TPM_REG_STS) { /* status register */ + /* ??? - appears to be just some value as we aren't a hardware TPM */ + val = (sizeof(s->buf) - s->offset) << 8 | s->loc[locty].sts; + } else + if (offset == TPM_REG_DATA_FIFO) { + val = tpm_data_read(s, locty); + } else + if (offset == TPM_REG_DID_VID) { + val = (TPM_DID << 16) | TPM_VID; + } else + if (offset == TPM_REG_RID) { + val = TPM_RID; + } + + if (shift && locty == 0) { + val >>= shift; + } + +#ifdef TPM_DEBUG + printf(" read(%08x) = %08x\n", + (int)addr, + val); +#endif + + return val; +} + +/* + * Write a value to a register of the TIS interface + * See specs pages 33-63 for description of the registers + */ +static void tis_mem_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + tpmState* s=(tpmState*)opaque; + uint16_t off = addr & 0xfff; + uint8_t locty = locality_from_addr(addr); + uint32_t len; + +#ifdef TPM_DEBUG + printf("write(%08x) = %08x\n", + (int)addr, + val); +#endif + + /* linux tpm_tis driver only uses locality 0 */ + if (locty > 0) { + fprintf(stderr, "Warning: Write to unavailable locality: %d \n", locty); + return; + } +#ifdef TPM_DEBUG + if(s->transmit) { + fprintf(stderr, "Warning: tpm received data while transmit" + " in progress.\n"); + printf("WARN: write(%08x) = %08x\n", + (int)addr, + val); + } +#endif + + if (off == TPM_REG_ACCESS) { + if (val & ACCESS_ACTIVE_LOCALITY) { + /* give up locality if currently owned */ + if (s->active_loc == locty) { + /*uint8_t newlocty = NO_LOCALITY;*/ + s->loc[locty].access &= ~(ACCESS_PENDING_REQUEST); + /* anybody wants the locality ? */ + if (s->loc[locty].access & ACCESS_REQUEST_USE) { + s->loc[locty].access |= ACCESS_TPM_REG_VALID_STS; + s->loc[locty].access &= ~ACCESS_REQUEST_USE; + } + tis_prep_abort(s, locty, locty); + } + } + if (val & ACCESS_BEEN_SEIZED) { + /* clear the flag */ + s->loc[locty].access &= ~ACCESS_BEEN_SEIZED; + } + if (val & ACCESS_SEIZE) { + if (locty > s->active_loc && IS_VALID_LOC(s->active_loc)) { + s->loc[s->active_loc].access |= ACCESS_BEEN_SEIZED; + s->loc[locty].access = ACCESS_TPM_REG_VALID_STS; + tis_prep_abort(s, s->active_loc, locty); + } + } + if (val & ACCESS_REQUEST_USE) { + if (IS_VALID_LOC(s->active_loc)) { + /* locality election */ + s->loc[s->active_loc].access |= ACCESS_PENDING_REQUEST; + } else { + /* no locality active -> make this one active now */ + s->loc[locty].access |= ACCESS_ACTIVE_LOCALITY; + s->active_loc = locty; + tis_raise_irq(s, locty, INT_LOCALITY_CHANGED); + } + } + } else + if (off == TPM_REG_INT_ENABLE) { + s->loc[locty].inte = (val & (INT_ENABLED | (0x3 << 3) | + INTERRUPTS_SUPPORTED)); + } else + if (off == TPM_REG_INT_STATUS) { + /* clearing of interrupt flags */ + if ((val & INTERRUPTS_SUPPORTED) && + (s->loc[locty].ints & INTERRUPTS_SUPPORTED)) { + s->set_irq(s->irq_opaque, s->irq, 0); + s->irq_pending = 0; + } + s->loc[locty].ints &= ~(val & INTERRUPTS_SUPPORTED); + } else + if (off == TPM_REG_STS) { + if (val & STS_COMMAND_READY) { + if (s->loc[locty].state == STATE_IDLE) { + s->loc[locty].sts = STS_COMMAND_READY; + s->loc[locty].state = STATE_READY; + tis_raise_irq(s, locty, INT_COMMAND_READY); + } else if (s->loc[locty].state == STATE_COMPLETION || + s->loc[locty].state == STATE_EXECUTION || + s->loc[locty].state == STATE_RECEPTION) { + /* abort currently running command */ + tis_prep_abort(s, locty, locty); + } + } + if (val & STS_TPM_GO) { + s->offset = 0; + + if (s->transmit != 1) { + qemu_mutex_lock(&s->tpm_thread_lock); + s->transmit = 1; + s->loc[locty].state = STATE_EXECUTION; + qemu_mutex_unlock(&s->tpm_thread_lock); + qemu_cond_signal(&s->tpm_thread_cond_data_avail); + } + } + if (val & STS_RESPONSE_RETRY) { + s->offset = 0; + } + } else if (off == TPM_REG_DATA_FIFO) { + /* data fifo */ + if (s->loc[locty].state == STATE_IDLE || + s->loc[locty].state == STATE_EXECUTION || + s->loc[locty].state == STATE_COMPLETION) { + /* drop the byte */ + } else { +#ifdef TPM_DEBUG + printf("Byte to send to TPM: %02x at offset: %03d\n", val, s->offset); +#endif + s->loc[locty].state = STATE_RECEPTION; + s->loc[locty].sts = STS_EXPECT | STS_VALID; + + if (s->offset < TPM_MAX_PKT) + s->buf[s->offset++] = (uint8_t)val; + + if (s->offset > 5) { + /* we have a packet length - see if we have all of it */ + len = tpm_get_size_from_buffer(s->buf); + if (s->offset >= len) { +#ifdef TPM_DEBUG + printf("We have a complete packet of %u bytes\n", len); +#endif + s->offset = 0; + + qemu_mutex_lock(&s->tpm_thread_lock); + s->transmit = 1; + qemu_mutex_unlock(&s->tpm_thread_lock); + qemu_cond_signal(&s->tpm_thread_cond_data_avail); + s->loc[locty].sts = STS_VALID; + } + } + } + } +} + + +static CPUReadMemoryFunc *tis_readfn[3]={ + tis_mem_readl, + tis_mem_readl, + tis_mem_readl +}; + +static CPUWriteMemoryFunc *tis_writefn[3]={ + tis_mem_writel, + tis_mem_writel, + tis_mem_writel +}; + +/* + * Save the internal state of this interface for later resumption. + * Need to get any outstanding responses from the vTPM back, so + * this might delay the suspend for a while. + */ +static void tpm_save(QEMUFile* f,void* opaque) +{ + tpmState* s=(tpmState*)opaque; + uint8_t locty = s->active_loc; + int c; + + /* need to wait for outstanding requests to complete */ + if (s->loc[locty].state == STATE_EXECUTION) { + int repeats = 30; /* 30 seconds; really should be infty */ + while (repeats > 0 && + !(s->loc[s->active_loc].sts & STS_DATA_AVAILABLE)) { + sleep(1); + } + } + + close(s->tpmfd); + + qemu_put_be32s(f,&s->offset); + qemu_put_buffer(f, s->buf, TPM_MAX_PKT); + qemu_put_8s(f, &s->active_loc); + qemu_put_8s(f, &s->irq_pending); + for (c = 0; c < NUM_LOCALITIES; c++) { + qemu_put_be32s(f, &s->loc[c].state); + qemu_put_8s(f, &s->loc[c].access); + qemu_put_8s(f, &s->loc[c].sts); + qemu_put_be32s(f, &s->loc[c].inte); + qemu_put_be32s(f, &s->loc[c].ints); + } + qemu_put_buffer(f, (uint8_t*) s->path, s->path_len); + qemu_put_be32s(f, &s->path_len); +} + + +/* + * load TIS interface state + */ +static int tpm_load(QEMUFile* f,void* opaque,int version_id) +{ + tpmState* s=(tpmState*)opaque; + int c; + + if (version_id != 1) + return -EINVAL; + + qemu_get_be32s(f,&s->offset); + qemu_get_buffer(f, s->buf, TPM_MAX_PKT); + qemu_get_8s(f, &s->active_loc); + qemu_get_8s(f, &s->irq_pending); + for (c = 0; c < NUM_LOCALITIES; c++) { + qemu_get_be32s(f, &s->loc[c].state); + qemu_get_8s(f, &s->loc[c].access); + qemu_get_8s(f, &s->loc[c].sts); + qemu_get_be32s(f, &s->loc[c].inte); + qemu_get_be32s(f, &s->loc[c].ints); + } + qemu_get_be32s(f, &s->path_len); + qemu_get_buffer(f, (uint8_t*) s->path, s->path_len); + + s->tpmfd = open(s->path, O_RDWR); + + if(s->tpmfd < 0) + hw_error("Cannot open %s: %s (%i)\n", s->path, strerror(errno), errno); + + qemu_mutex_init(&s->tpm_thread_lock); + qemu_cond_init( &s->tpm_thread_cond_data_avail); + qemu_mutex_init(&s->tpm_thread_abort_lock); + + qemu_thread_create(&s->tpm_thread_id, &tpm_thread, s); + + return 0; +} + + +typedef struct LPCtpmState { + tpmState tpm; + int mem; +} LPCtpmState; + + +/* + * initialize TIS interface + */ + +struct IRQState { + qemu_irq_handler handler; + void *opaque; + int n; +}; + +void tpm_tis_init(qemu_irq irq); + +void tpm_tis_init(qemu_irq irq) +{ + LPCtpmState *d; + tpmState *s; + int c = 0; + int tpmfd = -1; + + if(tpm_device == NULL) { + return; + } + + tpmfd = open(tpm_device, O_RDWR); + if(tpmfd < 0) { + fprintf(stderr, "Cannot open %s: %s (%i)\n", tpm_device, + strerror(errno), errno); + return; + } + + d = qemu_mallocz(sizeof(LPCtpmState)); + d->mem = cpu_register_io_memory(tis_readfn, tis_writefn, d); + + if (d->mem == -1) { + fprintf(stderr, "Failed to register IO memory TPM TIS\n"); + return; + } + + cpu_register_physical_memory(TIS_ADDR_BASE, + 0x1000 * NUM_LOCALITIES, d->mem); + + /* initialize tpmState */ + s = &d->tpm; + + s->offset = 0; + s->transmit = 0; + s->aborting = 0; + s->active_loc = NO_LOCALITY; + + while (c < NUM_LOCALITIES) { + s->loc[c].access = (1 << 7); + s->loc[c].sts = 0; + s->loc[c].inte = (1 << 3); + s->loc[c].ints = 0; + s->loc[c].state = STATE_IDLE; + c++; + } + s->set_irq = irq->handler; + s->irq_opaque = irq->opaque; + s->irq = irq->n; + s->aborting_locty = NO_LOCALITY; + s->tpmfd = tpmfd; + s->path_len = strlen(tpm_device); + s->path = tpm_device; + + memset(s->buf,0,sizeof(s->buf)); + + qemu_mutex_init(&s->tpm_thread_lock); + qemu_cond_init( &s->tpm_thread_cond_data_avail); + qemu_mutex_init(&s->tpm_thread_abort_lock); + + qemu_thread_create(&s->tpm_thread_id, &tpm_thread, s); + + register_savevm(NULL, "tpm-tis", 0, 1, tpm_save, tpm_load, s); +} + + +int tpm_init(QemuOpts *opts) { + if(tpm_device == NULL) { + tpm_device = qemu_opt_get(opts, "dev"); + } + return 0; +} + +/****************************************************************************/ +/* */ +/* optional verbose logging of data to/from tpm chip */ +/* */ +/****************************************************************************/ +#ifdef TPM_DEBUG +static void showBuff(unsigned char *buff, const char *string) +{ + uint32_t i, len; + + len = tpm_get_size_from_buffer(buff); + printf("%s length=%d\n", string, len); + for (i = 0; i < len; i++) { + if (i && !(i % 16)) { + printf("\n"); + } + printf("%.2X ", buff[i]); + } + printf("\n"); +} +#endif + +
This implementation is based on the TPM 1.2 interface for virtualized TPM devices from the Xen-4.0.0 ioemu-qemu-xen fork. A separate thread is used for I/O to the host TPM device because the Linux TPM driver does not allow for non-blocking I/O. Signed-off-by: Andreas Niederl <andreas.niederl@iaik.tugraz.at> --- Makefile.target | 3 + configure | 9 + hw/pc_piix.c | 8 + hw/tpm_tis.c | 831 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 851 insertions(+), 0 deletions(-) create mode 100644 hw/tpm_tis.c