/*
 * Copyright (c) 2014, Mentor Graphics Corporation
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <errno.h>
#include <metal/mutex.h>
#include <metal/spinlock.h>
#include <metal/utilities.h>
#include <openamp/open_amp.h>
#include <openamp/rpmsg_retarget.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>

/*************************************************************************
 *	Description
 *	This files contains rpmsg based redefinitions for C RTL system calls
 *	such as _open, _read, _write, _close.
 *************************************************************************/
static struct rpmsg_rpc_data *rpmsg_default_rpc;

static int rpmsg_rpc_ept_cb(struct rpmsg_endpoint *ept, void *data, size_t len,
			    uint32_t src, void *priv)
{
	struct rpmsg_rpc_syscall *syscall;

	(void)priv;
	(void)src;

	if (data && ept) {
		syscall = data;
		if (syscall->id == TERM_SYSCALL_ID) {
			rpmsg_destroy_ept(ept);
		} else {
			struct rpmsg_rpc_data *rpc;

			rpc = metal_container_of(ept,
						 struct rpmsg_rpc_data,
						 ept);
			metal_spinlock_acquire(&rpc->buflock);
			if (rpc->respbuf && rpc->respbuf_len != 0) {
				if (len > rpc->respbuf_len)
					len = rpc->respbuf_len;
				memcpy(rpc->respbuf, data, len);
			}
			atomic_flag_clear(&rpc->nacked);
			metal_spinlock_release(&rpc->buflock);
		}
	}

	return RPMSG_SUCCESS;
}

static void rpmsg_service_unbind(struct rpmsg_endpoint *ept)
{
	struct rpmsg_rpc_data *rpc;

	rpc = metal_container_of(ept, struct rpmsg_rpc_data, ept);
	rpc->ept_destroyed = 1;
	rpmsg_destroy_ept(ept);
	atomic_flag_clear(&rpc->nacked);
	if (rpc->shutdown_cb)
		rpc->shutdown_cb(rpc);
}

int rpmsg_rpc_init(struct rpmsg_rpc_data *rpc,
		   struct rpmsg_device *rdev,
		   const char *ept_name, uint32_t ept_addr,
		   uint32_t ept_raddr,
		   void *poll_arg, rpmsg_rpc_poll poll,
		   rpmsg_rpc_shutdown_cb shutdown_cb)
{
	int ret;

	if (!rpc || !rdev)
		return -EINVAL;
	metal_spinlock_init(&rpc->buflock);
	metal_mutex_init(&rpc->lock);
	rpc->shutdown_cb = shutdown_cb;
	rpc->poll_arg = poll_arg;
	rpc->poll = poll;
	rpc->ept_destroyed = 0;
	rpc->respbuf = NULL;
	rpc->respbuf_len = 0;
	rpc->nacked = (atomic_flag)ATOMIC_FLAG_INIT;
	atomic_flag_test_and_set(&rpc->nacked);
	ret = rpmsg_create_ept(&rpc->ept, rdev,
			       ept_name, ept_addr, ept_raddr,
			       rpmsg_rpc_ept_cb, rpmsg_service_unbind);
	if (ret != 0) {
		metal_mutex_release(&rpc->lock);
		return -EINVAL;
	}
	while (!is_rpmsg_ept_ready(&rpc->ept)) {
		if (rpc->poll)
			rpc->poll(rpc->poll_arg);
	}
	return 0;
}

void rpmsg_rpc_release(struct rpmsg_rpc_data *rpc)
{
	if (!rpc)
		return;
	if (rpc->ept_destroyed == 0)
		rpmsg_destroy_ept(&rpc->ept);
	metal_mutex_acquire(&rpc->lock);
	metal_spinlock_acquire(&rpc->buflock);
	rpc->respbuf = NULL;
	rpc->respbuf_len = 0;
	metal_spinlock_release(&rpc->buflock);
	metal_mutex_release(&rpc->lock);
	metal_mutex_deinit(&rpc->lock);
}

int rpmsg_rpc_send(struct rpmsg_rpc_data *rpc,
		   void *req, size_t len,
		   void *resp, size_t resp_len)
{
	int ret;

	if (!rpc)
		return -EINVAL;
	metal_spinlock_acquire(&rpc->buflock);
	rpc->respbuf = resp;
	rpc->respbuf_len = resp_len;
	metal_spinlock_release(&rpc->buflock);
	(void)atomic_flag_test_and_set(&rpc->nacked);
	ret = rpmsg_send(&rpc->ept, req, len);
	if (ret < 0)
		return -EINVAL;
	if (!resp)
		return ret;
	while ((atomic_flag_test_and_set(&rpc->nacked))) {
		if (rpc->poll)
			rpc->poll(rpc->poll_arg);
	}
	return ret;
}

void rpmsg_set_default_rpc(struct rpmsg_rpc_data *rpc)
{
	if (!rpc)
		return;
	rpmsg_default_rpc = rpc;
}

/*************************************************************************
 *
 *   FUNCTION
 *
 *       _open
 *
 *   DESCRIPTION
 *
 *       Open a file.  Minimal implementation
 *
 *************************************************************************/
#define MAX_BUF_LEN 496UL

int _open(const char *filename, int flags, int mode)
{
	struct rpmsg_rpc_data *rpc = rpmsg_default_rpc;
	struct rpmsg_rpc_syscall *syscall;
	struct rpmsg_rpc_syscall resp;
	int filename_len = strlen(filename) + 1;
	unsigned int payload_size = sizeof(*syscall) + filename_len;
	unsigned char tmpbuf[MAX_BUF_LEN];
	int ret;

	if (!filename || payload_size > (int)MAX_BUF_LEN) {
		return -EINVAL;
	}

	if (!rpc)
		return -EINVAL;

	/* Construct rpc payload */
	syscall = (void *)tmpbuf;
	syscall->id = OPEN_SYSCALL_ID;
	syscall->args.int_field1 = flags;
	syscall->args.int_field2 = mode;
	syscall->args.data_len = filename_len;
	memcpy(tmpbuf + sizeof(*syscall), filename, filename_len);

	resp.id = 0;
	ret = rpmsg_rpc_send(rpc, tmpbuf, payload_size,
			     (void *)&resp, sizeof(resp));
	if (ret >= 0) {
		/* Obtain return args and return to caller */
		if (resp.id == OPEN_SYSCALL_ID)
			ret = resp.args.int_field1;
		else
			ret = -EINVAL;
	}

	return ret;
}

/*************************************************************************
 *
 *   FUNCTION
 *
 *       _read
 *
 *   DESCRIPTION
 *
 *       Low level function to redirect IO to serial.
 *
 *************************************************************************/
int _read(int fd, char *buffer, int buflen)
{
	struct rpmsg_rpc_syscall syscall;
	struct rpmsg_rpc_syscall *resp;
	struct rpmsg_rpc_data *rpc = rpmsg_default_rpc;
	int payload_size = sizeof(syscall);
	unsigned char tmpbuf[MAX_BUF_LEN];
	int ret;

	if (!rpc || !buffer || buflen == 0)
		return -EINVAL;

	/* Construct rpc payload */
	syscall.id = READ_SYSCALL_ID;
	syscall.args.int_field1 = fd;
	syscall.args.int_field2 = buflen;
	syscall.args.data_len = 0;	/*not used */

	resp = (void *)tmpbuf;
	resp->id = 0;
	ret = rpmsg_rpc_send(rpc, (void *)&syscall, payload_size,
			     tmpbuf, sizeof(tmpbuf));

	/* Obtain return args and return to caller */
	if (ret >= 0) {
		if (resp->id == READ_SYSCALL_ID) {
			if (resp->args.int_field1 > 0) {
				int tmplen = resp->args.data_len;
				unsigned char *tmpptr = tmpbuf;

				tmpptr += sizeof(*resp);
				if (tmplen > buflen)
					tmplen = buflen;
				memcpy(buffer, tmpptr, tmplen);
			}
			ret = resp->args.int_field1;
		} else {
			ret = -EINVAL;
		}
	}

	return ret;
}

/*************************************************************************
 *
 *   FUNCTION
 *
 *       _write
 *
 *   DESCRIPTION
 *
 *       Low level function to redirect IO to serial.
 *
 *************************************************************************/
int _write(int fd, const char *ptr, int len)
{
	int ret;
	struct rpmsg_rpc_syscall *syscall;
	struct rpmsg_rpc_syscall resp;
	int payload_size = sizeof(*syscall) + len;
	struct rpmsg_rpc_data *rpc = rpmsg_default_rpc;
	unsigned char tmpbuf[MAX_BUF_LEN];
	unsigned char *tmpptr;
	int null_term = 0;

	if (!rpc)
		return -EINVAL;
	if (fd == 1)
		null_term = 1;

	syscall = (void *)tmpbuf;
	syscall->id = WRITE_SYSCALL_ID;
	syscall->args.int_field1 = fd;
	syscall->args.int_field2 = len;
	syscall->args.data_len = len + null_term;
	tmpptr = tmpbuf + sizeof(*syscall);
	memcpy(tmpptr, ptr, len);
	if (null_term == 1) {
		*(char *)(tmpptr + len + null_term) = 0;
		payload_size += 1;
	}
	resp.id = 0;
	ret = rpmsg_rpc_send(rpc, tmpbuf, payload_size,
			     (void *)&resp, sizeof(resp));

	if (ret >= 0) {
		if (resp.id == WRITE_SYSCALL_ID)
			ret = resp.args.int_field1;
		else
			ret = -EINVAL;
	}

	return ret;

}

/*************************************************************************
 *
 *   FUNCTION
 *
 *       _close
 *
 *   DESCRIPTION
 *
 *       Close a file.  Minimal implementation
 *
 *************************************************************************/
int _close(int fd)
{
	int ret;
	struct rpmsg_rpc_syscall syscall;
	struct rpmsg_rpc_syscall resp;
	int payload_size = sizeof(syscall);
	struct rpmsg_rpc_data *rpc = rpmsg_default_rpc;

	if (!rpc)
		return -EINVAL;
	syscall.id = CLOSE_SYSCALL_ID;
	syscall.args.int_field1 = fd;
	syscall.args.int_field2 = 0;	/*not used */
	syscall.args.data_len = 0;	/*not used */

	resp.id = 0;
	ret = rpmsg_rpc_send(rpc, (void *)&syscall, payload_size,
			     (void *)&resp, sizeof(resp));

	if (ret >= 0) {
		if (resp.id == CLOSE_SYSCALL_ID)
			ret = resp.args.int_field1;
		else
			ret = -EINVAL;
	}

	return ret;
}
