Blame view

net/dsa/master.c 9.15 KB
2874c5fd2   Thomas Gleixner   treewide: Replace...
1
  // SPDX-License-Identifier: GPL-2.0-or-later
f2f235668   Vivien Didelot   net: dsa: move ma...
2
3
4
5
6
  /*
   * Handling of a master device, switching frames via its switch fabric CPU port
   *
   * Copyright (c) 2017 Savoir-faire Linux Inc.
   *	Vivien Didelot <vivien.didelot@savoirfairelinux.com>
f2f235668   Vivien Didelot   net: dsa: move ma...
7
8
9
   */
  
  #include "dsa_priv.h"
48e233119   Vivien Didelot   net: dsa: dump CP...
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
  static int dsa_master_get_regs_len(struct net_device *dev)
  {
  	struct dsa_port *cpu_dp = dev->dsa_ptr;
  	const struct ethtool_ops *ops = cpu_dp->orig_ethtool_ops;
  	struct dsa_switch *ds = cpu_dp->ds;
  	int port = cpu_dp->index;
  	int ret = 0;
  	int len;
  
  	if (ops->get_regs_len) {
  		len = ops->get_regs_len(dev);
  		if (len < 0)
  			return len;
  		ret += len;
  	}
  
  	ret += sizeof(struct ethtool_drvinfo);
  	ret += sizeof(struct ethtool_regs);
  
  	if (ds->ops->get_regs_len) {
  		len = ds->ops->get_regs_len(ds, port);
  		if (len < 0)
  			return len;
  		ret += len;
  	}
  
  	return ret;
  }
  
  static void dsa_master_get_regs(struct net_device *dev,
  				struct ethtool_regs *regs, void *data)
  {
  	struct dsa_port *cpu_dp = dev->dsa_ptr;
  	const struct ethtool_ops *ops = cpu_dp->orig_ethtool_ops;
  	struct dsa_switch *ds = cpu_dp->ds;
  	struct ethtool_drvinfo *cpu_info;
  	struct ethtool_regs *cpu_regs;
  	int port = cpu_dp->index;
  	int len;
  
  	if (ops->get_regs_len && ops->get_regs) {
  		len = ops->get_regs_len(dev);
  		if (len < 0)
  			return;
  		regs->len = len;
  		ops->get_regs(dev, regs, data);
  		data += regs->len;
  	}
  
  	cpu_info = (struct ethtool_drvinfo *)data;
  	strlcpy(cpu_info->driver, "dsa", sizeof(cpu_info->driver));
  	data += sizeof(*cpu_info);
  	cpu_regs = (struct ethtool_regs *)data;
  	data += sizeof(*cpu_regs);
  
  	if (ds->ops->get_regs_len && ds->ops->get_regs) {
  		len = ds->ops->get_regs_len(ds, port);
  		if (len < 0)
  			return;
  		cpu_regs->len = len;
  		ds->ops->get_regs(ds, port, cpu_regs, data);
  	}
  }
f2f235668   Vivien Didelot   net: dsa: move ma...
73
74
75
76
  static void dsa_master_get_ethtool_stats(struct net_device *dev,
  					 struct ethtool_stats *stats,
  					 uint64_t *data)
  {
2f657a600   Vivien Didelot   net: dsa: change ...
77
  	struct dsa_port *cpu_dp = dev->dsa_ptr;
7ec764eef   Vivien Didelot   net: dsa: use cpu...
78
79
80
  	const struct ethtool_ops *ops = cpu_dp->orig_ethtool_ops;
  	struct dsa_switch *ds = cpu_dp->ds;
  	int port = cpu_dp->index;
f2f235668   Vivien Didelot   net: dsa: move ma...
81
  	int count = 0;
1d1e79f1c   Florian Fainelli   net: dsa: Do not ...
82
  	if (ops->get_sset_count && ops->get_ethtool_stats) {
f2f235668   Vivien Didelot   net: dsa: move ma...
83
84
85
86
87
  		count = ops->get_sset_count(dev, ETH_SS_STATS);
  		ops->get_ethtool_stats(dev, stats, data);
  	}
  
  	if (ds->ops->get_ethtool_stats)
7ec764eef   Vivien Didelot   net: dsa: use cpu...
88
  		ds->ops->get_ethtool_stats(ds, port, data + count);
f2f235668   Vivien Didelot   net: dsa: move ma...
89
  }
cf9635730   Florian Fainelli   net: dsa: Allow p...
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
  static void dsa_master_get_ethtool_phy_stats(struct net_device *dev,
  					     struct ethtool_stats *stats,
  					     uint64_t *data)
  {
  	struct dsa_port *cpu_dp = dev->dsa_ptr;
  	const struct ethtool_ops *ops = cpu_dp->orig_ethtool_ops;
  	struct dsa_switch *ds = cpu_dp->ds;
  	int port = cpu_dp->index;
  	int count = 0;
  
  	if (dev->phydev && !ops->get_ethtool_phy_stats) {
  		count = phy_ethtool_get_sset_count(dev->phydev);
  		if (count >= 0)
  			phy_ethtool_get_stats(dev->phydev, stats, data);
  	} else if (ops->get_sset_count && ops->get_ethtool_phy_stats) {
  		count = ops->get_sset_count(dev, ETH_SS_PHY_STATS);
  		ops->get_ethtool_phy_stats(dev, stats, data);
  	}
  
  	if (count < 0)
  		count = 0;
  
  	if (ds->ops->get_ethtool_phy_stats)
  		ds->ops->get_ethtool_phy_stats(ds, port, data + count);
  }
f2f235668   Vivien Didelot   net: dsa: move ma...
115
116
  static int dsa_master_get_sset_count(struct net_device *dev, int sset)
  {
2f657a600   Vivien Didelot   net: dsa: change ...
117
  	struct dsa_port *cpu_dp = dev->dsa_ptr;
7ec764eef   Vivien Didelot   net: dsa: use cpu...
118
119
  	const struct ethtool_ops *ops = cpu_dp->orig_ethtool_ops;
  	struct dsa_switch *ds = cpu_dp->ds;
f2f235668   Vivien Didelot   net: dsa: move ma...
120
  	int count = 0;
cf9635730   Florian Fainelli   net: dsa: Allow p...
121
122
123
124
  	if (sset == ETH_SS_PHY_STATS && dev->phydev &&
  	    !ops->get_ethtool_phy_stats)
  		count = phy_ethtool_get_sset_count(dev->phydev);
  	else if (ops->get_sset_count)
89f090483   Florian Fainelli   net: dsa: Pass st...
125
  		count = ops->get_sset_count(dev, sset);
cf9635730   Florian Fainelli   net: dsa: Allow p...
126
127
128
  
  	if (count < 0)
  		count = 0;
f2f235668   Vivien Didelot   net: dsa: move ma...
129

89f090483   Florian Fainelli   net: dsa: Pass st...
130
131
  	if (ds->ops->get_sset_count)
  		count += ds->ops->get_sset_count(ds, cpu_dp->index, sset);
f2f235668   Vivien Didelot   net: dsa: move ma...
132
133
134
135
136
137
138
  
  	return count;
  }
  
  static void dsa_master_get_strings(struct net_device *dev, uint32_t stringset,
  				   uint8_t *data)
  {
2f657a600   Vivien Didelot   net: dsa: change ...
139
  	struct dsa_port *cpu_dp = dev->dsa_ptr;
7ec764eef   Vivien Didelot   net: dsa: use cpu...
140
141
142
  	const struct ethtool_ops *ops = cpu_dp->orig_ethtool_ops;
  	struct dsa_switch *ds = cpu_dp->ds;
  	int port = cpu_dp->index;
f2f235668   Vivien Didelot   net: dsa: move ma...
143
144
145
146
147
  	int len = ETH_GSTRING_LEN;
  	int mcount = 0, count;
  	unsigned int i;
  	uint8_t pfx[4];
  	uint8_t *ndata;
7ec764eef   Vivien Didelot   net: dsa: use cpu...
148
  	snprintf(pfx, sizeof(pfx), "p%.2d", port);
f2f235668   Vivien Didelot   net: dsa: move ma...
149
150
  	/* We do not want to be NULL-terminated, since this is a prefix */
  	pfx[sizeof(pfx) - 1] = '_';
cf9635730   Florian Fainelli   net: dsa: Allow p...
151
152
153
154
155
156
157
158
  	if (stringset == ETH_SS_PHY_STATS && dev->phydev &&
  	    !ops->get_ethtool_phy_stats) {
  		mcount = phy_ethtool_get_sset_count(dev->phydev);
  		if (mcount < 0)
  			mcount = 0;
  		else
  			phy_ethtool_get_strings(dev->phydev, data);
  	} else if (ops->get_sset_count && ops->get_strings) {
89f090483   Florian Fainelli   net: dsa: Pass st...
159
160
161
  		mcount = ops->get_sset_count(dev, stringset);
  		if (mcount < 0)
  			mcount = 0;
f2f235668   Vivien Didelot   net: dsa: move ma...
162
163
  		ops->get_strings(dev, stringset, data);
  	}
89f090483   Florian Fainelli   net: dsa: Pass st...
164
  	if (ds->ops->get_strings) {
f2f235668   Vivien Didelot   net: dsa: move ma...
165
166
167
168
169
  		ndata = data + mcount * len;
  		/* This function copies ETH_GSTRINGS_LEN bytes, we will mangle
  		 * the output after to prepend our CPU port prefix we
  		 * constructed earlier
  		 */
89f090483   Florian Fainelli   net: dsa: Pass st...
170
171
  		ds->ops->get_strings(ds, port, stringset, ndata);
  		count = ds->ops->get_sset_count(ds, port, stringset);
f2f235668   Vivien Didelot   net: dsa: move ma...
172
173
174
175
176
177
178
  		for (i = 0; i < count; i++) {
  			memmove(ndata + (i * len + sizeof(pfx)),
  				ndata + i * len, len - sizeof(pfx));
  			memcpy(ndata + i * len, pfx, sizeof(pfx));
  		}
  	}
  }
f685e609a   Vladimir Oltean   net: dsa: Deny PT...
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
  static int dsa_master_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
  {
  	struct dsa_port *cpu_dp = dev->dsa_ptr;
  	struct dsa_switch *ds = cpu_dp->ds;
  	struct dsa_switch_tree *dst;
  	int err = -EOPNOTSUPP;
  	struct dsa_port *dp;
  
  	dst = ds->dst;
  
  	switch (cmd) {
  	case SIOCGHWTSTAMP:
  	case SIOCSHWTSTAMP:
  		/* Deny PTP operations on master if there is at least one
  		 * switch in the tree that is PTP capable.
  		 */
  		list_for_each_entry(dp, &dst->ports, list)
  			if (dp->ds->ops->port_hwtstamp_get ||
  			    dp->ds->ops->port_hwtstamp_set)
  				return -EBUSY;
  		break;
  	}
9c0c7014f   Florian Fainelli   net: dsa: Setup d...
201
202
  	if (dev->netdev_ops->ndo_do_ioctl)
  		err = dev->netdev_ops->ndo_do_ioctl(dev, ifr, cmd);
f685e609a   Vladimir Oltean   net: dsa: Deny PT...
203
204
205
  
  	return err;
  }
9c0c7014f   Florian Fainelli   net: dsa: Setup d...
206
207
  static const struct dsa_netdevice_ops dsa_netdev_ops = {
  	.ndo_do_ioctl = dsa_master_ioctl,
9c0c7014f   Florian Fainelli   net: dsa: Setup d...
208
  };
17a22fcfc   Vivien Didelot   net: dsa: setup a...
209
  static int dsa_master_ethtool_setup(struct net_device *dev)
f2f235668   Vivien Didelot   net: dsa: move ma...
210
  {
2f657a600   Vivien Didelot   net: dsa: change ...
211
  	struct dsa_port *cpu_dp = dev->dsa_ptr;
7ec764eef   Vivien Didelot   net: dsa: use cpu...
212
  	struct dsa_switch *ds = cpu_dp->ds;
f2f235668   Vivien Didelot   net: dsa: move ma...
213
214
215
216
217
  	struct ethtool_ops *ops;
  
  	ops = devm_kzalloc(ds->dev, sizeof(*ops), GFP_KERNEL);
  	if (!ops)
  		return -ENOMEM;
7ec764eef   Vivien Didelot   net: dsa: use cpu...
218
219
220
  	cpu_dp->orig_ethtool_ops = dev->ethtool_ops;
  	if (cpu_dp->orig_ethtool_ops)
  		memcpy(ops, cpu_dp->orig_ethtool_ops, sizeof(*ops));
f2f235668   Vivien Didelot   net: dsa: move ma...
221

48e233119   Vivien Didelot   net: dsa: dump CP...
222
223
  	ops->get_regs_len = dsa_master_get_regs_len;
  	ops->get_regs = dsa_master_get_regs;
f2f235668   Vivien Didelot   net: dsa: move ma...
224
225
226
  	ops->get_sset_count = dsa_master_get_sset_count;
  	ops->get_ethtool_stats = dsa_master_get_ethtool_stats;
  	ops->get_strings = dsa_master_get_strings;
cf9635730   Florian Fainelli   net: dsa: Allow p...
227
  	ops->get_ethtool_phy_stats = dsa_master_get_ethtool_phy_stats;
f2f235668   Vivien Didelot   net: dsa: move ma...
228
229
230
231
232
  
  	dev->ethtool_ops = ops;
  
  	return 0;
  }
17a22fcfc   Vivien Didelot   net: dsa: setup a...
233
  static void dsa_master_ethtool_teardown(struct net_device *dev)
f2f235668   Vivien Didelot   net: dsa: move ma...
234
  {
2f657a600   Vivien Didelot   net: dsa: change ...
235
  	struct dsa_port *cpu_dp = dev->dsa_ptr;
f2f235668   Vivien Didelot   net: dsa: move ma...
236

7ec764eef   Vivien Didelot   net: dsa: use cpu...
237
238
  	dev->ethtool_ops = cpu_dp->orig_ethtool_ops;
  	cpu_dp->orig_ethtool_ops = NULL;
f2f235668   Vivien Didelot   net: dsa: move ma...
239
  }
17a22fcfc   Vivien Didelot   net: dsa: setup a...
240

9c0c7014f   Florian Fainelli   net: dsa: Setup d...
241
242
  static void dsa_netdev_ops_set(struct net_device *dev,
  			       const struct dsa_netdevice_ops *ops)
da7b9e9b0   Florian Fainelli   net: dsa: Add ndo...
243
  {
9c0c7014f   Florian Fainelli   net: dsa: Setup d...
244
  	dev->dsa_ptr->netdev_ops = ops;
da7b9e9b0   Florian Fainelli   net: dsa: Add ndo...
245
  }
c3975400c   Vladimir Oltean   net: dsa: allow d...
246
247
248
249
250
251
252
253
254
255
256
  static void dsa_master_set_promiscuity(struct net_device *dev, int inc)
  {
  	const struct dsa_device_ops *ops = dev->dsa_ptr->tag_ops;
  
  	if (!ops->promisc_on_master)
  		return;
  
  	rtnl_lock();
  	dev_set_promiscuity(dev, inc);
  	rtnl_unlock();
  }
a3d7e01da   Florian Fainelli   net: dsa: Fix tag...
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
  static ssize_t tagging_show(struct device *d, struct device_attribute *attr,
  			    char *buf)
  {
  	struct net_device *dev = to_net_dev(d);
  	struct dsa_port *cpu_dp = dev->dsa_ptr;
  
  	return sprintf(buf, "%s
  ",
  		       dsa_tag_protocol_to_str(cpu_dp->tag_ops));
  }
  static DEVICE_ATTR_RO(tagging);
  
  static struct attribute *dsa_slave_attrs[] = {
  	&dev_attr_tagging.attr,
  	NULL
  };
  
  static const struct attribute_group dsa_group = {
  	.name	= "dsa",
  	.attrs	= dsa_slave_attrs,
  };
91ba47957   Andrew Lunn   net: dsa: Restore...
278
279
280
281
282
283
284
285
286
287
288
289
  static void dsa_master_reset_mtu(struct net_device *dev)
  {
  	int err;
  
  	rtnl_lock();
  	err = dev_set_mtu(dev, ETH_DATA_LEN);
  	if (err)
  		netdev_dbg(dev,
  			   "Unable to reset MTU to exclude DSA overheads
  ");
  	rtnl_unlock();
  }
845e0ebb4   Cong Wang   net: change addr_...
290
  static struct lock_class_key dsa_master_addr_list_lock_key;
17a22fcfc   Vivien Didelot   net: dsa: setup a...
291
292
  int dsa_master_setup(struct net_device *dev, struct dsa_port *cpu_dp)
  {
a3d7e01da   Florian Fainelli   net: dsa: Fix tag...
293
  	int ret;
bfcb81320   Vladimir Oltean   net: dsa: configu...
294
295
296
297
298
299
300
  	rtnl_lock();
  	ret = dev_set_mtu(dev, ETH_DATA_LEN + cpu_dp->tag_ops->overhead);
  	rtnl_unlock();
  	if (ret)
  		netdev_warn(dev, "error %d setting MTU to include DSA overhead
  ",
  			    ret);
dc0fe7d47   Andrew Lunn   net: dsa: Set the...
301

17a22fcfc   Vivien Didelot   net: dsa: setup a...
302
303
304
305
306
307
308
  	/* If we use a tagging format that doesn't have an ethertype
  	 * field, make sure that all packets from this point on get
  	 * sent to the tag format's receive function.
  	 */
  	wmb();
  
  	dev->dsa_ptr = cpu_dp;
845e0ebb4   Cong Wang   net: change addr_...
309
310
  	lockdep_set_class(&dev->addr_list_lock,
  			  &dsa_master_addr_list_lock_key);
c3975400c   Vladimir Oltean   net: dsa: allow d...
311
312
  
  	dsa_master_set_promiscuity(dev, 1);
a3d7e01da   Florian Fainelli   net: dsa: Fix tag...
313
314
  	ret = dsa_master_ethtool_setup(dev);
  	if (ret)
c3975400c   Vladimir Oltean   net: dsa: allow d...
315
  		goto out_err_reset_promisc;
a3d7e01da   Florian Fainelli   net: dsa: Fix tag...
316

9c0c7014f   Florian Fainelli   net: dsa: Setup d...
317
  	dsa_netdev_ops_set(dev, &dsa_netdev_ops);
da7b9e9b0   Florian Fainelli   net: dsa: Add ndo...
318

a3d7e01da   Florian Fainelli   net: dsa: Fix tag...
319
320
  	ret = sysfs_create_group(&dev->dev.kobj, &dsa_group);
  	if (ret)
da7b9e9b0   Florian Fainelli   net: dsa: Add ndo...
321
322
323
  		goto out_err_ndo_teardown;
  
  	return ret;
a3d7e01da   Florian Fainelli   net: dsa: Fix tag...
324

da7b9e9b0   Florian Fainelli   net: dsa: Add ndo...
325
  out_err_ndo_teardown:
9c0c7014f   Florian Fainelli   net: dsa: Setup d...
326
  	dsa_netdev_ops_set(dev, NULL);
da7b9e9b0   Florian Fainelli   net: dsa: Add ndo...
327
  	dsa_master_ethtool_teardown(dev);
c3975400c   Vladimir Oltean   net: dsa: allow d...
328
329
  out_err_reset_promisc:
  	dsa_master_set_promiscuity(dev, -1);
a3d7e01da   Florian Fainelli   net: dsa: Fix tag...
330
  	return ret;
17a22fcfc   Vivien Didelot   net: dsa: setup a...
331
332
333
334
  }
  
  void dsa_master_teardown(struct net_device *dev)
  {
a3d7e01da   Florian Fainelli   net: dsa: Fix tag...
335
  	sysfs_remove_group(&dev->dev.kobj, &dsa_group);
9c0c7014f   Florian Fainelli   net: dsa: Setup d...
336
  	dsa_netdev_ops_set(dev, NULL);
17a22fcfc   Vivien Didelot   net: dsa: setup a...
337
  	dsa_master_ethtool_teardown(dev);
91ba47957   Andrew Lunn   net: dsa: Restore...
338
  	dsa_master_reset_mtu(dev);
c3975400c   Vladimir Oltean   net: dsa: allow d...
339
  	dsa_master_set_promiscuity(dev, -1);
17a22fcfc   Vivien Didelot   net: dsa: setup a...
340
341
342
343
344
345
346
347
348
  
  	dev->dsa_ptr = NULL;
  
  	/* If we used a tagging format that doesn't have an ethertype
  	 * field, make sure that all packets from this point get sent
  	 * without the tag and go through the regular receive path.
  	 */
  	wmb();
  }