| /* |
| * 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(); |
| } |
| 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 = (const 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) |