| // 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; |
| } |