Skip to content

Commit

Permalink
ethtool: Add ability to flash transceiver modules' firmware
Browse files Browse the repository at this point in the history
Add the ability to flash the modules' firmware by implementing the
interface between the user space and the kernel.

Example from a succeeding implementation:

 # ethtool --flash-module-firmware swp40 file test.bin

 Transceiver module firmware flashing started for device swp40
 Transceiver module firmware flashing in progress for device swp40
 Progress: 99%
 Transceiver module firmware flashing completed for device swp40

In addition, add infrastructure that allows modules to set socket-specific
private data. This ensures that when a socket is closed from user space
during the flashing process, the right socket halts sending notifications
to user space until the work item is completed.

Signed-off-by: Danielle Ratson <[email protected]>
Reviewed-by: Petr Machata <[email protected]>
Signed-off-by: David S. Miller <[email protected]>
  • Loading branch information
daniellerts authored and davem330 committed Jun 28, 2024
1 parent c4f7813 commit 32b4c8b
Show file tree
Hide file tree
Showing 4 changed files with 334 additions and 0 deletions.
277 changes: 277 additions & 0 deletions net/ethtool/module.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-only

#include <linux/ethtool.h>
#include <linux/firmware.h>
#include <linux/sfp.h>
#include <net/devlink.h>

#include "netlink.h"
#include "common.h"
Expand Down Expand Up @@ -34,6 +37,12 @@ static int module_get_power_mode(struct net_device *dev,
if (!ops->get_module_power_mode)
return 0;

if (dev->module_fw_flash_in_progress) {
NL_SET_ERR_MSG(extack,
"Module firmware flashing is in progress");
return -EBUSY;
}

return ops->get_module_power_mode(dev, &data->power, extack);
}

Expand Down Expand Up @@ -110,6 +119,12 @@ ethnl_set_module_validate(struct ethnl_req_info *req_info,
if (!tb[ETHTOOL_A_MODULE_POWER_MODE_POLICY])
return 0;

if (req_info->dev->module_fw_flash_in_progress) {
NL_SET_ERR_MSG(info->extack,
"Module firmware flashing is in progress");
return -EBUSY;
}

if (!ops->get_module_power_mode || !ops->set_module_power_mode) {
NL_SET_ERR_MSG_ATTR(info->extack,
tb[ETHTOOL_A_MODULE_POWER_MODE_POLICY],
Expand Down Expand Up @@ -160,6 +175,268 @@ const struct ethnl_request_ops ethnl_module_request_ops = {
.set_ntf_cmd = ETHTOOL_MSG_MODULE_NTF,
};

/* MODULE_FW_FLASH_ACT */

const struct nla_policy
ethnl_module_fw_flash_act_policy[ETHTOOL_A_MODULE_FW_FLASH_PASSWORD + 1] = {
[ETHTOOL_A_MODULE_FW_FLASH_HEADER] =
NLA_POLICY_NESTED(ethnl_header_policy),
[ETHTOOL_A_MODULE_FW_FLASH_FILE_NAME] = { .type = NLA_NUL_STRING },
[ETHTOOL_A_MODULE_FW_FLASH_PASSWORD] = { .type = NLA_U32 },
};

static LIST_HEAD(module_fw_flash_work_list);
static DEFINE_SPINLOCK(module_fw_flash_work_list_lock);

static int
module_flash_fw_work_list_add(struct ethtool_module_fw_flash *module_fw,
struct genl_info *info)
{
struct ethtool_module_fw_flash *work;

/* First, check if already registered. */
spin_lock(&module_fw_flash_work_list_lock);
list_for_each_entry(work, &module_fw_flash_work_list, list) {
if (work->fw_update.ntf_params.portid == info->snd_portid &&
work->fw_update.dev == module_fw->fw_update.dev) {
spin_unlock(&module_fw_flash_work_list_lock);
return -EALREADY;
}
}

list_add_tail(&module_fw->list, &module_fw_flash_work_list);
spin_unlock(&module_fw_flash_work_list_lock);

return 0;
}

static void module_flash_fw_work_list_del(struct list_head *list)
{
spin_lock(&module_fw_flash_work_list_lock);
list_del(list);
spin_unlock(&module_fw_flash_work_list_lock);
}

static void module_flash_fw_work(struct work_struct *work)
{
struct ethtool_module_fw_flash *module_fw;

module_fw = container_of(work, struct ethtool_module_fw_flash, work);

ethtool_cmis_fw_update(&module_fw->fw_update);

module_flash_fw_work_list_del(&module_fw->list);
module_fw->fw_update.dev->module_fw_flash_in_progress = false;
netdev_put(module_fw->fw_update.dev, &module_fw->dev_tracker);
release_firmware(module_fw->fw_update.fw);
kfree(module_fw);
}

#define MODULE_EEPROM_PHYS_ID_PAGE 0
#define MODULE_EEPROM_PHYS_ID_I2C_ADDR 0x50

static int module_flash_fw_work_init(struct ethtool_module_fw_flash *module_fw,
struct net_device *dev,
struct netlink_ext_ack *extack)
{
const struct ethtool_ops *ops = dev->ethtool_ops;
struct ethtool_module_eeprom page_data = {};
u8 phys_id;
int err;

/* Fetch the SFF-8024 Identifier Value. For all supported standards, it
* is located at I2C address 0x50, byte 0. See section 4.1 in SFF-8024,
* revision 4.9.
*/
page_data.page = MODULE_EEPROM_PHYS_ID_PAGE;
page_data.offset = SFP_PHYS_ID;
page_data.length = sizeof(phys_id);
page_data.i2c_address = MODULE_EEPROM_PHYS_ID_I2C_ADDR;
page_data.data = &phys_id;

err = ops->get_module_eeprom_by_page(dev, &page_data, extack);
if (err < 0)
return err;

switch (phys_id) {
case SFF8024_ID_QSFP_DD:
case SFF8024_ID_OSFP:
case SFF8024_ID_DSFP:
case SFF8024_ID_QSFP_PLUS_CMIS:
case SFF8024_ID_SFP_DD_CMIS:
case SFF8024_ID_SFP_PLUS_CMIS:
INIT_WORK(&module_fw->work, module_flash_fw_work);
break;
default:
NL_SET_ERR_MSG(extack,
"Module type does not support firmware flashing");
return -EOPNOTSUPP;
}

return 0;
}

void ethnl_module_fw_flash_sock_destroy(struct ethnl_sock_priv *sk_priv)
{
struct ethtool_module_fw_flash *work;

spin_lock(&module_fw_flash_work_list_lock);
list_for_each_entry(work, &module_fw_flash_work_list, list) {
if (work->fw_update.dev == sk_priv->dev &&
work->fw_update.ntf_params.portid == sk_priv->portid) {
work->fw_update.ntf_params.closed_sock = true;
break;
}
}
spin_unlock(&module_fw_flash_work_list_lock);
}

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)
{
struct ethtool_cmis_fw_update_params *fw_update;
struct ethtool_module_fw_flash *module_fw;
int err;

module_fw = kzalloc(sizeof(*module_fw), GFP_KERNEL);
if (!module_fw)
return -ENOMEM;

fw_update = &module_fw->fw_update;
fw_update->params = *params;
err = request_firmware_direct(&fw_update->fw,
file_name, &dev->dev);
if (err) {
NL_SET_ERR_MSG(info->extack,
"Failed to request module firmware image");
goto err_free;
}

err = module_flash_fw_work_init(module_fw, dev, info->extack);
if (err < 0)
goto err_release_firmware;

dev->module_fw_flash_in_progress = true;
netdev_hold(dev, &module_fw->dev_tracker, GFP_KERNEL);
fw_update->dev = dev;
fw_update->ntf_params.portid = info->snd_portid;
fw_update->ntf_params.seq = info->snd_seq;
fw_update->ntf_params.closed_sock = false;

err = ethnl_sock_priv_set(skb, dev, fw_update->ntf_params.portid,
ETHTOOL_SOCK_TYPE_MODULE_FW_FLASH);
if (err < 0)
goto err_release_firmware;

err = module_flash_fw_work_list_add(module_fw, info);
if (err < 0)
goto err_release_firmware;

schedule_work(&module_fw->work);

return 0;

err_release_firmware:
release_firmware(fw_update->fw);
err_free:
kfree(module_fw);
return err;
}

static int module_flash_fw(struct net_device *dev, struct nlattr **tb,
struct sk_buff *skb, struct genl_info *info)
{
struct ethtool_module_fw_flash_params params = {};
const char *file_name;
struct nlattr *attr;

if (GENL_REQ_ATTR_CHECK(info, ETHTOOL_A_MODULE_FW_FLASH_FILE_NAME))
return -EINVAL;

file_name = nla_data(tb[ETHTOOL_A_MODULE_FW_FLASH_FILE_NAME]);

attr = tb[ETHTOOL_A_MODULE_FW_FLASH_PASSWORD];
if (attr) {
params.password = cpu_to_be32(nla_get_u32(attr));
params.password_valid = true;
}

return module_flash_fw_schedule(dev, file_name, &params, skb, info);
}

static int ethnl_module_fw_flash_validate(struct net_device *dev,
struct netlink_ext_ack *extack)
{
struct devlink_port *devlink_port = dev->devlink_port;
const struct ethtool_ops *ops = dev->ethtool_ops;

if (!ops->set_module_eeprom_by_page ||
!ops->get_module_eeprom_by_page) {
NL_SET_ERR_MSG(extack,
"Flashing module firmware is not supported by this device");
return -EOPNOTSUPP;
}

if (!ops->reset) {
NL_SET_ERR_MSG(extack,
"Reset module is not supported by this device, so flashing is not permitted");
return -EOPNOTSUPP;
}

if (dev->module_fw_flash_in_progress) {
NL_SET_ERR_MSG(extack, "Module firmware flashing already in progress");
return -EBUSY;
}

if (dev->flags & IFF_UP) {
NL_SET_ERR_MSG(extack, "Netdevice is up, so flashing is not permitted");
return -EBUSY;
}

if (devlink_port && devlink_port->attrs.split) {
NL_SET_ERR_MSG(extack, "Can't perform firmware flashing on a split port");
return -EOPNOTSUPP;
}

return 0;
}

int ethnl_act_module_fw_flash(struct sk_buff *skb, struct genl_info *info)
{
struct ethnl_req_info req_info = {};
struct nlattr **tb = info->attrs;
struct net_device *dev;
int ret;

ret = ethnl_parse_header_dev_get(&req_info,
tb[ETHTOOL_A_MODULE_FW_FLASH_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 = ethnl_module_fw_flash_validate(dev, info->extack);
if (ret < 0)
goto out_rtnl;

ret = module_flash_fw(dev, tb, skb, info);

ethnl_ops_complete(dev);

out_rtnl:
rtnl_unlock();
ethnl_parse_header_dev_put(&req_info);
return ret;
}

/* MODULE_FW_FLASH_NTF */

static int
Expand Down
3 changes: 3 additions & 0 deletions net/ethtool/module_fw.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0-only */

#include <uapi/linux/ethtool.h>
#include "netlink.h"

/**
* struct ethnl_module_fw_flash_ntf_params - module firmware flashing
Expand Down Expand Up @@ -54,6 +55,8 @@ struct ethtool_module_fw_flash {
struct ethtool_cmis_fw_update_params fw_update;
};

void ethnl_module_fw_flash_sock_destroy(struct ethnl_sock_priv *sk_priv);

void
ethnl_module_fw_flash_ntf_err(struct net_device *dev,
struct ethnl_module_fw_flash_ntf_params *params,
Expand Down
39 changes: 39 additions & 0 deletions net/ethtool/netlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <linux/ethtool_netlink.h>
#include <linux/pm_runtime.h>
#include "netlink.h"
#include "module_fw.h"

static struct genl_family ethtool_genl_family;

Expand All @@ -30,6 +31,35 @@ const struct nla_policy ethnl_header_policy_stats[] = {
ETHTOOL_FLAGS_STATS),
};

int ethnl_sock_priv_set(struct sk_buff *skb, struct net_device *dev, u32 portid,
enum ethnl_sock_type type)
{
struct ethnl_sock_priv *sk_priv;

sk_priv = genl_sk_priv_get(&ethtool_genl_family, NETLINK_CB(skb).sk);
if (IS_ERR(sk_priv))
return PTR_ERR(sk_priv);

sk_priv->dev = dev;
sk_priv->portid = portid;
sk_priv->type = type;

return 0;
}

static void ethnl_sock_priv_destroy(void *priv)
{
struct ethnl_sock_priv *sk_priv = priv;

switch (sk_priv->type) {
case ETHTOOL_SOCK_TYPE_MODULE_FW_FLASH:
ethnl_module_fw_flash_sock_destroy(sk_priv);
break;
default:
break;
}
}

int ethnl_ops_begin(struct net_device *dev)
{
int ret;
Expand Down Expand Up @@ -1142,6 +1172,13 @@ static const struct genl_ops ethtool_genl_ops[] = {
.policy = ethnl_mm_set_policy,
.maxattr = ARRAY_SIZE(ethnl_mm_set_policy) - 1,
},
{
.cmd = ETHTOOL_MSG_MODULE_FW_FLASH_ACT,
.flags = GENL_UNS_ADMIN_PERM,
.doit = ethnl_act_module_fw_flash,
.policy = ethnl_module_fw_flash_act_policy,
.maxattr = ARRAY_SIZE(ethnl_module_fw_flash_act_policy) - 1,
},
};

static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
Expand All @@ -1158,6 +1195,8 @@ static struct genl_family ethtool_genl_family __ro_after_init = {
.resv_start_op = ETHTOOL_MSG_MODULE_GET + 1,
.mcgrps = ethtool_nl_mcgrps,
.n_mcgrps = ARRAY_SIZE(ethtool_nl_mcgrps),
.sock_priv_size = sizeof(struct ethnl_sock_priv),
.sock_priv_destroy = ethnl_sock_priv_destroy,
};

/* module setup */
Expand Down
Loading

0 comments on commit 32b4c8b

Please sign in to comment.