Commit 86e99b5b authored by David S. Miller's avatar David S. Miller
Browse files

Merge branch 'ethtool-netlink-next'

Jakub Kicinski says:

====================
ethtool: netlink: handle SET intro/outro in the common code

Factor out the boilerplate code from SET handlers to common code.

I volunteered to refactor the extack in GET in a conversation
with Vladimir but I gave up.

The handling of failures during dump in GET handlers is a bit
unclear to me. Some code uses presence of info as indication
of dump and tries to avoid reporting errors altogether
(including extack messages).

There's also the question of whether we should have a validation
callback (similar to .set_validate here) for GET. It looks like
.parse_request was expected to perform the validation. It takes
the extack and tb directly, not via info:

	int (*parse_request)(struct ethnl_req_info *req_info,
			     struct nlattr **tb,
			     struct netlink_ext_ack *extack);

	int (*prepare_data)(const struct ethnl_req_info *req_info,
			    struct ethnl_reply_data *reply_data,
			    struct genl_info *info);

so no crashes dereferencing info possible.

But .parse_request doesn't run under rtnl nor ethnl_ops_begin().
As a result some implementations defer validation until .prepare_data
where all the locks are held and they can call out to the driver.

All this makes me think that maybe we should refactor GET in the
same direction I'm refactoring SET. Split .prepare_data, take
more locks in the core, and add a validation helper which would
take extack directly:

    - ret = ops->prepare_data(req_info, reply_data, info);
    + ret = ops->prepare_data_validate(req_info, reply_data, attrs, extack);
    + if (ret < 1) // if 0 -> skip for dump; -EOPNOTSUPP in do
    +   goto err1;
    +
    + ret = ethnl_ops_begin(dev);
    + if (ret)
    +   goto err1;
    +
    + ret = ops->prepare_data(req_info, reply_data); // no extack
    + ethnl_ops_complete(dev);

I'll file that away as a TODO for posterity / older me.

v2:
 - invert checks for coalescing to avoid error code changes
 - rebase and convert MM as well

v1: https://lore.kernel.org/all/20230121054430.642280-1-kuba@kernel.org/


====================

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents c766e077 04007961
Loading
Loading
Loading
Loading
+36 −56
Original line number Diff line number Diff line
@@ -86,18 +86,6 @@ static int channels_fill_reply(struct sk_buff *skb,
	return 0;
}

const struct ethnl_request_ops ethnl_channels_request_ops = {
	.request_cmd		= ETHTOOL_MSG_CHANNELS_GET,
	.reply_cmd		= ETHTOOL_MSG_CHANNELS_GET_REPLY,
	.hdr_attr		= ETHTOOL_A_CHANNELS_HEADER,
	.req_info_size		= sizeof(struct channels_req_info),
	.reply_data_size	= sizeof(struct channels_reply_data),

	.prepare_data		= channels_prepare_data,
	.reply_size		= channels_reply_size,
	.fill_reply		= channels_fill_reply,
};

/* CHANNELS_SET */

const struct nla_policy ethnl_channels_set_policy[] = {
@@ -109,36 +97,28 @@ const struct nla_policy ethnl_channels_set_policy[] = {
	[ETHTOOL_A_CHANNELS_COMBINED_COUNT]	= { .type = NLA_U32 },
};

int ethnl_set_channels(struct sk_buff *skb, struct genl_info *info)
static int
ethnl_set_channels_validate(struct ethnl_req_info *req_info,
			    struct genl_info *info)
{
	const struct ethtool_ops *ops = req_info->dev->ethtool_ops;

	return ops->get_channels && ops->set_channels ? 1 : -EOPNOTSUPP;
}

static int
ethnl_set_channels(struct ethnl_req_info *req_info, struct genl_info *info)
{
	unsigned int from_channel, old_total, i;
	bool mod = false, mod_combined = false;
	struct net_device *dev = req_info->dev;
	struct ethtool_channels channels = {};
	struct ethnl_req_info req_info = {};
	struct nlattr **tb = info->attrs;
	u32 err_attr, max_rxfh_in_use;
	const struct ethtool_ops *ops;
	struct net_device *dev;
	u64 max_rxnfc_in_use;
	int ret;

	ret = ethnl_parse_header_dev_get(&req_info,
					 tb[ETHTOOL_A_CHANNELS_HEADER],
					 genl_info_net(info), info->extack,
					 true);
	if (ret < 0)
		return ret;
	dev = req_info.dev;
	ops = dev->ethtool_ops;
	ret = -EOPNOTSUPP;
	if (!ops->get_channels || !ops->set_channels)
		goto out_dev;

	rtnl_lock();
	ret = ethnl_ops_begin(dev);
	if (ret < 0)
		goto out_rtnl;
	ops->get_channels(dev, &channels);
	dev->ethtool_ops->get_channels(dev, &channels);
	old_total = channels.combined_count +
		    max(channels.rx_count, channels.tx_count);

@@ -151,9 +131,8 @@ int ethnl_set_channels(struct sk_buff *skb, struct genl_info *info)
	ethnl_update_u32(&channels.combined_count,
			 tb[ETHTOOL_A_CHANNELS_COMBINED_COUNT], &mod_combined);
	mod |= mod_combined;
	ret = 0;
	if (!mod)
		goto out_ops;
		return 0;

	/* ensure new channel counts are within limits */
	if (channels.rx_count > channels.max_rx)
@@ -167,10 +146,9 @@ int ethnl_set_channels(struct sk_buff *skb, struct genl_info *info)
	else
		err_attr = 0;
	if (err_attr) {
		ret = -EINVAL;
		NL_SET_ERR_MSG_ATTR(info->extack, tb[err_attr],
				    "requested channel count exceeds maximum");
		goto out_ops;
		return -EINVAL;
	}

	/* ensure there is at least one RX and one TX channel */
@@ -183,10 +161,9 @@ int ethnl_set_channels(struct sk_buff *skb, struct genl_info *info)
	if (err_attr) {
		if (mod_combined)
			err_attr = ETHTOOL_A_CHANNELS_COMBINED_COUNT;
		ret = -EINVAL;
		NL_SET_ERR_MSG_ATTR(info->extack, tb[err_attr],
				    "requested channel counts would result in no RX or TX channel being configured");
		goto out_ops;
		return -EINVAL;
	}

	/* ensure the new Rx count fits within the configured Rx flow
@@ -198,14 +175,12 @@ int ethnl_set_channels(struct sk_buff *skb, struct genl_info *info)
	    ethtool_get_max_rxfh_channel(dev, &max_rxfh_in_use))
		max_rxfh_in_use = 0;
	if (channels.combined_count + channels.rx_count <= max_rxfh_in_use) {
		ret = -EINVAL;
		GENL_SET_ERR_MSG(info, "requested channel counts are too low for existing indirection table settings");
		goto out_ops;
		return -EINVAL;
	}
	if (channels.combined_count + channels.rx_count <= max_rxnfc_in_use) {
		ret = -EINVAL;
		GENL_SET_ERR_MSG(info, "requested channel counts are too low for existing ntuple filter settings");
		goto out_ops;
		return -EINVAL;
	}

	/* Disabling channels, query zero-copy AF_XDP sockets */
@@ -213,21 +188,26 @@ int ethnl_set_channels(struct sk_buff *skb, struct genl_info *info)
		       min(channels.rx_count, channels.tx_count);
	for (i = from_channel; i < old_total; i++)
		if (xsk_get_pool_from_qid(dev, i)) {
			ret = -EINVAL;
			GENL_SET_ERR_MSG(info, "requested channel counts are too low for existing zerocopy AF_XDP sockets");
			goto out_ops;
			return -EINVAL;
		}

	ret = dev->ethtool_ops->set_channels(dev, &channels);
	if (ret < 0)
		goto out_ops;
	ethtool_notify(dev, ETHTOOL_MSG_CHANNELS_NTF, NULL);

out_ops:
	ethnl_ops_complete(dev);
out_rtnl:
	rtnl_unlock();
out_dev:
	ethnl_parse_header_dev_put(&req_info);
	return ret;
	return ret < 0 ? ret : 1;
}

const struct ethnl_request_ops ethnl_channels_request_ops = {
	.request_cmd		= ETHTOOL_MSG_CHANNELS_GET,
	.reply_cmd		= ETHTOOL_MSG_CHANNELS_GET_REPLY,
	.hdr_attr		= ETHTOOL_A_CHANNELS_HEADER,
	.req_info_size		= sizeof(struct channels_req_info),
	.reply_data_size	= sizeof(struct channels_reply_data),

	.prepare_data		= channels_prepare_data,
	.reply_size		= channels_reply_size,
	.fill_reply		= channels_fill_reply,

	.set_validate		= ethnl_set_channels_validate,
	.set			= ethnl_set_channels,
	.set_ntf_cmd		= ETHTOOL_MSG_CHANNELS_NTF,
};
+40 −52
Original line number Diff line number Diff line
@@ -195,18 +195,6 @@ static int coalesce_fill_reply(struct sk_buff *skb,
	return 0;
}

const struct ethnl_request_ops ethnl_coalesce_request_ops = {
	.request_cmd		= ETHTOOL_MSG_COALESCE_GET,
	.reply_cmd		= ETHTOOL_MSG_COALESCE_GET_REPLY,
	.hdr_attr		= ETHTOOL_A_COALESCE_HEADER,
	.req_info_size		= sizeof(struct coalesce_req_info),
	.reply_data_size	= sizeof(struct coalesce_reply_data),

	.prepare_data		= coalesce_prepare_data,
	.reply_size		= coalesce_reply_size,
	.fill_reply		= coalesce_fill_reply,
};

/* COALESCE_SET */

const struct nla_policy ethnl_coalesce_set_policy[] = {
@@ -241,49 +229,44 @@ const struct nla_policy ethnl_coalesce_set_policy[] = {
	[ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS] = { .type = NLA_U32 },
};

int ethnl_set_coalesce(struct sk_buff *skb, struct genl_info *info)
static int
ethnl_set_coalesce_validate(struct ethnl_req_info *req_info,
			    struct genl_info *info)
{
	struct kernel_ethtool_coalesce kernel_coalesce = {};
	struct ethtool_coalesce coalesce = {};
	struct ethnl_req_info req_info = {};
	const struct ethtool_ops *ops = req_info->dev->ethtool_ops;
	struct nlattr **tb = info->attrs;
	const struct ethtool_ops *ops;
	struct net_device *dev;
	u32 supported_params;
	bool mod = false;
	int ret;
	u16 a;

	ret = ethnl_parse_header_dev_get(&req_info,
					 tb[ETHTOOL_A_COALESCE_HEADER],
					 genl_info_net(info), info->extack,
					 true);
	if (ret < 0)
		return ret;
	dev = req_info.dev;
	ops = dev->ethtool_ops;
	ret = -EOPNOTSUPP;
	if (!ops->get_coalesce || !ops->set_coalesce)
		goto out_dev;
		return -EOPNOTSUPP;

	/* make sure that only supported parameters are present */
	supported_params = ops->supported_coalesce_params;
	for (a = ETHTOOL_A_COALESCE_RX_USECS; a < __ETHTOOL_A_COALESCE_CNT; a++)
		if (tb[a] && !(supported_params & attr_to_mask(a))) {
			ret = -EINVAL;
			NL_SET_ERR_MSG_ATTR(info->extack, tb[a],
					    "cannot modify an unsupported parameter");
			goto out_dev;
			return -EINVAL;
		}

	rtnl_lock();
	ret = ethnl_ops_begin(dev);
	if (ret < 0)
		goto out_rtnl;
	ret = ops->get_coalesce(dev, &coalesce, &kernel_coalesce,
	return 1;
}

static int
ethnl_set_coalesce(struct ethnl_req_info *req_info, struct genl_info *info)
{
	struct kernel_ethtool_coalesce kernel_coalesce = {};
	struct net_device *dev = req_info->dev;
	struct ethtool_coalesce coalesce = {};
	struct nlattr **tb = info->attrs;
	bool mod = false;
	int ret;

	ret = dev->ethtool_ops->get_coalesce(dev, &coalesce, &kernel_coalesce,
					     info->extack);
	if (ret < 0)
		goto out_ops;
		return ret;

	ethnl_update_u32(&coalesce.rx_coalesce_usecs,
			 tb[ETHTOOL_A_COALESCE_RX_USECS], &mod);
@@ -339,21 +322,26 @@ int ethnl_set_coalesce(struct sk_buff *skb, struct genl_info *info)
			 tb[ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES], &mod);
	ethnl_update_u32(&kernel_coalesce.tx_aggr_time_usecs,
			 tb[ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS], &mod);
	ret = 0;
	if (!mod)
		goto out_ops;
		return 0;

	ret = dev->ethtool_ops->set_coalesce(dev, &coalesce, &kernel_coalesce,
					     info->extack);
	if (ret < 0)
		goto out_ops;
	ethtool_notify(dev, ETHTOOL_MSG_COALESCE_NTF, NULL);

out_ops:
	ethnl_ops_complete(dev);
out_rtnl:
	rtnl_unlock();
out_dev:
	ethnl_parse_header_dev_put(&req_info);
	return ret;
	return ret < 0 ? ret : 1;
}

const struct ethnl_request_ops ethnl_coalesce_request_ops = {
	.request_cmd		= ETHTOOL_MSG_COALESCE_GET,
	.reply_cmd		= ETHTOOL_MSG_COALESCE_GET_REPLY,
	.hdr_attr		= ETHTOOL_A_COALESCE_HEADER,
	.req_info_size		= sizeof(struct coalesce_req_info),
	.reply_data_size	= sizeof(struct coalesce_reply_data),

	.prepare_data		= coalesce_prepare_data,
	.reply_size		= coalesce_reply_size,
	.fill_reply		= coalesce_fill_reply,

	.set_validate		= ethnl_set_coalesce_validate,
	.set			= ethnl_set_coalesce,
	.set_ntf_cmd		= ETHTOOL_MSG_COALESCE_NTF,
};
+30 −41
Original line number Diff line number Diff line
@@ -63,18 +63,6 @@ static int debug_fill_reply(struct sk_buff *skb,
				  netif_msg_class_names, compact);
}

const struct ethnl_request_ops ethnl_debug_request_ops = {
	.request_cmd		= ETHTOOL_MSG_DEBUG_GET,
	.reply_cmd		= ETHTOOL_MSG_DEBUG_GET_REPLY,
	.hdr_attr		= ETHTOOL_A_DEBUG_HEADER,
	.req_info_size		= sizeof(struct debug_req_info),
	.reply_data_size	= sizeof(struct debug_reply_data),

	.prepare_data		= debug_prepare_data,
	.reply_size		= debug_reply_size,
	.fill_reply		= debug_fill_reply,
};

/* DEBUG_SET */

const struct nla_policy ethnl_debug_set_policy[] = {
@@ -83,46 +71,47 @@ const struct nla_policy ethnl_debug_set_policy[] = {
	[ETHTOOL_A_DEBUG_MSGMASK]	= { .type = NLA_NESTED },
};

int ethnl_set_debug(struct sk_buff *skb, struct genl_info *info)
static int
ethnl_set_debug_validate(struct ethnl_req_info *req_info,
			 struct genl_info *info)
{
	const struct ethtool_ops *ops = req_info->dev->ethtool_ops;

	return ops->get_msglevel && ops->set_msglevel ? 1 : -EOPNOTSUPP;
}

static int
ethnl_set_debug(struct ethnl_req_info *req_info, struct genl_info *info)
{
	struct ethnl_req_info req_info = {};
	struct net_device *dev = req_info->dev;
	struct nlattr **tb = info->attrs;
	struct net_device *dev;
	bool mod = false;
	u32 msg_mask;
	int ret;

	ret = ethnl_parse_header_dev_get(&req_info,
					 tb[ETHTOOL_A_DEBUG_HEADER],
					 genl_info_net(info), info->extack,
					 true);
	if (ret < 0)
		return ret;
	dev = req_info.dev;
	ret = -EOPNOTSUPP;
	if (!dev->ethtool_ops->get_msglevel || !dev->ethtool_ops->set_msglevel)
		goto out_dev;

	rtnl_lock();
	ret = ethnl_ops_begin(dev);
	if (ret < 0)
		goto out_rtnl;

	msg_mask = dev->ethtool_ops->get_msglevel(dev);
	ret = ethnl_update_bitset32(&msg_mask, NETIF_MSG_CLASS_COUNT,
				    tb[ETHTOOL_A_DEBUG_MSGMASK],
				    netif_msg_class_names, info->extack, &mod);
	if (ret < 0 || !mod)
		goto out_ops;
		return ret;

	dev->ethtool_ops->set_msglevel(dev, msg_mask);
	ethtool_notify(dev, ETHTOOL_MSG_DEBUG_NTF, NULL);

out_ops:
	ethnl_ops_complete(dev);
out_rtnl:
	rtnl_unlock();
out_dev:
	ethnl_parse_header_dev_put(&req_info);
	return ret;
	return 1;
}

const struct ethnl_request_ops ethnl_debug_request_ops = {
	.request_cmd		= ETHTOOL_MSG_DEBUG_GET,
	.reply_cmd		= ETHTOOL_MSG_DEBUG_GET_REPLY,
	.hdr_attr		= ETHTOOL_A_DEBUG_HEADER,
	.req_info_size		= sizeof(struct debug_req_info),
	.reply_data_size	= sizeof(struct debug_reply_data),

	.prepare_data		= debug_prepare_data,
	.reply_size		= debug_reply_size,
	.fill_reply		= debug_fill_reply,

	.set_validate		= ethnl_set_debug_validate,
	.set			= ethnl_set_debug,
	.set_ntf_cmd		= ETHTOOL_MSG_DEBUG_NTF,
};
+31 −47
Original line number Diff line number Diff line
@@ -108,18 +108,6 @@ static int eee_fill_reply(struct sk_buff *skb,
	return 0;
}

const struct ethnl_request_ops ethnl_eee_request_ops = {
	.request_cmd		= ETHTOOL_MSG_EEE_GET,
	.reply_cmd		= ETHTOOL_MSG_EEE_GET_REPLY,
	.hdr_attr		= ETHTOOL_A_EEE_HEADER,
	.req_info_size		= sizeof(struct eee_req_info),
	.reply_data_size	= sizeof(struct eee_reply_data),

	.prepare_data		= eee_prepare_data,
	.reply_size		= eee_reply_size,
	.fill_reply		= eee_fill_reply,
};

/* EEE_SET */

const struct nla_policy ethnl_eee_set_policy[] = {
@@ -131,60 +119,56 @@ const struct nla_policy ethnl_eee_set_policy[] = {
	[ETHTOOL_A_EEE_TX_LPI_TIMER]	= { .type = NLA_U32 },
};

int ethnl_set_eee(struct sk_buff *skb, struct genl_info *info)
static int
ethnl_set_eee_validate(struct ethnl_req_info *req_info, struct genl_info *info)
{
	const struct ethtool_ops *ops = req_info->dev->ethtool_ops;

	return ops->get_eee && ops->set_eee ? 1 : -EOPNOTSUPP;
}

static int
ethnl_set_eee(struct ethnl_req_info *req_info, struct genl_info *info)
{
	struct ethnl_req_info req_info = {};
	struct net_device *dev = req_info->dev;
	struct nlattr **tb = info->attrs;
	const struct ethtool_ops *ops;
	struct ethtool_eee eee = {};
	struct net_device *dev;
	bool mod = false;
	int ret;

	ret = ethnl_parse_header_dev_get(&req_info,
					 tb[ETHTOOL_A_EEE_HEADER],
					 genl_info_net(info), info->extack,
					 true);
	ret = dev->ethtool_ops->get_eee(dev, &eee);
	if (ret < 0)
		return ret;
	dev = req_info.dev;
	ops = dev->ethtool_ops;
	ret = -EOPNOTSUPP;
	if (!ops->get_eee || !ops->set_eee)
		goto out_dev;

	rtnl_lock();
	ret = ethnl_ops_begin(dev);
	if (ret < 0)
		goto out_rtnl;
	ret = ops->get_eee(dev, &eee);
	if (ret < 0)
		goto out_ops;

	ret = ethnl_update_bitset32(&eee.advertised, EEE_MODES_COUNT,
				    tb[ETHTOOL_A_EEE_MODES_OURS],
				    link_mode_names, info->extack, &mod);
	if (ret < 0)
		goto out_ops;
		return ret;
	ethnl_update_bool32(&eee.eee_enabled, tb[ETHTOOL_A_EEE_ENABLED], &mod);
	ethnl_update_bool32(&eee.tx_lpi_enabled,
			    tb[ETHTOOL_A_EEE_TX_LPI_ENABLED], &mod);
	ethnl_update_u32(&eee.tx_lpi_timer, tb[ETHTOOL_A_EEE_TX_LPI_TIMER],
			 &mod);
	ret = 0;
	if (!mod)
		goto out_ops;
		return 0;

	ret = dev->ethtool_ops->set_eee(dev, &eee);
	if (ret < 0)
		goto out_ops;
	ethtool_notify(dev, ETHTOOL_MSG_EEE_NTF, NULL);

out_ops:
	ethnl_ops_complete(dev);
out_rtnl:
	rtnl_unlock();
out_dev:
	ethnl_parse_header_dev_put(&req_info);
	return ret;
	return ret < 0 ? ret : 1;
}

const struct ethnl_request_ops ethnl_eee_request_ops = {
	.request_cmd		= ETHTOOL_MSG_EEE_GET,
	.reply_cmd		= ETHTOOL_MSG_EEE_GET_REPLY,
	.hdr_attr		= ETHTOOL_A_EEE_HEADER,
	.req_info_size		= sizeof(struct eee_req_info),
	.reply_data_size	= sizeof(struct eee_reply_data),

	.prepare_data		= eee_prepare_data,
	.reply_size		= eee_reply_size,
	.fill_reply		= eee_fill_reply,

	.set_validate		= ethnl_set_eee_validate,
	.set			= ethnl_set_eee,
	.set_ntf_cmd		= ETHTOOL_MSG_EEE_NTF,
};
+33 −50
Original line number Diff line number Diff line
@@ -217,18 +217,6 @@ static int fec_fill_reply(struct sk_buff *skb,
	return 0;
}

const struct ethnl_request_ops ethnl_fec_request_ops = {
	.request_cmd		= ETHTOOL_MSG_FEC_GET,
	.reply_cmd		= ETHTOOL_MSG_FEC_GET_REPLY,
	.hdr_attr		= ETHTOOL_A_FEC_HEADER,
	.req_info_size		= sizeof(struct fec_req_info),
	.reply_data_size	= sizeof(struct fec_reply_data),

	.prepare_data		= fec_prepare_data,
	.reply_size		= fec_reply_size,
	.fill_reply		= fec_fill_reply,
};

/* FEC_SET */

const struct nla_policy ethnl_fec_set_policy[ETHTOOL_A_FEC_AUTO + 1] = {
@@ -237,36 +225,28 @@ const struct nla_policy ethnl_fec_set_policy[ETHTOOL_A_FEC_AUTO + 1] = {
	[ETHTOOL_A_FEC_AUTO]	= NLA_POLICY_MAX(NLA_U8, 1),
};

int ethnl_set_fec(struct sk_buff *skb, struct genl_info *info)
static int
ethnl_set_fec_validate(struct ethnl_req_info *req_info, struct genl_info *info)
{
	const struct ethtool_ops *ops = req_info->dev->ethtool_ops;

	return ops->get_fecparam && ops->set_fecparam ? 1 : -EOPNOTSUPP;
}

static int
ethnl_set_fec(struct ethnl_req_info *req_info, struct genl_info *info)
{
	__ETHTOOL_DECLARE_LINK_MODE_MASK(fec_link_modes) = {};
	struct ethnl_req_info req_info = {};
	struct net_device *dev = req_info->dev;
	struct nlattr **tb = info->attrs;
	struct ethtool_fecparam fec = {};
	const struct ethtool_ops *ops;
	struct net_device *dev;
	bool mod = false;
	u8 fec_auto;
	int ret;

	ret = ethnl_parse_header_dev_get(&req_info, tb[ETHTOOL_A_FEC_HEADER],
					 genl_info_net(info), info->extack,
					 true);
	ret = dev->ethtool_ops->get_fecparam(dev, &fec);
	if (ret < 0)
		return ret;
	dev = req_info.dev;
	ops = dev->ethtool_ops;
	ret = -EOPNOTSUPP;
	if (!ops->get_fecparam || !ops->set_fecparam)
		goto out_dev;

	rtnl_lock();
	ret = ethnl_ops_begin(dev);
	if (ret < 0)
		goto out_rtnl;
	ret = ops->get_fecparam(dev, &fec);
	if (ret < 0)
		goto out_ops;

	ethtool_fec_to_link_modes(fec.fec, fec_link_modes, &fec_auto);

@@ -275,36 +255,39 @@ int ethnl_set_fec(struct sk_buff *skb, struct genl_info *info)
				  tb[ETHTOOL_A_FEC_MODES],
				  link_mode_names, info->extack, &mod);
	if (ret < 0)
		goto out_ops;
		return ret;
	ethnl_update_u8(&fec_auto, tb[ETHTOOL_A_FEC_AUTO], &mod);

	ret = 0;
	if (!mod)
		goto out_ops;
		return 0;

	ret = ethtool_link_modes_to_fecparam(&fec, fec_link_modes, fec_auto);
	if (ret) {
		NL_SET_ERR_MSG_ATTR(info->extack, tb[ETHTOOL_A_FEC_MODES],
				    "invalid FEC modes requested");
		goto out_ops;
		return ret;
	}
	if (!fec.fec) {
		ret = -EINVAL;
		NL_SET_ERR_MSG_ATTR(info->extack, tb[ETHTOOL_A_FEC_MODES],
				    "no FEC modes set");
		goto out_ops;
		return -EINVAL;
	}

	ret = dev->ethtool_ops->set_fecparam(dev, &fec);
	if (ret < 0)
		goto out_ops;
	ethtool_notify(dev, ETHTOOL_MSG_FEC_NTF, NULL);

out_ops:
	ethnl_ops_complete(dev);
out_rtnl:
	rtnl_unlock();
out_dev:
	ethnl_parse_header_dev_put(&req_info);
	return ret;
	return ret < 0 ? ret : 1;
}

const struct ethnl_request_ops ethnl_fec_request_ops = {
	.request_cmd		= ETHTOOL_MSG_FEC_GET,
	.reply_cmd		= ETHTOOL_MSG_FEC_GET_REPLY,
	.hdr_attr		= ETHTOOL_A_FEC_HEADER,
	.req_info_size		= sizeof(struct fec_req_info),
	.reply_data_size	= sizeof(struct fec_reply_data),

	.prepare_data		= fec_prepare_data,
	.reply_size		= fec_reply_size,
	.fill_reply		= fec_fill_reply,

	.set_validate		= ethnl_set_fec_validate,
	.set			= ethnl_set_fec,
	.set_ntf_cmd		= ETHTOOL_MSG_FEC_NTF,
};
Loading