qemu-e2k/hw/tpm/tpm_tis_i2c.c
Richard Henderson 5e6aceb2dd hw/tpm: Constify VMState
Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
Message-Id: <20231221031652.119827-58-richard.henderson@linaro.org>
2023-12-30 07:38:06 +11:00

572 lines
17 KiB
C

/*
* tpm_tis_i2c.c - QEMU's TPM TIS I2C Device
*
* Copyright (c) 2023 IBM Corporation
*
* Authors:
* Ninad Palsule <ninad@linux.ibm.com>
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*
* TPM I2C implementation follows TCG TPM I2c Interface specification,
* Family 2.0, Level 00, Revision 1.00
*
* TPM TIS for TPM 2 implementation following TCG PC Client Platform
* TPM Profile (PTP) Specification, Family 2.0, Revision 00.43
*
*/
#include "qemu/osdep.h"
#include "hw/i2c/i2c.h"
#include "hw/sysbus.h"
#include "hw/acpi/tpm.h"
#include "migration/vmstate.h"
#include "tpm_prop.h"
#include "qemu/log.h"
#include "trace.h"
#include "tpm_tis.h"
/* Operations */
#define OP_SEND 1
#define OP_RECV 2
/* Is locality valid */
#define TPM_TIS_I2C_IS_VALID_LOCTY(x) TPM_TIS_IS_VALID_LOCTY(x)
typedef struct TPMStateI2C {
/*< private >*/
I2CSlave parent_obj;
uint8_t offset; /* offset into data[] */
uint8_t operation; /* OP_SEND & OP_RECV */
uint8_t data[5]; /* Data */
/* i2c registers */
uint8_t loc_sel; /* Current locality */
uint8_t csum_enable; /* Is checksum enabled */
/* Derived from the above */
const char *reg_name; /* Register name */
uint32_t tis_addr; /* Converted tis address including locty */
/*< public >*/
TPMState state; /* not a QOM object */
} TPMStateI2C;
DECLARE_INSTANCE_CHECKER(TPMStateI2C, TPM_TIS_I2C,
TYPE_TPM_TIS_I2C)
/* Prototype */
static inline void tpm_tis_i2c_to_tis_reg(TPMStateI2C *i2cst, uint8_t i2c_reg);
/* Register map */
typedef struct regMap {
uint8_t i2c_reg; /* I2C register */
uint16_t tis_reg; /* TIS register */
const char *reg_name; /* Register name */
} I2CRegMap;
/*
* The register values in the common code is different than the latest
* register numbers as per the spec hence add the conversion map
*/
static const I2CRegMap tpm_tis_reg_map[] = {
/*
* These registers are sent to TIS layer. The register with UNKNOWN
* mapping are not sent to TIS layer and handled in I2c layer.
* NOTE: Adding frequently used registers at the start
*/
{ TPM_I2C_REG_DATA_FIFO, TPM_TIS_REG_DATA_FIFO, "FIFO", },
{ TPM_I2C_REG_STS, TPM_TIS_REG_STS, "STS", },
{ TPM_I2C_REG_DATA_CSUM_GET, TPM_I2C_REG_UNKNOWN, "CSUM_GET", },
{ TPM_I2C_REG_LOC_SEL, TPM_I2C_REG_UNKNOWN, "LOC_SEL", },
{ TPM_I2C_REG_ACCESS, TPM_TIS_REG_ACCESS, "ACCESS", },
{ TPM_I2C_REG_INT_ENABLE, TPM_TIS_REG_INT_ENABLE, "INTR_ENABLE",},
{ TPM_I2C_REG_INT_CAPABILITY, TPM_I2C_REG_UNKNOWN, "INTR_CAP", },
{ TPM_I2C_REG_INTF_CAPABILITY, TPM_TIS_REG_INTF_CAPABILITY, "INTF_CAP", },
{ TPM_I2C_REG_DID_VID, TPM_TIS_REG_DID_VID, "DID_VID", },
{ TPM_I2C_REG_RID, TPM_TIS_REG_RID, "RID", },
{ TPM_I2C_REG_I2C_DEV_ADDRESS, TPM_I2C_REG_UNKNOWN, "DEV_ADDRESS",},
{ TPM_I2C_REG_DATA_CSUM_ENABLE, TPM_I2C_REG_UNKNOWN, "CSUM_ENABLE",},
};
static int tpm_tis_i2c_pre_save(void *opaque)
{
TPMStateI2C *i2cst = opaque;
return tpm_tis_pre_save(&i2cst->state);
}
static int tpm_tis_i2c_post_load(void *opaque, int version_id)
{
TPMStateI2C *i2cst = opaque;
if (i2cst->offset >= 1) {
tpm_tis_i2c_to_tis_reg(i2cst, i2cst->data[0]);
}
return 0;
}
static const VMStateDescription vmstate_tpm_tis_i2c = {
.name = "tpm-tis-i2c",
.version_id = 0,
.pre_save = tpm_tis_i2c_pre_save,
.post_load = tpm_tis_i2c_post_load,
.fields = (const VMStateField[]) {
VMSTATE_BUFFER(state.buffer, TPMStateI2C),
VMSTATE_UINT16(state.rw_offset, TPMStateI2C),
VMSTATE_UINT8(state.active_locty, TPMStateI2C),
VMSTATE_UINT8(state.aborting_locty, TPMStateI2C),
VMSTATE_UINT8(state.next_locty, TPMStateI2C),
VMSTATE_STRUCT_ARRAY(state.loc, TPMStateI2C, TPM_TIS_NUM_LOCALITIES, 0,
vmstate_locty, TPMLocality),
/* i2c specifics */
VMSTATE_UINT8(offset, TPMStateI2C),
VMSTATE_UINT8(operation, TPMStateI2C),
VMSTATE_BUFFER(data, TPMStateI2C),
VMSTATE_UINT8(loc_sel, TPMStateI2C),
VMSTATE_UINT8(csum_enable, TPMStateI2C),
VMSTATE_END_OF_LIST()
}
};
/*
* Set data value. The i2cst->offset is not updated as called in
* the read path.
*/
static void tpm_tis_i2c_set_data(TPMStateI2C *i2cst, uint32_t data)
{
i2cst->data[1] = data;
i2cst->data[2] = data >> 8;
i2cst->data[3] = data >> 16;
i2cst->data[4] = data >> 24;
}
/*
* Generate interface capability based on what is returned by TIS and what is
* expected by I2C. Save the capability in the data array overwriting the TIS
* capability.
*/
static uint32_t tpm_tis_i2c_interface_capability(TPMStateI2C *i2cst,
uint32_t tis_cap)
{
uint32_t i2c_cap;
/* Now generate i2c capability */
i2c_cap = (TPM_I2C_CAP_INTERFACE_TYPE |
TPM_I2C_CAP_INTERFACE_VER |
TPM_I2C_CAP_TPM2_FAMILY |
TPM_I2C_CAP_LOCALITY_CAP |
TPM_I2C_CAP_BUS_SPEED |
TPM_I2C_CAP_DEV_ADDR_CHANGE);
/* Now check the TIS and set some capabilities */
/* Static burst count set */
if (tis_cap & TPM_TIS_CAP_BURST_COUNT_STATIC) {
i2c_cap |= TPM_I2C_CAP_BURST_COUNT_STATIC;
}
return i2c_cap;
}
/* Convert I2C register to TIS address and returns the name of the register */
static inline void tpm_tis_i2c_to_tis_reg(TPMStateI2C *i2cst, uint8_t i2c_reg)
{
const I2CRegMap *reg_map;
int i;
i2cst->tis_addr = 0xffffffff;
/* Special case for the STS register. */
if (i2c_reg >= TPM_I2C_REG_STS && i2c_reg <= TPM_I2C_REG_STS + 3) {
i2c_reg = TPM_I2C_REG_STS;
}
for (i = 0; i < ARRAY_SIZE(tpm_tis_reg_map); i++) {
reg_map = &tpm_tis_reg_map[i];
if (reg_map->i2c_reg == i2c_reg) {
i2cst->reg_name = reg_map->reg_name;
i2cst->tis_addr = reg_map->tis_reg;
/* Include the locality in the address. */
assert(TPM_TIS_I2C_IS_VALID_LOCTY(i2cst->loc_sel));
i2cst->tis_addr += (i2cst->loc_sel << TPM_TIS_LOCALITY_SHIFT);
break;
}
}
}
/* Clear some fields from the structure. */
static inline void tpm_tis_i2c_clear_data(TPMStateI2C *i2cst)
{
/* Clear operation and offset */
i2cst->operation = 0;
i2cst->offset = 0;
i2cst->tis_addr = 0xffffffff;
i2cst->reg_name = NULL;
memset(i2cst->data, 0, sizeof(i2cst->data));
return;
}
/* Send data to TPM */
static inline void tpm_tis_i2c_tpm_send(TPMStateI2C *i2cst)
{
uint32_t data;
size_t offset = 0;
uint32_t sz = 4;
if ((i2cst->operation == OP_SEND) && (i2cst->offset > 1)) {
switch (i2cst->data[0]) {
case TPM_I2C_REG_DATA_CSUM_ENABLE:
/*
* Checksum is not handled by TIS code hence we will consume the
* register here.
*/
i2cst->csum_enable = i2cst->data[1] & TPM_DATA_CSUM_ENABLED;
break;
case TPM_I2C_REG_DATA_FIFO:
/* Handled in the main i2c_send function */
break;
case TPM_I2C_REG_LOC_SEL:
/*
* This register is not handled by TIS so save the locality
* locally
*/
if (TPM_TIS_I2C_IS_VALID_LOCTY(i2cst->data[1])) {
i2cst->loc_sel = i2cst->data[1];
}
break;
default:
/* We handle non-FIFO here */
/* Index 0 is a register. Convert byte stream to uint32_t */
data = i2cst->data[1];
data |= i2cst->data[2] << 8;
data |= i2cst->data[3] << 16;
data |= i2cst->data[4] << 24;
/* Add register specific masking */
switch (i2cst->data[0]) {
case TPM_I2C_REG_INT_ENABLE:
data &= TPM_I2C_INT_ENABLE_MASK;
break;
case TPM_I2C_REG_STS ... TPM_I2C_REG_STS + 3:
/*
* STS register has 4 bytes data.
* As per the specs following writes must be allowed.
* - From base address 1 to 4 bytes are allowed.
* - Single byte write to first or last byte must
* be allowed.
*/
offset = i2cst->data[0] - TPM_I2C_REG_STS;
if (offset > 0) {
sz = 1;
}
data &= (TPM_I2C_STS_WRITE_MASK >> (offset * 8));
break;
}
tpm_tis_write_data(&i2cst->state, i2cst->tis_addr + offset, data,
sz);
break;
}
tpm_tis_i2c_clear_data(i2cst);
}
return;
}
/* Callback from TPM to indicate that response is copied */
static void tpm_tis_i2c_request_completed(TPMIf *ti, int ret)
{
TPMStateI2C *i2cst = TPM_TIS_I2C(ti);
TPMState *s = &i2cst->state;
/* Inform the common code. */
tpm_tis_request_completed(s, ret);
}
static enum TPMVersion tpm_tis_i2c_get_tpm_version(TPMIf *ti)
{
TPMStateI2C *i2cst = TPM_TIS_I2C(ti);
TPMState *s = &i2cst->state;
return tpm_tis_get_tpm_version(s);
}
static int tpm_tis_i2c_event(I2CSlave *i2c, enum i2c_event event)
{
TPMStateI2C *i2cst = TPM_TIS_I2C(i2c);
int ret = 0;
switch (event) {
case I2C_START_RECV:
trace_tpm_tis_i2c_event("START_RECV");
break;
case I2C_START_SEND:
trace_tpm_tis_i2c_event("START_SEND");
tpm_tis_i2c_clear_data(i2cst);
break;
case I2C_FINISH:
trace_tpm_tis_i2c_event("FINISH");
if (i2cst->operation == OP_SEND) {
tpm_tis_i2c_tpm_send(i2cst);
} else {
tpm_tis_i2c_clear_data(i2cst);
}
break;
default:
break;
}
return ret;
}
/*
* If data is for FIFO then it is received from tpm_tis_common buffer
* otherwise it will be handled using single call to common code and
* cached in the local buffer.
*/
static uint8_t tpm_tis_i2c_recv(I2CSlave *i2c)
{
int ret = 0;
uint32_t data_read;
TPMStateI2C *i2cst = TPM_TIS_I2C(i2c);
TPMState *s = &i2cst->state;
uint16_t i2c_reg = i2cst->data[0];
size_t offset;
if (i2cst->operation == OP_RECV) {
/* Do not cache FIFO data. */
if (i2cst->data[0] == TPM_I2C_REG_DATA_FIFO) {
data_read = tpm_tis_read_data(s, i2cst->tis_addr, 1);
ret = (data_read & 0xff);
} else if (i2cst->offset < sizeof(i2cst->data)) {
ret = i2cst->data[i2cst->offset++];
}
} else if ((i2cst->operation == OP_SEND) && (i2cst->offset < 2)) {
/* First receive call after send */
i2cst->operation = OP_RECV;
switch (i2c_reg) {
case TPM_I2C_REG_LOC_SEL:
/* Location selection register is managed by i2c */
tpm_tis_i2c_set_data(i2cst, i2cst->loc_sel);
break;
case TPM_I2C_REG_DATA_FIFO:
/* FIFO data is directly read from TPM TIS */
data_read = tpm_tis_read_data(s, i2cst->tis_addr, 1);
tpm_tis_i2c_set_data(i2cst, (data_read & 0xff));
break;
case TPM_I2C_REG_DATA_CSUM_ENABLE:
tpm_tis_i2c_set_data(i2cst, i2cst->csum_enable);
break;
case TPM_I2C_REG_INT_CAPABILITY:
/*
* Interrupt is not supported in the linux kernel hence we cannot
* test this model with interrupts.
*/
tpm_tis_i2c_set_data(i2cst, TPM_I2C_INT_ENABLE_MASK);
break;
case TPM_I2C_REG_DATA_CSUM_GET:
/*
* Checksum registers are not supported by common code hence
* call a common code to get the checksum.
*/
data_read = tpm_tis_get_checksum(s);
/* Save the byte stream in data field */
tpm_tis_i2c_set_data(i2cst, data_read);
break;
default:
data_read = tpm_tis_read_data(s, i2cst->tis_addr, 4);
switch (i2c_reg) {
case TPM_I2C_REG_INTF_CAPABILITY:
/* Prepare the capabilities as per I2C interface */
data_read = tpm_tis_i2c_interface_capability(i2cst,
data_read);
break;
case TPM_I2C_REG_STS ... TPM_I2C_REG_STS + 3:
offset = i2c_reg - TPM_I2C_REG_STS;
/*
* As per specs, STS bit 31:26 are reserved and must
* be set to 0
*/
data_read &= TPM_I2C_STS_READ_MASK;
/*
* STS register has 4 bytes data.
* As per the specs following reads must be allowed.
* - From base address 1 to 4 bytes are allowed.
* - Last byte must be allowed to read as a single byte
* - Second and third byte must be allowed to read as two
* two bytes.
*/
data_read >>= (offset * 8);
break;
}
/* Save byte stream in data[] */
tpm_tis_i2c_set_data(i2cst, data_read);
break;
}
/* Return first byte with this call */
i2cst->offset = 1; /* keep the register value intact for debug */
ret = i2cst->data[i2cst->offset++];
} else {
i2cst->operation = OP_RECV;
}
trace_tpm_tis_i2c_recv(ret);
return ret;
}
/*
* Send function only remembers data in the buffer and then calls
* TPM TIS common code during FINISH event.
*/
static int tpm_tis_i2c_send(I2CSlave *i2c, uint8_t data)
{
TPMStateI2C *i2cst = TPM_TIS_I2C(i2c);
/* Reject non-supported registers. */
if (i2cst->offset == 0) {
/* Convert I2C register to TIS register */
tpm_tis_i2c_to_tis_reg(i2cst, data);
if (i2cst->tis_addr == 0xffffffff) {
return 0xffffffff;
}
trace_tpm_tis_i2c_send_reg(i2cst->reg_name, data);
/* We do not support device address change */
if (data == TPM_I2C_REG_I2C_DEV_ADDRESS) {
qemu_log_mask(LOG_UNIMP, "%s: Device address change "
"is not supported.\n", __func__);
return 0xffffffff;
}
} else {
trace_tpm_tis_i2c_send(data);
}
if (i2cst->offset < sizeof(i2cst->data)) {
i2cst->operation = OP_SEND;
/*
* In two cases, we save values in the local buffer.
* 1) The first value is always a register.
* 2) In case of non-FIFO multibyte registers, TIS expects full
* register value hence I2C layer cache the register value and send
* to TIS during FINISH event.
*/
if ((i2cst->offset == 0) ||
(i2cst->data[0] != TPM_I2C_REG_DATA_FIFO)) {
i2cst->data[i2cst->offset++] = data;
} else {
/*
* The TIS can process FIFO data one byte at a time hence the FIFO
* data is sent to TIS directly.
*/
tpm_tis_write_data(&i2cst->state, i2cst->tis_addr, data, 1);
}
return 0;
}
/* Return non-zero to indicate NAK */
return 1;
}
static Property tpm_tis_i2c_properties[] = {
DEFINE_PROP_TPMBE("tpmdev", TPMStateI2C, state.be_driver),
DEFINE_PROP_END_OF_LIST(),
};
static void tpm_tis_i2c_realizefn(DeviceState *dev, Error **errp)
{
TPMStateI2C *i2cst = TPM_TIS_I2C(dev);
TPMState *s = &i2cst->state;
if (!tpm_find()) {
error_setg(errp, "at most one TPM device is permitted");
return;
}
/*
* Get the backend pointer. It is not initialized properly during
* device_class_set_props
*/
s->be_driver = qemu_find_tpm_be("tpm0");
if (!s->be_driver) {
error_setg(errp, "'tpmdev' property is required");
return;
}
}
static void tpm_tis_i2c_reset(DeviceState *dev)
{
TPMStateI2C *i2cst = TPM_TIS_I2C(dev);
TPMState *s = &i2cst->state;
tpm_tis_i2c_clear_data(i2cst);
i2cst->csum_enable = 0;
i2cst->loc_sel = 0x00;
return tpm_tis_reset(s);
}
static void tpm_tis_i2c_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
TPMIfClass *tc = TPM_IF_CLASS(klass);
dc->realize = tpm_tis_i2c_realizefn;
dc->reset = tpm_tis_i2c_reset;
dc->vmsd = &vmstate_tpm_tis_i2c;
device_class_set_props(dc, tpm_tis_i2c_properties);
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
k->event = tpm_tis_i2c_event;
k->recv = tpm_tis_i2c_recv;
k->send = tpm_tis_i2c_send;
tc->model = TPM_MODEL_TPM_TIS;
tc->request_completed = tpm_tis_i2c_request_completed;
tc->get_version = tpm_tis_i2c_get_tpm_version;
}
static const TypeInfo tpm_tis_i2c_info = {
.name = TYPE_TPM_TIS_I2C,
.parent = TYPE_I2C_SLAVE,
.instance_size = sizeof(TPMStateI2C),
.class_init = tpm_tis_i2c_class_init,
.interfaces = (InterfaceInfo[]) {
{ TYPE_TPM_IF },
{ }
}
};
static void tpm_tis_i2c_register_types(void)
{
type_register_static(&tpm_tis_i2c_info);
}
type_init(tpm_tis_i2c_register_types)