/* * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator * * PAPR Virtual TPM * * Copyright (c) 2015, 2017, 2019 IBM Corporation. * * Authors: * Stefan Berger * * 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; uint32_t numbytes; /* number of bytes to deliver on resume */ 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); 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); 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->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); 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); } /* 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)