| /* | 
 |  * 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) |