/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright(C) 2019 Marvell International Ltd.
 */

#include <rte_string_fns.h>

#include "l2fwd_event.h"
#include "l2fwd_poll.h"

/* display usage */
static void
l2fwd_event_usage(const char *prgname)
{
	printf("%s [EAL options] -- -p PORTMASK [-q NQ]\n"
	       "  -p PORTMASK: hexadecimal bitmask of ports to configure\n"
	       "  -q NQ: number of queue (=ports) per lcore (default is 1)\n"
	       "  -T PERIOD: statistics will be refreshed each PERIOD seconds "
	       "		(0 to disable, 10 default, 86400 maximum)\n"
	       "  --[no-]mac-updating: Enable or disable MAC addresses updating (enabled by default)\n"
	       "      When enabled:\n"
	       "       - The source MAC address is replaced by the TX port MAC address\n"
	       "       - The destination MAC address is replaced by 02:00:00:00:00:TX_PORT_ID\n"
	       "  --mode: Packet transfer mode for I/O, poll or eventdev\n"
	       "          Default mode = eventdev\n"
	       "  --eventq-sched: Event queue schedule type, ordered, atomic or parallel.\n"
	       "                  Default: atomic\n"
	       "                  Valid only if --mode=eventdev\n"
	       "  --config: Configure forwarding port pair mapping\n"
	       "	    Default: alternate port pairs\n\n",
	       prgname);
}

static int
l2fwd_event_parse_portmask(const char *portmask)
{
	char *end = NULL;
	unsigned long pm;

	/* parse hexadecimal string */
	pm = strtoul(portmask, &end, 16);
	if ((portmask[0] == '\0') || (end == NULL) || (*end != '\0'))
		return -1;

	if (pm == 0)
		return -1;

	return pm;
}

static unsigned int
l2fwd_event_parse_nqueue(const char *q_arg)
{
	char *end = NULL;
	unsigned long n;

	/* parse hexadecimal string */
	n = strtoul(q_arg, &end, 10);
	if ((q_arg[0] == '\0') || (end == NULL) || (*end != '\0'))
		return 0;
	if (n == 0)
		return 0;
	if (n >= MAX_RX_QUEUE_PER_LCORE)
		return 0;

	return n;
}

static int
l2fwd_event_parse_timer_period(const char *q_arg)
{
	char *end = NULL;
	int n;

	/* parse number string */
	n = strtol(q_arg, &end, 10);
	if ((q_arg[0] == '\0') || (end == NULL) || (*end != '\0'))
		return -1;
	if (n >= MAX_TIMER_PERIOD)
		return -1;

	return n;
}

static void
l2fwd_event_parse_mode(const char *optarg,
		       struct l2fwd_resources *rsrc)
{
	if (!strncmp(optarg, "poll", 4))
		rsrc->event_mode = false;
	else if (!strncmp(optarg, "eventdev", 8))
		rsrc->event_mode = true;
}

static void
l2fwd_event_parse_eventq_sched(const char *optarg,
			       struct l2fwd_resources *rsrc)
{
	if (!strncmp(optarg, "ordered", 7))
		rsrc->sched_type = RTE_SCHED_TYPE_ORDERED;
	else if (!strncmp(optarg, "atomic", 6))
		rsrc->sched_type = RTE_SCHED_TYPE_ATOMIC;
	else if (!strncmp(optarg, "parallel", 8))
		rsrc->sched_type = RTE_SCHED_TYPE_PARALLEL;
}

static int
l2fwd_parse_port_pair_config(const char *q_arg, struct l2fwd_resources *rsrc)
{
	enum fieldnames {
		FLD_PORT1 = 0,
		FLD_PORT2,
		_NUM_FLD
	};
	const char *p, *p0 = q_arg;
	uint16_t int_fld[_NUM_FLD];
	char *str_fld[_NUM_FLD];
	uint16_t port_pair = 0;
	unsigned int size;
	char s[256];
	char *end;
	int i;

	while ((p = strchr(p0, '(')) != NULL) {
		++p;
		p0 = strchr(p, ')');
		if (p0 == NULL)
			return -1;

		size = p0 - p;
		if (size >= sizeof(s))
			return -1;

		memcpy(s, p, size);
		if (rte_strsplit(s, sizeof(s), str_fld,
					_NUM_FLD, ',') != _NUM_FLD)
			return -1;

		for (i = 0; i < _NUM_FLD; i++) {
			errno = 0;
			int_fld[i] = strtoul(str_fld[i], &end, 0);
			if (errno != 0 || end == str_fld[i] ||
			    int_fld[i] >= RTE_MAX_ETHPORTS)
				return -1;
		}

		if (port_pair >= RTE_MAX_ETHPORTS / 2) {
			printf("exceeded max number of port pair params: Current %d Max = %d\n",
			       port_pair, RTE_MAX_ETHPORTS / 2);
			return -1;
		}

		if ((rsrc->dst_ports[int_fld[FLD_PORT1]] != UINT32_MAX) ||
			(rsrc->dst_ports[int_fld[FLD_PORT2]] != UINT32_MAX)) {
			printf("Duplicate port pair (%d,%d) config\n",
					int_fld[FLD_PORT1], int_fld[FLD_PORT2]);
			return -1;
		}

		rsrc->dst_ports[int_fld[FLD_PORT1]] = int_fld[FLD_PORT2];
		rsrc->dst_ports[int_fld[FLD_PORT2]] = int_fld[FLD_PORT1];

		port_pair++;
	}

	rsrc->port_pairs = true;

	return 0;
}

static const char short_options[] =
	"p:"  /* portmask */
	"q:"  /* number of queues */
	"T:"  /* timer period */
	;

#define CMD_LINE_OPT_MAC_UPDATING "mac-updating"
#define CMD_LINE_OPT_NO_MAC_UPDATING "no-mac-updating"
#define CMD_LINE_OPT_MODE "mode"
#define CMD_LINE_OPT_EVENTQ_SCHED "eventq-sched"
#define CMD_LINE_OPT_PORT_PAIR_CONF "config"

enum {
	/* long options mapped to a short option */

	/* first long only option value must be >= 256, so that we won't
	 * conflict with short options
	 */
	CMD_LINE_OPT_MIN_NUM = 256,
	CMD_LINE_OPT_MODE_NUM,
	CMD_LINE_OPT_EVENTQ_SCHED_NUM,
	CMD_LINE_OPT_PORT_PAIR_CONF_NUM,
};

/* Parse the argument given in the command line of the application */
static int
l2fwd_event_parse_args(int argc, char **argv, struct l2fwd_resources *rsrc)
{
	int mac_updating = 1;
	struct option lgopts[] = {
		{ CMD_LINE_OPT_MAC_UPDATING, no_argument, &mac_updating, 1},
		{ CMD_LINE_OPT_NO_MAC_UPDATING, no_argument, &mac_updating, 0},
		{ CMD_LINE_OPT_MODE, required_argument, NULL,
							CMD_LINE_OPT_MODE_NUM},
		{ CMD_LINE_OPT_EVENTQ_SCHED, required_argument, NULL,
						CMD_LINE_OPT_EVENTQ_SCHED_NUM},
		{ CMD_LINE_OPT_PORT_PAIR_CONF, required_argument, NULL,
					CMD_LINE_OPT_PORT_PAIR_CONF_NUM},
		{NULL, 0, 0, 0}
	};
	int opt, ret, timer_secs;
	char *prgname = argv[0];
	uint16_t port_id;
	int option_index;
	char **argvopt;

	/* reset l2fwd_dst_ports */
	for (port_id = 0; port_id < RTE_MAX_ETHPORTS; port_id++)
		rsrc->dst_ports[port_id] = UINT32_MAX;

	argvopt = argv;
	while ((opt = getopt_long(argc, argvopt, short_options,
				  lgopts, &option_index)) != EOF) {

		switch (opt) {
		/* portmask */
		case 'p':
			rsrc->enabled_port_mask =
					l2fwd_event_parse_portmask(optarg);
			if (rsrc->enabled_port_mask == 0) {
				printf("invalid portmask\n");
				l2fwd_event_usage(prgname);
				return -1;
			}
			break;

		/* nqueue */
		case 'q':
			rsrc->rx_queue_per_lcore =
					l2fwd_event_parse_nqueue(optarg);
			if (rsrc->rx_queue_per_lcore == 0) {
				printf("invalid queue number\n");
				l2fwd_event_usage(prgname);
				return -1;
			}
			break;

		/* timer period */
		case 'T':
			timer_secs = l2fwd_event_parse_timer_period(optarg);
			if (timer_secs < 0) {
				printf("invalid timer period\n");
				l2fwd_event_usage(prgname);
				return -1;
			}
			rsrc->timer_period = timer_secs;
			/* convert to number of cycles */
			rsrc->timer_period *= rte_get_timer_hz();
			break;

		case CMD_LINE_OPT_MODE_NUM:
			l2fwd_event_parse_mode(optarg, rsrc);
			break;

		case CMD_LINE_OPT_EVENTQ_SCHED_NUM:
			l2fwd_event_parse_eventq_sched(optarg, rsrc);
			break;

		case CMD_LINE_OPT_PORT_PAIR_CONF_NUM:
			ret = l2fwd_parse_port_pair_config(optarg, rsrc);
			if (ret) {
				printf("Invalid port pair config\n");
				l2fwd_event_usage(prgname);
				return -1;
			}
			break;

		/* long options */
		case 0:
			break;

		default:
			l2fwd_event_usage(prgname);
			return -1;
		}
	}

	rsrc->mac_updating = mac_updating;

	if (optind >= 0)
		argv[optind-1] = prgname;

	ret = optind-1;
	optind = 1; /* reset getopt lib */
	return ret;
}

/*
 * Check port pair config with enabled port mask,
 * and for valid port pair combinations.
 */
static int
check_port_pair_config(struct l2fwd_resources *rsrc)
{
	uint32_t port_pair_mask = 0;
	uint32_t portid;
	uint16_t index;

	for (index = 0; index < rte_eth_dev_count_avail(); index++) {
		if ((rsrc->enabled_port_mask & (1 << index)) == 0 ||
		    (port_pair_mask & (1 << index)))
			continue;

		portid = rsrc->dst_ports[index];
		if (portid == UINT32_MAX) {
			printf("port %u is enabled in but no valid port pair\n",
			       index);
			return -1;
		}

		if (!rte_eth_dev_is_valid_port(index)) {
			printf("port %u is not valid\n", index);
			return -1;
		}

		if (!rte_eth_dev_is_valid_port(portid)) {
			printf("port %u is not valid\n", portid);
			return -1;
		}

		if (port_pair_mask & (1 << portid) &&
				rsrc->dst_ports[portid] != index) {
			printf("port %u is used in other port pairs\n", portid);
			return -1;
		}

		port_pair_mask |= (1 << portid);
		port_pair_mask |= (1 << index);
	}

	return 0;
}

static int
l2fwd_launch_one_lcore(void *args)
{
	struct l2fwd_resources *rsrc = args;
	struct l2fwd_poll_resources *poll_rsrc = rsrc->poll_rsrc;
	struct l2fwd_event_resources *evt_rsrc = rsrc->evt_rsrc;

	if (rsrc->event_mode)
		evt_rsrc->ops.l2fwd_event_loop(rsrc);
	else
		poll_rsrc->poll_main_loop(rsrc);

	return 0;
}

/* Check the link status of all ports in up to 9s, and print them finally */
static void
check_all_ports_link_status(struct l2fwd_resources *rsrc,
			    uint32_t port_mask)
{
#define CHECK_INTERVAL 100 /* 100ms */
#define MAX_CHECK_TIME 90 /* 9s (90 * 100ms) in total */
	uint16_t port_id;
	uint8_t count, all_ports_up, print_flag = 0;
	struct rte_eth_link link;
	int ret;

	printf("\nChecking link status...");
	fflush(stdout);
	for (count = 0; count <= MAX_CHECK_TIME; count++) {
		if (rsrc->force_quit)
			return;
		all_ports_up = 1;
		RTE_ETH_FOREACH_DEV(port_id) {
			if (rsrc->force_quit)
				return;
			if ((port_mask & (1 << port_id)) == 0)
				continue;
			memset(&link, 0, sizeof(link));
			ret = rte_eth_link_get_nowait(port_id, &link);
			if (ret < 0) {
				all_ports_up = 0;
				if (print_flag == 1)
					printf("Port %u link get failed: %s\n",
						port_id, rte_strerror(-ret));
				continue;
			}
			/* print link status if flag set */
			if (print_flag == 1) {
				if (link.link_status)
					printf(
					"Port%d Link Up. Speed %u Mbps - %s\n",
						port_id, link.link_speed,
				(link.link_duplex == ETH_LINK_FULL_DUPLEX) ?
					("full-duplex") : ("half-duplex"));
				else
					printf("Port %d Link Down\n", port_id);
				continue;
			}
			/* clear all_ports_up flag if any link down */
			if (link.link_status == ETH_LINK_DOWN) {
				all_ports_up = 0;
				break;
			}
		}
		/* after finally printing all link status, get out */
		if (print_flag == 1)
			break;

		if (all_ports_up == 0) {
			printf(".");
			fflush(stdout);
			rte_delay_ms(CHECK_INTERVAL);
		}

		/* set the print_flag if all ports up or timeout */
		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
			print_flag = 1;
			printf("done\n");
		}
	}
}

/* Print out statistics on packets dropped */
static void
print_stats(struct l2fwd_resources *rsrc)
{
	uint64_t total_packets_dropped, total_packets_tx, total_packets_rx;
	uint32_t port_id;

	total_packets_dropped = 0;
	total_packets_tx = 0;
	total_packets_rx = 0;

	const char clr[] = {27, '[', '2', 'J', '\0' };
	const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0' };

		/* Clear screen and move to top left */
	printf("%s%s", clr, topLeft);

	printf("\nPort statistics ====================================");

	for (port_id = 0; port_id < RTE_MAX_ETHPORTS; port_id++) {
		/* skip disabled ports */
		if ((rsrc->enabled_port_mask & (1 << port_id)) == 0)
			continue;
		printf("\nStatistics for port %u ------------------------------"
			   "\nPackets sent: %29"PRIu64
			   "\nPackets received: %25"PRIu64
			   "\nPackets dropped: %26"PRIu64,
			   port_id,
			   rsrc->port_stats[port_id].tx,
			   rsrc->port_stats[port_id].rx,
			   rsrc->port_stats[port_id].dropped);

		total_packets_dropped +=
					rsrc->port_stats[port_id].dropped;
		total_packets_tx += rsrc->port_stats[port_id].tx;
		total_packets_rx += rsrc->port_stats[port_id].rx;
	}

	if (rsrc->event_mode) {
		struct l2fwd_event_resources *evt_rsrc = rsrc->evt_rsrc;
		struct rte_event_eth_rx_adapter_stats rx_adptr_stats;
		struct rte_event_eth_tx_adapter_stats tx_adptr_stats;
		int ret, i;

		for (i = 0; i < evt_rsrc->rx_adptr.nb_rx_adptr; i++) {
			ret = rte_event_eth_rx_adapter_stats_get(
					evt_rsrc->rx_adptr.rx_adptr[i],
					&rx_adptr_stats);
			if (ret < 0)
				continue;
			printf("\nRx adapter[%d] statistics===================="
				   "\nReceive queue poll count: %17"PRIu64
				   "\nReceived packet count: %20"PRIu64
				   "\nEventdev enqueue count: %19"PRIu64
				   "\nEventdev enqueue retry count: %13"PRIu64
				   "\nReceived packet dropped count: %12"PRIu64
				   "\nRx enqueue start timestamp: %15"PRIu64
				   "\nRx enqueue block cycles: %18"PRIu64
				   "\nRx enqueue unblock timestamp: %13"PRIu64,
				   evt_rsrc->rx_adptr.rx_adptr[i],
				   rx_adptr_stats.rx_poll_count,
				   rx_adptr_stats.rx_packets,
				   rx_adptr_stats.rx_enq_count,
				   rx_adptr_stats.rx_enq_retry,
				   rx_adptr_stats.rx_dropped,
				   rx_adptr_stats.rx_enq_start_ts,
				   rx_adptr_stats.rx_enq_block_cycles,
				   rx_adptr_stats.rx_enq_end_ts);
		}
		for (i = 0; i <  evt_rsrc->tx_adptr.nb_tx_adptr; i++) {
			ret = rte_event_eth_tx_adapter_stats_get(
					evt_rsrc->tx_adptr.tx_adptr[i],
					&tx_adptr_stats);
			if (ret < 0)
				continue;
			printf("\nTx adapter[%d] statistics===================="
				   "\nNumber of transmit retries: %15"PRIu64
				   "\nNumber of packets transmitted: %12"PRIu64
				   "\nNumber of packets dropped: %16"PRIu64,
				   evt_rsrc->tx_adptr.tx_adptr[i],
				   tx_adptr_stats.tx_retry,
				   tx_adptr_stats.tx_packets,
				   tx_adptr_stats.tx_dropped);
		}
	}
	printf("\nAggregate lcore statistics ========================="
		   "\nTotal packets sent: %23"PRIu64
		   "\nTotal packets received: %19"PRIu64
		   "\nTotal packets dropped: %20"PRIu64,
		   total_packets_tx,
		   total_packets_rx,
		   total_packets_dropped);
	printf("\n====================================================\n");
}

static void
l2fwd_event_print_stats(struct l2fwd_resources *rsrc)
{
	uint64_t prev_tsc = 0, diff_tsc, cur_tsc, timer_tsc = 0;
	const uint64_t timer_period = rsrc->timer_period;

	while (!rsrc->force_quit) {
		/* if timer is enabled */
		if (timer_period > 0) {
			cur_tsc = rte_rdtsc();
			diff_tsc = cur_tsc - prev_tsc;

			/* advance the timer */
			timer_tsc += diff_tsc;

			/* if timer has reached its timeout */
			if (unlikely(timer_tsc >= timer_period)) {
				print_stats(rsrc);
				/* reset the timer */
				timer_tsc = 0;
			}
			prev_tsc = cur_tsc;
		}
	}
}


static void
signal_handler(int signum)
{
	struct l2fwd_resources *rsrc = l2fwd_get_rsrc();
	if (signum == SIGINT || signum == SIGTERM) {
		printf("\n\nSignal %d received, preparing to exit...\n",
				signum);
		rsrc->force_quit = true;
	}
}

int
main(int argc, char **argv)
{
	struct l2fwd_resources *rsrc;
	uint16_t nb_ports_available = 0;
	uint32_t nb_ports_in_mask = 0;
	uint16_t port_id, last_port;
	uint32_t nb_mbufs;
	uint16_t nb_ports;
	int i, ret;

	/* init EAL */
	ret = rte_eal_init(argc, argv);
	if (ret < 0)
		rte_panic("Invalid EAL arguments\n");
	argc -= ret;
	argv += ret;

	rsrc = l2fwd_get_rsrc();

	signal(SIGINT, signal_handler);
	signal(SIGTERM, signal_handler);

	/* parse application arguments (after the EAL ones) */
	ret = l2fwd_event_parse_args(argc, argv, rsrc);
	if (ret < 0)
		rte_panic("Invalid L2FWD arguments\n");

	printf("MAC updating %s\n", rsrc->mac_updating ? "enabled" :
			"disabled");

	nb_ports = rte_eth_dev_count_avail();
	if (nb_ports == 0)
		rte_panic("No Ethernet ports - bye\n");

	/* check port mask to possible port mask */
	if (rsrc->enabled_port_mask & ~((1 << nb_ports) - 1))
		rte_panic("Invalid portmask; possible (0x%x)\n",
			(1 << nb_ports) - 1);

	if (!rsrc->port_pairs) {
		last_port = 0;
		/*
		 * Each logical core is assigned a dedicated TX queue on each
		 * port.
		 */
		RTE_ETH_FOREACH_DEV(port_id) {
			/* skip ports that are not enabled */
			if ((rsrc->enabled_port_mask & (1 << port_id)) == 0)
				continue;

			if (nb_ports_in_mask % 2) {
				rsrc->dst_ports[port_id] = last_port;
				rsrc->dst_ports[last_port] = port_id;
			} else {
				last_port = port_id;
			}

			nb_ports_in_mask++;
		}
		if (nb_ports_in_mask % 2) {
			printf("Notice: odd number of ports in portmask.\n");
			rsrc->dst_ports[last_port] = last_port;
		}
	} else {
		if (check_port_pair_config(rsrc) < 0)
			rte_panic("Invalid port pair config\n");
	}

	nb_mbufs = RTE_MAX(nb_ports * (RTE_TEST_RX_DESC_DEFAULT +
				       RTE_TEST_TX_DESC_DEFAULT +
				       MAX_PKT_BURST + rte_lcore_count() *
				       MEMPOOL_CACHE_SIZE), 8192U);

	/* create the mbuf pool */
	rsrc->pktmbuf_pool = rte_pktmbuf_pool_create("mbuf_pool",
			nb_mbufs, MEMPOOL_CACHE_SIZE, 0,
			RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
	if (rsrc->pktmbuf_pool == NULL)
		rte_panic("Cannot init mbuf pool\n");

	nb_ports_available = l2fwd_event_init_ports(rsrc);
	if (!nb_ports_available)
		rte_panic("All available ports are disabled. Please set portmask.\n");

	/* Configure eventdev parameters if required */
	if (rsrc->event_mode)
		l2fwd_event_resource_setup(rsrc);
	else
		l2fwd_poll_resource_setup(rsrc);

	/* initialize port stats */
	memset(&rsrc->port_stats, 0,
					sizeof(struct l2fwd_port_statistics));

	/* All settings are done. Now enable eth devices */
	RTE_ETH_FOREACH_DEV(port_id) {
		/* skip ports that are not enabled */
		if ((rsrc->enabled_port_mask &
					(1 << port_id)) == 0)
			continue;

		ret = rte_eth_dev_start(port_id);
		if (ret < 0)
			rte_panic("rte_eth_dev_start:err=%d, port=%u\n", ret,
				  port_id);
	}

	if (rsrc->event_mode)
		l2fwd_event_service_setup(rsrc);

	check_all_ports_link_status(rsrc, rsrc->enabled_port_mask);

	/* launch per-lcore init on every lcore */
	rte_eal_mp_remote_launch(l2fwd_launch_one_lcore, rsrc,
				 SKIP_MASTER);
	l2fwd_event_print_stats(rsrc);
	if (rsrc->event_mode) {
		struct l2fwd_event_resources *evt_rsrc =
							rsrc->evt_rsrc;
		for (i = 0; i < evt_rsrc->rx_adptr.nb_rx_adptr; i++)
			rte_event_eth_rx_adapter_stop(
				evt_rsrc->rx_adptr.rx_adptr[i]);
		for (i = 0; i < evt_rsrc->tx_adptr.nb_tx_adptr; i++)
			rte_event_eth_tx_adapter_stop(
				evt_rsrc->tx_adptr.tx_adptr[i]);

		RTE_ETH_FOREACH_DEV(port_id) {
			if ((rsrc->enabled_port_mask &
							(1 << port_id)) == 0)
				continue;
			rte_eth_dev_stop(port_id);
		}

		rte_eal_mp_wait_lcore();
		RTE_ETH_FOREACH_DEV(port_id) {
			if ((rsrc->enabled_port_mask &
							(1 << port_id)) == 0)
				continue;
			rte_eth_dev_close(port_id);
		}

		rte_event_dev_stop(evt_rsrc->event_d_id);
		rte_event_dev_close(evt_rsrc->event_d_id);

	} else {
		rte_eal_mp_wait_lcore();

		RTE_ETH_FOREACH_DEV(port_id) {
			if ((rsrc->enabled_port_mask &
							(1 << port_id)) == 0)
				continue;
			printf("Closing port %d...", port_id);
			rte_eth_dev_stop(port_id);
			rte_eth_dev_close(port_id);
			printf(" Done\n");
		}
	}
	printf("Bye...\n");

	return 0;
}
