/* SPDX-License-Identifier: LGPL-2.1-only */
/*
 * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
 */

#include <netlink-private/netlink.h>
#include <netlink-private/tc.h>
#include <netlink/netlink.h>
#include <netlink/utils.h>
#include <netlink-private/route/tc-api.h>
#include <netlink/route/qdisc.h>
#include <netlink/route/class.h>
#include <netlink/route/link.h>
#include <netlink/route/qdisc/cbq.h>
#include <netlink/route/cls/police.h>

/**
 * @ingroup qdisc
 * @ingroup class
 * @defgroup qdisc_cbq Class Based Queueing (CBQ)
 * @{
 */

static const struct trans_tbl ovl_strategies[] = {
	__ADD(TC_CBQ_OVL_CLASSIC,classic),
	__ADD(TC_CBQ_OVL_DELAY,delay),
	__ADD(TC_CBQ_OVL_LOWPRIO,lowprio),
	__ADD(TC_CBQ_OVL_DROP,drop),
	__ADD(TC_CBQ_OVL_RCLASSIC,rclassic),
};

/**
 * Convert a CBQ OVL strategy to a character string
 * @arg type		CBQ OVL strategy
 * @arg buf		destination buffer
 * @arg len		length of destination buffer
 *
 * Converts a CBQ OVL strategy to a character string and stores in the
 * provided buffer. Returns the destination buffer or the type
 * encoded in hex if no match was found.
 */
char *nl_ovl_strategy2str(int type, char *buf, size_t len)
{
	return __type2str(type, buf, len, ovl_strategies,
			    ARRAY_SIZE(ovl_strategies));
}

/**
 * Convert a string to a CBQ OVL strategy
 * @arg name		CBQ OVL stragegy name
 *
 * Converts a CBQ OVL stragegy name to it's corresponding CBQ OVL strategy
 * type. Returns the type or -1 if none was found.
 */
int nl_str2ovl_strategy(const char *name)
{
	return __str2type(name, ovl_strategies, ARRAY_SIZE(ovl_strategies));
}

static struct nla_policy cbq_policy[TCA_CBQ_MAX+1] = {
	[TCA_CBQ_LSSOPT]	= { .minlen = sizeof(struct tc_cbq_lssopt) },
	[TCA_CBQ_RATE]		= { .minlen = sizeof(struct tc_ratespec) },
	[TCA_CBQ_WRROPT]	= { .minlen = sizeof(struct tc_cbq_wrropt) },
	[TCA_CBQ_OVL_STRATEGY]	= { .minlen = sizeof(struct tc_cbq_ovl) },
	[TCA_CBQ_FOPT]		= { .minlen = sizeof(struct tc_cbq_fopt) },
	[TCA_CBQ_POLICE]	= { .minlen = sizeof(struct tc_cbq_police) },
};

static int cbq_msg_parser(struct rtnl_tc *tc, void *data)
{
	struct nlattr *tb[TCA_CBQ_MAX + 1];
	struct rtnl_cbq *cbq = data;
	int err;

	err = tca_parse(tb, TCA_CBQ_MAX, tc, cbq_policy);
	if (err < 0)
		return err;

	nla_memcpy(&cbq->cbq_lss, tb[TCA_CBQ_LSSOPT], sizeof(cbq->cbq_lss));
	nla_memcpy(&cbq->cbq_rate, tb[TCA_CBQ_RATE], sizeof(cbq->cbq_rate));
	nla_memcpy(&cbq->cbq_wrr, tb[TCA_CBQ_WRROPT], sizeof(cbq->cbq_wrr));
	nla_memcpy(&cbq->cbq_fopt, tb[TCA_CBQ_FOPT], sizeof(cbq->cbq_fopt));
	nla_memcpy(&cbq->cbq_ovl, tb[TCA_CBQ_OVL_STRATEGY],
		   sizeof(cbq->cbq_ovl));
	nla_memcpy(&cbq->cbq_police, tb[TCA_CBQ_POLICE],
		    sizeof(cbq->cbq_police));
	
	return 0;
}

static void cbq_dump_line(struct rtnl_tc *tc, void *data,
			  struct nl_dump_params *p)
{
	struct rtnl_cbq *cbq = data;
	double r, rbit;
	char *ru, *rubit;

	if (!cbq)
		return;

	r = nl_cancel_down_bytes(cbq->cbq_rate.rate, &ru);
	rbit = nl_cancel_down_bits(cbq->cbq_rate.rate * 8, &rubit);

	nl_dump(p, " rate %.2f%s/s (%.0f%s) prio %u",
		r, ru, rbit, rubit, cbq->cbq_wrr.priority);
}

static void cbq_dump_details(struct rtnl_tc *tc, void *data,
			     struct nl_dump_params *p)
{
	struct rtnl_cbq *cbq = data;
	char *unit, buf[32];
	double w;
	uint32_t el;

	if (!cbq)
		return;

	w = nl_cancel_down_bits(cbq->cbq_wrr.weight * 8, &unit);

	nl_dump(p, "avgpkt %u mpu %u cell %u allot %u weight %.0f%s\n",
		cbq->cbq_lss.avpkt,
		cbq->cbq_rate.mpu,
		1 << cbq->cbq_rate.cell_log,
		cbq->cbq_wrr.allot, w, unit);

	el = cbq->cbq_lss.ewma_log;
	nl_dump_line(p, "  minidle %uus maxidle %uus offtime "
				"%uus level %u ewma_log %u\n",
		nl_ticks2us(cbq->cbq_lss.minidle >> el),
		nl_ticks2us(cbq->cbq_lss.maxidle >> el),
		nl_ticks2us(cbq->cbq_lss.offtime >> el),
		cbq->cbq_lss.level,
		cbq->cbq_lss.ewma_log);

	nl_dump_line(p, "  penalty %uus strategy %s ",
		nl_ticks2us(cbq->cbq_ovl.penalty),
		nl_ovl_strategy2str(cbq->cbq_ovl.strategy, buf, sizeof(buf)));

	nl_dump(p, "split %s defmap 0x%08x ",
		rtnl_tc_handle2str(cbq->cbq_fopt.split, buf, sizeof(buf)),
		cbq->cbq_fopt.defmap);
	
	nl_dump(p, "police %s",
		nl_police2str(cbq->cbq_police.police, buf, sizeof(buf)));
}

static void cbq_dump_stats(struct rtnl_tc *tc, void *data,
			   struct nl_dump_params *p)
{
	struct tc_cbq_xstats *x;
	
	if (!(x = tca_xstats(tc)))
		return;

	nl_dump_line(p, "            borrows    overact  "
			"  avgidle  undertime\n");
	nl_dump_line(p, "         %10u %10u %10u %10u\n",
		     x->borrows, x->overactions, x->avgidle, x->undertime);
}

static struct rtnl_tc_ops cbq_qdisc_ops = {
	.to_kind		= "cbq",
	.to_type		= RTNL_TC_TYPE_QDISC,
	.to_size		= sizeof(struct rtnl_cbq),
	.to_msg_parser		= cbq_msg_parser,
	.to_dump = {
	    [NL_DUMP_LINE]	= cbq_dump_line,
	    [NL_DUMP_DETAILS]	= cbq_dump_details,
	    [NL_DUMP_STATS]	= cbq_dump_stats,
	},
};

static struct rtnl_tc_ops cbq_class_ops = {
	.to_kind		= "cbq",
	.to_type		= RTNL_TC_TYPE_CLASS,
	.to_size		= sizeof(struct rtnl_cbq),
	.to_msg_parser		= cbq_msg_parser,
	.to_dump = {
	    [NL_DUMP_LINE]	= cbq_dump_line,
	    [NL_DUMP_DETAILS]	= cbq_dump_details,
	    [NL_DUMP_STATS]	= cbq_dump_stats,
	},
};

static void __init cbq_init(void)
{
	rtnl_tc_register(&cbq_qdisc_ops);
	rtnl_tc_register(&cbq_class_ops);
}

static void __exit cbq_exit(void)
{
	rtnl_tc_unregister(&cbq_qdisc_ops);
	rtnl_tc_unregister(&cbq_class_ops);
}

/** @} */
