xref: /openbmc/linux/net/ethtool/pause.c (revision f946270d)
17f59fb32SMichal Kubecek // SPDX-License-Identifier: GPL-2.0-only
27f59fb32SMichal Kubecek 
37f59fb32SMichal Kubecek #include "netlink.h"
47f59fb32SMichal Kubecek #include "common.h"
57f59fb32SMichal Kubecek 
67f59fb32SMichal Kubecek struct pause_req_info {
77f59fb32SMichal Kubecek 	struct ethnl_req_info		base;
804692c90SVladimir Oltean 	enum ethtool_mac_stats_src	src;
97f59fb32SMichal Kubecek };
107f59fb32SMichal Kubecek 
1104692c90SVladimir Oltean #define PAUSE_REQINFO(__req_base) \
1204692c90SVladimir Oltean 	container_of(__req_base, struct pause_req_info, base)
1304692c90SVladimir Oltean 
147f59fb32SMichal Kubecek struct pause_reply_data {
157f59fb32SMichal Kubecek 	struct ethnl_reply_data		base;
167f59fb32SMichal Kubecek 	struct ethtool_pauseparam	pauseparam;
179a27a330SJakub Kicinski 	struct ethtool_pause_stats	pausestat;
187f59fb32SMichal Kubecek };
197f59fb32SMichal Kubecek 
207f59fb32SMichal Kubecek #define PAUSE_REPDATA(__reply_base) \
217f59fb32SMichal Kubecek 	container_of(__reply_base, struct pause_reply_data, base)
227f59fb32SMichal Kubecek 
23ff419afaSJakub Kicinski const struct nla_policy ethnl_pause_get_policy[] = {
24329d9c33SJakub Kicinski 	[ETHTOOL_A_PAUSE_HEADER]		=
25a0de1cd3SJakub Kicinski 		NLA_POLICY_NESTED(ethnl_header_policy_stats),
2604692c90SVladimir Oltean 	[ETHTOOL_A_PAUSE_STATS_SRC]		=
2704692c90SVladimir Oltean 		NLA_POLICY_MAX(NLA_U32, ETHTOOL_MAC_STATS_SRC_PMAC),
287f59fb32SMichal Kubecek };
297f59fb32SMichal Kubecek 
pause_parse_request(struct ethnl_req_info * req_base,struct nlattr ** tb,struct netlink_ext_ack * extack)3004692c90SVladimir Oltean static int pause_parse_request(struct ethnl_req_info *req_base,
3104692c90SVladimir Oltean 			       struct nlattr **tb,
3204692c90SVladimir Oltean 			       struct netlink_ext_ack *extack)
3304692c90SVladimir Oltean {
3404692c90SVladimir Oltean 	enum ethtool_mac_stats_src src = ETHTOOL_MAC_STATS_SRC_AGGREGATE;
3504692c90SVladimir Oltean 	struct pause_req_info *req_info = PAUSE_REQINFO(req_base);
3604692c90SVladimir Oltean 
3704692c90SVladimir Oltean 	if (tb[ETHTOOL_A_PAUSE_STATS_SRC]) {
3804692c90SVladimir Oltean 		if (!(req_base->flags & ETHTOOL_FLAG_STATS)) {
3904692c90SVladimir Oltean 			NL_SET_ERR_MSG_MOD(extack,
4004692c90SVladimir Oltean 					   "ETHTOOL_FLAG_STATS must be set when requesting a source of stats");
4104692c90SVladimir Oltean 			return -EINVAL;
4204692c90SVladimir Oltean 		}
4304692c90SVladimir Oltean 
4404692c90SVladimir Oltean 		src = nla_get_u32(tb[ETHTOOL_A_PAUSE_STATS_SRC]);
4504692c90SVladimir Oltean 	}
4604692c90SVladimir Oltean 
4704692c90SVladimir Oltean 	req_info->src = src;
4804692c90SVladimir Oltean 
4904692c90SVladimir Oltean 	return 0;
5004692c90SVladimir Oltean }
5104692c90SVladimir Oltean 
pause_prepare_data(const struct ethnl_req_info * req_base,struct ethnl_reply_data * reply_base,const struct genl_info * info)527f59fb32SMichal Kubecek static int pause_prepare_data(const struct ethnl_req_info *req_base,
537f59fb32SMichal Kubecek 			      struct ethnl_reply_data *reply_base,
54*f946270dSJakub Kicinski 			      const struct genl_info *info)
557f59fb32SMichal Kubecek {
5604692c90SVladimir Oltean 	const struct pause_req_info *req_info = PAUSE_REQINFO(req_base);
577f59fb32SMichal Kubecek 	struct pause_reply_data *data = PAUSE_REPDATA(reply_base);
5804692c90SVladimir Oltean 	enum ethtool_mac_stats_src src = req_info->src;
597f59fb32SMichal Kubecek 	struct net_device *dev = reply_base->dev;
607f59fb32SMichal Kubecek 	int ret;
617f59fb32SMichal Kubecek 
627f59fb32SMichal Kubecek 	if (!dev->ethtool_ops->get_pauseparam)
637f59fb32SMichal Kubecek 		return -EOPNOTSUPP;
649a27a330SJakub Kicinski 
6516756d3eSJakub Kicinski 	ethtool_stats_init((u64 *)&data->pausestat,
6616756d3eSJakub Kicinski 			   sizeof(data->pausestat) / 8);
6704692c90SVladimir Oltean 	data->pausestat.src = src;
6816756d3eSJakub Kicinski 
697f59fb32SMichal Kubecek 	ret = ethnl_ops_begin(dev);
707f59fb32SMichal Kubecek 	if (ret < 0)
717f59fb32SMichal Kubecek 		return ret;
7204692c90SVladimir Oltean 
7304692c90SVladimir Oltean 	if ((src == ETHTOOL_MAC_STATS_SRC_EMAC ||
7404692c90SVladimir Oltean 	     src == ETHTOOL_MAC_STATS_SRC_PMAC) &&
7504692c90SVladimir Oltean 	    !__ethtool_dev_mm_supported(dev)) {
76*f946270dSJakub Kicinski 		NL_SET_ERR_MSG_MOD(info->extack,
7704692c90SVladimir Oltean 				   "Device does not support MAC merge layer");
7804692c90SVladimir Oltean 		ethnl_ops_complete(dev);
7904692c90SVladimir Oltean 		return -EOPNOTSUPP;
8004692c90SVladimir Oltean 	}
8104692c90SVladimir Oltean 
827f59fb32SMichal Kubecek 	dev->ethtool_ops->get_pauseparam(dev, &data->pauseparam);
839a27a330SJakub Kicinski 	if (req_base->flags & ETHTOOL_FLAG_STATS &&
8416756d3eSJakub Kicinski 	    dev->ethtool_ops->get_pause_stats)
859a27a330SJakub Kicinski 		dev->ethtool_ops->get_pause_stats(dev, &data->pausestat);
8604692c90SVladimir Oltean 
877f59fb32SMichal Kubecek 	ethnl_ops_complete(dev);
887f59fb32SMichal Kubecek 
897f59fb32SMichal Kubecek 	return 0;
907f59fb32SMichal Kubecek }
917f59fb32SMichal Kubecek 
pause_reply_size(const struct ethnl_req_info * req_base,const struct ethnl_reply_data * reply_base)927f59fb32SMichal Kubecek static int pause_reply_size(const struct ethnl_req_info *req_base,
937f59fb32SMichal Kubecek 			    const struct ethnl_reply_data *reply_base)
947f59fb32SMichal Kubecek {
959a27a330SJakub Kicinski 	int n = nla_total_size(sizeof(u8)) +	/* _PAUSE_AUTONEG */
967f59fb32SMichal Kubecek 		nla_total_size(sizeof(u8)) +	/* _PAUSE_RX */
977f59fb32SMichal Kubecek 		nla_total_size(sizeof(u8));	/* _PAUSE_TX */
989a27a330SJakub Kicinski 
999a27a330SJakub Kicinski 	if (req_base->flags & ETHTOOL_FLAG_STATS)
1009a27a330SJakub Kicinski 		n += nla_total_size(0) +	/* _PAUSE_STATS */
10104692c90SVladimir Oltean 		     nla_total_size(sizeof(u32)) + /* _PAUSE_STATS_SRC */
1021aabe578SJakub Kicinski 		     nla_total_size_64bit(sizeof(u64)) * ETHTOOL_PAUSE_STAT_CNT;
1039a27a330SJakub Kicinski 	return n;
1049a27a330SJakub Kicinski }
1059a27a330SJakub Kicinski 
ethtool_put_stat(struct sk_buff * skb,u64 val,u16 attrtype,u16 padtype)1069a27a330SJakub Kicinski static int ethtool_put_stat(struct sk_buff *skb, u64 val, u16 attrtype,
1079a27a330SJakub Kicinski 			    u16 padtype)
1089a27a330SJakub Kicinski {
1099a27a330SJakub Kicinski 	if (val == ETHTOOL_STAT_NOT_SET)
1109a27a330SJakub Kicinski 		return 0;
1119a27a330SJakub Kicinski 	if (nla_put_u64_64bit(skb, attrtype, val, padtype))
1129a27a330SJakub Kicinski 		return -EMSGSIZE;
1139a27a330SJakub Kicinski 
1149a27a330SJakub Kicinski 	return 0;
1159a27a330SJakub Kicinski }
1169a27a330SJakub Kicinski 
pause_put_stats(struct sk_buff * skb,const struct ethtool_pause_stats * pause_stats)1179a27a330SJakub Kicinski static int pause_put_stats(struct sk_buff *skb,
1189a27a330SJakub Kicinski 			   const struct ethtool_pause_stats *pause_stats)
1199a27a330SJakub Kicinski {
1209a27a330SJakub Kicinski 	const u16 pad = ETHTOOL_A_PAUSE_STAT_PAD;
1219a27a330SJakub Kicinski 	struct nlattr *nest;
1229a27a330SJakub Kicinski 
12304692c90SVladimir Oltean 	if (nla_put_u32(skb, ETHTOOL_A_PAUSE_STATS_SRC, pause_stats->src))
12404692c90SVladimir Oltean 		return -EMSGSIZE;
12504692c90SVladimir Oltean 
1269a27a330SJakub Kicinski 	nest = nla_nest_start(skb, ETHTOOL_A_PAUSE_STATS);
1279a27a330SJakub Kicinski 	if (!nest)
1289a27a330SJakub Kicinski 		return -EMSGSIZE;
1299a27a330SJakub Kicinski 
1309a27a330SJakub Kicinski 	if (ethtool_put_stat(skb, pause_stats->tx_pause_frames,
1319a27a330SJakub Kicinski 			     ETHTOOL_A_PAUSE_STAT_TX_FRAMES, pad) ||
1329a27a330SJakub Kicinski 	    ethtool_put_stat(skb, pause_stats->rx_pause_frames,
1339a27a330SJakub Kicinski 			     ETHTOOL_A_PAUSE_STAT_RX_FRAMES, pad))
1349a27a330SJakub Kicinski 		goto err_cancel;
1359a27a330SJakub Kicinski 
1369a27a330SJakub Kicinski 	nla_nest_end(skb, nest);
1379a27a330SJakub Kicinski 	return 0;
1389a27a330SJakub Kicinski 
1399a27a330SJakub Kicinski err_cancel:
1409a27a330SJakub Kicinski 	nla_nest_cancel(skb, nest);
1419a27a330SJakub Kicinski 	return -EMSGSIZE;
1427f59fb32SMichal Kubecek }
1437f59fb32SMichal Kubecek 
pause_fill_reply(struct sk_buff * skb,const struct ethnl_req_info * req_base,const struct ethnl_reply_data * reply_base)1447f59fb32SMichal Kubecek static int pause_fill_reply(struct sk_buff *skb,
1457f59fb32SMichal Kubecek 			    const struct ethnl_req_info *req_base,
1467f59fb32SMichal Kubecek 			    const struct ethnl_reply_data *reply_base)
1477f59fb32SMichal Kubecek {
1487f59fb32SMichal Kubecek 	const struct pause_reply_data *data = PAUSE_REPDATA(reply_base);
1497f59fb32SMichal Kubecek 	const struct ethtool_pauseparam *pauseparam = &data->pauseparam;
1507f59fb32SMichal Kubecek 
1517f59fb32SMichal Kubecek 	if (nla_put_u8(skb, ETHTOOL_A_PAUSE_AUTONEG, !!pauseparam->autoneg) ||
1527f59fb32SMichal Kubecek 	    nla_put_u8(skb, ETHTOOL_A_PAUSE_RX, !!pauseparam->rx_pause) ||
1537f59fb32SMichal Kubecek 	    nla_put_u8(skb, ETHTOOL_A_PAUSE_TX, !!pauseparam->tx_pause))
1547f59fb32SMichal Kubecek 		return -EMSGSIZE;
1557f59fb32SMichal Kubecek 
1569a27a330SJakub Kicinski 	if (req_base->flags & ETHTOOL_FLAG_STATS &&
1579a27a330SJakub Kicinski 	    pause_put_stats(skb, &data->pausestat))
1589a27a330SJakub Kicinski 		return -EMSGSIZE;
1599a27a330SJakub Kicinski 
1607f59fb32SMichal Kubecek 	return 0;
1617f59fb32SMichal Kubecek }
1627f59fb32SMichal Kubecek 
16399132b6eSJakub Kicinski /* PAUSE_SET */
16499132b6eSJakub Kicinski 
16599132b6eSJakub Kicinski const struct nla_policy ethnl_pause_set_policy[] = {
16699132b6eSJakub Kicinski 	[ETHTOOL_A_PAUSE_HEADER]		=
16799132b6eSJakub Kicinski 		NLA_POLICY_NESTED(ethnl_header_policy),
16899132b6eSJakub Kicinski 	[ETHTOOL_A_PAUSE_AUTONEG]		= { .type = NLA_U8 },
16999132b6eSJakub Kicinski 	[ETHTOOL_A_PAUSE_RX]			= { .type = NLA_U8 },
17099132b6eSJakub Kicinski 	[ETHTOOL_A_PAUSE_TX]			= { .type = NLA_U8 },
17199132b6eSJakub Kicinski };
17299132b6eSJakub Kicinski 
17399132b6eSJakub Kicinski static int
ethnl_set_pause_validate(struct ethnl_req_info * req_info,struct genl_info * info)17499132b6eSJakub Kicinski ethnl_set_pause_validate(struct ethnl_req_info *req_info,
17599132b6eSJakub Kicinski 			 struct genl_info *info)
17699132b6eSJakub Kicinski {
17799132b6eSJakub Kicinski 	const struct ethtool_ops *ops = req_info->dev->ethtool_ops;
17899132b6eSJakub Kicinski 
17999132b6eSJakub Kicinski 	return ops->get_pauseparam && ops->set_pauseparam ? 1 : -EOPNOTSUPP;
18099132b6eSJakub Kicinski }
18199132b6eSJakub Kicinski 
18299132b6eSJakub Kicinski static int
ethnl_set_pause(struct ethnl_req_info * req_info,struct genl_info * info)18399132b6eSJakub Kicinski ethnl_set_pause(struct ethnl_req_info *req_info, struct genl_info *info)
18499132b6eSJakub Kicinski {
18599132b6eSJakub Kicinski 	struct net_device *dev = req_info->dev;
18699132b6eSJakub Kicinski 	struct ethtool_pauseparam params = {};
18799132b6eSJakub Kicinski 	struct nlattr **tb = info->attrs;
18899132b6eSJakub Kicinski 	bool mod = false;
18999132b6eSJakub Kicinski 	int ret;
19099132b6eSJakub Kicinski 
19199132b6eSJakub Kicinski 	dev->ethtool_ops->get_pauseparam(dev, &params);
19299132b6eSJakub Kicinski 
19399132b6eSJakub Kicinski 	ethnl_update_bool32(&params.autoneg, tb[ETHTOOL_A_PAUSE_AUTONEG], &mod);
19499132b6eSJakub Kicinski 	ethnl_update_bool32(&params.rx_pause, tb[ETHTOOL_A_PAUSE_RX], &mod);
19599132b6eSJakub Kicinski 	ethnl_update_bool32(&params.tx_pause, tb[ETHTOOL_A_PAUSE_TX], &mod);
19699132b6eSJakub Kicinski 	if (!mod)
19799132b6eSJakub Kicinski 		return 0;
19899132b6eSJakub Kicinski 
19999132b6eSJakub Kicinski 	ret = dev->ethtool_ops->set_pauseparam(dev, &params);
20099132b6eSJakub Kicinski 	return ret < 0 ? ret : 1;
20199132b6eSJakub Kicinski }
20299132b6eSJakub Kicinski 
2037f59fb32SMichal Kubecek const struct ethnl_request_ops ethnl_pause_request_ops = {
2047f59fb32SMichal Kubecek 	.request_cmd		= ETHTOOL_MSG_PAUSE_GET,
2057f59fb32SMichal Kubecek 	.reply_cmd		= ETHTOOL_MSG_PAUSE_GET_REPLY,
2067f59fb32SMichal Kubecek 	.hdr_attr		= ETHTOOL_A_PAUSE_HEADER,
2077f59fb32SMichal Kubecek 	.req_info_size		= sizeof(struct pause_req_info),
2087f59fb32SMichal Kubecek 	.reply_data_size	= sizeof(struct pause_reply_data),
2097f59fb32SMichal Kubecek 
21004692c90SVladimir Oltean 	.parse_request		= pause_parse_request,
2117f59fb32SMichal Kubecek 	.prepare_data		= pause_prepare_data,
2127f59fb32SMichal Kubecek 	.reply_size		= pause_reply_size,
2137f59fb32SMichal Kubecek 	.fill_reply		= pause_fill_reply,
21499132b6eSJakub Kicinski 
21599132b6eSJakub Kicinski 	.set_validate		= ethnl_set_pause_validate,
21699132b6eSJakub Kicinski 	.set			= ethnl_set_pause,
21799132b6eSJakub Kicinski 	.set_ntf_cmd		= ETHTOOL_MSG_PAUSE_NTF,
2187f59fb32SMichal Kubecek };
219