qemu/tpm/tpm_tis.c

930 lines
29 KiB
C
Raw Normal View History

Add TPM (frontend) hardware interface (TPM TIS) to QEMU This patch adds the main code of the TPM frontend driver, the TPM TIS interface, to QEMU. The code is largely based on the previous implementation for Xen but has been significantly extended to meet the standard's requirements, such as the support for changing of localities and all the functionality of the available flags. Communication with the backend (i.e., for Xen or the libtpms-based one) is cleanly separated through an interface which the backend driver needs to implement. Whenever the frontend has collected a complete packet, it will submit a task to the backend, which then starts processing the command. Once the result has been returned, the backend invokes a callback function (tpm_tis_receive_cb()). Testing the proper functioning of the different flags and localities cannot be done from user space when running in Linux for example, since access to the address space of the TPM TIS interface is not possible. Also the Linux driver itself does not exercise all functionality. So, for testing there is a fairly extensive test suite as part of the SeaBIOS patches since from within the BIOS one can have full access to all the TPM's registers. Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> Reviewed-by: Corey Bryant <coreyb@linux.vnet.ibm.com> Reviewed-by: Joel Schopp <jschopp@linux.vnet.ibm.com> Message-id: 1361987275-26289-3-git-send-email-stefanb@linux.vnet.ibm.com Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-02-27 21:47:50 +04:00
/*
* tpm_tis.c - QEMU's TPM TIS interface emulator
*
* Copyright (C) 2006,2010-2013 IBM Corporation
*
* Authors:
* Stefan Berger <stefanb@us.ibm.com>
* David Safford <safford@us.ibm.com>
*
* Xen 4 support: Andrease Niederl <andreas.niederl@iaik.tugraz.at>
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*
* Implementation of the TIS interface according to specs found at
* http://www.trustedcomputinggroup.org. This implementation currently
* supports version 1.21, revision 1.0.
* In the developers menu choose the PC Client section then find the TIS
* specification.
*/
#include "tpm_int.h"
#include "block/block.h"
#include "exec/address-spaces.h"
#include "hw/hw.h"
#include "hw/pc.h"
#include "hw/pci/pci_ids.h"
#include "tpm/tpm_tis.h"
#include "qemu-common.h"
/*#define DEBUG_TIS */
#ifdef DEBUG_TIS
#define DPRINTF(fmt, ...) \
do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
#else
#define DPRINTF(fmt, ...) \
do { } while (0)
#endif
/* whether the STS interrupt is supported */
#define RAISE_STS_IRQ
/* tis registers */
#define TPM_TIS_REG_ACCESS 0x00
#define TPM_TIS_REG_INT_ENABLE 0x08
#define TPM_TIS_REG_INT_VECTOR 0x0c
#define TPM_TIS_REG_INT_STATUS 0x10
#define TPM_TIS_REG_INTF_CAPABILITY 0x14
#define TPM_TIS_REG_STS 0x18
#define TPM_TIS_REG_DATA_FIFO 0x24
#define TPM_TIS_REG_DID_VID 0xf00
#define TPM_TIS_REG_RID 0xf04
/* vendor-specific registers */
#define TPM_TIS_REG_DEBUG 0xf90
Add TPM (frontend) hardware interface (TPM TIS) to QEMU This patch adds the main code of the TPM frontend driver, the TPM TIS interface, to QEMU. The code is largely based on the previous implementation for Xen but has been significantly extended to meet the standard's requirements, such as the support for changing of localities and all the functionality of the available flags. Communication with the backend (i.e., for Xen or the libtpms-based one) is cleanly separated through an interface which the backend driver needs to implement. Whenever the frontend has collected a complete packet, it will submit a task to the backend, which then starts processing the command. Once the result has been returned, the backend invokes a callback function (tpm_tis_receive_cb()). Testing the proper functioning of the different flags and localities cannot be done from user space when running in Linux for example, since access to the address space of the TPM TIS interface is not possible. Also the Linux driver itself does not exercise all functionality. So, for testing there is a fairly extensive test suite as part of the SeaBIOS patches since from within the BIOS one can have full access to all the TPM's registers. Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> Reviewed-by: Corey Bryant <coreyb@linux.vnet.ibm.com> Reviewed-by: Joel Schopp <jschopp@linux.vnet.ibm.com> Message-id: 1361987275-26289-3-git-send-email-stefanb@linux.vnet.ibm.com Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-02-27 21:47:50 +04:00
#define TPM_TIS_STS_VALID (1 << 7)
#define TPM_TIS_STS_COMMAND_READY (1 << 6)
#define TPM_TIS_STS_TPM_GO (1 << 5)
#define TPM_TIS_STS_DATA_AVAILABLE (1 << 4)
#define TPM_TIS_STS_EXPECT (1 << 3)
#define TPM_TIS_STS_RESPONSE_RETRY (1 << 1)
#define TPM_TIS_BURST_COUNT_SHIFT 8
#define TPM_TIS_BURST_COUNT(X) \
((X) << TPM_TIS_BURST_COUNT_SHIFT)
#define TPM_TIS_ACCESS_TPM_REG_VALID_STS (1 << 7)
#define TPM_TIS_ACCESS_ACTIVE_LOCALITY (1 << 5)
#define TPM_TIS_ACCESS_BEEN_SEIZED (1 << 4)
#define TPM_TIS_ACCESS_SEIZE (1 << 3)
#define TPM_TIS_ACCESS_PENDING_REQUEST (1 << 2)
#define TPM_TIS_ACCESS_REQUEST_USE (1 << 1)
#define TPM_TIS_ACCESS_TPM_ESTABLISHMENT (1 << 0)
#define TPM_TIS_INT_ENABLED (1 << 31)
#define TPM_TIS_INT_DATA_AVAILABLE (1 << 0)
#define TPM_TIS_INT_STS_VALID (1 << 1)
#define TPM_TIS_INT_LOCALITY_CHANGED (1 << 2)
#define TPM_TIS_INT_COMMAND_READY (1 << 7)
#define TPM_TIS_INT_POLARITY_MASK (3 << 3)
#define TPM_TIS_INT_POLARITY_LOW_LEVEL (1 << 3)
#ifndef RAISE_STS_IRQ
#define TPM_TIS_INTERRUPTS_SUPPORTED (TPM_TIS_INT_LOCALITY_CHANGED | \
TPM_TIS_INT_DATA_AVAILABLE | \
TPM_TIS_INT_COMMAND_READY)
#else
#define TPM_TIS_INTERRUPTS_SUPPORTED (TPM_TIS_INT_LOCALITY_CHANGED | \
TPM_TIS_INT_DATA_AVAILABLE | \
TPM_TIS_INT_STS_VALID | \
TPM_TIS_INT_COMMAND_READY)
#endif
#define TPM_TIS_CAP_INTERRUPT_LOW_LEVEL (1 << 4) /* support is mandatory */
#define TPM_TIS_CAPABILITIES_SUPPORTED (TPM_TIS_CAP_INTERRUPT_LOW_LEVEL | \
TPM_TIS_INTERRUPTS_SUPPORTED)
#define TPM_TIS_TPM_DID 0x0001
#define TPM_TIS_TPM_VID PCI_VENDOR_ID_IBM
#define TPM_TIS_TPM_RID 0x0001
#define TPM_TIS_NO_DATA_BYTE 0xff
/* local prototypes */
static uint64_t tpm_tis_mmio_read(void *opaque, hwaddr addr,
unsigned size);
Add TPM (frontend) hardware interface (TPM TIS) to QEMU This patch adds the main code of the TPM frontend driver, the TPM TIS interface, to QEMU. The code is largely based on the previous implementation for Xen but has been significantly extended to meet the standard's requirements, such as the support for changing of localities and all the functionality of the available flags. Communication with the backend (i.e., for Xen or the libtpms-based one) is cleanly separated through an interface which the backend driver needs to implement. Whenever the frontend has collected a complete packet, it will submit a task to the backend, which then starts processing the command. Once the result has been returned, the backend invokes a callback function (tpm_tis_receive_cb()). Testing the proper functioning of the different flags and localities cannot be done from user space when running in Linux for example, since access to the address space of the TPM TIS interface is not possible. Also the Linux driver itself does not exercise all functionality. So, for testing there is a fairly extensive test suite as part of the SeaBIOS patches since from within the BIOS one can have full access to all the TPM's registers. Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> Reviewed-by: Corey Bryant <coreyb@linux.vnet.ibm.com> Reviewed-by: Joel Schopp <jschopp@linux.vnet.ibm.com> Message-id: 1361987275-26289-3-git-send-email-stefanb@linux.vnet.ibm.com Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-02-27 21:47:50 +04:00
/* utility functions */
static uint8_t tpm_tis_locality_from_addr(hwaddr addr)
{
return (uint8_t)((addr >> TPM_TIS_LOCALITY_SHIFT) & 0x7);
}
static uint32_t tpm_tis_get_size_from_buffer(const TPMSizedBuffer *sb)
{
return be32_to_cpu(*(uint32_t *)&sb->buffer[2]);
}
static void tpm_tis_show_buffer(const TPMSizedBuffer *sb, const char *string)
{
#ifdef DEBUG_TIS
uint32_t len, i;
len = tpm_tis_get_size_from_buffer(sb);
DPRINTF("tpm_tis: %s length = %d\n", string, len);
for (i = 0; i < len; i++) {
if (i && !(i % 16)) {
DPRINTF("\n");
}
DPRINTF("%.2X ", sb->buffer[i]);
}
DPRINTF("\n");
#endif
}
/*
* Send a request to the TPM.
*/
static void tpm_tis_tpm_send(TPMState *s, uint8_t locty)
{
TPMTISEmuState *tis = &s->s.tis;
tpm_tis_show_buffer(&tis->loc[locty].w_buffer, "tpm_tis: To TPM");
s->locty_number = locty;
s->locty_data = &tis->loc[locty];
/*
* w_offset serves as length indicator for length of data;
* it's reset when the response comes back
*/
tis->loc[locty].state = TPM_TIS_STATE_EXECUTION;
s->be_driver->ops->deliver_request(s->be_driver);
}
/* raise an interrupt if allowed */
static void tpm_tis_raise_irq(TPMState *s, uint8_t locty, uint32_t irqmask)
{
TPMTISEmuState *tis = &s->s.tis;
if (!TPM_TIS_IS_VALID_LOCTY(locty)) {
return;
}
if ((tis->loc[locty].inte & TPM_TIS_INT_ENABLED) &&
(tis->loc[locty].inte & irqmask)) {
DPRINTF("tpm_tis: Raising IRQ for flag %08x\n", irqmask);
qemu_irq_raise(s->s.tis.irq);
tis->loc[locty].ints |= irqmask;
}
}
static uint32_t tpm_tis_check_request_use_except(TPMState *s, uint8_t locty)
{
uint8_t l;
for (l = 0; l < TPM_TIS_NUM_LOCALITIES; l++) {
if (l == locty) {
continue;
}
if ((s->s.tis.loc[l].access & TPM_TIS_ACCESS_REQUEST_USE)) {
return 1;
}
}
return 0;
}
static void tpm_tis_new_active_locality(TPMState *s, uint8_t new_active_locty)
{
TPMTISEmuState *tis = &s->s.tis;
bool change = (s->s.tis.active_locty != new_active_locty);
bool is_seize;
uint8_t mask;
if (change && TPM_TIS_IS_VALID_LOCTY(s->s.tis.active_locty)) {
is_seize = TPM_TIS_IS_VALID_LOCTY(new_active_locty) &&
tis->loc[new_active_locty].access & TPM_TIS_ACCESS_SEIZE;
if (is_seize) {
mask = ~(TPM_TIS_ACCESS_ACTIVE_LOCALITY);
} else {
mask = ~(TPM_TIS_ACCESS_ACTIVE_LOCALITY|
TPM_TIS_ACCESS_REQUEST_USE);
}
/* reset flags on the old active locality */
tis->loc[s->s.tis.active_locty].access &= mask;
if (is_seize) {
tis->loc[tis->active_locty].access |= TPM_TIS_ACCESS_BEEN_SEIZED;
}
}
tis->active_locty = new_active_locty;
DPRINTF("tpm_tis: Active locality is now %d\n", s->s.tis.active_locty);
if (TPM_TIS_IS_VALID_LOCTY(new_active_locty)) {
/* set flags on the new active locality */
tis->loc[new_active_locty].access |= TPM_TIS_ACCESS_ACTIVE_LOCALITY;
tis->loc[new_active_locty].access &= ~(TPM_TIS_ACCESS_REQUEST_USE |
TPM_TIS_ACCESS_SEIZE);
}
if (change) {
tpm_tis_raise_irq(s, tis->active_locty, TPM_TIS_INT_LOCALITY_CHANGED);
}
}
/* abort -- this function switches the locality */
static void tpm_tis_abort(TPMState *s, uint8_t locty)
{
TPMTISEmuState *tis = &s->s.tis;
tis->loc[locty].r_offset = 0;
tis->loc[locty].w_offset = 0;
DPRINTF("tpm_tis: tis_abort: new active locality is %d\n", tis->next_locty);
/*
* Need to react differently depending on who's aborting now and
* which locality will become active afterwards.
*/
if (tis->aborting_locty == tis->next_locty) {
tis->loc[tis->aborting_locty].state = TPM_TIS_STATE_READY;
tis->loc[tis->aborting_locty].sts = TPM_TIS_STS_COMMAND_READY;
tpm_tis_raise_irq(s, tis->aborting_locty, TPM_TIS_INT_COMMAND_READY);
}
/* locality after abort is another one than the current one */
tpm_tis_new_active_locality(s, tis->next_locty);
tis->next_locty = TPM_TIS_NO_LOCALITY;
/* nobody's aborting a command anymore */
tis->aborting_locty = TPM_TIS_NO_LOCALITY;
}
/* prepare aborting current command */
static void tpm_tis_prep_abort(TPMState *s, uint8_t locty, uint8_t newlocty)
{
TPMTISEmuState *tis = &s->s.tis;
uint8_t busy_locty;
tis->aborting_locty = locty;
tis->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.
*/
for (busy_locty = 0; busy_locty < TPM_TIS_NUM_LOCALITIES; busy_locty++) {
if (tis->loc[busy_locty].state == TPM_TIS_STATE_EXECUTION) {
/*
* request the backend to cancel. Some backends may not
* support it
*/
s->be_driver->ops->cancel_cmd(s->be_driver);
return;
}
}
tpm_tis_abort(s, locty);
}
static void tpm_tis_receive_bh(void *opaque)
{
TPMState *s = opaque;
TPMTISEmuState *tis = &s->s.tis;
uint8_t locty = s->locty_number;
tis->loc[locty].sts = TPM_TIS_STS_VALID | TPM_TIS_STS_DATA_AVAILABLE;
tis->loc[locty].state = TPM_TIS_STATE_COMPLETION;
tis->loc[locty].r_offset = 0;
tis->loc[locty].w_offset = 0;
if (TPM_TIS_IS_VALID_LOCTY(tis->next_locty)) {
tpm_tis_abort(s, locty);
}
#ifndef RAISE_STS_IRQ
tpm_tis_raise_irq(s, locty, TPM_TIS_INT_DATA_AVAILABLE);
#else
tpm_tis_raise_irq(s, locty,
TPM_TIS_INT_DATA_AVAILABLE | TPM_TIS_INT_STS_VALID);
#endif
}
/*
* Callback from the TPM to indicate that the response was received.
*/
static void tpm_tis_receive_cb(TPMState *s, uint8_t locty)
{
TPMTISEmuState *tis = &s->s.tis;
assert(s->locty_number == locty);
qemu_bh_schedule(tis->bh);
}
/*
* Read a byte of response data
*/
static uint32_t tpm_tis_data_read(TPMState *s, uint8_t locty)
{
TPMTISEmuState *tis = &s->s.tis;
uint32_t ret = TPM_TIS_NO_DATA_BYTE;
uint16_t len;
if ((tis->loc[locty].sts & TPM_TIS_STS_DATA_AVAILABLE)) {
len = tpm_tis_get_size_from_buffer(&tis->loc[locty].r_buffer);
ret = tis->loc[locty].r_buffer.buffer[tis->loc[locty].r_offset++];
if (tis->loc[locty].r_offset >= len) {
/* got last byte */
tis->loc[locty].sts = TPM_TIS_STS_VALID;
#ifdef RAISE_STS_IRQ
tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID);
#endif
}
DPRINTF("tpm_tis: tpm_tis_data_read byte 0x%02x [%d]\n",
ret, tis->loc[locty].r_offset-1);
}
return ret;
}
#ifdef DEBUG_TIS
static void tpm_tis_dump_state(void *opaque, hwaddr addr)
{
static const unsigned regs[] = {
TPM_TIS_REG_ACCESS,
TPM_TIS_REG_INT_ENABLE,
TPM_TIS_REG_INT_VECTOR,
TPM_TIS_REG_INT_STATUS,
TPM_TIS_REG_INTF_CAPABILITY,
TPM_TIS_REG_STS,
TPM_TIS_REG_DID_VID,
TPM_TIS_REG_RID,
0xfff};
int idx;
uint8_t locty = tpm_tis_locality_from_addr(addr);
hwaddr base = addr & ~0xfff;
TPMState *s = opaque;
TPMTISEmuState *tis = &s->s.tis;
DPRINTF("tpm_tis: active locality : %d\n"
"tpm_tis: state of locality %d : %d\n"
"tpm_tis: register dump:\n",
tis->active_locty,
locty, tis->loc[locty].state);
for (idx = 0; regs[idx] != 0xfff; idx++) {
DPRINTF("tpm_tis: 0x%04x : 0x%08x\n", regs[idx],
(uint32_t)tpm_tis_mmio_read(opaque, base + regs[idx], 4));
}
DPRINTF("tpm_tis: read offset : %d\n"
"tpm_tis: result buffer : ",
tis->loc[locty].r_offset);
for (idx = 0;
idx < tpm_tis_get_size_from_buffer(&tis->loc[locty].r_buffer);
idx++) {
DPRINTF("%c%02x%s",
tis->loc[locty].r_offset == idx ? '>' : ' ',
tis->loc[locty].r_buffer.buffer[idx],
((idx & 0xf) == 0xf) ? "\ntpm_tis: " : "");
}
DPRINTF("\n"
"tpm_tis: write offset : %d\n"
"tpm_tis: request buffer: ",
tis->loc[locty].w_offset);
for (idx = 0;
idx < tpm_tis_get_size_from_buffer(&tis->loc[locty].w_buffer);
idx++) {
DPRINTF("%c%02x%s",
tis->loc[locty].w_offset == idx ? '>' : ' ',
tis->loc[locty].w_buffer.buffer[idx],
((idx & 0xf) == 0xf) ? "\ntpm_tis: " : "");
}
DPRINTF("\n");
}
#endif
Add TPM (frontend) hardware interface (TPM TIS) to QEMU This patch adds the main code of the TPM frontend driver, the TPM TIS interface, to QEMU. The code is largely based on the previous implementation for Xen but has been significantly extended to meet the standard's requirements, such as the support for changing of localities and all the functionality of the available flags. Communication with the backend (i.e., for Xen or the libtpms-based one) is cleanly separated through an interface which the backend driver needs to implement. Whenever the frontend has collected a complete packet, it will submit a task to the backend, which then starts processing the command. Once the result has been returned, the backend invokes a callback function (tpm_tis_receive_cb()). Testing the proper functioning of the different flags and localities cannot be done from user space when running in Linux for example, since access to the address space of the TPM TIS interface is not possible. Also the Linux driver itself does not exercise all functionality. So, for testing there is a fairly extensive test suite as part of the SeaBIOS patches since from within the BIOS one can have full access to all the TPM's registers. Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> Reviewed-by: Corey Bryant <coreyb@linux.vnet.ibm.com> Reviewed-by: Joel Schopp <jschopp@linux.vnet.ibm.com> Message-id: 1361987275-26289-3-git-send-email-stefanb@linux.vnet.ibm.com Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-02-27 21:47:50 +04:00
/*
* Read a register of the TIS interface
* See specs pages 33-63 for description of the registers
*/
static uint64_t tpm_tis_mmio_read(void *opaque, hwaddr addr,
unsigned size)
{
TPMState *s = opaque;
TPMTISEmuState *tis = &s->s.tis;
uint16_t offset = addr & 0xffc;
uint8_t shift = (addr & 0x3) * 8;
uint32_t val = 0xffffffff;
uint8_t locty = tpm_tis_locality_from_addr(addr);
uint32_t avail;
if (s->be_driver->ops->had_startup_error(s->be_driver)) {
return val;
}
switch (offset) {
case TPM_TIS_REG_ACCESS:
/* never show the SEIZE flag even though we use it internally */
val = tis->loc[locty].access & ~TPM_TIS_ACCESS_SEIZE;
/* the pending flag is always calculated */
if (tpm_tis_check_request_use_except(s, locty)) {
val |= TPM_TIS_ACCESS_PENDING_REQUEST;
}
val |= !s->be_driver->ops->get_tpm_established_flag(s->be_driver);
break;
case TPM_TIS_REG_INT_ENABLE:
val = tis->loc[locty].inte;
break;
case TPM_TIS_REG_INT_VECTOR:
val = tis->irq_num;
break;
case TPM_TIS_REG_INT_STATUS:
val = tis->loc[locty].ints;
break;
case TPM_TIS_REG_INTF_CAPABILITY:
val = TPM_TIS_CAPABILITIES_SUPPORTED;
break;
case TPM_TIS_REG_STS:
if (tis->active_locty == locty) {
if ((tis->loc[locty].sts & TPM_TIS_STS_DATA_AVAILABLE)) {
val = TPM_TIS_BURST_COUNT(
tpm_tis_get_size_from_buffer(&tis->loc[locty].r_buffer)
- tis->loc[locty].r_offset) | tis->loc[locty].sts;
} else {
avail = tis->loc[locty].w_buffer.size
- tis->loc[locty].w_offset;
/*
* byte-sized reads should not return 0x00 for 0x100
* available bytes.
*/
if (size == 1 && avail > 0xff) {
avail = 0xff;
}
val = TPM_TIS_BURST_COUNT(avail) | tis->loc[locty].sts;
}
}
break;
case TPM_TIS_REG_DATA_FIFO:
if (tis->active_locty == locty) {
switch (tis->loc[locty].state) {
case TPM_TIS_STATE_COMPLETION:
val = tpm_tis_data_read(s, locty);
break;
default:
val = TPM_TIS_NO_DATA_BYTE;
break;
}
}
break;
case TPM_TIS_REG_DID_VID:
val = (TPM_TIS_TPM_DID << 16) | TPM_TIS_TPM_VID;
break;
case TPM_TIS_REG_RID:
val = TPM_TIS_TPM_RID;
break;
#ifdef DEBUG_TIS
case TPM_TIS_REG_DEBUG:
tpm_tis_dump_state(opaque, addr);
break;
#endif
Add TPM (frontend) hardware interface (TPM TIS) to QEMU This patch adds the main code of the TPM frontend driver, the TPM TIS interface, to QEMU. The code is largely based on the previous implementation for Xen but has been significantly extended to meet the standard's requirements, such as the support for changing of localities and all the functionality of the available flags. Communication with the backend (i.e., for Xen or the libtpms-based one) is cleanly separated through an interface which the backend driver needs to implement. Whenever the frontend has collected a complete packet, it will submit a task to the backend, which then starts processing the command. Once the result has been returned, the backend invokes a callback function (tpm_tis_receive_cb()). Testing the proper functioning of the different flags and localities cannot be done from user space when running in Linux for example, since access to the address space of the TPM TIS interface is not possible. Also the Linux driver itself does not exercise all functionality. So, for testing there is a fairly extensive test suite as part of the SeaBIOS patches since from within the BIOS one can have full access to all the TPM's registers. Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> Reviewed-by: Corey Bryant <coreyb@linux.vnet.ibm.com> Reviewed-by: Joel Schopp <jschopp@linux.vnet.ibm.com> Message-id: 1361987275-26289-3-git-send-email-stefanb@linux.vnet.ibm.com Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-02-27 21:47:50 +04:00
}
if (shift) {
val >>= shift;
}
DPRINTF("tpm_tis: read.%u(%08x) = %08x\n", size, (int)addr, (uint32_t)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 tpm_tis_mmio_write_intern(void *opaque, hwaddr addr,
uint64_t val, unsigned size,
bool hw_access)
{
TPMState *s = opaque;
TPMTISEmuState *tis = &s->s.tis;
uint16_t off = addr & 0xfff;
uint8_t locty = tpm_tis_locality_from_addr(addr);
uint8_t active_locty, l;
int c, set_new_locty = 1;
uint16_t len;
DPRINTF("tpm_tis: write.%u(%08x) = %08x\n", size, (int)addr, (uint32_t)val);
if (locty == 4 && !hw_access) {
DPRINTF("tpm_tis: Access to locality 4 only allowed from hardware\n");
return;
}
if (s->be_driver->ops->had_startup_error(s->be_driver)) {
return;
}
switch (off) {
case TPM_TIS_REG_ACCESS:
if ((val & TPM_TIS_ACCESS_SEIZE)) {
val &= ~(TPM_TIS_ACCESS_REQUEST_USE |
TPM_TIS_ACCESS_ACTIVE_LOCALITY);
}
active_locty = tis->active_locty;
if ((val & TPM_TIS_ACCESS_ACTIVE_LOCALITY)) {
/* give up locality if currently owned */
if (tis->active_locty == locty) {
DPRINTF("tpm_tis: Releasing locality %d\n", locty);
uint8_t newlocty = TPM_TIS_NO_LOCALITY;
/* anybody wants the locality ? */
for (c = TPM_TIS_NUM_LOCALITIES - 1; c >= 0; c--) {
if ((tis->loc[c].access & TPM_TIS_ACCESS_REQUEST_USE)) {
DPRINTF("tpm_tis: Locality %d requests use.\n", c);
newlocty = c;
break;
}
}
DPRINTF("tpm_tis: TPM_TIS_ACCESS_ACTIVE_LOCALITY: "
"Next active locality: %d\n",
newlocty);
if (TPM_TIS_IS_VALID_LOCTY(newlocty)) {
set_new_locty = 0;
tpm_tis_prep_abort(s, locty, newlocty);
} else {
active_locty = TPM_TIS_NO_LOCALITY;
}
} else {
/* not currently the owner; clear a pending request */
tis->loc[locty].access &= ~TPM_TIS_ACCESS_REQUEST_USE;
}
}
if ((val & TPM_TIS_ACCESS_BEEN_SEIZED)) {
tis->loc[locty].access &= ~TPM_TIS_ACCESS_BEEN_SEIZED;
}
if ((val & TPM_TIS_ACCESS_SEIZE)) {
/*
* allow seize if a locality is active and the requesting
* locality is higher than the one that's active
* OR
* allow seize for requesting locality if no locality is
* active
*/
while ((TPM_TIS_IS_VALID_LOCTY(tis->active_locty) &&
locty > tis->active_locty) ||
!TPM_TIS_IS_VALID_LOCTY(tis->active_locty)) {
bool higher_seize = FALSE;
/* already a pending SEIZE ? */
if ((tis->loc[locty].access & TPM_TIS_ACCESS_SEIZE)) {
break;
}
/* check for ongoing seize by a higher locality */
for (l = locty + 1; l < TPM_TIS_NUM_LOCALITIES; l++) {
if ((tis->loc[l].access & TPM_TIS_ACCESS_SEIZE)) {
higher_seize = TRUE;
break;
}
}
if (higher_seize) {
break;
}
/* cancel any seize by a lower locality */
for (l = 0; l < locty - 1; l++) {
tis->loc[l].access &= ~TPM_TIS_ACCESS_SEIZE;
}
tis->loc[locty].access |= TPM_TIS_ACCESS_SEIZE;
DPRINTF("tpm_tis: TPM_TIS_ACCESS_SEIZE: "
"Locality %d seized from locality %d\n",
locty, tis->active_locty);
DPRINTF("tpm_tis: TPM_TIS_ACCESS_SEIZE: Initiating abort.\n");
set_new_locty = 0;
tpm_tis_prep_abort(s, tis->active_locty, locty);
break;
}
}
if ((val & TPM_TIS_ACCESS_REQUEST_USE)) {
if (tis->active_locty != locty) {
if (TPM_TIS_IS_VALID_LOCTY(tis->active_locty)) {
tis->loc[locty].access |= TPM_TIS_ACCESS_REQUEST_USE;
} else {
/* no locality active -> make this one active now */
active_locty = locty;
}
}
}
if (set_new_locty) {
tpm_tis_new_active_locality(s, active_locty);
}
break;
case TPM_TIS_REG_INT_ENABLE:
if (tis->active_locty != locty) {
break;
}
tis->loc[locty].inte = (val & (TPM_TIS_INT_ENABLED |
TPM_TIS_INT_POLARITY_MASK |
TPM_TIS_INTERRUPTS_SUPPORTED));
break;
case TPM_TIS_REG_INT_VECTOR:
/* hard wired -- ignore */
break;
case TPM_TIS_REG_INT_STATUS:
if (tis->active_locty != locty) {
break;
}
/* clearing of interrupt flags */
if (((val & TPM_TIS_INTERRUPTS_SUPPORTED)) &&
(tis->loc[locty].ints & TPM_TIS_INTERRUPTS_SUPPORTED)) {
tis->loc[locty].ints &= ~val;
if (tis->loc[locty].ints == 0) {
qemu_irq_lower(tis->irq);
DPRINTF("tpm_tis: Lowering IRQ\n");
}
}
tis->loc[locty].ints &= ~(val & TPM_TIS_INTERRUPTS_SUPPORTED);
break;
case TPM_TIS_REG_STS:
if (tis->active_locty != locty) {
break;
}
val &= (TPM_TIS_STS_COMMAND_READY | TPM_TIS_STS_TPM_GO |
TPM_TIS_STS_RESPONSE_RETRY);
if (val == TPM_TIS_STS_COMMAND_READY) {
switch (tis->loc[locty].state) {
case TPM_TIS_STATE_READY:
tis->loc[locty].w_offset = 0;
tis->loc[locty].r_offset = 0;
break;
case TPM_TIS_STATE_IDLE:
tis->loc[locty].sts = TPM_TIS_STS_COMMAND_READY;
tis->loc[locty].state = TPM_TIS_STATE_READY;
tpm_tis_raise_irq(s, locty, TPM_TIS_INT_COMMAND_READY);
break;
case TPM_TIS_STATE_EXECUTION:
case TPM_TIS_STATE_RECEPTION:
/* abort currently running command */
DPRINTF("tpm_tis: %s: Initiating abort.\n",
__func__);
tpm_tis_prep_abort(s, locty, locty);
break;
case TPM_TIS_STATE_COMPLETION:
tis->loc[locty].w_offset = 0;
tis->loc[locty].r_offset = 0;
/* shortcut to ready state with C/R set */
tis->loc[locty].state = TPM_TIS_STATE_READY;
if (!(tis->loc[locty].sts & TPM_TIS_STS_COMMAND_READY)) {
tis->loc[locty].sts = TPM_TIS_STS_COMMAND_READY;
tpm_tis_raise_irq(s, locty, TPM_TIS_INT_COMMAND_READY);
}
tis->loc[locty].sts &= ~(TPM_TIS_STS_DATA_AVAILABLE);
break;
}
} else if (val == TPM_TIS_STS_TPM_GO) {
switch (tis->loc[locty].state) {
case TPM_TIS_STATE_RECEPTION:
if ((tis->loc[locty].sts & TPM_TIS_STS_EXPECT) == 0) {
tpm_tis_tpm_send(s, locty);
}
break;
default:
/* ignore */
break;
}
} else if (val == TPM_TIS_STS_RESPONSE_RETRY) {
switch (tis->loc[locty].state) {
case TPM_TIS_STATE_COMPLETION:
tis->loc[locty].r_offset = 0;
tis->loc[locty].sts = TPM_TIS_STS_VALID |
TPM_TIS_STS_DATA_AVAILABLE;
break;
default:
/* ignore */
break;
}
}
break;
case TPM_TIS_REG_DATA_FIFO:
/* data fifo */
if (tis->active_locty != locty) {
break;
}
if (tis->loc[locty].state == TPM_TIS_STATE_IDLE ||
tis->loc[locty].state == TPM_TIS_STATE_EXECUTION ||
tis->loc[locty].state == TPM_TIS_STATE_COMPLETION) {
/* drop the byte */
} else {
DPRINTF("tpm_tis: Byte to send to TPM: %02x\n", (uint8_t)val);
if (tis->loc[locty].state == TPM_TIS_STATE_READY) {
tis->loc[locty].state = TPM_TIS_STATE_RECEPTION;
tis->loc[locty].sts = TPM_TIS_STS_EXPECT | TPM_TIS_STS_VALID;
}
if ((tis->loc[locty].sts & TPM_TIS_STS_EXPECT)) {
if (tis->loc[locty].w_offset < tis->loc[locty].w_buffer.size) {
tis->loc[locty].w_buffer.
buffer[tis->loc[locty].w_offset++] = (uint8_t)val;
} else {
tis->loc[locty].sts = TPM_TIS_STS_VALID;
}
}
/* check for complete packet */
if (tis->loc[locty].w_offset > 5 &&
(tis->loc[locty].sts & TPM_TIS_STS_EXPECT)) {
/* we have a packet length - see if we have all of it */
#ifdef RAISE_STS_IRQ
bool needIrq = !(tis->loc[locty].sts & TPM_TIS_STS_VALID);
#endif
len = tpm_tis_get_size_from_buffer(&tis->loc[locty].w_buffer);
if (len > tis->loc[locty].w_offset) {
tis->loc[locty].sts = TPM_TIS_STS_EXPECT |
TPM_TIS_STS_VALID;
} else {
/* packet complete */
tis->loc[locty].sts = TPM_TIS_STS_VALID;
}
#ifdef RAISE_STS_IRQ
if (needIrq) {
tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID);
}
#endif
}
}
break;
}
}
static void tpm_tis_mmio_write(void *opaque, hwaddr addr,
uint64_t val, unsigned size)
{
return tpm_tis_mmio_write_intern(opaque, addr, val, size, false);
}
static const MemoryRegionOps tpm_tis_memory_ops = {
.read = tpm_tis_mmio_read,
.write = tpm_tis_mmio_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
.min_access_size = 1,
.max_access_size = 4,
},
};
static int tpm_tis_do_startup_tpm(TPMState *s)
{
return s->be_driver->ops->startup_tpm(s->be_driver);
}
/*
* This function is called when the machine starts, resets or due to
* S3 resume.
*/
static void tpm_tis_reset(DeviceState *dev)
{
TPMState *s = TPM(dev);
TPMTISEmuState *tis = &s->s.tis;
int c;
s->be_driver->ops->reset(s->be_driver);
tis->active_locty = TPM_TIS_NO_LOCALITY;
tis->next_locty = TPM_TIS_NO_LOCALITY;
tis->aborting_locty = TPM_TIS_NO_LOCALITY;
for (c = 0; c < TPM_TIS_NUM_LOCALITIES; c++) {
tis->loc[c].access = TPM_TIS_ACCESS_TPM_REG_VALID_STS;
tis->loc[c].sts = 0;
tis->loc[c].inte = TPM_TIS_INT_POLARITY_LOW_LEVEL;
tis->loc[c].ints = 0;
tis->loc[c].state = TPM_TIS_STATE_IDLE;
tis->loc[c].w_offset = 0;
s->be_driver->ops->realloc_buffer(&tis->loc[c].w_buffer);
tis->loc[c].r_offset = 0;
s->be_driver->ops->realloc_buffer(&tis->loc[c].r_buffer);
}
tpm_tis_do_startup_tpm(s);
}
static const VMStateDescription vmstate_tpm_tis = {
.name = "tpm",
.unmigratable = 1,
};
static Property tpm_tis_properties[] = {
DEFINE_PROP_UINT32("irq", TPMState,
s.tis.irq_num, TPM_TIS_IRQ),
DEFINE_PROP_STRING("tpmdev", TPMState, backend),
DEFINE_PROP_END_OF_LIST(),
};
static void tpm_tis_realizefn(DeviceState *dev, Error **errp)
{
TPMState *s = TPM(dev);
TPMTISEmuState *tis = &s->s.tis;
s->be_driver = qemu_find_tpm(s->backend);
if (!s->be_driver) {
error_setg(errp, "tpm_tis: backend driver with id %s could not be "
"found", s->backend);
return;
}
s->be_driver->fe_model = TPM_MODEL_TPM_TIS;
if (s->be_driver->ops->init(s->be_driver, s, tpm_tis_receive_cb)) {
error_setg(errp, "tpm_tis: backend driver with id %s could not be "
"initialized", s->backend);
return;
}
if (tis->irq_num > 15) {
error_setg(errp, "tpm_tis: IRQ %d for TPM TIS is outside valid range "
"of 0 to 15.\n", tis->irq_num);
return;
}
tis->bh = qemu_bh_new(tpm_tis_receive_bh, s);
isa_init_irq(&s->busdev, &tis->irq, tis->irq_num);
}
static void tpm_tis_initfn(Object *obj)
{
ISADevice *dev = ISA_DEVICE(obj);
TPMState *s = TPM(obj);
memory_region_init_io(&s->mmio, &tpm_tis_memory_ops, s, "tpm-tis-mmio",
TPM_TIS_NUM_LOCALITIES << TPM_TIS_LOCALITY_SHIFT);
memory_region_add_subregion(isa_address_space(dev), TPM_TIS_ADDR_BASE,
&s->mmio);
}
static void tpm_tis_uninitfn(Object *obj)
{
TPMState *s = TPM(obj);
memory_region_del_subregion(get_system_memory(), &s->mmio);
memory_region_destroy(&s->mmio);
}
static void tpm_tis_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = tpm_tis_realizefn;
dc->props = tpm_tis_properties;
dc->reset = tpm_tis_reset;
dc->vmsd = &vmstate_tpm_tis;
}
static const TypeInfo tpm_tis_info = {
.name = TYPE_TPM_TIS,
.parent = TYPE_ISA_DEVICE,
.instance_size = sizeof(TPMState),
.instance_init = tpm_tis_initfn,
.instance_finalize = tpm_tis_uninitfn,
.class_init = tpm_tis_class_init,
};
static void tpm_tis_register(void)
{
type_register_static(&tpm_tis_info);
tpm_register_model(TPM_MODEL_TPM_TIS);
}
type_init(tpm_tis_register)