root/drivers/net/dsa/realtek/rtl8366-core.c
// SPDX-License-Identifier: GPL-2.0
/* Realtek SMI library helpers for the RTL8366x variants
 * RTL8366RB and RTL8366S
 *
 * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
 * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
 * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
 * Copyright (C) 2010 Roman Yeryomin <roman@advem.lv>
 * Copyright (C) 2011 Colin Leitner <colin.leitner@googlemail.com>
 */
#include <linux/if_bridge.h>
#include <net/dsa.h>

#include "realtek.h"

int rtl8366_mc_is_used(struct realtek_priv *priv, int mc_index, int *used)
{
        int ret;
        int i;

        *used = 0;
        for (i = 0; i < priv->num_ports; i++) {
                int index = 0;

                ret = priv->ops->get_mc_index(priv, i, &index);
                if (ret)
                        return ret;

                if (mc_index == index) {
                        *used = 1;
                        break;
                }
        }

        return 0;
}
EXPORT_SYMBOL_NS_GPL(rtl8366_mc_is_used, "REALTEK_DSA");

/**
 * rtl8366_obtain_mc() - retrieve or allocate a VLAN member configuration
 * @priv: the Realtek SMI device instance
 * @vid: the VLAN ID to look up or allocate
 * @vlanmc: the pointer will be assigned to a pointer to a valid member config
 * if successful
 * @return: index of a new member config or negative error number
 */
static int rtl8366_obtain_mc(struct realtek_priv *priv, int vid,
                             struct rtl8366_vlan_mc *vlanmc)
{
        struct rtl8366_vlan_4k vlan4k;
        int ret;
        int i;

        /* Try to find an existing member config entry for this VID */
        for (i = 0; i < priv->num_vlan_mc; i++) {
                ret = priv->ops->get_vlan_mc(priv, i, vlanmc);
                if (ret) {
                        dev_err(priv->dev, "error searching for VLAN MC %d for VID %d\n",
                                i, vid);
                        return ret;
                }

                if (vid == vlanmc->vid)
                        return i;
        }

        /* We have no MC entry for this VID, try to find an empty one */
        for (i = 0; i < priv->num_vlan_mc; i++) {
                ret = priv->ops->get_vlan_mc(priv, i, vlanmc);
                if (ret) {
                        dev_err(priv->dev, "error searching for VLAN MC %d for VID %d\n",
                                i, vid);
                        return ret;
                }

                if (vlanmc->vid == 0 && vlanmc->member == 0) {
                        /* Update the entry from the 4K table */
                        ret = priv->ops->get_vlan_4k(priv, vid, &vlan4k);
                        if (ret) {
                                dev_err(priv->dev, "error looking for 4K VLAN MC %d for VID %d\n",
                                        i, vid);
                                return ret;
                        }

                        vlanmc->vid = vid;
                        vlanmc->member = vlan4k.member;
                        vlanmc->untag = vlan4k.untag;
                        vlanmc->fid = vlan4k.fid;
                        ret = priv->ops->set_vlan_mc(priv, i, vlanmc);
                        if (ret) {
                                dev_err(priv->dev, "unable to set/update VLAN MC %d for VID %d\n",
                                        i, vid);
                                return ret;
                        }

                        dev_dbg(priv->dev, "created new MC at index %d for VID %d\n",
                                i, vid);
                        return i;
                }
        }

        /* MC table is full, try to find an unused entry and replace it */
        for (i = 0; i < priv->num_vlan_mc; i++) {
                int used;

                ret = rtl8366_mc_is_used(priv, i, &used);
                if (ret)
                        return ret;

                if (!used) {
                        /* Update the entry from the 4K table */
                        ret = priv->ops->get_vlan_4k(priv, vid, &vlan4k);
                        if (ret)
                                return ret;

                        vlanmc->vid = vid;
                        vlanmc->member = vlan4k.member;
                        vlanmc->untag = vlan4k.untag;
                        vlanmc->fid = vlan4k.fid;
                        ret = priv->ops->set_vlan_mc(priv, i, vlanmc);
                        if (ret) {
                                dev_err(priv->dev, "unable to set/update VLAN MC %d for VID %d\n",
                                        i, vid);
                                return ret;
                        }
                        dev_dbg(priv->dev, "recycled MC at index %i for VID %d\n",
                                i, vid);
                        return i;
                }
        }

        dev_err(priv->dev, "all VLAN member configurations are in use\n");
        return -ENOSPC;
}

int rtl8366_set_vlan(struct realtek_priv *priv, int vid, u32 member,
                     u32 untag, u32 fid)
{
        struct rtl8366_vlan_mc vlanmc;
        struct rtl8366_vlan_4k vlan4k;
        int mc;
        int ret;

        if (!priv->ops->is_vlan_valid(priv, vid))
                return -EINVAL;

        dev_dbg(priv->dev,
                "setting VLAN%d 4k members: 0x%02x, untagged: 0x%02x\n",
                vid, member, untag);

        /* Update the 4K table */
        ret = priv->ops->get_vlan_4k(priv, vid, &vlan4k);
        if (ret)
                return ret;

        vlan4k.member |= member;
        vlan4k.untag |= untag;
        vlan4k.fid = fid;
        ret = priv->ops->set_vlan_4k(priv, &vlan4k);
        if (ret)
                return ret;

        dev_dbg(priv->dev,
                "resulting VLAN%d 4k members: 0x%02x, untagged: 0x%02x\n",
                vid, vlan4k.member, vlan4k.untag);

        /* Find or allocate a member config for this VID */
        ret = rtl8366_obtain_mc(priv, vid, &vlanmc);
        if (ret < 0)
                return ret;
        mc = ret;

        /* Update the MC entry */
        vlanmc.member |= member;
        vlanmc.untag |= untag;
        vlanmc.fid = fid;

        /* Commit updates to the MC entry */
        ret = priv->ops->set_vlan_mc(priv, mc, &vlanmc);
        if (ret)
                dev_err(priv->dev, "failed to commit changes to VLAN MC index %d for VID %d\n",
                        mc, vid);
        else
                dev_dbg(priv->dev,
                        "resulting VLAN%d MC members: 0x%02x, untagged: 0x%02x\n",
                        vid, vlanmc.member, vlanmc.untag);

        return ret;
}
EXPORT_SYMBOL_NS_GPL(rtl8366_set_vlan, "REALTEK_DSA");

int rtl8366_set_pvid(struct realtek_priv *priv, unsigned int port,
                     unsigned int vid)
{
        struct rtl8366_vlan_mc vlanmc;
        int mc;
        int ret;

        if (!priv->ops->is_vlan_valid(priv, vid))
                return -EINVAL;

        /* Find or allocate a member config for this VID */
        ret = rtl8366_obtain_mc(priv, vid, &vlanmc);
        if (ret < 0)
                return ret;
        mc = ret;

        ret = priv->ops->set_mc_index(priv, port, mc);
        if (ret) {
                dev_err(priv->dev, "set PVID: failed to set MC index %d for port %d\n",
                        mc, port);
                return ret;
        }

        dev_dbg(priv->dev, "set PVID: the PVID for port %d set to %d using existing MC index %d\n",
                port, vid, mc);

        return 0;
}
EXPORT_SYMBOL_NS_GPL(rtl8366_set_pvid, "REALTEK_DSA");

int rtl8366_enable_vlan4k(struct realtek_priv *priv, bool enable)
{
        int ret;

        /* To enable 4k VLAN, ordinary VLAN must be enabled first,
         * but if we disable 4k VLAN it is fine to leave ordinary
         * VLAN enabled.
         */
        if (enable) {
                /* Make sure VLAN is ON */
                ret = priv->ops->enable_vlan(priv, true);
                if (ret)
                        return ret;

                priv->vlan_enabled = true;
        }

        ret = priv->ops->enable_vlan4k(priv, enable);
        if (ret)
                return ret;

        priv->vlan4k_enabled = enable;
        return 0;
}
EXPORT_SYMBOL_NS_GPL(rtl8366_enable_vlan4k, "REALTEK_DSA");

int rtl8366_enable_vlan(struct realtek_priv *priv, bool enable)
{
        int ret;

        ret = priv->ops->enable_vlan(priv, enable);
        if (ret)
                return ret;

        priv->vlan_enabled = enable;

        /* If we turn VLAN off, make sure that we turn off
         * 4k VLAN as well, if that happened to be on.
         */
        if (!enable) {
                priv->vlan4k_enabled = false;
                ret = priv->ops->enable_vlan4k(priv, false);
        }

        return ret;
}
EXPORT_SYMBOL_NS_GPL(rtl8366_enable_vlan, "REALTEK_DSA");

int rtl8366_reset_vlan(struct realtek_priv *priv)
{
        struct rtl8366_vlan_mc vlanmc;
        int ret;
        int i;

        rtl8366_enable_vlan(priv, false);
        rtl8366_enable_vlan4k(priv, false);

        /* Clear the 16 VLAN member configurations */
        vlanmc.vid = 0;
        vlanmc.priority = 0;
        vlanmc.member = 0;
        vlanmc.untag = 0;
        vlanmc.fid = 0;
        for (i = 0; i < priv->num_vlan_mc; i++) {
                ret = priv->ops->set_vlan_mc(priv, i, &vlanmc);
                if (ret)
                        return ret;
        }

        return 0;
}
EXPORT_SYMBOL_NS_GPL(rtl8366_reset_vlan, "REALTEK_DSA");

int rtl8366_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 realtek_priv *priv = ds->priv;
        u32 member = 0;
        u32 untag = 0;
        int ret;

        if (!priv->ops->is_vlan_valid(priv, vlan->vid)) {
                NL_SET_ERR_MSG_MOD(extack, "VLAN ID not valid");
                return -EINVAL;
        }

        /* Enable VLAN in the hardware
         * FIXME: what's with this 4k business?
         * Just rtl8366_enable_vlan() seems inconclusive.
         */
        ret = rtl8366_enable_vlan4k(priv, true);
        if (ret) {
                NL_SET_ERR_MSG_MOD(extack, "Failed to enable VLAN 4K");
                return ret;
        }

        dev_dbg(priv->dev, "add VLAN %d on port %d, %s, %s\n",
                vlan->vid, port, untagged ? "untagged" : "tagged",
                pvid ? "PVID" : "no PVID");

        member |= BIT(port);

        if (untagged)
                untag |= BIT(port);

        ret = rtl8366_set_vlan(priv, vlan->vid, member, untag, 0);
        if (ret) {
                dev_err(priv->dev, "failed to set up VLAN %04x", vlan->vid);
                return ret;
        }

        if (!pvid)
                return 0;

        ret = rtl8366_set_pvid(priv, port, vlan->vid);
        if (ret) {
                dev_err(priv->dev, "failed to set PVID on port %d to VLAN %04x",
                        port, vlan->vid);
                return ret;
        }

        return 0;
}
EXPORT_SYMBOL_NS_GPL(rtl8366_vlan_add, "REALTEK_DSA");

int rtl8366_vlan_del(struct dsa_switch *ds, int port,
                     const struct switchdev_obj_port_vlan *vlan)
{
        struct realtek_priv *priv = ds->priv;
        int ret, i;

        dev_dbg(priv->dev, "del VLAN %d on port %d\n", vlan->vid, port);

        for (i = 0; i < priv->num_vlan_mc; i++) {
                struct rtl8366_vlan_mc vlanmc;

                ret = priv->ops->get_vlan_mc(priv, i, &vlanmc);
                if (ret)
                        return ret;

                if (vlan->vid == vlanmc.vid) {
                        /* Remove this port from the VLAN */
                        vlanmc.member &= ~BIT(port);
                        vlanmc.untag &= ~BIT(port);
                        /*
                         * If no ports are members of this VLAN
                         * anymore then clear the whole member
                         * config so it can be reused.
                         */
                        if (!vlanmc.member) {
                                vlanmc.vid = 0;
                                vlanmc.priority = 0;
                                vlanmc.fid = 0;
                        }
                        ret = priv->ops->set_vlan_mc(priv, i, &vlanmc);
                        if (ret) {
                                dev_err(priv->dev,
                                        "failed to remove VLAN %04x\n",
                                        vlan->vid);
                                return ret;
                        }
                        break;
                }
        }

        return 0;
}
EXPORT_SYMBOL_NS_GPL(rtl8366_vlan_del, "REALTEK_DSA");

void rtl8366_get_strings(struct dsa_switch *ds, int port, u32 stringset,
                         uint8_t *data)
{
        struct realtek_priv *priv = ds->priv;
        int i;

        if (port >= priv->num_ports)
                return;

        for (i = 0; i < priv->num_mib_counters; i++)
                ethtool_puts(&data, priv->mib_counters[i].name);
}
EXPORT_SYMBOL_NS_GPL(rtl8366_get_strings, "REALTEK_DSA");

int rtl8366_get_sset_count(struct dsa_switch *ds, int port, int sset)
{
        struct realtek_priv *priv = ds->priv;

        /* We only support SS_STATS */
        if (sset != ETH_SS_STATS)
                return 0;
        if (port >= priv->num_ports)
                return -EINVAL;

        return priv->num_mib_counters;
}
EXPORT_SYMBOL_NS_GPL(rtl8366_get_sset_count, "REALTEK_DSA");

void rtl8366_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data)
{
        struct realtek_priv *priv = ds->priv;
        int i;
        int ret;

        if (port >= priv->num_ports)
                return;

        for (i = 0; i < priv->num_mib_counters; i++) {
                struct rtl8366_mib_counter *mib;
                u64 mibvalue = 0;

                mib = &priv->mib_counters[i];
                ret = priv->ops->get_mib_counter(priv, port, mib, &mibvalue);
                if (ret) {
                        dev_err(priv->dev, "error reading MIB counter %s\n",
                                mib->name);
                }
                data[i] = mibvalue;
        }
}
EXPORT_SYMBOL_NS_GPL(rtl8366_get_ethtool_stats, "REALTEK_DSA");