mlxsw: spectrum: Add IEEE 802.1Qaz ETS support
[cascardo/linux.git] / drivers / net / ethernet / mellanox / mlxsw / spectrum.c
index 4afbc3e..1498e6a 100644 (file)
@@ -49,6 +49,7 @@
 #include <linux/jiffies.h>
 #include <linux/bitops.h>
 #include <linux/list.h>
+#include <linux/dcbnl.h>
 #include <net/devlink.h>
 #include <net/switchdev.h>
 #include <generated/utsrelease.h>
@@ -305,9 +306,9 @@ mlxsw_sp_port_system_port_mapping_set(struct mlxsw_sp_port *mlxsw_sp_port)
        return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sspr), sspr_pl);
 }
 
-static int mlxsw_sp_port_module_info_get(struct mlxsw_sp *mlxsw_sp,
-                                        u8 local_port, u8 *p_module,
-                                        u8 *p_width)
+static int __mlxsw_sp_port_module_info_get(struct mlxsw_sp *mlxsw_sp,
+                                          u8 local_port, u8 *p_module,
+                                          u8 *p_width, u8 *p_lane)
 {
        char pmlp_pl[MLXSW_REG_PMLP_LEN];
        int err;
@@ -318,9 +319,20 @@ static int mlxsw_sp_port_module_info_get(struct mlxsw_sp *mlxsw_sp,
                return err;
        *p_module = mlxsw_reg_pmlp_module_get(pmlp_pl, 0);
        *p_width = mlxsw_reg_pmlp_width_get(pmlp_pl);
+       *p_lane = mlxsw_reg_pmlp_tx_lane_get(pmlp_pl, 0);
        return 0;
 }
 
+static int mlxsw_sp_port_module_info_get(struct mlxsw_sp *mlxsw_sp,
+                                        u8 local_port, u8 *p_module,
+                                        u8 *p_width)
+{
+       u8 lane;
+
+       return __mlxsw_sp_port_module_info_get(mlxsw_sp, local_port, p_module,
+                                              p_width, &lane);
+}
+
 static int mlxsw_sp_port_module_map(struct mlxsw_sp *mlxsw_sp, u8 local_port,
                                    u8 module, u8 width, u8 lane)
 {
@@ -438,16 +450,72 @@ static int mlxsw_sp_port_set_mac_address(struct net_device *dev, void *p)
        return 0;
 }
 
+static void mlxsw_sp_pg_buf_pack(char *pbmc_pl, int pg_index, int mtu)
+{
+       u16 pg_size = 2 * MLXSW_SP_BYTES_TO_CELLS(mtu);
+
+       mlxsw_reg_pbmc_lossy_buffer_pack(pbmc_pl, pg_index, pg_size);
+}
+
+int __mlxsw_sp_port_headroom_set(struct mlxsw_sp_port *mlxsw_sp_port, int mtu,
+                                u8 *prio_tc)
+{
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
+       char pbmc_pl[MLXSW_REG_PBMC_LEN];
+       int i, j, err;
+
+       mlxsw_reg_pbmc_pack(pbmc_pl, mlxsw_sp_port->local_port, 0, 0);
+       err = mlxsw_reg_query(mlxsw_sp->core, MLXSW_REG(pbmc), pbmc_pl);
+       if (err)
+               return err;
+
+       for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
+               bool configure = false;
+
+               for (j = 0; j < IEEE_8021QAZ_MAX_TCS; j++) {
+                       if (prio_tc[j] == i) {
+                               configure = true;
+                               break;
+                       }
+               }
+
+               if (!configure)
+                       continue;
+               mlxsw_sp_pg_buf_pack(pbmc_pl, i, mtu);
+       }
+
+       return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pbmc), pbmc_pl);
+}
+
+static int mlxsw_sp_port_headroom_set(struct mlxsw_sp_port *mlxsw_sp_port,
+                                     int mtu)
+{
+       u8 def_prio_tc[IEEE_8021QAZ_MAX_TCS] = {0};
+       bool dcb_en = !!mlxsw_sp_port->dcb.ets;
+       u8 *prio_tc;
+
+       prio_tc = dcb_en ? mlxsw_sp_port->dcb.ets->prio_tc : def_prio_tc;
+
+       return __mlxsw_sp_port_headroom_set(mlxsw_sp_port, mtu, prio_tc);
+}
+
 static int mlxsw_sp_port_change_mtu(struct net_device *dev, int mtu)
 {
        struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
        int err;
 
-       err = mlxsw_sp_port_mtu_set(mlxsw_sp_port, mtu);
+       err = mlxsw_sp_port_headroom_set(mlxsw_sp_port, mtu);
        if (err)
                return err;
+       err = mlxsw_sp_port_mtu_set(mlxsw_sp_port, mtu);
+       if (err)
+               goto err_port_mtu_set;
        dev->mtu = mtu;
        return 0;
+
+err_port_mtu_set:
+       mlxsw_sp_port_headroom_set(mlxsw_sp_port, dev->mtu);
+       return err;
 }
 
 static struct rtnl_link_stats64 *
@@ -861,6 +929,33 @@ int mlxsw_sp_port_kill_vid(struct net_device *dev,
        return 0;
 }
 
+static int mlxsw_sp_port_get_phys_port_name(struct net_device *dev, char *name,
+                                           size_t len)
+{
+       struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
+       u8 module, width, lane;
+       int err;
+
+       err = __mlxsw_sp_port_module_info_get(mlxsw_sp_port->mlxsw_sp,
+                                             mlxsw_sp_port->local_port,
+                                             &module, &width, &lane);
+       if (err) {
+               netdev_err(dev, "Failed to retrieve module information\n");
+               return err;
+       }
+
+       if (!mlxsw_sp_port->split)
+               err = snprintf(name, len, "p%d", module + 1);
+       else
+               err = snprintf(name, len, "p%ds%d", module + 1,
+                              lane / width);
+
+       if (err >= len)
+               return -EINVAL;
+
+       return 0;
+}
+
 static const struct net_device_ops mlxsw_sp_port_netdev_ops = {
        .ndo_open               = mlxsw_sp_port_open,
        .ndo_stop               = mlxsw_sp_port_stop,
@@ -877,6 +972,7 @@ static const struct net_device_ops mlxsw_sp_port_netdev_ops = {
        .ndo_bridge_setlink     = switchdev_port_bridge_setlink,
        .ndo_bridge_getlink     = switchdev_port_bridge_getlink,
        .ndo_bridge_dellink     = switchdev_port_bridge_dellink,
+       .ndo_get_phys_port_name = mlxsw_sp_port_get_phys_port_name,
 };
 
 static void mlxsw_sp_port_get_drvinfo(struct net_device *dev,
@@ -1402,6 +1498,108 @@ mlxsw_sp_port_speed_by_width_set(struct mlxsw_sp_port *mlxsw_sp_port, u8 width)
        return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ptys), ptys_pl);
 }
 
+int mlxsw_sp_port_ets_set(struct mlxsw_sp_port *mlxsw_sp_port,
+                         enum mlxsw_reg_qeec_hr hr, u8 index, u8 next_index,
+                         bool dwrr, u8 dwrr_weight)
+{
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
+       char qeec_pl[MLXSW_REG_QEEC_LEN];
+
+       mlxsw_reg_qeec_pack(qeec_pl, mlxsw_sp_port->local_port, hr, index,
+                           next_index);
+       mlxsw_reg_qeec_de_set(qeec_pl, true);
+       mlxsw_reg_qeec_dwrr_set(qeec_pl, dwrr);
+       mlxsw_reg_qeec_dwrr_weight_set(qeec_pl, dwrr_weight);
+       return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(qeec), qeec_pl);
+}
+
+static int mlxsw_sp_port_ets_maxrate_set(struct mlxsw_sp_port *mlxsw_sp_port,
+                                        enum mlxsw_reg_qeec_hr hr, u8 index,
+                                        u8 next_index, u32 maxrate)
+{
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
+       char qeec_pl[MLXSW_REG_QEEC_LEN];
+
+       mlxsw_reg_qeec_pack(qeec_pl, mlxsw_sp_port->local_port, hr, index,
+                           next_index);
+       mlxsw_reg_qeec_mase_set(qeec_pl, true);
+       mlxsw_reg_qeec_max_shaper_rate_set(qeec_pl, maxrate);
+       return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(qeec), qeec_pl);
+}
+
+int mlxsw_sp_port_prio_tc_set(struct mlxsw_sp_port *mlxsw_sp_port,
+                             u8 switch_prio, u8 tclass)
+{
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
+       char qtct_pl[MLXSW_REG_QTCT_LEN];
+
+       mlxsw_reg_qtct_pack(qtct_pl, mlxsw_sp_port->local_port, switch_prio,
+                           tclass);
+       return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(qtct), qtct_pl);
+}
+
+static int mlxsw_sp_port_ets_init(struct mlxsw_sp_port *mlxsw_sp_port)
+{
+       int err, i;
+
+       /* Setup the elements hierarcy, so that each TC is linked to
+        * one subgroup, which are all member in the same group.
+        */
+       err = mlxsw_sp_port_ets_set(mlxsw_sp_port,
+                                   MLXSW_REG_QEEC_HIERARCY_GROUP, 0, 0, false,
+                                   0);
+       if (err)
+               return err;
+       for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
+               err = mlxsw_sp_port_ets_set(mlxsw_sp_port,
+                                           MLXSW_REG_QEEC_HIERARCY_SUBGROUP, i,
+                                           0, false, 0);
+               if (err)
+                       return err;
+       }
+       for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
+               err = mlxsw_sp_port_ets_set(mlxsw_sp_port,
+                                           MLXSW_REG_QEEC_HIERARCY_TC, i, i,
+                                           false, 0);
+               if (err)
+                       return err;
+       }
+
+       /* Make sure the max shaper is disabled in all hierarcies that
+        * support it.
+        */
+       err = mlxsw_sp_port_ets_maxrate_set(mlxsw_sp_port,
+                                           MLXSW_REG_QEEC_HIERARCY_PORT, 0, 0,
+                                           MLXSW_REG_QEEC_MAS_DIS);
+       if (err)
+               return err;
+       for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
+               err = mlxsw_sp_port_ets_maxrate_set(mlxsw_sp_port,
+                                                   MLXSW_REG_QEEC_HIERARCY_SUBGROUP,
+                                                   i, 0,
+                                                   MLXSW_REG_QEEC_MAS_DIS);
+               if (err)
+                       return err;
+       }
+       for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
+               err = mlxsw_sp_port_ets_maxrate_set(mlxsw_sp_port,
+                                                   MLXSW_REG_QEEC_HIERARCY_TC,
+                                                   i, i,
+                                                   MLXSW_REG_QEEC_MAS_DIS);
+               if (err)
+                       return err;
+       }
+
+       /* Map all priorities to traffic class 0. */
+       for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
+               err = mlxsw_sp_port_prio_tc_set(mlxsw_sp_port, i, 0);
+               if (err)
+                       return err;
+       }
+
+       return 0;
+}
+
 static int __mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port,
                                  bool split, u8 module, u8 width)
 {
@@ -1509,6 +1707,21 @@ static int __mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port,
                goto err_port_buffers_init;
        }
 
+       err = mlxsw_sp_port_ets_init(mlxsw_sp_port);
+       if (err) {
+               dev_err(mlxsw_sp->bus_info->dev, "Port %d: Failed to initialize ETS\n",
+                       mlxsw_sp_port->local_port);
+               goto err_port_ets_init;
+       }
+
+       /* ETS and buffers must be initialized before DCB. */
+       err = mlxsw_sp_port_dcb_init(mlxsw_sp_port);
+       if (err) {
+               dev_err(mlxsw_sp->bus_info->dev, "Port %d: Failed to initialize DCB\n",
+                       mlxsw_sp_port->local_port);
+               goto err_port_dcb_init;
+       }
+
        mlxsw_sp_port_switchdev_init(mlxsw_sp_port);
        err = register_netdev(dev);
        if (err) {
@@ -1529,6 +1742,8 @@ static int __mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port,
 err_port_vlan_init:
        unregister_netdev(dev);
 err_register_netdev:
+err_port_dcb_init:
+err_port_ets_init:
 err_port_buffers_init:
 err_port_admin_status_set:
 err_port_mtu_set:
@@ -1598,6 +1813,7 @@ static void mlxsw_sp_port_remove(struct mlxsw_sp *mlxsw_sp, u8 local_port)
        devlink_port = &mlxsw_sp_port->devlink_port;
        devlink_port_type_clear(devlink_port);
        unregister_netdev(mlxsw_sp_port->dev); /* This calls ndo_stop */
+       mlxsw_sp_port_dcb_fini(mlxsw_sp_port);
        devlink_port_unregister(devlink_port);
        mlxsw_sp_port_vports_fini(mlxsw_sp_port);
        mlxsw_sp_port_switchdev_fini(mlxsw_sp_port);