Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
98cd1552 FF |
2 | /* |
3 | * Distributed Switch Architecture loopback driver | |
4 | * | |
5 | * Copyright (C) 2016, Florian Fainelli <f.fainelli@gmail.com> | |
98cd1552 FF |
6 | */ |
7 | ||
8 | #include <linux/platform_device.h> | |
9 | #include <linux/netdevice.h> | |
10 | #include <linux/phy.h> | |
11 | #include <linux/phy_fixed.h> | |
12 | #include <linux/export.h> | |
484c0172 | 13 | #include <linux/ethtool.h> |
98cd1552 FF |
14 | #include <linux/workqueue.h> |
15 | #include <linux/module.h> | |
16 | #include <linux/if_bridge.h> | |
6c84a589 | 17 | #include <linux/dsa/loop.h> |
98cd1552 FF |
18 | #include <net/dsa.h> |
19 | ||
20 | #include "dsa_loop.h" | |
21 | ||
484c0172 FF |
22 | static struct dsa_loop_mib_entry dsa_loop_mibs[] = { |
23 | [DSA_LOOP_PHY_READ_OK] = { "phy_read_ok", }, | |
24 | [DSA_LOOP_PHY_READ_ERR] = { "phy_read_err", }, | |
25 | [DSA_LOOP_PHY_WRITE_OK] = { "phy_write_ok", }, | |
26 | [DSA_LOOP_PHY_WRITE_ERR] = { "phy_write_err", }, | |
27 | }; | |
28 | ||
98cd1552 FF |
29 | static struct phy_device *phydevs[PHY_MAX_ADDR]; |
30 | ||
142061eb FF |
31 | enum dsa_loop_devlink_resource_id { |
32 | DSA_LOOP_DEVLINK_PARAM_ID_VTU, | |
33 | }; | |
34 | ||
35 | static u64 dsa_loop_devlink_vtu_get(void *priv) | |
36 | { | |
37 | struct dsa_loop_priv *ps = priv; | |
38 | unsigned int i, count = 0; | |
39 | struct dsa_loop_vlan *vl; | |
40 | ||
41 | for (i = 0; i < ARRAY_SIZE(ps->vlans); i++) { | |
42 | vl = &ps->vlans[i]; | |
43 | if (vl->members) | |
44 | count++; | |
45 | } | |
46 | ||
47 | return count; | |
48 | } | |
49 | ||
50 | static int dsa_loop_setup_devlink_resources(struct dsa_switch *ds) | |
51 | { | |
52 | struct devlink_resource_size_params size_params; | |
53 | struct dsa_loop_priv *ps = ds->priv; | |
54 | int err; | |
55 | ||
56 | devlink_resource_size_params_init(&size_params, ARRAY_SIZE(ps->vlans), | |
57 | ARRAY_SIZE(ps->vlans), | |
58 | 1, DEVLINK_RESOURCE_UNIT_ENTRY); | |
59 | ||
60 | err = dsa_devlink_resource_register(ds, "VTU", ARRAY_SIZE(ps->vlans), | |
61 | DSA_LOOP_DEVLINK_PARAM_ID_VTU, | |
62 | DEVLINK_RESOURCE_ID_PARENT_TOP, | |
63 | &size_params); | |
64 | if (err) | |
65 | goto out; | |
66 | ||
67 | dsa_devlink_resource_occ_get_register(ds, | |
68 | DSA_LOOP_DEVLINK_PARAM_ID_VTU, | |
69 | dsa_loop_devlink_vtu_get, ps); | |
70 | ||
71 | return 0; | |
72 | ||
73 | out: | |
74 | dsa_devlink_resources_unregister(ds); | |
75 | return err; | |
76 | } | |
77 | ||
5ed4e3eb | 78 | static enum dsa_tag_protocol dsa_loop_get_protocol(struct dsa_switch *ds, |
4d776482 FF |
79 | int port, |
80 | enum dsa_tag_protocol mp) | |
98cd1552 | 81 | { |
e52cde71 | 82 | dev_dbg(ds->dev, "%s: port: %d\n", __func__, port); |
98cd1552 FF |
83 | |
84 | return DSA_TAG_PROTO_NONE; | |
85 | } | |
86 | ||
87 | static int dsa_loop_setup(struct dsa_switch *ds) | |
88 | { | |
484c0172 FF |
89 | struct dsa_loop_priv *ps = ds->priv; |
90 | unsigned int i; | |
91 | ||
92 | for (i = 0; i < ds->num_ports; i++) | |
93 | memcpy(ps->ports[i].mib, dsa_loop_mibs, | |
94 | sizeof(dsa_loop_mibs)); | |
95 | ||
98cd1552 FF |
96 | dev_dbg(ds->dev, "%s\n", __func__); |
97 | ||
142061eb FF |
98 | return dsa_loop_setup_devlink_resources(ds); |
99 | } | |
100 | ||
101 | static void dsa_loop_teardown(struct dsa_switch *ds) | |
102 | { | |
103 | dsa_devlink_resources_unregister(ds); | |
98cd1552 FF |
104 | } |
105 | ||
89f09048 | 106 | static int dsa_loop_get_sset_count(struct dsa_switch *ds, int port, int sset) |
484c0172 | 107 | { |
96cbddcd | 108 | if (sset != ETH_SS_STATS && sset != ETH_SS_PHY_STATS) |
89f09048 FF |
109 | return 0; |
110 | ||
484c0172 FF |
111 | return __DSA_LOOP_CNT_MAX; |
112 | } | |
113 | ||
89f09048 FF |
114 | static void dsa_loop_get_strings(struct dsa_switch *ds, int port, |
115 | u32 stringset, uint8_t *data) | |
484c0172 FF |
116 | { |
117 | struct dsa_loop_priv *ps = ds->priv; | |
118 | unsigned int i; | |
119 | ||
96cbddcd | 120 | if (stringset != ETH_SS_STATS && stringset != ETH_SS_PHY_STATS) |
89f09048 FF |
121 | return; |
122 | ||
484c0172 FF |
123 | for (i = 0; i < __DSA_LOOP_CNT_MAX; i++) |
124 | memcpy(data + i * ETH_GSTRING_LEN, | |
125 | ps->ports[port].mib[i].name, ETH_GSTRING_LEN); | |
126 | } | |
127 | ||
128 | static void dsa_loop_get_ethtool_stats(struct dsa_switch *ds, int port, | |
129 | uint64_t *data) | |
130 | { | |
131 | struct dsa_loop_priv *ps = ds->priv; | |
132 | unsigned int i; | |
133 | ||
134 | for (i = 0; i < __DSA_LOOP_CNT_MAX; i++) | |
135 | data[i] = ps->ports[port].mib[i].val; | |
136 | } | |
137 | ||
98cd1552 FF |
138 | static int dsa_loop_phy_read(struct dsa_switch *ds, int port, int regnum) |
139 | { | |
140 | struct dsa_loop_priv *ps = ds->priv; | |
141 | struct mii_bus *bus = ps->bus; | |
484c0172 | 142 | int ret; |
98cd1552 | 143 | |
484c0172 FF |
144 | ret = mdiobus_read_nested(bus, ps->port_base + port, regnum); |
145 | if (ret < 0) | |
146 | ps->ports[port].mib[DSA_LOOP_PHY_READ_ERR].val++; | |
147 | else | |
148 | ps->ports[port].mib[DSA_LOOP_PHY_READ_OK].val++; | |
149 | ||
150 | return ret; | |
98cd1552 FF |
151 | } |
152 | ||
153 | static int dsa_loop_phy_write(struct dsa_switch *ds, int port, | |
154 | int regnum, u16 value) | |
155 | { | |
156 | struct dsa_loop_priv *ps = ds->priv; | |
157 | struct mii_bus *bus = ps->bus; | |
484c0172 | 158 | int ret; |
98cd1552 | 159 | |
484c0172 FF |
160 | ret = mdiobus_write_nested(bus, ps->port_base + port, regnum, value); |
161 | if (ret < 0) | |
162 | ps->ports[port].mib[DSA_LOOP_PHY_WRITE_ERR].val++; | |
163 | else | |
164 | ps->ports[port].mib[DSA_LOOP_PHY_WRITE_OK].val++; | |
165 | ||
166 | return ret; | |
98cd1552 FF |
167 | } |
168 | ||
169 | static int dsa_loop_port_bridge_join(struct dsa_switch *ds, int port, | |
b079922b | 170 | struct dsa_bridge bridge, |
06b9cce4 VO |
171 | bool *tx_fwd_offload, |
172 | struct netlink_ext_ack *extack) | |
98cd1552 | 173 | { |
e52cde71 | 174 | dev_dbg(ds->dev, "%s: port: %d, bridge: %s\n", |
d3eed0e5 | 175 | __func__, port, bridge.dev->name); |
98cd1552 FF |
176 | |
177 | return 0; | |
178 | } | |
179 | ||
180 | static void dsa_loop_port_bridge_leave(struct dsa_switch *ds, int port, | |
d3eed0e5 | 181 | struct dsa_bridge bridge) |
98cd1552 | 182 | { |
e52cde71 | 183 | dev_dbg(ds->dev, "%s: port: %d, bridge: %s\n", |
d3eed0e5 | 184 | __func__, port, bridge.dev->name); |
98cd1552 FF |
185 | } |
186 | ||
187 | static void dsa_loop_port_stp_state_set(struct dsa_switch *ds, int port, | |
188 | u8 state) | |
189 | { | |
e52cde71 FF |
190 | dev_dbg(ds->dev, "%s: port: %d, state: %d\n", |
191 | __func__, port, state); | |
98cd1552 FF |
192 | } |
193 | ||
194 | static int dsa_loop_port_vlan_filtering(struct dsa_switch *ds, int port, | |
89153ed6 VO |
195 | bool vlan_filtering, |
196 | struct netlink_ext_ack *extack) | |
98cd1552 | 197 | { |
e52cde71 FF |
198 | dev_dbg(ds->dev, "%s: port: %d, vlan_filtering: %d\n", |
199 | __func__, port, vlan_filtering); | |
98cd1552 FF |
200 | |
201 | return 0; | |
202 | } | |
203 | ||
1958d581 | 204 | static int dsa_loop_port_vlan_add(struct dsa_switch *ds, int port, |
31046a5f VO |
205 | const struct switchdev_obj_port_vlan *vlan, |
206 | struct netlink_ext_ack *extack) | |
98cd1552 FF |
207 | { |
208 | bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; | |
209 | bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; | |
210 | struct dsa_loop_priv *ps = ds->priv; | |
211 | struct mii_bus *bus = ps->bus; | |
212 | struct dsa_loop_vlan *vl; | |
98cd1552 | 213 | |
646188c9 | 214 | if (vlan->vid >= ARRAY_SIZE(ps->vlans)) |
1958d581 VO |
215 | return -ERANGE; |
216 | ||
98cd1552 FF |
217 | /* Just do a sleeping operation to make lockdep checks effective */ |
218 | mdiobus_read(bus, ps->port_base + port, MII_BMSR); | |
219 | ||
b7a9e0da | 220 | vl = &ps->vlans[vlan->vid]; |
98cd1552 | 221 | |
b7a9e0da VO |
222 | vl->members |= BIT(port); |
223 | if (untagged) | |
224 | vl->untagged |= BIT(port); | |
225 | else | |
226 | vl->untagged &= ~BIT(port); | |
e52cde71 | 227 | |
b7a9e0da VO |
228 | dev_dbg(ds->dev, "%s: port: %d vlan: %d, %stagged, pvid: %d\n", |
229 | __func__, port, vlan->vid, untagged ? "un" : "", pvid); | |
98cd1552 FF |
230 | |
231 | if (pvid) | |
b7a9e0da | 232 | ps->ports[port].pvid = vlan->vid; |
1958d581 VO |
233 | |
234 | return 0; | |
98cd1552 FF |
235 | } |
236 | ||
237 | static int dsa_loop_port_vlan_del(struct dsa_switch *ds, int port, | |
238 | const struct switchdev_obj_port_vlan *vlan) | |
239 | { | |
240 | bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; | |
241 | struct dsa_loop_priv *ps = ds->priv; | |
b7a9e0da | 242 | u16 pvid = ps->ports[port].pvid; |
98cd1552 FF |
243 | struct mii_bus *bus = ps->bus; |
244 | struct dsa_loop_vlan *vl; | |
98cd1552 | 245 | |
98cd1552 FF |
246 | /* Just do a sleeping operation to make lockdep checks effective */ |
247 | mdiobus_read(bus, ps->port_base + port, MII_BMSR); | |
248 | ||
b7a9e0da | 249 | vl = &ps->vlans[vlan->vid]; |
98cd1552 | 250 | |
b7a9e0da VO |
251 | vl->members &= ~BIT(port); |
252 | if (untagged) | |
253 | vl->untagged &= ~BIT(port); | |
98cd1552 | 254 | |
b7a9e0da VO |
255 | if (pvid == vlan->vid) |
256 | pvid = 1; | |
e52cde71 | 257 | |
b7a9e0da VO |
258 | dev_dbg(ds->dev, "%s: port: %d vlan: %d, %stagged, pvid: %d\n", |
259 | __func__, port, vlan->vid, untagged ? "un" : "", pvid); | |
81d4e8e0 | 260 | ps->ports[port].pvid = pvid; |
98cd1552 FF |
261 | |
262 | return 0; | |
263 | } | |
264 | ||
c99194ed FF |
265 | static int dsa_loop_port_change_mtu(struct dsa_switch *ds, int port, |
266 | int new_mtu) | |
267 | { | |
268 | struct dsa_loop_priv *priv = ds->priv; | |
269 | ||
270 | priv->ports[port].mtu = new_mtu; | |
271 | ||
272 | return 0; | |
273 | } | |
274 | ||
275 | static int dsa_loop_port_max_mtu(struct dsa_switch *ds, int port) | |
276 | { | |
277 | return ETH_MAX_MTU; | |
278 | } | |
279 | ||
db2c6d5f RKO |
280 | static void dsa_loop_phylink_get_caps(struct dsa_switch *dsa, int port, |
281 | struct phylink_config *config) | |
282 | { | |
283 | bitmap_fill(config->supported_interfaces, PHY_INTERFACE_MODE_MAX); | |
284 | __clear_bit(PHY_INTERFACE_MODE_NA, config->supported_interfaces); | |
285 | config->mac_capabilities = ~0; | |
286 | } | |
287 | ||
d78d6776 | 288 | static const struct dsa_switch_ops dsa_loop_driver = { |
98cd1552 FF |
289 | .get_tag_protocol = dsa_loop_get_protocol, |
290 | .setup = dsa_loop_setup, | |
142061eb | 291 | .teardown = dsa_loop_teardown, |
484c0172 FF |
292 | .get_strings = dsa_loop_get_strings, |
293 | .get_ethtool_stats = dsa_loop_get_ethtool_stats, | |
294 | .get_sset_count = dsa_loop_get_sset_count, | |
96cbddcd | 295 | .get_ethtool_phy_stats = dsa_loop_get_ethtool_stats, |
98cd1552 FF |
296 | .phy_read = dsa_loop_phy_read, |
297 | .phy_write = dsa_loop_phy_write, | |
298 | .port_bridge_join = dsa_loop_port_bridge_join, | |
299 | .port_bridge_leave = dsa_loop_port_bridge_leave, | |
300 | .port_stp_state_set = dsa_loop_port_stp_state_set, | |
301 | .port_vlan_filtering = dsa_loop_port_vlan_filtering, | |
98cd1552 FF |
302 | .port_vlan_add = dsa_loop_port_vlan_add, |
303 | .port_vlan_del = dsa_loop_port_vlan_del, | |
c99194ed FF |
304 | .port_change_mtu = dsa_loop_port_change_mtu, |
305 | .port_max_mtu = dsa_loop_port_max_mtu, | |
db2c6d5f | 306 | .phylink_get_caps = dsa_loop_phylink_get_caps, |
98cd1552 FF |
307 | }; |
308 | ||
309 | static int dsa_loop_drv_probe(struct mdio_device *mdiodev) | |
310 | { | |
311 | struct dsa_loop_pdata *pdata = mdiodev->dev.platform_data; | |
312 | struct dsa_loop_priv *ps; | |
313 | struct dsa_switch *ds; | |
93165ecb | 314 | int ret; |
98cd1552 FF |
315 | |
316 | if (!pdata) | |
317 | return -ENODEV; | |
318 | ||
7e99e347 | 319 | ds = devm_kzalloc(&mdiodev->dev, sizeof(*ds), GFP_KERNEL); |
98cd1552 FF |
320 | if (!ds) |
321 | return -ENOMEM; | |
322 | ||
7e99e347 | 323 | ds->dev = &mdiodev->dev; |
947b6ef9 | 324 | ds->num_ports = DSA_LOOP_NUM_PORTS; |
7e99e347 | 325 | |
98cd1552 | 326 | ps = devm_kzalloc(&mdiodev->dev, sizeof(*ps), GFP_KERNEL); |
8ce7aaaa CJ |
327 | if (!ps) |
328 | return -ENOMEM; | |
329 | ||
98cd1552 FF |
330 | ps->netdev = dev_get_by_name(&init_net, pdata->netdev); |
331 | if (!ps->netdev) | |
332 | return -EPROBE_DEFER; | |
333 | ||
334 | pdata->cd.netdev[DSA_LOOP_CPU_PORT] = &ps->netdev->dev; | |
335 | ||
336 | ds->dev = &mdiodev->dev; | |
337 | ds->ops = &dsa_loop_driver; | |
338 | ds->priv = ps; | |
339 | ps->bus = mdiodev->bus; | |
340 | ||
341 | dev_set_drvdata(&mdiodev->dev, ds); | |
342 | ||
93165ecb FF |
343 | ret = dsa_register_switch(ds); |
344 | if (!ret) | |
345 | dev_info(&mdiodev->dev, "%s: 0x%0x\n", | |
346 | pdata->name, pdata->enabled_ports); | |
347 | ||
348 | return ret; | |
98cd1552 FF |
349 | } |
350 | ||
351 | static void dsa_loop_drv_remove(struct mdio_device *mdiodev) | |
352 | { | |
353 | struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev); | |
0650bf52 VO |
354 | struct dsa_loop_priv *ps; |
355 | ||
356 | if (!ds) | |
357 | return; | |
358 | ||
359 | ps = ds->priv; | |
98cd1552 FF |
360 | |
361 | dsa_unregister_switch(ds); | |
362 | dev_put(ps->netdev); | |
0650bf52 VO |
363 | } |
364 | ||
365 | static void dsa_loop_drv_shutdown(struct mdio_device *mdiodev) | |
366 | { | |
367 | struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev); | |
368 | ||
369 | if (!ds) | |
370 | return; | |
371 | ||
372 | dsa_switch_shutdown(ds); | |
373 | ||
374 | dev_set_drvdata(&mdiodev->dev, NULL); | |
98cd1552 FF |
375 | } |
376 | ||
377 | static struct mdio_driver dsa_loop_drv = { | |
378 | .mdiodrv.driver = { | |
379 | .name = "dsa-loop", | |
380 | }, | |
381 | .probe = dsa_loop_drv_probe, | |
382 | .remove = dsa_loop_drv_remove, | |
0650bf52 | 383 | .shutdown = dsa_loop_drv_shutdown, |
98cd1552 FF |
384 | }; |
385 | ||
386 | #define NUM_FIXED_PHYS (DSA_LOOP_NUM_PORTS - 2) | |
387 | ||
633efc8b CZ |
388 | static void dsa_loop_phydevs_unregister(void) |
389 | { | |
390 | unsigned int i; | |
391 | ||
392 | for (i = 0; i < NUM_FIXED_PHYS; i++) | |
393 | if (!IS_ERR(phydevs[i])) { | |
394 | fixed_phy_unregister(phydevs[i]); | |
395 | phy_device_free(phydevs[i]); | |
396 | } | |
397 | } | |
398 | ||
98cd1552 FF |
399 | static int __init dsa_loop_init(void) |
400 | { | |
401 | struct fixed_phy_status status = { | |
402 | .link = 1, | |
403 | .speed = SPEED_100, | |
404 | .duplex = DUPLEX_FULL, | |
405 | }; | |
633efc8b | 406 | unsigned int i, ret; |
98cd1552 FF |
407 | |
408 | for (i = 0; i < NUM_FIXED_PHYS; i++) | |
5468e82f | 409 | phydevs[i] = fixed_phy_register(PHY_POLL, &status, NULL); |
98cd1552 | 410 | |
633efc8b CZ |
411 | ret = mdio_driver_register(&dsa_loop_drv); |
412 | if (ret) | |
413 | dsa_loop_phydevs_unregister(); | |
414 | ||
415 | return ret; | |
98cd1552 FF |
416 | } |
417 | module_init(dsa_loop_init); | |
418 | ||
419 | static void __exit dsa_loop_exit(void) | |
420 | { | |
421 | mdio_driver_unregister(&dsa_loop_drv); | |
633efc8b | 422 | dsa_loop_phydevs_unregister(); |
98cd1552 FF |
423 | } |
424 | module_exit(dsa_loop_exit); | |
425 | ||
3047211c | 426 | MODULE_SOFTDEP("pre: dsa_loop_bdinfo"); |
98cd1552 FF |
427 | MODULE_LICENSE("GPL"); |
428 | MODULE_AUTHOR("Florian Fainelli"); | |
429 | MODULE_DESCRIPTION("DSA loopback driver"); |