ffab1be706
Note: the "Platform Reset Attack Mitigation" specification isn't explicit about NVDIMM, since they could have different usages. It uses the term "system memory" generally (and also "volatile memory RAM" in its introduction). For initial support, I propose to consider non-volatile memory as not being subject to the memory clear. There is an on-going discussion in the TCG "pcclientwg" working group for future revisions. CPU cache clearing is done unconditionally in edk2 since commit d20ae95a13e851 (edk2-stable201811). Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Tested-by: Stefan Berger <stefanb@linux.ibm.com> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
1039 lines
30 KiB
C
1039 lines
30 KiB
C
/*
|
|
* 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.3, 21 March 2013
|
|
* In the developers menu choose the PC Client section then find the TIS
|
|
* specification.
|
|
*
|
|
* TPM TIS for TPM 2 implementation following TCG PC Client Platform
|
|
* TPM Profile (PTP) Specification, Familiy 2.0, Revision 00.43
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "hw/isa/isa.h"
|
|
#include "qapi/error.h"
|
|
|
|
#include "hw/acpi/tpm.h"
|
|
#include "hw/pci/pci_ids.h"
|
|
#include "sysemu/tpm_backend.h"
|
|
#include "tpm_int.h"
|
|
#include "tpm_util.h"
|
|
#include "tpm_ppi.h"
|
|
#include "trace.h"
|
|
|
|
#define TPM_TIS_NUM_LOCALITIES 5 /* per spec */
|
|
#define TPM_TIS_LOCALITY_SHIFT 12
|
|
#define TPM_TIS_NO_LOCALITY 0xff
|
|
|
|
#define TPM_TIS_IS_VALID_LOCTY(x) ((x) < TPM_TIS_NUM_LOCALITIES)
|
|
|
|
#define TPM_TIS_BUFFER_MAX 4096
|
|
|
|
typedef enum {
|
|
TPM_TIS_STATE_IDLE = 0,
|
|
TPM_TIS_STATE_READY,
|
|
TPM_TIS_STATE_COMPLETION,
|
|
TPM_TIS_STATE_EXECUTION,
|
|
TPM_TIS_STATE_RECEPTION,
|
|
} TPMTISState;
|
|
|
|
/* locality data -- all fields are persisted */
|
|
typedef struct TPMLocality {
|
|
TPMTISState state;
|
|
uint8_t access;
|
|
uint32_t sts;
|
|
uint32_t iface_id;
|
|
uint32_t inte;
|
|
uint32_t ints;
|
|
} TPMLocality;
|
|
|
|
typedef struct TPMState {
|
|
ISADevice busdev;
|
|
MemoryRegion mmio;
|
|
|
|
unsigned char buffer[TPM_TIS_BUFFER_MAX];
|
|
uint16_t rw_offset;
|
|
|
|
uint8_t active_locty;
|
|
uint8_t aborting_locty;
|
|
uint8_t next_locty;
|
|
|
|
TPMLocality loc[TPM_TIS_NUM_LOCALITIES];
|
|
|
|
qemu_irq irq;
|
|
uint32_t irq_num;
|
|
|
|
TPMBackendCmd cmd;
|
|
|
|
TPMBackend *be_driver;
|
|
TPMVersion be_tpm_version;
|
|
|
|
size_t be_buffer_size;
|
|
|
|
bool ppi_enabled;
|
|
TPMPPI ppi;
|
|
} TPMState;
|
|
|
|
#define TPM(obj) OBJECT_CHECK(TPMState, (obj), TYPE_TPM_TIS)
|
|
|
|
#define DEBUG_TIS 0
|
|
|
|
/* local prototypes */
|
|
|
|
static uint64_t tpm_tis_mmio_read(void *opaque, hwaddr addr,
|
|
unsigned size);
|
|
|
|
/* utility functions */
|
|
|
|
static uint8_t tpm_tis_locality_from_addr(hwaddr addr)
|
|
{
|
|
return (uint8_t)((addr >> TPM_TIS_LOCALITY_SHIFT) & 0x7);
|
|
}
|
|
|
|
static void tpm_tis_show_buffer(const unsigned char *buffer,
|
|
size_t buffer_size, const char *string)
|
|
{
|
|
uint32_t len, i;
|
|
|
|
len = MIN(tpm_cmd_get_size(buffer), buffer_size);
|
|
printf("tpm_tis: %s length = %d\n", string, len);
|
|
for (i = 0; i < len; i++) {
|
|
if (i && !(i % 16)) {
|
|
printf("\n");
|
|
}
|
|
printf("%.2X ", buffer[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
/*
|
|
* Set the given flags in the STS register by clearing the register but
|
|
* preserving the SELFTEST_DONE and TPM_FAMILY_MASK flags and then setting
|
|
* the new flags.
|
|
*
|
|
* The SELFTEST_DONE flag is acquired from the backend that determines it by
|
|
* peeking into TPM commands.
|
|
*
|
|
* A VM suspend/resume will preserve the flag by storing it into the VM
|
|
* device state, but the backend will not remember it when QEMU is started
|
|
* again. Therefore, we cache the flag here. Once set, it will not be unset
|
|
* except by a reset.
|
|
*/
|
|
static void tpm_tis_sts_set(TPMLocality *l, uint32_t flags)
|
|
{
|
|
l->sts &= TPM_TIS_STS_SELFTEST_DONE | TPM_TIS_STS_TPM_FAMILY_MASK;
|
|
l->sts |= flags;
|
|
}
|
|
|
|
/*
|
|
* Send a request to the TPM.
|
|
*/
|
|
static void tpm_tis_tpm_send(TPMState *s, uint8_t locty)
|
|
{
|
|
if (DEBUG_TIS) {
|
|
tpm_tis_show_buffer(s->buffer, s->be_buffer_size,
|
|
"tpm_tis: To TPM");
|
|
}
|
|
|
|
/*
|
|
* rw_offset serves as length indicator for length of data;
|
|
* it's reset when the response comes back
|
|
*/
|
|
s->loc[locty].state = TPM_TIS_STATE_EXECUTION;
|
|
|
|
s->cmd = (TPMBackendCmd) {
|
|
.locty = locty,
|
|
.in = s->buffer,
|
|
.in_len = s->rw_offset,
|
|
.out = s->buffer,
|
|
.out_len = s->be_buffer_size,
|
|
};
|
|
|
|
tpm_backend_deliver_request(s->be_driver, &s->cmd);
|
|
}
|
|
|
|
/* raise an interrupt if allowed */
|
|
static void tpm_tis_raise_irq(TPMState *s, uint8_t locty, uint32_t irqmask)
|
|
{
|
|
if (!TPM_TIS_IS_VALID_LOCTY(locty)) {
|
|
return;
|
|
}
|
|
|
|
if ((s->loc[locty].inte & TPM_TIS_INT_ENABLED) &&
|
|
(s->loc[locty].inte & irqmask)) {
|
|
trace_tpm_tis_raise_irq(irqmask);
|
|
qemu_irq_raise(s->irq);
|
|
s->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->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)
|
|
{
|
|
bool change = (s->active_locty != new_active_locty);
|
|
bool is_seize;
|
|
uint8_t mask;
|
|
|
|
if (change && TPM_TIS_IS_VALID_LOCTY(s->active_locty)) {
|
|
is_seize = TPM_TIS_IS_VALID_LOCTY(new_active_locty) &&
|
|
s->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 */
|
|
s->loc[s->active_locty].access &= mask;
|
|
|
|
if (is_seize) {
|
|
s->loc[s->active_locty].access |= TPM_TIS_ACCESS_BEEN_SEIZED;
|
|
}
|
|
}
|
|
|
|
s->active_locty = new_active_locty;
|
|
|
|
trace_tpm_tis_new_active_locality(s->active_locty);
|
|
|
|
if (TPM_TIS_IS_VALID_LOCTY(new_active_locty)) {
|
|
/* set flags on the new active locality */
|
|
s->loc[new_active_locty].access |= TPM_TIS_ACCESS_ACTIVE_LOCALITY;
|
|
s->loc[new_active_locty].access &= ~(TPM_TIS_ACCESS_REQUEST_USE |
|
|
TPM_TIS_ACCESS_SEIZE);
|
|
}
|
|
|
|
if (change) {
|
|
tpm_tis_raise_irq(s, s->active_locty, TPM_TIS_INT_LOCALITY_CHANGED);
|
|
}
|
|
}
|
|
|
|
/* abort -- this function switches the locality */
|
|
static void tpm_tis_abort(TPMState *s)
|
|
{
|
|
s->rw_offset = 0;
|
|
|
|
trace_tpm_tis_abort(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 = TPM_TIS_STATE_READY;
|
|
tpm_tis_sts_set(&s->loc[s->aborting_locty],
|
|
TPM_TIS_STS_COMMAND_READY);
|
|
tpm_tis_raise_irq(s, s->aborting_locty, TPM_TIS_INT_COMMAND_READY);
|
|
}
|
|
|
|
/* locality after abort is another one than the current one */
|
|
tpm_tis_new_active_locality(s, s->next_locty);
|
|
|
|
s->next_locty = TPM_TIS_NO_LOCALITY;
|
|
/* nobody's aborting a command anymore */
|
|
s->aborting_locty = TPM_TIS_NO_LOCALITY;
|
|
}
|
|
|
|
/* prepare aborting current command */
|
|
static void tpm_tis_prep_abort(TPMState *s, uint8_t locty, uint8_t newlocty)
|
|
{
|
|
uint8_t busy_locty;
|
|
|
|
assert(TPM_TIS_IS_VALID_LOCTY(newlocty));
|
|
|
|
s->aborting_locty = locty; /* may also be TPM_TIS_NO_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.
|
|
*/
|
|
for (busy_locty = 0; busy_locty < TPM_TIS_NUM_LOCALITIES; busy_locty++) {
|
|
if (s->loc[busy_locty].state == TPM_TIS_STATE_EXECUTION) {
|
|
/*
|
|
* request the backend to cancel. Some backends may not
|
|
* support it
|
|
*/
|
|
tpm_backend_cancel_cmd(s->be_driver);
|
|
return;
|
|
}
|
|
}
|
|
|
|
tpm_tis_abort(s);
|
|
}
|
|
|
|
/*
|
|
* Callback from the TPM to indicate that the response was received.
|
|
*/
|
|
static void tpm_tis_request_completed(TPMIf *ti, int ret)
|
|
{
|
|
TPMState *s = TPM(ti);
|
|
uint8_t locty = s->cmd.locty;
|
|
uint8_t l;
|
|
|
|
assert(TPM_TIS_IS_VALID_LOCTY(locty));
|
|
|
|
if (s->cmd.selftest_done) {
|
|
for (l = 0; l < TPM_TIS_NUM_LOCALITIES; l++) {
|
|
s->loc[l].sts |= TPM_TIS_STS_SELFTEST_DONE;
|
|
}
|
|
}
|
|
|
|
/* FIXME: report error if ret != 0 */
|
|
tpm_tis_sts_set(&s->loc[locty],
|
|
TPM_TIS_STS_VALID | TPM_TIS_STS_DATA_AVAILABLE);
|
|
s->loc[locty].state = TPM_TIS_STATE_COMPLETION;
|
|
s->rw_offset = 0;
|
|
|
|
if (DEBUG_TIS) {
|
|
tpm_tis_show_buffer(s->buffer, s->be_buffer_size,
|
|
"tpm_tis: From TPM");
|
|
}
|
|
|
|
if (TPM_TIS_IS_VALID_LOCTY(s->next_locty)) {
|
|
tpm_tis_abort(s);
|
|
}
|
|
|
|
tpm_tis_raise_irq(s, locty,
|
|
TPM_TIS_INT_DATA_AVAILABLE | TPM_TIS_INT_STS_VALID);
|
|
}
|
|
|
|
/*
|
|
* Read a byte of response data
|
|
*/
|
|
static uint32_t tpm_tis_data_read(TPMState *s, uint8_t locty)
|
|
{
|
|
uint32_t ret = TPM_TIS_NO_DATA_BYTE;
|
|
uint16_t len;
|
|
|
|
if ((s->loc[locty].sts & TPM_TIS_STS_DATA_AVAILABLE)) {
|
|
len = MIN(tpm_cmd_get_size(&s->buffer),
|
|
s->be_buffer_size);
|
|
|
|
ret = s->buffer[s->rw_offset++];
|
|
if (s->rw_offset >= len) {
|
|
/* got last byte */
|
|
tpm_tis_sts_set(&s->loc[locty], TPM_TIS_STS_VALID);
|
|
tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID);
|
|
}
|
|
trace_tpm_tis_data_read(ret, s->rw_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;
|
|
|
|
printf("tpm_tis: active locality : %d\n"
|
|
"tpm_tis: state of locality %d : %d\n"
|
|
"tpm_tis: register dump:\n",
|
|
s->active_locty,
|
|
locty, s->loc[locty].state);
|
|
|
|
for (idx = 0; regs[idx] != 0xfff; idx++) {
|
|
printf("tpm_tis: 0x%04x : 0x%08x\n", regs[idx],
|
|
(int)tpm_tis_mmio_read(opaque, base + regs[idx], 4));
|
|
}
|
|
|
|
printf("tpm_tis: r/w offset : %d\n"
|
|
"tpm_tis: result buffer : ",
|
|
s->rw_offset);
|
|
for (idx = 0;
|
|
idx < MIN(tpm_cmd_get_size(&s->buffer), s->be_buffer_size);
|
|
idx++) {
|
|
printf("%c%02x%s",
|
|
s->rw_offset == idx ? '>' : ' ',
|
|
s->buffer[idx],
|
|
((idx & 0xf) == 0xf) ? "\ntpm_tis: " : "");
|
|
}
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* 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;
|
|
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;
|
|
uint8_t v;
|
|
|
|
if (tpm_backend_had_startup_error(s->be_driver)) {
|
|
return 0;
|
|
}
|
|
|
|
switch (offset) {
|
|
case TPM_TIS_REG_ACCESS:
|
|
/* never show the SEIZE flag even though we use it internally */
|
|
val = s->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 |= !tpm_backend_get_tpm_established_flag(s->be_driver);
|
|
break;
|
|
case TPM_TIS_REG_INT_ENABLE:
|
|
val = s->loc[locty].inte;
|
|
break;
|
|
case TPM_TIS_REG_INT_VECTOR:
|
|
val = s->irq_num;
|
|
break;
|
|
case TPM_TIS_REG_INT_STATUS:
|
|
val = s->loc[locty].ints;
|
|
break;
|
|
case TPM_TIS_REG_INTF_CAPABILITY:
|
|
switch (s->be_tpm_version) {
|
|
case TPM_VERSION_UNSPEC:
|
|
val = 0;
|
|
break;
|
|
case TPM_VERSION_1_2:
|
|
val = TPM_TIS_CAPABILITIES_SUPPORTED1_3;
|
|
break;
|
|
case TPM_VERSION_2_0:
|
|
val = TPM_TIS_CAPABILITIES_SUPPORTED2_0;
|
|
break;
|
|
}
|
|
break;
|
|
case TPM_TIS_REG_STS:
|
|
if (s->active_locty == locty) {
|
|
if ((s->loc[locty].sts & TPM_TIS_STS_DATA_AVAILABLE)) {
|
|
val = TPM_TIS_BURST_COUNT(
|
|
MIN(tpm_cmd_get_size(&s->buffer),
|
|
s->be_buffer_size)
|
|
- s->rw_offset) | s->loc[locty].sts;
|
|
} else {
|
|
avail = s->be_buffer_size - s->rw_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) | s->loc[locty].sts;
|
|
}
|
|
}
|
|
break;
|
|
case TPM_TIS_REG_DATA_FIFO:
|
|
case TPM_TIS_REG_DATA_XFIFO ... TPM_TIS_REG_DATA_XFIFO_END:
|
|
if (s->active_locty == locty) {
|
|
if (size > 4 - (addr & 0x3)) {
|
|
/* prevent access beyond FIFO */
|
|
size = 4 - (addr & 0x3);
|
|
}
|
|
val = 0;
|
|
shift = 0;
|
|
while (size > 0) {
|
|
switch (s->loc[locty].state) {
|
|
case TPM_TIS_STATE_COMPLETION:
|
|
v = tpm_tis_data_read(s, locty);
|
|
break;
|
|
default:
|
|
v = TPM_TIS_NO_DATA_BYTE;
|
|
break;
|
|
}
|
|
val |= (v << shift);
|
|
shift += 8;
|
|
size--;
|
|
}
|
|
shift = 0; /* no more adjustments */
|
|
}
|
|
break;
|
|
case TPM_TIS_REG_INTERFACE_ID:
|
|
val = s->loc[locty].iface_id;
|
|
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
|
|
}
|
|
|
|
if (shift) {
|
|
val >>= shift;
|
|
}
|
|
|
|
trace_tpm_tis_mmio_read(size, 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 tpm_tis_mmio_write(void *opaque, hwaddr addr,
|
|
uint64_t val, unsigned size)
|
|
{
|
|
TPMState *s = opaque;
|
|
uint16_t off = addr & 0xffc;
|
|
uint8_t shift = (addr & 0x3) * 8;
|
|
uint8_t locty = tpm_tis_locality_from_addr(addr);
|
|
uint8_t active_locty, l;
|
|
int c, set_new_locty = 1;
|
|
uint16_t len;
|
|
uint32_t mask = (size == 1) ? 0xff : ((size == 2) ? 0xffff : ~0);
|
|
|
|
trace_tpm_tis_mmio_write(size, addr, val);
|
|
|
|
if (locty == 4) {
|
|
trace_tpm_tis_mmio_write_locty4();
|
|
return;
|
|
}
|
|
|
|
if (tpm_backend_had_startup_error(s->be_driver)) {
|
|
return;
|
|
}
|
|
|
|
val &= mask;
|
|
|
|
if (shift) {
|
|
val <<= shift;
|
|
mask <<= shift;
|
|
}
|
|
|
|
mask ^= 0xffffffff;
|
|
|
|
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 = s->active_locty;
|
|
|
|
if ((val & TPM_TIS_ACCESS_ACTIVE_LOCALITY)) {
|
|
/* give up locality if currently owned */
|
|
if (s->active_locty == locty) {
|
|
trace_tpm_tis_mmio_write_release_locty(locty);
|
|
|
|
uint8_t newlocty = TPM_TIS_NO_LOCALITY;
|
|
/* anybody wants the locality ? */
|
|
for (c = TPM_TIS_NUM_LOCALITIES - 1; c >= 0; c--) {
|
|
if ((s->loc[c].access & TPM_TIS_ACCESS_REQUEST_USE)) {
|
|
trace_tpm_tis_mmio_write_locty_req_use(c);
|
|
newlocty = c;
|
|
break;
|
|
}
|
|
}
|
|
trace_tpm_tis_mmio_write_next_locty(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 */
|
|
s->loc[locty].access &= ~TPM_TIS_ACCESS_REQUEST_USE;
|
|
}
|
|
}
|
|
|
|
if ((val & TPM_TIS_ACCESS_BEEN_SEIZED)) {
|
|
s->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(s->active_locty) &&
|
|
locty > s->active_locty) ||
|
|
!TPM_TIS_IS_VALID_LOCTY(s->active_locty)) {
|
|
bool higher_seize = FALSE;
|
|
|
|
/* already a pending SEIZE ? */
|
|
if ((s->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 ((s->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++) {
|
|
s->loc[l].access &= ~TPM_TIS_ACCESS_SEIZE;
|
|
}
|
|
|
|
s->loc[locty].access |= TPM_TIS_ACCESS_SEIZE;
|
|
|
|
trace_tpm_tis_mmio_write_locty_seized(locty, s->active_locty);
|
|
trace_tpm_tis_mmio_write_init_abort();
|
|
|
|
set_new_locty = 0;
|
|
tpm_tis_prep_abort(s, s->active_locty, locty);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((val & TPM_TIS_ACCESS_REQUEST_USE)) {
|
|
if (s->active_locty != locty) {
|
|
if (TPM_TIS_IS_VALID_LOCTY(s->active_locty)) {
|
|
s->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 (s->active_locty != locty) {
|
|
break;
|
|
}
|
|
|
|
s->loc[locty].inte &= mask;
|
|
s->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 (s->active_locty != locty) {
|
|
break;
|
|
}
|
|
|
|
/* clearing of interrupt flags */
|
|
if (((val & TPM_TIS_INTERRUPTS_SUPPORTED)) &&
|
|
(s->loc[locty].ints & TPM_TIS_INTERRUPTS_SUPPORTED)) {
|
|
s->loc[locty].ints &= ~val;
|
|
if (s->loc[locty].ints == 0) {
|
|
qemu_irq_lower(s->irq);
|
|
trace_tpm_tis_mmio_write_lowering_irq();
|
|
}
|
|
}
|
|
s->loc[locty].ints &= ~(val & TPM_TIS_INTERRUPTS_SUPPORTED);
|
|
break;
|
|
case TPM_TIS_REG_STS:
|
|
if (s->active_locty != locty) {
|
|
break;
|
|
}
|
|
|
|
if (s->be_tpm_version == TPM_VERSION_2_0) {
|
|
/* some flags that are only supported for TPM 2 */
|
|
if (val & TPM_TIS_STS_COMMAND_CANCEL) {
|
|
if (s->loc[locty].state == TPM_TIS_STATE_EXECUTION) {
|
|
/*
|
|
* request the backend to cancel. Some backends may not
|
|
* support it
|
|
*/
|
|
tpm_backend_cancel_cmd(s->be_driver);
|
|
}
|
|
}
|
|
|
|
if (val & TPM_TIS_STS_RESET_ESTABLISHMENT_BIT) {
|
|
if (locty == 3 || locty == 4) {
|
|
tpm_backend_reset_tpm_established_flag(s->be_driver, locty);
|
|
}
|
|
}
|
|
}
|
|
|
|
val &= (TPM_TIS_STS_COMMAND_READY | TPM_TIS_STS_TPM_GO |
|
|
TPM_TIS_STS_RESPONSE_RETRY);
|
|
|
|
if (val == TPM_TIS_STS_COMMAND_READY) {
|
|
switch (s->loc[locty].state) {
|
|
|
|
case TPM_TIS_STATE_READY:
|
|
s->rw_offset = 0;
|
|
break;
|
|
|
|
case TPM_TIS_STATE_IDLE:
|
|
tpm_tis_sts_set(&s->loc[locty], TPM_TIS_STS_COMMAND_READY);
|
|
s->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 */
|
|
trace_tpm_tis_mmio_write_init_abort();
|
|
tpm_tis_prep_abort(s, locty, locty);
|
|
break;
|
|
|
|
case TPM_TIS_STATE_COMPLETION:
|
|
s->rw_offset = 0;
|
|
/* shortcut to ready state with C/R set */
|
|
s->loc[locty].state = TPM_TIS_STATE_READY;
|
|
if (!(s->loc[locty].sts & TPM_TIS_STS_COMMAND_READY)) {
|
|
tpm_tis_sts_set(&s->loc[locty],
|
|
TPM_TIS_STS_COMMAND_READY);
|
|
tpm_tis_raise_irq(s, locty, TPM_TIS_INT_COMMAND_READY);
|
|
}
|
|
s->loc[locty].sts &= ~(TPM_TIS_STS_DATA_AVAILABLE);
|
|
break;
|
|
|
|
}
|
|
} else if (val == TPM_TIS_STS_TPM_GO) {
|
|
switch (s->loc[locty].state) {
|
|
case TPM_TIS_STATE_RECEPTION:
|
|
if ((s->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 (s->loc[locty].state) {
|
|
case TPM_TIS_STATE_COMPLETION:
|
|
s->rw_offset = 0;
|
|
tpm_tis_sts_set(&s->loc[locty],
|
|
TPM_TIS_STS_VALID|
|
|
TPM_TIS_STS_DATA_AVAILABLE);
|
|
break;
|
|
default:
|
|
/* ignore */
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case TPM_TIS_REG_DATA_FIFO:
|
|
case TPM_TIS_REG_DATA_XFIFO ... TPM_TIS_REG_DATA_XFIFO_END:
|
|
/* data fifo */
|
|
if (s->active_locty != locty) {
|
|
break;
|
|
}
|
|
|
|
if (s->loc[locty].state == TPM_TIS_STATE_IDLE ||
|
|
s->loc[locty].state == TPM_TIS_STATE_EXECUTION ||
|
|
s->loc[locty].state == TPM_TIS_STATE_COMPLETION) {
|
|
/* drop the byte */
|
|
} else {
|
|
trace_tpm_tis_mmio_write_data2send(val, size);
|
|
if (s->loc[locty].state == TPM_TIS_STATE_READY) {
|
|
s->loc[locty].state = TPM_TIS_STATE_RECEPTION;
|
|
tpm_tis_sts_set(&s->loc[locty],
|
|
TPM_TIS_STS_EXPECT | TPM_TIS_STS_VALID);
|
|
}
|
|
|
|
val >>= shift;
|
|
if (size > 4 - (addr & 0x3)) {
|
|
/* prevent access beyond FIFO */
|
|
size = 4 - (addr & 0x3);
|
|
}
|
|
|
|
while ((s->loc[locty].sts & TPM_TIS_STS_EXPECT) && size > 0) {
|
|
if (s->rw_offset < s->be_buffer_size) {
|
|
s->buffer[s->rw_offset++] =
|
|
(uint8_t)val;
|
|
val >>= 8;
|
|
size--;
|
|
} else {
|
|
tpm_tis_sts_set(&s->loc[locty], TPM_TIS_STS_VALID);
|
|
}
|
|
}
|
|
|
|
/* check for complete packet */
|
|
if (s->rw_offset > 5 &&
|
|
(s->loc[locty].sts & TPM_TIS_STS_EXPECT)) {
|
|
/* we have a packet length - see if we have all of it */
|
|
bool need_irq = !(s->loc[locty].sts & TPM_TIS_STS_VALID);
|
|
|
|
len = tpm_cmd_get_size(&s->buffer);
|
|
if (len > s->rw_offset) {
|
|
tpm_tis_sts_set(&s->loc[locty],
|
|
TPM_TIS_STS_EXPECT | TPM_TIS_STS_VALID);
|
|
} else {
|
|
/* packet complete */
|
|
tpm_tis_sts_set(&s->loc[locty], TPM_TIS_STS_VALID);
|
|
}
|
|
if (need_irq) {
|
|
tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case TPM_TIS_REG_INTERFACE_ID:
|
|
if (val & TPM_TIS_IFACE_ID_INT_SEL_LOCK) {
|
|
for (l = 0; l < TPM_TIS_NUM_LOCALITIES; l++) {
|
|
s->loc[l].iface_id |= TPM_TIS_IFACE_ID_INT_SEL_LOCK;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
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,
|
|
},
|
|
};
|
|
|
|
/*
|
|
* Get the TPMVersion of the backend device being used
|
|
*/
|
|
static enum TPMVersion tpm_tis_get_tpm_version(TPMIf *ti)
|
|
{
|
|
TPMState *s = TPM(ti);
|
|
|
|
if (tpm_backend_had_startup_error(s->be_driver)) {
|
|
return TPM_VERSION_UNSPEC;
|
|
}
|
|
|
|
return tpm_backend_get_tpm_version(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);
|
|
int c;
|
|
|
|
s->be_tpm_version = tpm_backend_get_tpm_version(s->be_driver);
|
|
s->be_buffer_size = MIN(tpm_backend_get_buffer_size(s->be_driver),
|
|
TPM_TIS_BUFFER_MAX);
|
|
|
|
if (s->ppi_enabled) {
|
|
tpm_ppi_reset(&s->ppi);
|
|
}
|
|
tpm_backend_reset(s->be_driver);
|
|
|
|
s->active_locty = TPM_TIS_NO_LOCALITY;
|
|
s->next_locty = TPM_TIS_NO_LOCALITY;
|
|
s->aborting_locty = TPM_TIS_NO_LOCALITY;
|
|
|
|
for (c = 0; c < TPM_TIS_NUM_LOCALITIES; c++) {
|
|
s->loc[c].access = TPM_TIS_ACCESS_TPM_REG_VALID_STS;
|
|
switch (s->be_tpm_version) {
|
|
case TPM_VERSION_UNSPEC:
|
|
break;
|
|
case TPM_VERSION_1_2:
|
|
s->loc[c].sts = TPM_TIS_STS_TPM_FAMILY1_2;
|
|
s->loc[c].iface_id = TPM_TIS_IFACE_ID_SUPPORTED_FLAGS1_3;
|
|
break;
|
|
case TPM_VERSION_2_0:
|
|
s->loc[c].sts = TPM_TIS_STS_TPM_FAMILY2_0;
|
|
s->loc[c].iface_id = TPM_TIS_IFACE_ID_SUPPORTED_FLAGS2_0;
|
|
break;
|
|
}
|
|
s->loc[c].inte = TPM_TIS_INT_POLARITY_LOW_LEVEL;
|
|
s->loc[c].ints = 0;
|
|
s->loc[c].state = TPM_TIS_STATE_IDLE;
|
|
|
|
s->rw_offset = 0;
|
|
}
|
|
|
|
tpm_backend_startup_tpm(s->be_driver, s->be_buffer_size);
|
|
}
|
|
|
|
/* persistent state handling */
|
|
|
|
static int tpm_tis_pre_save(void *opaque)
|
|
{
|
|
TPMState *s = opaque;
|
|
uint8_t locty = s->active_locty;
|
|
|
|
trace_tpm_tis_pre_save(locty, s->rw_offset);
|
|
|
|
if (DEBUG_TIS) {
|
|
tpm_tis_dump_state(opaque, 0);
|
|
}
|
|
|
|
/*
|
|
* Synchronize with backend completion.
|
|
*/
|
|
tpm_backend_finish_sync(s->be_driver);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const VMStateDescription vmstate_locty = {
|
|
.name = "tpm-tis/locty",
|
|
.version_id = 0,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_UINT32(state, TPMLocality),
|
|
VMSTATE_UINT32(inte, TPMLocality),
|
|
VMSTATE_UINT32(ints, TPMLocality),
|
|
VMSTATE_UINT8(access, TPMLocality),
|
|
VMSTATE_UINT32(sts, TPMLocality),
|
|
VMSTATE_UINT32(iface_id, TPMLocality),
|
|
VMSTATE_END_OF_LIST(),
|
|
}
|
|
};
|
|
|
|
static const VMStateDescription vmstate_tpm_tis = {
|
|
.name = "tpm-tis",
|
|
.version_id = 0,
|
|
.pre_save = tpm_tis_pre_save,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_BUFFER(buffer, TPMState),
|
|
VMSTATE_UINT16(rw_offset, TPMState),
|
|
VMSTATE_UINT8(active_locty, TPMState),
|
|
VMSTATE_UINT8(aborting_locty, TPMState),
|
|
VMSTATE_UINT8(next_locty, TPMState),
|
|
|
|
VMSTATE_STRUCT_ARRAY(loc, TPMState, TPM_TIS_NUM_LOCALITIES, 0,
|
|
vmstate_locty, TPMLocality),
|
|
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static Property tpm_tis_properties[] = {
|
|
DEFINE_PROP_UINT32("irq", TPMState, irq_num, TPM_TIS_IRQ),
|
|
DEFINE_PROP_TPMBE("tpmdev", TPMState, be_driver),
|
|
DEFINE_PROP_BOOL("ppi", TPMState, ppi_enabled, true),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void tpm_tis_realizefn(DeviceState *dev, Error **errp)
|
|
{
|
|
TPMState *s = TPM(dev);
|
|
|
|
if (!tpm_find()) {
|
|
error_setg(errp, "at most one TPM device is permitted");
|
|
return;
|
|
}
|
|
|
|
if (!s->be_driver) {
|
|
error_setg(errp, "'tpmdev' property is required");
|
|
return;
|
|
}
|
|
if (s->irq_num > 15) {
|
|
error_setg(errp, "IRQ %d is outside valid range of 0 to 15",
|
|
s->irq_num);
|
|
return;
|
|
}
|
|
|
|
isa_init_irq(&s->busdev, &s->irq, s->irq_num);
|
|
|
|
memory_region_add_subregion(isa_address_space(ISA_DEVICE(dev)),
|
|
TPM_TIS_ADDR_BASE, &s->mmio);
|
|
|
|
if (s->ppi_enabled) {
|
|
tpm_ppi_init(&s->ppi, isa_address_space(ISA_DEVICE(dev)),
|
|
TPM_PPI_ADDR_BASE, OBJECT(s));
|
|
}
|
|
}
|
|
|
|
static void tpm_tis_initfn(Object *obj)
|
|
{
|
|
TPMState *s = TPM(obj);
|
|
|
|
memory_region_init_io(&s->mmio, OBJECT(s), &tpm_tis_memory_ops,
|
|
s, "tpm-tis-mmio",
|
|
TPM_TIS_NUM_LOCALITIES << TPM_TIS_LOCALITY_SHIFT);
|
|
}
|
|
|
|
static void tpm_tis_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
TPMIfClass *tc = TPM_IF_CLASS(klass);
|
|
|
|
dc->realize = tpm_tis_realizefn;
|
|
dc->props = tpm_tis_properties;
|
|
dc->reset = tpm_tis_reset;
|
|
dc->vmsd = &vmstate_tpm_tis;
|
|
tc->model = TPM_MODEL_TPM_TIS;
|
|
tc->get_version = tpm_tis_get_tpm_version;
|
|
tc->request_completed = tpm_tis_request_completed;
|
|
}
|
|
|
|
static const TypeInfo tpm_tis_info = {
|
|
.name = TYPE_TPM_TIS,
|
|
.parent = TYPE_ISA_DEVICE,
|
|
.instance_size = sizeof(TPMState),
|
|
.instance_init = tpm_tis_initfn,
|
|
.class_init = tpm_tis_class_init,
|
|
.interfaces = (InterfaceInfo[]) {
|
|
{ TYPE_TPM_IF },
|
|
{ }
|
|
}
|
|
};
|
|
|
|
static void tpm_tis_register(void)
|
|
{
|
|
type_register_static(&tpm_tis_info);
|
|
}
|
|
|
|
type_init(tpm_tis_register)
|