// SPDX-License-Identifier: GPL-2.0+

#include "lan966x_main.h"

enum lan966x_dcb_apptrust_values {
	LAN966X_DCB_APPTRUST_EMPTY,
	LAN966X_DCB_APPTRUST_DSCP,
	LAN966X_DCB_APPTRUST_PCP,
	LAN966X_DCB_APPTRUST_DSCP_PCP,
	__LAN966X_DCB_APPTRUST_MAX
};

static const struct lan966x_dcb_apptrust {
	u8 selectors[IEEE_8021QAZ_APP_SEL_MAX + 1];
	int nselectors;
} *lan966x_port_apptrust[NUM_PHYS_PORTS];

static const char *lan966x_dcb_apptrust_names[__LAN966X_DCB_APPTRUST_MAX] = {
	[LAN966X_DCB_APPTRUST_EMPTY]    = "empty",
	[LAN966X_DCB_APPTRUST_DSCP]     = "dscp",
	[LAN966X_DCB_APPTRUST_PCP]      = "pcp",
	[LAN966X_DCB_APPTRUST_DSCP_PCP] = "dscp pcp"
};

/* Lan966x supported apptrust policies */
static const struct lan966x_dcb_apptrust
	lan966x_dcb_apptrust_policies[__LAN966X_DCB_APPTRUST_MAX] = {
	/* Empty *must* be first */
	[LAN966X_DCB_APPTRUST_EMPTY]    = { { 0 }, 0 },
	[LAN966X_DCB_APPTRUST_DSCP]     = { { IEEE_8021QAZ_APP_SEL_DSCP }, 1 },
	[LAN966X_DCB_APPTRUST_PCP]      = { { DCB_APP_SEL_PCP }, 1 },
	[LAN966X_DCB_APPTRUST_DSCP_PCP] = { { IEEE_8021QAZ_APP_SEL_DSCP,
					      DCB_APP_SEL_PCP }, 2 },
};

static bool lan966x_dcb_apptrust_contains(int portno, u8 selector)
{
	const struct lan966x_dcb_apptrust *conf = lan966x_port_apptrust[portno];

	for (int i = 0; i < conf->nselectors; i++)
		if (conf->selectors[i] == selector)
			return true;

	return false;
}

static void lan966x_dcb_app_update(struct net_device *dev)
{
	struct dcb_ieee_app_prio_map dscp_rewr_map = {0};
	struct dcb_rewr_prio_pcp_map pcp_rewr_map = {0};
	struct lan966x_port *port = netdev_priv(dev);
	struct lan966x_port_qos qos = {0};
	struct dcb_app app_itr;
	bool dscp_rewr = false;
	bool pcp_rewr = false;

	/* Get pcp ingress mapping */
	for (int i = 0; i < ARRAY_SIZE(qos.pcp.map); i++) {
		app_itr.selector = DCB_APP_SEL_PCP;
		app_itr.protocol = i;
		qos.pcp.map[i] = dcb_getapp(dev, &app_itr);
	}

	/* Get dscp ingress mapping */
	for (int i = 0; i < ARRAY_SIZE(qos.dscp.map); i++) {
		app_itr.selector = IEEE_8021QAZ_APP_SEL_DSCP;
		app_itr.protocol = i;
		qos.dscp.map[i] = dcb_getapp(dev, &app_itr);
	}

	/* Get default prio */
	qos.default_prio = dcb_ieee_getapp_default_prio_mask(dev);
	if (qos.default_prio)
		qos.default_prio = fls(qos.default_prio) - 1;

	/* Get pcp rewrite mapping */
	dcb_getrewr_prio_pcp_mask_map(dev, &pcp_rewr_map);
	for (int i = 0; i < ARRAY_SIZE(pcp_rewr_map.map); i++) {
		if (!pcp_rewr_map.map[i])
			continue;

		pcp_rewr = true;
		qos.pcp_rewr.map[i] = fls(pcp_rewr_map.map[i]) - 1;
	}

	/* Get dscp rewrite mapping */
	dcb_getrewr_prio_dscp_mask_map(dev, &dscp_rewr_map);
	for (int i = 0; i < ARRAY_SIZE(dscp_rewr_map.map); i++) {
		if (!dscp_rewr_map.map[i])
			continue;

		dscp_rewr = true;
		qos.dscp_rewr.map[i] = fls64(dscp_rewr_map.map[i]) - 1;
	}

	/* Enable use of pcp for queue classification */
	if (lan966x_dcb_apptrust_contains(port->chip_port, DCB_APP_SEL_PCP)) {
		qos.pcp.enable = true;

		if (pcp_rewr)
			qos.pcp_rewr.enable = true;
	}

	/* Enable use of dscp for queue classification */
	if (lan966x_dcb_apptrust_contains(port->chip_port, IEEE_8021QAZ_APP_SEL_DSCP)) {
		qos.dscp.enable = true;

		if (dscp_rewr)
			qos.dscp_rewr.enable = true;
	}

	lan966x_port_qos_set(port, &qos);
}

/* DSCP mapping is global for all ports, so set and delete app entries are
 * replicated for each port.
 */
static int lan966x_dcb_ieee_dscp_setdel(struct net_device *dev,
					struct dcb_app *app,
					int (*setdel)(struct net_device *,
						      struct dcb_app *))
{
	struct lan966x_port *port = netdev_priv(dev);
	struct lan966x *lan966x = port->lan966x;
	int err;

	for (int i = 0; i < NUM_PHYS_PORTS; i++) {
		port = lan966x->ports[i];
		if (!port)
			continue;

		err = setdel(port->dev, app);
		if (err)
			return err;
	}

	return 0;
}

static int lan966x_dcb_app_validate(struct net_device *dev,
				    const struct dcb_app *app)
{
	int err = 0;

	switch (app->selector) {
	/* Default priority checks */
	case IEEE_8021QAZ_APP_SEL_ETHERTYPE:
		if (app->protocol)
			err = -EINVAL;
		else if (app->priority >= NUM_PRIO_QUEUES)
			err = -ERANGE;
		break;
	/* Dscp checks */
	case IEEE_8021QAZ_APP_SEL_DSCP:
		if (app->protocol >= LAN966X_PORT_QOS_DSCP_COUNT)
			err = -EINVAL;
		else if (app->priority >= NUM_PRIO_QUEUES)
			err = -ERANGE;
		break;
	/* Pcp checks */
	case DCB_APP_SEL_PCP:
		if (app->protocol >= LAN966X_PORT_QOS_PCP_DEI_COUNT)
			err = -EINVAL;
		else if (app->priority >= NUM_PRIO_QUEUES)
			err = -ERANGE;
		break;
	default:
		err = -EINVAL;
		break;
	}

	if (err)
		netdev_err(dev, "Invalid entry: %d:%d\n", app->protocol,
			   app->priority);

	return err;
}

static int lan966x_dcb_ieee_delapp(struct net_device *dev, struct dcb_app *app)
{
	int err;

	if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP)
		err = lan966x_dcb_ieee_dscp_setdel(dev, app, dcb_ieee_delapp);
	else
		err = dcb_ieee_delapp(dev, app);

	if (err)
		return err;

	lan966x_dcb_app_update(dev);

	return 0;
}

static int lan966x_dcb_ieee_setapp(struct net_device *dev, struct dcb_app *app)
{
	struct dcb_app app_itr;
	int err;
	u8 prio;

	err = lan966x_dcb_app_validate(dev, app);
	if (err)
		return err;

	/* Delete current mapping, if it exists */
	prio = dcb_getapp(dev, app);
	if (prio) {
		app_itr = *app;
		app_itr.priority = prio;
		lan966x_dcb_ieee_delapp(dev, &app_itr);
	}

	if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP)
		err = lan966x_dcb_ieee_dscp_setdel(dev, app, dcb_ieee_setapp);
	else
		err = dcb_ieee_setapp(dev, app);

	if (err)
		return err;

	lan966x_dcb_app_update(dev);

	return 0;
}

static int lan966x_dcb_apptrust_validate(struct net_device *dev,
					 u8 *selectors,
					 int nselectors)
{
	for (int i = 0; i < ARRAY_SIZE(lan966x_dcb_apptrust_policies); i++) {
		bool match;

		if (lan966x_dcb_apptrust_policies[i].nselectors != nselectors)
			continue;

		match = true;
		for (int j = 0; j < nselectors; j++) {
			if (lan966x_dcb_apptrust_policies[i].selectors[j] !=
			    *(selectors + j)) {
				match = false;
				break;
			}
		}
		if (match)
			return i;
	}

	netdev_err(dev, "Valid apptrust configurations are:\n");
	for (int i = 0; i < ARRAY_SIZE(lan966x_dcb_apptrust_names); i++)
		pr_info("order: %s\n", lan966x_dcb_apptrust_names[i]);

	return -EOPNOTSUPP;
}

static int lan966x_dcb_setapptrust(struct net_device *dev,
				   u8 *selectors,
				   int nselectors)
{
	struct lan966x_port *port = netdev_priv(dev);
	int idx;

	idx = lan966x_dcb_apptrust_validate(dev, selectors, nselectors);
	if (idx < 0)
		return idx;

	lan966x_port_apptrust[port->chip_port] = &lan966x_dcb_apptrust_policies[idx];
	lan966x_dcb_app_update(dev);

	return 0;
}

static int lan966x_dcb_getapptrust(struct net_device *dev, u8 *selectors,
				   int *nselectors)
{
	struct lan966x_port *port = netdev_priv(dev);
	const struct lan966x_dcb_apptrust *trust;

	trust = lan966x_port_apptrust[port->chip_port];

	memcpy(selectors, trust->selectors, trust->nselectors);
	*nselectors = trust->nselectors;

	return 0;
}

static int lan966x_dcb_delrewr(struct net_device *dev, struct dcb_app *app)
{
	int err;

	if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP)
		err = lan966x_dcb_ieee_dscp_setdel(dev, app, dcb_delrewr);
	else
		err = dcb_delrewr(dev, app);

	if (err < 0)
		return err;

	lan966x_dcb_app_update(dev);

	return 0;
}

static int lan966x_dcb_setrewr(struct net_device *dev, struct dcb_app *app)
{
	struct dcb_app app_itr;
	u16 proto;
	int err;

	err = lan966x_dcb_app_validate(dev, app);
	if (err)
		goto out;

	/* Delete current mapping, if it exists. */
	proto = dcb_getrewr(dev, app);
	if (proto) {
		app_itr = *app;
		app_itr.protocol = proto;
		lan966x_dcb_delrewr(dev, &app_itr);
	}

	if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP)
		err = lan966x_dcb_ieee_dscp_setdel(dev, app, dcb_setrewr);
	else
		err = dcb_setrewr(dev, app);

	if (err)
		goto out;

	lan966x_dcb_app_update(dev);

out:
	return err;
}

static const struct dcbnl_rtnl_ops lan966x_dcbnl_ops = {
	.ieee_setapp = lan966x_dcb_ieee_setapp,
	.ieee_delapp = lan966x_dcb_ieee_delapp,
	.dcbnl_setapptrust = lan966x_dcb_setapptrust,
	.dcbnl_getapptrust = lan966x_dcb_getapptrust,
	.dcbnl_setrewr = lan966x_dcb_setrewr,
	.dcbnl_delrewr = lan966x_dcb_delrewr,
};

void lan966x_dcb_init(struct lan966x *lan966x)
{
	for (int p = 0; p < lan966x->num_phys_ports; ++p) {
		struct lan966x_port *port;

		port = lan966x->ports[p];
		if (!port)
			continue;

		port->dev->dcbnl_ops = &lan966x_dcbnl_ops;

		lan966x_port_apptrust[port->chip_port] =
			&lan966x_dcb_apptrust_policies[LAN966X_DCB_APPTRUST_DSCP_PCP];

		/* Enable DSCP classification based on classified QoS class and
		 * DP, for all DSCP values, for all ports.
		 */
		lan966x_port_qos_dscp_rewr_mode_set(port,
						    LAN966X_PORT_QOS_REWR_DSCP_ALL);
	}
}