/*****************************************************************************
 * 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);
	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 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;
}
