xref: /linux-6.15/net/ethtool/module.c (revision 8ef890df)
1353407d9SIdo Schimmel // SPDX-License-Identifier: GPL-2.0-only
2353407d9SIdo Schimmel 
3353407d9SIdo Schimmel #include <linux/ethtool.h>
432b4c8b5SDanielle Ratson #include <linux/firmware.h>
532b4c8b5SDanielle Ratson #include <linux/sfp.h>
632b4c8b5SDanielle Ratson #include <net/devlink.h>
7*8ef890dfSJakub Kicinski #include <net/netdev_lock.h>
8353407d9SIdo Schimmel 
9353407d9SIdo Schimmel #include "netlink.h"
10353407d9SIdo Schimmel #include "common.h"
11353407d9SIdo Schimmel #include "bitset.h"
12d7d4cfc4SDanielle Ratson #include "module_fw.h"
13353407d9SIdo Schimmel 
14353407d9SIdo Schimmel struct module_req_info {
15353407d9SIdo Schimmel 	struct ethnl_req_info base;
16353407d9SIdo Schimmel };
17353407d9SIdo Schimmel 
18353407d9SIdo Schimmel struct module_reply_data {
19353407d9SIdo Schimmel 	struct ethnl_reply_data	base;
20353407d9SIdo Schimmel 	struct ethtool_module_power_mode_params power;
21353407d9SIdo Schimmel };
22353407d9SIdo Schimmel 
23353407d9SIdo Schimmel #define MODULE_REPDATA(__reply_base) \
24353407d9SIdo Schimmel 	container_of(__reply_base, struct module_reply_data, base)
25353407d9SIdo Schimmel 
26353407d9SIdo Schimmel /* MODULE_GET */
27353407d9SIdo Schimmel 
28353407d9SIdo Schimmel const struct nla_policy ethnl_module_get_policy[ETHTOOL_A_MODULE_HEADER + 1] = {
29353407d9SIdo Schimmel 	[ETHTOOL_A_MODULE_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
30353407d9SIdo Schimmel };
31353407d9SIdo Schimmel 
module_get_power_mode(struct net_device * dev,struct module_reply_data * data,struct netlink_ext_ack * extack)32353407d9SIdo Schimmel static int module_get_power_mode(struct net_device *dev,
33353407d9SIdo Schimmel 				 struct module_reply_data *data,
34353407d9SIdo Schimmel 				 struct netlink_ext_ack *extack)
35353407d9SIdo Schimmel {
36353407d9SIdo Schimmel 	const struct ethtool_ops *ops = dev->ethtool_ops;
37353407d9SIdo Schimmel 
38353407d9SIdo Schimmel 	if (!ops->get_module_power_mode)
39353407d9SIdo Schimmel 		return 0;
40353407d9SIdo Schimmel 
41caa93b7cSEdward Cree 	if (dev->ethtool->module_fw_flash_in_progress) {
4232b4c8b5SDanielle Ratson 		NL_SET_ERR_MSG(extack,
4332b4c8b5SDanielle Ratson 			       "Module firmware flashing is in progress");
4432b4c8b5SDanielle Ratson 		return -EBUSY;
4532b4c8b5SDanielle Ratson 	}
4632b4c8b5SDanielle Ratson 
47353407d9SIdo Schimmel 	return ops->get_module_power_mode(dev, &data->power, extack);
48353407d9SIdo Schimmel }
49353407d9SIdo Schimmel 
module_prepare_data(const struct ethnl_req_info * req_base,struct ethnl_reply_data * reply_base,const struct genl_info * info)50353407d9SIdo Schimmel static int module_prepare_data(const struct ethnl_req_info *req_base,
51353407d9SIdo Schimmel 			       struct ethnl_reply_data *reply_base,
52f946270dSJakub Kicinski 			       const struct genl_info *info)
53353407d9SIdo Schimmel {
54353407d9SIdo Schimmel 	struct module_reply_data *data = MODULE_REPDATA(reply_base);
55353407d9SIdo Schimmel 	struct net_device *dev = reply_base->dev;
56353407d9SIdo Schimmel 	int ret;
57353407d9SIdo Schimmel 
58353407d9SIdo Schimmel 	ret = ethnl_ops_begin(dev);
59353407d9SIdo Schimmel 	if (ret < 0)
60353407d9SIdo Schimmel 		return ret;
61353407d9SIdo Schimmel 
62f946270dSJakub Kicinski 	ret = module_get_power_mode(dev, data, info->extack);
63353407d9SIdo Schimmel 	if (ret < 0)
64353407d9SIdo Schimmel 		goto out_complete;
65353407d9SIdo Schimmel 
66353407d9SIdo Schimmel out_complete:
67353407d9SIdo Schimmel 	ethnl_ops_complete(dev);
68353407d9SIdo Schimmel 	return ret;
69353407d9SIdo Schimmel }
70353407d9SIdo Schimmel 
module_reply_size(const struct ethnl_req_info * req_base,const struct ethnl_reply_data * reply_base)71353407d9SIdo Schimmel static int module_reply_size(const struct ethnl_req_info *req_base,
72353407d9SIdo Schimmel 			     const struct ethnl_reply_data *reply_base)
73353407d9SIdo Schimmel {
74353407d9SIdo Schimmel 	struct module_reply_data *data = MODULE_REPDATA(reply_base);
75353407d9SIdo Schimmel 	int len = 0;
76353407d9SIdo Schimmel 
77353407d9SIdo Schimmel 	if (data->power.policy)
78353407d9SIdo Schimmel 		len += nla_total_size(sizeof(u8));	/* _MODULE_POWER_MODE_POLICY */
79353407d9SIdo Schimmel 
80353407d9SIdo Schimmel 	if (data->power.mode)
81353407d9SIdo Schimmel 		len += nla_total_size(sizeof(u8));	/* _MODULE_POWER_MODE */
82353407d9SIdo Schimmel 
83353407d9SIdo Schimmel 	return len;
84353407d9SIdo Schimmel }
85353407d9SIdo Schimmel 
module_fill_reply(struct sk_buff * skb,const struct ethnl_req_info * req_base,const struct ethnl_reply_data * reply_base)86353407d9SIdo Schimmel static int module_fill_reply(struct sk_buff *skb,
87353407d9SIdo Schimmel 			     const struct ethnl_req_info *req_base,
88353407d9SIdo Schimmel 			     const struct ethnl_reply_data *reply_base)
89353407d9SIdo Schimmel {
90353407d9SIdo Schimmel 	const struct module_reply_data *data = MODULE_REPDATA(reply_base);
91353407d9SIdo Schimmel 
92353407d9SIdo Schimmel 	if (data->power.policy &&
93353407d9SIdo Schimmel 	    nla_put_u8(skb, ETHTOOL_A_MODULE_POWER_MODE_POLICY,
94353407d9SIdo Schimmel 		       data->power.policy))
95353407d9SIdo Schimmel 		return -EMSGSIZE;
96353407d9SIdo Schimmel 
97353407d9SIdo Schimmel 	if (data->power.mode &&
98353407d9SIdo Schimmel 	    nla_put_u8(skb, ETHTOOL_A_MODULE_POWER_MODE, data->power.mode))
99353407d9SIdo Schimmel 		return -EMSGSIZE;
100353407d9SIdo Schimmel 
101353407d9SIdo Schimmel 	return 0;
102353407d9SIdo Schimmel }
103353407d9SIdo Schimmel 
10404007961SJakub Kicinski /* MODULE_SET */
10504007961SJakub Kicinski 
10604007961SJakub Kicinski const struct nla_policy ethnl_module_set_policy[ETHTOOL_A_MODULE_POWER_MODE_POLICY + 1] = {
10704007961SJakub Kicinski 	[ETHTOOL_A_MODULE_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
10804007961SJakub Kicinski 	[ETHTOOL_A_MODULE_POWER_MODE_POLICY] =
10904007961SJakub Kicinski 		NLA_POLICY_RANGE(NLA_U8, ETHTOOL_MODULE_POWER_MODE_POLICY_HIGH,
11004007961SJakub Kicinski 				 ETHTOOL_MODULE_POWER_MODE_POLICY_AUTO),
11104007961SJakub Kicinski };
11204007961SJakub Kicinski 
11304007961SJakub Kicinski static int
ethnl_set_module_validate(struct ethnl_req_info * req_info,struct genl_info * info)11404007961SJakub Kicinski ethnl_set_module_validate(struct ethnl_req_info *req_info,
11504007961SJakub Kicinski 			  struct genl_info *info)
11604007961SJakub Kicinski {
11704007961SJakub Kicinski 	const struct ethtool_ops *ops = req_info->dev->ethtool_ops;
11804007961SJakub Kicinski 	struct nlattr **tb = info->attrs;
11904007961SJakub Kicinski 
12004007961SJakub Kicinski 	if (!tb[ETHTOOL_A_MODULE_POWER_MODE_POLICY])
12104007961SJakub Kicinski 		return 0;
12204007961SJakub Kicinski 
123caa93b7cSEdward Cree 	if (req_info->dev->ethtool->module_fw_flash_in_progress) {
12432b4c8b5SDanielle Ratson 		NL_SET_ERR_MSG(info->extack,
12532b4c8b5SDanielle Ratson 			       "Module firmware flashing is in progress");
12632b4c8b5SDanielle Ratson 		return -EBUSY;
12732b4c8b5SDanielle Ratson 	}
12832b4c8b5SDanielle Ratson 
12904007961SJakub Kicinski 	if (!ops->get_module_power_mode || !ops->set_module_power_mode) {
13004007961SJakub Kicinski 		NL_SET_ERR_MSG_ATTR(info->extack,
13104007961SJakub Kicinski 				    tb[ETHTOOL_A_MODULE_POWER_MODE_POLICY],
13204007961SJakub Kicinski 				    "Setting power mode policy is not supported by this device");
13304007961SJakub Kicinski 		return -EOPNOTSUPP;
13404007961SJakub Kicinski 	}
13504007961SJakub Kicinski 
13604007961SJakub Kicinski 	return 1;
13704007961SJakub Kicinski }
13804007961SJakub Kicinski 
13904007961SJakub Kicinski static int
ethnl_set_module(struct ethnl_req_info * req_info,struct genl_info * info)14004007961SJakub Kicinski ethnl_set_module(struct ethnl_req_info *req_info, struct genl_info *info)
14104007961SJakub Kicinski {
14204007961SJakub Kicinski 	struct ethtool_module_power_mode_params power = {};
14304007961SJakub Kicinski 	struct ethtool_module_power_mode_params power_new;
14404007961SJakub Kicinski 	const struct ethtool_ops *ops;
14504007961SJakub Kicinski 	struct net_device *dev = req_info->dev;
14604007961SJakub Kicinski 	struct nlattr **tb = info->attrs;
14704007961SJakub Kicinski 	int ret;
14804007961SJakub Kicinski 
14904007961SJakub Kicinski 	ops = dev->ethtool_ops;
15004007961SJakub Kicinski 
15104007961SJakub Kicinski 	power_new.policy = nla_get_u8(tb[ETHTOOL_A_MODULE_POWER_MODE_POLICY]);
15204007961SJakub Kicinski 	ret = ops->get_module_power_mode(dev, &power, info->extack);
15304007961SJakub Kicinski 	if (ret < 0)
15404007961SJakub Kicinski 		return ret;
15504007961SJakub Kicinski 
15604007961SJakub Kicinski 	if (power_new.policy == power.policy)
15704007961SJakub Kicinski 		return 0;
15804007961SJakub Kicinski 
15904007961SJakub Kicinski 	ret = ops->set_module_power_mode(dev, &power_new, info->extack);
16004007961SJakub Kicinski 	return ret < 0 ? ret : 1;
16104007961SJakub Kicinski }
16204007961SJakub Kicinski 
163353407d9SIdo Schimmel const struct ethnl_request_ops ethnl_module_request_ops = {
164353407d9SIdo Schimmel 	.request_cmd		= ETHTOOL_MSG_MODULE_GET,
165353407d9SIdo Schimmel 	.reply_cmd		= ETHTOOL_MSG_MODULE_GET_REPLY,
166353407d9SIdo Schimmel 	.hdr_attr		= ETHTOOL_A_MODULE_HEADER,
167353407d9SIdo Schimmel 	.req_info_size		= sizeof(struct module_req_info),
168353407d9SIdo Schimmel 	.reply_data_size	= sizeof(struct module_reply_data),
169353407d9SIdo Schimmel 
170353407d9SIdo Schimmel 	.prepare_data		= module_prepare_data,
171353407d9SIdo Schimmel 	.reply_size		= module_reply_size,
172353407d9SIdo Schimmel 	.fill_reply		= module_fill_reply,
17304007961SJakub Kicinski 
17404007961SJakub Kicinski 	.set_validate		= ethnl_set_module_validate,
17504007961SJakub Kicinski 	.set			= ethnl_set_module,
17604007961SJakub Kicinski 	.set_ntf_cmd		= ETHTOOL_MSG_MODULE_NTF,
177353407d9SIdo Schimmel };
178d7d4cfc4SDanielle Ratson 
17932b4c8b5SDanielle Ratson /* MODULE_FW_FLASH_ACT */
18032b4c8b5SDanielle Ratson 
18132b4c8b5SDanielle Ratson const struct nla_policy
18232b4c8b5SDanielle Ratson ethnl_module_fw_flash_act_policy[ETHTOOL_A_MODULE_FW_FLASH_PASSWORD + 1] = {
18332b4c8b5SDanielle Ratson 	[ETHTOOL_A_MODULE_FW_FLASH_HEADER] =
18432b4c8b5SDanielle Ratson 		NLA_POLICY_NESTED(ethnl_header_policy),
18532b4c8b5SDanielle Ratson 	[ETHTOOL_A_MODULE_FW_FLASH_FILE_NAME] = { .type = NLA_NUL_STRING },
18632b4c8b5SDanielle Ratson 	[ETHTOOL_A_MODULE_FW_FLASH_PASSWORD] = { .type = NLA_U32 },
18732b4c8b5SDanielle Ratson };
18832b4c8b5SDanielle Ratson 
18932b4c8b5SDanielle Ratson static LIST_HEAD(module_fw_flash_work_list);
19032b4c8b5SDanielle Ratson static DEFINE_SPINLOCK(module_fw_flash_work_list_lock);
19132b4c8b5SDanielle Ratson 
19232b4c8b5SDanielle Ratson static int
module_flash_fw_work_list_add(struct ethtool_module_fw_flash * module_fw,struct genl_info * info)19332b4c8b5SDanielle Ratson module_flash_fw_work_list_add(struct ethtool_module_fw_flash *module_fw,
19432b4c8b5SDanielle Ratson 			      struct genl_info *info)
19532b4c8b5SDanielle Ratson {
19632b4c8b5SDanielle Ratson 	struct ethtool_module_fw_flash *work;
19732b4c8b5SDanielle Ratson 
19832b4c8b5SDanielle Ratson 	/* First, check if already registered. */
19932b4c8b5SDanielle Ratson 	spin_lock(&module_fw_flash_work_list_lock);
20032b4c8b5SDanielle Ratson 	list_for_each_entry(work, &module_fw_flash_work_list, list) {
20132b4c8b5SDanielle Ratson 		if (work->fw_update.ntf_params.portid == info->snd_portid &&
20232b4c8b5SDanielle Ratson 		    work->fw_update.dev == module_fw->fw_update.dev) {
20332b4c8b5SDanielle Ratson 			spin_unlock(&module_fw_flash_work_list_lock);
20432b4c8b5SDanielle Ratson 			return -EALREADY;
20532b4c8b5SDanielle Ratson 		}
20632b4c8b5SDanielle Ratson 	}
20732b4c8b5SDanielle Ratson 
20832b4c8b5SDanielle Ratson 	list_add_tail(&module_fw->list, &module_fw_flash_work_list);
20932b4c8b5SDanielle Ratson 	spin_unlock(&module_fw_flash_work_list_lock);
21032b4c8b5SDanielle Ratson 
21132b4c8b5SDanielle Ratson 	return 0;
21232b4c8b5SDanielle Ratson }
21332b4c8b5SDanielle Ratson 
module_flash_fw_work_list_del(struct list_head * list)21432b4c8b5SDanielle Ratson static void module_flash_fw_work_list_del(struct list_head *list)
21532b4c8b5SDanielle Ratson {
21632b4c8b5SDanielle Ratson 	spin_lock(&module_fw_flash_work_list_lock);
21732b4c8b5SDanielle Ratson 	list_del(list);
21832b4c8b5SDanielle Ratson 	spin_unlock(&module_fw_flash_work_list_lock);
21932b4c8b5SDanielle Ratson }
22032b4c8b5SDanielle Ratson 
module_flash_fw_work(struct work_struct * work)22132b4c8b5SDanielle Ratson static void module_flash_fw_work(struct work_struct *work)
22232b4c8b5SDanielle Ratson {
22332b4c8b5SDanielle Ratson 	struct ethtool_module_fw_flash *module_fw;
22432b4c8b5SDanielle Ratson 
22532b4c8b5SDanielle Ratson 	module_fw = container_of(work, struct ethtool_module_fw_flash, work);
22632b4c8b5SDanielle Ratson 
22732b4c8b5SDanielle Ratson 	ethtool_cmis_fw_update(&module_fw->fw_update);
22832b4c8b5SDanielle Ratson 
22932b4c8b5SDanielle Ratson 	module_flash_fw_work_list_del(&module_fw->list);
230caa93b7cSEdward Cree 	module_fw->fw_update.dev->ethtool->module_fw_flash_in_progress = false;
23132b4c8b5SDanielle Ratson 	netdev_put(module_fw->fw_update.dev, &module_fw->dev_tracker);
23232b4c8b5SDanielle Ratson 	release_firmware(module_fw->fw_update.fw);
23332b4c8b5SDanielle Ratson 	kfree(module_fw);
23432b4c8b5SDanielle Ratson }
23532b4c8b5SDanielle Ratson 
23632b4c8b5SDanielle Ratson #define MODULE_EEPROM_PHYS_ID_PAGE	0
23732b4c8b5SDanielle Ratson #define MODULE_EEPROM_PHYS_ID_I2C_ADDR	0x50
23832b4c8b5SDanielle Ratson 
module_flash_fw_work_init(struct ethtool_module_fw_flash * module_fw,struct net_device * dev,struct netlink_ext_ack * extack)23932b4c8b5SDanielle Ratson static int module_flash_fw_work_init(struct ethtool_module_fw_flash *module_fw,
24032b4c8b5SDanielle Ratson 				     struct net_device *dev,
24132b4c8b5SDanielle Ratson 				     struct netlink_ext_ack *extack)
24232b4c8b5SDanielle Ratson {
24332b4c8b5SDanielle Ratson 	const struct ethtool_ops *ops = dev->ethtool_ops;
24432b4c8b5SDanielle Ratson 	struct ethtool_module_eeprom page_data = {};
24532b4c8b5SDanielle Ratson 	u8 phys_id;
24632b4c8b5SDanielle Ratson 	int err;
24732b4c8b5SDanielle Ratson 
24832b4c8b5SDanielle Ratson 	/* Fetch the SFF-8024 Identifier Value. For all supported standards, it
24932b4c8b5SDanielle Ratson 	 * is located at I2C address 0x50, byte 0. See section 4.1 in SFF-8024,
25032b4c8b5SDanielle Ratson 	 * revision 4.9.
25132b4c8b5SDanielle Ratson 	 */
25232b4c8b5SDanielle Ratson 	page_data.page = MODULE_EEPROM_PHYS_ID_PAGE;
25332b4c8b5SDanielle Ratson 	page_data.offset = SFP_PHYS_ID;
25432b4c8b5SDanielle Ratson 	page_data.length = sizeof(phys_id);
25532b4c8b5SDanielle Ratson 	page_data.i2c_address = MODULE_EEPROM_PHYS_ID_I2C_ADDR;
25632b4c8b5SDanielle Ratson 	page_data.data = &phys_id;
25732b4c8b5SDanielle Ratson 
25832b4c8b5SDanielle Ratson 	err = ops->get_module_eeprom_by_page(dev, &page_data, extack);
25932b4c8b5SDanielle Ratson 	if (err < 0)
26032b4c8b5SDanielle Ratson 		return err;
26132b4c8b5SDanielle Ratson 
26232b4c8b5SDanielle Ratson 	switch (phys_id) {
26332b4c8b5SDanielle Ratson 	case SFF8024_ID_QSFP_DD:
26432b4c8b5SDanielle Ratson 	case SFF8024_ID_OSFP:
26532b4c8b5SDanielle Ratson 	case SFF8024_ID_DSFP:
26632b4c8b5SDanielle Ratson 	case SFF8024_ID_QSFP_PLUS_CMIS:
26732b4c8b5SDanielle Ratson 	case SFF8024_ID_SFP_DD_CMIS:
26832b4c8b5SDanielle Ratson 	case SFF8024_ID_SFP_PLUS_CMIS:
26932b4c8b5SDanielle Ratson 		INIT_WORK(&module_fw->work, module_flash_fw_work);
27032b4c8b5SDanielle Ratson 		break;
27132b4c8b5SDanielle Ratson 	default:
27232b4c8b5SDanielle Ratson 		NL_SET_ERR_MSG(extack,
27332b4c8b5SDanielle Ratson 			       "Module type does not support firmware flashing");
27432b4c8b5SDanielle Ratson 		return -EOPNOTSUPP;
27532b4c8b5SDanielle Ratson 	}
27632b4c8b5SDanielle Ratson 
27732b4c8b5SDanielle Ratson 	return 0;
27832b4c8b5SDanielle Ratson }
27932b4c8b5SDanielle Ratson 
ethnl_module_fw_flash_sock_destroy(struct ethnl_sock_priv * sk_priv)28032b4c8b5SDanielle Ratson void ethnl_module_fw_flash_sock_destroy(struct ethnl_sock_priv *sk_priv)
28132b4c8b5SDanielle Ratson {
28232b4c8b5SDanielle Ratson 	struct ethtool_module_fw_flash *work;
28332b4c8b5SDanielle Ratson 
28432b4c8b5SDanielle Ratson 	spin_lock(&module_fw_flash_work_list_lock);
28532b4c8b5SDanielle Ratson 	list_for_each_entry(work, &module_fw_flash_work_list, list) {
28632b4c8b5SDanielle Ratson 		if (work->fw_update.dev == sk_priv->dev &&
28732b4c8b5SDanielle Ratson 		    work->fw_update.ntf_params.portid == sk_priv->portid) {
28832b4c8b5SDanielle Ratson 			work->fw_update.ntf_params.closed_sock = true;
28932b4c8b5SDanielle Ratson 			break;
29032b4c8b5SDanielle Ratson 		}
29132b4c8b5SDanielle Ratson 	}
29232b4c8b5SDanielle Ratson 	spin_unlock(&module_fw_flash_work_list_lock);
29332b4c8b5SDanielle Ratson }
29432b4c8b5SDanielle Ratson 
29532b4c8b5SDanielle Ratson static int
module_flash_fw_schedule(struct net_device * dev,const char * file_name,struct ethtool_module_fw_flash_params * params,struct sk_buff * skb,struct genl_info * info)29632b4c8b5SDanielle Ratson module_flash_fw_schedule(struct net_device *dev, const char *file_name,
29732b4c8b5SDanielle Ratson 			 struct ethtool_module_fw_flash_params *params,
29832b4c8b5SDanielle Ratson 			 struct sk_buff *skb, struct genl_info *info)
29932b4c8b5SDanielle Ratson {
30032b4c8b5SDanielle Ratson 	struct ethtool_cmis_fw_update_params *fw_update;
30132b4c8b5SDanielle Ratson 	struct ethtool_module_fw_flash *module_fw;
30232b4c8b5SDanielle Ratson 	int err;
30332b4c8b5SDanielle Ratson 
30432b4c8b5SDanielle Ratson 	module_fw = kzalloc(sizeof(*module_fw), GFP_KERNEL);
30532b4c8b5SDanielle Ratson 	if (!module_fw)
30632b4c8b5SDanielle Ratson 		return -ENOMEM;
30732b4c8b5SDanielle Ratson 
30832b4c8b5SDanielle Ratson 	fw_update = &module_fw->fw_update;
30932b4c8b5SDanielle Ratson 	fw_update->params = *params;
31032b4c8b5SDanielle Ratson 	err = request_firmware_direct(&fw_update->fw,
31132b4c8b5SDanielle Ratson 				      file_name, &dev->dev);
31232b4c8b5SDanielle Ratson 	if (err) {
31332b4c8b5SDanielle Ratson 		NL_SET_ERR_MSG(info->extack,
31432b4c8b5SDanielle Ratson 			       "Failed to request module firmware image");
31532b4c8b5SDanielle Ratson 		goto err_free;
31632b4c8b5SDanielle Ratson 	}
31732b4c8b5SDanielle Ratson 
31832b4c8b5SDanielle Ratson 	err = module_flash_fw_work_init(module_fw, dev, info->extack);
31932b4c8b5SDanielle Ratson 	if (err < 0)
32032b4c8b5SDanielle Ratson 		goto err_release_firmware;
32132b4c8b5SDanielle Ratson 
322caa93b7cSEdward Cree 	dev->ethtool->module_fw_flash_in_progress = true;
32332b4c8b5SDanielle Ratson 	netdev_hold(dev, &module_fw->dev_tracker, GFP_KERNEL);
32432b4c8b5SDanielle Ratson 	fw_update->dev = dev;
32532b4c8b5SDanielle Ratson 	fw_update->ntf_params.portid = info->snd_portid;
32632b4c8b5SDanielle Ratson 	fw_update->ntf_params.seq = info->snd_seq;
32732b4c8b5SDanielle Ratson 	fw_update->ntf_params.closed_sock = false;
32832b4c8b5SDanielle Ratson 
32932b4c8b5SDanielle Ratson 	err = ethnl_sock_priv_set(skb, dev, fw_update->ntf_params.portid,
33032b4c8b5SDanielle Ratson 				  ETHTOOL_SOCK_TYPE_MODULE_FW_FLASH);
33132b4c8b5SDanielle Ratson 	if (err < 0)
33232b4c8b5SDanielle Ratson 		goto err_release_firmware;
33332b4c8b5SDanielle Ratson 
33432b4c8b5SDanielle Ratson 	err = module_flash_fw_work_list_add(module_fw, info);
33532b4c8b5SDanielle Ratson 	if (err < 0)
33632b4c8b5SDanielle Ratson 		goto err_release_firmware;
33732b4c8b5SDanielle Ratson 
33832b4c8b5SDanielle Ratson 	schedule_work(&module_fw->work);
33932b4c8b5SDanielle Ratson 
34032b4c8b5SDanielle Ratson 	return 0;
34132b4c8b5SDanielle Ratson 
34232b4c8b5SDanielle Ratson err_release_firmware:
34332b4c8b5SDanielle Ratson 	release_firmware(fw_update->fw);
34432b4c8b5SDanielle Ratson err_free:
34532b4c8b5SDanielle Ratson 	kfree(module_fw);
34632b4c8b5SDanielle Ratson 	return err;
34732b4c8b5SDanielle Ratson }
34832b4c8b5SDanielle Ratson 
module_flash_fw(struct net_device * dev,struct nlattr ** tb,struct sk_buff * skb,struct genl_info * info)34932b4c8b5SDanielle Ratson static int module_flash_fw(struct net_device *dev, struct nlattr **tb,
35032b4c8b5SDanielle Ratson 			   struct sk_buff *skb, struct genl_info *info)
35132b4c8b5SDanielle Ratson {
35232b4c8b5SDanielle Ratson 	struct ethtool_module_fw_flash_params params = {};
35332b4c8b5SDanielle Ratson 	const char *file_name;
35432b4c8b5SDanielle Ratson 	struct nlattr *attr;
35532b4c8b5SDanielle Ratson 
35632b4c8b5SDanielle Ratson 	if (GENL_REQ_ATTR_CHECK(info, ETHTOOL_A_MODULE_FW_FLASH_FILE_NAME))
35732b4c8b5SDanielle Ratson 		return -EINVAL;
35832b4c8b5SDanielle Ratson 
35932b4c8b5SDanielle Ratson 	file_name = nla_data(tb[ETHTOOL_A_MODULE_FW_FLASH_FILE_NAME]);
36032b4c8b5SDanielle Ratson 
36132b4c8b5SDanielle Ratson 	attr = tb[ETHTOOL_A_MODULE_FW_FLASH_PASSWORD];
36232b4c8b5SDanielle Ratson 	if (attr) {
36332b4c8b5SDanielle Ratson 		params.password = cpu_to_be32(nla_get_u32(attr));
36432b4c8b5SDanielle Ratson 		params.password_valid = true;
36532b4c8b5SDanielle Ratson 	}
36632b4c8b5SDanielle Ratson 
36732b4c8b5SDanielle Ratson 	return module_flash_fw_schedule(dev, file_name, &params, skb, info);
36832b4c8b5SDanielle Ratson }
36932b4c8b5SDanielle Ratson 
ethnl_module_fw_flash_validate(struct net_device * dev,struct netlink_ext_ack * extack)37032b4c8b5SDanielle Ratson static int ethnl_module_fw_flash_validate(struct net_device *dev,
37132b4c8b5SDanielle Ratson 					  struct netlink_ext_ack *extack)
37232b4c8b5SDanielle Ratson {
37332b4c8b5SDanielle Ratson 	struct devlink_port *devlink_port = dev->devlink_port;
37432b4c8b5SDanielle Ratson 	const struct ethtool_ops *ops = dev->ethtool_ops;
37532b4c8b5SDanielle Ratson 
37632b4c8b5SDanielle Ratson 	if (!ops->set_module_eeprom_by_page ||
37732b4c8b5SDanielle Ratson 	    !ops->get_module_eeprom_by_page) {
37832b4c8b5SDanielle Ratson 		NL_SET_ERR_MSG(extack,
37932b4c8b5SDanielle Ratson 			       "Flashing module firmware is not supported by this device");
38032b4c8b5SDanielle Ratson 		return -EOPNOTSUPP;
38132b4c8b5SDanielle Ratson 	}
38232b4c8b5SDanielle Ratson 
38332b4c8b5SDanielle Ratson 	if (!ops->reset) {
38432b4c8b5SDanielle Ratson 		NL_SET_ERR_MSG(extack,
38532b4c8b5SDanielle Ratson 			       "Reset module is not supported by this device, so flashing is not permitted");
38632b4c8b5SDanielle Ratson 		return -EOPNOTSUPP;
38732b4c8b5SDanielle Ratson 	}
38832b4c8b5SDanielle Ratson 
389caa93b7cSEdward Cree 	if (dev->ethtool->module_fw_flash_in_progress) {
39032b4c8b5SDanielle Ratson 		NL_SET_ERR_MSG(extack, "Module firmware flashing already in progress");
39132b4c8b5SDanielle Ratson 		return -EBUSY;
39232b4c8b5SDanielle Ratson 	}
39332b4c8b5SDanielle Ratson 
39432b4c8b5SDanielle Ratson 	if (dev->flags & IFF_UP) {
39532b4c8b5SDanielle Ratson 		NL_SET_ERR_MSG(extack, "Netdevice is up, so flashing is not permitted");
39632b4c8b5SDanielle Ratson 		return -EBUSY;
39732b4c8b5SDanielle Ratson 	}
39832b4c8b5SDanielle Ratson 
39932b4c8b5SDanielle Ratson 	if (devlink_port && devlink_port->attrs.split) {
40032b4c8b5SDanielle Ratson 		NL_SET_ERR_MSG(extack, "Can't perform firmware flashing on a split port");
40132b4c8b5SDanielle Ratson 		return -EOPNOTSUPP;
40232b4c8b5SDanielle Ratson 	}
40332b4c8b5SDanielle Ratson 
40432b4c8b5SDanielle Ratson 	return 0;
40532b4c8b5SDanielle Ratson }
40632b4c8b5SDanielle Ratson 
ethnl_act_module_fw_flash(struct sk_buff * skb,struct genl_info * info)40732b4c8b5SDanielle Ratson int ethnl_act_module_fw_flash(struct sk_buff *skb, struct genl_info *info)
40832b4c8b5SDanielle Ratson {
40932b4c8b5SDanielle Ratson 	struct ethnl_req_info req_info = {};
41032b4c8b5SDanielle Ratson 	struct nlattr **tb = info->attrs;
41132b4c8b5SDanielle Ratson 	struct net_device *dev;
41232b4c8b5SDanielle Ratson 	int ret;
41332b4c8b5SDanielle Ratson 
41432b4c8b5SDanielle Ratson 	ret = ethnl_parse_header_dev_get(&req_info,
41532b4c8b5SDanielle Ratson 					 tb[ETHTOOL_A_MODULE_FW_FLASH_HEADER],
41632b4c8b5SDanielle Ratson 					 genl_info_net(info), info->extack,
41732b4c8b5SDanielle Ratson 					 true);
41832b4c8b5SDanielle Ratson 	if (ret < 0)
41932b4c8b5SDanielle Ratson 		return ret;
42032b4c8b5SDanielle Ratson 	dev = req_info.dev;
42132b4c8b5SDanielle Ratson 
42232b4c8b5SDanielle Ratson 	rtnl_lock();
4232bcf4772SJakub Kicinski 	netdev_lock_ops(dev);
42432b4c8b5SDanielle Ratson 	ret = ethnl_ops_begin(dev);
42532b4c8b5SDanielle Ratson 	if (ret < 0)
4262bcf4772SJakub Kicinski 		goto out_unlock;
42732b4c8b5SDanielle Ratson 
42832b4c8b5SDanielle Ratson 	ret = ethnl_module_fw_flash_validate(dev, info->extack);
42932b4c8b5SDanielle Ratson 	if (ret < 0)
4302bcf4772SJakub Kicinski 		goto out_unlock;
43132b4c8b5SDanielle Ratson 
43232b4c8b5SDanielle Ratson 	ret = module_flash_fw(dev, tb, skb, info);
43332b4c8b5SDanielle Ratson 
43432b4c8b5SDanielle Ratson 	ethnl_ops_complete(dev);
43532b4c8b5SDanielle Ratson 
4362bcf4772SJakub Kicinski out_unlock:
4372bcf4772SJakub Kicinski 	netdev_unlock_ops(dev);
43832b4c8b5SDanielle Ratson 	rtnl_unlock();
43932b4c8b5SDanielle Ratson 	ethnl_parse_header_dev_put(&req_info);
44032b4c8b5SDanielle Ratson 	return ret;
44132b4c8b5SDanielle Ratson }
44232b4c8b5SDanielle Ratson 
443d7d4cfc4SDanielle Ratson /* MODULE_FW_FLASH_NTF */
444d7d4cfc4SDanielle Ratson 
445d7d4cfc4SDanielle Ratson static int
ethnl_module_fw_flash_ntf_put_err(struct sk_buff * skb,char * err_msg,char * sub_err_msg)446d7d4cfc4SDanielle Ratson ethnl_module_fw_flash_ntf_put_err(struct sk_buff *skb, char *err_msg,
447d7d4cfc4SDanielle Ratson 				  char *sub_err_msg)
448d7d4cfc4SDanielle Ratson {
449d7d4cfc4SDanielle Ratson 	int err_msg_len, sub_err_msg_len, total_len;
450d7d4cfc4SDanielle Ratson 	struct nlattr *attr;
451d7d4cfc4SDanielle Ratson 
452d7d4cfc4SDanielle Ratson 	if (!err_msg)
453d7d4cfc4SDanielle Ratson 		return 0;
454d7d4cfc4SDanielle Ratson 
455d7d4cfc4SDanielle Ratson 	err_msg_len = strlen(err_msg);
456d7d4cfc4SDanielle Ratson 	total_len = err_msg_len + 2; /* For period and NUL. */
457d7d4cfc4SDanielle Ratson 
458d7d4cfc4SDanielle Ratson 	if (sub_err_msg) {
459d7d4cfc4SDanielle Ratson 		sub_err_msg_len = strlen(sub_err_msg);
460d7d4cfc4SDanielle Ratson 		total_len += sub_err_msg_len + 2; /* For ", ". */
461d7d4cfc4SDanielle Ratson 	}
462d7d4cfc4SDanielle Ratson 
463d7d4cfc4SDanielle Ratson 	attr = nla_reserve(skb, ETHTOOL_A_MODULE_FW_FLASH_STATUS_MSG,
464d7d4cfc4SDanielle Ratson 			   total_len);
465d7d4cfc4SDanielle Ratson 	if (!attr)
466d7d4cfc4SDanielle Ratson 		return -ENOMEM;
467d7d4cfc4SDanielle Ratson 
468d7d4cfc4SDanielle Ratson 	if (sub_err_msg)
469d7d4cfc4SDanielle Ratson 		sprintf(nla_data(attr), "%s, %s.", err_msg, sub_err_msg);
470d7d4cfc4SDanielle Ratson 	else
471d7d4cfc4SDanielle Ratson 		sprintf(nla_data(attr), "%s.", err_msg);
472d7d4cfc4SDanielle Ratson 
473d7d4cfc4SDanielle Ratson 	return 0;
474d7d4cfc4SDanielle Ratson }
475d7d4cfc4SDanielle Ratson 
476d7d4cfc4SDanielle Ratson static void
ethnl_module_fw_flash_ntf(struct net_device * dev,enum ethtool_module_fw_flash_status status,struct ethnl_module_fw_flash_ntf_params * ntf_params,char * err_msg,char * sub_err_msg,u64 done,u64 total)477d7d4cfc4SDanielle Ratson ethnl_module_fw_flash_ntf(struct net_device *dev,
478d7d4cfc4SDanielle Ratson 			  enum ethtool_module_fw_flash_status status,
479d7d4cfc4SDanielle Ratson 			  struct ethnl_module_fw_flash_ntf_params *ntf_params,
480d7d4cfc4SDanielle Ratson 			  char *err_msg, char *sub_err_msg,
481d7d4cfc4SDanielle Ratson 			  u64 done, u64 total)
482d7d4cfc4SDanielle Ratson {
483d7d4cfc4SDanielle Ratson 	struct sk_buff *skb;
484d7d4cfc4SDanielle Ratson 	void *hdr;
485d7d4cfc4SDanielle Ratson 	int ret;
486d7d4cfc4SDanielle Ratson 
487d7d4cfc4SDanielle Ratson 	if (ntf_params->closed_sock)
488d7d4cfc4SDanielle Ratson 		return;
489d7d4cfc4SDanielle Ratson 
490d7d4cfc4SDanielle Ratson 	skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
491d7d4cfc4SDanielle Ratson 	if (!skb)
492d7d4cfc4SDanielle Ratson 		return;
493d7d4cfc4SDanielle Ratson 
494275a63c9SDanielle Ratson 	hdr = ethnl_unicast_put(skb, ntf_params->portid, ++ntf_params->seq,
495d7d4cfc4SDanielle Ratson 				ETHTOOL_MSG_MODULE_FW_FLASH_NTF);
496d7d4cfc4SDanielle Ratson 	if (!hdr)
497d7d4cfc4SDanielle Ratson 		goto err_skb;
498d7d4cfc4SDanielle Ratson 
499d7d4cfc4SDanielle Ratson 	ret = ethnl_fill_reply_header(skb, dev,
500d7d4cfc4SDanielle Ratson 				      ETHTOOL_A_MODULE_FW_FLASH_HEADER);
501d7d4cfc4SDanielle Ratson 	if (ret < 0)
502d7d4cfc4SDanielle Ratson 		goto err_skb;
503d7d4cfc4SDanielle Ratson 
504d7d4cfc4SDanielle Ratson 	if (nla_put_u32(skb, ETHTOOL_A_MODULE_FW_FLASH_STATUS, status))
505d7d4cfc4SDanielle Ratson 		goto err_skb;
506d7d4cfc4SDanielle Ratson 
507d7d4cfc4SDanielle Ratson 	ret = ethnl_module_fw_flash_ntf_put_err(skb, err_msg, sub_err_msg);
508d7d4cfc4SDanielle Ratson 	if (ret < 0)
509d7d4cfc4SDanielle Ratson 		goto err_skb;
510d7d4cfc4SDanielle Ratson 
511d7d4cfc4SDanielle Ratson 	if (nla_put_uint(skb, ETHTOOL_A_MODULE_FW_FLASH_DONE, done))
512d7d4cfc4SDanielle Ratson 		goto err_skb;
513d7d4cfc4SDanielle Ratson 
514d7d4cfc4SDanielle Ratson 	if (nla_put_uint(skb, ETHTOOL_A_MODULE_FW_FLASH_TOTAL, total))
515d7d4cfc4SDanielle Ratson 		goto err_skb;
516d7d4cfc4SDanielle Ratson 
517d7d4cfc4SDanielle Ratson 	genlmsg_end(skb, hdr);
518d7d4cfc4SDanielle Ratson 	genlmsg_unicast(dev_net(dev), skb, ntf_params->portid);
519d7d4cfc4SDanielle Ratson 	return;
520d7d4cfc4SDanielle Ratson 
521d7d4cfc4SDanielle Ratson err_skb:
522d7d4cfc4SDanielle Ratson 	nlmsg_free(skb);
523d7d4cfc4SDanielle Ratson }
524d7d4cfc4SDanielle Ratson 
ethnl_module_fw_flash_ntf_err(struct net_device * dev,struct ethnl_module_fw_flash_ntf_params * params,char * err_msg,char * sub_err_msg)525d7d4cfc4SDanielle Ratson void ethnl_module_fw_flash_ntf_err(struct net_device *dev,
526d7d4cfc4SDanielle Ratson 				   struct ethnl_module_fw_flash_ntf_params *params,
527d7d4cfc4SDanielle Ratson 				   char *err_msg, char *sub_err_msg)
528d7d4cfc4SDanielle Ratson {
529d7d4cfc4SDanielle Ratson 	ethnl_module_fw_flash_ntf(dev, ETHTOOL_MODULE_FW_FLASH_STATUS_ERROR,
530d7d4cfc4SDanielle Ratson 				  params, err_msg, sub_err_msg, 0, 0);
531d7d4cfc4SDanielle Ratson }
532d7d4cfc4SDanielle Ratson 
533d7d4cfc4SDanielle Ratson void
ethnl_module_fw_flash_ntf_start(struct net_device * dev,struct ethnl_module_fw_flash_ntf_params * params)534d7d4cfc4SDanielle Ratson ethnl_module_fw_flash_ntf_start(struct net_device *dev,
535d7d4cfc4SDanielle Ratson 				struct ethnl_module_fw_flash_ntf_params *params)
536d7d4cfc4SDanielle Ratson {
537d7d4cfc4SDanielle Ratson 	ethnl_module_fw_flash_ntf(dev, ETHTOOL_MODULE_FW_FLASH_STATUS_STARTED,
538d7d4cfc4SDanielle Ratson 				  params, NULL, NULL, 0, 0);
539d7d4cfc4SDanielle Ratson }
540d7d4cfc4SDanielle Ratson 
541d7d4cfc4SDanielle Ratson void
ethnl_module_fw_flash_ntf_complete(struct net_device * dev,struct ethnl_module_fw_flash_ntf_params * params)542d7d4cfc4SDanielle Ratson ethnl_module_fw_flash_ntf_complete(struct net_device *dev,
543d7d4cfc4SDanielle Ratson 				   struct ethnl_module_fw_flash_ntf_params *params)
544d7d4cfc4SDanielle Ratson {
545d7d4cfc4SDanielle Ratson 	ethnl_module_fw_flash_ntf(dev, ETHTOOL_MODULE_FW_FLASH_STATUS_COMPLETED,
546d7d4cfc4SDanielle Ratson 				  params, NULL, NULL, 0, 0);
547d7d4cfc4SDanielle Ratson }
548d7d4cfc4SDanielle Ratson 
549d7d4cfc4SDanielle Ratson void
ethnl_module_fw_flash_ntf_in_progress(struct net_device * dev,struct ethnl_module_fw_flash_ntf_params * params,u64 done,u64 total)550d7d4cfc4SDanielle Ratson ethnl_module_fw_flash_ntf_in_progress(struct net_device *dev,
551d7d4cfc4SDanielle Ratson 				      struct ethnl_module_fw_flash_ntf_params *params,
552d7d4cfc4SDanielle Ratson 				      u64 done, u64 total)
553d7d4cfc4SDanielle Ratson {
554d7d4cfc4SDanielle Ratson 	ethnl_module_fw_flash_ntf(dev,
555d7d4cfc4SDanielle Ratson 				  ETHTOOL_MODULE_FW_FLASH_STATUS_IN_PROGRESS,
556d7d4cfc4SDanielle Ratson 				  params, NULL, NULL, done, total);
557d7d4cfc4SDanielle Ratson }
558