aboutsummaryrefslogtreecommitdiff
path: root/lib/libtpm/tpm_drivers.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libtpm/tpm_drivers.c')
-rw-r--r--lib/libtpm/tpm_drivers.c437
1 files changed, 437 insertions, 0 deletions
diff --git a/lib/libtpm/tpm_drivers.c b/lib/libtpm/tpm_drivers.c
new file mode 100644
index 0000000..5005176
--- /dev/null
+++ b/lib/libtpm/tpm_drivers.c
@@ -0,0 +1,437 @@
+/*****************************************************************************
+ * Copyright (c) 2015-2020 IBM Corporation
+ * All rights reserved.
+ * This program and the accompanying materials
+ * are made available under the terms of the BSD License
+ * which accompanies this distribution, and is available at
+ * http://www.opensource.org/licenses/bsd-license.php
+ *
+ * Contributors:
+ * IBM Corporation - initial implementation
+ *****************************************************************************/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+#include "string.h"
+#include "helpers.h"
+#include "byteorder.h"
+#include "tcgbios_int.h"
+#include "tpm_drivers.h"
+#include "libhvcall.h"
+#include "paflof.h"
+
+#undef PAPR_VTPM_DEBUG
+//#define PAPR_VTPM_DEBUG
+#ifdef PAPR_VTPM_DEBUG
+#define dprintf(_x ...) do { printf("VTPM CRQ: " _x); } while(0)
+#else
+#define dprintf(_x ...)
+#endif
+
+#define MIN(a, b) ((a) > (b) ? (b) : (a))
+
+/* layout of the command request queue for vTPM; all fields are big endian */
+struct crq {
+ uint8_t valid;
+ uint8_t msg;
+ uint16_t len;
+ uint32_t data;
+ uint64_t reserved;
+} __attribute__((packed));
+
+#define PAPR_VTPM_INIT_CRQ_COMMAND 0xC0
+#define PAPR_VTPM_VALID_COMMAND 0x80
+#define PAPR_VTPM_MSG_RESULT 0x80
+
+/* crq.msg request types when crq.valid = PAPR_VTPM_INIT_CRQ_COMMAND */
+#define PAPR_VTPM_INIT_CRQ_RESULT 0x1
+
+/* crq.msg request types when crq.valid = PAPR_VTPM_VALID_COMMAND */
+#define PAPR_VTPM_GET_VERSION 0x1
+#define PAPR_VTPM_TPM_COMMAND 0x2
+#define PAPR_VTPM_GET_RTCE_BUFFER_SIZE 0x3
+
+#define TPM2_DEFAULT_DURATION_SHORT 750000 /* us */
+#define TPM2_DEFAULT_DURATION_MEDIUM 2000000 /* us */
+#define TPM2_DEFAULT_DURATION_LONG 2000000 /* us */
+
+static const uint32_t tpm2_durations[3] = {
+ TPM2_DEFAULT_DURATION_SHORT,
+ TPM2_DEFAULT_DURATION_MEDIUM,
+ TPM2_DEFAULT_DURATION_LONG,
+};
+
+#define QUEUE_SIZE 4096
+
+/* state of the PAPR CRQ VTPM driver */
+static struct {
+ /* whether it driver been initialized */
+ bool initialized;
+
+ /* unit number */
+ unsigned long unit;
+
+ /* CRQ queue address and size */
+ unsigned char *qaddr;
+ unsigned long qsize;
+
+ /* current q_entry */
+ unsigned int curr_q_entry;
+
+ /* current response CRQ */
+ struct crq *response;
+
+ /* power firmware defined state and error code */
+ vtpm_drv_state driver_state;
+ vtpm_drv_error driver_error;
+
+ /* size of buffer supported by hypervisor */
+ unsigned int buffer_size;
+
+ /* buffer for commands and responses */
+ char *buffer;
+} spapr_vtpm = {
+ .qsize = QUEUE_SIZE,
+ .driver_state = VTPM_DRV_STATE_INVALID,
+ .driver_error = VTPM_DRV_ERROR_NO_FAILURE,
+};
+
+static void vtpm_drv_state_set(vtpm_drv_state s, vtpm_drv_error e)
+{
+ spapr_vtpm.driver_state = s;
+ spapr_vtpm.driver_error = e;
+}
+
+static vtpm_drv_error vtpm_drv_error_get(void)
+{
+ return spapr_vtpm.driver_error;
+}
+
+static struct crq *spapr_get_crq(void *qaddr, unsigned long q_entry)
+{
+ return &((struct crq *)qaddr)[q_entry];
+}
+
+/*
+ * Get the crq where the response will be found. This
+ * function will clear the CRQ's valid field and advance
+ * the entry counter to the next entry.
+ */
+static struct crq *spapr_get_response_crq(void)
+{
+ struct crq *crq;
+
+ dprintf("curr_q_entry = %d\n", spapr_vtpm.curr_q_entry);
+
+ crq = spapr_get_crq(spapr_vtpm.qaddr, spapr_vtpm.curr_q_entry);
+ memset(crq, 0, sizeof(*crq));
+
+ spapr_vtpm.curr_q_entry += 1;
+ if (spapr_vtpm.curr_q_entry == (spapr_vtpm.qsize / sizeof(struct crq)))
+ spapr_vtpm.curr_q_entry = 0;
+
+ return crq;
+}
+
+/*
+ * Send a message via CRQ and wait for the response
+ */
+static bool spapr_send_crq_and_wait(unsigned long unit,
+ struct crq *crq,
+ struct crq **response,
+ unsigned timeout,
+ vtpm_drv_state state1,
+ vtpm_drv_state state2)
+{
+ long rc;
+ unsigned i;
+
+ *response = spapr_get_response_crq();
+
+ vtpm_drv_state_set(state1, VTPM_DRV_ERROR_NO_FAILURE);
+
+ rc = hv_send_crq(unit, (uint64_t *)crq);
+ if (rc != H_SUCCESS) {
+ vtpm_drv_state_set(VTPM_DRV_STATE_WAIT_INIT,
+ VTPM_DRV_ERROR_TPM_CRQ_ERROR);
+ return false;
+ }
+
+ vtpm_drv_state_set(state2, VTPM_DRV_ERROR_NO_FAILURE);
+
+ for (i = 0; i < timeout; i += 1000) {
+ if (((*response)->valid & PAPR_VTPM_MSG_RESULT))
+ return true;
+ SLOF_usleep(1000);
+ }
+
+ vtpm_drv_state_set(VTPM_DRV_STATE_FAILURE,
+ VTPM_DRV_ERROR_WAIT_TIMEOUT);
+
+ dprintf("Received no response from CRQ\n");
+ return false;
+}
+
+/*
+ * Get parameters from the CRQ
+ */
+static bool spapr_vtpm_get_params(void)
+{
+ struct crq crq, *response;
+ static bool completed = false; /* only once */
+
+ if (completed)
+ return true;
+
+ /* get the TPM's buffer size */
+ crq.valid = PAPR_VTPM_VALID_COMMAND;
+ crq.msg = PAPR_VTPM_GET_RTCE_BUFFER_SIZE;
+
+ if (!spapr_send_crq_and_wait(spapr_vtpm.unit, &crq, &response, 10,
+ VTPM_DRV_STATE_SEND_BUFSIZE_REQ,
+ VTPM_DRV_STATE_WAIT_BUFSIZE)) {
+ printf("%s: Failure getting RTCE buffer size from CRQ\n",
+ __func__);
+ return false;
+ }
+
+ vtpm_drv_state_set(VTPM_DRV_STATE_ALLOC_RTCE_BUF,
+ VTPM_DRV_ERROR_NO_FAILURE);
+
+ dprintf("RTCE buffer size: %u\n", be16_to_cpu(response->len));
+ spapr_vtpm.buffer_size = be16_to_cpu(response->len);
+ if (spapr_vtpm.buffer_size < 1024) {
+ printf("%s: RTCE buffer size of %u bytes is too small. "
+ "Minimum is 1024 bytes.\n", __func__,
+ spapr_vtpm.buffer_size);
+ vtpm_drv_state_set(VTPM_DRV_STATE_FAILURE,
+ VTPM_DRV_ERROR_BAD_RTCE_SIZE);
+ return false;
+ }
+ spapr_vtpm.buffer = SLOF_alloc_mem(spapr_vtpm.buffer_size);
+ if (!spapr_vtpm.buffer) {
+ printf("%s: Could not allocate buffer of size %u.\n",
+ __func__, spapr_vtpm.buffer_size);
+ vtpm_drv_state_set(VTPM_DRV_STATE_FAILURE,
+ VTPM_DRV_ERROR_BAD_RTCE_SIZE);
+ return false;
+ }
+
+ completed = true;
+
+ return true;
+}
+
+static bool spapr_vtpm_activate(void)
+{
+ long rc;
+ struct crq crq, *response;
+
+ if (vtpm_drv_error_get() != VTPM_DRV_ERROR_NO_FAILURE) {
+ printf("%s: CRQ: In failure mode\n", __func__);
+ return false;
+ }
+
+ vtpm_drv_state_set(VTPM_DRV_STATE_REG_CRQ,
+ VTPM_DRV_ERROR_NO_FAILURE);
+
+ rc = hv_reg_crq(spapr_vtpm.unit, (unsigned long)spapr_vtpm.qaddr,
+ spapr_vtpm.qsize);
+ if (rc != H_SUCCESS) {
+ vtpm_drv_state_set(VTPM_DRV_STATE_WAIT_INIT,
+ VTPM_DRV_ERROR_UNEXPECTED_REG_ERROR);
+ printf("%s: CRQ registration failed\n", __func__);
+ return false;
+ }
+
+ /* we always start with curr_q_entry 0 */
+ spapr_vtpm.curr_q_entry = 0;
+
+ if (!spapr_vtpm.initialized) {
+
+ crq.valid = PAPR_VTPM_INIT_CRQ_COMMAND;
+ crq.msg = PAPR_VTPM_INIT_CRQ_RESULT;
+
+ if (!spapr_send_crq_and_wait(spapr_vtpm.unit,
+ &crq,
+ &response,
+ 10,
+ VTPM_DRV_STATE_SEND_INIT,
+ VTPM_DRV_STATE_WAIT_INIT_COMP)) {
+ printf("%s: Initializing CRQ failed\n", __func__);
+ goto err_exit;
+ }
+ dprintf("Successfully initialized CRQ\n");
+
+ spapr_vtpm.initialized = true;
+ }
+
+ if (spapr_vtpm_get_params())
+ return true;
+
+err_exit:
+ hv_free_crq(spapr_vtpm.unit);
+ spapr_vtpm.unit = 0;
+
+ return false;
+}
+
+void spapr_vtpm_finalize(void)
+{
+ if (spapr_vtpm.unit) {
+ hv_free_crq(spapr_vtpm.unit);
+ spapr_vtpm.unit = 0;
+ }
+}
+
+/*
+ * Check whether we have a CRQ underneath us; if we do, the CRQ will
+ * be left open.
+ */
+static bool spapr_vtpm_probe(void)
+{
+ if (!spapr_vtpm.qaddr) {
+ spapr_vtpm.qaddr = SLOF_alloc_mem(spapr_vtpm.qsize);
+ if (!spapr_vtpm.qaddr) {
+ printf("%s: Unable to allocate memory\n", __func__);
+ return false;
+ }
+ memset(spapr_vtpm.qaddr, 0, spapr_vtpm.qsize);
+
+ dprintf("getting FORTH vtpm-unit\n");
+ spapr_vtpm.unit = SLOF_get_vtpm_unit();
+ if (!spapr_vtpm.unit) {
+ printf("%s: Could not get valid vtpm-unit\n", __func__);
+ return false;
+ }
+ }
+
+ dprintf("vtpm_unit = %lx, buffer = %p\n",
+ spapr_vtpm.unit, spapr_vtpm.qaddr);
+
+ if (!spapr_vtpm_activate())
+ return false;
+
+ return true;
+}
+
+static bool spapr_vtpm_senddata(const uint8_t *const data, uint32_t len)
+{
+ struct crq crq;
+ long rc;
+
+ if (vtpm_drv_error_get() != VTPM_DRV_ERROR_NO_FAILURE) {
+ printf("%s: VTPM CRQ: In failure mode\n", __func__);
+ return false;
+ }
+
+ if (len > spapr_vtpm.buffer_size) {
+ printf("%s: VTPM CRQ: Send buffer too large: %u > %u\n",
+ __func__, len, spapr_vtpm.buffer_size);
+ return false;
+ }
+
+ spapr_vtpm.response = spapr_get_response_crq();
+ spapr_vtpm.response->data = (uint64_t)spapr_vtpm.buffer;
+
+ crq.valid = PAPR_VTPM_VALID_COMMAND;
+ crq.msg = PAPR_VTPM_TPM_COMMAND;
+ crq.len = cpu_to_be16(len);
+ crq.data = (uint64_t)spapr_vtpm.buffer;
+ memcpy(spapr_vtpm.buffer, data, MIN(len, spapr_vtpm.buffer_size));
+
+ vtpm_drv_state_set(VTPM_DRV_STATE_SEND_TPM_CMD,
+ VTPM_DRV_ERROR_NO_FAILURE);
+
+ rc = hv_send_crq(spapr_vtpm.unit, (uint64_t *)&crq);
+
+ if (rc == H_SUCCESS)
+ vtpm_drv_state_set(VTPM_DRV_STATE_WAIT_TPM_RSP,
+ VTPM_DRV_ERROR_NO_FAILURE);
+ else
+ vtpm_drv_state_set(VTPM_DRV_STATE_WAIT_INIT,
+ VTPM_DRV_ERROR_UNEXPECTED_SEND_ERROR);
+
+ return (rc == H_SUCCESS);
+}
+
+static bool spapr_vtpm_waitresponseready(enum tpm_duration_type to_t)
+{
+ uint32_t timeout = tpm2_durations[to_t];
+ int i;
+
+ if (vtpm_drv_error_get() != VTPM_DRV_ERROR_NO_FAILURE) {
+ printf("%s: VTPM CRQ: In failure mode\n", __func__);
+ return false;
+ }
+
+ for (i = 0; i < timeout; i += 1000) {
+ if (spapr_vtpm.response->valid & PAPR_VTPM_MSG_RESULT) {
+ /* TPM responded: move to Send tpm-cmd state */
+ vtpm_drv_state_set(VTPM_DRV_STATE_SEND_TPM_CMD,
+ VTPM_DRV_ERROR_NO_FAILURE);
+ dprintf("Received response to TPM command\n");
+ return true;
+ }
+ SLOF_usleep(1000);
+ }
+
+ vtpm_drv_state_set(VTPM_DRV_STATE_FAILURE,
+ VTPM_DRV_ERROR_WAIT_TIMEOUT);
+
+ dprintf("Received NO response to TPM command");
+
+ return false;
+}
+
+static bool spapr_vtpm_readresponse(uint8_t *buffer, uint32_t *len)
+{
+ uint32_t length;
+
+ if (vtpm_drv_error_get() != VTPM_DRV_ERROR_NO_FAILURE) {
+ printf("%s: VTPM CRQ: In failure mode\n", __func__);
+ return false;
+ }
+
+ length = MIN(*len, be32_to_cpu(spapr_vtpm.response->len));
+
+ memcpy(buffer, (void *)(uint64_t)spapr_vtpm.response->data, length);
+
+ dprintf("Length of copied response: %d\n", length);
+
+ spapr_vtpm.response = NULL;
+ *len = length;
+
+ return true;
+}
+
+/**** higher layer interface ****/
+
+vtpm_drv_error spapr_vtpm_get_error(void)
+{
+ return vtpm_drv_error_get();
+}
+
+void spapr_vtpm_set_error(vtpm_drv_error errcode)
+{
+ spapr_vtpm.driver_error = errcode;
+}
+
+bool spapr_is_vtpm_present(void)
+{
+ return spapr_vtpm_probe();
+}
+
+int spapr_transmit(uint8_t locty, struct tpm_req_header *req,
+ void *respbuffer, uint32_t *respbufferlen,
+ enum tpm_duration_type to_t)
+{
+ if (!spapr_vtpm_senddata((uint8_t *)req, be32_to_cpu(req->totlen)) ||
+ !spapr_vtpm_waitresponseready(to_t) ||
+ !spapr_vtpm_readresponse(respbuffer, respbufferlen) ||
+ *respbufferlen < sizeof(struct tpm_rsp_header))
+ return -1;
+ return 0;
+}