diff options
Diffstat (limited to 'hw/tpm/tpm_spapr.c')
-rw-r--r-- | hw/tpm/tpm_spapr.c | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/hw/tpm/tpm_spapr.c b/hw/tpm/tpm_spapr.c new file mode 100644 index 000000000..dea7b1333 --- /dev/null +++ b/hw/tpm/tpm_spapr.c @@ -0,0 +1,430 @@ +/* + * 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) |