| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Distributed Switch Architecture loopback driver |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * Copyright (C) 2016, Florian Fainelli <f.fainelli@gmail.com> |
|---|
| 5 | | - * |
|---|
| 6 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 7 | | - * it under the terms of the GNU General Public License as published by |
|---|
| 8 | | - * the Free Software Foundation; either version 2 of the License, or |
|---|
| 9 | | - * (at your option) any later version. |
|---|
| 10 | 6 | */ |
|---|
| 11 | 7 | |
|---|
| 12 | 8 | #include <linux/platform_device.h> |
|---|
| .. | .. |
|---|
| 18 | 14 | #include <linux/workqueue.h> |
|---|
| 19 | 15 | #include <linux/module.h> |
|---|
| 20 | 16 | #include <linux/if_bridge.h> |
|---|
| 17 | +#include <linux/dsa/loop.h> |
|---|
| 21 | 18 | #include <net/dsa.h> |
|---|
| 22 | 19 | |
|---|
| 23 | 20 | #include "dsa_loop.h" |
|---|
| 24 | | - |
|---|
| 25 | | -struct dsa_loop_vlan { |
|---|
| 26 | | - u16 members; |
|---|
| 27 | | - u16 untagged; |
|---|
| 28 | | -}; |
|---|
| 29 | | - |
|---|
| 30 | | -struct dsa_loop_mib_entry { |
|---|
| 31 | | - char name[ETH_GSTRING_LEN]; |
|---|
| 32 | | - unsigned long val; |
|---|
| 33 | | -}; |
|---|
| 34 | | - |
|---|
| 35 | | -enum dsa_loop_mib_counters { |
|---|
| 36 | | - DSA_LOOP_PHY_READ_OK, |
|---|
| 37 | | - DSA_LOOP_PHY_READ_ERR, |
|---|
| 38 | | - DSA_LOOP_PHY_WRITE_OK, |
|---|
| 39 | | - DSA_LOOP_PHY_WRITE_ERR, |
|---|
| 40 | | - __DSA_LOOP_CNT_MAX, |
|---|
| 41 | | -}; |
|---|
| 42 | 21 | |
|---|
| 43 | 22 | static struct dsa_loop_mib_entry dsa_loop_mibs[] = { |
|---|
| 44 | 23 | [DSA_LOOP_PHY_READ_OK] = { "phy_read_ok", }, |
|---|
| .. | .. |
|---|
| 47 | 26 | [DSA_LOOP_PHY_WRITE_ERR] = { "phy_write_err", }, |
|---|
| 48 | 27 | }; |
|---|
| 49 | 28 | |
|---|
| 50 | | -struct dsa_loop_port { |
|---|
| 51 | | - struct dsa_loop_mib_entry mib[__DSA_LOOP_CNT_MAX]; |
|---|
| 52 | | -}; |
|---|
| 53 | | - |
|---|
| 54 | | -#define DSA_LOOP_VLANS 5 |
|---|
| 55 | | - |
|---|
| 56 | | -struct dsa_loop_priv { |
|---|
| 57 | | - struct mii_bus *bus; |
|---|
| 58 | | - unsigned int port_base; |
|---|
| 59 | | - struct dsa_loop_vlan vlans[DSA_LOOP_VLANS]; |
|---|
| 60 | | - struct net_device *netdev; |
|---|
| 61 | | - struct dsa_loop_port ports[DSA_MAX_PORTS]; |
|---|
| 62 | | - u16 pvid; |
|---|
| 63 | | -}; |
|---|
| 64 | | - |
|---|
| 65 | 29 | static struct phy_device *phydevs[PHY_MAX_ADDR]; |
|---|
| 66 | 30 | |
|---|
| 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 | + |
|---|
| 67 | 78 | static enum dsa_tag_protocol dsa_loop_get_protocol(struct dsa_switch *ds, |
|---|
| 68 | | - int port) |
|---|
| 79 | + int port, |
|---|
| 80 | + enum dsa_tag_protocol mp) |
|---|
| 69 | 81 | { |
|---|
| 70 | 82 | dev_dbg(ds->dev, "%s: port: %d\n", __func__, port); |
|---|
| 71 | 83 | |
|---|
| .. | .. |
|---|
| 83 | 95 | |
|---|
| 84 | 96 | dev_dbg(ds->dev, "%s\n", __func__); |
|---|
| 85 | 97 | |
|---|
| 86 | | - return 0; |
|---|
| 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); |
|---|
| 87 | 104 | } |
|---|
| 88 | 105 | |
|---|
| 89 | 106 | static int dsa_loop_get_sset_count(struct dsa_switch *ds, int port, int sset) |
|---|
| .. | .. |
|---|
| 173 | 190 | } |
|---|
| 174 | 191 | |
|---|
| 175 | 192 | static int dsa_loop_port_vlan_filtering(struct dsa_switch *ds, int port, |
|---|
| 176 | | - bool vlan_filtering) |
|---|
| 193 | + bool vlan_filtering, |
|---|
| 194 | + struct switchdev_trans *trans) |
|---|
| 177 | 195 | { |
|---|
| 178 | 196 | dev_dbg(ds->dev, "%s: port: %d, vlan_filtering: %d\n", |
|---|
| 179 | 197 | __func__, port, vlan_filtering); |
|---|
| .. | .. |
|---|
| 194 | 212 | /* Just do a sleeping operation to make lockdep checks effective */ |
|---|
| 195 | 213 | mdiobus_read(bus, ps->port_base + port, MII_BMSR); |
|---|
| 196 | 214 | |
|---|
| 197 | | - if (vlan->vid_end > DSA_LOOP_VLANS) |
|---|
| 215 | + if (vlan->vid_end > ARRAY_SIZE(ps->vlans)) |
|---|
| 198 | 216 | return -ERANGE; |
|---|
| 199 | 217 | |
|---|
| 200 | 218 | return 0; |
|---|
| .. | .. |
|---|
| 227 | 245 | } |
|---|
| 228 | 246 | |
|---|
| 229 | 247 | if (pvid) |
|---|
| 230 | | - ps->pvid = vid; |
|---|
| 248 | + ps->ports[port].pvid = vid; |
|---|
| 231 | 249 | } |
|---|
| 232 | 250 | |
|---|
| 233 | 251 | static int dsa_loop_port_vlan_del(struct dsa_switch *ds, int port, |
|---|
| .. | .. |
|---|
| 237 | 255 | struct dsa_loop_priv *ps = ds->priv; |
|---|
| 238 | 256 | struct mii_bus *bus = ps->bus; |
|---|
| 239 | 257 | struct dsa_loop_vlan *vl; |
|---|
| 240 | | - u16 vid, pvid = ps->pvid; |
|---|
| 258 | + u16 vid, pvid = ps->ports[port].pvid; |
|---|
| 241 | 259 | |
|---|
| 242 | 260 | /* Just do a sleeping operation to make lockdep checks effective */ |
|---|
| 243 | 261 | mdiobus_read(bus, ps->port_base + port, MII_BMSR); |
|---|
| .. | .. |
|---|
| 255 | 273 | dev_dbg(ds->dev, "%s: port: %d vlan: %d, %stagged, pvid: %d\n", |
|---|
| 256 | 274 | __func__, port, vid, untagged ? "un" : "", pvid); |
|---|
| 257 | 275 | } |
|---|
| 258 | | - ps->pvid = pvid; |
|---|
| 276 | + ps->ports[port].pvid = pvid; |
|---|
| 259 | 277 | |
|---|
| 260 | 278 | return 0; |
|---|
| 279 | +} |
|---|
| 280 | + |
|---|
| 281 | +static int dsa_loop_port_change_mtu(struct dsa_switch *ds, int port, |
|---|
| 282 | + int new_mtu) |
|---|
| 283 | +{ |
|---|
| 284 | + struct dsa_loop_priv *priv = ds->priv; |
|---|
| 285 | + |
|---|
| 286 | + priv->ports[port].mtu = new_mtu; |
|---|
| 287 | + |
|---|
| 288 | + return 0; |
|---|
| 289 | +} |
|---|
| 290 | + |
|---|
| 291 | +static int dsa_loop_port_max_mtu(struct dsa_switch *ds, int port) |
|---|
| 292 | +{ |
|---|
| 293 | + return ETH_MAX_MTU; |
|---|
| 261 | 294 | } |
|---|
| 262 | 295 | |
|---|
| 263 | 296 | static const struct dsa_switch_ops dsa_loop_driver = { |
|---|
| 264 | 297 | .get_tag_protocol = dsa_loop_get_protocol, |
|---|
| 265 | 298 | .setup = dsa_loop_setup, |
|---|
| 299 | + .teardown = dsa_loop_teardown, |
|---|
| 266 | 300 | .get_strings = dsa_loop_get_strings, |
|---|
| 267 | 301 | .get_ethtool_stats = dsa_loop_get_ethtool_stats, |
|---|
| 268 | 302 | .get_sset_count = dsa_loop_get_sset_count, |
|---|
| .. | .. |
|---|
| 276 | 310 | .port_vlan_prepare = dsa_loop_port_vlan_prepare, |
|---|
| 277 | 311 | .port_vlan_add = dsa_loop_port_vlan_add, |
|---|
| 278 | 312 | .port_vlan_del = dsa_loop_port_vlan_del, |
|---|
| 313 | + .port_change_mtu = dsa_loop_port_change_mtu, |
|---|
| 314 | + .port_max_mtu = dsa_loop_port_max_mtu, |
|---|
| 279 | 315 | }; |
|---|
| 280 | 316 | |
|---|
| 281 | 317 | static int dsa_loop_drv_probe(struct mdio_device *mdiodev) |
|---|
| .. | .. |
|---|
| 283 | 319 | struct dsa_loop_pdata *pdata = mdiodev->dev.platform_data; |
|---|
| 284 | 320 | struct dsa_loop_priv *ps; |
|---|
| 285 | 321 | struct dsa_switch *ds; |
|---|
| 322 | + int ret; |
|---|
| 286 | 323 | |
|---|
| 287 | 324 | if (!pdata) |
|---|
| 288 | 325 | return -ENODEV; |
|---|
| 289 | 326 | |
|---|
| 290 | | - dev_info(&mdiodev->dev, "%s: 0x%0x\n", |
|---|
| 291 | | - pdata->name, pdata->enabled_ports); |
|---|
| 292 | | - |
|---|
| 293 | | - ds = dsa_switch_alloc(&mdiodev->dev, DSA_MAX_PORTS); |
|---|
| 327 | + ds = devm_kzalloc(&mdiodev->dev, sizeof(*ds), GFP_KERNEL); |
|---|
| 294 | 328 | if (!ds) |
|---|
| 295 | 329 | return -ENOMEM; |
|---|
| 330 | + |
|---|
| 331 | + ds->dev = &mdiodev->dev; |
|---|
| 332 | + ds->num_ports = DSA_LOOP_NUM_PORTS; |
|---|
| 296 | 333 | |
|---|
| 297 | 334 | ps = devm_kzalloc(&mdiodev->dev, sizeof(*ps), GFP_KERNEL); |
|---|
| 298 | 335 | if (!ps) |
|---|
| .. | .. |
|---|
| 307 | 344 | ds->dev = &mdiodev->dev; |
|---|
| 308 | 345 | ds->ops = &dsa_loop_driver; |
|---|
| 309 | 346 | ds->priv = ps; |
|---|
| 347 | + ds->configure_vlan_while_not_filtering = true; |
|---|
| 310 | 348 | ps->bus = mdiodev->bus; |
|---|
| 311 | 349 | |
|---|
| 312 | 350 | dev_set_drvdata(&mdiodev->dev, ds); |
|---|
| 313 | 351 | |
|---|
| 314 | | - return dsa_register_switch(ds); |
|---|
| 352 | + ret = dsa_register_switch(ds); |
|---|
| 353 | + if (!ret) |
|---|
| 354 | + dev_info(&mdiodev->dev, "%s: 0x%0x\n", |
|---|
| 355 | + pdata->name, pdata->enabled_ports); |
|---|
| 356 | + |
|---|
| 357 | + return ret; |
|---|
| 315 | 358 | } |
|---|
| 316 | 359 | |
|---|
| 317 | 360 | static void dsa_loop_drv_remove(struct mdio_device *mdiodev) |
|---|
| .. | .. |
|---|
| 333 | 376 | |
|---|
| 334 | 377 | #define NUM_FIXED_PHYS (DSA_LOOP_NUM_PORTS - 2) |
|---|
| 335 | 378 | |
|---|
| 379 | +static void dsa_loop_phydevs_unregister(void) |
|---|
| 380 | +{ |
|---|
| 381 | + unsigned int i; |
|---|
| 382 | + |
|---|
| 383 | + for (i = 0; i < NUM_FIXED_PHYS; i++) |
|---|
| 384 | + if (!IS_ERR(phydevs[i])) { |
|---|
| 385 | + fixed_phy_unregister(phydevs[i]); |
|---|
| 386 | + phy_device_free(phydevs[i]); |
|---|
| 387 | + } |
|---|
| 388 | +} |
|---|
| 389 | + |
|---|
| 336 | 390 | static int __init dsa_loop_init(void) |
|---|
| 337 | 391 | { |
|---|
| 338 | 392 | struct fixed_phy_status status = { |
|---|
| .. | .. |
|---|
| 340 | 394 | .speed = SPEED_100, |
|---|
| 341 | 395 | .duplex = DUPLEX_FULL, |
|---|
| 342 | 396 | }; |
|---|
| 343 | | - unsigned int i; |
|---|
| 397 | + unsigned int i, ret; |
|---|
| 344 | 398 | |
|---|
| 345 | 399 | for (i = 0; i < NUM_FIXED_PHYS; i++) |
|---|
| 346 | | - phydevs[i] = fixed_phy_register(PHY_POLL, &status, -1, NULL); |
|---|
| 400 | + phydevs[i] = fixed_phy_register(PHY_POLL, &status, NULL); |
|---|
| 347 | 401 | |
|---|
| 348 | | - return mdio_driver_register(&dsa_loop_drv); |
|---|
| 402 | + ret = mdio_driver_register(&dsa_loop_drv); |
|---|
| 403 | + if (ret) |
|---|
| 404 | + dsa_loop_phydevs_unregister(); |
|---|
| 405 | + |
|---|
| 406 | + return ret; |
|---|
| 349 | 407 | } |
|---|
| 350 | 408 | module_init(dsa_loop_init); |
|---|
| 351 | 409 | |
|---|
| 352 | 410 | static void __exit dsa_loop_exit(void) |
|---|
| 353 | 411 | { |
|---|
| 354 | | - unsigned int i; |
|---|
| 355 | | - |
|---|
| 356 | 412 | mdio_driver_unregister(&dsa_loop_drv); |
|---|
| 357 | | - for (i = 0; i < NUM_FIXED_PHYS; i++) |
|---|
| 358 | | - if (!IS_ERR(phydevs[i])) |
|---|
| 359 | | - fixed_phy_unregister(phydevs[i]); |
|---|
| 413 | + dsa_loop_phydevs_unregister(); |
|---|
| 360 | 414 | } |
|---|
| 361 | 415 | module_exit(dsa_loop_exit); |
|---|
| 362 | 416 | |
|---|