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 backend driver infrastructure is provided to be able to use different device backends. Signed-off-by: Andreas Niederl <andreas.nied...@iaik.tugraz.at> --- Makefile.objs | 3 + hw/tpm.h | 22 ++ hw/tpm_backend.c | 77 ++++++ hw/tpm_int.h | 53 ++++ hw/tpm_tis.c | 705 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 860 insertions(+), 0 deletions(-) create mode 100644 hw/tpm.h create mode 100644 hw/tpm_backend.c create mode 100644 hw/tpm_int.h create mode 100644 hw/tpm_tis.c diff --git a/Makefile.objs b/Makefile.objs index b21f9d3..6c78453 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -283,6 +283,9 @@ hw-obj-$(CONFIG_REALLY_VIRTFS) += virtio-9p-debug.o hw-obj-$(CONFIG_VIRTFS) += virtio-9p-local.o virtio-9p-xattr.o hw-obj-$(CONFIG_VIRTFS) += virtio-9p-xattr-user.o virtio-9p-posix-acl.o +# TPM passthrough device +hw-obj-$(CONFIG_TPM) += tpm_tis.o tpm_backend.o + ###################################################################### # libdis # NOTE: the disassembler code is only needed for debugging diff --git a/hw/tpm.h b/hw/tpm.h new file mode 100644 index 0000000..f3e1395 --- /dev/null +++ b/hw/tpm.h @@ -0,0 +1,22 @@ +/* + * tpm.h - Interface for adding TPM backend drivers + * + * Copyright (C) 2011 IAIK, Graz University of Technology + * + * Author: Andreas Niederl <andreas.nied...@iaik.tugraz.at> + * + * 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. + * + */ + +#ifndef TPM_H +#define TPM_H + +#include "qemu-option.h" + +int qemu_tpm_add(QemuOpts *opts); + +#endif /* TPM_H */ diff --git a/hw/tpm_backend.c b/hw/tpm_backend.c new file mode 100644 index 0000000..b87c089 --- /dev/null +++ b/hw/tpm_backend.c @@ -0,0 +1,77 @@ +/* + * tpm_backend.c - TPM backend driver framework + * + * Copyright (C) 2011 IAIK, Graz University of Technology + * + * Author: Andreas Niederl <andreas.nied...@iaik.tugraz.at> + * + * 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. + * + */ + + +#include "qemu-option.h" + +#include "hw/tpm.h" +#include "hw/tpm_int.h" + + +static QLIST_HEAD(, TPMDriver) tpm_drivers = + QLIST_HEAD_INITIALIZER(tpm_drivers); + +TPMDriver *tpm_get_driver(const char *id) +{ + TPMDriver *drv; + QLIST_FOREACH(drv, &tpm_drivers, list) { + if (!strcmp(drv->id, id)) { + return drv; + } + } + return NULL; +} + + +typedef struct { + const char *name; + TPMDriver *(*open)(QemuOpts *opts); +} TPMDriverTable; + +static const TPMDriverTable driver_table[] = { +}; + +int qemu_tpm_add(QemuOpts *opts) { + TPMDriver *drv = NULL; + int i; + + if (qemu_opts_id(opts) == NULL) { + fprintf(stderr, "tpm: no id specified\n"); + return -1; + } + + for (i = 0; i < ARRAY_SIZE(driver_table); i++) { + if (strcmp(driver_table[i].name, qemu_opt_get(opts, "type")) == 0) { + break; + } + } + + if (i == ARRAY_SIZE(driver_table)) { + fprintf(stderr, "tpm: backend type %s not found\n", + qemu_opt_get(opts, "type")); + return -1; + } + + drv = driver_table[i].open(opts); + + if (drv == NULL) { + return -1; + } + + drv->id = qemu_strdup(qemu_opts_id(opts)); + + QLIST_INSERT_HEAD(&tpm_drivers, drv, list); + + return 0; +} diff --git a/hw/tpm_int.h b/hw/tpm_int.h new file mode 100644 index 0000000..7869a81 --- /dev/null +++ b/hw/tpm_int.h @@ -0,0 +1,53 @@ +/* + * tpm_int.h - Internal header file for TPM device emulation + * + * Copyright (C) 2011 IAIK, Graz University of Technology + * + * Author: Andreas Niederl <andreas.nied...@iaik.tugraz.at> + * + * 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. + * + */ + +#ifndef TPM_INT_H +#define TPM_INT_H + + +#include <inttypes.h> +#include "qemu-queue.h" +#include "qemu-option.h" + + +typedef struct TPMDriver TPMDriver; +struct TPMDriver { + char *id; + + uint8_t locty; + uint8_t *buf; + + int (*send)(TPMDriver *drv, uint8_t locty, uint32_t len); + int (*recv)(TPMDriver *drv, uint8_t locty, uint32_t len); + + QLIST_ENTRY(TPMDriver) list; +}; + +TPMDriver *tpm_get_driver(const char *id); + +#define TPM_MAX_PKT 4096 +#define TPM_MAX_PATH 4096 + +/*#define DEBUG_TPM*/ +#ifdef DEBUG_TPM +void show_buff(unsigned char *buff, const char *string); +#define DPRINTF(fmt, ...) \ + fprintf(stderr, "tpm_tis: %s: " fmt, __FUNCTION__, ##__VA_ARGS__) +#define DSHOW_BUFF(buf, info) show_buff(buf, info) +#else +#define DPRINTF(fmt, ...) +#define DSHOW_BUFF(buf, info) +#endif + +#endif /* TPM_INT_H */ diff --git a/hw/tpm_tis.c b/hw/tpm_tis.c new file mode 100644 index 0000000..a4c2a4e --- /dev/null +++ b/hw/tpm_tis.c @@ -0,0 +1,705 @@ +/* + * tpm_tis.c - QEMU emulator for a 1.2 TPM with TIS interface + * + * Copyright (C) 2006 IBM Corporation + * Copyright (C) 2011 IAIK, Graz University of Technology + * + * Author: Stefan Berger <stef...@us.ibm.com> + * David Safford <saff...@us.ibm.com> + * + * Author: Andreas Niederl <andreas.nied...@iaik.tugraz.at> + * Modified to use a separate TPM device backend + * + * 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/ + * + */ + +#include <sys/types.h> +#include <sys/stat.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-timer.h" + +#include "hw/tpm.h" +#include "hw/tpm_int.h" + + +#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 + +/* locality data */ +typedef struct { + uint32_t state; + uint32_t inte; + uint32_t ints; + uint8_t access; + uint8_t sts; +} TPMLocality; + +/* TPM device state */ +typedef struct { + ISADevice dev; + qemu_irq pic; + uint32_t irq; + uint8_t irq_pending; + + QEMUTimer *poll_timer; + int poll_attempts; + + uint8_t aborting; /* boolean value */ + uint32_t offset; + uint8_t buf[TPM_MAX_PKT]; + + uint8_t active_locty; + uint8_t aborting_locty; + uint8_t next_locty; + + TPMLocality loc[NUM_LOCALITIES]; + + char *backend; + + TPMDriver *drv; +} TPMState; + + +/********************************************************************** + 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); +} + + +#ifdef DEBUG_TPM +/****************************************************************************/ +/* */ +/* optional verbose logging of data to/from tpm chip */ +/* */ +/****************************************************************************/ +void show_buff(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 + + +/* 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) { + DPRINTF("Raising IRQ for flag %08x\n",irqmask); + qemu_set_irq(s->pic, 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_locty = s->next_locty; + + /* + * 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 */ +} + +/**********************************************************************/ + +/* + * Prepare the next interrupt for example after a command has + * been sent out for the purpose of receiving the response. + * Depending on how many interrupts (used for polling on the fd) have + * already been schedule, this function determines the delta in time + * to the next interrupt. This accomodates for commands that finish + * quickly. + */ +static void tis_prep_next_interrupt(TPMState *s) +{ + int64_t expiration; + int rate = 5; /* 5 times per second */ + + /* + * poll often at the beginning for quickly finished commands, + * then back off + */ + if (s->poll_attempts < 5) { + rate = 20; + } else if (s->poll_attempts < 10) { + rate = 10; + } + + expiration = qemu_get_clock(vm_clock) + (get_ticks_per_sec() / rate); + qemu_mod_timer(s->poll_timer, expiration); + s->poll_attempts++; +} + + +/* + * The polling routine called when the 'timer interrupt' fires. + * Tries to receive a command from the vTPM. + */ +static void tis_poll_timer(void *opaque) +{ + TPMState *s = opaque; + uint8_t locty = s->active_locty; + + if (!IS_VALID_LOC(locty) || + (!(s->loc[locty].inte & INT_ENABLED) && + (s->aborting_locty != NO_LOCALITY))) { + /* no more interrupts requested, so no more polling needed */ + qemu_del_timer(s->poll_timer); + } + + if (s->aborting_locty != NO_LOCALITY) { + int n = s->drv->recv(s->drv, locty, TPM_MAX_PKT); + DPRINTF("Receiving for abort.\n"); + if (n > 0) { + tis_abort(s); + DPRINTF("Abort is complete.\n"); + } else { + tis_prep_next_interrupt(s); + } + } else if (IS_VALID_LOC(locty)) { + if (s->loc[locty].state == STATE_EXECUTION) { + int n = s->drv->recv(s->drv, locty, TPM_MAX_PKT); + + /* poll for result */ + if (n > 0) { + s->loc[locty].sts = STS_VALID | STS_DATA_AVAILABLE; + s->loc[locty].state = STATE_COMPLETION; + tis_raise_irq(s, locty, INT_DATA_AVAILABLE); + } else { + /* nothing received */ + tis_prep_next_interrupt(s); + } + } + } +} + + +/* + * Try to receive a response from the backend TPM + */ +static void tis_attempt_receive(TPMState *s, uint8_t locty) +{ + /* + * Attempt to read from the backend TPM here if + * - not aborting a command + * - command has been sent and state is 'EXECUTION' now + * - no data are already available (data have already been read) + */ + if (!IS_VALID_LOC(s->aborting_locty)) { + if (s->loc[locty].state == STATE_EXECUTION) { + if (0 == (s->loc[locty].sts & STS_DATA_AVAILABLE)){ + int n = s->drv->recv(s->drv, locty, TPM_MAX_PKT); + if (n > 0) { + s->loc[locty].sts = STS_VALID | STS_DATA_AVAILABLE; + s->loc[locty].state = STATE_COMPLETION; + tis_raise_irq(s, locty, INT_DATA_AVAILABLE); + } + } + } + } +} + +/* + * read a byte of response data + */ +static uint32_t tpm_data_read(TPMState *s, uint8_t locty) +{ + uint32_t ret, len; + + /* try to receive data, if none are there it is ok */ + tis_attempt_receive(s, locty); + + if (s->loc[locty].state != STATE_COMPLETION) { + DPRINTF("tpm_data_read with no data available!\n"); + 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; + } + DPRINTF("tpm_data_read byte x%02x [%d]\n", ret, s->offset - 1); + return ret; +} + + +/* 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 */ + + /* + * 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) { + /* start timer and inside the timer wait for the result */ + s->poll_attempts = 0; + tis_prep_next_interrupt(s); + } 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 = opaque; + uint16_t offset = addr & 0xffc; + uint8_t shift = (addr & 0x3) * 8; + uint32_t val = 0; + uint8_t locty = locality_from_addr(addr); + + switch (offset) { + case TPM_REG_ACCESS: + if (s->active_locty == locty) { + s->loc[locty].access |= (1 << 5); + } else { + s->loc[locty].access &= ~(1 << 5); + } + val = s->loc[locty].access; + break; + case TPM_REG_INT_ENABLE: + val = s->loc[locty].inte; + break; + case TPM_REG_INT_VECTOR: + val = s->irq; + break; + case TPM_REG_INT_STATUS: + tis_attempt_receive(s, locty); + val = s->loc[locty].ints; + break; + case TPM_REG_INTF_CAPABILITY: + val = CAPABILITIES_SUPPORTED; + break; + case TPM_REG_STS: /* status register */ + tis_attempt_receive(s, locty); + val = (sizeof(s->buf) - s->offset) << 8 | s->loc[locty].sts; + break; + case TPM_REG_DATA_FIFO: + val = tpm_data_read(s, locty); + break; + case TPM_REG_DID_VID: + val = (TPM_DID << 16) | TPM_VID; + break; + case TPM_REG_RID: + val = TPM_RID; + break; + default: + DPRINTF("Bad register offset %#08x\n", offset); + break; + } + + if (shift && locty == 0) { + val >>= shift; + } + + DPRINTF("read(0x" TARGET_FMT_plx ") = %#08x\n", addr, val); + + 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 = opaque; + uint16_t offset = addr & 0xfff; + uint8_t locty = locality_from_addr(addr); + uint32_t len; + + DPRINTF("write(0x" TARGET_FMT_plx ") = %#08x\n", addr, val); + + switch (offset) { + case TPM_REG_ACCESS: + if (val & ACCESS_ACTIVE_LOCALITY) { + /* give up locality if currently owned */ + if (s->active_locty == 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_locty && IS_VALID_LOC(s->active_locty)) { + s->loc[s->active_locty].access |= ACCESS_BEEN_SEIZED; + s->loc[locty].access = ACCESS_TPM_REG_VALID_STS; + tis_prep_abort(s, s->active_locty, locty); + } + } + if (val & ACCESS_REQUEST_USE) { + if (IS_VALID_LOC(s->active_locty)) { + /* locality election */ + s->loc[s->active_locty].access |= ACCESS_PENDING_REQUEST; + } else { + /* no locality active -> make this one active now */ + s->loc[locty].access |= ACCESS_ACTIVE_LOCALITY; + s->active_locty = locty; + tis_raise_irq(s, locty, INT_LOCALITY_CHANGED); + } + } + break; + case TPM_REG_INT_ENABLE: + s->loc[locty].inte = (val & (INT_ENABLED | (0x3 << 3) | + INTERRUPTS_SUPPORTED)); + break; + case TPM_REG_INT_VECTOR: + /* + * isa_init_irq would exit qemu + * if the requested IRQ is already assigned + */ + /*isa_init_irq(&s->dev, &s->pic, val);*/ + DPRINTF("writes to TPM_INT_VECTOR not supported!\n"); + break; + case TPM_REG_INT_STATUS: + /* clearing of interrupt flags */ + if ((val & INTERRUPTS_SUPPORTED) && + (s->loc[locty].ints & INTERRUPTS_SUPPORTED)) { + qemu_set_irq(s->pic, 0); + s->irq_pending = 0; + } + s->loc[locty].ints &= ~(val & INTERRUPTS_SUPPORTED); + break; + case 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) { + if (s->loc[locty].state == STATE_RECEPTION) { + int n = s->drv->send(s->drv, locty, + tpm_get_size_from_buffer(s->buf)); + if (n > 0) { + /* sending of data was successful */ + s->offset = 0; + s->loc[locty].state = STATE_EXECUTION; + if (s->loc[locty].inte & + (INT_ENABLED | INT_DATA_AVAILABLE)) { + s->poll_attempts = 0; + tis_prep_next_interrupt(s); + } + } + } + } + if (val & STS_RESPONSE_RETRY) { + s->offset = 0; + } + break; + case 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 { + DPRINTF("Byte to send to TPM: %02x at offset: %03d\n", val, + s->offset); + s->loc[locty].state = STATE_RECEPTION; + + 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 (len > s->offset) { + s->loc[locty].sts = STS_EXPECT | STS_VALID; + } else { + s->loc[locty].sts = STS_VALID; + } + } + } + break; + default: + DPRINTF("Bad register offset %#08x\n", offset); + break; + } +} + + +static CPUReadMemoryFunc * const tis_readfn[3] = { + tis_mem_readl, + tis_mem_readl, + tis_mem_readl +}; + +static CPUWriteMemoryFunc * const tis_writefn[3] = { + tis_mem_writel, + tis_mem_writel, + tis_mem_writel +}; + +/* + * Need to get any outstanding responses from the TPM back, so + * this might delay the suspend for a while. + */ +static void tpm_pre_save(void *opaque) +{ + TPMState *s = opaque; + uint8_t locty = s->active_locty; + + /* 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_locty].sts & STS_DATA_AVAILABLE)) { + sleep(1); + } + } +} + + +static void tpm_reset(TPMState *s) +{ + int c; + + s->offset = 0; + s->aborting = 0; + s->active_locty = NO_LOCALITY; + + for (c = 0; c < NUM_LOCALITIES; c++) { + 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; + } + s->aborting_locty = NO_LOCALITY; + + memset(s->buf, 0, sizeof(s->buf)); +} + +static const VMStateDescription vmstate_loc = { + .name = "loc", + .version_id = 0, + .fields = (VMStateField []) { + VMSTATE_UINT32(state, TPMLocality), + VMSTATE_UINT32(inte, TPMLocality), + VMSTATE_UINT32(ints, TPMLocality), + VMSTATE_UINT8(access, TPMLocality), + VMSTATE_UINT8(sts, TPMLocality), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_tpm = { + .name = "tpm", + .version_id = 0, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .pre_save = tpm_pre_save, + .fields = (VMStateField []) { + VMSTATE_UINT32(irq, TPMState), + VMSTATE_UINT32(offset, TPMState), + VMSTATE_BUFFER(buf, TPMState), + VMSTATE_UINT8( active_locty, TPMState), + VMSTATE_UINT8(aborting_locty, TPMState), + VMSTATE_UINT8( next_locty, TPMState), + VMSTATE_STRUCT_ARRAY(loc, TPMState, NUM_LOCALITIES, 0, + vmstate_loc, TPMLocality), + VMSTATE_END_OF_LIST() + } +}; + +/* + * initialize TIS interface + */ +static int tpm_init(ISADevice *dev) +{ + TPMState *s = DO_UPCAST(TPMState, dev, dev); + int isairq = 11; + TPMDriver *drv = NULL; + int iomem; + int i; + + if (!s->backend) { + fprintf(stderr, "tpm: no backend selected!\n"); + return -1; + } + drv = tpm_get_driver(s->backend); + if (drv == NULL) { + fprintf(stderr, "tpm: backend id %s not found\n", s->backend); + return -1; + } + + tpm_reset(s); + + drv->buf = s->buf; + s->drv = drv; + + vmstate_register(&dev->qdev, 1, &vmstate_tpm, s); + for (i = 0; i < NUM_LOCALITIES; i++) { + vmstate_register(&dev->qdev, 1, &vmstate_loc, &s->loc[i]); + } + + isa_init_irq(dev, &s->pic, isairq); + s->irq = isairq; + + iomem = cpu_register_io_memory(tis_readfn, tis_writefn, s, + DEVICE_LITTLE_ENDIAN); + cpu_register_physical_memory(TIS_ADDR_BASE, + 0x1000 * NUM_LOCALITIES, iomem); + + s->poll_timer = qemu_new_timer(vm_clock, tis_poll_timer, s); + + return 0; +} + +static void qdev_tpm_reset(DeviceState *dev) +{ + TPMState *s = DO_UPCAST(TPMState, dev.qdev, dev); + tpm_reset(s); +} + +static ISADeviceInfo tpm_info = { + .init = tpm_init, + .qdev.name = "tpm", + .qdev.desc = "TPM TIS Interface", + .qdev.size = sizeof (TPMState), + .qdev.vmsd = &vmstate_tpm, + .qdev.reset = qdev_tpm_reset, + .qdev.props = (Property[]) { + DEFINE_PROP_STRING("backend", TPMState, backend), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void tpm_register (void) +{ + isa_qdev_register(&tpm_info); +} +device_init (tpm_register) -- 1.7.4.1