with "+", parent nest can contain multiple attributes of the same type. This
 implements an array of entries.
 
+Attributes that need to be filled-in by device drivers and that are dumped to
+user space based on whether they are valid or not should not use zero as a
+valid value. This avoids the need to explicitly signal the validity of the
+attribute in the device driver API.
+
 
 Request header
 ==============
 
 Userspace to kernel:
 
-  ===================================== ================================
+  ===================================== =================================
   ``ETHTOOL_MSG_STRSET_GET``            get string set
   ``ETHTOOL_MSG_LINKINFO_GET``          get link settings
   ``ETHTOOL_MSG_LINKINFO_SET``          set link settings
   ``ETHTOOL_MSG_MODULE_EEPROM_GET``     read SFP module EEPROM
   ``ETHTOOL_MSG_STATS_GET``             get standard statistics
   ``ETHTOOL_MSG_PHC_VCLOCKS_GET``       get PHC virtual clocks info
-  ===================================== ================================
+  ``ETHTOOL_MSG_MODULE_SET``            set transceiver module parameters
+  ``ETHTOOL_MSG_MODULE_GET``            get transceiver module parameters
+  ===================================== =================================
 
 Kernel to userspace:
 
   ``ETHTOOL_MSG_MODULE_EEPROM_GET_REPLY``  read SFP module EEPROM
   ``ETHTOOL_MSG_STATS_GET_REPLY``          standard statistics
   ``ETHTOOL_MSG_PHC_VCLOCKS_GET_REPLY``    PHC virtual clocks info
+  ``ETHTOOL_MSG_MODULE_GET_REPLY``         transceiver module parameters
   ======================================== =================================
 
 ``GET`` requests are sent by userspace applications to retrieve device
   ``ETHTOOL_A_PHC_VCLOCKS_INDEX``       s32     PHC index array
   ====================================  ======  ==========================
 
+MODULE_GET
+==========
+
+Gets transceiver module parameters.
+
+Request contents:
+
+  =====================================  ======  ==========================
+  ``ETHTOOL_A_MODULE_HEADER``            nested  request header
+  =====================================  ======  ==========================
+
+Kernel response contents:
+
+  ======================================  ======  ==========================
+  ``ETHTOOL_A_MODULE_HEADER``             nested  reply header
+  ``ETHTOOL_A_MODULE_POWER_MODE_POLICY``  u8      power mode policy
+  ``ETHTOOL_A_MODULE_POWER_MODE``         u8      operational power mode
+  ======================================  ======  ==========================
+
+The optional ``ETHTOOL_A_MODULE_POWER_MODE_POLICY`` attribute encodes the
+transceiver module power mode policy enforced by the host. The default policy
+is driver-dependent, but "auto" is the recommended default and it should be
+implemented by new drivers and drivers where conformance to a legacy behavior
+is not critical.
+
+The optional ``ETHTHOOL_A_MODULE_POWER_MODE`` attribute encodes the operational
+power mode policy of the transceiver module. It is only reported when a module
+is plugged-in. Possible values are:
+
+.. kernel-doc:: include/uapi/linux/ethtool.h
+    :identifiers: ethtool_module_power_mode
+
+MODULE_SET
+==========
+
+Sets transceiver module parameters.
+
+Request contents:
+
+  ======================================  ======  ==========================
+  ``ETHTOOL_A_MODULE_HEADER``             nested  request header
+  ``ETHTOOL_A_MODULE_POWER_MODE_POLICY``  u8      power mode policy
+  ======================================  ======  ==========================
+
+When set, the optional ``ETHTOOL_A_MODULE_POWER_MODE_POLICY`` attribute is used
+to set the transceiver module power policy enforced by the host. Possible
+values are:
+
+.. kernel-doc:: include/uapi/linux/ethtool.h
+    :identifiers: ethtool_module_power_mode_policy
+
+For SFF-8636 modules, low power mode is forced by the host according to table
+6-10 in revision 2.10a of the specification.
+
+For CMIS modules, low power mode is forced by the host according to table 6-12
+in revision 5.0 of the specification.
+
 Request translation
 ===================
 
   n/a                                 ``ETHTOOL_MSG_CABLE_TEST_TDR_ACT``
   n/a                                 ``ETHTOOL_MSG_TUNNEL_INFO_GET``
   n/a                                 ``ETHTOOL_MSG_PHC_VCLOCKS_GET``
+  n/a                                 ``ETHTOOL_MSG_MODULE_GET``
+  n/a                                 ``ETHTOOL_MSG_MODULE_SET``
   =================================== =====================================
 
        u8      *data;
 };
 
+/**
+ * struct ethtool_module_power_mode_params - module power mode parameters
+ * @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.
+ */
+struct ethtool_module_power_mode_params {
+       enum ethtool_module_power_mode_policy policy;
+       enum ethtool_module_power_mode mode;
+};
+
 /**
  * struct ethtool_ops - optional netdev operations
  * @cap_link_lanes_supported: indicates if the driver supports lanes
  * @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_mode: Get the power mode policy for the plug-in module
+ *     used by the network device and its operational power mode, if
+ *     plugged-in.
+ * @set_module_power_mode: Set the power mode policy for the plug-in module
+ *     used by the network device.
  *
  * All operations are optional (i.e. the function pointer may be set
  * to %NULL) and callers must take this into account.  Callers must
        void    (*get_rmon_stats)(struct net_device *dev,
                                  struct ethtool_rmon_stats *rmon_stats,
                                  const struct ethtool_rmon_hist_range **ranges);
+       int     (*get_module_power_mode)(struct net_device *dev,
+                                        struct ethtool_module_power_mode_params *params,
+                                        struct netlink_ext_ack *extack);
+       int     (*set_module_power_mode)(struct net_device *dev,
+                                        const struct ethtool_module_power_mode_params *params,
+                                        struct netlink_ext_ack *extack);
 };
 
 int ethtool_check_ops(const struct ethtool_ops *ops);
 
        ETH_SS_COUNT
 };
 
+/**
+ * enum ethtool_module_power_mode_policy - plug-in module power mode policy
+ * @ETHTOOL_MODULE_POWER_MODE_POLICY_HIGH: Module is always in high power mode.
+ * @ETHTOOL_MODULE_POWER_MODE_POLICY_AUTO: Module is transitioned by the host
+ *     to high power mode when the first port using it is put administratively
+ *     up and to low power mode when the last port using it is put
+ *     administratively down.
+ */
+enum ethtool_module_power_mode_policy {
+       ETHTOOL_MODULE_POWER_MODE_POLICY_HIGH = 1,
+       ETHTOOL_MODULE_POWER_MODE_POLICY_AUTO,
+};
+
+/**
+ * enum ethtool_module_power_mode - plug-in module power mode
+ * @ETHTOOL_MODULE_POWER_MODE_LOW: Module is in low power mode.
+ * @ETHTOOL_MODULE_POWER_MODE_HIGH: Module is in high power mode.
+ */
+enum ethtool_module_power_mode {
+       ETHTOOL_MODULE_POWER_MODE_LOW = 1,
+       ETHTOOL_MODULE_POWER_MODE_HIGH,
+};
+
 /**
  * struct ethtool_gstrings - string set for data tagging
  * @cmd: Command number = %ETHTOOL_GSTRINGS
 
        ETHTOOL_MSG_MODULE_EEPROM_GET,
        ETHTOOL_MSG_STATS_GET,
        ETHTOOL_MSG_PHC_VCLOCKS_GET,
+       ETHTOOL_MSG_MODULE_GET,
+       ETHTOOL_MSG_MODULE_SET,
 
        /* add new constants above here */
        __ETHTOOL_MSG_USER_CNT,
        ETHTOOL_MSG_MODULE_EEPROM_GET_REPLY,
        ETHTOOL_MSG_STATS_GET_REPLY,
        ETHTOOL_MSG_PHC_VCLOCKS_GET_REPLY,
+       ETHTOOL_MSG_MODULE_GET_REPLY,
+       ETHTOOL_MSG_MODULE_NTF,
 
        /* add new constants above here */
        __ETHTOOL_MSG_KERNEL_CNT,
        ETHTOOL_A_STATS_RMON_MAX = (__ETHTOOL_A_STATS_RMON_CNT - 1)
 };
 
+/* MODULE */
+
+enum {
+       ETHTOOL_A_MODULE_UNSPEC,
+       ETHTOOL_A_MODULE_HEADER,                /* nest - _A_HEADER_* */
+       ETHTOOL_A_MODULE_POWER_MODE_POLICY,     /* u8 */
+       ETHTOOL_A_MODULE_POWER_MODE,            /* u8 */
+
+       /* add new constants above here */
+       __ETHTOOL_A_MODULE_CNT,
+       ETHTOOL_A_MODULE_MAX = (__ETHTOOL_A_MODULE_CNT - 1)
+};
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
 
 ethtool_nl-y   := netlink.o bitset.o strset.o linkinfo.o linkmodes.o \
                   linkstate.o debug.o wol.o features.o privflags.o rings.o \
                   channels.o coalesce.o pause.o eee.o tsinfo.o cabletest.o \
-                  tunnels.o fec.o eeprom.o stats.o phc_vclocks.o
+                  tunnels.o fec.o eeprom.o stats.o phc_vclocks.o module.o
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/ethtool.h>
+
+#include "netlink.h"
+#include "common.h"
+#include "bitset.h"
+
+struct module_req_info {
+       struct ethnl_req_info base;
+};
+
+struct module_reply_data {
+       struct ethnl_reply_data base;
+       struct ethtool_module_power_mode_params power;
+};
+
+#define MODULE_REPDATA(__reply_base) \
+       container_of(__reply_base, struct module_reply_data, base)
+
+/* MODULE_GET */
+
+const struct nla_policy ethnl_module_get_policy[ETHTOOL_A_MODULE_HEADER + 1] = {
+       [ETHTOOL_A_MODULE_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
+};
+
+static int module_get_power_mode(struct net_device *dev,
+                                struct module_reply_data *data,
+                                struct netlink_ext_ack *extack)
+{
+       const struct ethtool_ops *ops = dev->ethtool_ops;
+
+       if (!ops->get_module_power_mode)
+               return 0;
+
+       return ops->get_module_power_mode(dev, &data->power, extack);
+}
+
+static int module_prepare_data(const struct ethnl_req_info *req_base,
+                              struct ethnl_reply_data *reply_base,
+                              struct genl_info *info)
+{
+       struct module_reply_data *data = MODULE_REPDATA(reply_base);
+       struct netlink_ext_ack *extack = info ? info->extack : NULL;
+       struct net_device *dev = reply_base->dev;
+       int ret;
+
+       ret = ethnl_ops_begin(dev);
+       if (ret < 0)
+               return ret;
+
+       ret = module_get_power_mode(dev, data, extack);
+       if (ret < 0)
+               goto out_complete;
+
+out_complete:
+       ethnl_ops_complete(dev);
+       return ret;
+}
+
+static int module_reply_size(const struct ethnl_req_info *req_base,
+                            const struct ethnl_reply_data *reply_base)
+{
+       struct module_reply_data *data = MODULE_REPDATA(reply_base);
+       int len = 0;
+
+       if (data->power.policy)
+               len += nla_total_size(sizeof(u8));      /* _MODULE_POWER_MODE_POLICY */
+
+       if (data->power.mode)
+               len += nla_total_size(sizeof(u8));      /* _MODULE_POWER_MODE */
+
+       return len;
+}
+
+static int module_fill_reply(struct sk_buff *skb,
+                            const struct ethnl_req_info *req_base,
+                            const struct ethnl_reply_data *reply_base)
+{
+       const struct module_reply_data *data = MODULE_REPDATA(reply_base);
+
+       if (data->power.policy &&
+           nla_put_u8(skb, ETHTOOL_A_MODULE_POWER_MODE_POLICY,
+                      data->power.policy))
+               return -EMSGSIZE;
+
+       if (data->power.mode &&
+           nla_put_u8(skb, ETHTOOL_A_MODULE_POWER_MODE, data->power.mode))
+               return -EMSGSIZE;
+
+       return 0;
+}
+
+const struct ethnl_request_ops ethnl_module_request_ops = {
+       .request_cmd            = ETHTOOL_MSG_MODULE_GET,
+       .reply_cmd              = ETHTOOL_MSG_MODULE_GET_REPLY,
+       .hdr_attr               = ETHTOOL_A_MODULE_HEADER,
+       .req_info_size          = sizeof(struct module_req_info),
+       .reply_data_size        = sizeof(struct module_reply_data),
+
+       .prepare_data           = module_prepare_data,
+       .reply_size             = module_reply_size,
+       .fill_reply             = module_fill_reply,
+};
+
+/* MODULE_SET */
+
+const struct nla_policy ethnl_module_set_policy[ETHTOOL_A_MODULE_POWER_MODE_POLICY + 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),
+};
+
+static int module_set_power_mode(struct net_device *dev, struct nlattr **tb,
+                                bool *p_mod, struct netlink_ext_ack *extack)
+{
+       struct ethtool_module_power_mode_params power = {};
+       struct ethtool_module_power_mode_params power_new;
+       const struct ethtool_ops *ops = dev->ethtool_ops;
+       int ret;
+
+       if (!tb[ETHTOOL_A_MODULE_POWER_MODE_POLICY])
+               return 0;
+
+       if (!ops->get_module_power_mode || !ops->set_module_power_mode) {
+               NL_SET_ERR_MSG_ATTR(extack,
+                                   tb[ETHTOOL_A_MODULE_POWER_MODE_POLICY],
+                                   "Setting power mode policy is not supported by this device");
+               return -EOPNOTSUPP;
+       }
+
+       power_new.policy = nla_get_u8(tb[ETHTOOL_A_MODULE_POWER_MODE_POLICY]);
+       ret = ops->get_module_power_mode(dev, &power, extack);
+       if (ret < 0)
+               return ret;
+
+       if (power_new.policy == power.policy)
+               return 0;
+       *p_mod = true;
+
+       return ops->set_module_power_mode(dev, &power_new, extack);
+}
+
+int ethnl_set_module(struct sk_buff *skb, struct genl_info *info)
+{
+       struct ethnl_req_info req_info = {};
+       struct nlattr **tb = info->attrs;
+       struct net_device *dev;
+       bool mod = false;
+       int ret;
+
+       ret = ethnl_parse_header_dev_get(&req_info, tb[ETHTOOL_A_MODULE_HEADER],
+                                        genl_info_net(info), info->extack,
+                                        true);
+       if (ret < 0)
+               return ret;
+       dev = req_info.dev;
+
+       rtnl_lock();
+       ret = ethnl_ops_begin(dev);
+       if (ret < 0)
+               goto out_rtnl;
+
+       ret = module_set_power_mode(dev, tb, &mod, info->extack);
+       if (ret < 0)
+               goto out_ops;
+
+       if (!mod)
+               goto out_ops;
+
+       ethtool_notify(dev, ETHTOOL_MSG_MODULE_NTF, NULL);
+
+out_ops:
+       ethnl_ops_complete(dev);
+out_rtnl:
+       rtnl_unlock();
+       dev_put(dev);
+       return ret;
+}
 
        [ETHTOOL_MSG_MODULE_EEPROM_GET] = ðnl_module_eeprom_request_ops,
        [ETHTOOL_MSG_STATS_GET]         = ðnl_stats_request_ops,
        [ETHTOOL_MSG_PHC_VCLOCKS_GET]   = ðnl_phc_vclocks_request_ops,
+       [ETHTOOL_MSG_MODULE_GET]        = ðnl_module_request_ops,
 };
 
 static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
        [ETHTOOL_MSG_PAUSE_NTF]         = ðnl_pause_request_ops,
        [ETHTOOL_MSG_EEE_NTF]           = ðnl_eee_request_ops,
        [ETHTOOL_MSG_FEC_NTF]           = ðnl_fec_request_ops,
+       [ETHTOOL_MSG_MODULE_NTF]        = ðnl_module_request_ops,
 };
 
 /* default notification handler */
        [ETHTOOL_MSG_PAUSE_NTF]         = ethnl_default_notify,
        [ETHTOOL_MSG_EEE_NTF]           = ethnl_default_notify,
        [ETHTOOL_MSG_FEC_NTF]           = ethnl_default_notify,
+       [ETHTOOL_MSG_MODULE_NTF]        = ethnl_default_notify,
 };
 
 void ethtool_notify(struct net_device *dev, unsigned int cmd, const void *data)
                .policy = ethnl_phc_vclocks_get_policy,
                .maxattr = ARRAY_SIZE(ethnl_phc_vclocks_get_policy) - 1,
        },
+       {
+               .cmd    = ETHTOOL_MSG_MODULE_GET,
+               .doit   = ethnl_default_doit,
+               .start  = ethnl_default_start,
+               .dumpit = ethnl_default_dumpit,
+               .done   = ethnl_default_done,
+               .policy = ethnl_module_get_policy,
+               .maxattr = ARRAY_SIZE(ethnl_module_get_policy) - 1,
+       },
+       {
+               .cmd    = ETHTOOL_MSG_MODULE_SET,
+               .flags  = GENL_UNS_ADMIN_PERM,
+               .doit   = ethnl_set_module,
+               .policy = ethnl_module_set_policy,
+               .maxattr = ARRAY_SIZE(ethnl_module_set_policy) - 1,
+       },
 };
 
 static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
 
 extern const struct ethnl_request_ops ethnl_module_eeprom_request_ops;
 extern const struct ethnl_request_ops ethnl_stats_request_ops;
 extern const struct ethnl_request_ops ethnl_phc_vclocks_request_ops;
+extern const struct ethnl_request_ops ethnl_module_request_ops;
 
 extern const struct nla_policy ethnl_header_policy[ETHTOOL_A_HEADER_FLAGS + 1];
 extern const struct nla_policy ethnl_header_policy_stats[ETHTOOL_A_HEADER_FLAGS + 1];
 extern const struct nla_policy ethnl_module_eeprom_get_policy[ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS + 1];
 extern const struct nla_policy ethnl_stats_get_policy[ETHTOOL_A_STATS_GROUPS + 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];
 
 int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info);
 int ethnl_set_linkmodes(struct sk_buff *skb, struct genl_info *info);
 int ethnl_tunnel_info_start(struct netlink_callback *cb);
 int ethnl_tunnel_info_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
 int ethnl_set_fec(struct sk_buff *skb, struct genl_info *info);
+int ethnl_set_module(struct sk_buff *skb, struct genl_info *info);
 
 extern const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN];
 extern const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN];