Skip to content

Commit 0455dc5

Browse files
idoschkuba-moo
authored andcommitted
mlxsw: Add ability to control transceiver modules' power mode
Implement support for ethtool_ops::.get_module_power_mode and ethtool_ops::set_module_power_mode. The get operation is implemented using the Management Cable IO and Notifications (MCION) register that reports the operational power mode of the module and its presence. In case a module is not present, its operational power mode is not reported to ethtool and user space. If not set before, the power mode policy is reported as "high", which is the default on Mellanox systems. The set operation is implemented using the Port Module Memory Map Properties (PMMP) register. The register instructs the device's firmware to transition a plugged-in module to / out of low power mode by writing to its memory map. When the power mode policy is set to 'auto', a module will not transition to low power mode as long as any ports using it are administratively up. Example: # devlink port split swp11 count 4 # ethtool --set-module swp11s0 power-mode-policy auto $ ethtool --show-module swp11s0 Module parameters for swp11s0: power-mode-policy auto power-mode low # ip link set dev swp11s0 up # ip link set dev swp11s1 up $ ethtool --show-module swp11s0 Module parameters for swp11s0: power-mode-policy auto power-mode high # ip link set dev swp11s1 down $ ethtool --show-module swp11s0 Module parameters for swp11s0: power-mode-policy auto power-mode high # ip link set dev swp11s0 down $ ethtool --show-module swp11s0 Module parameters for swp11s0: power-mode-policy auto power-mode low Signed-off-by: Ido Schimmel <idosch@nvidia.com> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
1 parent fc53f5f commit 0455dc5

File tree

4 files changed

+254
-3
lines changed

4 files changed

+254
-3
lines changed

drivers/net/ethernet/mellanox/mlxsw/core_env.c

Lines changed: 190 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ struct mlxsw_env_module_info {
1717
bool is_overheat;
1818
int num_ports_mapped;
1919
int num_ports_up;
20+
enum ethtool_module_power_mode_policy power_mode_policy;
2021
};
2122

2223
struct mlxsw_env {
@@ -445,6 +446,152 @@ int mlxsw_env_reset_module(struct net_device *netdev,
445446
}
446447
EXPORT_SYMBOL(mlxsw_env_reset_module);
447448

449+
int
450+
mlxsw_env_get_module_power_mode(struct mlxsw_core *mlxsw_core, u8 module,
451+
struct ethtool_module_power_mode_params *params,
452+
struct netlink_ext_ack *extack)
453+
{
454+
struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
455+
char mcion_pl[MLXSW_REG_MCION_LEN];
456+
u32 status_bits;
457+
int err;
458+
459+
if (WARN_ON_ONCE(module >= mlxsw_env->module_count))
460+
return -EINVAL;
461+
462+
mutex_lock(&mlxsw_env->module_info_lock);
463+
464+
params->policy = mlxsw_env->module_info[module].power_mode_policy;
465+
466+
mlxsw_reg_mcion_pack(mcion_pl, module);
467+
err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mcion), mcion_pl);
468+
if (err) {
469+
NL_SET_ERR_MSG_MOD(extack, "Failed to retrieve module's power mode");
470+
goto out;
471+
}
472+
473+
status_bits = mlxsw_reg_mcion_module_status_bits_get(mcion_pl);
474+
if (!(status_bits & MLXSW_REG_MCION_MODULE_STATUS_BITS_PRESENT_MASK))
475+
goto out;
476+
477+
if (status_bits & MLXSW_REG_MCION_MODULE_STATUS_BITS_LOW_POWER_MASK)
478+
params->mode = ETHTOOL_MODULE_POWER_MODE_LOW;
479+
else
480+
params->mode = ETHTOOL_MODULE_POWER_MODE_HIGH;
481+
482+
out:
483+
mutex_unlock(&mlxsw_env->module_info_lock);
484+
return err;
485+
}
486+
EXPORT_SYMBOL(mlxsw_env_get_module_power_mode);
487+
488+
static int mlxsw_env_module_enable_set(struct mlxsw_core *mlxsw_core,
489+
u8 module, bool enable)
490+
{
491+
enum mlxsw_reg_pmaos_admin_status admin_status;
492+
char pmaos_pl[MLXSW_REG_PMAOS_LEN];
493+
494+
mlxsw_reg_pmaos_pack(pmaos_pl, module);
495+
admin_status = enable ? MLXSW_REG_PMAOS_ADMIN_STATUS_ENABLED :
496+
MLXSW_REG_PMAOS_ADMIN_STATUS_DISABLED;
497+
mlxsw_reg_pmaos_admin_status_set(pmaos_pl, admin_status);
498+
mlxsw_reg_pmaos_ase_set(pmaos_pl, true);
499+
500+
return mlxsw_reg_write(mlxsw_core, MLXSW_REG(pmaos), pmaos_pl);
501+
}
502+
503+
static int mlxsw_env_module_low_power_set(struct mlxsw_core *mlxsw_core,
504+
u8 module, bool low_power)
505+
{
506+
u16 eeprom_override_mask, eeprom_override;
507+
char pmmp_pl[MLXSW_REG_PMMP_LEN];
508+
509+
mlxsw_reg_pmmp_pack(pmmp_pl, module);
510+
mlxsw_reg_pmmp_sticky_set(pmmp_pl, true);
511+
/* Mask all the bits except low power mode. */
512+
eeprom_override_mask = ~MLXSW_REG_PMMP_EEPROM_OVERRIDE_LOW_POWER_MASK;
513+
mlxsw_reg_pmmp_eeprom_override_mask_set(pmmp_pl, eeprom_override_mask);
514+
eeprom_override = low_power ? MLXSW_REG_PMMP_EEPROM_OVERRIDE_LOW_POWER_MASK :
515+
0;
516+
mlxsw_reg_pmmp_eeprom_override_set(pmmp_pl, eeprom_override);
517+
518+
return mlxsw_reg_write(mlxsw_core, MLXSW_REG(pmmp), pmmp_pl);
519+
}
520+
521+
static int __mlxsw_env_set_module_power_mode(struct mlxsw_core *mlxsw_core,
522+
u8 module, bool low_power,
523+
struct netlink_ext_ack *extack)
524+
{
525+
int err;
526+
527+
err = mlxsw_env_module_enable_set(mlxsw_core, module, false);
528+
if (err) {
529+
NL_SET_ERR_MSG_MOD(extack, "Failed to disable module");
530+
return err;
531+
}
532+
533+
err = mlxsw_env_module_low_power_set(mlxsw_core, module, low_power);
534+
if (err) {
535+
NL_SET_ERR_MSG_MOD(extack, "Failed to set module's power mode");
536+
goto err_module_low_power_set;
537+
}
538+
539+
err = mlxsw_env_module_enable_set(mlxsw_core, module, true);
540+
if (err) {
541+
NL_SET_ERR_MSG_MOD(extack, "Failed to enable module");
542+
goto err_module_enable_set;
543+
}
544+
545+
return 0;
546+
547+
err_module_enable_set:
548+
mlxsw_env_module_low_power_set(mlxsw_core, module, !low_power);
549+
err_module_low_power_set:
550+
mlxsw_env_module_enable_set(mlxsw_core, module, true);
551+
return err;
552+
}
553+
554+
int
555+
mlxsw_env_set_module_power_mode(struct mlxsw_core *mlxsw_core, u8 module,
556+
enum ethtool_module_power_mode_policy policy,
557+
struct netlink_ext_ack *extack)
558+
{
559+
struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
560+
bool low_power;
561+
int err = 0;
562+
563+
if (WARN_ON_ONCE(module >= mlxsw_env->module_count))
564+
return -EINVAL;
565+
566+
if (policy != ETHTOOL_MODULE_POWER_MODE_POLICY_HIGH &&
567+
policy != ETHTOOL_MODULE_POWER_MODE_POLICY_AUTO) {
568+
NL_SET_ERR_MSG_MOD(extack, "Unsupported power mode policy");
569+
return -EOPNOTSUPP;
570+
}
571+
572+
mutex_lock(&mlxsw_env->module_info_lock);
573+
574+
if (mlxsw_env->module_info[module].power_mode_policy == policy)
575+
goto out;
576+
577+
/* If any ports are up, we are already in high power mode. */
578+
if (mlxsw_env->module_info[module].num_ports_up)
579+
goto out_set_policy;
580+
581+
low_power = policy == ETHTOOL_MODULE_POWER_MODE_POLICY_AUTO;
582+
err = __mlxsw_env_set_module_power_mode(mlxsw_core, module, low_power,
583+
extack);
584+
if (err)
585+
goto out;
586+
587+
out_set_policy:
588+
mlxsw_env->module_info[module].power_mode_policy = policy;
589+
out:
590+
mutex_unlock(&mlxsw_env->module_info_lock);
591+
return err;
592+
}
593+
EXPORT_SYMBOL(mlxsw_env_set_module_power_mode);
594+
448595
static int mlxsw_env_module_has_temp_sensor(struct mlxsw_core *mlxsw_core,
449596
u8 module,
450597
bool *p_has_temp_sensor)
@@ -794,15 +941,33 @@ EXPORT_SYMBOL(mlxsw_env_module_port_unmap);
794941
int mlxsw_env_module_port_up(struct mlxsw_core *mlxsw_core, u8 module)
795942
{
796943
struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
944+
int err = 0;
797945

798946
if (WARN_ON_ONCE(module >= mlxsw_env->module_count))
799947
return -EINVAL;
800948

801949
mutex_lock(&mlxsw_env->module_info_lock);
950+
951+
if (mlxsw_env->module_info[module].power_mode_policy !=
952+
ETHTOOL_MODULE_POWER_MODE_POLICY_AUTO)
953+
goto out_inc;
954+
955+
if (mlxsw_env->module_info[module].num_ports_up != 0)
956+
goto out_inc;
957+
958+
/* Transition to high power mode following first port using the module
959+
* being put administratively up.
960+
*/
961+
err = __mlxsw_env_set_module_power_mode(mlxsw_core, module, false,
962+
NULL);
963+
if (err)
964+
goto out_unlock;
965+
966+
out_inc:
802967
mlxsw_env->module_info[module].num_ports_up++;
968+
out_unlock:
803969
mutex_unlock(&mlxsw_env->module_info_lock);
804-
805-
return 0;
970+
return err;
806971
}
807972
EXPORT_SYMBOL(mlxsw_env_module_port_up);
808973

@@ -814,7 +979,22 @@ void mlxsw_env_module_port_down(struct mlxsw_core *mlxsw_core, u8 module)
814979
return;
815980

816981
mutex_lock(&mlxsw_env->module_info_lock);
982+
817983
mlxsw_env->module_info[module].num_ports_up--;
984+
985+
if (mlxsw_env->module_info[module].power_mode_policy !=
986+
ETHTOOL_MODULE_POWER_MODE_POLICY_AUTO)
987+
goto out_unlock;
988+
989+
if (mlxsw_env->module_info[module].num_ports_up != 0)
990+
goto out_unlock;
991+
992+
/* Transition to low power mode following last port using the module
993+
* being put administratively down.
994+
*/
995+
__mlxsw_env_set_module_power_mode(mlxsw_core, module, true, NULL);
996+
997+
out_unlock:
818998
mutex_unlock(&mlxsw_env->module_info_lock);
819999
}
8201000
EXPORT_SYMBOL(mlxsw_env_module_port_down);
@@ -824,7 +1004,7 @@ int mlxsw_env_init(struct mlxsw_core *mlxsw_core, struct mlxsw_env **p_env)
8241004
char mgpir_pl[MLXSW_REG_MGPIR_LEN];
8251005
struct mlxsw_env *env;
8261006
u8 module_count;
827-
int err;
1007+
int i, err;
8281008

8291009
mlxsw_reg_mgpir_pack(mgpir_pl);
8301010
err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mgpir), mgpir_pl);
@@ -837,6 +1017,13 @@ int mlxsw_env_init(struct mlxsw_core *mlxsw_core, struct mlxsw_env **p_env)
8371017
if (!env)
8381018
return -ENOMEM;
8391019

1020+
/* Firmware defaults to high power mode policy where modules are
1021+
* transitioned to high power mode following plug-in.
1022+
*/
1023+
for (i = 0; i < module_count; i++)
1024+
env->module_info[i].power_mode_policy =
1025+
ETHTOOL_MODULE_POWER_MODE_POLICY_HIGH;
1026+
8401027
mutex_init(&env->module_info_lock);
8411028
env->core = mlxsw_core;
8421029
env->module_count = module_count;

drivers/net/ethernet/mellanox/mlxsw/core_env.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ int mlxsw_env_reset_module(struct net_device *netdev,
2828
struct mlxsw_core *mlxsw_core, u8 module,
2929
u32 *flags);
3030

31+
int
32+
mlxsw_env_get_module_power_mode(struct mlxsw_core *mlxsw_core, u8 module,
33+
struct ethtool_module_power_mode_params *params,
34+
struct netlink_ext_ack *extack);
35+
36+
int
37+
mlxsw_env_set_module_power_mode(struct mlxsw_core *mlxsw_core, u8 module,
38+
enum ethtool_module_power_mode_policy policy,
39+
struct netlink_ext_ack *extack);
40+
3141
int
3242
mlxsw_env_module_overheat_counter_get(struct mlxsw_core *mlxsw_core, u8 module,
3343
u64 *p_counter);

drivers/net/ethernet/mellanox/mlxsw/minimal.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,38 @@ static int mlxsw_m_reset(struct net_device *netdev, u32 *flags)
145145
flags);
146146
}
147147

148+
static int
149+
mlxsw_m_get_module_power_mode(struct net_device *netdev,
150+
struct ethtool_module_power_mode_params *params,
151+
struct netlink_ext_ack *extack)
152+
{
153+
struct mlxsw_m_port *mlxsw_m_port = netdev_priv(netdev);
154+
struct mlxsw_core *core = mlxsw_m_port->mlxsw_m->core;
155+
156+
return mlxsw_env_get_module_power_mode(core, mlxsw_m_port->module,
157+
params, extack);
158+
}
159+
160+
static int
161+
mlxsw_m_set_module_power_mode(struct net_device *netdev,
162+
const struct ethtool_module_power_mode_params *params,
163+
struct netlink_ext_ack *extack)
164+
{
165+
struct mlxsw_m_port *mlxsw_m_port = netdev_priv(netdev);
166+
struct mlxsw_core *core = mlxsw_m_port->mlxsw_m->core;
167+
168+
return mlxsw_env_set_module_power_mode(core, mlxsw_m_port->module,
169+
params->policy, extack);
170+
}
171+
148172
static const struct ethtool_ops mlxsw_m_port_ethtool_ops = {
149173
.get_drvinfo = mlxsw_m_module_get_drvinfo,
150174
.get_module_info = mlxsw_m_get_module_info,
151175
.get_module_eeprom = mlxsw_m_get_module_eeprom,
152176
.get_module_eeprom_by_page = mlxsw_m_get_module_eeprom_by_page,
153177
.reset = mlxsw_m_reset,
178+
.get_module_power_mode = mlxsw_m_get_module_power_mode,
179+
.set_module_power_mode = mlxsw_m_set_module_power_mode,
154180
};
155181

156182
static int

drivers/net/ethernet/mellanox/mlxsw/spectrum_ethtool.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,32 @@ static int mlxsw_sp_reset(struct net_device *dev, u32 *flags)
12061206
return mlxsw_env_reset_module(dev, mlxsw_sp->core, module, flags);
12071207
}
12081208

1209+
static int
1210+
mlxsw_sp_get_module_power_mode(struct net_device *dev,
1211+
struct ethtool_module_power_mode_params *params,
1212+
struct netlink_ext_ack *extack)
1213+
{
1214+
struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
1215+
struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
1216+
u8 module = mlxsw_sp_port->mapping.module;
1217+
1218+
return mlxsw_env_get_module_power_mode(mlxsw_sp->core, module, params,
1219+
extack);
1220+
}
1221+
1222+
static int
1223+
mlxsw_sp_set_module_power_mode(struct net_device *dev,
1224+
const struct ethtool_module_power_mode_params *params,
1225+
struct netlink_ext_ack *extack)
1226+
{
1227+
struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
1228+
struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
1229+
u8 module = mlxsw_sp_port->mapping.module;
1230+
1231+
return mlxsw_env_set_module_power_mode(mlxsw_sp->core, module,
1232+
params->policy, extack);
1233+
}
1234+
12091235
const struct ethtool_ops mlxsw_sp_port_ethtool_ops = {
12101236
.cap_link_lanes_supported = true,
12111237
.get_drvinfo = mlxsw_sp_port_get_drvinfo,
@@ -1228,6 +1254,8 @@ const struct ethtool_ops mlxsw_sp_port_ethtool_ops = {
12281254
.get_eth_ctrl_stats = mlxsw_sp_get_eth_ctrl_stats,
12291255
.get_rmon_stats = mlxsw_sp_get_rmon_stats,
12301256
.reset = mlxsw_sp_reset,
1257+
.get_module_power_mode = mlxsw_sp_get_module_power_mode,
1258+
.set_module_power_mode = mlxsw_sp_set_module_power_mode,
12311259
};
12321260

12331261
struct mlxsw_sp1_port_link_mode {

0 commit comments

Comments
 (0)