diff mbox series

[RFC,net-next,1/4] ethtool: Add ability to query transceiver modules' firmware information

Message ID 20211127174530.3600237-2-idosch@idosch.org (mailing list archive)
State RFC
Delegated to: Netdev Maintainers
Headers show
Series ethtool: Add ability to flash and query transceiver modules' firmware | expand

Checks

Context Check Description
netdev/tree_selection success Clearly marked for net-next
netdev/fixes_present success Fixes tag not required for -next series
netdev/subject_prefix success Link
netdev/cover_letter success Series has a cover letter
netdev/patch_count success Link
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 2004 this patch: 2004
netdev/cc_maintainers warning 3 maintainers not CCed: hkallweit1@gmail.com corbet@lwn.net linux-doc@vger.kernel.org
netdev/build_clang success Errors and warnings before: 619 this patch: 619
netdev/module_param success Was 0 now: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 2065 this patch: 2065
netdev/checkpatch warning CHECK: Assignment operator '=' should be on the previous line WARNING: line length of 100 exceeds 80 columns WARNING: line length of 81 exceeds 80 columns WARNING: line length of 96 exceeds 80 columns
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Ido Schimmel Nov. 27, 2021, 5:45 p.m. UTC
From: Ido Schimmel <idosch@nvidia.com>

CMIS compliant modules such as QSFP-DD may have up to three firmware
images: Two host updateable images stored in up to two storage banks (A
and B) and an internal factory image. These images have various states:

* Running: Whether the image is currently running or not
* Committed: Whether the image is to be run upon reset or not
* Valid: Whether the image is runnable and persistently stored
completely undamaged

When user space wishes to update the firmware of such a module, it needs to
choose one of two images provided by the module vendor based on the current
state. For example, if image A is running and committed, image B should be
used. By only downloading and running (but not committing) image B, user space
can choose to fallback to image A if image B does not work as expected.

To that end, add a new ethtool message that allows user space to query
this information. Example output from the succeeding netdevsim
implementation:

 # ethtool --show-module-firmware-info eth0
 Module firmware info for eth0:
 image:
   name: a
   running: true
   committed: true
   valid: true
   version: 1.2.3-test
 image:
   name: b
   running: false
   committed: false
   valid: true
   version: 5.6.7
 image:
   name: factory
   running: false
   committed: false
   valid: true
   version: 11.12.13

The user interface is designed with CMIS compliant modules in mind, but
kept generic enough to accommodate future use cases, if these arise.

On the other hand, the device driver API is kept CMIS specific, but
extensible (see 'enum ethtool_module_fw_info_type'). This design choice
exploits the fact that the interface to query firmware information from
CMIS compliant modules is standardized by the CMIS specification. As
such, the information queried from device drivers is structured and
easier to police.

Signed-off-by: Ido Schimmel <idosch@nvidia.com>
---
 Documentation/networking/ethtool-netlink.rst |  68 ++++++-
 include/linux/ethtool.h                      |  70 +++++++
 include/uapi/linux/ethtool_netlink.h         |  28 +++
 net/ethtool/module.c                         | 202 +++++++++++++++++++
 net/ethtool/netlink.c                        |  10 +
 net/ethtool/netlink.h                        |   2 +
 6 files changed, 378 insertions(+), 2 deletions(-)

Comments

Jakub Kicinski Nov. 29, 2021, 5:43 p.m. UTC | #1
On Sat, 27 Nov 2021 19:45:27 +0200 Ido Schimmel wrote:
> + * @committed: Whether the image is run upon resets.

Isn't activation tangential to state of the image?

> + * @valid: Whether the image is runnable and persistently stored completely
> + *	undamaged.

Is it worth distinguishing empty vs broken? How do we let the user know
that the device has downgraded the FW image because of bad flash vs the
device was never updated? Some statistic?
diff mbox series

Patch

diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst
index 9d98e0511249..01393de9d759 100644
--- a/Documentation/networking/ethtool-netlink.rst
+++ b/Documentation/networking/ethtool-netlink.rst
@@ -184,7 +184,7 @@  according to message purpose:
 
 Userspace to kernel:
 
-  ===================================== =================================
+  ===================================== ====================================
   ``ETHTOOL_MSG_STRSET_GET``            get string set
   ``ETHTOOL_MSG_LINKINFO_GET``          get link settings
   ``ETHTOOL_MSG_LINKINFO_SET``          set link settings
@@ -220,7 +220,8 @@  Userspace to kernel:
   ``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
-  ===================================== =================================
+  ``ETHTOOL_MSG_MODULE_FW_INFO_GET``    get transceiver module firmware info
+  ===================================== ====================================
 
 Kernel to userspace:
 
@@ -260,6 +261,7 @@  Kernel to userspace:
   ``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
+  ``ETHTOOL_MSG_MODULE_FW_INFO_GET_REPLY`` transceiver module firmware info
   ======================================== =================================
 
 ``GET`` requests are sent by userspace applications to retrieve device
@@ -1598,6 +1600,67 @@  For SFF-8636 modules, low power mode is forced by the host according to table
 For CMIS modules, low power mode is forced by the host according to table 6-12
 in revision 5.0 of the specification.
 
+MODULE_FW_INFO_GET
+==================
+
+Gets transceiver module firmware information.
+
+Request contents:
+
+  ======================================  ======  ==========================
+  ``ETHTOOL_A_MODULE_FW_INFO_HEADER``     nested  request header
+  ======================================  ======  ==========================
+
+Kernel response contents:
+
+ +---------------------------------------------------+--------+----------------+
+ | ``ETHTOOL_A_MODULE_FW_INFO_HEADER``               | nested | reply header   |
+ +---------------------------------------------------+--------+----------------+
+ | ``ETHTOOL_A_MODULE_FW_INFO_IMAGE``                | nested | firmware image |
+ +-+-------------------------------------------------+--------+----------------+
+ | | ``ETHTOOL_A_MODULE_FW_INFO_IMAGE_NAME``         | string | image name     |
+ +-+-------------------------------------------------+--------+----------------+
+ | | ``ETHTOOL_A_MODULE_FW_INFO_IMAGE_RUNNING``      | bool   | running        |
+ +-+-------------------------------------------------+--------+----------------+
+ | | ``ETHTOOL_A_MODULE_FW_INFO_IMAGE_COMMITTED``    | bool   | committed      |
+ +-+-------------------------------------------------+--------+----------------+
+ | | ``ETHTOOL_A_MODULE_FW_INFO_IMAGE_VALID``        | bool   | valid          |
+ +-+-------------------------------------------------+--------+----------------+
+ | | ``ETHTOOL_A_MODULE_FW_INFO_IMAGE_VERSION``      | string | image version  |
+ +-+-------------------------------------------------+--------+----------------+
+
+The ``ETHTOOL_A_MODULE_FW_INFO_IMAGE`` nested attribute may appear multiple
+times in the response, according to the number of firmware images stored in the
+transceiver module. CMIS modules, for example, may have up to three images: Two
+host updateable images stored in up to two storage banks (A and B) and an
+internal factory image. The following paragraphs describe various image
+attributes.
+
+The ``ETHTOOL_A_MODULE_FW_INFO_IMAGE_NAME`` attribute encodes the image's name.
+The name is significant as a vendor may provide two firmware images, each one
+intended for a different bank.
+
+The ``ETHTOOL_A_MODULE_FW_INFO_IMAGE_RUNNING`` attribute indicates if the image
+is currently running or not.
+
+The ``ETHTOOL_A_MODULE_FW_INFO_IMAGE_COMMITTED`` attribute indicates if the
+image is to be run upon reset or not.
+
+The last two attributes can be used by user space to determine which firmware
+image should be used for the firmware update. In CMIS modules, for example, if
+image A is running and committed, image B should be used.
+
+The ``ETHTOOL_A_MODULE_FW_INFO_IMAGE_VALID`` attribute encodes the validity of
+the image. A valid image is runnable and persistently stored completely
+undamaged.
+
+The ``ETHTOOL_A_MODULE_FW_INFO_IMAGE_VERSION`` attribute encodes the version of
+the image.
+
+For CMIS modules, the above mentioned information can be queried from the
+module using CDB CMD 0100h (Get Firmware Info). See section 9.7.1 in revision
+5.0 of the specification.
+
 Request translation
 ===================
 
@@ -1699,4 +1762,5 @@  are netlink only.
   n/a                                 ``ETHTOOL_MSG_PHC_VCLOCKS_GET``
   n/a                                 ``ETHTOOL_MSG_MODULE_GET``
   n/a                                 ``ETHTOOL_MSG_MODULE_SET``
+  n/a                                 ``ETHTOOL_MSG_MODULE_FW_INFO_GET``
   =================================== =====================================
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index a26f37a27167..43506b119429 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h
@@ -443,6 +443,71 @@  struct ethtool_module_power_mode_params {
 	enum ethtool_module_power_mode mode;
 };
 
+#define ETH_MODULE_FW_VER_LEN 48
+
+/**
+ * struct ethtool_module_fw_info_image - Module firmware image information
+ * @running: Whether the image is currently running or not.
+ * @committed: Whether the image is run upon resets.
+ * @valid: Whether the image is runnable and persistently stored completely
+ *	undamaged.
+ * @ver_major: Firmware image major revision.
+ * @ver_minor: Firmware image minor revision.
+ * @ver_build: Firmware image build number.
+ * @ver_extra_str: Firmware image additional information.
+ */
+struct ethtool_module_fw_info_image {
+	u8 running:1,
+	   committed:1,
+	   valid:1;
+	u8 ver_major;
+	u8 ver_minor;
+	u8 ver_build;
+	char ver_extra_str[ETH_MODULE_FW_VER_LEN];
+};
+
+/**
+ * struct ethtool_module_fw_info_cmis - CMIS module firmware information
+ * @a_present: Whether image A is present or not.
+ * @b_present: Whether image B is present or not.
+ * @factory_present: Whether factory image is present or not.
+ * @a: Image A firmware information.
+ * @b: Image B firmware information.
+ * @factory: Factory image firmware information.
+ *
+ * CMIS modules can have up to two host updateable images stored in up to two
+ * firmware banks, called A and B. In addition, the module may also have an
+ * internal factory image.
+ */
+struct ethtool_module_fw_info_cmis {
+	u8 a_present:1,
+	   b_present:1,
+	   factory_present:1;
+	struct ethtool_module_fw_info_image a;
+	struct ethtool_module_fw_info_image b;
+	struct ethtool_module_fw_info_image factory;
+};
+
+/**
+ * enum ethtool_module_fw_info_type - Module firmware information type
+ * @ETHTOOL_MODULE_FW_INFO_TYPE_CMIS: CMIS module firmware information type.
+ */
+enum ethtool_module_fw_info_type {
+	ETHTOOL_MODULE_FW_INFO_TYPE_CMIS = 1,
+};
+
+/**
+ * struct ethtool_module_fw_info - module firmware information
+ * @type: Module firmware information type.
+ * @cmis: CMIS module firmware information.
+ */
+struct ethtool_module_fw_info {
+	enum ethtool_module_fw_info_type type;
+	union {
+		struct ethtool_module_fw_info_cmis cmis;
+	};
+};
+
 /**
  * struct ethtool_ops - optional netdev operations
  * @cap_link_lanes_supported: indicates if the driver supports lanes
@@ -614,6 +679,8 @@  struct ethtool_module_power_mode_params {
  *	plugged-in.
  * @set_module_power_mode: Set the power mode policy for the plug-in module
  *	used by the network device.
+ * @get_module_fw_info: Get the firmware information of 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
@@ -750,6 +817,9 @@  struct ethtool_ops {
 	int	(*set_module_power_mode)(struct net_device *dev,
 					 const struct ethtool_module_power_mode_params *params,
 					 struct netlink_ext_ack *extack);
+	int	(*get_module_fw_info)(struct net_device *dev,
+				      struct ethtool_module_fw_info *info,
+				      struct netlink_ext_ack *extack);
 };
 
 int ethtool_check_ops(const struct ethtool_ops *ops);
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index cca6e474a085..7f09bfb28a42 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -49,6 +49,7 @@  enum {
 	ETHTOOL_MSG_PHC_VCLOCKS_GET,
 	ETHTOOL_MSG_MODULE_GET,
 	ETHTOOL_MSG_MODULE_SET,
+	ETHTOOL_MSG_MODULE_FW_INFO_GET,
 
 	/* add new constants above here */
 	__ETHTOOL_MSG_USER_CNT,
@@ -94,6 +95,7 @@  enum {
 	ETHTOOL_MSG_PHC_VCLOCKS_GET_REPLY,
 	ETHTOOL_MSG_MODULE_GET_REPLY,
 	ETHTOOL_MSG_MODULE_NTF,
+	ETHTOOL_MSG_MODULE_FW_INFO_GET_REPLY,
 
 	/* add new constants above here */
 	__ETHTOOL_MSG_KERNEL_CNT,
@@ -853,6 +855,32 @@  enum {
 	ETHTOOL_A_MODULE_MAX = (__ETHTOOL_A_MODULE_CNT - 1)
 };
 
+/* MODULE_FW_INFO */
+
+enum {
+	ETHTOOL_A_MODULE_FW_INFO_UNSPEC,
+	ETHTOOL_A_MODULE_FW_INFO_HEADER,	/* nest - _A_HEADER_* */
+	ETHTOOL_A_MODULE_FW_INFO_IMAGE,		/* nest */
+
+	/* add new constants above here */
+	__ETHTOOL_A_MODULE_FW_INFO_CNT,
+	ETHTOOL_A_MODULE_FW_INFO_MAX = (__ETHTOOL_A_MODULE_FW_INFO_CNT - 1)
+};
+
+enum {
+	ETHTOOL_A_MODULE_FW_INFO_IMAGE_UNSPEC,
+	ETHTOOL_A_MODULE_FW_INFO_IMAGE_NAME,		/* string */
+	ETHTOOL_A_MODULE_FW_INFO_IMAGE_RUNNING,		/* u8 */
+	ETHTOOL_A_MODULE_FW_INFO_IMAGE_COMMITTED,	/* u8 */
+	ETHTOOL_A_MODULE_FW_INFO_IMAGE_VALID,		/* u8 */
+	ETHTOOL_A_MODULE_FW_INFO_IMAGE_VERSION,		/* string */
+
+	/* add new constants above here */
+	__ETHTOOL_A_MODULE_FW_INFO_IMAGE_CNT,
+	ETHTOOL_A_MODULE_FW_INFO_IMAGE_MAX
+		= (__ETHTOOL_A_MODULE_FW_INFO_IMAGE_CNT - 1)
+};
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
diff --git a/net/ethtool/module.c b/net/ethtool/module.c
index bc2cef11bbda..7f86c01a0b40 100644
--- a/net/ethtool/module.c
+++ b/net/ethtool/module.c
@@ -18,6 +18,18 @@  struct module_reply_data {
 #define MODULE_REPDATA(__reply_base) \
 	container_of(__reply_base, struct module_reply_data, base)
 
+struct module_fw_info_req_info {
+	struct ethnl_req_info base;
+};
+
+struct module_fw_info_reply_data {
+	struct ethnl_reply_data	base;
+	struct ethtool_module_fw_info fw_info;
+};
+
+#define MODULE_FW_INFO_REPDATA(__reply_base) \
+	container_of(__reply_base, struct module_fw_info_reply_data, base)
+
 /* MODULE_GET */
 
 const struct nla_policy ethnl_module_get_policy[ETHTOOL_A_MODULE_HEADER + 1] = {
@@ -178,3 +190,193 @@  int ethnl_set_module(struct sk_buff *skb, struct genl_info *info)
 	dev_put(dev);
 	return ret;
 }
+
+/* MODULE_FW_INFO_GET */
+
+const struct nla_policy ethnl_module_fw_info_get_policy[ETHTOOL_A_MODULE_FW_INFO_HEADER + 1] = {
+	[ETHTOOL_A_MODULE_FW_INFO_HEADER] =
+		NLA_POLICY_NESTED(ethnl_header_policy),
+};
+
+static int module_get_fw_info(struct net_device *dev,
+			      struct ethtool_module_fw_info *fw_info,
+			      struct netlink_ext_ack *extack)
+{
+	int ret;
+
+	ret = dev->ethtool_ops->get_module_fw_info(dev, fw_info, extack);
+	if (ret < 0)
+		return ret;
+
+	if (!fw_info->type) {
+		NL_SET_ERR_MSG(extack, "Module firmware info type was not set");
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static int module_fw_info_prepare_data(const struct ethnl_req_info *req_base,
+				       struct ethnl_reply_data *reply_base,
+				       struct genl_info *info)
+{
+	struct netlink_ext_ack *extack = info ? info->extack : NULL;
+	struct net_device *dev = reply_base->dev;
+	struct module_fw_info_reply_data *data;
+	int ret;
+
+	if (!dev->ethtool_ops->get_module_fw_info)
+		return -EOPNOTSUPP;
+
+	ret = ethnl_ops_begin(dev);
+	if (ret < 0)
+		return ret;
+
+	data = MODULE_FW_INFO_REPDATA(reply_base);
+	ret = module_get_fw_info(dev, &data->fw_info, extack);
+	if (ret < 0)
+		goto out_complete;
+
+out_complete:
+	ethnl_ops_complete(dev);
+	return ret;
+}
+
+static int
+module_fw_info_reply_size_image(const struct ethtool_module_fw_info_image *image,
+				int name_len)
+{
+	       /* _MODULE_FW_INFO_IMAGE */
+	return nla_total_size(0) +
+	       /* _MODULE_FW_INFO_IMAGE_NAME */
+	       nla_total_size(name_len + 1) +
+	       /* _MODULE_FW_INFO_IMAGE_RUNNING */
+	       nla_total_size(sizeof(u8)) +
+	       /* _MODULE_FW_INFO_IMAGE_COMMITTED */
+	       nla_total_size(sizeof(u8)) +
+	       /* _MODULE_FW_INFO_IMAGE_VALID */
+	       nla_total_size(sizeof(u8)) +
+	       /* _MODULE_FW_INFO_IMAGE_VERSION */
+	       nla_total_size(ETH_MODULE_FW_VER_LEN + 1);
+}
+
+static int
+module_fw_info_reply_size_cmis(const struct ethtool_module_fw_info_cmis *cmis)
+{
+	int len = 0;
+
+	if (cmis->a_present)
+		len += module_fw_info_reply_size_image(&cmis->a, strlen("a"));
+	if (cmis->b_present)
+		len += module_fw_info_reply_size_image(&cmis->b, strlen("b"));
+	if (cmis->factory_present)
+		len += module_fw_info_reply_size_image(&cmis->factory,
+						       strlen("factory"));
+
+	return len;
+}
+
+static int module_fw_info_reply_size(const struct ethnl_req_info *req_base,
+				     const struct ethnl_reply_data *reply_base)
+{
+	struct module_fw_info_reply_data *data;
+
+	data = MODULE_FW_INFO_REPDATA(reply_base);
+
+	switch (data->fw_info.type) {
+	case ETHTOOL_MODULE_FW_INFO_TYPE_CMIS:
+		return module_fw_info_reply_size_cmis(&data->fw_info.cmis);
+	default:
+		/* Module firmware information type was already validated to be
+		 * set in prepare_data() callback.
+		 */
+		WARN_ON(1);
+		return -EINVAL;
+	}
+}
+
+static int
+module_fw_info_fill_reply_image(struct sk_buff *skb,
+				const struct ethtool_module_fw_info_image *image,
+				const char *image_name)
+{
+	char buf[ETH_MODULE_FW_VER_LEN];
+	struct nlattr *nest;
+
+	if (strlen(image->ver_extra_str))
+		snprintf(buf, ETH_MODULE_FW_VER_LEN, "%d.%d.%d-%s",
+			 image->ver_major, image->ver_minor, image->ver_build,
+			 image->ver_extra_str);
+	else
+		snprintf(buf, ETH_MODULE_FW_VER_LEN, "%d.%d.%d",
+			 image->ver_major, image->ver_minor, image->ver_build);
+
+	nest = nla_nest_start(skb, ETHTOOL_A_MODULE_FW_INFO_IMAGE);
+	if (!nest)
+		return -EMSGSIZE;
+
+	if (nla_put_string(skb, ETHTOOL_A_MODULE_FW_INFO_IMAGE_NAME,
+			   image_name) ||
+	    nla_put_u8(skb, ETHTOOL_A_MODULE_FW_INFO_IMAGE_RUNNING,
+		       image->running) ||
+	    nla_put_u8(skb, ETHTOOL_A_MODULE_FW_INFO_IMAGE_COMMITTED,
+		       image->committed) ||
+	    nla_put_u8(skb, ETHTOOL_A_MODULE_FW_INFO_IMAGE_VALID,
+		       image->valid) ||
+	    nla_put_string(skb, ETHTOOL_A_MODULE_FW_INFO_IMAGE_VERSION, buf))
+		goto err_cancel;
+
+	nla_nest_end(skb, nest);
+
+	return 0;
+
+err_cancel:
+	nla_nest_cancel(skb, nest);
+	return -EMSGSIZE;
+}
+
+static int
+module_fw_info_fill_reply_cmis(struct sk_buff *skb,
+			       const struct ethtool_module_fw_info_cmis *cmis)
+{
+	if (cmis->a_present &&
+	    module_fw_info_fill_reply_image(skb, &cmis->a, "a"))
+		return -EMSGSIZE;
+	if (cmis->b_present &&
+	    module_fw_info_fill_reply_image(skb, &cmis->b, "b"))
+		return -EMSGSIZE;
+	if (cmis->factory_present &&
+	    module_fw_info_fill_reply_image(skb, &cmis->factory, "factory"))
+		return -EMSGSIZE;
+
+	return 0;
+}
+
+static int module_fw_info_fill_reply(struct sk_buff *skb,
+				     const struct ethnl_req_info *req_base,
+				     const struct ethnl_reply_data *reply_base)
+{
+	const struct module_fw_info_reply_data *data;
+
+	data = MODULE_FW_INFO_REPDATA(reply_base);
+
+	switch (data->fw_info.type) {
+	case ETHTOOL_MODULE_FW_INFO_TYPE_CMIS:
+		return module_fw_info_fill_reply_cmis(skb, &data->fw_info.cmis);
+	default:
+		WARN_ON(1);
+		return -EINVAL;
+	}
+}
+
+const struct ethnl_request_ops ethnl_module_fw_info_request_ops = {
+	.request_cmd		= ETHTOOL_MSG_MODULE_FW_INFO_GET,
+	.reply_cmd		= ETHTOOL_MSG_MODULE_FW_INFO_GET_REPLY,
+	.hdr_attr		= ETHTOOL_A_MODULE_FW_INFO_HEADER,
+	.req_info_size		= sizeof(struct module_fw_info_req_info),
+	.reply_data_size	= sizeof(struct module_fw_info_reply_data),
+
+	.prepare_data		= module_fw_info_prepare_data,
+	.reply_size		= module_fw_info_reply_size,
+	.fill_reply		= module_fw_info_fill_reply,
+};
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 38b44c0291b1..380a38b8535c 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -283,6 +283,7 @@  ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
 	[ETHTOOL_MSG_STATS_GET]		= &ethnl_stats_request_ops,
 	[ETHTOOL_MSG_PHC_VCLOCKS_GET]	= &ethnl_phc_vclocks_request_ops,
 	[ETHTOOL_MSG_MODULE_GET]	= &ethnl_module_request_ops,
+	[ETHTOOL_MSG_MODULE_FW_INFO_GET] = &ethnl_module_fw_info_request_ops,
 };
 
 static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
@@ -1018,6 +1019,15 @@  static const struct genl_ops ethtool_genl_ops[] = {
 		.policy = ethnl_module_set_policy,
 		.maxattr = ARRAY_SIZE(ethnl_module_set_policy) - 1,
 	},
+	{
+		.cmd	= ETHTOOL_MSG_MODULE_FW_INFO_GET,
+		.doit	= ethnl_default_doit,
+		.start	= ethnl_default_start,
+		.dumpit	= ethnl_default_dumpit,
+		.done	= ethnl_default_done,
+		.policy = ethnl_module_fw_info_get_policy,
+		.maxattr = ARRAY_SIZE(ethnl_module_fw_info_get_policy) - 1,
+	},
 };
 
 static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 490598e5eedd..041ffe8db8cb 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -338,6 +338,7 @@  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 ethnl_request_ops ethnl_module_fw_info_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];
@@ -376,6 +377,7 @@  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];
+extern const struct nla_policy ethnl_module_fw_info_get_policy[ETHTOOL_A_MODULE_FW_INFO_HEADER + 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);