431 lines
12 KiB
C
431 lines
12 KiB
C
/*
|
|
* QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator
|
|
*
|
|
* PAPR Virtual TPM
|
|
*
|
|
* Copyright (c) 2015, 2017, 2019 IBM Corporation.
|
|
*
|
|
* Authors:
|
|
* Stefan Berger <stefanb@linux.vnet.ibm.com>
|
|
*
|
|
* This code is licensed under the GPL version 2 or later. See the
|
|
* COPYING file in the top-level directory.
|
|
*
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/error-report.h"
|
|
#include "qapi/error.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "migration/vmstate.h"
|
|
|
|
#include "sysemu/tpm_backend.h"
|
|
#include "sysemu/tpm_util.h"
|
|
#include "tpm_prop.h"
|
|
|
|
#include "hw/ppc/spapr.h"
|
|
#include "hw/ppc/spapr_vio.h"
|
|
#include "trace.h"
|
|
#include "qom/object.h"
|
|
|
|
#define DEBUG_SPAPR 0
|
|
|
|
typedef struct SpaprTpmState SpaprTpmState;
|
|
DECLARE_INSTANCE_CHECKER(SpaprTpmState, VIO_SPAPR_VTPM,
|
|
TYPE_TPM_SPAPR)
|
|
|
|
typedef struct TpmCrq {
|
|
uint8_t valid; /* 0x80: cmd; 0xc0: init crq */
|
|
/* 0x81-0x83: CRQ message response */
|
|
uint8_t msg; /* see below */
|
|
uint16_t len; /* len of TPM request; len of TPM response */
|
|
uint32_t data; /* rtce_dma_handle when sending TPM request */
|
|
uint64_t reserved;
|
|
} TpmCrq;
|
|
|
|
#define SPAPR_VTPM_VALID_INIT_CRQ_COMMAND 0xC0
|
|
#define SPAPR_VTPM_VALID_COMMAND 0x80
|
|
#define SPAPR_VTPM_MSG_RESULT 0x80
|
|
|
|
/* msg types for valid = SPAPR_VTPM_VALID_INIT_CRQ */
|
|
#define SPAPR_VTPM_INIT_CRQ_RESULT 0x1
|
|
#define SPAPR_VTPM_INIT_CRQ_COMPLETE_RESULT 0x2
|
|
|
|
/* msg types for valid = SPAPR_VTPM_VALID_CMD */
|
|
#define SPAPR_VTPM_GET_VERSION 0x1
|
|
#define SPAPR_VTPM_TPM_COMMAND 0x2
|
|
#define SPAPR_VTPM_GET_RTCE_BUFFER_SIZE 0x3
|
|
#define SPAPR_VTPM_PREPARE_TO_SUSPEND 0x4
|
|
|
|
/* response error messages */
|
|
#define SPAPR_VTPM_VTPM_ERROR 0xff
|
|
|
|
/* error codes */
|
|
#define SPAPR_VTPM_ERR_COPY_IN_FAILED 0x3
|
|
#define SPAPR_VTPM_ERR_COPY_OUT_FAILED 0x4
|
|
|
|
#define TPM_SPAPR_BUFFER_MAX 4096
|
|
|
|
struct SpaprTpmState {
|
|
SpaprVioDevice vdev;
|
|
|
|
TpmCrq crq; /* track single TPM command */
|
|
|
|
uint8_t state;
|
|
#define SPAPR_VTPM_STATE_NONE 0
|
|
#define SPAPR_VTPM_STATE_EXECUTION 1
|
|
#define SPAPR_VTPM_STATE_COMPLETION 2
|
|
|
|
unsigned char *buffer;
|
|
|
|
uint32_t numbytes; /* number of bytes to deliver on resume */
|
|
|
|
TPMBackendCmd cmd;
|
|
|
|
TPMBackend *be_driver;
|
|
TPMVersion be_tpm_version;
|
|
|
|
size_t be_buffer_size;
|
|
};
|
|
|
|
/*
|
|
* Send a request to the TPM.
|
|
*/
|
|
static void tpm_spapr_tpm_send(SpaprTpmState *s)
|
|
{
|
|
tpm_util_show_buffer(s->buffer, s->be_buffer_size, "To TPM");
|
|
|
|
s->state = SPAPR_VTPM_STATE_EXECUTION;
|
|
s->cmd = (TPMBackendCmd) {
|
|
.locty = 0,
|
|
.in = s->buffer,
|
|
.in_len = MIN(tpm_cmd_get_size(s->buffer), s->be_buffer_size),
|
|
.out = s->buffer,
|
|
.out_len = s->be_buffer_size,
|
|
};
|
|
|
|
tpm_backend_deliver_request(s->be_driver, &s->cmd);
|
|
}
|
|
|
|
static int tpm_spapr_process_cmd(SpaprTpmState *s, uint64_t dataptr)
|
|
{
|
|
long rc;
|
|
|
|
/* a max. of be_buffer_size bytes can be transported */
|
|
rc = spapr_vio_dma_read(&s->vdev, dataptr,
|
|
s->buffer, s->be_buffer_size);
|
|
if (rc) {
|
|
error_report("tpm_spapr_got_payload: DMA read failure");
|
|
}
|
|
/* let vTPM handle any malformed request */
|
|
tpm_spapr_tpm_send(s);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static inline int spapr_tpm_send_crq(struct SpaprVioDevice *dev, TpmCrq *crq)
|
|
{
|
|
return spapr_vio_send_crq(dev, (uint8_t *)crq);
|
|
}
|
|
|
|
static int tpm_spapr_do_crq(struct SpaprVioDevice *dev, uint8_t *crq_data)
|
|
{
|
|
SpaprTpmState *s = VIO_SPAPR_VTPM(dev);
|
|
TpmCrq local_crq;
|
|
TpmCrq *crq = &s->crq; /* requests only */
|
|
int rc;
|
|
uint8_t valid = crq_data[0];
|
|
uint8_t msg = crq_data[1];
|
|
|
|
trace_tpm_spapr_do_crq(valid, msg);
|
|
|
|
switch (valid) {
|
|
case SPAPR_VTPM_VALID_INIT_CRQ_COMMAND: /* Init command/response */
|
|
|
|
/* Respond to initialization request */
|
|
switch (msg) {
|
|
case SPAPR_VTPM_INIT_CRQ_RESULT:
|
|
trace_tpm_spapr_do_crq_crq_result();
|
|
memset(&local_crq, 0, sizeof(local_crq));
|
|
local_crq.valid = SPAPR_VTPM_VALID_INIT_CRQ_COMMAND;
|
|
local_crq.msg = SPAPR_VTPM_INIT_CRQ_RESULT;
|
|
spapr_tpm_send_crq(dev, &local_crq);
|
|
break;
|
|
|
|
case SPAPR_VTPM_INIT_CRQ_COMPLETE_RESULT:
|
|
trace_tpm_spapr_do_crq_crq_complete_result();
|
|
memset(&local_crq, 0, sizeof(local_crq));
|
|
local_crq.valid = SPAPR_VTPM_VALID_INIT_CRQ_COMMAND;
|
|
local_crq.msg = SPAPR_VTPM_INIT_CRQ_COMPLETE_RESULT;
|
|
spapr_tpm_send_crq(dev, &local_crq);
|
|
break;
|
|
}
|
|
|
|
break;
|
|
case SPAPR_VTPM_VALID_COMMAND: /* Payloads */
|
|
switch (msg) {
|
|
case SPAPR_VTPM_TPM_COMMAND:
|
|
trace_tpm_spapr_do_crq_tpm_command();
|
|
if (s->state == SPAPR_VTPM_STATE_EXECUTION) {
|
|
return H_BUSY;
|
|
}
|
|
memcpy(crq, crq_data, sizeof(*crq));
|
|
|
|
rc = tpm_spapr_process_cmd(s, be32_to_cpu(crq->data));
|
|
|
|
if (rc == H_SUCCESS) {
|
|
crq->valid = be16_to_cpu(0);
|
|
} else {
|
|
local_crq.valid = SPAPR_VTPM_MSG_RESULT;
|
|
local_crq.msg = SPAPR_VTPM_VTPM_ERROR;
|
|
local_crq.len = cpu_to_be16(0);
|
|
local_crq.data = cpu_to_be32(SPAPR_VTPM_ERR_COPY_IN_FAILED);
|
|
spapr_tpm_send_crq(dev, &local_crq);
|
|
}
|
|
break;
|
|
|
|
case SPAPR_VTPM_GET_RTCE_BUFFER_SIZE:
|
|
trace_tpm_spapr_do_crq_tpm_get_rtce_buffer_size(s->be_buffer_size);
|
|
local_crq.valid = SPAPR_VTPM_VALID_COMMAND;
|
|
local_crq.msg = SPAPR_VTPM_GET_RTCE_BUFFER_SIZE |
|
|
SPAPR_VTPM_MSG_RESULT;
|
|
local_crq.len = cpu_to_be16(s->be_buffer_size);
|
|
spapr_tpm_send_crq(dev, &local_crq);
|
|
break;
|
|
|
|
case SPAPR_VTPM_GET_VERSION:
|
|
local_crq.valid = SPAPR_VTPM_VALID_COMMAND;
|
|
local_crq.msg = SPAPR_VTPM_GET_VERSION | SPAPR_VTPM_MSG_RESULT;
|
|
local_crq.len = cpu_to_be16(0);
|
|
switch (s->be_tpm_version) {
|
|
case TPM_VERSION_1_2:
|
|
local_crq.data = cpu_to_be32(1);
|
|
break;
|
|
case TPM_VERSION_2_0:
|
|
local_crq.data = cpu_to_be32(2);
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
trace_tpm_spapr_do_crq_get_version(be32_to_cpu(local_crq.data));
|
|
spapr_tpm_send_crq(dev, &local_crq);
|
|
break;
|
|
|
|
case SPAPR_VTPM_PREPARE_TO_SUSPEND:
|
|
trace_tpm_spapr_do_crq_prepare_to_suspend();
|
|
local_crq.valid = SPAPR_VTPM_VALID_COMMAND;
|
|
local_crq.msg = SPAPR_VTPM_PREPARE_TO_SUSPEND |
|
|
SPAPR_VTPM_MSG_RESULT;
|
|
spapr_tpm_send_crq(dev, &local_crq);
|
|
break;
|
|
|
|
default:
|
|
trace_tpm_spapr_do_crq_unknown_msg_type(crq->msg);
|
|
}
|
|
break;
|
|
default:
|
|
trace_tpm_spapr_do_crq_unknown_crq(valid, msg);
|
|
};
|
|
|
|
return H_SUCCESS;
|
|
}
|
|
|
|
static void tpm_spapr_request_completed(TPMIf *ti, int ret)
|
|
{
|
|
SpaprTpmState *s = VIO_SPAPR_VTPM(ti);
|
|
TpmCrq *crq = &s->crq;
|
|
uint32_t len;
|
|
int rc;
|
|
|
|
s->state = SPAPR_VTPM_STATE_COMPLETION;
|
|
|
|
/* a max. of be_buffer_size bytes can be transported */
|
|
len = MIN(tpm_cmd_get_size(s->buffer), s->be_buffer_size);
|
|
|
|
if (runstate_check(RUN_STATE_FINISH_MIGRATE)) {
|
|
trace_tpm_spapr_caught_response(len);
|
|
/* defer delivery of response until .post_load */
|
|
s->numbytes = len;
|
|
return;
|
|
}
|
|
|
|
rc = spapr_vio_dma_write(&s->vdev, be32_to_cpu(crq->data),
|
|
s->buffer, len);
|
|
|
|
tpm_util_show_buffer(s->buffer, len, "From TPM");
|
|
|
|
crq->valid = SPAPR_VTPM_MSG_RESULT;
|
|
if (rc == H_SUCCESS) {
|
|
crq->msg = SPAPR_VTPM_TPM_COMMAND | SPAPR_VTPM_MSG_RESULT;
|
|
crq->len = cpu_to_be16(len);
|
|
} else {
|
|
error_report("%s: DMA write failure", __func__);
|
|
crq->msg = SPAPR_VTPM_VTPM_ERROR;
|
|
crq->len = cpu_to_be16(0);
|
|
crq->data = cpu_to_be32(SPAPR_VTPM_ERR_COPY_OUT_FAILED);
|
|
}
|
|
|
|
rc = spapr_tpm_send_crq(&s->vdev, crq);
|
|
if (rc) {
|
|
error_report("%s: Error sending response", __func__);
|
|
}
|
|
}
|
|
|
|
static int tpm_spapr_do_startup_tpm(SpaprTpmState *s, size_t buffersize)
|
|
{
|
|
return tpm_backend_startup_tpm(s->be_driver, buffersize);
|
|
}
|
|
|
|
static const char *tpm_spapr_get_dt_compatible(SpaprVioDevice *dev)
|
|
{
|
|
SpaprTpmState *s = VIO_SPAPR_VTPM(dev);
|
|
|
|
switch (s->be_tpm_version) {
|
|
case TPM_VERSION_1_2:
|
|
return "IBM,vtpm";
|
|
case TPM_VERSION_2_0:
|
|
return "IBM,vtpm20";
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
}
|
|
|
|
static void tpm_spapr_reset(SpaprVioDevice *dev)
|
|
{
|
|
SpaprTpmState *s = VIO_SPAPR_VTPM(dev);
|
|
|
|
s->state = SPAPR_VTPM_STATE_NONE;
|
|
s->numbytes = 0;
|
|
|
|
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_SPAPR_BUFFER_MAX);
|
|
|
|
tpm_backend_reset(s->be_driver);
|
|
|
|
if (tpm_spapr_do_startup_tpm(s, s->be_buffer_size) < 0) {
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static enum TPMVersion tpm_spapr_get_version(TPMIf *ti)
|
|
{
|
|
SpaprTpmState *s = VIO_SPAPR_VTPM(ti);
|
|
|
|
if (tpm_backend_had_startup_error(s->be_driver)) {
|
|
return TPM_VERSION_UNSPEC;
|
|
}
|
|
|
|
return tpm_backend_get_tpm_version(s->be_driver);
|
|
}
|
|
|
|
/* persistent state handling */
|
|
|
|
static int tpm_spapr_pre_save(void *opaque)
|
|
{
|
|
SpaprTpmState *s = opaque;
|
|
|
|
tpm_backend_finish_sync(s->be_driver);
|
|
/*
|
|
* we cannot deliver the results to the VM since DMA would touch VM memory
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tpm_spapr_post_load(void *opaque, int version_id)
|
|
{
|
|
SpaprTpmState *s = opaque;
|
|
|
|
if (s->numbytes) {
|
|
trace_tpm_spapr_post_load();
|
|
/* deliver the results to the VM via DMA */
|
|
tpm_spapr_request_completed(TPM_IF(s), 0);
|
|
s->numbytes = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const VMStateDescription vmstate_spapr_vtpm = {
|
|
.name = "tpm-spapr",
|
|
.pre_save = tpm_spapr_pre_save,
|
|
.post_load = tpm_spapr_post_load,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_SPAPR_VIO(vdev, SpaprTpmState),
|
|
|
|
VMSTATE_UINT8(state, SpaprTpmState),
|
|
VMSTATE_UINT32(numbytes, SpaprTpmState),
|
|
VMSTATE_VBUFFER_UINT32(buffer, SpaprTpmState, 0, NULL, numbytes),
|
|
/* remember DMA address */
|
|
VMSTATE_UINT32(crq.data, SpaprTpmState),
|
|
VMSTATE_END_OF_LIST(),
|
|
}
|
|
};
|
|
|
|
static Property tpm_spapr_properties[] = {
|
|
DEFINE_SPAPR_PROPERTIES(SpaprTpmState, vdev),
|
|
DEFINE_PROP_TPMBE("tpmdev", SpaprTpmState, be_driver),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void tpm_spapr_realizefn(SpaprVioDevice *dev, Error **errp)
|
|
{
|
|
SpaprTpmState *s = VIO_SPAPR_VTPM(dev);
|
|
|
|
if (!tpm_find()) {
|
|
error_setg(errp, "at most one TPM device is permitted");
|
|
return;
|
|
}
|
|
|
|
dev->crq.SendFunc = tpm_spapr_do_crq;
|
|
|
|
if (!s->be_driver) {
|
|
error_setg(errp, "'tpmdev' property is required");
|
|
return;
|
|
}
|
|
s->buffer = g_malloc(TPM_SPAPR_BUFFER_MAX);
|
|
}
|
|
|
|
static void tpm_spapr_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
SpaprVioDeviceClass *k = VIO_SPAPR_DEVICE_CLASS(klass);
|
|
TPMIfClass *tc = TPM_IF_CLASS(klass);
|
|
|
|
k->realize = tpm_spapr_realizefn;
|
|
k->reset = tpm_spapr_reset;
|
|
k->dt_name = "vtpm";
|
|
k->dt_type = "IBM,vtpm";
|
|
k->get_dt_compatible = tpm_spapr_get_dt_compatible;
|
|
k->signal_mask = 0x00000001;
|
|
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
|
|
device_class_set_props(dc, tpm_spapr_properties);
|
|
k->rtce_window_size = 0x10000000;
|
|
dc->vmsd = &vmstate_spapr_vtpm;
|
|
|
|
tc->model = TPM_MODEL_TPM_SPAPR;
|
|
tc->get_version = tpm_spapr_get_version;
|
|
tc->request_completed = tpm_spapr_request_completed;
|
|
}
|
|
|
|
static const TypeInfo tpm_spapr_info = {
|
|
.name = TYPE_TPM_SPAPR,
|
|
.parent = TYPE_VIO_SPAPR_DEVICE,
|
|
.instance_size = sizeof(SpaprTpmState),
|
|
.class_init = tpm_spapr_class_init,
|
|
.interfaces = (InterfaceInfo[]) {
|
|
{ TYPE_TPM_IF },
|
|
{ }
|
|
}
|
|
};
|
|
|
|
static void tpm_spapr_register_types(void)
|
|
{
|
|
type_register_static(&tpm_spapr_info);
|
|
}
|
|
|
|
type_init(tpm_spapr_register_types)
|