summaryrefslogtreecommitdiff
path: root/drivers/net/dsa/dsa_loop.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/dsa/dsa_loop.c')
-rw-r--r--drivers/net/dsa/dsa_loop.c390
1 files changed, 233 insertions, 157 deletions
diff --git a/drivers/net/dsa/dsa_loop.c b/drivers/net/dsa/dsa_loop.c
index fdd8f3872102..4a416f2717ba 100644
--- a/drivers/net/dsa/dsa_loop.c
+++ b/drivers/net/dsa/dsa_loop.c
@@ -1,12 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Distributed Switch Architecture loopback driver
*
* Copyright (C) 2016, Florian Fainelli <f.fainelli@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
*/
#include <linux/platform_device.h>
@@ -18,26 +14,21 @@
#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/if_bridge.h>
+#include <linux/dsa/loop.h>
#include <net/dsa.h>
-#include "dsa_loop.h"
-
-struct dsa_loop_vlan {
- u16 members;
- u16 untagged;
-};
-
-struct dsa_loop_mib_entry {
- char name[ETH_GSTRING_LEN];
- unsigned long val;
-};
-
-enum dsa_loop_mib_counters {
- DSA_LOOP_PHY_READ_OK,
- DSA_LOOP_PHY_READ_ERR,
- DSA_LOOP_PHY_WRITE_OK,
- DSA_LOOP_PHY_WRITE_ERR,
- __DSA_LOOP_CNT_MAX,
+#define DSA_LOOP_NUM_PORTS 6
+#define DSA_LOOP_CPU_PORT (DSA_LOOP_NUM_PORTS - 1)
+#define NUM_FIXED_PHYS (DSA_LOOP_NUM_PORTS - 2)
+
+struct dsa_loop_pdata {
+ /* Must be first, such that dsa_register_switch() can access this
+ * without gory pointer manipulations
+ */
+ struct dsa_chip_data cd;
+ const char *name;
+ unsigned int enabled_ports;
+ const char *netdev;
};
static struct dsa_loop_mib_entry dsa_loop_mibs[] = {
@@ -47,26 +38,61 @@ static struct dsa_loop_mib_entry dsa_loop_mibs[] = {
[DSA_LOOP_PHY_WRITE_ERR] = { "phy_write_err", },
};
-struct dsa_loop_port {
- struct dsa_loop_mib_entry mib[__DSA_LOOP_CNT_MAX];
+static struct phy_device *phydevs[PHY_MAX_ADDR];
+static struct mdio_device *switch_mdiodev;
+
+enum dsa_loop_devlink_resource_id {
+ DSA_LOOP_DEVLINK_PARAM_ID_VTU,
};
-#define DSA_LOOP_VLANS 5
+static u64 dsa_loop_devlink_vtu_get(void *priv)
+{
+ struct dsa_loop_priv *ps = priv;
+ unsigned int i, count = 0;
+ struct dsa_loop_vlan *vl;
-struct dsa_loop_priv {
- struct mii_bus *bus;
- unsigned int port_base;
- struct dsa_loop_vlan vlans[DSA_LOOP_VLANS];
- struct net_device *netdev;
- struct dsa_loop_port ports[DSA_MAX_PORTS];
- u16 pvid;
-};
+ for (i = 0; i < ARRAY_SIZE(ps->vlans); i++) {
+ vl = &ps->vlans[i];
+ if (vl->members)
+ count++;
+ }
-static struct phy_device *phydevs[PHY_MAX_ADDR];
+ return count;
+}
-static enum dsa_tag_protocol dsa_loop_get_protocol(struct dsa_switch *ds)
+static int dsa_loop_setup_devlink_resources(struct dsa_switch *ds)
{
- dev_dbg(ds->dev, "%s\n", __func__);
+ struct devlink_resource_size_params size_params;
+ struct dsa_loop_priv *ps = ds->priv;
+ int err;
+
+ devlink_resource_size_params_init(&size_params, ARRAY_SIZE(ps->vlans),
+ ARRAY_SIZE(ps->vlans),
+ 1, DEVLINK_RESOURCE_UNIT_ENTRY);
+
+ err = dsa_devlink_resource_register(ds, "VTU", ARRAY_SIZE(ps->vlans),
+ DSA_LOOP_DEVLINK_PARAM_ID_VTU,
+ DEVLINK_RESOURCE_ID_PARENT_TOP,
+ &size_params);
+ if (err)
+ goto out;
+
+ dsa_devlink_resource_occ_get_register(ds,
+ DSA_LOOP_DEVLINK_PARAM_ID_VTU,
+ dsa_loop_devlink_vtu_get, ps);
+
+ return 0;
+
+out:
+ dsa_devlink_resources_unregister(ds);
+ return err;
+}
+
+static enum dsa_tag_protocol dsa_loop_get_protocol(struct dsa_switch *ds,
+ int port,
+ enum dsa_tag_protocol mp)
+{
+ dev_dbg(ds->dev, "%s: port: %d\n", __func__, port);
return DSA_TAG_PROTO_NONE;
}
@@ -82,22 +108,33 @@ static int dsa_loop_setup(struct dsa_switch *ds)
dev_dbg(ds->dev, "%s\n", __func__);
- return 0;
+ return dsa_loop_setup_devlink_resources(ds);
+}
+
+static void dsa_loop_teardown(struct dsa_switch *ds)
+{
+ dsa_devlink_resources_unregister(ds);
}
-static int dsa_loop_get_sset_count(struct dsa_switch *ds)
+static int dsa_loop_get_sset_count(struct dsa_switch *ds, int port, int sset)
{
+ if (sset != ETH_SS_STATS && sset != ETH_SS_PHY_STATS)
+ return 0;
+
return __DSA_LOOP_CNT_MAX;
}
-static void dsa_loop_get_strings(struct dsa_switch *ds, int port, uint8_t *data)
+static void dsa_loop_get_strings(struct dsa_switch *ds, int port,
+ u32 stringset, uint8_t *data)
{
struct dsa_loop_priv *ps = ds->priv;
unsigned int i;
+ if (stringset != ETH_SS_STATS && stringset != ETH_SS_PHY_STATS)
+ return;
+
for (i = 0; i < __DSA_LOOP_CNT_MAX; i++)
- memcpy(data + i * ETH_GSTRING_LEN,
- ps->ports[port].mib[i].name, ETH_GSTRING_LEN);
+ ethtool_puts(&data, ps->ports[port].mib[i].name);
}
static void dsa_loop_get_ethtool_stats(struct dsa_switch *ds, int port,
@@ -110,21 +147,12 @@ static void dsa_loop_get_ethtool_stats(struct dsa_switch *ds, int port,
data[i] = ps->ports[port].mib[i].val;
}
-static int dsa_loop_set_addr(struct dsa_switch *ds, u8 *addr)
-{
- dev_dbg(ds->dev, "%s\n", __func__);
-
- return 0;
-}
-
static int dsa_loop_phy_read(struct dsa_switch *ds, int port, int regnum)
{
struct dsa_loop_priv *ps = ds->priv;
struct mii_bus *bus = ps->bus;
int ret;
- dev_dbg(ds->dev, "%s\n", __func__);
-
ret = mdiobus_read_nested(bus, ps->port_base + port, regnum);
if (ret < 0)
ps->ports[port].mib[DSA_LOOP_PHY_READ_ERR].val++;
@@ -141,8 +169,6 @@ static int dsa_loop_phy_write(struct dsa_switch *ds, int port,
struct mii_bus *bus = ps->bus;
int ret;
- dev_dbg(ds->dev, "%s\n", __func__);
-
ret = mdiobus_write_nested(bus, ps->port_base + port, regnum, value);
if (ret < 0)
ps->ports[port].mib[DSA_LOOP_PHY_WRITE_ERR].val++;
@@ -153,79 +179,71 @@ static int dsa_loop_phy_write(struct dsa_switch *ds, int port,
}
static int dsa_loop_port_bridge_join(struct dsa_switch *ds, int port,
- struct net_device *bridge)
+ struct dsa_bridge bridge,
+ bool *tx_fwd_offload,
+ struct netlink_ext_ack *extack)
{
- dev_dbg(ds->dev, "%s\n", __func__);
+ dev_dbg(ds->dev, "%s: port: %d, bridge: %s\n",
+ __func__, port, bridge.dev->name);
return 0;
}
static void dsa_loop_port_bridge_leave(struct dsa_switch *ds, int port,
- struct net_device *bridge)
+ struct dsa_bridge bridge)
{
- dev_dbg(ds->dev, "%s\n", __func__);
+ dev_dbg(ds->dev, "%s: port: %d, bridge: %s\n",
+ __func__, port, bridge.dev->name);
}
static void dsa_loop_port_stp_state_set(struct dsa_switch *ds, int port,
u8 state)
{
- dev_dbg(ds->dev, "%s\n", __func__);
+ dev_dbg(ds->dev, "%s: port: %d, state: %d\n",
+ __func__, port, state);
}
static int dsa_loop_port_vlan_filtering(struct dsa_switch *ds, int port,
- bool vlan_filtering)
-{
- dev_dbg(ds->dev, "%s\n", __func__);
-
- return 0;
-}
-
-static int dsa_loop_port_vlan_prepare(struct dsa_switch *ds, int port,
- const struct switchdev_obj_port_vlan *vlan,
- struct switchdev_trans *trans)
+ bool vlan_filtering,
+ struct netlink_ext_ack *extack)
{
- struct dsa_loop_priv *ps = ds->priv;
- struct mii_bus *bus = ps->bus;
-
- dev_dbg(ds->dev, "%s\n", __func__);
-
- /* Just do a sleeping operation to make lockdep checks effective */
- mdiobus_read(bus, ps->port_base + port, MII_BMSR);
-
- if (vlan->vid_end > DSA_LOOP_VLANS)
- return -ERANGE;
+ dev_dbg(ds->dev, "%s: port: %d, vlan_filtering: %d\n",
+ __func__, port, vlan_filtering);
return 0;
}
-static void dsa_loop_port_vlan_add(struct dsa_switch *ds, int port,
- const struct switchdev_obj_port_vlan *vlan,
- struct switchdev_trans *trans)
+static int dsa_loop_port_vlan_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct netlink_ext_ack *extack)
{
bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
struct dsa_loop_priv *ps = ds->priv;
struct mii_bus *bus = ps->bus;
struct dsa_loop_vlan *vl;
- u16 vid;
- dev_dbg(ds->dev, "%s\n", __func__);
+ if (vlan->vid >= ARRAY_SIZE(ps->vlans))
+ return -ERANGE;
/* Just do a sleeping operation to make lockdep checks effective */
mdiobus_read(bus, ps->port_base + port, MII_BMSR);
- for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) {
- vl = &ps->vlans[vid];
+ vl = &ps->vlans[vlan->vid];
- vl->members |= BIT(port);
- if (untagged)
- vl->untagged |= BIT(port);
- else
- vl->untagged &= ~BIT(port);
- }
+ vl->members |= BIT(port);
+ if (untagged)
+ vl->untagged |= BIT(port);
+ else
+ vl->untagged &= ~BIT(port);
+
+ dev_dbg(ds->dev, "%s: port: %d vlan: %d, %stagged, pvid: %d\n",
+ __func__, port, vlan->vid, untagged ? "un" : "", pvid);
if (pvid)
- ps->pvid = vid;
+ ps->ports[port].pvid = vlan->vid;
+
+ return 0;
}
static int dsa_loop_port_vlan_del(struct dsa_switch *ds, int port,
@@ -233,84 +251,71 @@ static int dsa_loop_port_vlan_del(struct dsa_switch *ds, int port,
{
bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
struct dsa_loop_priv *ps = ds->priv;
+ u16 pvid = ps->ports[port].pvid;
struct mii_bus *bus = ps->bus;
struct dsa_loop_vlan *vl;
- u16 vid, pvid = ps->pvid;
-
- dev_dbg(ds->dev, "%s\n", __func__);
/* Just do a sleeping operation to make lockdep checks effective */
mdiobus_read(bus, ps->port_base + port, MII_BMSR);
- for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) {
- vl = &ps->vlans[vid];
+ vl = &ps->vlans[vlan->vid];
- vl->members &= ~BIT(port);
- if (untagged)
- vl->untagged &= ~BIT(port);
+ vl->members &= ~BIT(port);
+ if (untagged)
+ vl->untagged &= ~BIT(port);
- if (pvid == vid)
- pvid = 1;
- }
- ps->pvid = pvid;
+ if (pvid == vlan->vid)
+ pvid = 1;
+
+ dev_dbg(ds->dev, "%s: port: %d vlan: %d, %stagged, pvid: %d\n",
+ __func__, port, vlan->vid, untagged ? "un" : "", pvid);
+ ps->ports[port].pvid = pvid;
return 0;
}
-static int dsa_loop_port_vlan_dump(struct dsa_switch *ds, int port,
- struct switchdev_obj_port_vlan *vlan,
- switchdev_obj_dump_cb_t *cb)
+static int dsa_loop_port_change_mtu(struct dsa_switch *ds, int port,
+ int new_mtu)
{
- struct dsa_loop_priv *ps = ds->priv;
- struct mii_bus *bus = ps->bus;
- struct dsa_loop_vlan *vl;
- u16 vid, vid_start = 0;
- int err = 0;
+ struct dsa_loop_priv *priv = ds->priv;
- dev_dbg(ds->dev, "%s\n", __func__);
-
- /* Just do a sleeping operation to make lockdep checks effective */
- mdiobus_read(bus, ps->port_base + port, MII_BMSR);
+ priv->ports[port].mtu = new_mtu;
- for (vid = vid_start; vid < DSA_LOOP_VLANS; vid++) {
- vl = &ps->vlans[vid];
-
- if (!(vl->members & BIT(port)))
- continue;
-
- vlan->vid_begin = vlan->vid_end = vid;
- vlan->flags = 0;
-
- if (vl->untagged & BIT(port))
- vlan->flags |= BRIDGE_VLAN_INFO_UNTAGGED;
- if (ps->pvid == vid)
- vlan->flags |= BRIDGE_VLAN_INFO_PVID;
+ return 0;
+}
- err = cb(&vlan->obj);
- if (err)
- break;
- }
+static int dsa_loop_port_max_mtu(struct dsa_switch *ds, int port)
+{
+ return ETH_MAX_MTU;
+}
- return err;
+static void dsa_loop_phylink_get_caps(struct dsa_switch *dsa, int port,
+ struct phylink_config *config)
+{
+ bitmap_fill(config->supported_interfaces, PHY_INTERFACE_MODE_MAX);
+ __clear_bit(PHY_INTERFACE_MODE_NA, config->supported_interfaces);
+ config->mac_capabilities = ~0;
}
-static struct dsa_switch_ops dsa_loop_driver = {
+static const struct dsa_switch_ops dsa_loop_driver = {
.get_tag_protocol = dsa_loop_get_protocol,
.setup = dsa_loop_setup,
+ .teardown = dsa_loop_teardown,
.get_strings = dsa_loop_get_strings,
.get_ethtool_stats = dsa_loop_get_ethtool_stats,
.get_sset_count = dsa_loop_get_sset_count,
- .set_addr = dsa_loop_set_addr,
+ .get_ethtool_phy_stats = dsa_loop_get_ethtool_stats,
.phy_read = dsa_loop_phy_read,
.phy_write = dsa_loop_phy_write,
.port_bridge_join = dsa_loop_port_bridge_join,
.port_bridge_leave = dsa_loop_port_bridge_leave,
.port_stp_state_set = dsa_loop_port_stp_state_set,
.port_vlan_filtering = dsa_loop_port_vlan_filtering,
- .port_vlan_prepare = dsa_loop_port_vlan_prepare,
.port_vlan_add = dsa_loop_port_vlan_add,
.port_vlan_del = dsa_loop_port_vlan_del,
- .port_vlan_dump = dsa_loop_port_vlan_dump,
+ .port_change_mtu = dsa_loop_port_change_mtu,
+ .port_max_mtu = dsa_loop_port_max_mtu,
+ .phylink_get_caps = dsa_loop_phylink_get_caps,
};
static int dsa_loop_drv_probe(struct mdio_device *mdiodev)
@@ -318,17 +323,18 @@ static int dsa_loop_drv_probe(struct mdio_device *mdiodev)
struct dsa_loop_pdata *pdata = mdiodev->dev.platform_data;
struct dsa_loop_priv *ps;
struct dsa_switch *ds;
+ int ret;
if (!pdata)
return -ENODEV;
- dev_info(&mdiodev->dev, "%s: 0x%0x\n",
- pdata->name, pdata->enabled_ports);
-
- ds = dsa_switch_alloc(&mdiodev->dev, DSA_MAX_PORTS);
+ ds = devm_kzalloc(&mdiodev->dev, sizeof(*ds), GFP_KERNEL);
if (!ds)
return -ENOMEM;
+ ds->dev = &mdiodev->dev;
+ ds->num_ports = DSA_LOOP_NUM_PORTS;
+
ps = devm_kzalloc(&mdiodev->dev, sizeof(*ps), GFP_KERNEL);
if (!ps)
return -ENOMEM;
@@ -346,52 +352,122 @@ static int dsa_loop_drv_probe(struct mdio_device *mdiodev)
dev_set_drvdata(&mdiodev->dev, ds);
- return dsa_register_switch(ds);
+ ret = dsa_register_switch(ds);
+ if (!ret)
+ dev_info(&mdiodev->dev, "%s: 0x%0x\n",
+ pdata->name, pdata->enabled_ports);
+
+ return ret;
}
static void dsa_loop_drv_remove(struct mdio_device *mdiodev)
{
struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
- struct dsa_loop_priv *ps = ds->priv;
+ struct dsa_loop_priv *ps;
+
+ if (!ds)
+ return;
+
+ ps = ds->priv;
dsa_unregister_switch(ds);
dev_put(ps->netdev);
}
+static void dsa_loop_drv_shutdown(struct mdio_device *mdiodev)
+{
+ struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
+
+ if (!ds)
+ return;
+
+ dsa_switch_shutdown(ds);
+
+ dev_set_drvdata(&mdiodev->dev, NULL);
+}
+
static struct mdio_driver dsa_loop_drv = {
.mdiodrv.driver = {
.name = "dsa-loop",
},
.probe = dsa_loop_drv_probe,
.remove = dsa_loop_drv_remove,
+ .shutdown = dsa_loop_drv_shutdown,
};
-#define NUM_FIXED_PHYS (DSA_LOOP_NUM_PORTS - 2)
+static void dsa_loop_phydevs_unregister(void)
+{
+ for (int i = 0; i < NUM_FIXED_PHYS; i++) {
+ if (!IS_ERR(phydevs[i]))
+ fixed_phy_unregister(phydevs[i]);
+ }
+}
-static int __init dsa_loop_init(void)
+static int __init dsa_loop_create_switch_mdiodev(void)
{
- struct fixed_phy_status status = {
- .link = 1,
- .speed = SPEED_100,
- .duplex = DUPLEX_FULL,
+ static struct dsa_loop_pdata dsa_loop_pdata = {
+ .cd = {
+ .port_names[0] = "lan1",
+ .port_names[1] = "lan2",
+ .port_names[2] = "lan3",
+ .port_names[3] = "lan4",
+ .port_names[DSA_LOOP_CPU_PORT] = "cpu",
+ },
+ .name = "DSA mockup driver",
+ .enabled_ports = 0x1f,
+ .netdev = "eth0",
};
+ struct mii_bus *bus;
+ int ret = -ENODEV;
+
+ bus = mdio_find_bus("fixed-0");
+ if (WARN_ON(!bus))
+ return ret;
+
+ switch_mdiodev = mdio_device_create(bus, 31);
+ if (IS_ERR(switch_mdiodev))
+ goto out;
+
+ strscpy(switch_mdiodev->modalias, "dsa-loop");
+ switch_mdiodev->dev.platform_data = &dsa_loop_pdata;
+
+ ret = mdio_device_register(switch_mdiodev);
+ if (ret)
+ mdio_device_free(switch_mdiodev);
+out:
+ put_device(&bus->dev);
+ return ret;
+}
+
+static int __init dsa_loop_init(void)
+{
unsigned int i;
+ int ret;
+
+ ret = dsa_loop_create_switch_mdiodev();
+ if (ret)
+ return ret;
for (i = 0; i < NUM_FIXED_PHYS; i++)
- phydevs[i] = fixed_phy_register(PHY_POLL, &status, -1, NULL);
+ phydevs[i] = fixed_phy_register_100fd();
+
+ ret = mdio_driver_register(&dsa_loop_drv);
+ if (ret) {
+ dsa_loop_phydevs_unregister();
+ mdio_device_remove(switch_mdiodev);
+ mdio_device_free(switch_mdiodev);
+ }
- return mdio_driver_register(&dsa_loop_drv);
+ return ret;
}
module_init(dsa_loop_init);
static void __exit dsa_loop_exit(void)
{
- unsigned int i;
-
mdio_driver_unregister(&dsa_loop_drv);
- for (i = 0; i < NUM_FIXED_PHYS; i++)
- if (phydevs[i])
- fixed_phy_unregister(phydevs[i]);
+ dsa_loop_phydevs_unregister();
+ mdio_device_remove(switch_mdiodev);
+ mdio_device_free(switch_mdiodev);
}
module_exit(dsa_loop_exit);