445 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			445 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			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);
 |