/*
 * Copyright (C) 2018-2022 Intel Corporation.
 *
 * 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"
#include "log.h"

static int tpm_crb_debug;
#define LOG_TAG "tpm_crb: "
#define DPRINTF(fmt, args...) \
	do { if (tpm_crb_debug) pr_dbg(LOG_TAG "%s: " fmt, __func__, ##args); } while (0)
#define WPRINTF(fmt, args...) \
	do { pr_err(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) - get_vtpm_crb_mmio_addr();

	val = mmio_read(&tpm_vdev->crb_regs.regs.bytes[off], size);

	if (off == 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;
	uint64_t off = addr - get_vtpm_crb_mmio_addr();

	switch (off) {
	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 - get_vtpm_crb_mmio_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 = get_vtpm_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 = get_vtpm_crb_mmio_addr() + 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 = get_vtpm_crb_mmio_addr();
	mr.size = TPM_CRB_REG_SIZE;
	unregister_mem(&mr);

	mr.name = "tpm_crb_buffer";
	mr.base = get_vtpm_crb_mmio_addr() + 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;
	}
}
