563 lines
14 KiB
C
563 lines
14 KiB
C
/*
|
|
* Copyright (C) 2018 Intel Corporation
|
|
* All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <pthread.h>
|
|
|
|
#include "vmmapi.h"
|
|
#include "inout.h"
|
|
#include "mem.h"
|
|
#include "tpm.h"
|
|
#include "tpm_internal.h"
|
|
|
|
static int tpm_crb_debug;
|
|
#define LOG_TAG "tpm_crb: "
|
|
#define DPRINTF(fmt, args...) \
|
|
do { if (tpm_crb_debug) printf(LOG_TAG "%s: " fmt, __func__, ##args); } while (0)
|
|
#define WPRINTF(fmt, args...) \
|
|
do { printf(LOG_TAG "%s: " fmt, __func__, ##args); } while (0)
|
|
|
|
#define __packed __attribute__((packed))
|
|
|
|
#define CRB_LOC_CTRL_REQUEST_ACCESS (1U << 0U)
|
|
#define CRB_LOC_CTRL_RELINQUISH (1U << 1U)
|
|
#define CRB_LOC_CTRL_SEIZE (1U << 2U)
|
|
#define CRB_LOC_CTRL_RESET_ESTABLISHMENT (1U << 3U)
|
|
|
|
#define CRB_CTRL_REQ_CMD_READY (1U << 0U)
|
|
#define CRB_CTRL_REQ_CMD_IDLE (1U << 1U)
|
|
|
|
#define CRB_CTRL_CANCEL_CMD 0x00000001U
|
|
#define CRB_CTRL_CMD_CANCELLED 0x00000000U
|
|
|
|
#define CRB_CTRL_START_CMD 0x00000001U
|
|
#define CRB_CTRL_CMD_COMPLETED 0x00000000U
|
|
|
|
struct locality_state {
|
|
uint32_t tpmEstablished : 1;
|
|
uint32_t locAssigned : 1;
|
|
uint32_t activeLocality : 3;
|
|
uint32_t reserved0 : 2;
|
|
uint32_t tpmRegValidSts : 1;
|
|
uint32_t reserved1 : 24;
|
|
} __packed;
|
|
|
|
struct locality_ctrl {
|
|
uint32_t requestAccess : 1;
|
|
uint32_t relinquish : 1;
|
|
uint32_t seize : 1;
|
|
uint32_t resetEstablishmentBit : 1;
|
|
uint32_t reserved : 28;
|
|
} __packed;
|
|
|
|
struct locality_sts {
|
|
uint32_t granted : 1;
|
|
uint32_t beenSeized : 1;
|
|
uint32_t reserved : 30;
|
|
} __packed;
|
|
|
|
struct interface_identifier {
|
|
struct {
|
|
uint32_t interfaceType : 4;
|
|
uint32_t interfaceVersion : 4;
|
|
uint32_t capLocality : 1;
|
|
uint32_t capCRBIdleBypass : 1;
|
|
uint32_t reserved0 : 1;
|
|
uint32_t capDataXferSizeSupport : 2;
|
|
uint32_t capFIFO : 1;
|
|
uint32_t capCRB : 1;
|
|
uint32_t capIFRes : 2;
|
|
uint32_t interfaceSelector : 2;
|
|
uint32_t intfSelLock : 1;
|
|
uint32_t reserved1 : 4;
|
|
uint32_t RID : 8;
|
|
} lo __packed;
|
|
|
|
struct {
|
|
uint32_t VID : 16;
|
|
uint32_t DID : 16;
|
|
} hi __packed;
|
|
} __packed;
|
|
|
|
struct control_area_ext {
|
|
uint32_t clear;
|
|
uint32_t remaining_bytes;
|
|
} __packed;
|
|
|
|
struct control_area_req {
|
|
uint32_t cmdReady : 1;
|
|
uint32_t goIdle : 1;
|
|
uint32_t reserved : 30;
|
|
} __packed;
|
|
|
|
struct control_area_sts {
|
|
uint32_t tpmSts : 1;
|
|
uint32_t tpmIdle : 1;
|
|
uint32_t reserved : 30;
|
|
} __packed;
|
|
|
|
struct interrupt_enable {
|
|
uint32_t startIntEnable : 1;
|
|
uint32_t cmdReadyIntEnable : 1;
|
|
uint32_t establishmentClearIntEnable : 1;
|
|
uint32_t localityChangeIntEnable : 1;
|
|
uint32_t reserved : 27;
|
|
uint32_t globalInterruptEnable : 1;
|
|
} __packed;
|
|
|
|
struct interrupt_status {
|
|
uint32_t startInt : 1;
|
|
uint32_t cmdReadyInt : 1;
|
|
uint32_t establishmentClearInt : 1;
|
|
uint32_t localityChangeInt : 1;
|
|
uint32_t reserved : 28;
|
|
} __packed;
|
|
|
|
struct crb_reg_space {
|
|
union {
|
|
struct {
|
|
struct locality_state loc_state;
|
|
uint32_t reserved0;
|
|
struct locality_ctrl loc_ctrl;
|
|
struct locality_sts loc_sts;
|
|
uint32_t reserved1[8];
|
|
struct interface_identifier intf_id;
|
|
struct control_area_ext ctrl_ext;
|
|
struct control_area_req ctrl_req;
|
|
struct control_area_sts ctrl_sts;
|
|
uint32_t ctrl_cancel;
|
|
uint32_t ctrl_start;
|
|
struct interrupt_enable int_enable;
|
|
struct interrupt_status int_status;
|
|
uint32_t ctrl_cmd_size;
|
|
uint32_t ctrl_cmd_addr_lo;
|
|
uint32_t ctrl_cmd_addr_hi;
|
|
uint32_t ctrl_rsp_size;
|
|
uint64_t ctrl_rsp_addr;
|
|
};
|
|
uint8_t bytes[TPM_CRB_REG_SIZE];
|
|
} regs;
|
|
} __packed;
|
|
|
|
/* TPM CRB virtual device structure */
|
|
struct tpm_crb_vdev {
|
|
struct crb_reg_space crb_regs;
|
|
uint8_t data_buffer[TPM_CRB_DATA_BUFFER_SIZE];
|
|
TPMCommBuffer cmd;
|
|
|
|
pthread_t request_thread;
|
|
pthread_mutex_t request_mutex;
|
|
pthread_cond_t request_cond;
|
|
};
|
|
|
|
static uint64_t mmio_read(void *addr, int size)
|
|
{
|
|
uint64_t val = 0;
|
|
switch (size) {
|
|
case 1:
|
|
val = *(uint8_t *)addr;
|
|
break;
|
|
case 2:
|
|
val = *(uint16_t *)addr;
|
|
break;
|
|
case 4:
|
|
val = *(uint32_t *)addr;
|
|
break;
|
|
case 8:
|
|
val = *(uint64_t *)addr;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static void mmio_write(void *addr, int size, uint64_t val)
|
|
{
|
|
switch (size) {
|
|
case 1:
|
|
*(uint8_t *)addr = val;
|
|
break;
|
|
case 2:
|
|
*(uint16_t *)addr = val;
|
|
break;
|
|
case 4:
|
|
*(uint32_t *)addr = val;
|
|
break;
|
|
case 8:
|
|
*(uint64_t *)addr = val;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static uint64_t crb_reg_read(struct tpm_crb_vdev *tpm_vdev, uint64_t addr, int size)
|
|
{
|
|
uint32_t val;
|
|
uint64_t off;
|
|
|
|
off = (addr & ~3UL) - TPM_CRB_MMIO_ADDR;
|
|
|
|
val = mmio_read(&tpm_vdev->crb_regs.regs.bytes[off], size);
|
|
|
|
if (addr == CRB_REGS_LOC_STATE) {
|
|
val |= !swtpm_get_tpm_established_flag();
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static void clear_data_buffer(struct tpm_crb_vdev *vdev)
|
|
{
|
|
memset(vdev->data_buffer, 0, sizeof(vdev->data_buffer));
|
|
}
|
|
|
|
static uint8_t get_active_locality(struct tpm_crb_vdev *vdev)
|
|
{
|
|
if (vdev->crb_regs.regs.loc_state.locAssigned == 0) {
|
|
return 0xFF;
|
|
}
|
|
|
|
return vdev->crb_regs.regs.loc_state.activeLocality;
|
|
}
|
|
|
|
static uint32_t get_tpm_cmd_size(void *data_buffer)
|
|
{
|
|
if (!data_buffer)
|
|
return 0;
|
|
/*
|
|
* The command header is formated by:
|
|
* tag (2 bytes): 80 01
|
|
* length (4 bytes): 00 00 00 00
|
|
* ordinal(4 bytes): 00 00 00 00
|
|
*/
|
|
return be32dec(data_buffer + 2);
|
|
}
|
|
|
|
static void tpm_crb_request_completed(struct tpm_crb_vdev *vdev, int err)
|
|
{
|
|
vdev->crb_regs.regs.ctrl_start = CRB_CTRL_CMD_COMPLETED;
|
|
if (err) {
|
|
/* Fatal error */
|
|
vdev->crb_regs.regs.ctrl_sts.tpmSts = 0b1;
|
|
}
|
|
}
|
|
|
|
static void tpm_crb_request_deliver(void *arg)
|
|
{
|
|
struct tpm_crb_vdev *tpm_vdev = (struct tpm_crb_vdev *)arg;
|
|
int ret;
|
|
|
|
while (1) {
|
|
ret = pthread_mutex_lock(&tpm_vdev->request_mutex);
|
|
if (ret) {
|
|
DPRINTF("ERROR: Failed to acquire mutex lock(%d)\n", ret);
|
|
break;
|
|
}
|
|
|
|
while (!ret &&
|
|
tpm_vdev->crb_regs.regs.ctrl_start == CRB_CTRL_CMD_COMPLETED) {
|
|
ret = pthread_cond_wait(
|
|
&tpm_vdev->request_cond, &tpm_vdev->request_mutex);
|
|
}
|
|
|
|
if (ret) {
|
|
DPRINTF("ERROR: Failed to wait condition(%d)\n", ret);
|
|
break;
|
|
}
|
|
|
|
ret = swtpm_handle_request(&tpm_vdev->cmd);
|
|
tpm_crb_request_completed(tpm_vdev, ret);
|
|
|
|
ret = pthread_mutex_unlock(&tpm_vdev->request_mutex);
|
|
if (ret) {
|
|
DPRINTF("ERROR: Failed to release mutex lock(%d)\n", ret);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void crb_reg_write(struct tpm_crb_vdev *tpm_vdev, uint64_t addr, int size, uint64_t val)
|
|
{
|
|
uint8_t target_loc = (addr >> 12) & 0b111; /* convert address to locality */
|
|
uint32_t cmd_size;
|
|
|
|
switch (addr) {
|
|
case CRB_REGS_CTRL_REQ:
|
|
if (tpm_vdev->crb_regs.regs.ctrl_start == CRB_CTRL_START_CMD)
|
|
break;
|
|
|
|
if (val == CRB_CTRL_REQ_CMD_READY) {
|
|
tpm_vdev->crb_regs.regs.ctrl_sts.tpmIdle = 0;
|
|
} else if (val == CRB_CTRL_REQ_CMD_IDLE) {
|
|
clear_data_buffer(tpm_vdev);
|
|
tpm_vdev->crb_regs.regs.ctrl_sts.tpmIdle = 1;
|
|
}
|
|
break;
|
|
case CRB_REGS_CTRL_CANCEL:
|
|
if ((val == CRB_CTRL_CANCEL_CMD) &&
|
|
(tpm_vdev->crb_regs.regs.ctrl_sts.tpmIdle != 1) &&
|
|
(tpm_vdev->crb_regs.regs.ctrl_start == CRB_CTRL_START_CMD)) {
|
|
swtpm_cancel_cmd();
|
|
}
|
|
break;
|
|
case CRB_REGS_CTRL_START:
|
|
if ((val == CRB_CTRL_START_CMD) &&
|
|
(tpm_vdev->crb_regs.regs.ctrl_start != CRB_CTRL_START_CMD) &&
|
|
(tpm_vdev->crb_regs.regs.ctrl_sts.tpmIdle != 1) &&
|
|
(get_active_locality(tpm_vdev) == target_loc)) {
|
|
|
|
if (pthread_mutex_lock(&tpm_vdev->request_mutex)) {
|
|
DPRINTF("ERROR: Failed to acquire mutex lock\n");
|
|
break;
|
|
}
|
|
|
|
tpm_vdev->crb_regs.regs.ctrl_start = CRB_CTRL_START_CMD;
|
|
cmd_size = MIN(get_tpm_cmd_size(tpm_vdev->data_buffer),
|
|
TPM_CRB_DATA_BUFFER_SIZE);
|
|
|
|
tpm_vdev->cmd.locty = 0;
|
|
tpm_vdev->cmd.in = &tpm_vdev->data_buffer[0];
|
|
tpm_vdev->cmd.in_len = cmd_size;
|
|
tpm_vdev->cmd.out = &tpm_vdev->data_buffer[0];
|
|
tpm_vdev->cmd.out_len = TPM_CRB_DATA_BUFFER_SIZE;
|
|
|
|
if (pthread_cond_signal(&tpm_vdev->request_cond)) {
|
|
DPRINTF("ERROR: Failed to wait condition\n");
|
|
}
|
|
|
|
if (pthread_mutex_unlock(&tpm_vdev->request_mutex)) {
|
|
DPRINTF("ERROR: Failed to release mutex lock\n");
|
|
}
|
|
}
|
|
break;
|
|
case CRB_REGS_LOC_CTRL:
|
|
switch (val) {
|
|
case CRB_LOC_CTRL_RESET_ESTABLISHMENT:
|
|
break;
|
|
case CRB_LOC_CTRL_RELINQUISH:
|
|
tpm_vdev->crb_regs.regs.loc_state.locAssigned = 0;
|
|
tpm_vdev->crb_regs.regs.loc_sts.granted = 0;
|
|
break;
|
|
case CRB_LOC_CTRL_REQUEST_ACCESS:
|
|
tpm_vdev->crb_regs.regs.loc_sts.granted = 1;
|
|
tpm_vdev->crb_regs.regs.loc_sts.beenSeized = 0;
|
|
tpm_vdev->crb_regs.regs.loc_state.locAssigned = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int tpm_crb_reg_handler(struct vmctx *ctx, int vcpu, int dir, uint64_t addr,
|
|
int size, uint64_t *val, void *arg1, long arg2)
|
|
{
|
|
struct tpm_crb_vdev *tpm_vdev;
|
|
tpm_vdev = (struct tpm_crb_vdev *)arg1;
|
|
|
|
if (dir == MEM_F_READ) {
|
|
*val = crb_reg_read(tpm_vdev, addr, size);
|
|
} else {
|
|
crb_reg_write(tpm_vdev, addr, size, *val);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tpm_crb_data_buffer_handler(struct vmctx *ctx, int vcpu, int dir, uint64_t addr,
|
|
int size, uint64_t *val, void *arg1, long arg2)
|
|
{
|
|
struct tpm_crb_vdev *tpm_vdev;
|
|
uint64_t off;
|
|
|
|
tpm_vdev = (struct tpm_crb_vdev *)arg1;
|
|
if (tpm_vdev->crb_regs.regs.ctrl_sts.tpmIdle == 1)
|
|
return 0;
|
|
|
|
off = addr - CRB_DATA_BUFFER;
|
|
|
|
if (dir == MEM_F_READ) {
|
|
*val = mmio_read(&tpm_vdev->data_buffer[off], size);
|
|
} else {
|
|
mmio_write(&tpm_vdev->data_buffer[off], size, *val);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define CRB_INTF_ID_TYPE_CRB_ACTIVE 0b0001
|
|
#define CRB_INTF_VERSION 0b0001
|
|
#define CRB_INTF_CAP_LOC_0_ONLY 0b0
|
|
#define CRB_INTF_CAP_FAST_IDLE 0b0
|
|
#define CRB_INTF_CAP_DATAXFER_SIZE_64 0b11
|
|
#define CRB_INTF_CAP_FIFO_NOT_SUPPORTED 0b0
|
|
#define CRB_INTF_CAP_CRB_SUPPORTED 0b1
|
|
#define CRB_INTF_CAP_INTERFACE_SEL_CRB 0b01
|
|
#define CRB_INTF_REVISION_ID 0b0000
|
|
#define CRB_INTF_VENDOR_ID 0x8086
|
|
static int tpm_crb_reset(void *dev)
|
|
{
|
|
struct tpm_crb_vdev *tpm_vdev = (struct tpm_crb_vdev *)dev;
|
|
|
|
memset(&tpm_vdev->crb_regs, 0, sizeof(tpm_vdev->crb_regs));
|
|
|
|
tpm_vdev->crb_regs.regs.loc_state.tpmRegValidSts = 1U;
|
|
tpm_vdev->crb_regs.regs.ctrl_sts.tpmIdle = 1U;
|
|
tpm_vdev->crb_regs.regs.intf_id.lo.interfaceType = CRB_INTF_ID_TYPE_CRB_ACTIVE;
|
|
tpm_vdev->crb_regs.regs.intf_id.lo.interfaceVersion = CRB_INTF_VERSION;
|
|
tpm_vdev->crb_regs.regs.intf_id.lo.capLocality = CRB_INTF_CAP_LOC_0_ONLY;
|
|
tpm_vdev->crb_regs.regs.intf_id.lo.capCRBIdleBypass = CRB_INTF_CAP_FAST_IDLE;
|
|
tpm_vdev->crb_regs.regs.intf_id.lo.capDataXferSizeSupport = CRB_INTF_CAP_DATAXFER_SIZE_64;
|
|
tpm_vdev->crb_regs.regs.intf_id.lo.capFIFO = CRB_INTF_CAP_FIFO_NOT_SUPPORTED;
|
|
tpm_vdev->crb_regs.regs.intf_id.lo.capCRB = CRB_INTF_CAP_CRB_SUPPORTED;
|
|
tpm_vdev->crb_regs.regs.intf_id.lo.interfaceSelector = CRB_INTF_CAP_INTERFACE_SEL_CRB;
|
|
tpm_vdev->crb_regs.regs.intf_id.lo.RID = CRB_INTF_REVISION_ID;
|
|
tpm_vdev->crb_regs.regs.intf_id.hi.VID = CRB_INTF_VENDOR_ID;
|
|
|
|
tpm_vdev->crb_regs.regs.ctrl_cmd_size = TPM_CRB_DATA_BUFFER_SIZE;
|
|
tpm_vdev->crb_regs.regs.ctrl_cmd_addr_lo = CRB_DATA_BUFFER;
|
|
tpm_vdev->crb_regs.regs.ctrl_rsp_size = TPM_CRB_DATA_BUFFER_SIZE;
|
|
tpm_vdev->crb_regs.regs.ctrl_rsp_addr = CRB_DATA_BUFFER;
|
|
|
|
/* Emulator startup */
|
|
if (swtpm_startup(TPM_CRB_DATA_BUFFER_SIZE)) {
|
|
WPRINTF("Failed to startup TPM emulator!\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int init_tpm_crb(struct vmctx *ctx)
|
|
{
|
|
struct mem_range mr_cmd, mr_data;
|
|
int error;
|
|
struct tpm_crb_vdev *tpm_vdev;
|
|
|
|
tpm_vdev = calloc(1, sizeof(struct tpm_crb_vdev));
|
|
if (tpm_vdev == NULL) {
|
|
WPRINTF("Failed alloc resource tpm device\n");
|
|
goto fail;
|
|
}
|
|
|
|
ctx->tpm_dev = tpm_vdev;
|
|
|
|
mr_cmd.name = "tpm_crb_reg";
|
|
mr_cmd.base = TPM_CRB_MMIO_ADDR;
|
|
mr_cmd.size = TPM_CRB_REG_SIZE;
|
|
mr_cmd.flags = MEM_F_RW;
|
|
mr_cmd.handler = tpm_crb_reg_handler;
|
|
mr_cmd.arg1 = tpm_vdev;
|
|
mr_cmd.arg2 = 0;
|
|
|
|
error = register_mem(&mr_cmd);
|
|
if (error) {
|
|
WPRINTF("Failed register command area for vTPM\n");
|
|
goto fail;
|
|
}
|
|
|
|
mr_data.name = "tpm_crb_buffer";
|
|
mr_data.base = CRB_DATA_BUFFER;
|
|
mr_data.size = TPM_CRB_DATA_BUFFER_SIZE;
|
|
mr_data.flags = MEM_F_RW;
|
|
mr_data.handler = tpm_crb_data_buffer_handler;
|
|
mr_data.arg1 = tpm_vdev;
|
|
mr_data.arg2 = 0;
|
|
|
|
error = register_mem(&mr_data);
|
|
if (error) {
|
|
WPRINTF("Failed register data area for vTPM\n");
|
|
goto fail_reset;
|
|
}
|
|
|
|
error = tpm_crb_reset(tpm_vdev);
|
|
if (error) {
|
|
WPRINTF("Failed reset vtpm device!\n");
|
|
goto fail_reset;
|
|
}
|
|
|
|
error = pthread_mutex_init(&tpm_vdev->request_mutex, NULL);
|
|
if (error) {
|
|
WPRINTF("Failed init mutex!\n");
|
|
goto fail_mutex;
|
|
}
|
|
|
|
error = pthread_cond_init(&tpm_vdev->request_cond, NULL);
|
|
if (error) {
|
|
WPRINTF("Failed init condition!\n");
|
|
goto fail_cond;
|
|
}
|
|
|
|
error = pthread_create(&tpm_vdev->request_thread, NULL, (void *)&tpm_crb_request_deliver, (void *)tpm_vdev);
|
|
if (error) {
|
|
WPRINTF("Failed init request thread!\n");
|
|
goto fail_thread;
|
|
}
|
|
pthread_setname_np(tpm_vdev->request_thread, "tpm_crb_deli");
|
|
|
|
return 0;
|
|
|
|
fail_thread:
|
|
pthread_cond_destroy(&tpm_vdev->request_cond);
|
|
|
|
fail_cond:
|
|
pthread_mutex_destroy(&tpm_vdev->request_mutex);
|
|
|
|
fail_mutex:
|
|
unregister_mem(&mr_data);
|
|
|
|
fail_reset:
|
|
unregister_mem(&mr_cmd);
|
|
|
|
fail:
|
|
if (ctx->tpm_dev)
|
|
free(ctx->tpm_dev);
|
|
ctx->tpm_dev = NULL;
|
|
|
|
return -1;
|
|
}
|
|
|
|
void deinit_tpm_crb(struct vmctx *ctx)
|
|
{
|
|
struct mem_range mr;
|
|
struct tpm_crb_vdev *tpm_vdev = (struct tpm_crb_vdev *)ctx->tpm_dev;
|
|
void *status;
|
|
|
|
mr.name = "tpm_crb_reg";
|
|
mr.base = TPM_CRB_MMIO_ADDR;
|
|
mr.size = TPM_CRB_REG_SIZE;
|
|
unregister_mem(&mr);
|
|
|
|
mr.name = "tpm_crb_buffer";
|
|
mr.base = CRB_DATA_BUFFER;
|
|
mr.size = TPM_CRB_DATA_BUFFER_SIZE;
|
|
unregister_mem(&mr);
|
|
|
|
pthread_cancel(tpm_vdev->request_thread);
|
|
pthread_join(tpm_vdev->request_thread, &status);
|
|
if (status != PTHREAD_CANCELED) {
|
|
WPRINTF("Failed to cancel TPM command request thread!\n");
|
|
}
|
|
|
|
pthread_cond_destroy(&tpm_vdev->request_cond);
|
|
pthread_mutex_destroy(&tpm_vdev->request_mutex);
|
|
|
|
if (ctx->tpm_dev) {
|
|
free(ctx->tpm_dev);
|
|
ctx->tpm_dev = NULL;
|
|
}
|
|
}
|