diff options
Diffstat (limited to 'drivers/net/ethernet/mellanox/mlxsw/spectrum.c')
-rw-r--r-- | drivers/net/ethernet/mellanox/mlxsw/spectrum.c | 272 |
1 files changed, 253 insertions, 19 deletions
diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum.c index a94daa8c346c..4afbc3e9e381 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum.c @@ -49,6 +49,7 @@ #include <linux/jiffies.h> #include <linux/bitops.h> #include <linux/list.h> +#include <net/devlink.h> #include <net/switchdev.h> #include <generated/utsrelease.h> @@ -304,21 +305,47 @@ 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_check(struct mlxsw_sp_port *mlxsw_sp_port, - bool *p_usable) +static int mlxsw_sp_port_module_info_get(struct mlxsw_sp *mlxsw_sp, + u8 local_port, u8 *p_module, + u8 *p_width) { - struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp; char pmlp_pl[MLXSW_REG_PMLP_LEN]; int err; - mlxsw_reg_pmlp_pack(pmlp_pl, mlxsw_sp_port->local_port); + mlxsw_reg_pmlp_pack(pmlp_pl, local_port); err = mlxsw_reg_query(mlxsw_sp->core, MLXSW_REG(pmlp), pmlp_pl); if (err) return err; - *p_usable = mlxsw_reg_pmlp_width_get(pmlp_pl) ? true : false; + *p_module = mlxsw_reg_pmlp_module_get(pmlp_pl, 0); + *p_width = mlxsw_reg_pmlp_width_get(pmlp_pl); return 0; } +static int mlxsw_sp_port_module_map(struct mlxsw_sp *mlxsw_sp, u8 local_port, + u8 module, u8 width, u8 lane) +{ + char pmlp_pl[MLXSW_REG_PMLP_LEN]; + int i; + + mlxsw_reg_pmlp_pack(pmlp_pl, local_port); + mlxsw_reg_pmlp_width_set(pmlp_pl, width); + for (i = 0; i < width; i++) { + mlxsw_reg_pmlp_module_set(pmlp_pl, i, module); + mlxsw_reg_pmlp_tx_lane_set(pmlp_pl, i, lane + i); /* Rx & Tx */ + } + + return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pmlp), pmlp_pl); +} + +static int mlxsw_sp_port_module_unmap(struct mlxsw_sp *mlxsw_sp, u8 local_port) +{ + char pmlp_pl[MLXSW_REG_PMLP_LEN]; + + mlxsw_reg_pmlp_pack(pmlp_pl, local_port); + mlxsw_reg_pmlp_width_set(pmlp_pl, 0); + return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pmlp), pmlp_pl); +} + static int mlxsw_sp_port_open(struct net_device *dev) { struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev); @@ -1273,6 +1300,18 @@ static u32 mlxsw_sp_to_ptys_speed(u32 speed) return ptys_proto; } +static u32 mlxsw_sp_to_ptys_upper_speed(u32 upper_speed) +{ + u32 ptys_proto = 0; + int i; + + for (i = 0; i < MLXSW_SP_PORT_LINK_MODE_LEN; i++) { + if (mlxsw_sp_port_link_mode[i].speed <= upper_speed) + ptys_proto |= mlxsw_sp_port_link_mode[i].mask; + } + return ptys_proto; +} + static int mlxsw_sp_port_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) { @@ -1349,11 +1388,27 @@ static const struct ethtool_ops mlxsw_sp_port_ethtool_ops = { .set_settings = mlxsw_sp_port_set_settings, }; -static int mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port) +static int +mlxsw_sp_port_speed_by_width_set(struct mlxsw_sp_port *mlxsw_sp_port, u8 width) +{ + struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp; + u32 upper_speed = MLXSW_SP_PORT_BASE_SPEED * width; + char ptys_pl[MLXSW_REG_PTYS_LEN]; + u32 eth_proto_admin; + + eth_proto_admin = mlxsw_sp_to_ptys_upper_speed(upper_speed); + mlxsw_reg_ptys_pack(ptys_pl, mlxsw_sp_port->local_port, + eth_proto_admin); + return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ptys), ptys_pl); +} + +static int __mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port, + bool split, u8 module, u8 width) { + struct devlink *devlink = priv_to_devlink(mlxsw_sp->core); struct mlxsw_sp_port *mlxsw_sp_port; + struct devlink_port *devlink_port; struct net_device *dev; - bool usable; size_t bytes; int err; @@ -1364,6 +1419,7 @@ static int mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port) mlxsw_sp_port->dev = dev; mlxsw_sp_port->mlxsw_sp = mlxsw_sp; mlxsw_sp_port->local_port = local_port; + mlxsw_sp_port->split = split; bytes = DIV_ROUND_UP(VLAN_N_VID, BITS_PER_BYTE); mlxsw_sp_port->active_vlans = kzalloc(bytes, GFP_KERNEL); if (!mlxsw_sp_port->active_vlans) { @@ -1404,17 +1460,14 @@ static int mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port) */ dev->hard_header_len += MLXSW_TXHDR_LEN; - err = mlxsw_sp_port_module_check(mlxsw_sp_port, &usable); + devlink_port = &mlxsw_sp_port->devlink_port; + if (mlxsw_sp_port->split) + devlink_port_split_set(devlink_port, module); + err = devlink_port_register(devlink, devlink_port, local_port); if (err) { - dev_err(mlxsw_sp->bus_info->dev, "Port %d: Failed to check module\n", + dev_err(mlxsw_sp->bus_info->dev, "Port %d: Failed to register devlink port\n", mlxsw_sp_port->local_port); - goto err_port_module_check; - } - - if (!usable) { - dev_dbg(mlxsw_sp->bus_info->dev, "Port %d: Not usable, skipping initialization\n", - mlxsw_sp_port->local_port); - goto port_not_usable; + goto err_devlink_port_register; } err = mlxsw_sp_port_system_port_mapping_set(mlxsw_sp_port); @@ -1431,6 +1484,13 @@ static int mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port) goto err_port_swid_set; } + err = mlxsw_sp_port_speed_by_width_set(mlxsw_sp_port, width); + if (err) { + dev_err(mlxsw_sp->bus_info->dev, "Port %d: Failed to enable speeds\n", + mlxsw_sp_port->local_port); + goto err_port_speed_by_width_set; + } + err = mlxsw_sp_port_mtu_set(mlxsw_sp_port, ETH_DATA_LEN); if (err) { dev_err(mlxsw_sp->bus_info->dev, "Port %d: Failed to set MTU\n", @@ -1457,6 +1517,8 @@ static int mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port) goto err_register_netdev; } + devlink_port_type_eth_set(devlink_port, dev); + err = mlxsw_sp_port_vlan_init(mlxsw_sp_port); if (err) goto err_port_vlan_init; @@ -1470,10 +1532,11 @@ err_register_netdev: err_port_buffers_init: err_port_admin_status_set: err_port_mtu_set: +err_port_speed_by_width_set: err_port_swid_set: err_port_system_port_mapping_set: -port_not_usable: -err_port_module_check: + devlink_port_unregister(&mlxsw_sp_port->devlink_port); +err_devlink_port_register: err_dev_addr_init: free_percpu(mlxsw_sp_port->pcpu_stats); err_alloc_stats: @@ -1485,6 +1548,28 @@ err_port_active_vlans_alloc: return err; } +static int mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port, + bool split, u8 module, u8 width, u8 lane) +{ + int err; + + err = mlxsw_sp_port_module_map(mlxsw_sp, local_port, module, width, + lane); + if (err) + return err; + + err = __mlxsw_sp_port_create(mlxsw_sp, local_port, split, module, + width); + if (err) + goto err_port_create; + + return 0; + +err_port_create: + mlxsw_sp_port_module_unmap(mlxsw_sp, local_port); + return err; +} + static void mlxsw_sp_port_vports_fini(struct mlxsw_sp_port *mlxsw_sp_port) { struct net_device *dev = mlxsw_sp_port->dev; @@ -1505,12 +1590,19 @@ static void mlxsw_sp_port_vports_fini(struct mlxsw_sp_port *mlxsw_sp_port) static void mlxsw_sp_port_remove(struct mlxsw_sp *mlxsw_sp, u8 local_port) { struct mlxsw_sp_port *mlxsw_sp_port = mlxsw_sp->ports[local_port]; + struct devlink_port *devlink_port; if (!mlxsw_sp_port) return; + mlxsw_sp->ports[local_port] = NULL; + devlink_port = &mlxsw_sp_port->devlink_port; + devlink_port_type_clear(devlink_port); unregister_netdev(mlxsw_sp_port->dev); /* This calls ndo_stop */ + devlink_port_unregister(devlink_port); mlxsw_sp_port_vports_fini(mlxsw_sp_port); mlxsw_sp_port_switchdev_fini(mlxsw_sp_port); + mlxsw_sp_port_swid_set(mlxsw_sp_port, MLXSW_PORT_SWID_DISABLED_PORT); + mlxsw_sp_port_module_unmap(mlxsw_sp, mlxsw_sp_port->local_port); free_percpu(mlxsw_sp_port->pcpu_stats); kfree(mlxsw_sp_port->untagged_vlans); kfree(mlxsw_sp_port->active_vlans); @@ -1529,6 +1621,7 @@ static void mlxsw_sp_ports_remove(struct mlxsw_sp *mlxsw_sp) static int mlxsw_sp_ports_create(struct mlxsw_sp *mlxsw_sp) { size_t alloc_size; + u8 module, width; int i; int err; @@ -1538,19 +1631,158 @@ static int mlxsw_sp_ports_create(struct mlxsw_sp *mlxsw_sp) return -ENOMEM; for (i = 1; i < MLXSW_PORT_MAX_PORTS; i++) { - err = mlxsw_sp_port_create(mlxsw_sp, i); + err = mlxsw_sp_port_module_info_get(mlxsw_sp, i, &module, + &width); + if (err) + goto err_port_module_info_get; + if (!width) + continue; + mlxsw_sp->port_to_module[i] = module; + err = __mlxsw_sp_port_create(mlxsw_sp, i, false, module, width); if (err) goto err_port_create; } return 0; err_port_create: +err_port_module_info_get: for (i--; i >= 1; i--) mlxsw_sp_port_remove(mlxsw_sp, i); kfree(mlxsw_sp->ports); return err; } +static u8 mlxsw_sp_cluster_base_port_get(u8 local_port) +{ + u8 offset = (local_port - 1) % MLXSW_SP_PORTS_PER_CLUSTER_MAX; + + return local_port - offset; +} + +static int mlxsw_sp_port_split(void *priv, u8 local_port, unsigned int count) +{ + struct mlxsw_sp *mlxsw_sp = priv; + struct mlxsw_sp_port *mlxsw_sp_port; + u8 width = MLXSW_PORT_MODULE_MAX_WIDTH / count; + u8 module, cur_width, base_port; + int i; + int err; + + mlxsw_sp_port = mlxsw_sp->ports[local_port]; + if (!mlxsw_sp_port) { + dev_err(mlxsw_sp->bus_info->dev, "Port number \"%d\" does not exist\n", + local_port); + return -EINVAL; + } + + if (count != 2 && count != 4) { + netdev_err(mlxsw_sp_port->dev, "Port can only be split into 2 or 4 ports\n"); + return -EINVAL; + } + + err = mlxsw_sp_port_module_info_get(mlxsw_sp, local_port, &module, + &cur_width); + if (err) { + netdev_err(mlxsw_sp_port->dev, "Failed to get port's width\n"); + return err; + } + + if (cur_width != MLXSW_PORT_MODULE_MAX_WIDTH) { + netdev_err(mlxsw_sp_port->dev, "Port cannot be split further\n"); + return -EINVAL; + } + + /* Make sure we have enough slave (even) ports for the split. */ + if (count == 2) { + base_port = local_port; + if (mlxsw_sp->ports[base_port + 1]) { + netdev_err(mlxsw_sp_port->dev, "Invalid split configuration\n"); + return -EINVAL; + } + } else { + base_port = mlxsw_sp_cluster_base_port_get(local_port); + if (mlxsw_sp->ports[base_port + 1] || + mlxsw_sp->ports[base_port + 3]) { + netdev_err(mlxsw_sp_port->dev, "Invalid split configuration\n"); + return -EINVAL; + } + } + + for (i = 0; i < count; i++) + mlxsw_sp_port_remove(mlxsw_sp, base_port + i); + + for (i = 0; i < count; i++) { + err = mlxsw_sp_port_create(mlxsw_sp, base_port + i, true, + module, width, i * width); + if (err) { + dev_err(mlxsw_sp->bus_info->dev, "Failed to create split port\n"); + goto err_port_create; + } + } + + return 0; + +err_port_create: + for (i--; i >= 0; i--) + mlxsw_sp_port_remove(mlxsw_sp, base_port + i); + for (i = 0; i < count / 2; i++) { + module = mlxsw_sp->port_to_module[base_port + i * 2]; + mlxsw_sp_port_create(mlxsw_sp, base_port + i * 2, false, + module, MLXSW_PORT_MODULE_MAX_WIDTH, 0); + } + return err; +} + +static int mlxsw_sp_port_unsplit(void *priv, u8 local_port) +{ + struct mlxsw_sp *mlxsw_sp = priv; + struct mlxsw_sp_port *mlxsw_sp_port; + u8 module, cur_width, base_port; + unsigned int count; + int i; + int err; + + mlxsw_sp_port = mlxsw_sp->ports[local_port]; + if (!mlxsw_sp_port) { + dev_err(mlxsw_sp->bus_info->dev, "Port number \"%d\" does not exist\n", + local_port); + return -EINVAL; + } + + if (!mlxsw_sp_port->split) { + netdev_err(mlxsw_sp_port->dev, "Port wasn't split\n"); + return -EINVAL; + } + + err = mlxsw_sp_port_module_info_get(mlxsw_sp, local_port, &module, + &cur_width); + if (err) { + netdev_err(mlxsw_sp_port->dev, "Failed to get port's width\n"); + return err; + } + count = cur_width == 1 ? 4 : 2; + + base_port = mlxsw_sp_cluster_base_port_get(local_port); + + /* Determine which ports to remove. */ + if (count == 2 && local_port >= base_port + 2) + base_port = base_port + 2; + + for (i = 0; i < count; i++) + mlxsw_sp_port_remove(mlxsw_sp, base_port + i); + + for (i = 0; i < count / 2; i++) { + module = mlxsw_sp->port_to_module[base_port + i * 2]; + err = mlxsw_sp_port_create(mlxsw_sp, base_port + i * 2, false, + module, MLXSW_PORT_MODULE_MAX_WIDTH, + 0); + if (err) + dev_err(mlxsw_sp->bus_info->dev, "Failed to reinstantiate port\n"); + } + + return 0; +} + static void mlxsw_sp_pude_event_func(const struct mlxsw_reg_info *reg, char *pude_pl, void *priv) { @@ -1974,6 +2206,8 @@ static struct mlxsw_driver mlxsw_sp_driver = { .priv_size = sizeof(struct mlxsw_sp), .init = mlxsw_sp_init, .fini = mlxsw_sp_fini, + .port_split = mlxsw_sp_port_split, + .port_unsplit = mlxsw_sp_port_unsplit, .txhdr_construct = mlxsw_sp_txhdr_construct, .txhdr_len = MLXSW_TXHDR_LEN, .profile = &mlxsw_sp_config_profile, |