/* Copyright 2014-2015 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <errorlog.h>
#include <fsp.h>
#include <ipmi.h>
#include <lock.h>
#include <opal-api.h>

/*
 * Under the hood, FSP IPMI component implements the KCS (Keyboard Controller
 * Style) interface
 *
 * KCS interface request message format
 *
 *    BYTE 1	 BYTE 2	       BYTE 3:N
 *  -------------------------------------
 * | NetFn/LUN |    Cmd    |    Data     |
 *  -------------------------------------
 *
 * KCS interface response message format
 *
 *    BYTE 1	 BYTE 2		BYTE 3	  BYTE 4:N
 *  ------------------------------------------------
 * | NetFn/LUN |    Cmd    |  CompCode  |   Data    |
 *  ------------------------------------------------

 */

#define FSP_IPMI_REQ_MIN_LEN	2 /* NetFn + Cmd */
#define FSP_IPMI_RESP_MIN_LEN	3 /* NetFn + Cmd + Completion code */

DEFINE_LOG_ENTRY(OPAL_RC_IPMI_REQ, OPAL_PLATFORM_ERR_EVT, OPAL_IPMI,
		 OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL,
		 OPAL_NA);
DEFINE_LOG_ENTRY(OPAL_RC_IPMI_RESP, OPAL_PLATFORM_ERR_EVT, OPAL_IPMI,
		 OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL,
		 OPAL_NA);

DEFINE_LOG_ENTRY(OPAL_RC_IPMI_DMA_ERROR_RESP, OPAL_PLATFORM_ERR_EVT, OPAL_IPMI,
		 OPAL_PLATFORM_FIRMWARE, OPAL_INFO,
		 OPAL_NA);

struct fsp_ipmi_msg {
	struct list_node	link;
	struct ipmi_msg		ipmi_msg;
};

static struct fsp_ipmi {
	struct list_head	msg_queue;
	void			*ipmi_req_buf;
	void			*ipmi_resp_buf;
	/* There can only be one outstanding request whose reference is stored
	 * in 'cur_msg' and the 'lock' protects against the concurrent updates
	 * of it through request and response. The same 'lock' also protects
	 * the list manipulation.
	 */
	struct fsp_ipmi_msg	*cur_msg;
	struct lock		lock;
} fsp_ipmi;

static int fsp_ipmi_send_request(void);

static void fsp_ipmi_cmd_done(uint8_t cmd, uint8_t netfn, uint8_t cc)
{
	struct fsp_ipmi_msg *fsp_ipmi_msg = fsp_ipmi.cur_msg;

	lock(&fsp_ipmi.lock);
	list_del(&fsp_ipmi_msg->link);
	fsp_ipmi.cur_msg = NULL;
	unlock(&fsp_ipmi.lock);

	ipmi_cmd_done(cmd, netfn, cc, &fsp_ipmi_msg->ipmi_msg);
}


static void fsp_ipmi_req_complete(struct fsp_msg *msg)
{
	uint8_t status = (msg->resp->word1 >> 8) & 0xff;
	uint32_t length = msg->resp->data.words[0];
	struct fsp_ipmi_msg *fsp_ipmi_msg = msg->user_data;
	struct ipmi_msg *ipmi_msg;

	fsp_freemsg(msg);

	if (status != FSP_STATUS_SUCCESS) {
		assert(fsp_ipmi_msg == fsp_ipmi.cur_msg);

		ipmi_msg = &fsp_ipmi_msg->ipmi_msg;

		if (length != (ipmi_msg->req_size + FSP_IPMI_REQ_MIN_LEN))
			prlog(PR_DEBUG, "IPMI: Length mismatch in req completion "
			      "(%d, %d)\n", ipmi_msg->req_size, length);

		log_simple_error(&e_info(OPAL_RC_IPMI_REQ), "IPMI: Request "
				 "failed with status:0x%02x\n", status);
		/* FSP will not send the response now, so clear the current
		 * outstanding request
		 */
		fsp_ipmi_cmd_done(ipmi_msg->cmd,
				  IPMI_NETFN_RETURN_CODE(ipmi_msg->netfn),
				  IPMI_ERR_UNSPECIFIED);

		/* Send the next request in the queue */
		fsp_ipmi_send_request();
	}
}

static int fsp_ipmi_send_request(void)
{
	uint8_t *req_buf = fsp_ipmi.ipmi_req_buf;
	struct ipmi_msg *ipmi_msg;
	struct fsp_msg *msg;
	int rc;

	if (fsp_in_rr())
		return OPAL_BUSY;

	lock(&fsp_ipmi.lock);
	/* An outstanding request is still pending */
	if (fsp_ipmi.cur_msg) {
		unlock(&fsp_ipmi.lock);
		return OPAL_SUCCESS;
	}

	fsp_ipmi.cur_msg = list_top(&fsp_ipmi.msg_queue, struct fsp_ipmi_msg,
				    link);
	unlock(&fsp_ipmi.lock);

	if (!fsp_ipmi.cur_msg)
		return OPAL_SUCCESS;

	ipmi_msg = &fsp_ipmi.cur_msg->ipmi_msg;
	prlog(PR_TRACE, "IPMI: Send request, netfn:0x%02x, cmd:0x%02x, "
	      "req_len:%d\n", ipmi_msg->netfn, ipmi_msg->cmd, ipmi_msg->req_size);

	/* KCS request message format */
	*req_buf++ = ipmi_msg->netfn;	/* BYTE 1 */
	*req_buf++ = ipmi_msg->cmd;	/* BYTE 2 */
	if (ipmi_msg->req_size)
		memcpy(req_buf, ipmi_msg->data, ipmi_msg->req_size);

	msg = fsp_mkmsg(FSP_CMD_FETCH_PLAT_DATA, 5, 0, PSI_DMA_PLAT_REQ_BUF,
			0, PSI_DMA_PLAT_RESP_BUF,
			ipmi_msg->req_size + FSP_IPMI_REQ_MIN_LEN);
	if (!msg) {
		log_simple_error(&e_info(OPAL_RC_IPMI_REQ), "IPMI: Failed to "
				 "allocate request message\n");
		fsp_ipmi_cmd_done(ipmi_msg->cmd,
				  IPMI_NETFN_RETURN_CODE(ipmi_msg->netfn),
				  IPMI_ERR_UNSPECIFIED);
		return OPAL_NO_MEM;
	}

	msg->user_data = fsp_ipmi.cur_msg;
	rc = fsp_queue_msg(msg, fsp_ipmi_req_complete);
	if (rc) {
		log_simple_error(&e_info(OPAL_RC_IPMI_REQ), "IPMI: Failed to "
				 "queue request message (%d)\n", rc);
		fsp_freemsg(msg);
		fsp_ipmi_cmd_done(ipmi_msg->cmd,
				  IPMI_NETFN_RETURN_CODE(ipmi_msg->netfn),
				  IPMI_ERR_UNSPECIFIED);
		return OPAL_INTERNAL_ERROR;
	}

	return OPAL_SUCCESS;
}

static struct ipmi_msg *fsp_ipmi_alloc_msg(size_t req_size, size_t resp_size)
{
	struct fsp_ipmi_msg *fsp_ipmi_msg;
	struct ipmi_msg *ipmi_msg;

	fsp_ipmi_msg = zalloc(sizeof(*fsp_ipmi_msg) + MAX(req_size, resp_size));
	if (!fsp_ipmi_msg)
		return NULL;

	ipmi_msg = &fsp_ipmi_msg->ipmi_msg;

	ipmi_msg->req_size = req_size;
	ipmi_msg->resp_size = resp_size;
	ipmi_msg->data = (uint8_t *)(fsp_ipmi_msg + 1);

	return ipmi_msg;
}

static void fsp_ipmi_free_msg(struct ipmi_msg *ipmi_msg)
{
	struct fsp_ipmi_msg *fsp_ipmi_msg = container_of(ipmi_msg,
			struct fsp_ipmi_msg, ipmi_msg);

	free(fsp_ipmi_msg);
}

static int fsp_ipmi_queue_msg(struct ipmi_msg *ipmi_msg)
{
	struct fsp_ipmi_msg *fsp_ipmi_msg = container_of(ipmi_msg,
			struct fsp_ipmi_msg, ipmi_msg);

	if (fsp_in_rr())
		return OPAL_BUSY;

	lock(&fsp_ipmi.lock);
	list_add_tail(&fsp_ipmi.msg_queue, &fsp_ipmi_msg->link);
	unlock(&fsp_ipmi.lock);

	return fsp_ipmi_send_request();
}

static int fsp_ipmi_queue_msg_head(struct ipmi_msg *ipmi_msg)
{
	struct fsp_ipmi_msg *fsp_ipmi_msg = container_of(ipmi_msg,
			struct fsp_ipmi_msg, ipmi_msg);

	if (fsp_in_rr())
		return OPAL_BUSY;

	lock(&fsp_ipmi.lock);
	list_add(&fsp_ipmi.msg_queue, &fsp_ipmi_msg->link);
	unlock(&fsp_ipmi.lock);

	return fsp_ipmi_send_request();
}

static int fsp_ipmi_dequeue_msg(struct ipmi_msg *ipmi_msg)
{
	struct fsp_ipmi_msg *fsp_ipmi_msg = container_of(ipmi_msg,
			struct fsp_ipmi_msg, ipmi_msg);

	lock(&fsp_ipmi.lock);
	list_del_from(&fsp_ipmi.msg_queue, &fsp_ipmi_msg->link);
	unlock(&fsp_ipmi.lock);

	return 0;
}

static struct ipmi_backend fsp_ipmi_backend = {
	.alloc_msg	= fsp_ipmi_alloc_msg,
	.free_msg	= fsp_ipmi_free_msg,
	.queue_msg	= fsp_ipmi_queue_msg,
	.queue_msg_head	= fsp_ipmi_queue_msg_head,
	.dequeue_msg	= fsp_ipmi_dequeue_msg,
};

static bool fsp_ipmi_send_response(uint32_t cmd)
{
	struct fsp_msg *resp;
	int rc;

	resp = fsp_mkmsg(cmd, 0);
	if (!resp) {
		log_simple_error(&e_info(OPAL_RC_IPMI_RESP), "IPMI: Failed to "
				 "allocate response message\n");
		return false;
	}

	rc = fsp_queue_msg(resp, fsp_freemsg);
	if (rc) {
		fsp_freemsg(resp);
		log_simple_error(&e_info(OPAL_RC_IPMI_RESP), "IPMI: Failed to "
				 "queue response message\n");
		return false;
	}

	return true;
}

static bool fsp_ipmi_read_response(struct fsp_msg *msg)
{
	uint8_t *resp_buf = fsp_ipmi.ipmi_resp_buf;
	uint32_t status = msg->data.words[3];
	uint32_t length = msg->data.words[2];
	struct ipmi_msg *ipmi_msg;
	uint8_t netfn, cmd, cc;

	assert(fsp_ipmi.cur_msg);
	ipmi_msg = &fsp_ipmi.cur_msg->ipmi_msg;

	/* Response TCE token */
	assert(msg->data.words[1] == PSI_DMA_PLAT_RESP_BUF);

	if (status != FSP_STATUS_SUCCESS) {
		if(status == FSP_STATUS_DMA_ERROR)
			log_simple_error(&e_info(OPAL_RC_IPMI_DMA_ERROR_RESP), "IPMI: Received "
				"DMA ERROR response from FSP, this may be due to FSP "
				"is in termination state:0x%02x\n", status);
		else
			log_simple_error(&e_info(OPAL_RC_IPMI_RESP), "IPMI: FSP response "
				 "received with bad status:0x%02x\n", status);

		fsp_ipmi_cmd_done(ipmi_msg->cmd,
				  IPMI_NETFN_RETURN_CODE(ipmi_msg->netfn),
				  IPMI_ERR_UNSPECIFIED);
		return fsp_ipmi_send_response(FSP_RSP_PLAT_DATA |
					      FSP_STATUS_SUCCESS);
	}

	/* KCS response message format */
	netfn = *resp_buf++;
	cmd = *resp_buf++;
	cc = *resp_buf++;
	length -= FSP_IPMI_RESP_MIN_LEN;

	prlog(PR_TRACE, "IPMI: fsp response received, netfn:0x%02x, cmd:0x%02x,"
	      " cc:0x%02x, length:%d\n", netfn, cmd, cc, length);

	if (length > ipmi_msg->resp_size) {
		prlog(PR_DEBUG, "IPMI: Length mismatch in response (%d, %d)\n",
		      length, ipmi_msg->resp_size);
		length = ipmi_msg->resp_size; /* Truncate */
		cc = IPMI_ERR_MSG_TRUNCATED;
	}

	ipmi_msg->resp_size = length;
	if (length)
		memcpy(ipmi_msg->data, resp_buf, length);

	fsp_ipmi_cmd_done(cmd, netfn, cc);

	return fsp_ipmi_send_response(FSP_RSP_PLAT_DATA);
}

static bool fsp_ipmi_response(uint32_t cmd_sub_mod, struct fsp_msg *msg)
{
	bool rc;

	switch (cmd_sub_mod) {
	case FSP_CMD_SEND_PLAT_DATA:
		prlog(PR_TRACE, "FSP_CMD_SEND_PLAT_DATA command received\n");
		rc = fsp_ipmi_read_response(msg);
		break;
	default:
		return false;
	};

	/* If response sent successfully, pick the next request */
	if (rc == true)
		fsp_ipmi_send_request();

	return rc;
}

static struct fsp_client fsp_ipmi_client = {
	.message = fsp_ipmi_response,
};

void fsp_ipmi_init(void)
{
	fsp_tce_map(PSI_DMA_PLAT_REQ_BUF, fsp_ipmi.ipmi_req_buf,
		    PSI_DMA_PLAT_REQ_BUF_SIZE);
	fsp_tce_map(PSI_DMA_PLAT_RESP_BUF, fsp_ipmi.ipmi_resp_buf,
		    PSI_DMA_PLAT_RESP_BUF_SIZE);

	list_head_init(&fsp_ipmi.msg_queue);
	init_lock(&fsp_ipmi.lock);

	fsp_register_client(&fsp_ipmi_client, FSP_MCLASS_FETCH_SPDATA);
	ipmi_register_backend(&fsp_ipmi_backend);
}
