diff --git a/include/net/devlink.h b/include/net/devlink.h
index 4358c111ce83..51f08edb1138 100644
--- a/include/net/devlink.h
+++ b/include/net/devlink.h
@@ -477,6 +477,8 @@ struct devlink_ops {
 				      struct netlink_ext_ack *extack);
 	int (*serial_get)(struct devlink *devlink, u8 *buf, size_t buf_len,
 			  size_t *len, struct netlink_ext_ack *extack);
+	int (*versions_get)(struct devlink *devlink, struct sk_buff *skb,
+			    struct netlink_ext_ack *extack);
 };
 
 static inline void *devlink_priv(struct devlink *devlink)
@@ -586,6 +588,10 @@ int devlink_region_snapshot_create(struct devlink_region *region, u64 data_len,
 				   u8 *data, u32 snapshot_id,
 				   devlink_snapshot_data_dest_t *data_destructor);
 
+int devlink_versions_report(struct sk_buff *skb, enum devlink_attr attr,
+			    const char *version_name,
+			    const char *version_value);
+
 #else
 
 static inline struct devlink *devlink_alloc(const struct devlink_ops *ops,
@@ -846,6 +852,13 @@ devlink_region_snapshot_create(struct devlink_region *region, u64 data_len,
 	return 0;
 }
 
+static inline int
+devlink_versions_report(struct sk_buff *skb, enum devlink_attr attr,
+			const char *version_name, const char *version_value)
+{
+	return 0;
+}
+
 #endif
 
 #endif /* _NET_DEVLINK_H_ */
diff --git a/include/uapi/linux/devlink.h b/include/uapi/linux/devlink.h
index 760c9c360330..eb97a52246ef 100644
--- a/include/uapi/linux/devlink.h
+++ b/include/uapi/linux/devlink.h
@@ -288,6 +288,12 @@ enum devlink_attr {
 	DEVLINK_ATTR_REGION_CHUNK_LEN,          /* u64 */
 
 	DEVLINK_ATTR_INFO_SERIAL_NUMBER,	/* binary */
+	DEVLINK_ATTR_INFO_VERSIONS,		/* nested */
+	DEVLINK_ATTR_INFO_VERSIONS_FIXED,	/* nested */
+	DEVLINK_ATTR_INFO_VERSIONS_RUNNING,	/* nested */
+	DEVLINK_ATTR_INFO_VERSIONS_STORED,	/* nested */
+	DEVLINK_ATTR_INFO_VERSIONS_NAME,	/* string */
+	DEVLINK_ATTR_INFO_VERSIONS_VALUE,	/* string */
 
 	/* add new attributes above here, update the policy in devlink.c */
 
diff --git a/net/core/devlink.c b/net/core/devlink.c
index 55b7b006df28..f35d917da7f2 100644
--- a/net/core/devlink.c
+++ b/net/core/devlink.c
@@ -3615,6 +3615,32 @@ static int devlink_nl_info_sn_fill(struct sk_buff *msg, struct devlink *devlink,
 	return nla_put(msg, DEVLINK_ATTR_INFO_SERIAL_NUMBER, len, sn);
 }
 
+static int devlink_nl_info_versions_fill(struct sk_buff *msg,
+					 struct devlink *devlink,
+					 struct netlink_ext_ack *extack)
+{
+	struct nlattr *attr;
+	int err;
+
+	if (!devlink->ops->versions_get)
+		return 0;
+
+	attr = nla_nest_start(msg, DEVLINK_ATTR_INFO_VERSIONS);
+	if (!attr)
+		return -EMSGSIZE;
+
+	err = devlink->ops->versions_get(devlink, msg, extack);
+	if (err)
+		goto err_cancel;
+
+	nla_nest_end(msg, attr);
+	return 0;
+
+err_cancel:
+	nla_nest_cancel(msg, attr);
+	return err;
+}
+
 static int
 devlink_nl_info_fill(struct sk_buff *msg, struct devlink *devlink,
 		     enum devlink_command cmd, u32 portid,
@@ -3635,6 +3661,10 @@ devlink_nl_info_fill(struct sk_buff *msg, struct devlink *devlink,
 	if (err)
 		goto err_cancel_msg;
 
+	err = devlink_nl_info_versions_fill(msg, devlink, extack);
+	if (err)
+		goto err_cancel_msg;
+
 	genlmsg_end(msg, hdr);
 	return 0;
 
@@ -3650,7 +3680,8 @@ static int devlink_nl_cmd_info_get_doit(struct sk_buff *skb,
 	struct sk_buff *msg;
 	int err;
 
-	if (!devlink->ops || !devlink->ops->serial_get)
+	if (!devlink->ops ||
+	    (!devlink->ops->serial_get && !devlink->ops->versions_get))
 		return -EOPNOTSUPP;
 
 	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
@@ -3701,6 +3732,40 @@ static int devlink_nl_cmd_info_get_dumpit(struct sk_buff *msg,
 	return msg->len;
 }
 
+int devlink_versions_report(struct sk_buff *skb, enum devlink_attr attr,
+			    const char *version_name, const char *version_value)
+{
+	struct nlattr *nest;
+	int err;
+
+	if (attr < DEVLINK_ATTR_INFO_VERSIONS_FIXED ||
+	    attr > DEVLINK_ATTR_INFO_VERSIONS_STORED)
+		return -EINVAL;
+
+	nest = nla_nest_start(skb, attr);
+	if (!nest)
+		return -EMSGSIZE;
+
+	err = nla_put_string(skb, DEVLINK_ATTR_INFO_VERSIONS_NAME,
+			     version_name);
+	if (err)
+		goto nla_put_failure;
+
+	err = nla_put_string(skb, DEVLINK_ATTR_INFO_VERSIONS_VALUE,
+			     version_value);
+	if (err)
+		goto nla_put_failure;
+
+	nla_nest_end(skb, nest);
+
+	return 0;
+
+nla_put_failure:
+	nla_nest_cancel(skb, nest);
+	return err;
+}
+EXPORT_SYMBOL_GPL(devlink_versions_report);
+
 static const struct nla_policy devlink_nl_policy[DEVLINK_ATTR_MAX + 1] = {
 	[DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING },
 	[DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING },
