bbadfb2e0a
Qemu already supports devices attached to ISA and sysbus. This drop adds support for the I2C bus attached TPM devices. This commit includes changes for the common code. - Added support for the new checksum registers which are required for the I2C support. The checksum calculation is handled in the qemu common code. - Added wrapper function for read and write data so that I2C code can call it without MMIO interface. The TPM TIS I2C spec describes in the table in section "Interface Locality Usage per Register" that the TPM_INT_ENABLE and TPM_INT_STATUS registers must be writable for any locality even if the locality is not the active locality. Therefore, remove the checks whether the writing locality is the active locality for these registers. Signed-off-by: Ninad Palsule <ninad@linux.ibm.com> Signed-off-by: Stefan Berger <stefanb@linux.ibm.com> Reviewed-by: Stefan Berger <stefanb@linux.ibm.com> Tested-by: Stefan Berger <stefanb@linux.ibm.com> Reviewed-by: Cédric Le Goater <clg@kaod.org> Reviewed-by: Joel Stanley <joel@jms.id.au> Tested-by: Joel Stanley <joel@jms.id.au> Message-id: 20230414220754.1191476-3-ninadpalsule@us.ibm.com
893 lines
27 KiB
C
893 lines
27 KiB
C
/*
|
|
* tpm_tis_common.c - QEMU's TPM TIS interface emulator
|
|
* device agnostic functions
|
|
*
|
|
* 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/irq.h"
|
|
#include "hw/isa/isa.h"
|
|
#include "qapi/error.h"
|
|
#include "qemu/bswap.h"
|
|
#include "qemu/crc-ccitt.h"
|
|
#include "qemu/module.h"
|
|
|
|
#include "hw/acpi/tpm.h"
|
|
#include "hw/pci/pci_ids.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "migration/vmstate.h"
|
|
#include "sysemu/tpm_backend.h"
|
|
#include "sysemu/tpm_util.h"
|
|
#include "tpm_ppi.h"
|
|
#include "trace.h"
|
|
|
|
#include "tpm_tis.h"
|
|
|
|
#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)
|
|
{
|
|
uint8_t locty;
|
|
|
|
locty = (uint8_t)((addr >> TPM_TIS_LOCALITY_SHIFT) & 0x7);
|
|
assert(TPM_TIS_IS_VALID_LOCTY(locty));
|
|
|
|
return locty;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
tpm_util_show_buffer(s->buffer, s->be_buffer_size, "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.
|
|
*/
|
|
void tpm_tis_request_completed(TPMState *s, int ret)
|
|
{
|
|
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;
|
|
|
|
tpm_util_show_buffer(s->buffer, s->be_buffer_size, "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(TPMState *s, 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;
|
|
|
|
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(s, 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(s, addr);
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
if (shift) {
|
|
val >>= shift;
|
|
}
|
|
|
|
trace_tpm_tis_mmio_read(size, addr, val);
|
|
|
|
return val;
|
|
}
|
|
|
|
/*
|
|
* A wrapper read function so that it can be directly called without
|
|
* mmio.
|
|
*/
|
|
uint32_t tpm_tis_read_data(TPMState *s, hwaddr addr, unsigned size)
|
|
{
|
|
return tpm_tis_mmio_read(s, addr, size);
|
|
}
|
|
|
|
/*
|
|
* Calculate current data buffer checksum
|
|
*/
|
|
uint16_t tpm_tis_get_checksum(TPMState *s)
|
|
{
|
|
return bswap16(crc_ccitt(0, s->buffer, s->rw_offset));
|
|
}
|
|
|
|
/*
|
|
* 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; 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:
|
|
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:
|
|
/* 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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* A wrapper write function so that it can be directly called without
|
|
* mmio.
|
|
*/
|
|
void tpm_tis_write_data(TPMState *s, hwaddr addr, uint64_t val, uint32_t size)
|
|
{
|
|
tpm_tis_mmio_write(s, addr, val, size);
|
|
}
|
|
|
|
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
|
|
*/
|
|
enum TPMVersion tpm_tis_get_tpm_version(TPMState *s)
|
|
{
|
|
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.
|
|
*/
|
|
void tpm_tis_reset(TPMState *s)
|
|
{
|
|
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;
|
|
}
|
|
|
|
if (tpm_backend_startup_tpm(s->be_driver, s->be_buffer_size) < 0) {
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/* persistent state handling */
|
|
|
|
int tpm_tis_pre_save(TPMState *s)
|
|
{
|
|
uint8_t locty = s->active_locty;
|
|
|
|
trace_tpm_tis_pre_save(locty, s->rw_offset);
|
|
|
|
if (DEBUG_TIS) {
|
|
tpm_tis_dump_state(s, 0);
|
|
}
|
|
|
|
/*
|
|
* Synchronize with backend completion.
|
|
*/
|
|
tpm_backend_finish_sync(s->be_driver);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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(),
|
|
}
|
|
};
|
|
|