提交 1adc58ea 编写于 作者: D David S. Miller

Merge branch 'devlink-locking'

Jakub Kicinski says:

====================
improve ethtool/rtnl vs devlink locking

During ethtool netlink development we decided to move some of
the commmands to devlink. Since we don't want drivers to implement
both devlink and ethtool version of the commands ethtool ioctl
falls back to calling devlink. Unfortunately devlink locks must
be taken before rtnl_lock. This results in a questionable
dev_hold() / rtnl_unlock() / devlink / rtnl_lock() / dev_put()
pattern.

This method "works" but it working depends on drivers in question
not doing much in ethtool_ops->begin / complete, and on the netdev
not having needs_free_netdev set.

Since commit 437ebfd9 ("devlink: Count struct devlink consumers")
we can hold a reference on a devlink instance and prevent it from
going away (sort of like netdev with dev_hold()). We can use this
to create a more natural reference nesting where we get a ref on
the devlink instance and make the devlink call entirely outside
of the rtnl_lock section.
====================
Signed-off-by: NDavid S. Miller <davem@davemloft.net>
...@@ -1726,9 +1726,12 @@ devlink_trap_policers_unregister(struct devlink *devlink, ...@@ -1726,9 +1726,12 @@ devlink_trap_policers_unregister(struct devlink *devlink,
#if IS_ENABLED(CONFIG_NET_DEVLINK) #if IS_ENABLED(CONFIG_NET_DEVLINK)
void devlink_compat_running_version(struct net_device *dev, struct devlink *__must_check devlink_try_get(struct devlink *devlink);
void devlink_put(struct devlink *devlink);
void devlink_compat_running_version(struct devlink *devlink,
char *buf, size_t len); char *buf, size_t len);
int devlink_compat_flash_update(struct net_device *dev, const char *file_name); int devlink_compat_flash_update(struct devlink *devlink, const char *file_name);
int devlink_compat_phys_port_name_get(struct net_device *dev, int devlink_compat_phys_port_name_get(struct net_device *dev,
char *name, size_t len); char *name, size_t len);
int devlink_compat_switch_id_get(struct net_device *dev, int devlink_compat_switch_id_get(struct net_device *dev,
...@@ -1736,13 +1739,22 @@ int devlink_compat_switch_id_get(struct net_device *dev, ...@@ -1736,13 +1739,22 @@ int devlink_compat_switch_id_get(struct net_device *dev,
#else #else
static inline struct devlink *devlink_try_get(struct devlink *devlink)
{
return NULL;
}
static inline void devlink_put(struct devlink *devlink)
{
}
static inline void static inline void
devlink_compat_running_version(struct net_device *dev, char *buf, size_t len) devlink_compat_running_version(struct devlink *devlink, char *buf, size_t len)
{ {
} }
static inline int static inline int
devlink_compat_flash_update(struct net_device *dev, const char *file_name) devlink_compat_flash_update(struct devlink *devlink, const char *file_name)
{ {
return -EOPNOTSUPP; return -EOPNOTSUPP;
} }
......
...@@ -518,9 +518,7 @@ int dev_ioctl(struct net *net, unsigned int cmd, struct ifreq *ifr, ...@@ -518,9 +518,7 @@ int dev_ioctl(struct net *net, unsigned int cmd, struct ifreq *ifr,
case SIOCETHTOOL: case SIOCETHTOOL:
dev_load(net, ifr->ifr_name); dev_load(net, ifr->ifr_name);
rtnl_lock();
ret = dev_ethtool(net, ifr, data); ret = dev_ethtool(net, ifr, data);
rtnl_unlock();
if (colon) if (colon)
*colon = ':'; *colon = ':';
return ret; return ret;
......
...@@ -182,15 +182,17 @@ struct net *devlink_net(const struct devlink *devlink) ...@@ -182,15 +182,17 @@ struct net *devlink_net(const struct devlink *devlink)
} }
EXPORT_SYMBOL_GPL(devlink_net); EXPORT_SYMBOL_GPL(devlink_net);
static void devlink_put(struct devlink *devlink) void devlink_put(struct devlink *devlink)
{ {
if (refcount_dec_and_test(&devlink->refcount)) if (refcount_dec_and_test(&devlink->refcount))
complete(&devlink->comp); complete(&devlink->comp);
} }
static bool __must_check devlink_try_get(struct devlink *devlink) struct devlink *__must_check devlink_try_get(struct devlink *devlink)
{ {
return refcount_inc_not_zero(&devlink->refcount); if (refcount_inc_not_zero(&devlink->refcount))
return devlink;
return NULL;
} }
static struct devlink *devlink_get_from_attrs(struct net *net, static struct devlink *devlink_get_from_attrs(struct net *net,
...@@ -11281,55 +11283,28 @@ static struct devlink_port *netdev_to_devlink_port(struct net_device *dev) ...@@ -11281,55 +11283,28 @@ static struct devlink_port *netdev_to_devlink_port(struct net_device *dev)
return dev->netdev_ops->ndo_get_devlink_port(dev); return dev->netdev_ops->ndo_get_devlink_port(dev);
} }
static struct devlink *netdev_to_devlink(struct net_device *dev) void devlink_compat_running_version(struct devlink *devlink,
{
struct devlink_port *devlink_port = netdev_to_devlink_port(dev);
if (!devlink_port)
return NULL;
return devlink_port->devlink;
}
void devlink_compat_running_version(struct net_device *dev,
char *buf, size_t len) char *buf, size_t len)
{ {
struct devlink *devlink; if (!devlink->ops->info_get)
return;
dev_hold(dev);
rtnl_unlock();
devlink = netdev_to_devlink(dev);
if (!devlink || !devlink->ops->info_get)
goto out;
mutex_lock(&devlink->lock); mutex_lock(&devlink->lock);
__devlink_compat_running_version(devlink, buf, len); __devlink_compat_running_version(devlink, buf, len);
mutex_unlock(&devlink->lock); mutex_unlock(&devlink->lock);
out:
rtnl_lock();
dev_put(dev);
} }
int devlink_compat_flash_update(struct net_device *dev, const char *file_name) int devlink_compat_flash_update(struct devlink *devlink, const char *file_name)
{ {
struct devlink_flash_update_params params = {}; struct devlink_flash_update_params params = {};
struct devlink *devlink;
int ret; int ret;
dev_hold(dev); if (!devlink->ops->flash_update)
rtnl_unlock(); return -EOPNOTSUPP;
devlink = netdev_to_devlink(dev);
if (!devlink || !devlink->ops->flash_update) {
ret = -EOPNOTSUPP;
goto out;
}
ret = request_firmware(&params.fw, file_name, devlink->dev); ret = request_firmware(&params.fw, file_name, devlink->dev);
if (ret) if (ret)
goto out; return ret;
mutex_lock(&devlink->lock); mutex_lock(&devlink->lock);
devlink_flash_update_begin_notify(devlink); devlink_flash_update_begin_notify(devlink);
...@@ -11339,10 +11314,6 @@ int devlink_compat_flash_update(struct net_device *dev, const char *file_name) ...@@ -11339,10 +11314,6 @@ int devlink_compat_flash_update(struct net_device *dev, const char *file_name)
release_firmware(params.fw); release_firmware(params.fw);
out:
rtnl_lock();
dev_put(dev);
return ret; return ret;
} }
......
...@@ -32,6 +32,29 @@ ...@@ -32,6 +32,29 @@
#include <generated/utsrelease.h> #include <generated/utsrelease.h>
#include "common.h" #include "common.h"
/* State held across locks and calls for commands which have devlink fallback */
struct ethtool_devlink_compat {
struct devlink *devlink;
union {
struct ethtool_flash efl;
struct ethtool_drvinfo info;
};
};
static struct devlink *netdev_to_devlink_get(struct net_device *dev)
{
struct devlink_port *devlink_port;
if (!dev->netdev_ops->ndo_get_devlink_port)
return NULL;
devlink_port = dev->netdev_ops->ndo_get_devlink_port(dev);
if (!devlink_port)
return NULL;
return devlink_try_get(devlink_port->devlink);
}
/* /*
* Some useful ethtool_ops methods that're device independent. * Some useful ethtool_ops methods that're device independent.
* If we find that all drivers want to do the same thing here, * If we find that all drivers want to do the same thing here,
...@@ -697,22 +720,20 @@ static int ethtool_set_settings(struct net_device *dev, void __user *useraddr) ...@@ -697,22 +720,20 @@ static int ethtool_set_settings(struct net_device *dev, void __user *useraddr)
return ret; return ret;
} }
static noinline_for_stack int ethtool_get_drvinfo(struct net_device *dev, static int
void __user *useraddr) ethtool_get_drvinfo(struct net_device *dev, struct ethtool_devlink_compat *rsp)
{ {
struct ethtool_drvinfo info;
const struct ethtool_ops *ops = dev->ethtool_ops; const struct ethtool_ops *ops = dev->ethtool_ops;
memset(&info, 0, sizeof(info)); rsp->info.cmd = ETHTOOL_GDRVINFO;
info.cmd = ETHTOOL_GDRVINFO; strlcpy(rsp->info.version, UTS_RELEASE, sizeof(rsp->info.version));
strlcpy(info.version, UTS_RELEASE, sizeof(info.version));
if (ops->get_drvinfo) { if (ops->get_drvinfo) {
ops->get_drvinfo(dev, &info); ops->get_drvinfo(dev, &rsp->info);
} else if (dev->dev.parent && dev->dev.parent->driver) { } else if (dev->dev.parent && dev->dev.parent->driver) {
strlcpy(info.bus_info, dev_name(dev->dev.parent), strlcpy(rsp->info.bus_info, dev_name(dev->dev.parent),
sizeof(info.bus_info)); sizeof(rsp->info.bus_info));
strlcpy(info.driver, dev->dev.parent->driver->name, strlcpy(rsp->info.driver, dev->dev.parent->driver->name,
sizeof(info.driver)); sizeof(rsp->info.driver));
} else { } else {
return -EOPNOTSUPP; return -EOPNOTSUPP;
} }
...@@ -726,30 +747,27 @@ static noinline_for_stack int ethtool_get_drvinfo(struct net_device *dev, ...@@ -726,30 +747,27 @@ static noinline_for_stack int ethtool_get_drvinfo(struct net_device *dev,
rc = ops->get_sset_count(dev, ETH_SS_TEST); rc = ops->get_sset_count(dev, ETH_SS_TEST);
if (rc >= 0) if (rc >= 0)
info.testinfo_len = rc; rsp->info.testinfo_len = rc;
rc = ops->get_sset_count(dev, ETH_SS_STATS); rc = ops->get_sset_count(dev, ETH_SS_STATS);
if (rc >= 0) if (rc >= 0)
info.n_stats = rc; rsp->info.n_stats = rc;
rc = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS); rc = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS);
if (rc >= 0) if (rc >= 0)
info.n_priv_flags = rc; rsp->info.n_priv_flags = rc;
} }
if (ops->get_regs_len) { if (ops->get_regs_len) {
int ret = ops->get_regs_len(dev); int ret = ops->get_regs_len(dev);
if (ret > 0) if (ret > 0)
info.regdump_len = ret; rsp->info.regdump_len = ret;
} }
if (ops->get_eeprom_len) if (ops->get_eeprom_len)
info.eedump_len = ops->get_eeprom_len(dev); rsp->info.eedump_len = ops->get_eeprom_len(dev);
if (!info.fw_version[0]) if (!rsp->info.fw_version[0])
devlink_compat_running_version(dev, info.fw_version, rsp->devlink = netdev_to_devlink_get(dev);
sizeof(info.fw_version));
if (copy_to_user(useraddr, &info, sizeof(info)))
return -EFAULT;
return 0; return 0;
} }
...@@ -2178,19 +2196,15 @@ static int ethtool_set_value(struct net_device *dev, char __user *useraddr, ...@@ -2178,19 +2196,15 @@ static int ethtool_set_value(struct net_device *dev, char __user *useraddr,
return actor(dev, edata.data); return actor(dev, edata.data);
} }
static noinline_for_stack int ethtool_flash_device(struct net_device *dev, static int
char __user *useraddr) ethtool_flash_device(struct net_device *dev, struct ethtool_devlink_compat *req)
{ {
struct ethtool_flash efl; if (!dev->ethtool_ops->flash_device) {
req->devlink = netdev_to_devlink_get(dev);
if (copy_from_user(&efl, useraddr, sizeof(efl))) return 0;
return -EFAULT; }
efl.data[ETHTOOL_FLASH_MAX_FILENAME - 1] = 0;
if (!dev->ethtool_ops->flash_device)
return devlink_compat_flash_update(dev, efl.data);
return dev->ethtool_ops->flash_device(dev, &efl); return dev->ethtool_ops->flash_device(dev, &req->efl);
} }
static int ethtool_set_dump(struct net_device *dev, static int ethtool_set_dump(struct net_device *dev,
...@@ -2700,19 +2714,19 @@ static int ethtool_set_fecparam(struct net_device *dev, void __user *useraddr) ...@@ -2700,19 +2714,19 @@ static int ethtool_set_fecparam(struct net_device *dev, void __user *useraddr)
/* The main entry point in this file. Called from net/core/dev_ioctl.c */ /* The main entry point in this file. Called from net/core/dev_ioctl.c */
int dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr) static int
__dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr,
u32 ethcmd, struct ethtool_devlink_compat *devlink_state)
{ {
struct net_device *dev = __dev_get_by_name(net, ifr->ifr_name); struct net_device *dev;
u32 ethcmd, sub_cmd; u32 sub_cmd;
int rc; int rc;
netdev_features_t old_features; netdev_features_t old_features;
dev = __dev_get_by_name(net, ifr->ifr_name);
if (!dev) if (!dev)
return -ENODEV; return -ENODEV;
if (copy_from_user(&ethcmd, useraddr, sizeof(ethcmd)))
return -EFAULT;
if (ethcmd == ETHTOOL_PERQUEUE) { if (ethcmd == ETHTOOL_PERQUEUE) {
if (copy_from_user(&sub_cmd, useraddr + sizeof(ethcmd), sizeof(sub_cmd))) if (copy_from_user(&sub_cmd, useraddr + sizeof(ethcmd), sizeof(sub_cmd)))
return -EFAULT; return -EFAULT;
...@@ -2786,7 +2800,7 @@ int dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr) ...@@ -2786,7 +2800,7 @@ int dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr)
rc = ethtool_set_settings(dev, useraddr); rc = ethtool_set_settings(dev, useraddr);
break; break;
case ETHTOOL_GDRVINFO: case ETHTOOL_GDRVINFO:
rc = ethtool_get_drvinfo(dev, useraddr); rc = ethtool_get_drvinfo(dev, devlink_state);
break; break;
case ETHTOOL_GREGS: case ETHTOOL_GREGS:
rc = ethtool_get_regs(dev, useraddr); rc = ethtool_get_regs(dev, useraddr);
...@@ -2888,7 +2902,7 @@ int dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr) ...@@ -2888,7 +2902,7 @@ int dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr)
rc = ethtool_set_rxnfc(dev, ethcmd, useraddr); rc = ethtool_set_rxnfc(dev, ethcmd, useraddr);
break; break;
case ETHTOOL_FLASHDEV: case ETHTOOL_FLASHDEV:
rc = ethtool_flash_device(dev, useraddr); rc = ethtool_flash_device(dev, devlink_state);
break; break;
case ETHTOOL_RESET: case ETHTOOL_RESET:
rc = ethtool_reset(dev, useraddr); rc = ethtool_reset(dev, useraddr);
...@@ -3000,6 +3014,60 @@ int dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr) ...@@ -3000,6 +3014,60 @@ int dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr)
return rc; return rc;
} }
int dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr)
{
struct ethtool_devlink_compat *state;
u32 ethcmd;
int rc;
if (copy_from_user(&ethcmd, useraddr, sizeof(ethcmd)))
return -EFAULT;
state = kzalloc(sizeof(*state), GFP_KERNEL);
if (!state)
return -ENOMEM;
switch (ethcmd) {
case ETHTOOL_FLASHDEV:
if (copy_from_user(&state->efl, useraddr, sizeof(state->efl))) {
rc = -EFAULT;
goto exit_free;
}
state->efl.data[ETHTOOL_FLASH_MAX_FILENAME - 1] = 0;
break;
}
rtnl_lock();
rc = __dev_ethtool(net, ifr, useraddr, ethcmd, state);
rtnl_unlock();
if (rc)
goto exit_free;
switch (ethcmd) {
case ETHTOOL_FLASHDEV:
if (state->devlink)
rc = devlink_compat_flash_update(state->devlink,
state->efl.data);
break;
case ETHTOOL_GDRVINFO:
if (state->devlink)
devlink_compat_running_version(state->devlink,
state->info.fw_version,
sizeof(state->info.fw_version));
if (copy_to_user(useraddr, &state->info, sizeof(state->info))) {
rc = -EFAULT;
goto exit_free;
}
break;
}
exit_free:
if (state->devlink)
devlink_put(state->devlink);
kfree(state);
return rc;
}
struct ethtool_rx_flow_key { struct ethtool_rx_flow_key {
struct flow_dissector_key_basic basic; struct flow_dissector_key_basic basic;
union { union {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册