diff options
author | Angelos Mouzakitis <a.mouzakitis@virtualopensystems.com> | 2023-10-10 14:33:42 +0000 |
---|---|---|
committer | Angelos Mouzakitis <a.mouzakitis@virtualopensystems.com> | 2023-10-10 14:33:42 +0000 |
commit | af1a266670d040d2f4083ff309d732d648afba2a (patch) | |
tree | 2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/SLOF/lib/libtpm/tpm_drivers.c | |
parent | e02cda008591317b1625707ff8e115a4841aa889 (diff) |
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/SLOF/lib/libtpm/tpm_drivers.c')
-rw-r--r-- | roms/SLOF/lib/libtpm/tpm_drivers.c | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/roms/SLOF/lib/libtpm/tpm_drivers.c b/roms/SLOF/lib/libtpm/tpm_drivers.c new file mode 100644 index 000000000..4a4fde89a --- /dev/null +++ b/roms/SLOF/lib/libtpm/tpm_drivers.c @@ -0,0 +1,436 @@ +/***************************************************************************** + * 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 + +/* 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->valid); + 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.valid); + + 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 i, timeout = tpm2_durations[to_t]; + + 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 (locty) + return -1; + 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; +} |