Some modules use nonstandard power levels. Adjust ethtool
module implementation to support new attributes that will allow user
to change maximum power.

Add three new get attributes:
ETHTOOL_A_MODULE_MAX_POWER_SET (used for set as well) - currently set
  maximum power in the cage
ETHTOOL_A_MODULE_MIN_POWER_ALLOWED - minimum power allowed in the
  cage reported by device
ETHTOOL_A_MODULE_MAX_POWER_ALLOWED - maximum power allowed in the
  cage reported by device

Add two new set attributes:
ETHTOOL_A_MODULE_MAX_POWER_SET (used for get as well) - change
  maximum power in the cage to the given value (milliwatts)
ETHTOOL_A_MODULE_MAX_POWER_RESET - reset maximum power setting to the
  default value

Reviewed-by: Marcin Szycik <[email protected]>
Signed-off-by: Wojciech Drewek <[email protected]>
---
 include/linux/ethtool.h              | 17 +++++--
 include/uapi/linux/ethtool_netlink.h |  4 ++
 net/ethtool/module.c                 | 74 ++++++++++++++++++++++++++--
 net/ethtool/netlink.h                |  2 +-
 4 files changed, 87 insertions(+), 10 deletions(-)

diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index f3af6b31c9f1..74ed8997443a 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h
@@ -510,10 +510,18 @@ struct ethtool_module_eeprom {
  * @policy: The power mode policy enforced by the host for the plug-in module.
  * @mode: The operational power mode of the plug-in module. Should be filled by
  *     device drivers on get operations.
+ * @min_pwr_allowed: minimum power allowed in the cage reported by device
+ * @max_pwr_allowed: maximum power allowed in the cage reported by device
+ * @max_pwr_set: maximum power currently set in the cage
+ * @max_pwr_reset: restore default minimum power
  */
 struct ethtool_module_power_params {
        enum ethtool_module_power_mode_policy policy;
        enum ethtool_module_power_mode mode;
+       u32 min_pwr_allowed;
+       u32 max_pwr_allowed;
+       u32 max_pwr_set;
+       u8 max_pwr_reset;
 };
 
 /**
@@ -804,11 +812,12 @@ struct ethtool_rxfh_param {
  * @get_eth_ctrl_stats: Query some of the IEEE 802.3 MAC Ctrl statistics.
  * @get_rmon_stats: Query some of the RMON (RFC 2819) statistics.
  *     Set %ranges to a pointer to zero-terminated array of byte ranges.
- * @get_module_power_cfg: Get the power mode policy for the plug-in module
- *     used by the network device and its operational power mode, if
- *     plugged-in.
+ * @get_module_power_cfg: Get the power configuration for the plug-in module
+ *     used by the network device which includes: its power mode policy and
+ *     operational power mode, if plugged-in; maximum power settings
+ *     (min and max allowed power and max power currently set)
  * @set_module_power_cfg: Set the power mode policy for the plug-in module
- *     used by the network device.
+ *     used by the network device and its maximum power.
  * @get_mm: Query the 802.3 MAC Merge layer state.
  * @set_mm: Set the 802.3 MAC Merge layer parameters.
  * @get_mm_stats: Query the 802.3 MAC Merge layer statistics.
diff --git a/include/uapi/linux/ethtool_netlink.h 
b/include/uapi/linux/ethtool_netlink.h
index 3f89074aa06c..f7cd446b2a83 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -882,6 +882,10 @@ enum {
        ETHTOOL_A_MODULE_HEADER,                /* nest - _A_HEADER_* */
        ETHTOOL_A_MODULE_POWER_MODE_POLICY,     /* u8 */
        ETHTOOL_A_MODULE_POWER_MODE,            /* u8 */
+       ETHTOOL_A_MODULE_MAX_POWER_SET,         /* u32 */
+       ETHTOOL_A_MODULE_MIN_POWER_ALLOWED,     /* u32 */
+       ETHTOOL_A_MODULE_MAX_POWER_ALLOWED,     /* u32 */
+       ETHTOOL_A_MODULE_MAX_POWER_RESET,       /* u8 */
 
        /* add new constants above here */
        __ETHTOOL_A_MODULE_CNT,
diff --git a/net/ethtool/module.c b/net/ethtool/module.c
index 193ca4642e04..9f63a276357e 100644
--- a/net/ethtool/module.c
+++ b/net/ethtool/module.c
@@ -69,6 +69,15 @@ static int module_reply_size(const struct ethnl_req_info 
*req_base,
        if (data->power.mode)
                len += nla_total_size(sizeof(u8));      /* _MODULE_POWER_MODE */
 
+       if (data->power.min_pwr_allowed)
+               len += nla_total_size(sizeof(u32));     /* _MIN_POWER_ALLOWED */
+
+       if (data->power.max_pwr_allowed)
+               len += nla_total_size(sizeof(u32));     /* _MAX_POWER_ALLOWED */
+
+       if (data->power.max_pwr_set)
+               len += nla_total_size(sizeof(u32));     /* _MAX_POWER_SET */
+
        return len;
 }
 
@@ -77,6 +86,7 @@ static int module_fill_reply(struct sk_buff *skb,
                             const struct ethnl_reply_data *reply_base)
 {
        const struct module_reply_data *data = MODULE_REPDATA(reply_base);
+       u32 temp;
 
        if (data->power.policy &&
            nla_put_u8(skb, ETHTOOL_A_MODULE_POWER_MODE_POLICY,
@@ -87,16 +97,30 @@ static int module_fill_reply(struct sk_buff *skb,
            nla_put_u8(skb, ETHTOOL_A_MODULE_POWER_MODE, data->power.mode))
                return -EMSGSIZE;
 
+       temp = data->power.min_pwr_allowed;
+       if (temp && nla_put_u32(skb, ETHTOOL_A_MODULE_MIN_POWER_ALLOWED, temp))
+               return -EMSGSIZE;
+
+       temp = data->power.max_pwr_allowed;
+       if (temp && nla_put_u32(skb, ETHTOOL_A_MODULE_MAX_POWER_ALLOWED, temp))
+               return -EMSGSIZE;
+
+       temp = data->power.max_pwr_set;
+       if (temp && nla_put_u32(skb, ETHTOOL_A_MODULE_MAX_POWER_SET, temp))
+               return -EMSGSIZE;
+
        return 0;
 }
 
 /* MODULE_SET */
 
-const struct nla_policy 
ethnl_module_set_policy[ETHTOOL_A_MODULE_POWER_MODE_POLICY + 1] = {
+const struct nla_policy ethnl_module_set_policy[ETHTOOL_A_MODULE_MAX + 1] = {
        [ETHTOOL_A_MODULE_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
        [ETHTOOL_A_MODULE_POWER_MODE_POLICY] =
                NLA_POLICY_RANGE(NLA_U8, ETHTOOL_MODULE_POWER_MODE_POLICY_HIGH,
                                 ETHTOOL_MODULE_POWER_MODE_POLICY_AUTO),
+       [ETHTOOL_A_MODULE_MAX_POWER_SET] = { .type = NLA_U32 },
+       [ETHTOOL_A_MODULE_MAX_POWER_RESET] = { .type = NLA_U8 },
 };
 
 static int
@@ -106,7 +130,9 @@ ethnl_set_module_validate(struct ethnl_req_info *req_info,
        const struct ethtool_ops *ops = req_info->dev->ethtool_ops;
        struct nlattr **tb = info->attrs;
 
-       if (!tb[ETHTOOL_A_MODULE_POWER_MODE_POLICY])
+       if (!tb[ETHTOOL_A_MODULE_POWER_MODE_POLICY] &&
+           !tb[ETHTOOL_A_MODULE_MAX_POWER_SET] &&
+           !tb[ETHTOOL_A_MODULE_MAX_POWER_RESET])
                return 0;
 
        if (!ops->get_module_power_cfg || !ops->set_module_power_cfg) {
@@ -117,26 +143,64 @@ ethnl_set_module_validate(struct ethnl_req_info *req_info,
        return 1;
 }
 
+static void
+ethnl_update_policy(enum ethtool_module_power_mode_policy *dst,
+                   const struct nlattr *attr, bool *mod)
+{
+       u8 val = *dst;
+
+       ethnl_update_u8(&val, attr, mod);
+
+       if (mod)
+               *dst = val;
+}
+
 static int
 ethnl_set_module(struct ethnl_req_info *req_info, struct genl_info *info)
 {
        struct ethtool_module_power_params power = {};
        struct ethtool_module_power_params power_new;
-       const struct ethtool_ops *ops;
        struct net_device *dev = req_info->dev;
        struct nlattr **tb = info->attrs;
+       const struct ethtool_ops *ops;
        int ret;
+       bool mod;
 
        ops = dev->ethtool_ops;
 
-       power_new.policy = nla_get_u8(tb[ETHTOOL_A_MODULE_POWER_MODE_POLICY]);
        ret = ops->get_module_power_cfg(dev, &power, info->extack);
        if (ret < 0)
                return ret;
 
-       if (power_new.policy == power.policy)
+       power_new.max_pwr_set = power.max_pwr_set;
+       power_new.policy = power.policy;
+
+       ethnl_update_u32(&power_new.max_pwr_set,
+                        tb[ETHTOOL_A_MODULE_MAX_POWER_SET], &mod);
+
+       if (mod) {
+               if (power_new.max_pwr_set > power.max_pwr_allowed) {
+                       NL_SET_ERR_MSG(info->extack, "Provided value is higher 
than maximum allowed");
+                       return -EINVAL;
+               } else if (power_new.max_pwr_set < power.min_pwr_allowed) {
+                       NL_SET_ERR_MSG(info->extack, "Provided value is lower 
than minimum allowed");
+                       return -EINVAL;
+               }
+       }
+
+       ethnl_update_policy(&power_new.policy,
+                           tb[ETHTOOL_A_MODULE_POWER_MODE_POLICY], &mod);
+       ethnl_update_u8(&power_new.max_pwr_reset,
+                       tb[ETHTOOL_A_MODULE_MAX_POWER_RESET], &mod);
+
+       if (!mod)
                return 0;
 
+       if (power_new.max_pwr_reset && power_new.max_pwr_set) {
+               NL_SET_ERR_MSG(info->extack, "Maximum power set and reset 
cannot be used at the same time");
+               return 0;
+       }
+
        ret = ops->set_module_power_cfg(dev, &power_new, info->extack);
        return ret < 0 ? ret : 1;
 }
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 9a333a8d04c1..6282f84811ce 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -432,7 +432,7 @@ extern const struct nla_policy 
ethnl_module_eeprom_get_policy[ETHTOOL_A_MODULE_E
 extern const struct nla_policy ethnl_stats_get_policy[ETHTOOL_A_STATS_SRC + 1];
 extern const struct nla_policy 
ethnl_phc_vclocks_get_policy[ETHTOOL_A_PHC_VCLOCKS_HEADER + 1];
 extern const struct nla_policy ethnl_module_get_policy[ETHTOOL_A_MODULE_HEADER 
+ 1];
-extern const struct nla_policy 
ethnl_module_set_policy[ETHTOOL_A_MODULE_POWER_MODE_POLICY + 1];
+extern const struct nla_policy ethnl_module_set_policy[ETHTOOL_A_MODULE_MAX + 
1];
 extern const struct nla_policy ethnl_pse_get_policy[ETHTOOL_A_PSE_HEADER + 1];
 extern const struct nla_policy ethnl_pse_set_policy[ETHTOOL_A_PSE_MAX + 1];
 extern const struct nla_policy ethnl_rss_get_policy[ETHTOOL_A_RSS_CONTEXT + 1];
-- 
2.40.1

Reply via email to