Browse Source
Implement support for TPM on ppc64 by implementing the vTPM CRQ interface as a frontend. It can use the tpm_emulator driver backend with the external swtpm. The Linux vTPM driver for ppc64 works with this emulation. This TPM emulator also handles the TPM 2 case. Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> Reviewed-by: David Gibson <david@gibson.dropbear.id.au> Message-Id: <20200121152935.649898-4-stefanb@linux.ibm.com> [dwg: Use device_class_set_props(), tweak Kconfig] Signed-off-by: David Gibson <david@gibson.dropbear.id.au>pull/89/head
committed by
David Gibson
7 changed files with 423 additions and 4 deletions
@ -0,0 +1,379 @@ |
|||
/*
|
|||
* 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 "tpm_int.h" |
|||
#include "tpm_util.h" |
|||
|
|||
#include "hw/ppc/spapr.h" |
|||
#include "hw/ppc/spapr_vio.h" |
|||
#include "trace.h" |
|||
|
|||
#define DEBUG_SPAPR 0 |
|||
|
|||
#define VIO_SPAPR_VTPM(obj) \ |
|||
OBJECT_CHECK(SpaprTpmState, (obj), 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 |
|||
|
|||
typedef struct { |
|||
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; |
|||
|
|||
TPMBackendCmd cmd; |
|||
|
|||
TPMBackend *be_driver; |
|||
TPMVersion be_tpm_version; |
|||
|
|||
size_t be_buffer_size; |
|||
} SpaprTpmState; |
|||
|
|||
/*
|
|||
* Send a request to the TPM. |
|||
*/ |
|||
static void tpm_spapr_tpm_send(SpaprTpmState *s) |
|||
{ |
|||
if (trace_event_get_state_backends(TRACE_TPM_SPAPR_SHOW_BUFFER)) { |
|||
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); |
|||
rc = spapr_vio_dma_write(&s->vdev, be32_to_cpu(crq->data), |
|||
s->buffer, len); |
|||
|
|||
if (trace_event_get_state_backends(TRACE_TPM_SPAPR_SHOW_BUFFER)) { |
|||
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->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); |
|||
tpm_spapr_do_startup_tpm(s, s->be_buffer_size); |
|||
} |
|||
|
|||
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); |
|||
} |
|||
|
|||
static const VMStateDescription vmstate_spapr_vtpm = { |
|||
.name = "tpm-spapr", |
|||
.unmigratable = 1, |
|||
}; |
|||
|
|||
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) |
|||
Loading…
Reference in new issue