/*
 * Copyright (c) 2014, Mentor Graphics Corporation
 * All rights reserved.
 * Copyright (c) 2016 Freescale Semiconductor, Inc. All rights reserved.
 * Copyright (c) 2018 Linaro, Inc. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <internal/utilities.h>
#include <openamp/rpmsg.h>
#include <metal/alloc.h>
#include <metal/sys.h>

#include "rpmsg_internal.h"

/**
 * @internal
 *
 * @brief rpmsg_get_address
 *
 * This function provides unique 32 bit address.
 *
 * @param bitmap	Bit map for addresses
 * @param size		Size of bitmap
 *
 * @return A unique address
 */
static uint32_t rpmsg_get_address(unsigned long *bitmap, unsigned int start, int size)
{
	unsigned int addr = RPMSG_ADDR_ANY;
	unsigned int nextbit;

	nextbit = metal_bitmap_next_clear_bit(bitmap, start, size);
	RPMSG_ASSERT(nextbit < (uint32_t)size, "addr bitmap full\r\n");
	addr = RPMSG_RESERVED_ADDRESSES + nextbit;
	metal_bitmap_set_bit(bitmap, nextbit);

	return addr;
}

/**
 * @internal
 *
 * @brief Frees the given address.
 *
 * @param bitmap	Bit map for addresses
 * @param size		Size of bitmap
 * @param addr		Address to free
 */
static void rpmsg_release_address(unsigned long *bitmap, int size,
				  int addr)
{
	addr -= RPMSG_RESERVED_ADDRESSES;
	if (addr >= 0 && addr < size)
		metal_bitmap_clear_bit(bitmap, addr);
}

/**
 * @internal
 *
 * @brief Checks whether address is used or free.
 *
 * @param bitmap	Bit map for addresses
 * @param size		Size of bitmap
 * @param addr		Address to free
 *
 * @return TRUE/FALSE
 */
static int rpmsg_is_address_set(unsigned long *bitmap, int size, int addr)
{
	int ret = RPMSG_ERR_PARAM;

	addr -= RPMSG_RESERVED_ADDRESSES;
	if (addr >= 0 && addr < size)
		ret = metal_bitmap_is_bit_set(bitmap, addr);

	return ret;
}

/**
 * @internal
 *
 * @brief Marks the address as consumed.
 *
 * @param bitmap	Bit map for addresses
 * @param size		Size of bitmap
 * @param addr		Address to free
 *
 * @return 0 on success, otherwise error code
 */
static int rpmsg_set_address(unsigned long *bitmap, int size, int addr)
{
	int ret = RPMSG_ERR_PARAM;

	addr -= RPMSG_RESERVED_ADDRESSES;
	if (addr >= 0 && addr < size) {
		metal_bitmap_set_bit(bitmap, addr);
		ret = RPMSG_SUCCESS;
	}

	return ret;
}

void rpmsg_ept_incref(struct rpmsg_endpoint *ept)
{
	if (ept) {
		metal_mutex_acquire(&ept->rdev->lock);
		ept->refcnt++;
		metal_mutex_release(&ept->rdev->lock);
	}
}

void rpmsg_ept_decref(struct rpmsg_endpoint *ept)
{
	if (ept) {
		metal_mutex_acquire(&ept->rdev->lock);
		ept->refcnt--;
		if (!ept->refcnt) {
			metal_mutex_release(&ept->rdev->lock);
			if (ept->release_cb)
				ept->release_cb(ept);
			else
				ept->rdev = NULL;
		} else {
			metal_mutex_release(&ept->rdev->lock);
		}
	}
}

int rpmsg_send_offchannel_raw(struct rpmsg_endpoint *ept, uint32_t src,
			      uint32_t dst, const void *data, int len,
			      int wait)
{
	struct rpmsg_device *rdev;
	int ret = RPMSG_ERR_PARAM;

	if (ept && ept->rdev && data && dst != RPMSG_ADDR_ANY && len >= 0) {
		rdev = ept->rdev;
		if (rdev->ops.send_offchannel_raw) {
			metal_trace(ept->name, data, len, "send ept:%p name:%s "
				    "cb:%p rdev:%p src:%d dst:%d data:%p len:%d",
				    ept, ept->name, ept->cb, rdev, src, dst, data, len);
			ret = rdev->ops.send_offchannel_raw(rdev, src, dst, data,
							    len, wait, ept->priority);
		}
	}

	return ret;
}

int rpmsg_send_ns_message(struct rpmsg_endpoint *ept, unsigned long flags)
{
	struct rpmsg_ns_msg ns_msg;
	int ret;

	ns_msg.flags = flags;
	ns_msg.addr = ept->addr;
	(void)safe_strcpy(ns_msg.name, sizeof(ns_msg.name), ept->name, sizeof(ept->name));
	ret = rpmsg_send_offchannel_raw(ept, ept->addr,
					RPMSG_NS_EPT_ADDR,
					&ns_msg, sizeof(ns_msg), true);
	if (ret >= 0)
		ret = RPMSG_SUCCESS;

	return ret;
}

void rpmsg_hold_rx_buffer(struct rpmsg_endpoint *ept, void *rxbuf)
{
	struct rpmsg_device *rdev;

	if (ept && ept->rdev && rxbuf) {
		rdev = ept->rdev;
		if (rdev->ops.hold_rx_buffer)
			rdev->ops.hold_rx_buffer(rdev, rxbuf);
	}
}

void rpmsg_release_rx_buffer(struct rpmsg_endpoint *ept, void *rxbuf)
{
	struct rpmsg_device *rdev;

	if (ept && ept->rdev && rxbuf) {
		rdev = ept->rdev;
		if (rdev->ops.release_rx_buffer)
			rdev->ops.release_rx_buffer(rdev, rxbuf);
	}
}

int rpmsg_release_tx_buffer(struct rpmsg_endpoint *ept, void *buf)
{
	struct rpmsg_device *rdev;
	int ret = RPMSG_ERR_PARAM;

	if (ept && ept->rdev && buf) {
		rdev = ept->rdev;
		if (rdev->ops.release_tx_buffer) {
			metal_trace(ept->name, NULL, 0,
				    "release tx buffer ept:%p name:%s cb:%p buf:%p rdev:%p",
				    ept, ept->name, ept->cb, buf, rdev);
			ret = rdev->ops.release_tx_buffer(rdev, buf);
		}
	}

	return ret;
}

void *rpmsg_get_tx_payload_buffer(struct rpmsg_endpoint *ept,
				  uint32_t *len, int wait)
{
	struct rpmsg_device *rdev;
	void *buf = NULL;

	if (ept && ept->rdev && len) {
		rdev = ept->rdev;
		if (rdev->ops.get_tx_payload_buffer) {
			buf = rdev->ops.get_tx_payload_buffer(rdev, len, wait,
							      ept->priority);
			if (buf) {
				metal_trace(ept->name, NULL, 0, "get tx buffer ept:%p "
					    "name:%s cb:%p buf:%p rdev:%p",
					    ept, ept->name, ept->cb, buf, rdev);
			}
		}
	}

	return buf;
}

int rpmsg_get_tx_buffer_size(struct rpmsg_endpoint *ept)
{
	struct rpmsg_device *rdev;
	int ret = RPMSG_ERR_PARAM;

	if (ept && ept->rdev) {
		rdev = ept->rdev;
		if (rdev->ops.get_tx_buffer_size)
			ret = rdev->ops.get_tx_buffer_size(rdev);
		else
			ret = RPMSG_EOPNOTSUPP;
	}

	return ret;
}

int rpmsg_get_rx_buffer_size(struct rpmsg_endpoint *ept)
{
	struct rpmsg_device *rdev;
	int ret = RPMSG_ERR_PARAM;

	if (ept && ept->rdev) {
		rdev = ept->rdev;
		if (rdev->ops.get_rx_buffer_size)
			ret = rdev->ops.get_rx_buffer_size(rdev);
		else
			ret = RPMSG_EOPNOTSUPP;
	}

	return ret;
}

int rpmsg_send_offchannel_nocopy(struct rpmsg_endpoint *ept, uint32_t src,
				 uint32_t dst, const void *data, int len)
{
	struct rpmsg_device *rdev;
	int ret = RPMSG_ERR_PARAM;

	if (ept && ept->rdev && data && dst != RPMSG_ADDR_ANY && len >= 0) {
		rdev = ept->rdev;
		if (rdev->ops.send_offchannel_nocopy) {
			metal_trace(ept->name, data, len, "send ept:%p name:%s "
				    "cb:%p rdev:%p src:%d dst:%d data:%p len:%d",
				    ept, ept->name, ept->cb, rdev, src, dst, data, len);
			ret = rdev->ops.send_offchannel_nocopy(rdev, src, dst, data, len);
		}
	}

	return ret;
}

struct rpmsg_endpoint *rpmsg_get_endpoint(struct rpmsg_device *rdev,
					  const char *name, uint32_t addr,
					  uint32_t dest_addr)
{
	struct rpmsg_endpoint *ept = NULL;
	struct metal_list *node;

	metal_list_for_each(&rdev->endpoints, node) {
		int name_match = 0;

		ept = metal_container_of(node, struct rpmsg_endpoint, node);
		/* try to get by local address only */
		if (addr != RPMSG_ADDR_ANY && ept->addr == addr)
			break;
		/* else use name service and destination address */
		if (name)
			name_match = !strncmp(ept->name, name,
					      sizeof(ept->name));
		if (!name || !name_match) {
			ept = NULL;
			continue;
		}
		/* destination address is known, equal to ept remote address */
		if (dest_addr != RPMSG_ADDR_ANY && ept->dest_addr == dest_addr)
			break;
		/* ept is registered but not associated to remote ept */
		if (addr == RPMSG_ADDR_ANY && ept->dest_addr == RPMSG_ADDR_ANY)
			break;
	}
	return ept;
}

int rpmsg_set_priority(FAR struct rpmsg_endpoint *ept, uint8_t priority)
{
	int ret = -EINVAL;

	if (ept) {
		ept->priority = priority;
		ret = 0;
	}

	return ret;
}

static void rpmsg_unregister_endpoint(struct rpmsg_endpoint *ept)
{
	struct rpmsg_device *rdev = ept->rdev;

	metal_mutex_acquire(&rdev->lock);
	if (ept->addr != RPMSG_ADDR_ANY)
		rpmsg_release_address(rdev->bitmap, RPMSG_ADDR_BMP_SIZE,
				      ept->addr);
	metal_list_del(&ept->node);
	metal_mutex_release(&rdev->lock);
	rpmsg_ept_decref(ept);
}

void rpmsg_register_endpoint(struct rpmsg_device *rdev,
			     struct rpmsg_endpoint *ept,
			     const char *name,
			     uint32_t src, uint32_t dest,
			     rpmsg_ept_cb cb,
			     rpmsg_ns_unbind_cb ns_unbind_cb, void *priv)
{
	if (name)
		(void)safe_strcpy(ept->name, sizeof(ept->name), name, RPMSG_NAME_SIZE);
	else
		ept->name[0] = 0;

	ept->refcnt = 1;
	ept->addr = src;
	ept->dest_addr = dest;
	ept->cb = cb;
	ept->ns_unbind_cb = ns_unbind_cb;
	ept->priv = priv;
	ept->rdev = rdev;
	if (ept->priority == RPMSG_PRIO_MIN)
		ept->priority = RPMSG_PRIO_DEFAULT;

	metal_list_add_tail(&rdev->endpoints, &ept->node);
}

int rpmsg_create_ept(struct rpmsg_endpoint *ept, struct rpmsg_device *rdev,
		     const char *name, uint32_t src, uint32_t dest,
		     rpmsg_ept_cb cb, rpmsg_ns_unbind_cb unbind_cb)
{
	int status = RPMSG_SUCCESS;
	uint32_t addr = src;

	if (ept && rdev && cb) {
		metal_mutex_acquire(&rdev->lock);
		if (src == RPMSG_ADDR_ANY) {
			addr = rpmsg_get_address(rdev->bitmap, rdev->bitnext, RPMSG_ADDR_BMP_SIZE);
			if (addr == RPMSG_ADDR_ANY) {
				status = RPMSG_ERR_ADDR;
			} else {
				rdev->bitnext = (addr + 1) % RPMSG_ADDR_BMP_SIZE;
			}
		} else if (src >= RPMSG_RESERVED_ADDRESSES) {
			status = rpmsg_is_address_set(rdev->bitmap, RPMSG_ADDR_BMP_SIZE, src);
			if (!status) {
				/* Mark the address as used in the address bitmap. */
				rpmsg_set_address(rdev->bitmap, RPMSG_ADDR_BMP_SIZE, src);
			} else if (status > 0) {
				status = RPMSG_ERR_ADDR;
			}
		} else {
			/* Skip check the address duplication in 0-1023:
			 * 1.Trust the author of predefined service
			 * 2.Simplify the tracking implementation
			 */
		}

		if (!status) {
			rpmsg_register_endpoint(rdev, ept, name, addr, dest, cb, unbind_cb, ept->priv);
			metal_mutex_release(&rdev->lock);

			/* Send NS announcement/acknowledge to remote processor */
			if (ept->name[0] && rdev->support_ns &&
			    ept->dest_addr == RPMSG_ADDR_ANY)
				status = rpmsg_send_ns_message(ept, RPMSG_NS_CREATE);
			else if (ept->name[0] && rdev->support_ack &&
				 ept->dest_addr != RPMSG_ADDR_ANY)
				status = rpmsg_send_ns_message(ept, RPMSG_NS_CREATE_ACK);

			if (status)
				rpmsg_unregister_endpoint(ept);
		} else
			metal_mutex_release(&rdev->lock);
	} else
		status = RPMSG_ERR_PARAM;

	return status;
}

void rpmsg_destroy_ept(struct rpmsg_endpoint *ept)
{
	struct rpmsg_device *rdev;

	if (ept && ept->rdev) {
		rdev = ept->rdev;
		if (ept->name[0] && rdev->support_ns &&
		    ept->addr >= RPMSG_RESERVED_ADDRESSES)
			(void)rpmsg_send_ns_message(ept, RPMSG_NS_DESTROY);
		rpmsg_unregister_endpoint(ept);
	}

}