diff mbox series

[v5,1/5] tpm_spapr: Support TPM for ppc64 using CRQ based interface

Message ID 20191212202430.1079725-2-stefanb@linux.vnet.ibm.com (mailing list archive)
State New, archived
Headers show
Series Add vTPM emulator support for ppc64 platform | expand

Commit Message

Stefan Berger Dec. 12, 2019, 8:24 p.m. UTC
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>

Comments

Eric Blake Dec. 12, 2019, 8:33 p.m. UTC | #1
On 12/12/19 2:24 PM, Stefan Berger 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>
> 
> diff --git a/hw/tpm/Kconfig b/hw/tpm/Kconfig

Odd that your diff doesn't include the usual --- marker or a diffstat.


> +++ 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.

Do you want to claim 2019?
Stefan Berger Dec. 12, 2019, 8:34 p.m. UTC | #2
On 12/12/19 3:33 PM, Eric Blake wrote:
> On 12/12/19 2:24 PM, Stefan Berger 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>
>>
>> diff --git a/hw/tpm/Kconfig b/hw/tpm/Kconfig
>
> Odd that your diff doesn't include the usual --- marker or a diffstat.
>
>
>> +++ 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.
>
> Do you want to claim 2019?
>
:-)
David Gibson Dec. 13, 2019, 5:34 a.m. UTC | #3
On Thu, Dec 12, 2019 at 03:24:26PM -0500, Stefan Berger 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>
> 
> diff --git a/hw/tpm/Kconfig b/hw/tpm/Kconfig
> index 4c8ee87d67..66a570aac1 100644
> --- a/hw/tpm/Kconfig
> +++ b/hw/tpm/Kconfig
> @@ -22,3 +22,9 @@ config TPM_EMULATOR
>      bool
>      default y
>      depends on TPMDEV
> +
> +config TPM_SPAPR
> +    bool
> +    default n
> +    select TPMDEV
> +    depends on PSERIES
> diff --git a/hw/tpm/Makefile.objs b/hw/tpm/Makefile.objs
> index de0b85d02a..85eb99ae05 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_TPM_SPAPR) += 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 {

How does this structure relate to the existing SpaprVioCrq?

Also we're now avoiding exceptions to StudlyCaps, because it causes
more confusion even if it is to match other capitalization
conventions.  So, I'd suggest 'VioCrq', 'TpmSpaprCrq' etc.

> +    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;

A union just to get raw bytes seems a really weird thing to do (as
opposed to just casting to (char *))

> +
> +#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

I see this field written, but never read.  What's up with that?

> +
> +    unsigned char buffer[MAX_BUFFER_SIZE];
> +
> +    TPMBackendCmd cmd;
> +
> +    TPMBackend *be_driver;
> +    TPMVersion be_tpm_version;
> +
> +    size_t be_buffer_size;
> +} SPAPRvTPMState;

SpaprVtpmState

Or just SpaprTpmState, since we use just "tpm spapr" rather than
"vtpm" in plenty of other places.

> +
> +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);

You can use g_strdup_printf() / g_string_append_printf() to avoid
fiddly messing around with allocations like this.

> +
> +    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));

Again, no real need for a union here, you can just memcpy onto a
structure variable.

> +    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;

To make the breakage obvious when/if the backend adds a new version we
should probably have a default: case with g_assert_not_reached() or
the like.

> +            }
> +            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;

Erk.  Updating DeviceClass structures on the fly is hideously ugly.
We might need to take a different approach for this.

> +    }
> +}
> +
> +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));

I'm very confused as to what be_buffer_size is supposed to represent.
it's not the backend size, because you're rounding that up and taking
MAX with another thing.  But it's not the max safe size for this
locally, because it can be bigger than 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()) {

This seems wrong, even though I also see it in existing TPM code.
AFAICT tpm_find() returns a TPMIf pointer for the existing TPM if it
exists, meaning !tpm_find() will be true if there is *not* an existing
TPM.

> +        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'] }
Stefan Berger Dec. 13, 2019, 1:03 p.m. UTC | #4
On 12/13/19 12:34 AM, David Gibson wrote:
> On Thu, Dec 12, 2019 at 03:24:26PM -0500, Stefan Berger 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>
>>
>> diff --git a/hw/tpm/Kconfig b/hw/tpm/Kconfig
>> index 4c8ee87d67..66a570aac1 100644
>> --- a/hw/tpm/Kconfig
>> +++ b/hw/tpm/Kconfig
>> @@ -22,3 +22,9 @@ config TPM_EMULATOR
>>       bool
>>       default y
>>       depends on TPMDEV
>> +
>> +config TPM_SPAPR
>> +    bool
>> +    default n
>> +    select TPMDEV
>> +    depends on PSERIES
>> diff --git a/hw/tpm/Makefile.objs b/hw/tpm/Makefile.objs
>> index de0b85d02a..85eb99ae05 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_TPM_SPAPR) += 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 {
> How does this structure relate to the existing SpaprVioCrq?

The existing one looks like this:

typedef struct SpaprVioCrq {
     uint64_t qladdr;
     uint32_t qsize;
     uint32_t qnext;
     int(*SendFunc)(struct SpaprVioDevice *vdev, uint8_t *crq);
} SpaprVioCrq;

I don't seem to find the fields there that we need for vTPM support.

>
> Also we're now avoiding exceptions to StudlyCaps, because it causes
> more confusion even if it is to match other capitalization
> conventions.  So, I'd suggest 'VioCrq', 'TpmSpaprCrq' etc.


Will adjust.


>
>> +    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;
> A union just to get raw bytes seems a really weird thing to do (as
> opposed to just casting to (char *))


Ok, I will change it.



>
>> +
>> +#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
> I see this field written, but never read.  What's up with that?


             if (s->state == SPAPR_VTPM_STATE_EXECUTION) {
                 return H_BUSY;
             }

Is this what you mean?


>
>> +
>> +    unsigned char buffer[MAX_BUFFER_SIZE];
>> +
>> +    TPMBackendCmd cmd;
>> +
>> +    TPMBackend *be_driver;
>> +    TPMVersion be_tpm_version;
>> +
>> +    size_t be_buffer_size;
>> +} SPAPRvTPMState;
> SpaprVtpmState
>
> Or just SpaprTpmState, since we use just "tpm spapr" rather than
> "vtpm" in plenty of other places.


Will adjust.


>
>> +
>> +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);
> You can use g_strdup_printf() / g_string_append_printf() to avoid
> fiddly messing around with allocations like this.

This is a 1:1 copy from the existing TIS driver.


>
>> +
>> +    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));
> Again, no real need for a union here, you can just memcpy onto a
> structure variable.
>
>> +    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;
> To make the breakage obvious when/if the backend adds a new version we
> should probably have a default: case with g_assert_not_reached() or
> the like.
I will add this.
>
>> +            }
>> +            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;
> Erk.  Updating DeviceClass structures on the fly is hideously ugly.
> We might need to take a different approach for this.

Make a suggestion... Obviously, we can hard-initialize dt_name and 
dt_type but dt_compatible can only be set after we have determined the 
version of TPM.


>
>> +    }
>> +}
>> +
>> +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));
> I'm very confused as to what be_buffer_size is supposed to represent.
> it's not the backend size, because you're rounding that up and taking
> MAX with another thing.  But it's not the max safe size for this
> locally, because it can be bigger than s->buffer.


Will adjust.

>
>> +    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()) {
> This seems wrong, even though I also see it in existing TPM code.
> AFAICT tpm_find() returns a TPMIf pointer for the existing TPM if it
> exists, meaning !tpm_find() will be true if there is *not* an existing
> TPM.
/* returns NULL unless there is exactly one TPM device */
static inline TPMIf *tpm_find(void)
{
     Object *obj = object_resolve_path_type("", TYPE_TPM_IF, NULL);

     return TPM_IF(obj);
}


I would rather leave it like this.


>
>> +        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'] }
David Gibson Dec. 17, 2019, 12:29 a.m. UTC | #5
On Fri, Dec 13, 2019 at 08:03:36AM -0500, Stefan Berger wrote:
> On 12/13/19 12:34 AM, David Gibson wrote:
> > On Thu, Dec 12, 2019 at 03:24:26PM -0500, Stefan Berger 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>
> > > 
> > > diff --git a/hw/tpm/Kconfig b/hw/tpm/Kconfig
> > > index 4c8ee87d67..66a570aac1 100644
> > > --- a/hw/tpm/Kconfig
> > > +++ b/hw/tpm/Kconfig
> > > @@ -22,3 +22,9 @@ config TPM_EMULATOR
> > >       bool
> > >       default y
> > >       depends on TPMDEV
> > > +
> > > +config TPM_SPAPR
> > > +    bool
> > > +    default n
> > > +    select TPMDEV
> > > +    depends on PSERIES
> > > diff --git a/hw/tpm/Makefile.objs b/hw/tpm/Makefile.objs
> > > index de0b85d02a..85eb99ae05 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_TPM_SPAPR) += 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 {
> > How does this structure relate to the existing SpaprVioCrq?
> 
> The existing one looks like this:
> 
> typedef struct SpaprVioCrq {
>     uint64_t qladdr;
>     uint32_t qsize;
>     uint32_t qnext;
>     int(*SendFunc)(struct SpaprVioDevice *vdev, uint8_t *crq);
> } SpaprVioCrq;
> 
> I don't seem to find the fields there that we need for vTPM support.

Yeah, I can see the difference in the structures.  What I'm after is
what is the difference in purpose which means they have different
content.

Having read through the whole series now, I *think* the answer is that
the tpm specific structure is one entry in the request queue for the
vtpm, whereas the VioCrq structure is a handle on an entire queue.

I think the tpm one needs a rename to reflect that a) it's vtpm
specific and b) it's not actually a queue, just part of it.

> > Also we're now avoiding exceptions to StudlyCaps, because it causes
> > more confusion even if it is to match other capitalization
> > conventions.  So, I'd suggest 'VioCrq', 'TpmSpaprCrq' etc.
> 
> 
> Will adjust.
> 
> 
> > 
> > > +    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;
> > A union just to get raw bytes seems a really weird thing to do (as
> > opposed to just casting to (char *))
> 
> 
> Ok, I will change it.
> 
> 
> 
> > 
> > > +
> > > +#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
> > I see this field written, but never read.  What's up with that?
> 
> 
>             if (s->state == SPAPR_VTPM_STATE_EXECUTION) {
>                 return H_BUSY;
>             }
> 
> Is this what you mean?

Oh, I must have missed that, sorry.

> 
> 
> > 
> > > +
> > > +    unsigned char buffer[MAX_BUFFER_SIZE];
> > > +
> > > +    TPMBackendCmd cmd;
> > > +
> > > +    TPMBackend *be_driver;
> > > +    TPMVersion be_tpm_version;
> > > +
> > > +    size_t be_buffer_size;
> > > +} SPAPRvTPMState;
> > SpaprVtpmState
> > 
> > Or just SpaprTpmState, since we use just "tpm spapr" rather than
> > "vtpm" in plenty of other places.
> 
> 
> Will adjust.
> 
> 
> > 
> > > +
> > > +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);
> > You can use g_strdup_printf() / g_string_append_printf() to avoid
> > fiddly messing around with allocations like this.
> 
> This is a 1:1 copy from the existing TIS driver.

Hm, right.  Probably not a bad idea to move that out as a helper
function then.

> > > +
> > > +    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));
> > Again, no real need for a union here, you can just memcpy onto a
> > structure variable.
> > 
> > > +    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;
> > To make the breakage obvious when/if the backend adds a new version we
> > should probably have a default: case with g_assert_not_reached() or
> > the like.
> I will add this.
> > 
> > > +            }
> > > +            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;
> > Erk.  Updating DeviceClass structures on the fly is hideously ugly.
> > We might need to take a different approach for this.
> 
> Make a suggestion... Obviously, we can hard-initialize dt_name and dt_type
> but dt_compatible can only be set after we have determined the version of
> TPM.

As you say name and type can just be put into the class statically.
Since you need to change compatible based on an internal variable,
we'd need to replace the static dt_compatible in the class with a
callback.

> > > +    }
> > > +}
> > > +
> > > +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));
> > I'm very confused as to what be_buffer_size is supposed to represent.
> > it's not the backend size, because you're rounding that up and taking
> > MAX with another thing.  But it's not the max safe size for this
> > locally, because it can be bigger than s->buffer.
> 
> 
> Will adjust.
> 
> > 
> > > +    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()) {
> > This seems wrong, even though I also see it in existing TPM code.
> > AFAICT tpm_find() returns a TPMIf pointer for the existing TPM if it
> > exists, meaning !tpm_find() will be true if there is *not* an existing
> > TPM.
> /* returns NULL unless there is exactly one TPM device */
> static inline TPMIf *tpm_find(void)
> {
>     Object *obj = object_resolve_path_type("", TYPE_TPM_IF, NULL);
> 
>     return TPM_IF(obj);
> }
> 
> 
> I would rather leave it like this.

Oh, I see.  Seems confusing to me, but I guess that's the idiom, so
let it be.

> > > +        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'] }
> 
>
Stefan Berger Dec. 17, 2019, 7:44 p.m. UTC | #6
On 12/16/19 7:29 PM, David Gibson wrote:
> On Fri, Dec 13, 2019 at 08:03:36AM -0500, Stefan Berger wrote:
>> On 12/13/19 12:34 AM, David Gibson wrote:
>>
>> The existing one looks like this:
>>
>> typedef struct SpaprVioCrq {
>>      uint64_t qladdr;
>>      uint32_t qsize;
>>      uint32_t qnext;
>>      int(*SendFunc)(struct SpaprVioDevice *vdev, uint8_t *crq);
>> } SpaprVioCrq;
>>
>> I don't seem to find the fields there that we need for vTPM support.
> Yeah, I can see the difference in the structures.  What I'm after is
> what is the difference in purpose which means they have different
> content.
>
> Having read through the whole series now, I *think* the answer is that
> the tpm specific structure is one entry in the request queue for the
> vtpm, whereas the VioCrq structure is a handle on an entire queue.
>
> I think the tpm one needs a rename to reflect that a) it's vtpm
> specific and b) it's not actually a queue, just part of it.


v6 has it as TpmCrq. It's local to the file, so from that perspective 
it's specific to (v)TPM.


>> This is a 1:1 copy from the existing TIS driver.
> Hm, right.  Probably not a bad idea to move that out as a helper
> function then.


In V7 then.


>>>> +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;
>>> Erk.  Updating DeviceClass structures on the fly is hideously ugly.
>>> We might need to take a different approach for this.
>> Make a suggestion... Obviously, we can hard-initialize dt_name and dt_type
>> but dt_compatible can only be set after we have determined the version of
>> TPM.
> As you say name and type can just be put into the class statically.


I did this in v6.


> Since you need to change compatible based on an internal variable,
> we'd need to replace the static dt_compatible in the class with a
> callback.


Why can we not initialize it once we know the version of TPM? From the 
perspective of SLOF at least this seems to be building the device tree 
fine since it sees the proper value...


    Stefan
David Gibson Dec. 19, 2019, 1:54 a.m. UTC | #7
On Tue, Dec 17, 2019 at 02:44:04PM -0500, Stefan Berger wrote:
> On 12/16/19 7:29 PM, David Gibson wrote:
> > On Fri, Dec 13, 2019 at 08:03:36AM -0500, Stefan Berger wrote:
> > > On 12/13/19 12:34 AM, David Gibson wrote:
> > > 
> > > The existing one looks like this:
> > > 
> > > typedef struct SpaprVioCrq {
> > >      uint64_t qladdr;
> > >      uint32_t qsize;
> > >      uint32_t qnext;
> > >      int(*SendFunc)(struct SpaprVioDevice *vdev, uint8_t *crq);
> > > } SpaprVioCrq;
> > > 
> > > I don't seem to find the fields there that we need for vTPM support.
> > Yeah, I can see the difference in the structures.  What I'm after is
> > what is the difference in purpose which means they have different
> > content.
> > 
> > Having read through the whole series now, I *think* the answer is that
> > the tpm specific structure is one entry in the request queue for the
> > vtpm, whereas the VioCrq structure is a handle on an entire queue.
> > 
> > I think the tpm one needs a rename to reflect that a) it's vtpm
> > specific and b) it's not actually a queue, just part of it.
> 
> 
> v6 has it as TpmCrq. It's local to the file, so from that perspective it's
> specific to (v)TPM.

Ok.

> > > This is a 1:1 copy from the existing TIS driver.
> > Hm, right.  Probably not a bad idea to move that out as a helper
> > function then.
> 
> 
> In V7 then.

Ok.

> > > > > +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;
> > > > Erk.  Updating DeviceClass structures on the fly is hideously ugly.
> > > > We might need to take a different approach for this.
> > > Make a suggestion... Obviously, we can hard-initialize dt_name and dt_type
> > > but dt_compatible can only be set after we have determined the version of
> > > TPM.
> > As you say name and type can just be put into the class statically.
> 
> 
> I did this in v6.
> 
> 
> > Since you need to change compatible based on an internal variable,
> > we'd need to replace the static dt_compatible in the class with a
> > callback.
> 
> 
> Why can we not initialize it once we know the version of TPM? From the
> perspective of SLOF at least this seems to be building the device tree fine
> since it sees the proper value...

Because it's a serious layering / isolation violation.  You're
modifying QOM type information from the runtime code of a specific
instance.  You get away with it (now) because there's only one
instance and the ordering of things happens to let it work, but that's
assuming way too much about QOM's implementation details.

As a rule, once the QOM classes are set up with their class_init
function, they should never be modified.
Stefan Berger Dec. 19, 2019, 1:59 a.m. UTC | #8
On 12/18/19 8:54 PM, David Gibson wrote:
> On Tue, Dec 17, 2019 at 02:44:04PM -0500, Stefan Berger wrote:
>> On 12/16/19 7:29 PM, David Gibson wrote:
>>
>>
>>> Since you need to change compatible based on an internal variable,
>>> we'd need to replace the static dt_compatible in the class with a
>>> callback.
>>
>> Why can we not initialize it once we know the version of TPM? From the
>> perspective of SLOF at least this seems to be building the device tree fine
>> since it sees the proper value...
> Because it's a serious layering / isolation violation.  You're
> modifying QOM type information from the runtime code of a specific
> instance.  You get away with it (now) because there's only one
> instance and the ordering of things happens to let it work, but that's
> assuming way too much about QOM's implementation details.
>
> As a rule, once the QOM classes are set up with their class_init
> function, they should never be modified.


If we now add a get_dt_compatible() callback to the class that gets 
invoked when dt_compatible is NULL, does this then solve the issue?
David Gibson Dec. 19, 2019, 5:13 a.m. UTC | #9
On Wed, Dec 18, 2019 at 08:59:18PM -0500, Stefan Berger wrote:
> On 12/18/19 8:54 PM, David Gibson wrote:
> > On Tue, Dec 17, 2019 at 02:44:04PM -0500, Stefan Berger wrote:
> > > On 12/16/19 7:29 PM, David Gibson wrote:
> > > 
> > > 
> > > > Since you need to change compatible based on an internal variable,
> > > > we'd need to replace the static dt_compatible in the class with a
> > > > callback.
> > > 
> > > Why can we not initialize it once we know the version of TPM? From the
> > > perspective of SLOF at least this seems to be building the device tree fine
> > > since it sees the proper value...
> > Because it's a serious layering / isolation violation.  You're
> > modifying QOM type information from the runtime code of a specific
> > instance.  You get away with it (now) because there's only one
> > instance and the ordering of things happens to let it work, but that's
> > assuming way too much about QOM's implementation details.
> > 
> > As a rule, once the QOM classes are set up with their class_init
> > function, they should never be modified.
> 
> 
> If we now add a get_dt_compatible() callback to the class that gets invoked
> when dt_compatible is NULL, does this then solve the issue?

Yes, that's what I'm suggesting.
David Gibson Dec. 19, 2019, 5:14 a.m. UTC | #10
On Thu, Dec 19, 2019 at 04:13:57PM +1100, David Gibson wrote:
> On Wed, Dec 18, 2019 at 08:59:18PM -0500, Stefan Berger wrote:
> > On 12/18/19 8:54 PM, David Gibson wrote:
> > > On Tue, Dec 17, 2019 at 02:44:04PM -0500, Stefan Berger wrote:
> > > > On 12/16/19 7:29 PM, David Gibson wrote:
> > > > 
> > > > 
> > > > > Since you need to change compatible based on an internal variable,
> > > > > we'd need to replace the static dt_compatible in the class with a
> > > > > callback.
> > > > 
> > > > Why can we not initialize it once we know the version of TPM? From the
> > > > perspective of SLOF at least this seems to be building the device tree fine
> > > > since it sees the proper value...
> > > Because it's a serious layering / isolation violation.  You're
> > > modifying QOM type information from the runtime code of a specific
> > > instance.  You get away with it (now) because there's only one
> > > instance and the ordering of things happens to let it work, but that's
> > > assuming way too much about QOM's implementation details.
> > > 
> > > As a rule, once the QOM classes are set up with their class_init
> > > function, they should never be modified.
> > 
> > 
> > If we now add a get_dt_compatible() callback to the class that gets invoked
> > when dt_compatible is NULL, does this then solve the issue?
> 
> Yes, that's what I'm suggesting.

Well, almost.  Actually I'd suggest the other way around - call the
callback method, but if that's NULL, fallback to the static value.
diff mbox series

Patch

diff --git a/hw/tpm/Kconfig b/hw/tpm/Kconfig
index 4c8ee87d67..66a570aac1 100644
--- a/hw/tpm/Kconfig
+++ b/hw/tpm/Kconfig
@@ -22,3 +22,9 @@  config TPM_EMULATOR
     bool
     default y
     depends on TPMDEV
+
+config TPM_SPAPR
+    bool
+    default n
+    select TPMDEV
+    depends on PSERIES
diff --git a/hw/tpm/Makefile.objs b/hw/tpm/Makefile.objs
index de0b85d02a..85eb99ae05 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_TPM_SPAPR) += 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'] }