Message ID | 20191211162050.970199-2-stefanb@linux.vnet.ibm.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Add vTPM emulator supportfor ppc64 platform | expand |
Hi On Wed, Dec 11, 2019 at 8:25 PM Stefan Berger <stefanb@linux.vnet.ibm.com> wrote: > > 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> > --- > hw/tpm/Makefile.objs | 1 + > hw/tpm/tpm_spapr.c | 405 +++++++++++++++++++++++++++++++++++++++++++ > hw/tpm/trace-events | 12 ++ > include/sysemu/tpm.h | 3 + > qapi/tpm.json | 6 +- > 5 files changed, 424 insertions(+), 3 deletions(-) > create mode 100644 hw/tpm/tpm_spapr.c > > diff --git a/hw/tpm/Makefile.objs b/hw/tpm/Makefile.objs > index de0b85d02a..f9f29e0082 100644 > --- a/hw/tpm/Makefile.objs > +++ b/hw/tpm/Makefile.objs > @@ -4,3 +4,4 @@ common-obj-$(CONFIG_TPM_TIS) += tpm_tis.o > common-obj-$(CONFIG_TPM_CRB) += tpm_crb.o > common-obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o > common-obj-$(CONFIG_TPM_EMULATOR) += tpm_emulator.o > +obj-$(CONFIG_PSERIES) += tpm_spapr.o looks good to me, but why not introduce CONFIG_TPM_SPAPR ? > diff --git a/hw/tpm/tpm_spapr.c b/hw/tpm/tpm_spapr.c > new file mode 100644 > index 0000000000..c4a67e2403 > --- /dev/null > +++ b/hw/tpm/tpm_spapr.c > @@ -0,0 +1,405 @@ > +/* > + * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator > + * > + * PAPR Virtual TPM > + * > + * Copyright (c) 2015, 2017 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(SPAPRvTPMState, (obj), TYPE_TPM_SPAPR) > + > +typedef struct VioCRQ { > + 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; > +} VioCRQ; > + > +typedef union TPMSpaprCRQ { > + VioCRQ s; > + uint8_t raw[sizeof(VioCRQ)]; > +} TPMSpaprCRQ; > + > +#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 MAX_BUFFER_SIZE TARGET_PAGE_SIZE > + > +typedef struct { > + SpaprVioDevice vdev; > + > + TPMSpaprCRQ 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[MAX_BUFFER_SIZE]; > + > + TPMBackendCmd cmd; > + > + TPMBackend *be_driver; > + TPMVersion be_tpm_version; > + > + size_t be_buffer_size; > +} SPAPRvTPMState; > + > +static void tpm_spapr_show_buffer(const unsigned char *buffer, > + size_t buffer_size, const char *string) > +{ > + size_t len, i; > + char *line_buffer, *p; > + > + len = MIN(tpm_cmd_get_size(buffer), buffer_size); > + > + /* > + * allocate enough room for 3 chars per buffer entry plus a > + * newline after every 16 chars and a final null terminator. > + */ > + line_buffer = g_malloc(len * 3 + (len / 16) + 1); > + > + for (i = 0, p = line_buffer; i < len; i++) { > + if (i && !(i % 16)) { > + p += sprintf(p, "\n"); > + } > + p += sprintf(p, "%.2X ", buffer[i]); > + } > + trace_tpm_spapr_show_buffer(string, len, line_buffer); > + > + g_free(line_buffer); > +} > + > +/* > + * Send a request to the TPM. > + */ > +static void tpm_spapr_tpm_send(SPAPRvTPMState *s) > +{ > + if (trace_event_get_state_backends(TRACE_TPM_SPAPR_SHOW_BUFFER)) { > + tpm_spapr_show_buffer(s->buffer, sizeof(s->buffer), "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), sizeof(s->buffer)), > + .out = s->buffer, > + .out_len = sizeof(s->buffer), > + }; > + > + tpm_backend_deliver_request(s->be_driver, &s->cmd); > +} > + > +static int tpm_spapr_process_cmd(SPAPRvTPMState *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 int tpm_spapr_do_crq(struct SpaprVioDevice *dev, uint8_t *crq_data) > +{ > + SPAPRvTPMState *s = VIO_SPAPR_VTPM(dev); > + TPMSpaprCRQ local_crq; > + TPMSpaprCRQ *crq = &s->crq; /* requests only */ > + int rc; > + > + memcpy(&local_crq.raw, crq_data, sizeof(local_crq.raw)); > + > + trace_tpm_spapr_do_crq(local_crq.raw[0], local_crq.raw[1]); > + > + switch (local_crq.s.valid) { > + case SPAPR_VTPM_VALID_INIT_CRQ_COMMAND: /* Init command/response */ > + > + /* Respond to initialization request */ > + switch (local_crq.s.msg) { > + case SPAPR_VTPM_INIT_CRQ_RESULT: > + trace_tpm_spapr_do_crq_crq_result(); > + memset(local_crq.raw, 0, sizeof(local_crq.raw)); > + local_crq.s.valid = SPAPR_VTPM_VALID_INIT_CRQ_COMMAND; > + local_crq.s.msg = SPAPR_VTPM_INIT_CRQ_RESULT; > + spapr_vio_send_crq(dev, local_crq.raw); > + break; > + > + case SPAPR_VTPM_INIT_CRQ_COMPLETE_RESULT: > + trace_tpm_spapr_do_crq_crq_complete_result(); > + memset(local_crq.raw, 0, sizeof(local_crq.raw)); > + local_crq.s.valid = SPAPR_VTPM_VALID_INIT_CRQ_COMMAND; > + local_crq.s.msg = SPAPR_VTPM_INIT_CRQ_COMPLETE_RESULT; > + spapr_vio_send_crq(dev, local_crq.raw); > + break; > + } > + > + break; > + case SPAPR_VTPM_VALID_COMMAND: /* Payloads */ > + switch (local_crq.s.msg) { > + case SPAPR_VTPM_TPM_COMMAND: > + trace_tpm_spapr_do_crq_tpm_command(); > + if (s->state == SPAPR_VTPM_STATE_EXECUTION) { > + return H_BUSY; > + } > + /* this crq is tracked */ > + memcpy(crq->raw, crq_data, sizeof(crq->raw)); > + > + rc = tpm_spapr_process_cmd(s, be32_to_cpu(crq->s.data)); > + > + if (rc == H_SUCCESS) { > + crq->s.valid = be16_to_cpu(0); > + } else { > + local_crq.s.valid = SPAPR_VTPM_MSG_RESULT; > + local_crq.s.msg = SPAPR_VTPM_VTPM_ERROR; > + local_crq.s.data = cpu_to_be32(SPAPR_VTPM_ERR_COPY_IN_FAILED); > + spapr_vio_send_crq(dev, local_crq.raw); > + } > + break; > + > + case SPAPR_VTPM_GET_RTCE_BUFFER_SIZE: > + trace_tpm_spapr_do_crq_tpm_get_rtce_buffer_size(s->be_buffer_size); > + local_crq.s.msg |= SPAPR_VTPM_MSG_RESULT; > + local_crq.s.len = cpu_to_be16(s->be_buffer_size); > + spapr_vio_send_crq(dev, local_crq.raw); > + break; > + > + case SPAPR_VTPM_GET_VERSION: > + local_crq.s.msg |= SPAPR_VTPM_MSG_RESULT; > + local_crq.s.len = cpu_to_be16(0); > + switch (s->be_tpm_version) { > + case TPM_VERSION_UNSPEC: > + local_crq.s.data = cpu_to_be32(0); > + break; > + case TPM_VERSION_1_2: > + local_crq.s.data = cpu_to_be32(1); > + break; > + case TPM_VERSION_2_0: > + local_crq.s.data = cpu_to_be32(2); > + break; > + } > + trace_tpm_spapr_do_crq_get_version(be32_to_cpu(local_crq.s.data)); > + spapr_vio_send_crq(dev, local_crq.raw); > + break; > + > + case SPAPR_VTPM_PREPARE_TO_SUSPEND: > + trace_tpm_spapr_do_crq_prepare_to_suspend(); > + local_crq.s.msg |= SPAPR_VTPM_MSG_RESULT; > + spapr_vio_send_crq(dev, local_crq.raw); > + break; > + > + default: > + trace_tpm_spapr_do_crq_unknown_msg_type(crq->s.msg); > + } > + break; > + default: > + trace_tpm_spapr_do_crq_unknown_crq(local_crq.raw[0], local_crq.raw[1]); > + }; > + > + return H_SUCCESS; > +} > + > +static void tpm_spapr_request_completed(TPMIf *ti, int ret) > +{ > + SPAPRvTPMState *s = VIO_SPAPR_VTPM(ti); > + TPMSpaprCRQ *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->s.data), > + s->buffer, len); > + > + if (trace_event_get_state_backends(TRACE_TPM_SPAPR_SHOW_BUFFER)) { > + tpm_spapr_show_buffer(s->buffer, len, "From TPM"); > + } > + > + crq->s.valid = SPAPR_VTPM_MSG_RESULT; > + if (rc == H_SUCCESS) { > + crq->s.msg = SPAPR_VTPM_TPM_COMMAND | SPAPR_VTPM_MSG_RESULT; > + crq->s.len = cpu_to_be16(len); > + } else { > + error_report("%s: DMA write failure", __func__); > + crq->s.msg = SPAPR_VTPM_VTPM_ERROR; > + crq->s.len = cpu_to_be16(0); > + crq->s.data = cpu_to_be32(SPAPR_VTPM_ERR_COPY_OUT_FAILED); > + } > + > + rc = spapr_vio_send_crq(&s->vdev, crq->raw); > + if (rc) { > + error_report("%s: Error sending response", __func__); > + } > +} > + > +static int tpm_spapr_do_startup_tpm(SPAPRvTPMState *s, size_t buffersize) > +{ > + return tpm_backend_startup_tpm(s->be_driver, buffersize); > +} > + > +static void tpm_spapr_update_deviceclass(SpaprVioDevice *dev) > +{ > + SPAPRvTPMState *s = VIO_SPAPR_VTPM(dev); > + SpaprVioDeviceClass *k = VIO_SPAPR_DEVICE_GET_CLASS(dev); > + > + switch (s->be_tpm_version) { > + case TPM_VERSION_UNSPEC: > + assert(false); > + break; > + case TPM_VERSION_1_2: > + k->dt_name = "vtpm"; > + k->dt_type = "IBM,vtpm"; > + k->dt_compatible = "IBM,vtpm"; > + break; > + case TPM_VERSION_2_0: > + k->dt_name = "vtpm"; > + k->dt_type = "IBM,vtpm"; > + k->dt_compatible = "IBM,vtpm20"; > + break; > + } > +} > + > +static void tpm_spapr_reset(SpaprVioDevice *dev) > +{ > + SPAPRvTPMState *s = VIO_SPAPR_VTPM(dev); > + > + s->state = SPAPR_VTPM_STATE_NONE; > + > + s->be_tpm_version = tpm_backend_get_tpm_version(s->be_driver); > + tpm_spapr_update_deviceclass(dev); > + > + s->be_buffer_size = MAX(ROUND_UP(tpm_backend_get_buffer_size(s->be_driver), > + TARGET_PAGE_SIZE), > + sizeof(s->buffer)); > + > + 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) > +{ > + SPAPRvTPMState *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(SPAPRvTPMState, vdev), > + DEFINE_PROP_TPMBE("tpmdev", SPAPRvTPMState, be_driver), > + DEFINE_PROP_END_OF_LIST(), > +}; > + > +static void tpm_spapr_realizefn(SpaprVioDevice *dev, Error **errp) > +{ > + SPAPRvTPMState *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; > + } > +} > + > +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->signal_mask = 0x00000001; > + set_bit(DEVICE_CATEGORY_MISC, dc->categories); > + dc->props = 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(SPAPRvTPMState), > + .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) > diff --git a/hw/tpm/trace-events b/hw/tpm/trace-events > index 89804bcd64..6278a39618 100644 > --- a/hw/tpm/trace-events > +++ b/hw/tpm/trace-events > @@ -55,3 +55,15 @@ tpm_tis_pre_save(uint8_t locty, uint32_t rw_offset) "locty: %d, rw_offset = %u" > > # tpm_ppi.c > tpm_ppi_memset(uint8_t *ptr, size_t size) "memset: %p %zu" > + > +# hw/tpm/tpm_spapr.c > +tpm_spapr_show_buffer(const char *direction, size_t len, const char *buf) "direction: %s len: %zu\n%s" > +tpm_spapr_do_crq(uint8_t raw1, uint8_t raw2) "1st 2 bytes in CRQ: 0x%02x 0x%02x" > +tpm_spapr_do_crq_crq_result(void) "SPAPR_VTPM_INIT_CRQ_RESULT" > +tpm_spapr_do_crq_crq_complete_result(void) "SPAPR_VTPM_INIT_CRQ_COMP_RESULT" > +tpm_spapr_do_crq_tpm_command(void) "got TPM command payload" > +tpm_spapr_do_crq_tpm_get_rtce_buffer_size(size_t buffersize) "response: buffer size is %zu" > +tpm_spapr_do_crq_get_version(uint32_t version) "response: version %u" > +tpm_spapr_do_crq_prepare_to_suspend(void) "response: preparing to suspend" > +tpm_spapr_do_crq_unknown_msg_type(uint8_t type) "Unknown message type 0x%02x" > +tpm_spapr_do_crq_unknown_crq(uint8_t raw1, uint8_t raw2) "unknown CRQ 0x%02x 0x%02x ..." > diff --git a/include/sysemu/tpm.h b/include/sysemu/tpm.h > index 5b541a71c8..15979a3647 100644 > --- a/include/sysemu/tpm.h > +++ b/include/sysemu/tpm.h > @@ -45,11 +45,14 @@ typedef struct TPMIfClass { > > #define TYPE_TPM_TIS "tpm-tis" > #define TYPE_TPM_CRB "tpm-crb" > +#define TYPE_TPM_SPAPR "tpm-spapr" > > #define TPM_IS_TIS(chr) \ > object_dynamic_cast(OBJECT(chr), TYPE_TPM_TIS) > #define TPM_IS_CRB(chr) \ > object_dynamic_cast(OBJECT(chr), TYPE_TPM_CRB) > +#define TPM_IS_SPAPR(chr) \ > + object_dynamic_cast(OBJECT(chr), TYPE_TPM_SPAPR) > > /* returns NULL unless there is exactly one TPM device */ > static inline TPMIf *tpm_find(void) > diff --git a/qapi/tpm.json b/qapi/tpm.json > index b30323bb6b..63878aa0f4 100644 > --- a/qapi/tpm.json > +++ b/qapi/tpm.json > @@ -12,11 +12,11 @@ > # > # @tpm-tis: TPM TIS model > # @tpm-crb: TPM CRB model (since 2.12) > +# @tpm-spapr: TPM SPAPR model (since 5.0) > # > # Since: 1.5 > ## > -{ 'enum': 'TpmModel', 'data': [ 'tpm-tis', 'tpm-crb' ] } > - > +{ 'enum': 'TpmModel', 'data': [ 'tpm-tis', 'tpm-crb', 'tpm-spapr' ] } > ## > # @query-tpm-models: > # > @@ -29,7 +29,7 @@ > # Example: > # > # -> { "execute": "query-tpm-models" } > -# <- { "return": [ "tpm-tis", "tpm-crb" ] } > +# <- { "return": [ "tpm-tis", "tpm-crb", "tpm-spapr" ] } > # > ## > { 'command': 'query-tpm-models', 'returns': ['TpmModel'] } > -- > 2.21.0 > >
On 12/12/19 6:05 AM, Marc-André Lureau wrote: > Hi > > On Wed, Dec 11, 2019 at 8:25 PM Stefan Berger > <stefanb@linux.vnet.ibm.com> wrote: >> 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> >> --- >> hw/tpm/Makefile.objs | 1 + >> hw/tpm/tpm_spapr.c | 405 +++++++++++++++++++++++++++++++++++++++++++ >> hw/tpm/trace-events | 12 ++ >> include/sysemu/tpm.h | 3 + >> qapi/tpm.json | 6 +- >> 5 files changed, 424 insertions(+), 3 deletions(-) >> create mode 100644 hw/tpm/tpm_spapr.c >> >> diff --git a/hw/tpm/Makefile.objs b/hw/tpm/Makefile.objs >> index de0b85d02a..f9f29e0082 100644 >> --- a/hw/tpm/Makefile.objs >> +++ b/hw/tpm/Makefile.objs >> @@ -4,3 +4,4 @@ common-obj-$(CONFIG_TPM_TIS) += tpm_tis.o >> common-obj-$(CONFIG_TPM_CRB) += tpm_crb.o >> common-obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o >> common-obj-$(CONFIG_TPM_EMULATOR) += tpm_emulator.o >> +obj-$(CONFIG_PSERIES) += tpm_spapr.o > looks good to me, but why not introduce CONFIG_TPM_SPAPR ? Good point. I fixed it.
diff --git a/hw/tpm/Makefile.objs b/hw/tpm/Makefile.objs index de0b85d02a..f9f29e0082 100644 --- a/hw/tpm/Makefile.objs +++ b/hw/tpm/Makefile.objs @@ -4,3 +4,4 @@ common-obj-$(CONFIG_TPM_TIS) += tpm_tis.o common-obj-$(CONFIG_TPM_CRB) += tpm_crb.o common-obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o common-obj-$(CONFIG_TPM_EMULATOR) += tpm_emulator.o +obj-$(CONFIG_PSERIES) += tpm_spapr.o diff --git a/hw/tpm/tpm_spapr.c b/hw/tpm/tpm_spapr.c new file mode 100644 index 0000000000..c4a67e2403 --- /dev/null +++ b/hw/tpm/tpm_spapr.c @@ -0,0 +1,405 @@ +/* + * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator + * + * PAPR Virtual TPM + * + * Copyright (c) 2015, 2017 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(SPAPRvTPMState, (obj), TYPE_TPM_SPAPR) + +typedef struct VioCRQ { + 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; +} VioCRQ; + +typedef union TPMSpaprCRQ { + VioCRQ s; + uint8_t raw[sizeof(VioCRQ)]; +} TPMSpaprCRQ; + +#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 MAX_BUFFER_SIZE TARGET_PAGE_SIZE + +typedef struct { + SpaprVioDevice vdev; + + TPMSpaprCRQ 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[MAX_BUFFER_SIZE]; + + TPMBackendCmd cmd; + + TPMBackend *be_driver; + TPMVersion be_tpm_version; + + size_t be_buffer_size; +} SPAPRvTPMState; + +static void tpm_spapr_show_buffer(const unsigned char *buffer, + size_t buffer_size, const char *string) +{ + size_t len, i; + char *line_buffer, *p; + + len = MIN(tpm_cmd_get_size(buffer), buffer_size); + + /* + * allocate enough room for 3 chars per buffer entry plus a + * newline after every 16 chars and a final null terminator. + */ + line_buffer = g_malloc(len * 3 + (len / 16) + 1); + + for (i = 0, p = line_buffer; i < len; i++) { + if (i && !(i % 16)) { + p += sprintf(p, "\n"); + } + p += sprintf(p, "%.2X ", buffer[i]); + } + trace_tpm_spapr_show_buffer(string, len, line_buffer); + + g_free(line_buffer); +} + +/* + * Send a request to the TPM. + */ +static void tpm_spapr_tpm_send(SPAPRvTPMState *s) +{ + if (trace_event_get_state_backends(TRACE_TPM_SPAPR_SHOW_BUFFER)) { + tpm_spapr_show_buffer(s->buffer, sizeof(s->buffer), "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), sizeof(s->buffer)), + .out = s->buffer, + .out_len = sizeof(s->buffer), + }; + + tpm_backend_deliver_request(s->be_driver, &s->cmd); +} + +static int tpm_spapr_process_cmd(SPAPRvTPMState *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 int tpm_spapr_do_crq(struct SpaprVioDevice *dev, uint8_t *crq_data) +{ + SPAPRvTPMState *s = VIO_SPAPR_VTPM(dev); + TPMSpaprCRQ local_crq; + TPMSpaprCRQ *crq = &s->crq; /* requests only */ + int rc; + + memcpy(&local_crq.raw, crq_data, sizeof(local_crq.raw)); + + trace_tpm_spapr_do_crq(local_crq.raw[0], local_crq.raw[1]); + + switch (local_crq.s.valid) { + case SPAPR_VTPM_VALID_INIT_CRQ_COMMAND: /* Init command/response */ + + /* Respond to initialization request */ + switch (local_crq.s.msg) { + case SPAPR_VTPM_INIT_CRQ_RESULT: + trace_tpm_spapr_do_crq_crq_result(); + memset(local_crq.raw, 0, sizeof(local_crq.raw)); + local_crq.s.valid = SPAPR_VTPM_VALID_INIT_CRQ_COMMAND; + local_crq.s.msg = SPAPR_VTPM_INIT_CRQ_RESULT; + spapr_vio_send_crq(dev, local_crq.raw); + break; + + case SPAPR_VTPM_INIT_CRQ_COMPLETE_RESULT: + trace_tpm_spapr_do_crq_crq_complete_result(); + memset(local_crq.raw, 0, sizeof(local_crq.raw)); + local_crq.s.valid = SPAPR_VTPM_VALID_INIT_CRQ_COMMAND; + local_crq.s.msg = SPAPR_VTPM_INIT_CRQ_COMPLETE_RESULT; + spapr_vio_send_crq(dev, local_crq.raw); + break; + } + + break; + case SPAPR_VTPM_VALID_COMMAND: /* Payloads */ + switch (local_crq.s.msg) { + case SPAPR_VTPM_TPM_COMMAND: + trace_tpm_spapr_do_crq_tpm_command(); + if (s->state == SPAPR_VTPM_STATE_EXECUTION) { + return H_BUSY; + } + /* this crq is tracked */ + memcpy(crq->raw, crq_data, sizeof(crq->raw)); + + rc = tpm_spapr_process_cmd(s, be32_to_cpu(crq->s.data)); + + if (rc == H_SUCCESS) { + crq->s.valid = be16_to_cpu(0); + } else { + local_crq.s.valid = SPAPR_VTPM_MSG_RESULT; + local_crq.s.msg = SPAPR_VTPM_VTPM_ERROR; + local_crq.s.data = cpu_to_be32(SPAPR_VTPM_ERR_COPY_IN_FAILED); + spapr_vio_send_crq(dev, local_crq.raw); + } + break; + + case SPAPR_VTPM_GET_RTCE_BUFFER_SIZE: + trace_tpm_spapr_do_crq_tpm_get_rtce_buffer_size(s->be_buffer_size); + local_crq.s.msg |= SPAPR_VTPM_MSG_RESULT; + local_crq.s.len = cpu_to_be16(s->be_buffer_size); + spapr_vio_send_crq(dev, local_crq.raw); + break; + + case SPAPR_VTPM_GET_VERSION: + local_crq.s.msg |= SPAPR_VTPM_MSG_RESULT; + local_crq.s.len = cpu_to_be16(0); + switch (s->be_tpm_version) { + case TPM_VERSION_UNSPEC: + local_crq.s.data = cpu_to_be32(0); + break; + case TPM_VERSION_1_2: + local_crq.s.data = cpu_to_be32(1); + break; + case TPM_VERSION_2_0: + local_crq.s.data = cpu_to_be32(2); + break; + } + trace_tpm_spapr_do_crq_get_version(be32_to_cpu(local_crq.s.data)); + spapr_vio_send_crq(dev, local_crq.raw); + break; + + case SPAPR_VTPM_PREPARE_TO_SUSPEND: + trace_tpm_spapr_do_crq_prepare_to_suspend(); + local_crq.s.msg |= SPAPR_VTPM_MSG_RESULT; + spapr_vio_send_crq(dev, local_crq.raw); + break; + + default: + trace_tpm_spapr_do_crq_unknown_msg_type(crq->s.msg); + } + break; + default: + trace_tpm_spapr_do_crq_unknown_crq(local_crq.raw[0], local_crq.raw[1]); + }; + + return H_SUCCESS; +} + +static void tpm_spapr_request_completed(TPMIf *ti, int ret) +{ + SPAPRvTPMState *s = VIO_SPAPR_VTPM(ti); + TPMSpaprCRQ *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->s.data), + s->buffer, len); + + if (trace_event_get_state_backends(TRACE_TPM_SPAPR_SHOW_BUFFER)) { + tpm_spapr_show_buffer(s->buffer, len, "From TPM"); + } + + crq->s.valid = SPAPR_VTPM_MSG_RESULT; + if (rc == H_SUCCESS) { + crq->s.msg = SPAPR_VTPM_TPM_COMMAND | SPAPR_VTPM_MSG_RESULT; + crq->s.len = cpu_to_be16(len); + } else { + error_report("%s: DMA write failure", __func__); + crq->s.msg = SPAPR_VTPM_VTPM_ERROR; + crq->s.len = cpu_to_be16(0); + crq->s.data = cpu_to_be32(SPAPR_VTPM_ERR_COPY_OUT_FAILED); + } + + rc = spapr_vio_send_crq(&s->vdev, crq->raw); + if (rc) { + error_report("%s: Error sending response", __func__); + } +} + +static int tpm_spapr_do_startup_tpm(SPAPRvTPMState *s, size_t buffersize) +{ + return tpm_backend_startup_tpm(s->be_driver, buffersize); +} + +static void tpm_spapr_update_deviceclass(SpaprVioDevice *dev) +{ + SPAPRvTPMState *s = VIO_SPAPR_VTPM(dev); + SpaprVioDeviceClass *k = VIO_SPAPR_DEVICE_GET_CLASS(dev); + + switch (s->be_tpm_version) { + case TPM_VERSION_UNSPEC: + assert(false); + break; + case TPM_VERSION_1_2: + k->dt_name = "vtpm"; + k->dt_type = "IBM,vtpm"; + k->dt_compatible = "IBM,vtpm"; + break; + case TPM_VERSION_2_0: + k->dt_name = "vtpm"; + k->dt_type = "IBM,vtpm"; + k->dt_compatible = "IBM,vtpm20"; + break; + } +} + +static void tpm_spapr_reset(SpaprVioDevice *dev) +{ + SPAPRvTPMState *s = VIO_SPAPR_VTPM(dev); + + s->state = SPAPR_VTPM_STATE_NONE; + + s->be_tpm_version = tpm_backend_get_tpm_version(s->be_driver); + tpm_spapr_update_deviceclass(dev); + + s->be_buffer_size = MAX(ROUND_UP(tpm_backend_get_buffer_size(s->be_driver), + TARGET_PAGE_SIZE), + sizeof(s->buffer)); + + 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) +{ + SPAPRvTPMState *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(SPAPRvTPMState, vdev), + DEFINE_PROP_TPMBE("tpmdev", SPAPRvTPMState, be_driver), + DEFINE_PROP_END_OF_LIST(), +}; + +static void tpm_spapr_realizefn(SpaprVioDevice *dev, Error **errp) +{ + SPAPRvTPMState *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; + } +} + +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->signal_mask = 0x00000001; + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + dc->props = 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(SPAPRvTPMState), + .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) diff --git a/hw/tpm/trace-events b/hw/tpm/trace-events index 89804bcd64..6278a39618 100644 --- a/hw/tpm/trace-events +++ b/hw/tpm/trace-events @@ -55,3 +55,15 @@ tpm_tis_pre_save(uint8_t locty, uint32_t rw_offset) "locty: %d, rw_offset = %u" # tpm_ppi.c tpm_ppi_memset(uint8_t *ptr, size_t size) "memset: %p %zu" + +# hw/tpm/tpm_spapr.c +tpm_spapr_show_buffer(const char *direction, size_t len, const char *buf) "direction: %s len: %zu\n%s" +tpm_spapr_do_crq(uint8_t raw1, uint8_t raw2) "1st 2 bytes in CRQ: 0x%02x 0x%02x" +tpm_spapr_do_crq_crq_result(void) "SPAPR_VTPM_INIT_CRQ_RESULT" +tpm_spapr_do_crq_crq_complete_result(void) "SPAPR_VTPM_INIT_CRQ_COMP_RESULT" +tpm_spapr_do_crq_tpm_command(void) "got TPM command payload" +tpm_spapr_do_crq_tpm_get_rtce_buffer_size(size_t buffersize) "response: buffer size is %zu" +tpm_spapr_do_crq_get_version(uint32_t version) "response: version %u" +tpm_spapr_do_crq_prepare_to_suspend(void) "response: preparing to suspend" +tpm_spapr_do_crq_unknown_msg_type(uint8_t type) "Unknown message type 0x%02x" +tpm_spapr_do_crq_unknown_crq(uint8_t raw1, uint8_t raw2) "unknown CRQ 0x%02x 0x%02x ..." diff --git a/include/sysemu/tpm.h b/include/sysemu/tpm.h index 5b541a71c8..15979a3647 100644 --- a/include/sysemu/tpm.h +++ b/include/sysemu/tpm.h @@ -45,11 +45,14 @@ typedef struct TPMIfClass { #define TYPE_TPM_TIS "tpm-tis" #define TYPE_TPM_CRB "tpm-crb" +#define TYPE_TPM_SPAPR "tpm-spapr" #define TPM_IS_TIS(chr) \ object_dynamic_cast(OBJECT(chr), TYPE_TPM_TIS) #define TPM_IS_CRB(chr) \ object_dynamic_cast(OBJECT(chr), TYPE_TPM_CRB) +#define TPM_IS_SPAPR(chr) \ + object_dynamic_cast(OBJECT(chr), TYPE_TPM_SPAPR) /* returns NULL unless there is exactly one TPM device */ static inline TPMIf *tpm_find(void) diff --git a/qapi/tpm.json b/qapi/tpm.json index b30323bb6b..63878aa0f4 100644 --- a/qapi/tpm.json +++ b/qapi/tpm.json @@ -12,11 +12,11 @@ # # @tpm-tis: TPM TIS model # @tpm-crb: TPM CRB model (since 2.12) +# @tpm-spapr: TPM SPAPR model (since 5.0) # # Since: 1.5 ## -{ 'enum': 'TpmModel', 'data': [ 'tpm-tis', 'tpm-crb' ] } - +{ 'enum': 'TpmModel', 'data': [ 'tpm-tis', 'tpm-crb', 'tpm-spapr' ] } ## # @query-tpm-models: # @@ -29,7 +29,7 @@ # Example: # # -> { "execute": "query-tpm-models" } -# <- { "return": [ "tpm-tis", "tpm-crb" ] } +# <- { "return": [ "tpm-tis", "tpm-crb", "tpm-spapr" ] } # ## { 'command': 'query-tpm-models', 'returns': ['TpmModel'] }