root/drivers/net/dsa/dsa_loop.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Distributed Switch Architecture loopback driver
 *
 * Copyright (C) 2016, Florian Fainelli <f.fainelli@gmail.com>
 */

#include <linux/platform_device.h>
#include <linux/netdevice.h>
#include <linux/phy.h>
#include <linux/phy_fixed.h>
#include <linux/export.h>
#include <linux/ethtool.h>
#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/if_bridge.h>
#include <linux/dsa/loop.h>
#include <net/dsa.h>

#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[] = {
        [DSA_LOOP_PHY_READ_OK]  = { "phy_read_ok", },
        [DSA_LOOP_PHY_READ_ERR] = { "phy_read_err", },
        [DSA_LOOP_PHY_WRITE_OK] = { "phy_write_ok", },
        [DSA_LOOP_PHY_WRITE_ERR] = { "phy_write_err", },
};

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,
};

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;

        for (i = 0; i < ARRAY_SIZE(ps->vlans); i++) {
                vl = &ps->vlans[i];
                if (vl->members)
                        count++;
        }

        return count;
}

static int dsa_loop_setup_devlink_resources(struct dsa_switch *ds)
{
        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;
}

static int dsa_loop_setup(struct dsa_switch *ds)
{
        struct dsa_loop_priv *ps = ds->priv;
        unsigned int i;

        for (i = 0; i < ds->num_ports; i++)
                memcpy(ps->ports[i].mib, dsa_loop_mibs,
                       sizeof(dsa_loop_mibs));

        dev_dbg(ds->dev, "%s\n", __func__);

        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, 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,
                                 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++)
                ethtool_puts(&data, ps->ports[port].mib[i].name);
}

static void dsa_loop_get_ethtool_stats(struct dsa_switch *ds, int port,
                                       uint64_t *data)
{
        struct dsa_loop_priv *ps = ds->priv;
        unsigned int i;

        for (i = 0; i < __DSA_LOOP_CNT_MAX; i++)
                data[i] = ps->ports[port].mib[i].val;
}

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;

        ret = mdiobus_read_nested(bus, ps->port_base + port, regnum);
        if (ret < 0)
                ps->ports[port].mib[DSA_LOOP_PHY_READ_ERR].val++;
        else
                ps->ports[port].mib[DSA_LOOP_PHY_READ_OK].val++;

        return ret;
}

static int dsa_loop_phy_write(struct dsa_switch *ds, int port,
                              int regnum, u16 value)
{
        struct dsa_loop_priv *ps = ds->priv;
        struct mii_bus *bus = ps->bus;
        int ret;

        ret = mdiobus_write_nested(bus, ps->port_base + port, regnum, value);
        if (ret < 0)
                ps->ports[port].mib[DSA_LOOP_PHY_WRITE_ERR].val++;
        else
                ps->ports[port].mib[DSA_LOOP_PHY_WRITE_OK].val++;

        return ret;
}

static int dsa_loop_port_bridge_join(struct dsa_switch *ds, int port,
                                     struct dsa_bridge bridge,
                                     bool *tx_fwd_offload,
                                     struct netlink_ext_ack *extack)
{
        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 dsa_bridge bridge)
{
        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: port: %d, state: %d\n",
                __func__, port, state);
}

static int dsa_loop_port_vlan_filtering(struct dsa_switch *ds, int port,
                                        bool vlan_filtering,
                                        struct netlink_ext_ack *extack)
{
        dev_dbg(ds->dev, "%s: port: %d, vlan_filtering: %d\n",
                __func__, port, vlan_filtering);

        return 0;
}

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;

        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);

        vl = &ps->vlans[vlan->vid];

        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->ports[port].pvid = vlan->vid;

        return 0;
}

static int dsa_loop_port_vlan_del(struct dsa_switch *ds, int port,
                                  const struct switchdev_obj_port_vlan *vlan)
{
        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;

        /* Just do a sleeping operation to make lockdep checks effective */
        mdiobus_read(bus, ps->port_base + port, MII_BMSR);

        vl = &ps->vlans[vlan->vid];

        vl->members &= ~BIT(port);
        if (untagged)
                vl->untagged &= ~BIT(port);

        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_change_mtu(struct dsa_switch *ds, int port,
                                    int new_mtu)
{
        struct dsa_loop_priv *priv = ds->priv;

        priv->ports[port].mtu = new_mtu;

        return 0;
}

static int dsa_loop_port_max_mtu(struct dsa_switch *ds, int port)
{
        return ETH_MAX_MTU;
}

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 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,
        .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_add          = dsa_loop_port_vlan_add,
        .port_vlan_del          = dsa_loop_port_vlan_del,
        .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)
{
        struct dsa_loop_pdata *pdata = mdiodev->dev.platform_data;
        struct dsa_loop_priv *ps;
        struct dsa_switch *ds;
        int ret;

        if (!pdata)
                return -ENODEV;

        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;

        ps->netdev = dev_get_by_name(&init_net, pdata->netdev);
        if (!ps->netdev)
                return -EPROBE_DEFER;

        pdata->cd.netdev[DSA_LOOP_CPU_PORT] = &ps->netdev->dev;

        ds->dev = &mdiodev->dev;
        ds->ops = &dsa_loop_driver;
        ds->priv = ps;
        ps->bus = mdiodev->bus;

        dev_set_drvdata(&mdiodev->dev, 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;

        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,
};

static int dsa_loop_bus_match(struct device *dev,
                              const struct device_driver *drv)
{
        return drv == &dsa_loop_drv.mdiodrv.driver;
}

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_create_switch_mdiodev(void)
{
        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;

        switch_mdiodev->bus_match = dsa_loop_bus_match;
        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_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 ret;
}
module_init(dsa_loop_init);

static void __exit dsa_loop_exit(void)
{
        mdio_driver_unregister(&dsa_loop_drv);
        dsa_loop_phydevs_unregister();
        mdio_device_remove(switch_mdiodev);
        mdio_device_free(switch_mdiodev);
}
module_exit(dsa_loop_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Florian Fainelli");
MODULE_DESCRIPTION("DSA loopback driver");