/*-
 * Copyright (c) 2018 VMware, Inc.
 *
 * SPDX-License-Identifier: (BSD-2-Clause OR GPL-2.0)
 */

/* VMCI initialization. */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

#include "vmci.h"
#include "vmci_doorbell.h"
#include "vmci_driver.h"
#include "vmci_event.h"
#include "vmci_kernel_api.h"
#include "vmci_kernel_defs.h"
#include "vmci_resource.h"

#define LGPFX			"vmci: "
#define VMCI_UTIL_NUM_RESOURCES	1

static vmci_id ctx_update_sub_id = VMCI_INVALID_ID;
static volatile int vm_context_id = VMCI_INVALID_ID;

/*
 *------------------------------------------------------------------------------
 *
 * vmci_util_cid_update --
 *
 *     Gets called with the new context id if updated or resumed.
 *
 * Results:
 *     Context id.
 *
 * Side effects:
 *     None.
 *
 *------------------------------------------------------------------------------
 */

static void
vmci_util_cid_update(vmci_id sub_id, struct vmci_event_data *event_data,
    void *client_data)
{
	struct vmci_event_payload_context *ev_payload;

	ev_payload = vmci_event_data_payload(event_data);

	if (sub_id != ctx_update_sub_id) {
		VMCI_LOG_DEBUG(LGPFX"Invalid subscriber (ID=0x%x).\n", sub_id);
		return;
	}
	if (event_data == NULL || ev_payload->context_id == VMCI_INVALID_ID) {
		VMCI_LOG_DEBUG(LGPFX"Invalid event data.\n");
		return;
	}
	VMCI_LOG_INFO(LGPFX"Updating context from (ID=0x%x) to (ID=0x%x) on "
	    "event (type=%d).\n", atomic_load_int(&vm_context_id),
	    ev_payload->context_id, event_data->event);
	atomic_store_int(&vm_context_id, ev_payload->context_id);
}

/*
 *------------------------------------------------------------------------------
 *
 * vmci_util_init --
 *
 *     Subscribe to context id update event.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *------------------------------------------------------------------------------
 */

void
vmci_util_init(void)
{

	/*
	 * We subscribe to the VMCI_EVENT_CTX_ID_UPDATE here so we can update
	 * the internal context id when needed.
	 */
	if (vmci_event_subscribe(VMCI_EVENT_CTX_ID_UPDATE,
	    vmci_util_cid_update, NULL, &ctx_update_sub_id) < VMCI_SUCCESS) {
		VMCI_LOG_WARNING(LGPFX"Failed to subscribe to event "
		    "(type=%d).\n", VMCI_EVENT_CTX_ID_UPDATE);
	}
}

/*
 *------------------------------------------------------------------------------
 *
 * vmci_util_exit --
 *
 *     Cleanup
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *------------------------------------------------------------------------------
 */

void
vmci_util_exit(void)
{

	if (vmci_event_unsubscribe(ctx_update_sub_id) < VMCI_SUCCESS)
		VMCI_LOG_WARNING(LGPFX"Failed to unsubscribe to event "
		    "(type=%d) with subscriber (ID=0x%x).\n",
		    VMCI_EVENT_CTX_ID_UPDATE, ctx_update_sub_id);
}

/*
 *------------------------------------------------------------------------------
 *
 * vmci_util_check_host_capabilities --
 *
 *     Verify that the host supports the hypercalls we need. If it does not, try
 *     to find fallback hypercalls and use those instead.
 *
 * Results:
 *     true if required hypercalls (or fallback hypercalls) are supported by the
 *     host, false otherwise.
 *
 * Side effects:
 *     None.
 *
 *------------------------------------------------------------------------------
 */

static bool
vmci_util_check_host_capabilities(void)
{
	struct vmci_resources_query_msg *msg;
	struct vmci_datagram *check_msg;
	int result;
	uint32_t msg_size;

	msg_size = sizeof(struct vmci_resources_query_hdr) +
	    VMCI_UTIL_NUM_RESOURCES * sizeof(vmci_resource);
	check_msg = vmci_alloc_kernel_mem(msg_size, VMCI_MEMORY_NORMAL);

	if (check_msg == NULL) {
		VMCI_LOG_WARNING(LGPFX"Check host: Insufficient memory.\n");
		return (false);
	}

	check_msg->dst = VMCI_MAKE_HANDLE(VMCI_HYPERVISOR_CONTEXT_ID,
	    VMCI_RESOURCES_QUERY);
	check_msg->src = VMCI_ANON_SRC_HANDLE;
	check_msg->payload_size = msg_size - VMCI_DG_HEADERSIZE;
	msg = (struct vmci_resources_query_msg *)VMCI_DG_PAYLOAD(check_msg);

	msg->num_resources = VMCI_UTIL_NUM_RESOURCES;
	msg->resources[0] = VMCI_GET_CONTEXT_ID;

	result = vmci_send_datagram(check_msg);
	vmci_free_kernel_mem(check_msg, msg_size);

	/* We need the vector. There are no fallbacks. */
	return (result == 0x1);
}

/*
 *------------------------------------------------------------------------------
 *
 * vmci_check_host_capabilities --
 *
 *     Tell host which guestcalls we support and let each API check that the
 *     host supports the hypercalls it needs. If a hypercall is not supported,
 *     the API can check for a fallback hypercall, or fail the check.
 *
 * Results:
 *     true if successful, false otherwise.
 *
 * Side effects:
 *     Fallback mechanisms may be enabled in the API and vmmon.
 *
 *------------------------------------------------------------------------------
 */

bool
vmci_check_host_capabilities(void)
{
	bool result;

	result = vmci_event_check_host_capabilities();
	result &= vmci_datagram_check_host_capabilities();
	result &= vmci_util_check_host_capabilities();

	if (!result) {
		/*
		 * If it failed, then make sure this goes to the system event
		 * log.
		 */
		VMCI_LOG_WARNING(LGPFX"Host capability checked failed.\n");
	} else
		VMCI_LOG_DEBUG(LGPFX"Host capability check passed.\n");

	return (result);
}

/*
 *------------------------------------------------------------------------------
 *
 * vmci_read_datagrams_from_port --
 *
 *     Reads datagrams from the data in port and dispatches them. We always
 *     start reading datagrams into only the first page of the datagram buffer.
 *     If the datagrams don't fit into one page, we use the maximum datagram
 *     buffer size for the remainder of the  invocation. This is a simple
 *     heuristic for not penalizing small datagrams.
 *
 *     This function assumes that it has exclusive access to the data in port
 *     for the duration of the call.
 *
 * Results:
 *     No result.
 *
 * Side effects:
 *     Datagram handlers may be invoked.
 *
 *------------------------------------------------------------------------------
 */

void
vmci_read_datagrams_from_port(vmci_io_handle io_handle, vmci_io_port dg_in_port,
    uint8_t *dg_in_buffer, size_t dg_in_buffer_size)
{
	struct vmci_datagram *dg;
	size_t current_dg_in_buffer_size;
	size_t remaining_bytes;

	current_dg_in_buffer_size = PAGE_SIZE;

	ASSERT(dg_in_buffer_size >= PAGE_SIZE);

	vmci_read_port_bytes(io_handle, dg_in_port, dg_in_buffer,
	    current_dg_in_buffer_size);
	dg = (struct vmci_datagram *)dg_in_buffer;
	remaining_bytes = current_dg_in_buffer_size;

	while (dg->dst.resource != VMCI_INVALID_ID ||
	    remaining_bytes > PAGE_SIZE) {
		size_t dg_in_size;

		/*
		 * When the input buffer spans multiple pages, a datagram can
		 * start on any page boundary in the buffer.
		 */

		if (dg->dst.resource == VMCI_INVALID_ID) {
			ASSERT(remaining_bytes > PAGE_SIZE);
			dg = (struct vmci_datagram *)ROUNDUP((uintptr_t)dg + 1,
			    PAGE_SIZE);
			ASSERT((uint8_t *)dg < dg_in_buffer +
			    current_dg_in_buffer_size);
			remaining_bytes = (size_t)(dg_in_buffer +
			    current_dg_in_buffer_size - (uint8_t *)dg);
			continue;
		}

		dg_in_size = VMCI_DG_SIZE_ALIGNED(dg);

		if (dg_in_size <= dg_in_buffer_size) {
			int result;

			/*
			 * If the remaining bytes in the datagram buffer doesn't
			 * contain the complete datagram, we first make sure we
			 * have enough room for it and then we read the reminder
			 * of the datagram and possibly any following datagrams.
			 */

			if (dg_in_size > remaining_bytes) {
				if (remaining_bytes !=
				    current_dg_in_buffer_size) {
					/*
					 * We move the partial datagram to the
					 * front and read the reminder of the
					 * datagram and possibly following calls
					 * into the following bytes.
					 */

					memmove(dg_in_buffer, dg_in_buffer +
					    current_dg_in_buffer_size -
					    remaining_bytes,
					    remaining_bytes);

					dg = (struct vmci_datagram *)
					    dg_in_buffer;
				}

				if (current_dg_in_buffer_size !=
				    dg_in_buffer_size)
					current_dg_in_buffer_size =
					    dg_in_buffer_size;

				vmci_read_port_bytes(io_handle, dg_in_port,
				    dg_in_buffer + remaining_bytes,
				    current_dg_in_buffer_size -
				    remaining_bytes);
			}

			/*
			 * We special case event datagrams from the
			 * hypervisor.
			 */
			if (dg->src.context == VMCI_HYPERVISOR_CONTEXT_ID &&
			    dg->dst.resource == VMCI_EVENT_HANDLER)
				result = vmci_event_dispatch(dg);
			else
				result =
				    vmci_datagram_invoke_guest_handler(dg);
			if (result < VMCI_SUCCESS)
				VMCI_LOG_DEBUG(LGPFX"Datagram with resource"
				    " (ID=0x%x) failed (err=%d).\n",
				    dg->dst.resource, result);

			/* On to the next datagram. */
			dg = (struct vmci_datagram *)((uint8_t *)dg +
			    dg_in_size);
		} else {
			size_t bytes_to_skip;

			/*
			 * Datagram doesn't fit in datagram buffer of maximal
			 * size. We drop it.
			 */

			VMCI_LOG_DEBUG(LGPFX"Failed to receive datagram "
			    "(size=%zu bytes).\n", dg_in_size);

			bytes_to_skip = dg_in_size - remaining_bytes;
			if (current_dg_in_buffer_size != dg_in_buffer_size)
				current_dg_in_buffer_size = dg_in_buffer_size;
			for (;;) {
				vmci_read_port_bytes(io_handle, dg_in_port,
				    dg_in_buffer, current_dg_in_buffer_size);
				if (bytes_to_skip <=
				    current_dg_in_buffer_size)
					break;
				bytes_to_skip -= current_dg_in_buffer_size;
			}
			dg = (struct vmci_datagram *)(dg_in_buffer +
			    bytes_to_skip);
		}

		remaining_bytes = (size_t) (dg_in_buffer +
		    current_dg_in_buffer_size - (uint8_t *)dg);

		if (remaining_bytes < VMCI_DG_HEADERSIZE) {
			/* Get the next batch of datagrams. */

			vmci_read_port_bytes(io_handle, dg_in_port,
			    dg_in_buffer, current_dg_in_buffer_size);
			dg = (struct vmci_datagram *)dg_in_buffer;
			remaining_bytes = current_dg_in_buffer_size;
		}
	}
}

/*
 *------------------------------------------------------------------------------
 *
 * vmci_get_context_id --
 *
 *     Returns the current context ID.  Note that since this is accessed only
 *     from code running in the host, this always returns the host context ID.
 *
 * Results:
 *     Context ID.
 *
 * Side effects:
 *     None.
 *
 *------------------------------------------------------------------------------
 */

vmci_id
vmci_get_context_id(void)
{
	if (atomic_load_int(&vm_context_id) == VMCI_INVALID_ID) {
		uint32_t result;
		struct vmci_datagram get_cid_msg;
		get_cid_msg.dst =  VMCI_MAKE_HANDLE(VMCI_HYPERVISOR_CONTEXT_ID,
		    VMCI_GET_CONTEXT_ID);
		get_cid_msg.src = VMCI_ANON_SRC_HANDLE;
		get_cid_msg.payload_size = 0;
		result = vmci_send_datagram(&get_cid_msg);
		atomic_store_int(&vm_context_id, result);
	}
	return (atomic_load_int(&vm_context_id));
}

/*
 *------------------------------------------------------------------------------
 *
 * vmci_components_init --
 *
 *     Initializes VMCI components and registers core hypercalls.
 *
 * Results:
 *     VMCI_SUCCESS if successful, appropriate error code otherwise.
 *
 * Side effects:
 *     None.
 *
 *------------------------------------------------------------------------------
 */

int
vmci_components_init(void)
{
	int result;

	result = vmci_resource_init();
	if (result < VMCI_SUCCESS) {
		VMCI_LOG_WARNING(LGPFX"Failed to initialize vmci_resource "
		    "(result=%d).\n", result);
		goto error_exit;
	}

	result = vmci_event_init();
	if (result < VMCI_SUCCESS) {
		VMCI_LOG_WARNING(LGPFX"Failed to initialize vmci_event "
		    "(result=%d).\n", result);
		goto resource_exit;
	}

	result = vmci_doorbell_init();
	if (result < VMCI_SUCCESS) {
		VMCI_LOG_WARNING(LGPFX"Failed to initialize vmci_doorbell "
		    "(result=%d).\n", result);
		goto event_exit;
	}

	VMCI_LOG_DEBUG(LGPFX"components initialized.\n");
	return (VMCI_SUCCESS);

event_exit:
	vmci_event_exit();

resource_exit:
	vmci_resource_exit();

error_exit:
	return (result);
}

/*
 *------------------------------------------------------------------------------
 *
 * vmci_components_cleanup --
 *
 *     Cleans up VMCI components.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *------------------------------------------------------------------------------
 */

void
vmci_components_cleanup(void)
{

	vmci_doorbell_exit();
	vmci_event_exit();
	vmci_resource_exit();
}