|  | // SPDX-License-Identifier: GPL-2.0+ | 
|  | /* Microchip Sparx5 Switch driver | 
|  | * | 
|  | * Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries. | 
|  | */ | 
|  |  | 
|  | #include <net/dcbnl.h> | 
|  |  | 
|  | #include "sparx5_port.h" | 
|  |  | 
|  | enum sparx5_dcb_apptrust_values { | 
|  | SPARX5_DCB_APPTRUST_EMPTY, | 
|  | SPARX5_DCB_APPTRUST_DSCP, | 
|  | SPARX5_DCB_APPTRUST_PCP, | 
|  | SPARX5_DCB_APPTRUST_DSCP_PCP, | 
|  | __SPARX5_DCB_APPTRUST_MAX | 
|  | }; | 
|  |  | 
|  | static const struct sparx5_dcb_apptrust { | 
|  | u8 selectors[IEEE_8021QAZ_APP_SEL_MAX + 1]; | 
|  | int nselectors; | 
|  | } *sparx5_port_apptrust[SPX5_PORTS]; | 
|  |  | 
|  | static const char *sparx5_dcb_apptrust_names[__SPARX5_DCB_APPTRUST_MAX] = { | 
|  | [SPARX5_DCB_APPTRUST_EMPTY]    = "empty", | 
|  | [SPARX5_DCB_APPTRUST_DSCP]     = "dscp", | 
|  | [SPARX5_DCB_APPTRUST_PCP]      = "pcp", | 
|  | [SPARX5_DCB_APPTRUST_DSCP_PCP] = "dscp pcp" | 
|  | }; | 
|  |  | 
|  | /* Sparx5 supported apptrust policies */ | 
|  | static const struct sparx5_dcb_apptrust | 
|  | sparx5_dcb_apptrust_policies[__SPARX5_DCB_APPTRUST_MAX] = { | 
|  | /* Empty *must* be first */ | 
|  | [SPARX5_DCB_APPTRUST_EMPTY]    = { { 0 }, 0 }, | 
|  | [SPARX5_DCB_APPTRUST_DSCP]     = { { IEEE_8021QAZ_APP_SEL_DSCP }, 1 }, | 
|  | [SPARX5_DCB_APPTRUST_PCP]      = { { DCB_APP_SEL_PCP }, 1 }, | 
|  | [SPARX5_DCB_APPTRUST_DSCP_PCP] = { { IEEE_8021QAZ_APP_SEL_DSCP, | 
|  | DCB_APP_SEL_PCP }, 2 }, | 
|  | }; | 
|  |  | 
|  | /* Validate app entry. | 
|  | * | 
|  | * Check for valid selectors and valid protocol and priority ranges. | 
|  | */ | 
|  | static int sparx5_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 != 0) | 
|  | err = -EINVAL; | 
|  | else if (app->priority >= SPX5_PRIOS) | 
|  | err = -ERANGE; | 
|  | break; | 
|  | /* Dscp checks */ | 
|  | case IEEE_8021QAZ_APP_SEL_DSCP: | 
|  | if (app->protocol >= SPARX5_PORT_QOS_DSCP_COUNT) | 
|  | err = -EINVAL; | 
|  | else if (app->priority >= SPX5_PRIOS) | 
|  | err = -ERANGE; | 
|  | break; | 
|  | /* Pcp checks */ | 
|  | case DCB_APP_SEL_PCP: | 
|  | if (app->protocol >= SPARX5_PORT_QOS_PCP_DEI_COUNT) | 
|  | err = -EINVAL; | 
|  | else if (app->priority >= SPX5_PRIOS) | 
|  | err = -ERANGE; | 
|  | break; | 
|  | default: | 
|  | err = -EINVAL; | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (err) | 
|  | netdev_err(dev, "Invalid entry: %d:%d\n", app->protocol, | 
|  | app->priority); | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | /* Validate apptrust configuration. | 
|  | * | 
|  | * Return index of supported apptrust configuration if valid, otherwise return | 
|  | * error. | 
|  | */ | 
|  | static int sparx5_dcb_apptrust_validate(struct net_device *dev, u8 *selectors, | 
|  | int nselectors, int *err) | 
|  | { | 
|  | bool match = false; | 
|  | int i, ii; | 
|  |  | 
|  | for (i = 0; i < ARRAY_SIZE(sparx5_dcb_apptrust_policies); i++) { | 
|  | if (sparx5_dcb_apptrust_policies[i].nselectors != nselectors) | 
|  | continue; | 
|  | match = true; | 
|  | for (ii = 0; ii < nselectors; ii++) { | 
|  | if (sparx5_dcb_apptrust_policies[i].selectors[ii] != | 
|  | *(selectors + ii)) { | 
|  | match = false; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (match) | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* Requested trust configuration is not supported */ | 
|  | if (!match) { | 
|  | netdev_err(dev, "Valid apptrust configurations are:\n"); | 
|  | for (i = 0; i < ARRAY_SIZE(sparx5_dcb_apptrust_names); i++) | 
|  | pr_info("order: %s\n", sparx5_dcb_apptrust_names[i]); | 
|  | *err = -EOPNOTSUPP; | 
|  | } | 
|  |  | 
|  | return i; | 
|  | } | 
|  |  | 
|  | static bool sparx5_dcb_apptrust_contains(int portno, u8 selector) | 
|  | { | 
|  | const struct sparx5_dcb_apptrust *conf = sparx5_port_apptrust[portno]; | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < conf->nselectors; i++) | 
|  | if (conf->selectors[i] == selector) | 
|  | return true; | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static int sparx5_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 sparx5_port *port = netdev_priv(dev); | 
|  | struct sparx5_port_qos_dscp_map *dscp_map; | 
|  | struct sparx5_port_qos_pcp_map *pcp_map; | 
|  | struct sparx5_port_qos qos = {0}; | 
|  | struct dcb_app app_itr = {0}; | 
|  | int portno = port->portno; | 
|  | bool dscp_rewr = false; | 
|  | bool pcp_rewr = false; | 
|  | u16 dscp; | 
|  | int i; | 
|  |  | 
|  | dscp_map = &qos.dscp.map; | 
|  | pcp_map = &qos.pcp.map; | 
|  |  | 
|  | /* 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 dscp ingress mapping */ | 
|  | for (i = 0; i < ARRAY_SIZE(dscp_map->map); i++) { | 
|  | app_itr.selector = IEEE_8021QAZ_APP_SEL_DSCP; | 
|  | app_itr.protocol = i; | 
|  | dscp_map->map[i] = dcb_getapp(dev, &app_itr); | 
|  | } | 
|  |  | 
|  | /* Get pcp ingress mapping */ | 
|  | for (i = 0; i < ARRAY_SIZE(pcp_map->map); i++) { | 
|  | app_itr.selector = DCB_APP_SEL_PCP; | 
|  | app_itr.protocol = i; | 
|  | pcp_map->map[i] = dcb_getapp(dev, &app_itr); | 
|  | } | 
|  |  | 
|  | /* Get pcp rewrite mapping */ | 
|  | dcb_getrewr_prio_pcp_mask_map(dev, &pcp_rewr_map); | 
|  | for (i = 0; i < ARRAY_SIZE(pcp_rewr_map.map); i++) { | 
|  | if (!pcp_rewr_map.map[i]) | 
|  | continue; | 
|  | pcp_rewr = true; | 
|  | qos.pcp_rewr.map.map[i] = fls(pcp_rewr_map.map[i]) - 1; | 
|  | } | 
|  |  | 
|  | /* Get dscp rewrite mapping */ | 
|  | dcb_getrewr_prio_dscp_mask_map(dev, &dscp_rewr_map); | 
|  | for (i = 0; i < ARRAY_SIZE(dscp_rewr_map.map); i++) { | 
|  | if (!dscp_rewr_map.map[i]) | 
|  | continue; | 
|  |  | 
|  | /* The rewrite table of the switch has 32 entries; one for each | 
|  | * priority for each DP level. Currently, the rewrite map does | 
|  | * not indicate DP level, so we map classified QoS class to | 
|  | * classified DSCP, for each classified DP level. Rewrite of | 
|  | * DSCP is only enabled, if we have active mappings. | 
|  | */ | 
|  | dscp_rewr = true; | 
|  | dscp = fls64(dscp_rewr_map.map[i]) - 1; | 
|  | qos.dscp_rewr.map.map[i] = dscp;      /* DP 0 */ | 
|  | qos.dscp_rewr.map.map[i + 8] = dscp;  /* DP 1 */ | 
|  | qos.dscp_rewr.map.map[i + 16] = dscp; /* DP 2 */ | 
|  | qos.dscp_rewr.map.map[i + 24] = dscp; /* DP 3 */ | 
|  | } | 
|  |  | 
|  | /* Enable use of pcp for queue classification ? */ | 
|  | if (sparx5_dcb_apptrust_contains(portno, DCB_APP_SEL_PCP)) { | 
|  | qos.pcp.qos_enable = true; | 
|  | qos.pcp.dp_enable = qos.pcp.qos_enable; | 
|  | /* Enable rewrite of PCP and DEI if PCP is trusted *and* rewrite | 
|  | * table is not empty. | 
|  | */ | 
|  | if (pcp_rewr) | 
|  | qos.pcp_rewr.enable = true; | 
|  | } | 
|  |  | 
|  | /* Enable use of dscp for queue classification ? */ | 
|  | if (sparx5_dcb_apptrust_contains(portno, IEEE_8021QAZ_APP_SEL_DSCP)) { | 
|  | qos.dscp.qos_enable = true; | 
|  | qos.dscp.dp_enable = qos.dscp.qos_enable; | 
|  | if (dscp_rewr) | 
|  | /* Do not enable rewrite if no mappings are active, as | 
|  | * classified DSCP will then be zero for all classified | 
|  | * QoS class and DP combinations. | 
|  | */ | 
|  | qos.dscp_rewr.enable = true; | 
|  | } | 
|  |  | 
|  | return sparx5_port_qos_set(port, &qos); | 
|  | } | 
|  |  | 
|  | /* Set or delete DSCP app entry. | 
|  | * | 
|  | * DSCP mapping is global for all ports, so set and delete app entries are | 
|  | * replicated for each port. | 
|  | */ | 
|  | static int sparx5_dcb_ieee_dscp_setdel(struct net_device *dev, | 
|  | struct dcb_app *app, | 
|  | int (*setdel)(struct net_device *, | 
|  | struct dcb_app *)) | 
|  | { | 
|  | struct sparx5_port *port = netdev_priv(dev); | 
|  | struct sparx5_port *port_itr; | 
|  | int err, i; | 
|  |  | 
|  | for (i = 0; i < SPX5_PORTS; i++) { | 
|  | port_itr = port->sparx5->ports[i]; | 
|  | if (!port_itr) | 
|  | continue; | 
|  | err = setdel(port_itr->ndev, app); | 
|  | if (err) | 
|  | return err; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int sparx5_dcb_ieee_delapp(struct net_device *dev, struct dcb_app *app) | 
|  | { | 
|  | int err; | 
|  |  | 
|  | if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP) | 
|  | err = sparx5_dcb_ieee_dscp_setdel(dev, app, dcb_ieee_delapp); | 
|  | else | 
|  | err = dcb_ieee_delapp(dev, app); | 
|  |  | 
|  | if (err < 0) | 
|  | return err; | 
|  |  | 
|  | return sparx5_dcb_app_update(dev); | 
|  | } | 
|  |  | 
|  | static int sparx5_dcb_ieee_setapp(struct net_device *dev, struct dcb_app *app) | 
|  | { | 
|  | struct dcb_app app_itr; | 
|  | int err = 0; | 
|  | u8 prio; | 
|  |  | 
|  | err = sparx5_dcb_app_validate(dev, app); | 
|  | if (err) | 
|  | goto out; | 
|  |  | 
|  | /* Delete current mapping, if it exists */ | 
|  | prio = dcb_getapp(dev, app); | 
|  | if (prio) { | 
|  | app_itr = *app; | 
|  | app_itr.priority = prio; | 
|  | sparx5_dcb_ieee_delapp(dev, &app_itr); | 
|  | } | 
|  |  | 
|  | if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP) | 
|  | err = sparx5_dcb_ieee_dscp_setdel(dev, app, dcb_ieee_setapp); | 
|  | else | 
|  | err = dcb_ieee_setapp(dev, app); | 
|  |  | 
|  | if (err) | 
|  | goto out; | 
|  |  | 
|  | sparx5_dcb_app_update(dev); | 
|  |  | 
|  | out: | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int sparx5_dcb_setapptrust(struct net_device *dev, u8 *selectors, | 
|  | int nselectors) | 
|  | { | 
|  | struct sparx5_port *port = netdev_priv(dev); | 
|  | int err = 0, idx; | 
|  |  | 
|  | idx = sparx5_dcb_apptrust_validate(dev, selectors, nselectors, &err); | 
|  | if (err < 0) | 
|  | return err; | 
|  |  | 
|  | sparx5_port_apptrust[port->portno] = &sparx5_dcb_apptrust_policies[idx]; | 
|  |  | 
|  | return sparx5_dcb_app_update(dev); | 
|  | } | 
|  |  | 
|  | static int sparx5_dcb_getapptrust(struct net_device *dev, u8 *selectors, | 
|  | int *nselectors) | 
|  | { | 
|  | struct sparx5_port *port = netdev_priv(dev); | 
|  | const struct sparx5_dcb_apptrust *trust; | 
|  |  | 
|  | trust = sparx5_port_apptrust[port->portno]; | 
|  |  | 
|  | memcpy(selectors, trust->selectors, trust->nselectors); | 
|  | *nselectors = trust->nselectors; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int sparx5_dcb_delrewr(struct net_device *dev, struct dcb_app *app) | 
|  | { | 
|  | int err; | 
|  |  | 
|  | if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP) | 
|  | err = sparx5_dcb_ieee_dscp_setdel(dev, app, dcb_delrewr); | 
|  | else | 
|  | err = dcb_delrewr(dev, app); | 
|  |  | 
|  | if (err < 0) | 
|  | return err; | 
|  |  | 
|  | return sparx5_dcb_app_update(dev); | 
|  | } | 
|  |  | 
|  | static int sparx5_dcb_setrewr(struct net_device *dev, struct dcb_app *app) | 
|  | { | 
|  | struct dcb_app app_itr; | 
|  | int err = 0; | 
|  | u16 proto; | 
|  |  | 
|  | err = sparx5_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; | 
|  | sparx5_dcb_delrewr(dev, &app_itr); | 
|  | } | 
|  |  | 
|  | if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP) | 
|  | err = sparx5_dcb_ieee_dscp_setdel(dev, app, dcb_setrewr); | 
|  | else | 
|  | err = dcb_setrewr(dev, app); | 
|  |  | 
|  | if (err) | 
|  | goto out; | 
|  |  | 
|  | sparx5_dcb_app_update(dev); | 
|  |  | 
|  | out: | 
|  | return err; | 
|  | } | 
|  |  | 
|  | const struct dcbnl_rtnl_ops sparx5_dcbnl_ops = { | 
|  | .ieee_setapp = sparx5_dcb_ieee_setapp, | 
|  | .ieee_delapp = sparx5_dcb_ieee_delapp, | 
|  | .dcbnl_setapptrust = sparx5_dcb_setapptrust, | 
|  | .dcbnl_getapptrust = sparx5_dcb_getapptrust, | 
|  | .dcbnl_setrewr = sparx5_dcb_setrewr, | 
|  | .dcbnl_delrewr = sparx5_dcb_delrewr, | 
|  | }; | 
|  |  | 
|  | int sparx5_dcb_init(struct sparx5 *sparx5) | 
|  | { | 
|  | struct sparx5_port *port; | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < SPX5_PORTS; i++) { | 
|  | port = sparx5->ports[i]; | 
|  | if (!port) | 
|  | continue; | 
|  | port->ndev->dcbnl_ops = &sparx5_dcbnl_ops; | 
|  | /* Initialize [dscp, pcp] default trust */ | 
|  | sparx5_port_apptrust[port->portno] = | 
|  | &sparx5_dcb_apptrust_policies | 
|  | [SPARX5_DCB_APPTRUST_DSCP_PCP]; | 
|  |  | 
|  | /* Enable DSCP classification based on classified QoS class and | 
|  | * DP, for all DSCP values, for all ports. | 
|  | */ | 
|  | sparx5_port_qos_dscp_rewr_mode_set(port, | 
|  | SPARX5_PORT_REW_DSCP_ALL); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } |