/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright(c) 2010-2018 Intel Corporation
 */

#include <stdlib.h>

#include <eal_export.h>
#include <rte_common.h>
#include <rte_cycles.h>
#include <rte_lcore.h>
#include <rte_service_component.h>
#include <rte_ring.h>

#include "rte_eth_softnic_internals.h"

/**
 * Main thread: data plane thread init
 */
void
softnic_thread_free(struct pmd_internals *softnic)
{
	uint32_t i;

	RTE_LCORE_FOREACH_WORKER(i) {
		struct softnic_thread *t = &softnic->thread[i];

		/* MSGQs */
		rte_ring_free(t->msgq_req);

		rte_ring_free(t->msgq_rsp);
	}
}

int
softnic_thread_init(struct pmd_internals *softnic)
{
	uint32_t i;

	for (i = 0; i < RTE_MAX_LCORE; i++) {
		char ring_name[NAME_MAX];
		struct rte_ring *msgq_req, *msgq_rsp;
		struct softnic_thread *t = &softnic->thread[i];
		struct softnic_thread_data *t_data = &softnic->thread_data[i];
		uint32_t cpu_id = rte_lcore_to_socket_id(i);

		/* MSGQs */
		snprintf(ring_name, sizeof(ring_name), "%s-TH%u-REQ",
			softnic->params.name,
			i);

		msgq_req = rte_ring_create(ring_name,
			THREAD_MSGQ_SIZE,
			cpu_id,
			RING_F_SP_ENQ | RING_F_SC_DEQ);

		if (msgq_req == NULL) {
			softnic_thread_free(softnic);
			return -1;
		}

		snprintf(ring_name, sizeof(ring_name), "%s-TH%u-RSP",
			softnic->params.name,
			i);

		msgq_rsp = rte_ring_create(ring_name,
			THREAD_MSGQ_SIZE,
			cpu_id,
			RING_F_SP_ENQ | RING_F_SC_DEQ);

		if (msgq_rsp == NULL) {
			softnic_thread_free(softnic);
			return -1;
		}

		/* Main thread records */
		t->msgq_req = msgq_req;
		t->msgq_rsp = msgq_rsp;
		t->service_id = UINT32_MAX;

		/* Data plane thread records */
		t_data->n_pipelines = 0;
		t_data->msgq_req = msgq_req;
		t_data->msgq_rsp = msgq_rsp;
		t_data->timer_period =
			(rte_get_tsc_hz() * THREAD_TIMER_PERIOD_MS) / 1000;
		t_data->time_next = rte_get_tsc_cycles() + t_data->timer_period;
	}

	return 0;
}

static inline int
thread_is_valid(struct pmd_internals *softnic, uint32_t thread_id)
{
	if (thread_id >= RTE_MAX_LCORE)
		return 0; /* FALSE */

	if (thread_id == rte_get_main_lcore())
		return 0; /* FALSE */

	if (softnic->params.sc && rte_lcore_has_role(thread_id, ROLE_SERVICE))
		return 1; /* TRUE */
	if (!softnic->params.sc && rte_lcore_has_role(thread_id, ROLE_RTE))
		return 1; /* TRUE */

	return 0; /* FALSE */
}

static inline int
thread_is_running(uint32_t thread_id)
{
	enum rte_lcore_state_t thread_state;

	thread_state = rte_eal_get_lcore_state(thread_id);
	return (thread_state == RUNNING)? 1 : 0;
}

static int32_t
rte_pmd_softnic_run_internal(void *arg);

static inline int
thread_sc_service_up(struct pmd_internals *softnic, uint32_t thread_id)
{
	struct rte_service_spec service_params;
	struct softnic_thread *t = &softnic->thread[thread_id];
	struct rte_eth_dev *dev;
	int status;

	/* service params */
	dev = rte_eth_dev_get_by_name(softnic->params.name);
	if (!dev)
		return -EINVAL;

	snprintf(service_params.name, sizeof(service_params.name), "%s_%u",
		softnic->params.name,
		thread_id);
	service_params.callback = rte_pmd_softnic_run_internal;
	service_params.callback_userdata = dev;
	service_params.capabilities = 0;
	service_params.socket_id = (int)softnic->params.cpu_id;

	/* service register */
	status = rte_service_component_register(&service_params, &t->service_id);
	if (status)
		return status;

	status = rte_service_component_runstate_set(t->service_id, 1);
	if (status) {
		rte_service_component_unregister(t->service_id);
		t->service_id = UINT32_MAX;
		return status;
	}

	status = rte_service_runstate_set(t->service_id, 1);
	if (status) {
		rte_service_component_runstate_set(t->service_id, 0);
		rte_service_component_unregister(t->service_id);
		t->service_id = UINT32_MAX;
		return status;
	}

	/* service map to thread */
	status = rte_service_map_lcore_set(t->service_id, thread_id, 1);
	if (status) {
		rte_service_runstate_set(t->service_id, 0);
		rte_service_component_runstate_set(t->service_id, 0);
		rte_service_component_unregister(t->service_id);
		t->service_id = UINT32_MAX;
		return status;
	}

	return 0;
}

static inline void
thread_sc_service_down(struct pmd_internals *softnic, uint32_t thread_id)
{
	struct softnic_thread *t = &softnic->thread[thread_id];

	/* service unmap from thread */
	rte_service_map_lcore_set(t->service_id, thread_id, 0);

	/* service unregister */
	rte_service_runstate_set(t->service_id, 0);
	rte_service_component_runstate_set(t->service_id, 0);
	rte_service_component_unregister(t->service_id);

	t->service_id = UINT32_MAX;
}

void
softnic_thread_pipeline_disable_all(struct pmd_internals *softnic)
{
	uint32_t thread_id;

	for (thread_id = 0; thread_id < RTE_MAX_LCORE; thread_id++) {
		struct softnic_thread_data *td = &softnic->thread_data[thread_id];

		if (!thread_is_valid(softnic, thread_id))
			continue;

		if (softnic->params.sc && td->n_pipelines)
			thread_sc_service_down(softnic, thread_id);

		td->n_pipelines = 0;
	}
}

/**
 * Main thread & data plane threads: message passing
 */
enum thread_req_type {
	THREAD_REQ_PIPELINE_ENABLE = 0,
	THREAD_REQ_PIPELINE_DISABLE,
	THREAD_REQ_MAX
};

struct thread_msg_req {
	enum thread_req_type type;

	union {
		struct {
			struct rte_swx_pipeline *p;
		} pipeline_enable;

		struct {
			struct rte_swx_pipeline *p;
		} pipeline_disable;
	};
};

struct thread_msg_rsp {
	int status;
};

/**
 * Main thread
 */
static struct thread_msg_req *
thread_msg_alloc(void)
{
	size_t size = RTE_MAX(sizeof(struct thread_msg_req),
		sizeof(struct thread_msg_rsp));

	return calloc(1, size);
}

static void
thread_msg_free(struct thread_msg_rsp *rsp)
{
	free(rsp);
}

static struct thread_msg_rsp *
thread_msg_send_recv(struct pmd_internals *softnic,
	uint32_t thread_id,
	struct thread_msg_req *req)
{
	struct softnic_thread *t = &softnic->thread[thread_id];
	struct rte_ring *msgq_req = t->msgq_req;
	struct rte_ring *msgq_rsp = t->msgq_rsp;
	struct thread_msg_rsp *rsp;
	int status;

	/* send */
	do {
		status = rte_ring_sp_enqueue(msgq_req, req);
	} while (status == -ENOBUFS);

	/* recv */
	do {
		status = rte_ring_sc_dequeue(msgq_rsp, (void **)&rsp);
	} while (status != 0);

	return rsp;
}

int
softnic_thread_pipeline_enable(struct pmd_internals *softnic,
	uint32_t thread_id,
	struct pipeline *p)
{
	struct thread_msg_req *req;
	struct thread_msg_rsp *rsp;
	uint32_t n_pipelines;
	int status;

	/* Check input params */
	if (!thread_is_valid(softnic, thread_id) ||
		(p == NULL) ||
		p->enabled)
		return -1;

	n_pipelines = softnic_pipeline_thread_count(softnic, thread_id);
	if (n_pipelines >= THREAD_PIPELINES_MAX)
		return -1;

	if (softnic->params.sc && (n_pipelines == 0)) {
		status = thread_sc_service_up(softnic, thread_id);
		if (status)
			return status;
	}

	if (!thread_is_running(thread_id)) {
		struct softnic_thread_data *td = &softnic->thread_data[thread_id];

		/* Data plane thread */
		td->p[td->n_pipelines] = p->p;
		td->n_pipelines++;

		/* Pipeline */
		p->thread_id = thread_id;
		p->enabled = 1;

		return 0;
	}

	/* Allocate request */
	req = thread_msg_alloc();
	if (req == NULL)
		return -1;

	/* Write request */
	req->type = THREAD_REQ_PIPELINE_ENABLE;
	req->pipeline_enable.p = p->p;

	/* Send request and wait for response */
	rsp = thread_msg_send_recv(softnic, thread_id, req);

	/* Read response */
	status = rsp->status;

	/* Free response */
	thread_msg_free(rsp);

	/* Request completion */
	if (status)
		return status;

	p->thread_id = thread_id;
	p->enabled = 1;

	return 0;
}

int
softnic_thread_pipeline_disable(struct pmd_internals *softnic,
	uint32_t thread_id,
	struct pipeline *p)
{
	struct thread_msg_req *req;
	struct thread_msg_rsp *rsp;
	uint32_t n_pipelines;
	int status;

	/* Check input params */
	if (!thread_is_valid(softnic, thread_id) ||
		(p == NULL) ||
		(p->enabled && (p->thread_id != thread_id)))
		return -1;

	if (p->enabled == 0)
		return 0;

	if (!thread_is_running(thread_id)) {
		struct softnic_thread_data *td = &softnic->thread_data[thread_id];
		uint32_t i;

		for (i = 0; i < td->n_pipelines; i++) {
			if (td->p[i] != p->p)
				continue;

			/* Data plane thread */
			if (i < td->n_pipelines - 1)
				td->p[i] = td->p[td->n_pipelines - 1];

			td->n_pipelines--;

			/* Pipeline */
			p->enabled = 0;

			break;
		}

		if (softnic->params.sc && (td->n_pipelines == 0))
			thread_sc_service_down(softnic, thread_id);

		return 0;
	}

	/* Allocate request */
	req = thread_msg_alloc();
	if (req == NULL)
		return -1;

	/* Write request */
	req->type = THREAD_REQ_PIPELINE_DISABLE;
	req->pipeline_disable.p = p->p;

	/* Send request and wait for response */
	rsp = thread_msg_send_recv(softnic, thread_id, req);

	/* Read response */
	status = rsp->status;

	/* Free response */
	thread_msg_free(rsp);

	/* Request completion */
	if (status)
		return status;

	p->enabled = 0;

	n_pipelines = softnic_pipeline_thread_count(softnic, thread_id);
	if (softnic->params.sc && (n_pipelines == 0))
		thread_sc_service_down(softnic, thread_id);

	return 0;
}

/**
 * Data plane threads: message handling
 */
static inline struct thread_msg_req *
thread_msg_recv(struct rte_ring *msgq_req)
{
	struct thread_msg_req *req;

	int status = rte_ring_sc_dequeue(msgq_req, (void **)&req);

	if (status != 0)
		return NULL;

	return req;
}

static inline void
thread_msg_send(struct rte_ring *msgq_rsp,
	struct thread_msg_rsp *rsp)
{
	int status;

	do {
		status = rte_ring_sp_enqueue(msgq_rsp, rsp);
	} while (status == -ENOBUFS);
}

static struct thread_msg_rsp *
thread_msg_handle_pipeline_enable(struct softnic_thread_data *t,
	struct thread_msg_req *req)
{
	struct thread_msg_rsp *rsp = (struct thread_msg_rsp *)req;

	/* Request */
	t->p[t->n_pipelines] = req->pipeline_enable.p;
	t->n_pipelines++;

	/* Response */
	rsp->status = 0;
	return rsp;
}

static struct thread_msg_rsp *
thread_msg_handle_pipeline_disable(struct softnic_thread_data *t,
	struct thread_msg_req *req)
{
	struct thread_msg_rsp *rsp = (struct thread_msg_rsp *)req;
	uint32_t n_pipelines = t->n_pipelines;
	struct rte_swx_pipeline *pipeline = req->pipeline_disable.p;
	uint32_t i;

	/* find pipeline */
	for (i = 0; i < n_pipelines; i++) {
		if (t->p[i] != pipeline)
			continue;

		if (i < n_pipelines - 1)
			t->p[i] = t->p[n_pipelines - 1];

		t->n_pipelines--;

		rsp->status = 0;
		return rsp;
	}

	/* should not get here */
	rsp->status = 0;
	return rsp;
}

static void
thread_msg_handle(struct softnic_thread_data *t)
{
	for ( ; ; ) {
		struct thread_msg_req *req;
		struct thread_msg_rsp *rsp;

		req = thread_msg_recv(t->msgq_req);
		if (req == NULL)
			break;

		switch (req->type) {
		case THREAD_REQ_PIPELINE_ENABLE:
			rsp = thread_msg_handle_pipeline_enable(t, req);
			break;

		case THREAD_REQ_PIPELINE_DISABLE:
			rsp = thread_msg_handle_pipeline_disable(t, req);
			break;

		default:
			rsp = (struct thread_msg_rsp *)req;
			rsp->status = -1;
		}

		thread_msg_send(t->msgq_rsp, rsp);
	}
}

/**
 * Data plane threads: main
 */
static int32_t
rte_pmd_softnic_run_internal(void *arg)
{
	struct rte_eth_dev *dev = arg;
	struct pmd_internals *softnic;
	struct softnic_thread_data *t;
	uint32_t thread_id, j;

	softnic = dev->data->dev_private;
	thread_id = rte_lcore_id();
	t = &softnic->thread_data[thread_id];
	t->iter++;

	/* Data Plane */
	for (j = 0; j < t->n_pipelines; j++)
		rte_swx_pipeline_run(t->p[j], PIPELINE_INSTR_QUANTA);

	/* Control Plane */
	if ((t->iter & 0xFLLU) == 0) {
		uint64_t time = rte_get_tsc_cycles();
		uint64_t time_next = t->time_next;

		if (time < time_next)
			return 0;

		/* Thread message queues */
		thread_msg_handle(t);

		t->time_next = time_next + t->timer_period;
	}

	return 0;
}

RTE_EXPORT_SYMBOL(rte_pmd_softnic_run)
int
rte_pmd_softnic_run(uint16_t port_id)
{
	struct rte_eth_dev *dev = &rte_eth_devices[port_id];

#ifdef RTE_LIBRTE_ETHDEV_DEBUG
	RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, 0);
#endif

	return (int)rte_pmd_softnic_run_internal(dev);
}
