* [PATCH v1 RFC 6/7] Add MIB counter reading support
From: Tristram.Ha @ 2017-10-06 20:33 UTC (permalink / raw)
To: Andrew Lunn, Florian Fainelli, Pavel Machek, Ruediger Schmitt
Cc: Tristram Ha, muvarov, nathan.leigh.conrad, vivien.didelot,
UNGLinuxDriver, netdev, linux-kernel
In-Reply-To: <1507321985-15097-1-git-send-email-Tristram.Ha@microchip.com>
From: Tristram Ha <Tristram.Ha@microchip.com>
Add MIB counter reading support.
Rename ksz_9477_reg.h to ksz9477_reg.h for consistency as the product
name is always KSZ####.
Header file ksz_priv.h no longer contains any chip specific data.
Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
---
drivers/net/dsa/microchip/ksz9477.c | 130 ++++++++++++++-------
.../microchip/{ksz_9477_reg.h => ksz9477_reg.h} | 0
drivers/net/dsa/microchip/ksz_common.c | 122 +++++++++++++++++++
drivers/net/dsa/microchip/ksz_common.h | 4 +
drivers/net/dsa/microchip/ksz_priv.h | 8 +-
5 files changed, 219 insertions(+), 45 deletions(-)
rename drivers/net/dsa/microchip/{ksz_9477_reg.h => ksz9477_reg.h} (100%)
diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c
index 214d380..9579d03 100644
--- a/drivers/net/dsa/microchip/ksz9477.c
+++ b/drivers/net/dsa/microchip/ksz9477.c
@@ -31,7 +31,7 @@
#include "ksz_priv.h"
#include "ksz_common.h"
-#include "ksz_9477_reg.h"
+#include "ksz9477_reg.h"
static const struct {
int index;
@@ -272,6 +272,74 @@ static int ksz9477_reset_switch(struct ksz_device *dev)
return 0;
}
+static void ksz9477_r_mib_cnt(struct ksz_device *dev, int port, u16 addr,
+ u64 *cnt)
+{
+ u32 data;
+ int timeout;
+
+ /* retain the flush/freeze bit */
+ ksz_pread32(dev, port, REG_PORT_MIB_CTRL_STAT__4, &data);
+ data &= MIB_COUNTER_FLUSH_FREEZE;
+
+ data |= MIB_COUNTER_READ;
+ data |= (addr << MIB_COUNTER_INDEX_S);
+ ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, data);
+
+ timeout = 1000;
+ do {
+ ksz_pread32(dev, port, REG_PORT_MIB_CTRL_STAT__4,
+ &data);
+ usleep_range(1, 10);
+ if (!(data & MIB_COUNTER_READ))
+ break;
+ } while (timeout-- > 0);
+
+ /* failed to read MIB. get out of loop */
+ if (!timeout) {
+ dev_dbg(dev->dev, "Failed to get MIB\n");
+ return;
+ }
+
+ /* count resets upon read */
+ ksz_pread32(dev, port, REG_PORT_MIB_DATA, &data);
+ *cnt += data;
+}
+
+static void ksz9477_r_mib_pkt(struct ksz_device *dev, int port, u16 addr,
+ u64 *dropped, u64 *cnt)
+{
+ addr = mib_names[addr].index;
+ ksz9477_r_mib_cnt(dev, port, addr, cnt);
+}
+
+static void ksz9477_freeze_mib(struct ksz_device *dev, int port, bool freeze)
+{
+ /* enable the port for flush/freeze function */
+ if (freeze)
+ ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4,
+ MIB_COUNTER_FLUSH_FREEZE);
+ ksz_cfg(dev, REG_SW_MAC_CTRL_6, SW_MIB_COUNTER_FREEZE, freeze);
+
+ /* disable the port after freeze is done */
+ if (!freeze)
+ ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, 0);
+}
+
+static void ksz9477_port_init_cnt(struct ksz_device *dev, int port)
+{
+ struct ksz_port_mib *mib = &dev->ports[port].mib;
+
+ /* flush all enabled port MIB counters */
+ ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4,
+ MIB_COUNTER_FLUSH_FREEZE);
+ ksz_write8(dev, REG_SW_MAC_CTRL_6, SW_MIB_COUNTER_FLUSH);
+ ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, 0);
+
+ mib->cnt_ptr = 0;
+ memset(mib->counters, 0, dev->mib_cnt * sizeof(u64));
+}
+
static enum dsa_tag_protocol ksz9477_get_tag_protocol(struct dsa_switch *ds)
{
return DSA_TAG_PROTO_KSZ;
@@ -350,46 +418,6 @@ static void ksz9477_get_strings(struct dsa_switch *ds, int port, uint8_t *buf)
}
}
-static void ksz_get_ethtool_stats(struct dsa_switch *ds, int port,
- uint64_t *buf)
-{
- struct ksz_device *dev = ds->priv;
- int i;
- u32 data;
- int timeout;
-
- mutex_lock(&dev->stats_mutex);
-
- for (i = 0; i < TOTAL_SWITCH_COUNTER_NUM; i++) {
- data = MIB_COUNTER_READ;
- data |= ((mib_names[i].index & 0xFF) << MIB_COUNTER_INDEX_S);
- ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, data);
-
- timeout = 1000;
- do {
- ksz_pread32(dev, port, REG_PORT_MIB_CTRL_STAT__4,
- &data);
- usleep_range(1, 10);
- if (!(data & MIB_COUNTER_READ))
- break;
- } while (timeout-- > 0);
-
- /* failed to read MIB. get out of loop */
- if (!timeout) {
- dev_dbg(dev->dev, "Failed to get MIB\n");
- break;
- }
-
- /* count resets upon read */
- ksz_pread32(dev, port, REG_PORT_MIB_DATA, &data);
-
- dev->mib_value[i] += (uint64_t)data;
- buf[i] = dev->mib_value[i];
- }
-
- mutex_unlock(&dev->stats_mutex);
-}
-
static void ksz9477_cfg_port_member(struct ksz_device *dev, int port,
u8 member)
{
@@ -977,6 +1005,17 @@ static void ksz9477_port_mirror_del(struct dsa_switch *ds, int port,
PORT_MIRROR_SNIFFER, false);
}
+static void ksz9477_phy_setup(struct ksz_device *dev, int port,
+ struct phy_device *phy)
+{
+ if (port < dev->phy_port_cnt) {
+ /* SUPPORTED_Asym_Pause and SUPPORTED_Pause can be removed to
+ * disable flow control when rate limiting is used.
+ */
+ phy->advertising = phy->supported;
+ }
+}
+
static void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port)
{
u8 data8;
@@ -1159,6 +1198,8 @@ static int ksz9477_setup(struct dsa_switch *ds)
/* start switch */
ksz_cfg(dev, REG_SW_OPERATION, SW_START, true);
+ ksz_init_mib_timer(dev);
+
return 0;
}
@@ -1168,6 +1209,7 @@ static int ksz9477_setup(struct dsa_switch *ds)
.set_addr = ksz9477_set_addr,
.phy_read = ksz9477_phy_read16,
.phy_write = ksz9477_phy_write16,
+ .adjust_link = ksz_adjust_link,
.port_enable = ksz_enable_port,
.port_disable = ksz_disable_port,
.get_strings = ksz9477_get_strings,
@@ -1289,6 +1331,7 @@ static int ksz9477_switch_init(struct ksz_device *dev)
if (!dev->ports)
return -ENOMEM;
for (i = 0; i < dev->mib_port_cnt; i++) {
+ mutex_init(&dev->ports[i].mib.cnt_mutex);
dev->ports[i].mib.counters =
devm_kzalloc(dev->dev,
sizeof(u64) *
@@ -1310,7 +1353,12 @@ static void ksz9477_switch_exit(struct ksz_device *dev)
.get_port_addr = ksz9477_get_port_addr,
.cfg_port_member = ksz9477_cfg_port_member,
.flush_dyn_mac_table = ksz9477_flush_dyn_mac_table,
+ .phy_setup = ksz9477_phy_setup,
.port_setup = ksz9477_port_setup,
+ .r_mib_cnt = ksz9477_r_mib_cnt,
+ .r_mib_pkt = ksz9477_r_mib_pkt,
+ .freeze_mib = ksz9477_freeze_mib,
+ .port_init_cnt = ksz9477_port_init_cnt,
.shutdown = ksz9477_reset_switch,
.detect = ksz9477_switch_detect,
.init = ksz9477_switch_init,
diff --git a/drivers/net/dsa/microchip/ksz_9477_reg.h b/drivers/net/dsa/microchip/ksz9477_reg.h
similarity index 100%
rename from drivers/net/dsa/microchip/ksz_9477_reg.h
rename to drivers/net/dsa/microchip/ksz9477_reg.h
diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c
index 1c9c4c5..885b3e3 100644
--- a/drivers/net/dsa/microchip/ksz_common.c
+++ b/drivers/net/dsa/microchip/ksz_common.c
@@ -50,6 +50,81 @@ void ksz_update_port_member(struct ksz_device *dev, int port)
}
}
+static void port_r_cnt(struct ksz_device *dev, int port)
+{
+ struct ksz_port_mib *mib = &dev->ports[port].mib;
+ u64 *dropped;
+
+ /* Some ports may not have MIB counters before SWITCH_COUNTER_NUM. */
+ while (mib->cnt_ptr < dev->reg_mib_cnt) {
+ dev->dev_ops->r_mib_cnt(dev, port, mib->cnt_ptr,
+ &mib->counters[mib->cnt_ptr]);
+ ++mib->cnt_ptr;
+ }
+
+ /* last one in storage */
+ dropped = &mib->counters[dev->mib_cnt];
+
+ /* Some ports may not have MIB counters after SWITCH_COUNTER_NUM. */
+ while (mib->cnt_ptr < dev->mib_cnt) {
+ dev->dev_ops->r_mib_pkt(dev, port, mib->cnt_ptr,
+ dropped, &mib->counters[mib->cnt_ptr]);
+ ++mib->cnt_ptr;
+ }
+ mib->cnt_ptr = 0;
+}
+
+static void ksz_mib_read_work(struct work_struct *work)
+{
+ struct ksz_device *dev =
+ container_of(work, struct ksz_device, mib_read);
+ struct ksz_port *p;
+ struct ksz_port_mib *mib;
+ int i;
+
+ for (i = 0; i < dev->mib_port_cnt; i++) {
+ p = &dev->ports[i];
+ if (!p->on)
+ continue;
+ mib = &p->mib;
+ mutex_lock(&mib->cnt_mutex);
+
+ /* read only dropped counters when link is not up */
+ if (p->link_down)
+ p->link_down = 0;
+ else if (!p->link_up)
+ mib->cnt_ptr = dev->reg_mib_cnt;
+ port_r_cnt(dev, i);
+ mutex_unlock(&mib->cnt_mutex);
+ }
+}
+
+static void mib_monitor(unsigned long ptr)
+{
+ struct ksz_device *dev = (struct ksz_device *)ptr;
+
+ mod_timer(&dev->mib_read_timer, jiffies + dev->mib_read_interval);
+ schedule_work(&dev->mib_read);
+}
+
+void ksz_init_mib_timer(struct ksz_device *dev)
+{
+ int i;
+
+ /* Read MIB counters every 30 seconds to avoid overflow. */
+ dev->mib_read_interval = msecs_to_jiffies(30000);
+
+ INIT_WORK(&dev->mib_read, ksz_mib_read_work);
+ setup_timer(&dev->mib_read_timer, mib_monitor, (unsigned long)dev);
+
+ for (i = 0; i < dev->mib_port_cnt; i++)
+ dev->dev_ops->port_init_cnt(dev, i);
+
+ /* Start the timer 2 seconds later. */
+ dev->mib_read_timer.expires = jiffies + msecs_to_jiffies(2000);
+ add_timer(&dev->mib_read_timer);
+}
+
int ksz_phy_read16(struct dsa_switch *ds, int addr, int reg)
{
struct ksz_device *dev = ds->priv;
@@ -69,6 +144,25 @@ int ksz_phy_write16(struct dsa_switch *ds, int addr, int reg, u16 val)
return 0;
}
+void ksz_adjust_link(struct dsa_switch *ds, int port,
+ struct phy_device *phydev)
+{
+ struct ksz_device *dev = ds->priv;
+ struct ksz_port *p = &dev->ports[port];
+
+ if (phydev->link) {
+ p->speed = phydev->speed;
+ p->duplex = phydev->duplex;
+ p->flow_ctrl = phydev->pause;
+ p->link_up = 1;
+ dev->live_ports |= (1 << port) & dev->on_ports;
+ } else if (p->link_up) {
+ p->link_up = 0;
+ p->link_down = 1;
+ dev->live_ports &= ~(1 << port);
+ }
+}
+
int ksz_sset_count(struct dsa_switch *ds)
{
struct ksz_device *dev = ds->priv;
@@ -76,6 +170,27 @@ int ksz_sset_count(struct dsa_switch *ds)
return dev->mib_cnt;
}
+void ksz_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *buf)
+{
+ struct ksz_device *dev = ds->priv;
+ struct ksz_port_mib *mib;
+
+ mib = &dev->ports[port].mib;
+
+ mutex_lock(&dev->stats_mutex);
+
+ /* freeze MIB counters if supported */
+ if (dev->dev_ops->freeze_mib)
+ dev->dev_ops->freeze_mib(dev, port, true);
+ mutex_lock(&mib->cnt_mutex);
+ port_r_cnt(dev, port);
+ mutex_unlock(&mib->cnt_mutex);
+ if (dev->dev_ops->freeze_mib)
+ dev->dev_ops->freeze_mib(dev, port, false);
+ memcpy(buf, mib->counters, dev->mib_cnt * sizeof(u64));
+ mutex_unlock(&dev->stats_mutex);
+}
+
int ksz_port_bridge_join(struct dsa_switch *ds, int port,
struct net_device *br)
{
@@ -249,6 +364,7 @@ int ksz_enable_port(struct dsa_switch *ds, int port, struct phy_device *phy)
/* setup slave port */
dev->dev_ops->port_setup(dev, port, false);
+ dev->dev_ops->phy_setup(dev, port, phy);
/* port_stp_state_set() will be called after to enable the port so
* there is no need to do anything.
@@ -331,6 +447,12 @@ int ksz_switch_register(struct ksz_device *dev,
void ksz_switch_remove(struct ksz_device *dev)
{
+ /* timer started */
+ if (dev->mib_read_timer.expires) {
+ del_timer_sync(&dev->mib_read_timer);
+ flush_work(&dev->mib_read);
+ }
+
dev->dev_ops->exit(dev);
dsa_unregister_switch(dev->ds);
}
diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h
index 1c1cbad..20afea3f 100644
--- a/drivers/net/dsa/microchip/ksz_common.h
+++ b/drivers/net/dsa/microchip/ksz_common.h
@@ -21,12 +21,16 @@
#define __KSZ_COMMON_H
void ksz_update_port_member(struct ksz_device *dev, int port);
+void ksz_init_mib_timer(struct ksz_device *dev);
/* Common DSA access functions */
int ksz_phy_read16(struct dsa_switch *ds, int addr, int reg);
int ksz_phy_write16(struct dsa_switch *ds, int addr, int reg, u16 val);
+void ksz_adjust_link(struct dsa_switch *ds, int port,
+ struct phy_device *phydev);
int ksz_sset_count(struct dsa_switch *ds);
+void ksz_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *buf);
int ksz_port_bridge_join(struct dsa_switch *ds, int port,
struct net_device *br);
void ksz_port_bridge_leave(struct dsa_switch *ds, int port,
diff --git a/drivers/net/dsa/microchip/ksz_priv.h b/drivers/net/dsa/microchip/ksz_priv.h
index 4126749..0e1ccd3 100644
--- a/drivers/net/dsa/microchip/ksz_priv.h
+++ b/drivers/net/dsa/microchip/ksz_priv.h
@@ -26,8 +26,6 @@
#include <linux/etherdevice.h>
#include <net/dsa.h>
-#include "ksz_9477_reg.h"
-
struct ksz_io_ops;
struct vlan_table {
@@ -35,6 +33,7 @@ struct vlan_table {
};
struct ksz_port_mib {
+ struct mutex cnt_mutex; /* structure access */
u8 cnt_ptr;
u64 *counters;
};
@@ -92,8 +91,6 @@ struct ksz_device {
struct vlan_table *vlan_cache;
- u64 mib_value[TOTAL_SWITCH_COUNTER_NUM];
-
u8 *txbuf;
struct ksz_port *ports;
@@ -150,6 +147,8 @@ struct ksz_dev_ops {
u32 (*get_port_addr)(int port, int offset);
void (*cfg_port_member)(struct ksz_device *dev, int port, u8 member);
void (*flush_dyn_mac_table)(struct ksz_device *dev, int port);
+ void (*phy_setup)(struct ksz_device *dev, int port,
+ struct phy_device *phy);
void (*port_setup)(struct ksz_device *dev, int port, bool cpu_port);
void (*r_phy)(struct ksz_device *dev, u16 phy, u16 reg, u16 *val);
void (*w_phy)(struct ksz_device *dev, u16 phy, u16 reg, u16 val);
@@ -164,6 +163,7 @@ struct ksz_dev_ops {
u64 *cnt);
void (*r_mib_pkt)(struct ksz_device *dev, int port, u16 addr,
u64 *dropped, u64 *cnt);
+ void (*freeze_mib)(struct ksz_device *dev, int port, bool freeze);
void (*port_init_cnt)(struct ksz_device *dev, int port);
int (*shutdown)(struct ksz_device *dev);
int (*detect)(struct ksz_device *dev);
--
1.9.1
^ permalink raw reply related
* [PATCH v1 RFC 7/7] Modify tag_ksz.c so that tail tag code can be used by other KSZ switch drivers
From: Tristram.Ha @ 2017-10-06 20:33 UTC (permalink / raw)
To: Andrew Lunn, Florian Fainelli, Pavel Machek, Ruediger Schmitt
Cc: Tristram Ha, muvarov, nathan.leigh.conrad, vivien.didelot,
UNGLinuxDriver, netdev, linux-kernel
In-Reply-To: <1507321985-15097-1-git-send-email-Tristram.Ha@microchip.com>
From: Tristram Ha <Tristram.Ha@microchip.com>
Modify tag_ksz.c so that tail tag code can be used by other KSZ switch
drivers.
Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
---
drivers/net/dsa/microchip/Kconfig | 2 +-
drivers/net/dsa/microchip/ksz9477.c | 2 +-
include/net/dsa.h | 2 +-
net/dsa/Kconfig | 4 ++
net/dsa/dsa.c | 4 +-
net/dsa/dsa_priv.h | 2 +-
net/dsa/tag_ksz.c | 107 ++++++++++++++++++++++++++----------
7 files changed, 88 insertions(+), 35 deletions(-)
diff --git a/drivers/net/dsa/microchip/Kconfig b/drivers/net/dsa/microchip/Kconfig
index 5a8660d..ab8f9f6 100644
--- a/drivers/net/dsa/microchip/Kconfig
+++ b/drivers/net/dsa/microchip/Kconfig
@@ -1,7 +1,7 @@
menuconfig MICROCHIP_KSZ9477
tristate "Microchip KSZ9477 series switch support"
depends on NET_DSA
- select NET_DSA_TAG_KSZ
+ select NET_DSA_TAG_KSZ9477
help
This driver adds support for Microchip KSZ9477 switch chips.
diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c
index 9579d03..e6d2956 100644
--- a/drivers/net/dsa/microchip/ksz9477.c
+++ b/drivers/net/dsa/microchip/ksz9477.c
@@ -342,7 +342,7 @@ static void ksz9477_port_init_cnt(struct ksz_device *dev, int port)
static enum dsa_tag_protocol ksz9477_get_tag_protocol(struct dsa_switch *ds)
{
- return DSA_TAG_PROTO_KSZ;
+ return DSA_TAG_PROTO_KSZ9477;
}
static int ksz9477_phy_read16(struct dsa_switch *ds, int addr, int reg)
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 10dcecc..28cdc5e 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -31,7 +31,7 @@ enum dsa_tag_protocol {
DSA_TAG_PROTO_BRCM,
DSA_TAG_PROTO_DSA,
DSA_TAG_PROTO_EDSA,
- DSA_TAG_PROTO_KSZ,
+ DSA_TAG_PROTO_KSZ9477,
DSA_TAG_PROTO_LAN9303,
DSA_TAG_PROTO_MTK,
DSA_TAG_PROTO_QCA,
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index cc5f8f9..d2bbd21 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -28,6 +28,10 @@ config NET_DSA_TAG_EDSA
config NET_DSA_TAG_KSZ
bool
+config NET_DSA_TAG_KSZ9477
+ bool
+ select NET_DSA_TAG_KSZ
+
config NET_DSA_TAG_LAN9303
bool
diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
index 51ca2a5..cc03a09 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -49,8 +49,8 @@ static struct sk_buff *dsa_slave_notag_xmit(struct sk_buff *skb,
#ifdef CONFIG_NET_DSA_TAG_EDSA
[DSA_TAG_PROTO_EDSA] = &edsa_netdev_ops,
#endif
-#ifdef CONFIG_NET_DSA_TAG_KSZ
- [DSA_TAG_PROTO_KSZ] = &ksz_netdev_ops,
+#ifdef CONFIG_NET_DSA_TAG_KSZ9477
+ [DSA_TAG_PROTO_KSZ9477] = &ksz9477_netdev_ops,
#endif
#ifdef CONFIG_NET_DSA_TAG_LAN9303
[DSA_TAG_PROTO_LAN9303] = &lan9303_netdev_ops,
diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
index 2850077c..e0dc2d4 100644
--- a/net/dsa/dsa_priv.h
+++ b/net/dsa/dsa_priv.h
@@ -183,7 +183,7 @@ int dsa_port_vlan_del(struct dsa_port *dp,
extern const struct dsa_device_ops edsa_netdev_ops;
/* tag_ksz.c */
-extern const struct dsa_device_ops ksz_netdev_ops;
+extern const struct dsa_device_ops ksz9477_netdev_ops;
/* tag_lan9303.c */
extern const struct dsa_device_ops lan9303_netdev_ops;
diff --git a/net/dsa/tag_ksz.c b/net/dsa/tag_ksz.c
index b241c99..1bb5b7d 100644
--- a/net/dsa/tag_ksz.c
+++ b/net/dsa/tag_ksz.c
@@ -14,34 +14,28 @@
#include <net/dsa.h>
#include "dsa_priv.h"
-/* For Ingress (Host -> KSZ), 2 bytes are added before FCS.
- * ---------------------------------------------------------------------------
- * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|tag1(1byte)|FCS(4bytes)
- * ---------------------------------------------------------------------------
- * tag0 : Prioritization (not used now)
- * tag1 : each bit represents port (eg, 0x01=port1, 0x02=port2, 0x10=port5)
- *
- * For Egress (KSZ -> Host), 1 byte is added before FCS.
- * ---------------------------------------------------------------------------
- * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|FCS(4bytes)
- * ---------------------------------------------------------------------------
- * tag0 : zero-based value represents port
- * (eg, 0x00=port1, 0x02=port3, 0x06=port7)
- */
+/* Typically only one byte is used for tail tag. */
+#define KSZ_EGRESS_TAG_LEN 1
+#define KSZ_INGRESS_TAG_LEN 1
-#define KSZ_INGRESS_TAG_LEN 2
-#define KSZ_EGRESS_TAG_LEN 1
+/* Frames with following addresse may need to be sent even when the port is
+ * closed.
+ */
+static const u8 special_mult_addr[] = {
+ 0x01, 0x80, 0xC2, 0x00, 0x00, 0x00
+};
-static struct sk_buff *ksz_xmit(struct sk_buff *skb, struct net_device *dev)
+static struct sk_buff *ksz_xmit(struct sk_buff *skb, struct net_device *dev,
+ int len,
+ void (*set_tag)(void *ptr, u8 *addr, int p))
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct sk_buff *nskb;
int padlen;
- u8 *tag;
padlen = (skb->len >= ETH_ZLEN) ? 0 : ETH_ZLEN - skb->len;
- if (skb_tailroom(skb) >= padlen + KSZ_INGRESS_TAG_LEN) {
+ if (skb_tailroom(skb) >= padlen + len) {
/* Let dsa_slave_xmit() free skb */
if (__skb_put_padto(skb, skb->len + padlen, false))
return NULL;
@@ -49,7 +43,7 @@ static struct sk_buff *ksz_xmit(struct sk_buff *skb, struct net_device *dev)
nskb = skb;
} else {
nskb = alloc_skb(NET_IP_ALIGN + skb->len +
- padlen + KSZ_INGRESS_TAG_LEN, GFP_ATOMIC);
+ padlen + len, GFP_ATOMIC);
if (!nskb)
return NULL;
skb_reserve(nskb, NET_IP_ALIGN);
@@ -70,33 +64,88 @@ static struct sk_buff *ksz_xmit(struct sk_buff *skb, struct net_device *dev)
consume_skb(skb);
}
- tag = skb_put(nskb, KSZ_INGRESS_TAG_LEN);
- tag[0] = 0;
- tag[1] = 1 << p->dp->index; /* destination port */
+ set_tag(skb_put(nskb, len), skb_mac_header(nskb), p->dp->index);
return nskb;
}
static struct sk_buff *ksz_rcv(struct sk_buff *skb, struct net_device *dev,
- struct packet_type *pt)
+ struct packet_type *pt,
+ int (*get_tag)(u8 *tag, int *port))
{
u8 *tag;
+ int len;
int source_port;
tag = skb_tail_pointer(skb) - KSZ_EGRESS_TAG_LEN;
- source_port = tag[0] & 7;
+ len = get_tag(tag, &source_port);
skb->dev = dsa_master_get_slave(dev, 0, source_port);
if (!skb->dev)
return NULL;
- pskb_trim_rcsum(skb, skb->len - KSZ_EGRESS_TAG_LEN);
+ pskb_trim_rcsum(skb, skb->len - len);
return skb;
}
-const struct dsa_device_ops ksz_netdev_ops = {
- .xmit = ksz_xmit,
- .rcv = ksz_rcv,
+/* For Ingress (Host -> KSZ9477), 2 bytes are added before FCS.
+ * ---------------------------------------------------------------------------
+ * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|tag1(1byte)|FCS(4bytes)
+ * ---------------------------------------------------------------------------
+ * tag0 : Prioritization (not used now)
+ * tag1 : each bit represents port (eg, 0x01=port1, 0x02=port2, 0x10=port5)
+ *
+ * For Egress (KSZ9477 -> Host), 1 byte is added before FCS.
+ * ---------------------------------------------------------------------------
+ * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|FCS(4bytes)
+ * ---------------------------------------------------------------------------
+ * tag0 : zero-based value represents port
+ * (eg, 0x00=port1, 0x02=port3, 0x06=port7)
+ */
+
+#define KSZ9477_INGRESS_TAG_LEN 2
+#define KSZ9477_PTP_TAG_LEN 4
+#define KSZ9477_PTP_TAG_INDICATION 0x80
+
+#define KSZ9477_TAIL_TAG_OVERRIDE BIT(9)
+#define KSZ9477_TAIL_TAG_LOOKUP BIT(10)
+
+static int ksz9477_get_tag(u8 *tag, int *port)
+{
+ int len = KSZ_EGRESS_TAG_LEN;
+
+ /* Extra 4-bytes PTP timestamp */
+ if (tag[0] & KSZ9477_PTP_TAG_INDICATION)
+ len += KSZ9477_PTP_TAG_LEN;
+ *port = tag[0] & 7;
+ return len;
+}
+
+static void ksz9477_set_tag(void *ptr, u8 *addr, int p)
+{
+ u16 *tag = (u16 *)ptr;
+
+ *tag = 1 << p;
+ if (!memcmp(addr, special_mult_addr, ETH_ALEN))
+ *tag |= KSZ9477_TAIL_TAG_OVERRIDE;
+ *tag = cpu_to_be16(*tag);
+}
+
+static struct sk_buff *ksz9477_xmit(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ return ksz_xmit(skb, dev, KSZ9477_INGRESS_TAG_LEN, ksz9477_set_tag);
+}
+
+static struct sk_buff *ksz9477_rcv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *pt)
+{
+ return ksz_rcv(skb, dev, pt, ksz9477_get_tag);
+}
+
+const struct dsa_device_ops ksz9477_netdev_ops = {
+ .xmit = ksz9477_xmit,
+ .rcv = ksz9477_rcv,
};
--
1.9.1
^ permalink raw reply related
* [PATCH RFC] Add other KSZ switch support so that patch check does not complain
From: Tristram.Ha @ 2017-10-06 20:33 UTC (permalink / raw)
To: Andrew Lunn, Florian Fainelli, Pavel Machek, Ruediger Schmitt
Cc: Tristram Ha, muvarov, nathan.leigh.conrad, vivien.didelot,
UNGLinuxDriver, netdev, linux-kernel
From: Tristram Ha <Tristram.Ha@microchip.com>
Add other KSZ switch support so that patch check does not complain.
Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
---
Documentation/devicetree/bindings/net/dsa/ksz.txt | 189 ++++++++++++++++------
1 file changed, 136 insertions(+), 53 deletions(-)
diff --git a/Documentation/devicetree/bindings/net/dsa/ksz.txt b/Documentation/devicetree/bindings/net/dsa/ksz.txt
index fd23904..705f9d9 100644
--- a/Documentation/devicetree/bindings/net/dsa/ksz.txt
+++ b/Documentation/devicetree/bindings/net/dsa/ksz.txt
@@ -3,8 +3,15 @@ Microchip KSZ Series Ethernet switches
Required properties:
-- compatible: For external switch chips, compatible string must be exactly one
- of: "microchip,ksz9477"
+- compatible: "microchip,ksz9477",
+ "microchip,ksz8795",
+ "microchip,ksz8794",
+ "microchip,ksz8765",
+ "microchip,ksz8895",
+ "microchip,ksz8864",
+ "microchip,ksz8873",
+ "microchip,ksz8863",
+ "microchip,ksz8463"
See Documentation/devicetree/bindings/dsa/dsa.txt for a list of additional
required and optional properties.
@@ -13,58 +20,134 @@ Examples:
Ethernet switch connected via SPI to the host, CPU port wired to eth0:
- eth0: ethernet@10001000 {
- fixed-link {
- speed = <1000>;
- full-duplex;
- };
- };
+ eth0: ethernet@10001000 {
+ fixed-link {
+ speed = <1000>;
+ full-duplex;
+ };
+ };
- spi1: spi@f8008000 {
- pinctrl-0 = <&pinctrl_spi_ksz>;
- cs-gpios = <&pioC 25 0>;
- id = <1>;
+ spi1: spi@f8008000 {
+ cs-gpios = <&pioC 25 0>;
+ id = <1>;
- ksz9477: ksz9477@0 {
- compatible = "microchip,ksz9477";
- reg = <0>;
+ ksz9477: ksz9477@0 {
+ compatible = "microchip,ksz9477";
+ reg = <0>;
- spi-max-frequency = <44000000>;
- spi-cpha;
- spi-cpol;
+ spi-max-frequency = <44000000>;
+ spi-cpha;
+ spi-cpol;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>;
+ label = "lan1";
+ };
+ port@1 {
+ reg = <1>;
+ label = "lan2";
+ };
+ port@2 {
+ reg = <2>;
+ label = "lan3";
+ };
+ port@3 {
+ reg = <3>;
+ label = "lan4";
+ };
+ port@4 {
+ reg = <4>;
+ label = "lan5";
+ };
+ port@5 {
+ reg = <5>;
+ label = "cpu";
+ ethernet = <ð0>;
+ fixed-link {
+ speed = <1000>;
+ full-duplex;
+ };
+ };
+ port@6 {
+ reg = <6>;
+ label = "lan6";
+ fixed-link {
+ speed = <1000>;
+ full-duplex;
+ };
+ };
+ };
+ };
+ ksz8794: ksz8794@0 {
+ compatible = "microchip,ksz8794";
+ reg = <0>;
+
+ spi-max-frequency = <30000000>;
+ spi-cpha;
+ spi-cpol;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>;
+ label = "lan1";
+ };
+ port@1 {
+ reg = <1>;
+ label = "lan2";
+ };
+ port@2 {
+ reg = <2>;
+ label = "lan3";
+ };
+ port@3 {
+ reg = <4>;
+ label = "cpu";
+ ethernet = <ð0>;
+ fixed-link {
+ speed = <100>;
+ full-duplex;
+ };
+ };
+ };
+ };
+ ksz8864: ksz8864@0 {
+ compatible = "microchip,ksz8864";
+ reg = <0>;
+
+ spi-max-frequency = <12000000>;
+ spi-cpha;
+ spi-cpol;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <1>;
+ label = "lan1";
+ };
+ port@1 {
+ reg = <2>;
+ label = "lan2";
+ };
+ port@2 {
+ reg = <3>;
+ label = "lan3";
+ };
+ port@3 {
+ reg = <4>;
+ label = "cpu";
+ ethernet = <ð0>;
+ fixed-link {
+ speed = <100>;
+ full-duplex;
+ };
+ };
+ };
+ };
+ };
- ports {
- #address-cells = <1>;
- #size-cells = <0>;
- port@0 {
- reg = <0>;
- label = "lan1";
- };
- port@1 {
- reg = <1>;
- label = "lan2";
- };
- port@2 {
- reg = <2>;
- label = "lan3";
- };
- port@3 {
- reg = <3>;
- label = "lan4";
- };
- port@4 {
- reg = <4>;
- label = "lan5";
- };
- port@5 {
- reg = <5>;
- label = "cpu";
- ethernet = <ð0>;
- fixed-link {
- speed = <1000>;
- full-duplex;
- };
- };
- };
- };
- };
--
1.9.1
^ permalink raw reply related
* [PATCH v1 RFC 0/1] Add Microchip KSZ8795 DSA driver
From: Tristram.Ha @ 2017-10-06 20:33 UTC (permalink / raw)
To: Andrew Lunn, Florian Fainelli, Pavel Machek, Ruediger Schmitt
Cc: Tristram Ha, muvarov, nathan.leigh.conrad, vivien.didelot,
UNGLinuxDriver, netdev, linux-kernel
From: Tristram Ha <Tristram.Ha@microchip.com>
This patch requires the previous patches for Microchip KSZ9477 DSA driver.
v1
- Return error codes instead of numbers
- Add more comments to clarify operation
- Use ksz8795 prefix to indicate KSZ8795 specific code
- Simplify MIB counter reading code
- Switch driver code is not accessed from tag_ksz.c
Tristram Ha (1):
Add Microchip KSZ8795 DSA driver.
drivers/net/dsa/microchip/Kconfig | 17 +
drivers/net/dsa/microchip/Makefile | 2 +
drivers/net/dsa/microchip/ksz8795.c | 1372 +++++++++++++++++++++++++++++++
drivers/net/dsa/microchip/ksz8795_reg.h | 1016 +++++++++++++++++++++++
drivers/net/dsa/microchip/ksz8795_spi.c | 166 ++++
drivers/net/dsa/microchip/ksz_priv.h | 1 +
include/net/dsa.h | 1 +
net/dsa/Kconfig | 4 +
net/dsa/dsa.c | 3 +
net/dsa/dsa_priv.h | 1 +
net/dsa/tag_ksz.c | 37 +
11 files changed, 2620 insertions(+)
create mode 100644 drivers/net/dsa/microchip/ksz8795.c
create mode 100644 drivers/net/dsa/microchip/ksz8795_reg.h
create mode 100644 drivers/net/dsa/microchip/ksz8795_spi.c
--
1.9.1
^ permalink raw reply
* [PATCH v1 RFC 1/1] Add Microchip KSZ8795 DSA driver
From: Tristram.Ha @ 2017-10-06 20:33 UTC (permalink / raw)
To: Andrew Lunn, Florian Fainelli, Pavel Machek, Ruediger Schmitt
Cc: Tristram Ha, muvarov, nathan.leigh.conrad, vivien.didelot,
UNGLinuxDriver, netdev, linux-kernel
In-Reply-To: <1507322023-15182-1-git-send-email-Tristram.Ha@microchip.com>
From: Tristram Ha <Tristram.Ha@microchip.com>
Add Microchip KSZ8795 DSA driver.
Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
---
drivers/net/dsa/microchip/Kconfig | 17 +
drivers/net/dsa/microchip/Makefile | 2 +
drivers/net/dsa/microchip/ksz8795.c | 1372 +++++++++++++++++++++++++++++++
drivers/net/dsa/microchip/ksz8795_reg.h | 1016 +++++++++++++++++++++++
drivers/net/dsa/microchip/ksz8795_spi.c | 166 ++++
drivers/net/dsa/microchip/ksz_priv.h | 1 +
include/net/dsa.h | 1 +
net/dsa/Kconfig | 4 +
net/dsa/dsa.c | 3 +
net/dsa/dsa_priv.h | 1 +
net/dsa/tag_ksz.c | 37 +
11 files changed, 2620 insertions(+)
create mode 100644 drivers/net/dsa/microchip/ksz8795.c
create mode 100644 drivers/net/dsa/microchip/ksz8795_reg.h
create mode 100644 drivers/net/dsa/microchip/ksz8795_spi.c
diff --git a/drivers/net/dsa/microchip/Kconfig b/drivers/net/dsa/microchip/Kconfig
index ab8f9f6..cb95d3d 100644
--- a/drivers/net/dsa/microchip/Kconfig
+++ b/drivers/net/dsa/microchip/Kconfig
@@ -10,3 +10,20 @@ config MICROCHIP_KSZ9477_SPI_DRIVER
depends on MICROCHIP_KSZ9477 && SPI
help
Select to enable support for registering switches configured through SPI.
+
+menuconfig MICROCHIP_KSZ8795
+ tristate "Microchip KSZ8795 series switch support"
+ depends on NET_DSA
+ select NET_DSA_TAG_KSZ8795
+ help
+ This driver adds support for Microchip KSZ8795 switch chips.
+
+config MICROCHIP_KSZ8795_SPI_DRIVER
+ tristate "KSZ8795 series SPI connected switch driver"
+ depends on MICROCHIP_KSZ8795 && SPI
+ default y
+ help
+ This driver accesses KSZ8795 chip through SPI.
+
+ It is required to use the KSZ8795 switch driver as the only access
+ is through SPI.
diff --git a/drivers/net/dsa/microchip/Makefile b/drivers/net/dsa/microchip/Makefile
index 13dd8f0..99a283e 100644
--- a/drivers/net/dsa/microchip/Makefile
+++ b/drivers/net/dsa/microchip/Makefile
@@ -1,2 +1,4 @@
obj-$(CONFIG_MICROCHIP_KSZ9477) += ksz9477.o ksz_common.o
obj-$(CONFIG_MICROCHIP_KSZ9477_SPI_DRIVER) += ksz9477_spi.o
+obj-$(CONFIG_MICROCHIP_KSZ8795) += ksz8795.o ksz_common.o
+obj-$(CONFIG_MICROCHIP_KSZ8795_SPI_DRIVER) += ksz8795_spi.o
diff --git a/drivers/net/dsa/microchip/ksz8795.c b/drivers/net/dsa/microchip/ksz8795.c
new file mode 100644
index 0000000..7e727d3
--- /dev/null
+++ b/drivers/net/dsa/microchip/ksz8795.c
@@ -0,0 +1,1372 @@
+/*
+ * Microchip KSZ8795 switch driver
+ *
+ * Copyright (C) 2017 Microchip Technology Inc.
+ * Tristram Ha <Tristram.Ha@microchip.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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/delay.h>
+#include <linux/export.h>
+#include <linux/gpio.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_data/microchip-ksz.h>
+#include <linux/phy.h>
+#include <linux/etherdevice.h>
+#include <linux/if_bridge.h>
+#include <net/dsa.h>
+#include <net/switchdev.h>
+
+#include "ksz_priv.h"
+#include "ksz_common.h"
+#include "ksz8795_reg.h"
+
+static const struct {
+ char string[ETH_GSTRING_LEN];
+} mib_names[TOTAL_SWITCH_COUNTER_NUM] = {
+ { "rx_hi" },
+ { "rx_undersize" },
+ { "rx_fragments" },
+ { "rx_oversize" },
+ { "rx_jabbers" },
+ { "rx_symbol_err" },
+ { "rx_crc_err" },
+ { "rx_align_err" },
+ { "rx_mac_ctrl" },
+ { "rx_pause" },
+ { "rx_bcast" },
+ { "rx_mcast" },
+ { "rx_ucast" },
+ { "rx_64_or_less" },
+ { "rx_65_127" },
+ { "rx_128_255" },
+ { "rx_256_511" },
+ { "rx_512_1023" },
+ { "rx_1024_1522" },
+ { "rx_1523_2000" },
+ { "rx_2001" },
+ { "tx_hi" },
+ { "tx_late_col" },
+ { "tx_pause" },
+ { "tx_bcast" },
+ { "tx_mcast" },
+ { "tx_ucast" },
+ { "tx_deferred" },
+ { "tx_total_col" },
+ { "tx_exc_col" },
+ { "tx_single_col" },
+ { "tx_mult_col" },
+ { "rx_total" },
+ { "tx_total" },
+ { "rx_discards" },
+ { "tx_discards" },
+};
+
+static int ksz8795_reset_switch(struct ksz_device *dev)
+{
+ /* reset switch */
+ ksz_write8(dev, REG_POWER_MANAGEMENT_1,
+ SW_SOFTWARE_POWER_DOWN << SW_POWER_MANAGEMENT_MODE_S);
+ ksz_write8(dev, REG_POWER_MANAGEMENT_1, 0);
+
+ return 0;
+}
+
+static void ksz8795_set_prio_queue(struct ksz_device *dev, int port, int queue)
+{
+ u8 hi;
+ u8 lo;
+
+ /* Number of queues can only be 1, 2, or 4. */
+ switch (queue) {
+ case 4:
+ case 3:
+ queue = PORT_QUEUE_SPLIT_4;
+ break;
+ case 2:
+ queue = PORT_QUEUE_SPLIT_2;
+ break;
+ default:
+ queue = PORT_QUEUE_SPLIT_1;
+ }
+ ksz_pread8(dev, port, REG_PORT_CTRL_0, &lo);
+ ksz_pread8(dev, port, P_DROP_TAG_CTRL, &hi);
+ lo &= ~PORT_QUEUE_SPLIT_L;
+ if (queue & PORT_QUEUE_SPLIT_2)
+ lo |= PORT_QUEUE_SPLIT_L;
+ hi &= ~PORT_QUEUE_SPLIT_H;
+ if (queue & PORT_QUEUE_SPLIT_4)
+ hi |= PORT_QUEUE_SPLIT_H;
+ ksz_pwrite8(dev, port, REG_PORT_CTRL_0, lo);
+ ksz_pwrite8(dev, port, P_DROP_TAG_CTRL, hi);
+
+ /* Default is port based for egress rate limit. */
+ if (queue != PORT_QUEUE_SPLIT_1)
+ ksz_cfg(dev, REG_SW_CTRL_19, SW_OUT_RATE_LIMIT_QUEUE_BASED,
+ true);
+}
+
+static void ksz8795_r_mib_cnt(struct ksz_device *dev, int port, u16 addr,
+ u64 *cnt)
+{
+ u32 data;
+ u16 ctrl_addr;
+ u8 check;
+ int loop;
+
+ ctrl_addr = addr + SWITCH_COUNTER_NUM * port;
+ ctrl_addr |= IND_ACC_TABLE(TABLE_MIB | TABLE_READ);
+
+ mutex_lock(&dev->alu_mutex);
+ ksz_write16(dev, REG_IND_CTRL_0, ctrl_addr);
+
+ /* It is almost guaranteed to always read the valid bit because of
+ * slow SPI speed.
+ */
+ for (loop = 2; loop > 0; loop--) {
+ ksz_read8(dev, REG_IND_MIB_CHECK, &check);
+
+ if (check & MIB_COUNTER_VALID) {
+ ksz_read32(dev, REG_IND_DATA_LO, &data);
+ if (check & MIB_COUNTER_OVERFLOW)
+ *cnt += MIB_COUNTER_VALUE + 1;
+ *cnt += data & MIB_COUNTER_VALUE;
+ break;
+ }
+ }
+ mutex_unlock(&dev->alu_mutex);
+}
+
+static void ksz8795_r_mib_pkt(struct ksz_device *dev, int port, u16 addr,
+ u64 *dropped, u64 *cnt)
+{
+ u32 data;
+ u16 ctrl_addr;
+ u8 check;
+ int loop;
+
+ addr -= SWITCH_COUNTER_NUM;
+ ctrl_addr = (KS_MIB_TOTAL_RX_1 - KS_MIB_TOTAL_RX_0) * port;
+ ctrl_addr += addr + KS_MIB_TOTAL_RX_0;
+ ctrl_addr |= IND_ACC_TABLE(TABLE_MIB | TABLE_READ);
+
+ mutex_lock(&dev->alu_mutex);
+ ksz_write16(dev, REG_IND_CTRL_0, ctrl_addr);
+
+ /* It is almost guaranteed to always read the valid bit because of
+ * slow SPI speed.
+ */
+ for (loop = 2; loop > 0; loop--) {
+ ksz_read8(dev, REG_IND_MIB_CHECK, &check);
+
+ if (check & MIB_COUNTER_VALID) {
+ ksz_read32(dev, REG_IND_DATA_LO, &data);
+ if (addr < 2) {
+ u64 total;
+
+ total = check & MIB_TOTAL_BYTES_H;
+ total <<= 32;
+ *cnt += total;
+ *cnt += data;
+ if (check & MIB_COUNTER_OVERFLOW) {
+ total = MIB_TOTAL_BYTES_H + 1;
+ total <<= 32;
+ *cnt += total;
+ }
+ } else {
+ if (check & MIB_COUNTER_OVERFLOW)
+ *cnt += MIB_PACKET_DROPPED + 1;
+ *cnt += data & MIB_PACKET_DROPPED;
+ }
+ break;
+ }
+ }
+ mutex_unlock(&dev->alu_mutex);
+}
+
+static void ksz8795_freeze_mib(struct ksz_device *dev, int port, bool freeze)
+{
+ /* enable the port for flush/freeze function */
+ if (freeze)
+ ksz_cfg(dev, REG_SW_CTRL_6, 1 << port, true);
+ ksz_cfg(dev, REG_SW_CTRL_6, SW_MIB_COUNTER_FREEZE, freeze);
+
+ /* disable the port after freeze is done */
+ if (!freeze)
+ ksz_cfg(dev, REG_SW_CTRL_6, 1 << port, false);
+}
+
+static void ksz8795_port_init_cnt(struct ksz_device *dev, int port)
+{
+ struct ksz_port_mib *mib = &dev->ports[port].mib;
+
+ /* flush all enabled port MIB counters */
+ ksz_cfg(dev, REG_SW_CTRL_6, 1 << port, true);
+ ksz_cfg(dev, REG_SW_CTRL_6, SW_MIB_COUNTER_FLUSH, true);
+ ksz_cfg(dev, REG_SW_CTRL_6, 1 << port, false);
+
+ mib->cnt_ptr = 0;
+
+ /* Some ports may not have MIB counters before SWITCH_COUNTER_NUM. */
+ while (mib->cnt_ptr < dev->reg_mib_cnt) {
+ dev->dev_ops->r_mib_cnt(dev, port, mib->cnt_ptr,
+ &mib->counters[mib->cnt_ptr]);
+ ++mib->cnt_ptr;
+ }
+
+ /* Some ports may not have MIB counters after SWITCH_COUNTER_NUM. */
+ while (mib->cnt_ptr < dev->mib_cnt) {
+ dev->dev_ops->r_mib_pkt(dev, port, mib->cnt_ptr,
+ NULL, &mib->counters[mib->cnt_ptr]);
+ ++mib->cnt_ptr;
+ }
+ mib->cnt_ptr = 0;
+ memset(mib->counters, 0, dev->mib_cnt * sizeof(u64));
+}
+
+static void ksz8795_r_table(struct ksz_device *dev, int table, u16 addr,
+ u64 *data)
+{
+ u16 ctrl_addr;
+
+ ctrl_addr = IND_ACC_TABLE(table | TABLE_READ) | addr;
+
+ mutex_lock(&dev->alu_mutex);
+ ksz_write16(dev, REG_IND_CTRL_0, ctrl_addr);
+ ksz_get(dev, REG_IND_DATA_HI, data, sizeof(u64));
+ mutex_unlock(&dev->alu_mutex);
+ *data = be64_to_cpu(*data);
+}
+
+static void ksz8795_w_table(struct ksz_device *dev, int table, u16 addr,
+ u64 data)
+{
+ u16 ctrl_addr;
+
+ ctrl_addr = IND_ACC_TABLE(table) | addr;
+ data = cpu_to_be64(data);
+
+ mutex_lock(&dev->alu_mutex);
+ ksz_set(dev, REG_IND_DATA_HI, &data, sizeof(u64));
+ ksz_write16(dev, REG_IND_CTRL_0, ctrl_addr);
+ mutex_unlock(&dev->alu_mutex);
+}
+
+static int ksz8795_valid_dyn_entry(struct ksz_device *dev, u8 *data)
+{
+ int timeout = 100;
+
+ do {
+ ksz_read8(dev, REG_IND_DATA_CHECK, data);
+ timeout--;
+ } while ((*data & DYNAMIC_MAC_TABLE_NOT_READY) && timeout);
+
+ /* Entry is not ready for accessing. */
+ if (*data & DYNAMIC_MAC_TABLE_NOT_READY) {
+ return -EAGAIN;
+ /* Entry is ready for accessing. */
+ } else {
+ ksz_read8(dev, REG_IND_DATA_8, data);
+
+ /* There is no valid entry in the table. */
+ if (*data & DYNAMIC_MAC_TABLE_MAC_EMPTY)
+ return -ENXIO;
+ }
+ return 0;
+}
+
+static int ksz8795_r_dyn_mac_table(struct ksz_device *dev, u16 addr,
+ u8 *mac_addr, u8 *fid, u8 *src_port,
+ u8 *timestamp, u16 *entries)
+{
+ u32 data_hi;
+ u32 data_lo;
+ u16 ctrl_addr;
+ int rc;
+ u8 data;
+
+ ctrl_addr = IND_ACC_TABLE(TABLE_DYNAMIC_MAC | TABLE_READ) | addr;
+
+ mutex_lock(&dev->alu_mutex);
+ ksz_write16(dev, REG_IND_CTRL_0, ctrl_addr);
+
+ rc = ksz8795_valid_dyn_entry(dev, &data);
+ if (rc == -EAGAIN) {
+ if (addr == 0)
+ *entries = 0;
+ } else if (rc == -ENXIO) {
+ *entries = 0;
+ /* At least one valid entry in the table. */
+ } else {
+ u64 buf;
+ int cnt;
+
+ ksz_get(dev, REG_IND_DATA_HI, &buf, sizeof(buf));
+ buf = be64_to_cpu(buf);
+ data_hi = (u32)(buf >> 32);
+ data_lo = (u32)buf;
+
+ /* Check out how many valid entry in the table. */
+ cnt = data & DYNAMIC_MAC_TABLE_ENTRIES_H;
+ cnt <<= DYNAMIC_MAC_ENTRIES_H_S;
+ cnt |= (data_hi & DYNAMIC_MAC_TABLE_ENTRIES) >>
+ DYNAMIC_MAC_ENTRIES_S;
+ *entries = cnt + 1;
+
+ *fid = (data_hi & DYNAMIC_MAC_TABLE_FID) >>
+ DYNAMIC_MAC_FID_S;
+ *src_port = (data_hi & DYNAMIC_MAC_TABLE_SRC_PORT) >>
+ DYNAMIC_MAC_SRC_PORT_S;
+ *timestamp = (data_hi & DYNAMIC_MAC_TABLE_TIMESTAMP) >>
+ DYNAMIC_MAC_TIMESTAMP_S;
+
+ mac_addr[5] = (u8)data_lo;
+ mac_addr[4] = (u8)(data_lo >> 8);
+ mac_addr[3] = (u8)(data_lo >> 16);
+ mac_addr[2] = (u8)(data_lo >> 24);
+
+ mac_addr[1] = (u8)data_hi;
+ mac_addr[0] = (u8)(data_hi >> 8);
+ rc = 0;
+ }
+ mutex_unlock(&dev->alu_mutex);
+
+ return rc;
+}
+
+static int ksz8795_r_sta_mac_table(struct ksz_device *dev, u16 addr,
+ struct alu_struct *alu)
+{
+ u64 data;
+ u32 data_hi;
+ u32 data_lo;
+
+ ksz8795_r_table(dev, TABLE_STATIC_MAC, addr, &data);
+ data_hi = data >> 32;
+ data_lo = (u32)data;
+ if (data_hi & (STATIC_MAC_TABLE_VALID | STATIC_MAC_TABLE_OVERRIDE)) {
+ alu->mac[5] = (u8)data_lo;
+ alu->mac[4] = (u8)(data_lo >> 8);
+ alu->mac[3] = (u8)(data_lo >> 16);
+ alu->mac[2] = (u8)(data_lo >> 24);
+ alu->mac[1] = (u8)data_hi;
+ alu->mac[0] = (u8)(data_hi >> 8);
+ alu->port_forward = (data_hi & STATIC_MAC_TABLE_FWD_PORTS) >>
+ STATIC_MAC_FWD_PORTS_S;
+ alu->is_override =
+ (data_hi & STATIC_MAC_TABLE_OVERRIDE) ? 1 : 0;
+ data_hi >>= 1;
+ alu->is_use_fid = (data_hi & STATIC_MAC_TABLE_USE_FID) ? 1 : 0;
+ alu->fid = (data_hi & STATIC_MAC_TABLE_FID) >>
+ STATIC_MAC_FID_S;
+ return 0;
+ }
+ return -ENXIO;
+}
+
+static void ksz8795_w_sta_mac_table(struct ksz_device *dev, u16 addr,
+ struct alu_struct *alu)
+{
+ u64 data;
+ u32 data_hi;
+ u32 data_lo;
+
+ data_lo = ((u32)alu->mac[2] << 24) |
+ ((u32)alu->mac[3] << 16) |
+ ((u32)alu->mac[4] << 8) | alu->mac[5];
+ data_hi = ((u32)alu->mac[0] << 8) | alu->mac[1];
+ data_hi |= (u32)alu->port_forward << STATIC_MAC_FWD_PORTS_S;
+
+ if (alu->is_override)
+ data_hi |= STATIC_MAC_TABLE_OVERRIDE;
+ if (alu->is_use_fid) {
+ data_hi |= STATIC_MAC_TABLE_USE_FID;
+ data_hi |= (u32)alu->fid << STATIC_MAC_FID_S;
+ }
+ if (alu->is_static)
+ data_hi |= STATIC_MAC_TABLE_VALID;
+ else
+ data_hi &= ~STATIC_MAC_TABLE_OVERRIDE;
+
+ data = (u64)data_hi << 32 | data_lo;
+ ksz8795_w_table(dev, TABLE_STATIC_MAC, addr, data);
+}
+
+static inline void ksz8795_from_vlan(u16 vlan, u8 *fid, u8 *member, u8 *valid)
+{
+ *fid = vlan & VLAN_TABLE_FID;
+ *member = (vlan & VLAN_TABLE_MEMBERSHIP) >> VLAN_TABLE_MEMBERSHIP_S;
+ *valid = !!(vlan & VLAN_TABLE_VALID);
+}
+
+static inline void ksz8795_to_vlan(u8 fid, u8 member, u8 valid, u16 *vlan)
+{
+ *vlan = fid;
+ *vlan |= (u16)member << VLAN_TABLE_MEMBERSHIP_S;
+ if (valid)
+ *vlan |= VLAN_TABLE_VALID;
+}
+
+static void ksz8795_r_vlan_entries(struct ksz_device *dev, u16 addr)
+{
+ u64 data;
+ int i;
+
+ ksz8795_r_table(dev, TABLE_VLAN, addr, &data);
+ addr *= 4;
+ for (i = 0; i < 4; i++) {
+ dev->vlan_cache[addr + i].table[0] = (u16)data;
+ data >>= VLAN_TABLE_S;
+ }
+}
+
+static void ksz8795_r_vlan_table(struct ksz_device *dev, u16 vid, u16 *vlan)
+{
+ u64 buf;
+ u16 *data = (u16 *)&buf;
+ u16 addr;
+ int index;
+
+ addr = vid / 4;
+ index = vid & 3;
+ ksz8795_r_table(dev, TABLE_VLAN, addr, &buf);
+ *vlan = data[index];
+}
+
+static void ksz8795_w_vlan_table(struct ksz_device *dev, u16 vid, u16 vlan)
+{
+ u64 buf;
+ u16 *data = (u16 *)&buf;
+ u16 addr;
+ int index;
+
+ addr = vid / 4;
+ index = vid & 3;
+ ksz8795_r_table(dev, TABLE_VLAN, addr, &buf);
+ data[index] = vlan;
+ dev->vlan_cache[vid].table[0] = vlan;
+ ksz8795_w_table(dev, TABLE_VLAN, addr, buf);
+}
+
+static void ksz8795_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val)
+{
+ struct ksz_port *port;
+ u8 ctrl;
+ u8 restart;
+ u8 link;
+ u8 speed;
+ u8 p = phy;
+ u16 data = 0;
+ int processed = true;
+
+ port = &dev->ports[p];
+ switch (reg) {
+ case PHY_REG_CTRL:
+ ksz_pread8(dev, p, P_NEG_RESTART_CTRL, &restart);
+ ksz_pread8(dev, p, P_SPEED_STATUS, &speed);
+ ksz_pread8(dev, p, P_FORCE_CTRL, &ctrl);
+ if (restart & PORT_PHY_LOOPBACK)
+ data |= PHY_LOOPBACK;
+ if (ctrl & PORT_FORCE_100_MBIT)
+ data |= PHY_SPEED_100MBIT;
+ if (!(ctrl & PORT_AUTO_NEG_DISABLE))
+ data |= PHY_AUTO_NEG_ENABLE;
+ if (restart & PORT_POWER_DOWN)
+ data |= PHY_POWER_DOWN;
+ if (restart & PORT_AUTO_NEG_RESTART)
+ data |= PHY_AUTO_NEG_RESTART;
+ if (ctrl & PORT_FORCE_FULL_DUPLEX)
+ data |= PHY_FULL_DUPLEX;
+ if (speed & PORT_HP_MDIX)
+ data |= PHY_HP_MDIX;
+ if (restart & PORT_FORCE_MDIX)
+ data |= PHY_FORCE_MDIX;
+ if (restart & PORT_AUTO_MDIX_DISABLE)
+ data |= PHY_AUTO_MDIX_DISABLE;
+ if (restart & PORT_TX_DISABLE)
+ data |= PHY_TRANSMIT_DISABLE;
+ if (restart & PORT_LED_OFF)
+ data |= PHY_LED_DISABLE;
+ break;
+ case PHY_REG_STATUS:
+ ksz_pread8(dev, p, P_LINK_STATUS, &link);
+ data = PHY_100BTX_FD_CAPABLE |
+ PHY_100BTX_CAPABLE |
+ PHY_10BT_FD_CAPABLE |
+ PHY_10BT_CAPABLE |
+ PHY_AUTO_NEG_CAPABLE;
+ if (link & PORT_AUTO_NEG_COMPLETE)
+ data |= PHY_AUTO_NEG_ACKNOWLEDGE;
+ if (link & PORT_STAT_LINK_GOOD)
+ data |= PHY_LINK_STATUS;
+ break;
+ case PHY_REG_ID_1:
+ data = KSZ8795_ID_HI;
+ break;
+ case PHY_REG_ID_2:
+ data = KSZ8795_ID_LO;
+ break;
+ case PHY_REG_AUTO_NEGOTIATION:
+ ksz_pread8(dev, p, P_LOCAL_CTRL, &ctrl);
+ data = PHY_AUTO_NEG_802_3;
+ if (ctrl & PORT_AUTO_NEG_SYM_PAUSE)
+ data |= PHY_AUTO_NEG_SYM_PAUSE;
+ if (ctrl & PORT_AUTO_NEG_100BTX_FD)
+ data |= PHY_AUTO_NEG_100BTX_FD;
+ if (ctrl & PORT_AUTO_NEG_100BTX)
+ data |= PHY_AUTO_NEG_100BTX;
+ if (ctrl & PORT_AUTO_NEG_10BT_FD)
+ data |= PHY_AUTO_NEG_10BT_FD;
+ if (ctrl & PORT_AUTO_NEG_10BT)
+ data |= PHY_AUTO_NEG_10BT;
+ break;
+ case PHY_REG_REMOTE_CAPABILITY:
+ ksz_pread8(dev, p, P_REMOTE_STATUS, &link);
+ data = PHY_AUTO_NEG_802_3;
+ if (link & PORT_REMOTE_SYM_PAUSE)
+ data |= PHY_AUTO_NEG_SYM_PAUSE;
+ if (link & PORT_REMOTE_100BTX_FD)
+ data |= PHY_AUTO_NEG_100BTX_FD;
+ if (link & PORT_REMOTE_100BTX)
+ data |= PHY_AUTO_NEG_100BTX;
+ if (link & PORT_REMOTE_10BT_FD)
+ data |= PHY_AUTO_NEG_10BT_FD;
+ if (link & PORT_REMOTE_10BT)
+ data |= PHY_AUTO_NEG_10BT;
+ if (data & ~PHY_AUTO_NEG_802_3)
+ data |= PHY_REMOTE_ACKNOWLEDGE_NOT;
+ break;
+ default:
+ processed = false;
+ break;
+ }
+ if (processed)
+ *val = data;
+}
+
+static void ksz8795_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val)
+{
+ u8 ctrl;
+ u8 restart;
+ u8 speed;
+ u8 data;
+ u8 p = phy;
+
+ switch (reg) {
+ case PHY_REG_CTRL:
+
+ /* Do not support PHY reset function. */
+ if (val & PHY_RESET)
+ break;
+ ksz_pread8(dev, p, P_SPEED_STATUS, &speed);
+ data = speed;
+ if (val & PHY_HP_MDIX)
+ data |= PORT_HP_MDIX;
+ else
+ data &= ~PORT_HP_MDIX;
+ if (data != speed)
+ ksz_pwrite8(dev, p, P_SPEED_STATUS, data);
+ ksz_pread8(dev, p, P_FORCE_CTRL, &ctrl);
+ data = ctrl;
+ if (!(val & PHY_AUTO_NEG_ENABLE))
+ data |= PORT_AUTO_NEG_DISABLE;
+ else
+ data &= ~PORT_AUTO_NEG_DISABLE;
+
+ /* Fiber port does not support auto-negotiation. */
+ if (dev->ports[p].fiber)
+ data |= PORT_AUTO_NEG_DISABLE;
+ if (val & PHY_SPEED_100MBIT)
+ data |= PORT_FORCE_100_MBIT;
+ else
+ data &= ~PORT_FORCE_100_MBIT;
+ if (val & PHY_FULL_DUPLEX)
+ data |= PORT_FORCE_FULL_DUPLEX;
+ else
+ data &= ~PORT_FORCE_FULL_DUPLEX;
+ if (data != ctrl)
+ ksz_pwrite8(dev, p, P_FORCE_CTRL, data);
+ ksz_pread8(dev, p, P_NEG_RESTART_CTRL, &restart);
+ data = restart;
+ if (val & PHY_LED_DISABLE)
+ data |= PORT_LED_OFF;
+ else
+ data &= ~PORT_LED_OFF;
+ if (val & PHY_TRANSMIT_DISABLE)
+ data |= PORT_TX_DISABLE;
+ else
+ data &= ~PORT_TX_DISABLE;
+ if (val & PHY_AUTO_NEG_RESTART)
+ data |= PORT_AUTO_NEG_RESTART;
+ else
+ data &= ~(PORT_AUTO_NEG_RESTART);
+ if (val & PHY_POWER_DOWN)
+ data |= PORT_POWER_DOWN;
+ else
+ data &= ~PORT_POWER_DOWN;
+ if (val & PHY_AUTO_MDIX_DISABLE)
+ data |= PORT_AUTO_MDIX_DISABLE;
+ else
+ data &= ~PORT_AUTO_MDIX_DISABLE;
+ if (val & PHY_FORCE_MDIX)
+ data |= PORT_FORCE_MDIX;
+ else
+ data &= ~PORT_FORCE_MDIX;
+ if (val & PHY_LOOPBACK)
+ data |= PORT_PHY_LOOPBACK;
+ else
+ data &= ~PORT_PHY_LOOPBACK;
+ if (data != restart)
+ ksz_pwrite8(dev, p, P_NEG_RESTART_CTRL, data);
+ break;
+ case PHY_REG_AUTO_NEGOTIATION:
+ ksz_pread8(dev, p, P_LOCAL_CTRL, &ctrl);
+ data = ctrl;
+ data &= ~(PORT_AUTO_NEG_SYM_PAUSE |
+ PORT_AUTO_NEG_100BTX_FD |
+ PORT_AUTO_NEG_100BTX |
+ PORT_AUTO_NEG_10BT_FD |
+ PORT_AUTO_NEG_10BT);
+ if (val & PHY_AUTO_NEG_SYM_PAUSE)
+ data |= PORT_AUTO_NEG_SYM_PAUSE;
+ if (val & PHY_AUTO_NEG_100BTX_FD)
+ data |= PORT_AUTO_NEG_100BTX_FD;
+ if (val & PHY_AUTO_NEG_100BTX)
+ data |= PORT_AUTO_NEG_100BTX;
+ if (val & PHY_AUTO_NEG_10BT_FD)
+ data |= PORT_AUTO_NEG_10BT_FD;
+ if (val & PHY_AUTO_NEG_10BT)
+ data |= PORT_AUTO_NEG_10BT;
+ if (data != ctrl)
+ ksz_pwrite8(dev, p, P_LOCAL_CTRL, data);
+ break;
+ default:
+ break;
+ }
+}
+
+static enum dsa_tag_protocol ksz8795_get_tag_protocol(struct dsa_switch *ds)
+{
+ return DSA_TAG_PROTO_KSZ8795;
+}
+
+static void ksz8795_get_strings(struct dsa_switch *ds, int port, uint8_t *buf)
+{
+ int i;
+
+ for (i = 0; i < TOTAL_SWITCH_COUNTER_NUM; i++) {
+ memcpy(buf + i * ETH_GSTRING_LEN, mib_names[i].string,
+ ETH_GSTRING_LEN);
+ }
+}
+
+static const u8 stp_multicast_addr[] = {
+ 0x01, 0x80, 0xC2, 0x00, 0x00, 0x00
+};
+
+static void ksz8795_cfg_port_member(struct ksz_device *dev, int port,
+ u8 member)
+{
+ u8 data;
+
+ ksz_pread8(dev, port, P_MIRROR_CTRL, &data);
+ data &= ~PORT_VLAN_MEMBERSHIP;
+ data |= (member & dev->port_mask);
+ ksz_pwrite8(dev, port, P_MIRROR_CTRL, data);
+ dev->ports[port].member = member;
+}
+
+static void ksz8795_port_stp_state_set(struct dsa_switch *ds, int port,
+ u8 state)
+{
+ struct ksz_device *dev = ds->priv;
+ struct ksz_port *p = &dev->ports[port];
+ u8 data;
+ int member = -1;
+
+ ksz_pread8(dev, port, P_STP_CTRL, &data);
+ data &= ~(PORT_TX_ENABLE | PORT_RX_ENABLE | PORT_LEARN_DISABLE);
+
+ switch (state) {
+ case BR_STATE_DISABLED:
+ data |= PORT_LEARN_DISABLE;
+ if (port < SWITCH_PORT_NUM)
+ member = 0;
+ break;
+ case BR_STATE_LISTENING:
+ data |= (PORT_RX_ENABLE | PORT_LEARN_DISABLE);
+ if (port < SWITCH_PORT_NUM &&
+ p->stp_state == BR_STATE_DISABLED)
+ member = dev->host_mask | p->vid_member;
+ break;
+ case BR_STATE_LEARNING:
+ data |= PORT_RX_ENABLE;
+ break;
+ case BR_STATE_FORWARDING:
+ data |= (PORT_TX_ENABLE | PORT_RX_ENABLE);
+
+ /* This function is also used internally. */
+ if (port == dev->cpu_port)
+ break;
+
+ /* Port is a member of a bridge. */
+ if (dev->br_member & (1 << port)) {
+ dev->member |= (1 << port);
+ member = dev->member;
+ } else {
+ member = dev->host_mask | p->vid_member;
+ }
+ break;
+ case BR_STATE_BLOCKING:
+ data |= PORT_LEARN_DISABLE;
+ if (port < SWITCH_PORT_NUM &&
+ p->stp_state == BR_STATE_DISABLED)
+ member = dev->host_mask | p->vid_member;
+ break;
+ default:
+ dev_err(ds->dev, "invalid STP state: %d\n", state);
+ return;
+ }
+
+ ksz_pwrite8(dev, port, P_STP_CTRL, data);
+ p->stp_state = state;
+ if (data & PORT_RX_ENABLE)
+ dev->rx_ports |= (1 << port);
+ else
+ dev->rx_ports &= ~(1 << port);
+ if (data & PORT_TX_ENABLE)
+ dev->tx_ports |= (1 << port);
+ else
+ dev->tx_ports &= ~(1 << port);
+
+ /* Port membership may share register with STP state. */
+ if (member >= 0 && member != p->member)
+ ksz8795_cfg_port_member(dev, port, (u8)member);
+
+ /* Check if forwarding needs to be updated. */
+ if (state != BR_STATE_FORWARDING) {
+ if (dev->br_member & (1 << port))
+ dev->member &= ~(1 << port);
+ }
+
+ /* When topology has changed the function ksz_update_port_member
+ * should be called to modify port forwarding behavior. However
+ * as the offload_fwd_mark indication cannot be reported here
+ * the switch forwarding function is not enabled.
+ */
+}
+
+static void ksz8795_flush_dyn_mac_table(struct ksz_device *dev, int port)
+{
+ struct ksz_port *p;
+ int cnt;
+ int first;
+ int index;
+ u8 learn[TOTAL_PORT_NUM];
+
+ if ((uint)port < TOTAL_PORT_NUM) {
+ first = port;
+ cnt = port + 1;
+ } else {
+ /* Flush all ports. */
+ first = 0;
+ cnt = dev->mib_port_cnt;
+ }
+ for (index = first; index < cnt; index++) {
+ p = &dev->ports[index];
+ if (!p->on)
+ continue;
+ ksz_pread8(dev, index, P_STP_CTRL, &learn[index]);
+ if (!(learn[index] & PORT_LEARN_DISABLE))
+ ksz_pwrite8(dev, index, P_STP_CTRL,
+ learn[index] | PORT_LEARN_DISABLE);
+ }
+ ksz_cfg(dev, S_FLUSH_TABLE_CTRL, SW_FLUSH_DYN_MAC_TABLE, true);
+ for (index = first; index < cnt; index++) {
+ p = &dev->ports[index];
+ if (!p->on)
+ continue;
+ if (!(learn[index] & PORT_LEARN_DISABLE))
+ ksz_pwrite8(dev, index, P_STP_CTRL, learn[index]);
+ }
+}
+
+static int ksz8795_port_vlan_filtering(struct dsa_switch *ds, int port,
+ bool flag)
+{
+ struct ksz_device *dev = ds->priv;
+
+ ksz_cfg(dev, S_MIRROR_CTRL, SW_VLAN_ENABLE, flag);
+
+ return 0;
+}
+
+static void ksz8795_port_vlan_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct switchdev_trans *trans)
+{
+ struct ksz_device *dev = ds->priv;
+ bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
+ u16 data;
+ u16 vid;
+ u8 fid;
+ u8 member;
+ u8 valid;
+ u16 new_pvid = 0;
+
+ ksz_port_cfg(dev, port, P_TAG_CTRL, PORT_REMOVE_TAG, untagged);
+
+ for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
+ ksz8795_r_vlan_table(dev, vid, &data);
+ ksz8795_from_vlan(data, &fid, &member, &valid);
+
+ /* First time to setup the VLAN entry. */
+ if (!valid) {
+ /* Need to find a way to map VID to FID. */
+ fid = 1;
+ valid = 1;
+ }
+ member |= BIT(port);
+
+ ksz8795_to_vlan(fid, member, valid, &data);
+ ksz8795_w_vlan_table(dev, vid, data);
+
+ /* change PVID */
+ if (vlan->flags & BRIDGE_VLAN_INFO_PVID)
+ new_pvid = vid;
+ }
+
+ if (new_pvid) {
+ ksz_pread16(dev, port, REG_PORT_CTRL_VID, &vid);
+ vid &= 0xfff;
+ vid |= new_pvid;
+ ksz_pwrite16(dev, port, REG_PORT_CTRL_VID, vid);
+ }
+}
+
+static int ksz8795_port_vlan_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan)
+{
+ struct ksz_device *dev = ds->priv;
+ bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
+ u16 data;
+ u16 vid;
+ u16 pvid;
+ u8 fid;
+ u8 member;
+ u8 valid;
+ u16 new_pvid = 0;
+
+ ksz_pread16(dev, port, REG_PORT_CTRL_VID, &pvid);
+ pvid = pvid & 0xFFF;
+
+ ksz_port_cfg(dev, port, P_TAG_CTRL, PORT_REMOVE_TAG, untagged);
+
+ for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
+ ksz8795_r_vlan_table(dev, vid, &data);
+ ksz8795_from_vlan(data, &fid, &member, &valid);
+
+ member &= ~BIT(port);
+
+ /* Invalidate the entry if no more member. */
+ if (!member) {
+ fid = 0;
+ valid = 0;
+ }
+
+ if (pvid == vid)
+ new_pvid = 1;
+
+ ksz8795_to_vlan(fid, member, valid, &data);
+ ksz8795_w_vlan_table(dev, vid, data);
+ }
+
+ if (new_pvid != pvid)
+ ksz_pwrite16(dev, port, REG_PORT_CTRL_VID, pvid);
+
+ return 0;
+}
+
+static int ksz8795_port_mirror_add(struct dsa_switch *ds, int port,
+ struct dsa_mall_mirror_tc_entry *mirror,
+ bool ingress)
+{
+ struct ksz_device *dev = ds->priv;
+
+ if (ingress) {
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, true);
+ dev->mirror_rx |= (1 << port);
+ } else {
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, true);
+ dev->mirror_tx |= (1 << port);
+ }
+
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_SNIFFER, false);
+
+ /* configure mirror port */
+ if (dev->mirror_rx || dev->mirror_tx)
+ ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL,
+ PORT_MIRROR_SNIFFER, true);
+
+ return 0;
+}
+
+static void ksz8795_port_mirror_del(struct dsa_switch *ds, int port,
+ struct dsa_mall_mirror_tc_entry *mirror)
+{
+ struct ksz_device *dev = ds->priv;
+ u8 data;
+
+ if (mirror->ingress) {
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, false);
+ dev->mirror_rx &= ~(1 << port);
+ } else {
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, false);
+ dev->mirror_tx &= ~(1 << port);
+ }
+
+ ksz_pread8(dev, port, P_MIRROR_CTRL, &data);
+
+ if (!dev->mirror_rx && !dev->mirror_tx)
+ ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL,
+ PORT_MIRROR_SNIFFER, false);
+}
+
+static void ksz8795_phy_setup(struct ksz_device *dev, int port,
+ struct phy_device *phy)
+{
+ /* SUPPORTED_Pause can be removed to disable flow control when
+ * rate limiting is used.
+ */
+ phy->supported &= ~SUPPORTED_Asym_Pause;
+ phy->advertising = phy->supported;
+}
+
+static void ksz8795_port_setup(struct ksz_device *dev, int port, bool cpu_port)
+{
+ u8 data8;
+ u8 member;
+ struct ksz_port *p = &dev->ports[port];
+
+ /* enable broadcast storm limit */
+ ksz_port_cfg(dev, port, P_BCAST_STORM_CTRL, PORT_BROADCAST_STORM, true);
+
+ ksz8795_set_prio_queue(dev, port, 4);
+
+ /* disable DiffServ priority */
+ ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_DIFFSERV_ENABLE, false);
+
+ /* replace priority */
+ ksz_port_cfg(dev, port, P_802_1P_CTRL, PORT_802_1P_REMAPPING, false);
+
+ /* enable 802.1p priority */
+ ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_802_1P_ENABLE, true);
+
+ if (cpu_port) {
+ /* Configure MII interface for proper network communication. */
+ ksz_read8(dev, REG_PORT_5_CTRL_6, &data8);
+ data8 &= ~PORT_INTERFACE_TYPE;
+ data8 &= ~PORT_GMII_1GPS_MODE;
+ switch (dev->interface) {
+ case PHY_INTERFACE_MODE_MII:
+ p->speed = SPEED_100;
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ data8 |= PORT_INTERFACE_RMII;
+ p->speed = SPEED_100;
+ break;
+ case PHY_INTERFACE_MODE_GMII:
+ data8 |= PORT_GMII_1GPS_MODE;
+ data8 |= PORT_INTERFACE_GMII;
+ p->speed = SPEED_1000;
+ break;
+ default:
+ data8 &= ~PORT_RGMII_ID_IN_ENABLE;
+ data8 &= ~PORT_RGMII_ID_OUT_ENABLE;
+ if (dev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+ dev->interface == PHY_INTERFACE_MODE_RGMII_RXID)
+ data8 |= PORT_RGMII_ID_IN_ENABLE;
+ if (dev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+ dev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
+ data8 |= PORT_RGMII_ID_OUT_ENABLE;
+ data8 |= PORT_GMII_1GPS_MODE;
+ data8 |= PORT_INTERFACE_RGMII;
+ p->speed = SPEED_1000;
+ break;
+ }
+ ksz_write8(dev, REG_PORT_5_CTRL_6, data8);
+ p->duplex = 1;
+
+ member = dev->port_mask;
+ dev->on_ports = dev->host_mask;
+ dev->live_ports = dev->host_mask;
+ } else {
+ member = dev->host_mask | p->vid_member;
+ dev->on_ports |= (1 << port);
+
+ /* Link was detected before port is enabled. */
+ if (p->link_up)
+ dev->live_ports |= (1 << port);
+ }
+ ksz8795_cfg_port_member(dev, port, member);
+}
+
+static void ksz8795_config_cpu_port(struct dsa_switch *ds)
+{
+ struct ksz_device *dev = ds->priv;
+ struct ksz_port *p;
+ int i;
+ u8 remote;
+
+ ds->num_ports = dev->port_cnt + 1;
+
+ /* Switch marks the maximum frame with extra byte as oversize. */
+ ksz_cfg(dev, REG_SW_CTRL_2, SW_LEGAL_PACKET_DISABLE, true);
+ ksz_cfg(dev, S_TAIL_TAG_CTRL, SW_TAIL_TAG_ENABLE, true);
+
+ p = &dev->ports[dev->cpu_port];
+ p->vid_member = dev->port_mask;
+ p->on = 1;
+
+ ksz8795_port_setup(dev, dev->cpu_port, true);
+ dev->member = dev->host_mask;
+
+ for (i = 0; i < SWITCH_PORT_NUM; i++) {
+ p = &dev->ports[i];
+
+ /* Initialize to non-zero so that ksz_cfg_port_member() will
+ * be called.
+ */
+ p->vid_member = (1 << i);
+ p->member = dev->port_mask;
+ ksz8795_port_stp_state_set(ds, i, BR_STATE_DISABLED);
+
+ /* Last port may be disabled. */
+ if (i == dev->port_cnt)
+ break;
+ p->on = 1;
+ p->phy = 1;
+ }
+ for (i = 0; i < dev->phy_port_cnt; i++) {
+ p = &dev->ports[i];
+ if (!p->on)
+ continue;
+ ksz_pread8(dev, i, P_REMOTE_STATUS, &remote);
+ if (remote & PORT_FIBER_MODE)
+ p->fiber = 1;
+ if (p->fiber)
+ ksz_port_cfg(dev, i, P_STP_CTRL, PORT_FORCE_FLOW_CTRL,
+ true);
+ else
+ ksz_port_cfg(dev, i, P_STP_CTRL, PORT_FORCE_FLOW_CTRL,
+ false);
+ }
+}
+
+static int ksz8795_set_addr(struct dsa_switch *ds, u8 *addr)
+{
+ /* Setting the switch MAC address is required for the self-address
+ * filtering feature. However, there is a flaw in the feature and
+ * so it cannot be used.
+ * The dynamic MAC table also does not learn the address so unicast
+ * forwarding is not operating correctly for the host.
+ */
+ return 0;
+}
+
+static int ksz8795_setup(struct dsa_switch *ds)
+{
+ u8 data8;
+ u16 data16;
+ u32 value;
+ int i;
+ struct alu_struct alu;
+ struct ksz_device *dev = ds->priv;
+ int ret = 0;
+
+ dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table),
+ dev->num_vlans, GFP_KERNEL);
+ if (!dev->vlan_cache)
+ return -ENOMEM;
+
+ ret = ksz8795_reset_switch(dev);
+ if (ret) {
+ dev_err(ds->dev, "failed to reset switch\n");
+ return ret;
+ }
+
+ ksz_cfg(dev, S_REPLACE_VID_CTRL, SW_FLOW_CTRL, true);
+
+ /* Enable automatic fast aging when link changed detected. */
+ ksz_cfg(dev, S_LINK_AGING_CTRL, SW_LINK_AUTO_AGING, true);
+
+ ksz_read8(dev, REG_SW_CTRL_1, &data8);
+
+ /* Enable aggressive back off algorithm in half duplex mode. */
+ data8 |= SW_AGGR_BACKOFF;
+ ksz_write8(dev, REG_SW_CTRL_1, data8);
+
+ ksz_read8(dev, REG_SW_CTRL_2, &data8);
+
+ /* Make sure unicast VLAN boundary is set as default. */
+ data8 |= UNICAST_VLAN_BOUNDARY;
+
+ /* Enable no excessive collision drop. */
+ data8 |= NO_EXC_COLLISION_DROP;
+ ksz_write8(dev, REG_SW_CTRL_2, data8);
+
+ ksz8795_config_cpu_port(ds);
+
+ ksz_cfg(dev, REG_SW_CTRL_2, MULTICAST_STORM_DISABLE, true);
+
+ ksz_cfg(dev, S_REPLACE_VID_CTRL, SW_REPLACE_VID, false);
+
+ ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false);
+
+ /* set broadcast storm protection 10% rate */
+ data8 = BROADCAST_STORM_PROT_RATE;
+ value = ((u32)BROADCAST_STORM_VALUE * data8) / 100;
+ if (value > BROADCAST_STORM_RATE)
+ value = BROADCAST_STORM_RATE;
+ ksz_read16(dev, S_REPLACE_VID_CTRL, &data16);
+ data16 &= ~BROADCAST_STORM_RATE;
+ data16 |= value;
+ ksz_write16(dev, S_REPLACE_VID_CTRL, data16);
+
+ for (i = 0; i < VLAN_TABLE_ENTRIES; i++)
+ ksz8795_r_vlan_entries(dev, i);
+
+ /* Setup STP address for STP operation. */
+ memset(&alu, 0, sizeof(alu));
+ memcpy(alu.mac, stp_multicast_addr, ETH_ALEN);
+ alu.is_static = true;
+ alu.is_override = true;
+ alu.port_forward = dev->host_mask;
+
+ ksz8795_w_sta_mac_table(dev, 0, &alu);
+
+ ksz_init_mib_timer(dev);
+
+ return 0;
+}
+
+static const struct dsa_switch_ops ksz8795_switch_ops = {
+ .get_tag_protocol = ksz8795_get_tag_protocol,
+ .setup = ksz8795_setup,
+ .set_addr = ksz8795_set_addr,
+ .phy_read = ksz_phy_read16,
+ .phy_write = ksz_phy_write16,
+ .adjust_link = ksz_adjust_link,
+ .port_enable = ksz_enable_port,
+ .port_disable = ksz_disable_port,
+ .get_strings = ksz8795_get_strings,
+ .get_ethtool_stats = ksz_get_ethtool_stats,
+ .get_sset_count = ksz_sset_count,
+ .port_bridge_join = ksz_port_bridge_join,
+ .port_bridge_leave = ksz_port_bridge_leave,
+ .port_stp_state_set = ksz8795_port_stp_state_set,
+ .port_fast_age = ksz_port_fast_age,
+ .port_vlan_filtering = ksz8795_port_vlan_filtering,
+ .port_vlan_prepare = ksz_port_vlan_prepare,
+ .port_vlan_add = ksz8795_port_vlan_add,
+ .port_vlan_del = ksz8795_port_vlan_del,
+ .port_fdb_dump = ksz_port_fdb_dump,
+ .port_mdb_prepare = ksz_port_mdb_prepare,
+ .port_mdb_add = ksz_port_mdb_add,
+ .port_mdb_del = ksz_port_mdb_del,
+ .port_mirror_add = ksz8795_port_mirror_add,
+ .port_mirror_del = ksz8795_port_mirror_del,
+};
+
+static u32 ksz8795_get_port_addr(int port, int offset)
+{
+ return PORT_CTRL_ADDR(port, offset);
+}
+
+static int ksz8795_switch_detect(struct ksz_device *dev)
+{
+ u16 id16;
+ u8 id1;
+ u8 id2;
+ int ret;
+
+ /* read chip id */
+ ret = ksz_read16(dev, REG_CHIP_ID0, &id16);
+ if (ret)
+ return ret;
+
+ id1 = id16 >> 8;
+ id2 = id16 & SW_CHIP_ID_M;
+ if (id1 != FAMILY_ID ||
+ (id2 != CHIP_ID_94 && id2 != CHIP_ID_95))
+ return -ENODEV;
+
+ dev->mib_port_cnt = TOTAL_PORT_NUM;
+ dev->phy_port_cnt = SWITCH_PORT_NUM;
+ dev->port_cnt = SWITCH_PORT_NUM;
+
+ if (id2 == CHIP_ID_95) {
+ u8 val;
+
+ id2 = 0x95;
+ ksz_read8(dev, REG_PORT_1_STATUS_0, &val);
+ if (val & PORT_FIBER_MODE)
+ id2 = 0x65;
+ } else if (id2 == CHIP_ID_94) {
+ dev->port_cnt--;
+ dev->last_port = dev->port_cnt;
+ id2 = 0x94;
+ }
+ id16 &= ~0xff;
+ id16 |= id2;
+ dev->chip_id = id16;
+
+ dev->cpu_port = dev->mib_port_cnt - 1;
+ dev->host_mask = (1 << dev->cpu_port);
+
+ return 0;
+}
+
+struct ksz_chip_data {
+ u16 chip_id;
+ const char *dev_name;
+ int num_vlans;
+ int num_alus;
+ int num_statics;
+ int cpu_ports;
+ int port_cnt;
+};
+
+static const struct ksz_chip_data ksz8795_switch_chips[] = {
+ {
+ .chip_id = 0x8795,
+ .dev_name = "KSZ8795",
+ .num_vlans = 4096,
+ .num_alus = 0,
+ .num_statics = 8,
+ .cpu_ports = 0x10, /* can be configured as cpu port */
+ .port_cnt = 4, /* total physical port count */
+ },
+ {
+ .chip_id = 0x8794,
+ .dev_name = "KSZ8794",
+ .num_vlans = 4096,
+ .num_alus = 0,
+ .num_statics = 8,
+ .cpu_ports = 0x10, /* can be configured as cpu port */
+ .port_cnt = 3, /* total physical port count */
+ },
+ {
+ .chip_id = 0x8765,
+ .dev_name = "KSZ8765",
+ .num_vlans = 4096,
+ .num_alus = 0,
+ .num_statics = 8,
+ .cpu_ports = 0x10, /* can be configured as cpu port */
+ .port_cnt = 4, /* total physical port count */
+ },
+};
+
+static int ksz8795_switch_init(struct ksz_device *dev)
+{
+ int i;
+
+ mutex_init(&dev->reg_mutex);
+ mutex_init(&dev->stats_mutex);
+ mutex_init(&dev->alu_mutex);
+ mutex_init(&dev->vlan_mutex);
+
+ dev->ds->ops = &ksz8795_switch_ops;
+
+ for (i = 0; i < ARRAY_SIZE(ksz8795_switch_chips); i++) {
+ const struct ksz_chip_data *chip = &ksz8795_switch_chips[i];
+
+ if (dev->chip_id == chip->chip_id) {
+ dev->name = chip->dev_name;
+ dev->num_vlans = chip->num_vlans;
+ dev->num_alus = chip->num_alus;
+ dev->num_statics = chip->num_statics;
+ dev->port_cnt = chip->port_cnt;
+ dev->cpu_ports = chip->cpu_ports;
+
+ break;
+ }
+ }
+
+ /* no switch found */
+ if (!dev->cpu_ports)
+ return -ENODEV;
+
+ dev->port_mask = (1 << dev->port_cnt) - 1;
+ dev->port_mask |= dev->host_mask;
+
+ dev->reg_mib_cnt = SWITCH_COUNTER_NUM;
+ dev->mib_cnt = TOTAL_SWITCH_COUNTER_NUM;
+
+ i = dev->mib_port_cnt;
+ dev->ports = devm_kzalloc(dev->dev, sizeof(struct ksz_port) * i,
+ GFP_KERNEL);
+ if (!dev->ports)
+ return -ENOMEM;
+ for (i = 0; i < dev->mib_port_cnt; i++) {
+ mutex_init(&dev->ports[i].mib.cnt_mutex);
+ dev->ports[i].mib.counters =
+ devm_kzalloc(dev->dev,
+ sizeof(u64) *
+ (TOTAL_SWITCH_COUNTER_NUM + 1),
+ GFP_KERNEL);
+ if (!dev->ports[i].mib.counters)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void ksz8795_switch_exit(struct ksz_device *dev)
+{
+ ksz8795_reset_switch(dev);
+}
+
+static const struct ksz_dev_ops ksz8795_dev_ops = {
+ .get_port_addr = ksz8795_get_port_addr,
+ .cfg_port_member = ksz8795_cfg_port_member,
+ .flush_dyn_mac_table = ksz8795_flush_dyn_mac_table,
+ .phy_setup = ksz8795_phy_setup,
+ .port_setup = ksz8795_port_setup,
+ .r_phy = ksz8795_r_phy,
+ .w_phy = ksz8795_w_phy,
+ .r_dyn_mac_table = ksz8795_r_dyn_mac_table,
+ .r_sta_mac_table = ksz8795_r_sta_mac_table,
+ .w_sta_mac_table = ksz8795_w_sta_mac_table,
+ .r_mib_cnt = ksz8795_r_mib_cnt,
+ .r_mib_pkt = ksz8795_r_mib_pkt,
+ .freeze_mib = ksz8795_freeze_mib,
+ .port_init_cnt = ksz8795_port_init_cnt,
+ .shutdown = ksz8795_reset_switch,
+ .detect = ksz8795_switch_detect,
+ .init = ksz8795_switch_init,
+ .exit = ksz8795_switch_exit,
+};
+
+int ksz8795_switch_register(struct ksz_device *dev)
+{
+ return ksz_switch_register(dev, &ksz8795_dev_ops);
+}
+EXPORT_SYMBOL(ksz8795_switch_register);
+
+MODULE_AUTHOR("Tristram Ha <Tristram.Ha@microchip.com>");
+MODULE_DESCRIPTION("Microchip KSZ8795 Series Switch DSA Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/dsa/microchip/ksz8795_reg.h b/drivers/net/dsa/microchip/ksz8795_reg.h
new file mode 100644
index 0000000..fe2aa8d
--- /dev/null
+++ b/drivers/net/dsa/microchip/ksz8795_reg.h
@@ -0,0 +1,1016 @@
+/**
+ * Microchip KSZ8795 register definitions
+ *
+ * Copyright (c) 2017 Microchip Technology Inc.
+ * Tristram Ha <Tristram.Ha@microchip.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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __KSZ8795_REG_H
+#define __KSZ8795_REG_H
+
+#define KS_PORT_M 0x1F
+
+#define KS_PRIO_M 0x3
+#define KS_PRIO_S 2
+
+#define REG_CHIP_ID0 0x00
+
+#define FAMILY_ID 0x87
+
+#define REG_CHIP_ID1 0x01
+
+#define SW_CHIP_ID_M 0xF0
+#define SW_CHIP_ID_S 4
+#define SW_REVISION_M 0x0E
+#define SW_REVISION_S 1
+#define SW_START 0x01
+
+#define CHIP_ID_94 0x60
+#define CHIP_ID_95 0x90
+
+#define REG_SW_CTRL_0 0x02
+
+#define SW_NEW_BACKOFF BIT(7)
+#define SW_GLOBAL_RESET BIT(6)
+#define SW_FLUSH_DYN_MAC_TABLE BIT(5)
+#define SW_FLUSH_STA_MAC_TABLE BIT(4)
+#define SW_LINK_AUTO_AGING BIT(0)
+
+#define REG_SW_CTRL_1 0x03
+
+#define SW_HUGE_PACKET BIT(6)
+#define SW_TX_FLOW_CTRL_DISABLE BIT(5)
+#define SW_RX_FLOW_CTRL_DISABLE BIT(4)
+#define SW_CHECK_LENGTH BIT(3)
+#define SW_AGING_ENABLE BIT(2)
+#define SW_FAST_AGING BIT(1)
+#define SW_AGGR_BACKOFF BIT(0)
+
+#define REG_SW_CTRL_2 0x04
+
+#define UNICAST_VLAN_BOUNDARY BIT(7)
+#define MULTICAST_STORM_DISABLE BIT(6)
+#define SW_BACK_PRESSURE BIT(5)
+#define FAIR_FLOW_CTRL BIT(4)
+#define NO_EXC_COLLISION_DROP BIT(3)
+#define SW_LEGAL_PACKET_DISABLE BIT(1)
+
+#define REG_SW_CTRL_3 0x05
+ #define WEIGHTED_FAIR_QUEUE_ENABLE BIT(3)
+
+#define SW_VLAN_ENABLE BIT(7)
+#define SW_IGMP_SNOOP BIT(6)
+#define SW_MIRROR_RX_TX BIT(0)
+
+#define REG_SW_CTRL_4 0x06
+
+#define SW_HALF_DUPLEX_FLOW_CTRL BIT(7)
+#define SW_HALF_DUPLEX BIT(6)
+#define SW_FLOW_CTRL BIT(5)
+#define SW_10_MBIT BIT(4)
+#define SW_REPLACE_VID BIT(3)
+#define BROADCAST_STORM_RATE_HI 0x07
+
+#define REG_SW_CTRL_5 0x07
+
+#define BROADCAST_STORM_RATE_LO 0xFF
+#define BROADCAST_STORM_RATE 0x07FF
+
+#define REG_SW_CTRL_6 0x08
+
+#define SW_MIB_COUNTER_FLUSH BIT(7)
+#define SW_MIB_COUNTER_FREEZE BIT(6)
+#define SW_MIB_COUNTER_CTRL_ENABLE KS_PORT_M
+
+#define REG_SW_CTRL_9 0x0B
+
+#define SPI_CLK_125_MHZ 0x80
+#define SPI_CLK_62_5_MHZ 0x40
+#define SPI_CLK_31_25_MHZ 0x00
+
+#define SW_LED_MODE_M 0x3
+#define SW_LED_MODE_S 4
+#define SW_LED_LINK_ACT_SPEED 0
+#define SW_LED_LINK_ACT 1
+#define SW_LED_LINK_ACT_DUPLEX 2
+#define SW_LED_LINK_DUPLEX 3
+
+#define REG_SW_CTRL_10 0x0C
+
+#define SW_TAIL_TAG_ENABLE BIT(1)
+#define SW_PASS_PAUSE BIT(0)
+
+#define REG_SW_CTRL_11 0x0D
+
+#define REG_POWER_MANAGEMENT_1 0x0E
+
+#define SW_PLL_POWER_DOWN BIT(5)
+#define SW_POWER_MANAGEMENT_MODE_M 0x3
+#define SW_POWER_MANAGEMENT_MODE_S 3
+#define SW_POWER_NORMAL 0
+#define SW_ENERGY_DETECTION 1
+#define SW_SOFTWARE_POWER_DOWN 2
+
+#define REG_POWER_MANAGEMENT_2 0x0F
+
+#define REG_PORT_1_CTRL_0 0x10
+#define REG_PORT_2_CTRL_0 0x20
+#define REG_PORT_3_CTRL_0 0x30
+#define REG_PORT_4_CTRL_0 0x40
+#define REG_PORT_5_CTRL_0 0x50
+
+#define PORT_BROADCAST_STORM BIT(7)
+#define PORT_DIFFSERV_ENABLE BIT(6)
+#define PORT_802_1P_ENABLE BIT(5)
+#define PORT_BASED_PRIO_S 3
+#define PORT_BASED_PRIO_M KS_PRIO_M
+#define PORT_BASED_PRIO_0 0
+#define PORT_BASED_PRIO_1 1
+#define PORT_BASED_PRIO_2 2
+#define PORT_BASED_PRIO_3 3
+#define PORT_INSERT_TAG BIT(2)
+#define PORT_REMOVE_TAG BIT(1)
+#define PORT_QUEUE_SPLIT_L BIT(0)
+
+#define REG_PORT_1_CTRL_1 0x11
+#define REG_PORT_2_CTRL_1 0x21
+#define REG_PORT_3_CTRL_1 0x31
+#define REG_PORT_4_CTRL_1 0x41
+#define REG_PORT_5_CTRL_1 0x51
+
+#define PORT_MIRROR_SNIFFER BIT(7)
+#define PORT_MIRROR_RX BIT(6)
+#define PORT_MIRROR_TX BIT(5)
+#define PORT_VLAN_MEMBERSHIP KS_PORT_M
+
+#define REG_PORT_1_CTRL_2 0x12
+#define REG_PORT_2_CTRL_2 0x22
+#define REG_PORT_3_CTRL_2 0x32
+#define REG_PORT_4_CTRL_2 0x42
+#define REG_PORT_5_CTRL_2 0x52
+
+#define PORT_802_1P_REMAPPING BIT(7)
+#define PORT_INGRESS_FILTER BIT(6)
+#define PORT_DISCARD_NON_VID BIT(5)
+#define PORT_FORCE_FLOW_CTRL BIT(4)
+#define PORT_BACK_PRESSURE BIT(3)
+#define PORT_TX_ENABLE BIT(2)
+#define PORT_RX_ENABLE BIT(1)
+#define PORT_LEARN_DISABLE BIT(0)
+
+#define REG_PORT_1_CTRL_3 0x13
+#define REG_PORT_2_CTRL_3 0x23
+#define REG_PORT_3_CTRL_3 0x33
+#define REG_PORT_4_CTRL_3 0x43
+#define REG_PORT_5_CTRL_3 0x53
+#define REG_PORT_1_CTRL_4 0x14
+#define REG_PORT_2_CTRL_4 0x24
+#define REG_PORT_3_CTRL_4 0x34
+#define REG_PORT_4_CTRL_4 0x44
+#define REG_PORT_5_CTRL_4 0x54
+
+#define PORT_DEFAULT_VID 0x0001
+
+#define REG_PORT_1_CTRL_5 0x15
+#define REG_PORT_2_CTRL_5 0x25
+#define REG_PORT_3_CTRL_5 0x35
+#define REG_PORT_4_CTRL_5 0x45
+#define REG_PORT_5_CTRL_5 0x55
+
+#define PORT_ACL_ENABLE BIT(2)
+#define PORT_AUTHEN_MODE 0x3
+#define PORT_AUTHEN_PASS 0
+#define PORT_AUTHEN_BLOCK 1
+#define PORT_AUTHEN_TRAP 2
+
+#define REG_PORT_5_CTRL_6 0x56
+
+#define PORT_MII_INTERNAL_CLOCK BIT(7)
+#define PORT_GMII_1GPS_MODE BIT(6)
+#define PORT_RGMII_ID_IN_ENABLE BIT(4)
+#define PORT_RGMII_ID_OUT_ENABLE BIT(3)
+#define PORT_GMII_MAC_MODE BIT(2)
+#define PORT_INTERFACE_TYPE 0x3
+#define PORT_INTERFACE_MII 0
+#define PORT_INTERFACE_RMII 1
+#define PORT_INTERFACE_GMII 2
+#define PORT_INTERFACE_RGMII 3
+
+#define REG_PORT_1_CTRL_7 0x17
+#define REG_PORT_2_CTRL_7 0x27
+#define REG_PORT_3_CTRL_7 0x37
+#define REG_PORT_4_CTRL_7 0x47
+
+#define PORT_AUTO_NEG_ASYM_PAUSE BIT(5)
+#define PORT_AUTO_NEG_SYM_PAUSE BIT(4)
+#define PORT_AUTO_NEG_100BTX_FD BIT(3)
+#define PORT_AUTO_NEG_100BTX BIT(2)
+#define PORT_AUTO_NEG_10BT_FD BIT(1)
+#define PORT_AUTO_NEG_10BT BIT(0)
+
+#define REG_PORT_1_STATUS_0 0x18
+#define REG_PORT_2_STATUS_0 0x28
+#define REG_PORT_3_STATUS_0 0x38
+#define REG_PORT_4_STATUS_0 0x48
+
+/* For KSZ8765. */
+#define PORT_FIBER_MODE BIT(7)
+
+#define PORT_REMOTE_ASYM_PAUSE BIT(5)
+#define PORT_REMOTE_SYM_PAUSE BIT(4)
+#define PORT_REMOTE_100BTX_FD BIT(3)
+#define PORT_REMOTE_100BTX BIT(2)
+#define PORT_REMOTE_10BT_FD BIT(1)
+#define PORT_REMOTE_10BT BIT(0)
+
+#define REG_PORT_1_STATUS_1 0x19
+#define REG_PORT_2_STATUS_1 0x29
+#define REG_PORT_3_STATUS_1 0x39
+#define REG_PORT_4_STATUS_1 0x49
+
+#define PORT_HP_MDIX BIT(7)
+#define PORT_REVERSED_POLARITY BIT(5)
+#define PORT_TX_FLOW_CTRL BIT(4)
+#define PORT_RX_FLOW_CTRL BIT(3)
+#define PORT_STAT_SPEED_100MBIT BIT(2)
+#define PORT_STAT_FULL_DUPLEX BIT(1)
+
+#define PORT_REMOTE_FAULT BIT(0)
+
+#define REG_PORT_1_LINK_MD_CTRL 0x1A
+#define REG_PORT_2_LINK_MD_CTRL 0x2A
+#define REG_PORT_3_LINK_MD_CTRL 0x3A
+#define REG_PORT_4_LINK_MD_CTRL 0x4A
+
+#define PORT_CABLE_10M_SHORT BIT(7)
+#define PORT_CABLE_DIAG_RESULT_M 0x3
+#define PORT_CABLE_DIAG_RESULT_S 5
+#define PORT_CABLE_STAT_NORMAL 0
+#define PORT_CABLE_STAT_OPEN 1
+#define PORT_CABLE_STAT_SHORT 2
+#define PORT_CABLE_STAT_FAILED 3
+#define PORT_START_CABLE_DIAG BIT(4)
+#define PORT_FORCE_LINK BIT(3)
+#define PORT_POWER_SAVING BIT(2)
+#define PORT_PHY_REMOTE_LOOPBACK BIT(1)
+#define PORT_CABLE_FAULT_COUNTER_H 0x01
+
+#define REG_PORT_1_LINK_MD_RESULT 0x1B
+#define REG_PORT_2_LINK_MD_RESULT 0x2B
+#define REG_PORT_3_LINK_MD_RESULT 0x3B
+#define REG_PORT_4_LINK_MD_RESULT 0x4B
+
+#define PORT_CABLE_FAULT_COUNTER_L 0xFF
+#define PORT_CABLE_FAULT_COUNTER 0x1FF
+
+#define REG_PORT_1_CTRL_9 0x1C
+#define REG_PORT_2_CTRL_9 0x2C
+#define REG_PORT_3_CTRL_9 0x3C
+#define REG_PORT_4_CTRL_9 0x4C
+
+#define PORT_AUTO_NEG_DISABLE BIT(7)
+#define PORT_FORCE_100_MBIT BIT(6)
+#define PORT_FORCE_FULL_DUPLEX BIT(5)
+
+#define REG_PORT_1_CTRL_10 0x1D
+#define REG_PORT_2_CTRL_10 0x2D
+#define REG_PORT_3_CTRL_10 0x3D
+#define REG_PORT_4_CTRL_10 0x4D
+
+#define PORT_LED_OFF BIT(7)
+#define PORT_TX_DISABLE BIT(6)
+#define PORT_AUTO_NEG_RESTART BIT(5)
+#define PORT_POWER_DOWN BIT(3)
+#define PORT_AUTO_MDIX_DISABLE BIT(2)
+#define PORT_FORCE_MDIX BIT(1)
+#define PORT_MAC_LOOPBACK BIT(0)
+
+#define REG_PORT_1_STATUS_2 0x1E
+#define REG_PORT_2_STATUS_2 0x2E
+#define REG_PORT_3_STATUS_2 0x3E
+#define REG_PORT_4_STATUS_2 0x4E
+
+#define PORT_MDIX_STATUS BIT(7)
+#define PORT_AUTO_NEG_COMPLETE BIT(6)
+#define PORT_STAT_LINK_GOOD BIT(5)
+
+#define REG_PORT_1_STATUS_3 0x1F
+#define REG_PORT_2_STATUS_3 0x2F
+#define REG_PORT_3_STATUS_3 0x3F
+#define REG_PORT_4_STATUS_3 0x4F
+
+#define PORT_PHY_LOOPBACK BIT(7)
+#define PORT_PHY_ISOLATE BIT(5)
+#define PORT_PHY_SOFT_RESET BIT(4)
+#define PORT_PHY_FORCE_LINK BIT(3)
+#define PORT_PHY_MODE_M 0x7
+#define PHY_MODE_IN_AUTO_NEG 1
+#define PHY_MODE_10BT_HALF 2
+#define PHY_MODE_100BT_HALF 3
+#define PHY_MODE_10BT_FULL 5
+#define PHY_MODE_100BT_FULL 6
+#define PHY_MODE_ISOLDATE 7
+
+#define REG_PORT_CTRL_0 0x00
+#define REG_PORT_CTRL_1 0x01
+#define REG_PORT_CTRL_2 0x02
+#define REG_PORT_CTRL_VID 0x03
+
+#define REG_PORT_CTRL_5 0x05
+
+#define REG_PORT_CTRL_7 0x07
+#define REG_PORT_STATUS_0 0x08
+#define REG_PORT_STATUS_1 0x09
+#define REG_PORT_LINK_MD_CTRL 0x0A
+#define REG_PORT_LINK_MD_RESULT 0x0B
+#define REG_PORT_CTRL_9 0x0C
+#define REG_PORT_CTRL_10 0x0D
+#define REG_PORT_STATUS_2 0x0E
+#define REG_PORT_STATUS_3 0x0F
+
+#define REG_PORT_CTRL_12 0xA0
+#define REG_PORT_CTRL_13 0xA1
+#define REG_PORT_RATE_CTRL_3 0xA2
+#define REG_PORT_RATE_CTRL_2 0xA3
+#define REG_PORT_RATE_CTRL_1 0xA4
+#define REG_PORT_RATE_CTRL_0 0xA5
+#define REG_PORT_RATE_LIMIT 0xA6
+#define REG_PORT_IN_RATE_0 0xA7
+#define REG_PORT_IN_RATE_1 0xA8
+#define REG_PORT_IN_RATE_2 0xA9
+#define REG_PORT_IN_RATE_3 0xAA
+#define REG_PORT_OUT_RATE_0 0xAB
+#define REG_PORT_OUT_RATE_1 0xAC
+#define REG_PORT_OUT_RATE_2 0xAD
+#define REG_PORT_OUT_RATE_3 0xAE
+
+#define PORT_CTRL_ADDR(port, addr) \
+ ((addr) + REG_PORT_1_CTRL_0 + (port) * \
+ (REG_PORT_2_CTRL_0 - REG_PORT_1_CTRL_0))
+
+#define REG_SW_MAC_ADDR_0 0x68
+#define REG_SW_MAC_ADDR_1 0x69
+#define REG_SW_MAC_ADDR_2 0x6A
+#define REG_SW_MAC_ADDR_3 0x6B
+#define REG_SW_MAC_ADDR_4 0x6C
+#define REG_SW_MAC_ADDR_5 0x6D
+
+#define REG_IND_CTRL_0 0x6E
+
+#define TABLE_EXT_SELECT_S 5
+#define TABLE_EEE_V 1
+#define TABLE_ACL_V 2
+#define TABLE_PME_V 4
+#define TABLE_LINK_MD_V 5
+#define TABLE_EEE (TABLE_EEE_V << TABLE_EXT_SELECT_S)
+#define TABLE_ACL (TABLE_ACL_V << TABLE_EXT_SELECT_S)
+#define TABLE_PME (TABLE_PME_V << TABLE_EXT_SELECT_S)
+#define TABLE_LINK_MD (TABLE_LINK_MD << TABLE_EXT_SELECT_S)
+#define TABLE_READ BIT(4)
+#define TABLE_SELECT_S 2
+#define TABLE_STATIC_MAC_V 0
+#define TABLE_VLAN_V 1
+#define TABLE_DYNAMIC_MAC_V 2
+#define TABLE_MIB_V 3
+#define TABLE_STATIC_MAC (TABLE_STATIC_MAC_V << TABLE_SELECT_S)
+#define TABLE_VLAN (TABLE_VLAN_V << TABLE_SELECT_S)
+#define TABLE_DYNAMIC_MAC (TABLE_DYNAMIC_MAC_V << TABLE_SELECT_S)
+#define TABLE_MIB (TABLE_MIB_V << TABLE_SELECT_S)
+
+#define REG_IND_CTRL_1 0x6F
+
+#define TABLE_ENTRY_MASK 0x03FF
+#define TABLE_EXT_ENTRY_MASK 0x0FFF
+
+#define REG_IND_DATA_8 0x70
+#define REG_IND_DATA_7 0x71
+#define REG_IND_DATA_6 0x72
+#define REG_IND_DATA_5 0x73
+#define REG_IND_DATA_4 0x74
+#define REG_IND_DATA_3 0x75
+#define REG_IND_DATA_2 0x76
+#define REG_IND_DATA_1 0x77
+#define REG_IND_DATA_0 0x78
+
+#define REG_IND_DATA_PME_EEE_ACL 0xA0
+
+#define REG_IND_DATA_CHECK REG_IND_DATA_6
+#define REG_IND_MIB_CHECK REG_IND_DATA_4
+#define REG_IND_DATA_HI REG_IND_DATA_7
+#define REG_IND_DATA_LO REG_IND_DATA_3
+
+#define REG_INT_STATUS 0x7C
+#define REG_INT_ENABLE 0x7D
+
+#define INT_PME BIT(4)
+
+#define REG_ACL_INT_STATUS 0x7E
+#define REG_ACL_INT_ENABLE 0x7F
+
+#define INT_PORT_5 BIT(4)
+#define INT_PORT_4 BIT(3)
+#define INT_PORT_3 BIT(2)
+#define INT_PORT_2 BIT(1)
+#define INT_PORT_1 BIT(0)
+
+#define INT_PORT_ALL \
+ (INT_PORT_5 | INT_PORT_4 | INT_PORT_3 | INT_PORT_2 | INT_PORT_1)
+
+#define REG_SW_CTRL_12 0x80
+#define REG_SW_CTRL_13 0x81
+
+#define SWITCH_802_1P_MASK 3
+#define SWITCH_802_1P_BASE 3
+#define SWITCH_802_1P_SHIFT 2
+
+#define SW_802_1P_MAP_M KS_PRIO_M
+#define SW_802_1P_MAP_S KS_PRIO_S
+
+#define REG_SWITCH_CTRL_14 0x82
+
+#define SW_PRIO_MAPPING_M KS_PRIO_M
+#define SW_PRIO_MAPPING_S 6
+#define SW_PRIO_MAP_3_HI 0
+#define SW_PRIO_MAP_2_HI 2
+#define SW_PRIO_MAP_0_LO 3
+
+#define REG_SW_CTRL_15 0x83
+#define REG_SW_CTRL_16 0x84
+#define REG_SW_CTRL_17 0x85
+#define REG_SW_CTRL_18 0x86
+
+#define SW_SELF_ADDR_FILTER_ENABLE BIT(6)
+
+#define REG_SW_UNK_UCAST_CTRL 0x83
+#define REG_SW_UNK_MCAST_CTRL 0x84
+#define REG_SW_UNK_VID_CTRL 0x85
+#define REG_SW_UNK_IP_MCAST_CTRL 0x86
+
+#define SW_UNK_FWD_ENABLE BIT(5)
+#define SW_UNK_FWD_MAP KS_PORT_M
+
+#define REG_SW_CTRL_19 0x87
+
+#define SW_IN_RATE_LIMIT_PERIOD_M 0x3
+#define SW_IN_RATE_LIMIT_PERIOD_S 4
+#define SW_IN_RATE_LIMIT_16_MS 0
+#define SW_IN_RATE_LIMIT_64_MS 1
+#define SW_IN_RATE_LIMIT_256_MS 2
+#define SW_OUT_RATE_LIMIT_QUEUE_BASED BIT(3)
+#define SW_INS_TAG_ENABLE BIT(2)
+
+#define REG_TOS_PRIO_CTRL_0 0x90
+#define REG_TOS_PRIO_CTRL_1 0x91
+#define REG_TOS_PRIO_CTRL_2 0x92
+#define REG_TOS_PRIO_CTRL_3 0x93
+#define REG_TOS_PRIO_CTRL_4 0x94
+#define REG_TOS_PRIO_CTRL_5 0x95
+#define REG_TOS_PRIO_CTRL_6 0x96
+#define REG_TOS_PRIO_CTRL_7 0x97
+#define REG_TOS_PRIO_CTRL_8 0x98
+#define REG_TOS_PRIO_CTRL_9 0x99
+#define REG_TOS_PRIO_CTRL_10 0x9A
+#define REG_TOS_PRIO_CTRL_11 0x9B
+#define REG_TOS_PRIO_CTRL_12 0x9C
+#define REG_TOS_PRIO_CTRL_13 0x9D
+#define REG_TOS_PRIO_CTRL_14 0x9E
+#define REG_TOS_PRIO_CTRL_15 0x9F
+
+#define TOS_PRIO_M KS_PRIO_M
+#define TOS_PRIO_S KS_PRIO_S
+
+#define REG_SW_CTRL_20 0xA3
+
+#define SW_GMII_DRIVE_STRENGTH_S 4
+#define SW_DRIVE_STRENGTH_M 0x7
+#define SW_DRIVE_STRENGTH_2MA 0
+#define SW_DRIVE_STRENGTH_4MA 1
+#define SW_DRIVE_STRENGTH_8MA 2
+#define SW_DRIVE_STRENGTH_12MA 3
+#define SW_DRIVE_STRENGTH_16MA 4
+#define SW_DRIVE_STRENGTH_20MA 5
+#define SW_DRIVE_STRENGTH_24MA 6
+#define SW_DRIVE_STRENGTH_28MA 7
+#define SW_MII_DRIVE_STRENGTH_S 0
+
+#define REG_SW_CTRL_21 0xA4
+
+#define SW_IPV6_MLD_OPTION BIT(3)
+#define SW_IPV6_MLD_SNOOP BIT(2)
+
+#define REG_PORT_1_CTRL_12 0xB0
+#define REG_PORT_2_CTRL_12 0xC0
+#define REG_PORT_3_CTRL_12 0xD0
+#define REG_PORT_4_CTRL_12 0xE0
+#define REG_PORT_5_CTRL_12 0xF0
+
+#define PORT_PASS_ALL BIT(6)
+#define PORT_INS_TAG_FOR_PORT_5_S 3
+#define PORT_INS_TAG_FOR_PORT_5 BIT(3)
+#define PORT_INS_TAG_FOR_PORT_4 BIT(2)
+#define PORT_INS_TAG_FOR_PORT_3 BIT(1)
+#define PORT_INS_TAG_FOR_PORT_2 BIT(0)
+
+#define REG_PORT_1_CTRL_13 0xB1
+#define REG_PORT_2_CTRL_13 0xC1
+#define REG_PORT_3_CTRL_13 0xD1
+#define REG_PORT_4_CTRL_13 0xE1
+#define REG_PORT_5_CTRL_13 0xF1
+
+#define PORT_QUEUE_SPLIT_H BIT(1)
+#define PORT_QUEUE_SPLIT_1 0
+#define PORT_QUEUE_SPLIT_2 1
+#define PORT_QUEUE_SPLIT_4 2
+#define PORT_DROP_TAG BIT(0)
+
+#define REG_PORT_1_CTRL_14 0xB2
+#define REG_PORT_2_CTRL_14 0xC2
+#define REG_PORT_3_CTRL_14 0xD2
+#define REG_PORT_4_CTRL_14 0xE2
+#define REG_PORT_5_CTRL_14 0xF2
+#define REG_PORT_1_CTRL_15 0xB3
+#define REG_PORT_2_CTRL_15 0xC3
+#define REG_PORT_3_CTRL_15 0xD3
+#define REG_PORT_4_CTRL_15 0xE3
+#define REG_PORT_5_CTRL_15 0xF3
+#define REG_PORT_1_CTRL_16 0xB4
+#define REG_PORT_2_CTRL_16 0xC4
+#define REG_PORT_3_CTRL_16 0xD4
+#define REG_PORT_4_CTRL_16 0xE4
+#define REG_PORT_5_CTRL_16 0xF4
+#define REG_PORT_1_CTRL_17 0xB5
+#define REG_PORT_2_CTRL_17 0xC5
+#define REG_PORT_3_CTRL_17 0xD5
+#define REG_PORT_4_CTRL_17 0xE5
+#define REG_PORT_5_CTRL_17 0xF5
+
+#define REG_PORT_1_RATE_CTRL_3 0xB2
+#define REG_PORT_1_RATE_CTRL_2 0xB3
+#define REG_PORT_1_RATE_CTRL_1 0xB4
+#define REG_PORT_1_RATE_CTRL_0 0xB5
+#define REG_PORT_2_RATE_CTRL_3 0xC2
+#define REG_PORT_2_RATE_CTRL_2 0xC3
+#define REG_PORT_2_RATE_CTRL_1 0xC4
+#define REG_PORT_2_RATE_CTRL_0 0xC5
+#define REG_PORT_3_RATE_CTRL_3 0xD2
+#define REG_PORT_3_RATE_CTRL_2 0xD3
+#define REG_PORT_3_RATE_CTRL_1 0xD4
+#define REG_PORT_3_RATE_CTRL_0 0xD5
+#define REG_PORT_4_RATE_CTRL_3 0xE2
+#define REG_PORT_4_RATE_CTRL_2 0xE3
+#define REG_PORT_4_RATE_CTRL_1 0xE4
+#define REG_PORT_4_RATE_CTRL_0 0xE5
+#define REG_PORT_5_RATE_CTRL_3 0xF2
+#define REG_PORT_5_RATE_CTRL_2 0xF3
+#define REG_PORT_5_RATE_CTRL_1 0xF4
+#define REG_PORT_5_RATE_CTRL_0 0xF5
+
+#define RATE_CTRL_ENABLE BIT(7)
+#define RATE_RATIO_M (BIT(7) - 1)
+
+#define PORT_OUT_RATE_ENABLE BIT(7)
+
+#define REG_PORT_1_RATE_LIMIT 0xB6
+#define REG_PORT_2_RATE_LIMIT 0xC6
+#define REG_PORT_3_RATE_LIMIT 0xD6
+#define REG_PORT_4_RATE_LIMIT 0xE6
+#define REG_PORT_5_RATE_LIMIT 0xF6
+
+#define PORT_IN_PORT_BASED_S 6
+#define PORT_RATE_PACKET_BASED_S 5
+#define PORT_IN_FLOW_CTRL_S 4
+#define PORT_IN_LIMIT_MODE_M 0x3
+#define PORT_IN_LIMIT_MODE_S 2
+#define PORT_COUNT_IFG_S 1
+#define PORT_COUNT_PREAMBLE_S 0
+#define PORT_IN_PORT_BASED BIT(PORT_IN_PORT_BASED_S)
+#define PORT_RATE_PACKET_BASED BIT(PORT_RATE_PACKET_BASED_S)
+#define PORT_IN_FLOW_CTRL BIT(PORT_IN_FLOW_CTRL_S)
+#define PORT_IN_ALL 0
+#define PORT_IN_UNICAST 1
+#define PORT_IN_MULTICAST 2
+#define PORT_IN_BROADCAST 3
+#define PORT_COUNT_IFG BIT(PORT_COUNT_IFG_S)
+#define PORT_COUNT_PREAMBLE BIT(PORT_COUNT_PREAMBLE_S)
+
+#define REG_PORT_1_IN_RATE_0 0xB7
+#define REG_PORT_2_IN_RATE_0 0xC7
+#define REG_PORT_3_IN_RATE_0 0xD7
+#define REG_PORT_4_IN_RATE_0 0xE7
+#define REG_PORT_5_IN_RATE_0 0xF7
+#define REG_PORT_1_IN_RATE_1 0xB8
+#define REG_PORT_2_IN_RATE_1 0xC8
+#define REG_PORT_3_IN_RATE_1 0xD8
+#define REG_PORT_4_IN_RATE_1 0xE8
+#define REG_PORT_5_IN_RATE_1 0xF8
+#define REG_PORT_1_IN_RATE_2 0xB9
+#define REG_PORT_2_IN_RATE_2 0xC9
+#define REG_PORT_3_IN_RATE_2 0xD9
+#define REG_PORT_4_IN_RATE_2 0xE9
+#define REG_PORT_5_IN_RATE_2 0xF9
+#define REG_PORT_1_IN_RATE_3 0xBA
+#define REG_PORT_2_IN_RATE_3 0xCA
+#define REG_PORT_3_IN_RATE_3 0xDA
+#define REG_PORT_4_IN_RATE_3 0xEA
+#define REG_PORT_5_IN_RATE_3 0xFA
+
+#define PORT_IN_RATE_ENABLE BIT(7)
+#define PORT_RATE_LIMIT_M (BIT(7) - 1)
+
+#define REG_PORT_1_OUT_RATE_0 0xBB
+#define REG_PORT_2_OUT_RATE_0 0xCB
+#define REG_PORT_3_OUT_RATE_0 0xDB
+#define REG_PORT_4_OUT_RATE_0 0xEB
+#define REG_PORT_5_OUT_RATE_0 0xFB
+#define REG_PORT_1_OUT_RATE_1 0xBC
+#define REG_PORT_2_OUT_RATE_1 0xCC
+#define REG_PORT_3_OUT_RATE_1 0xDC
+#define REG_PORT_4_OUT_RATE_1 0xEC
+#define REG_PORT_5_OUT_RATE_1 0xFC
+#define REG_PORT_1_OUT_RATE_2 0xBD
+#define REG_PORT_2_OUT_RATE_2 0xCD
+#define REG_PORT_3_OUT_RATE_2 0xDD
+#define REG_PORT_4_OUT_RATE_2 0xED
+#define REG_PORT_5_OUT_RATE_2 0xFD
+#define REG_PORT_1_OUT_RATE_3 0xBE
+#define REG_PORT_2_OUT_RATE_3 0xCE
+#define REG_PORT_3_OUT_RATE_3 0xDE
+#define REG_PORT_4_OUT_RATE_3 0xEE
+#define REG_PORT_5_OUT_RATE_3 0xFE
+
+/* PME */
+
+#define SW_PME_OUTPUT_ENABLE BIT(1)
+#define SW_PME_ACTIVE_HIGH BIT(0)
+
+#define PORT_MAGIC_PACKET_DETECT BIT(2)
+#define PORT_LINK_UP_DETECT BIT(1)
+#define PORT_ENERGY_DETECT BIT(0)
+
+/* ACL */
+
+#define ACL_FIRST_RULE_M 0xF
+
+#define ACL_MODE_M 0x3
+#define ACL_MODE_S 4
+#define ACL_MODE_DISABLE 0
+#define ACL_MODE_LAYER_2 1
+#define ACL_MODE_LAYER_3 2
+#define ACL_MODE_LAYER_4 3
+#define ACL_ENABLE_M 0x3
+#define ACL_ENABLE_S 2
+#define ACL_ENABLE_2_COUNT 0
+#define ACL_ENABLE_2_TYPE 1
+#define ACL_ENABLE_2_MAC 2
+#define ACL_ENABLE_2_BOTH 3
+#define ACL_ENABLE_3_IP 1
+#define ACL_ENABLE_3_SRC_DST_COMP 2
+#define ACL_ENABLE_4_PROTOCOL 0
+#define ACL_ENABLE_4_TCP_PORT_COMP 1
+#define ACL_ENABLE_4_UDP_PORT_COMP 2
+#define ACL_ENABLE_4_TCP_SEQN_COMP 3
+#define ACL_SRC BIT(1)
+#define ACL_EQUAL BIT(0)
+
+#define ACL_MAX_PORT 0xFFFF
+
+#define ACL_MIN_PORT 0xFFFF
+#define ACL_IP_ADDR 0xFFFFFFFF
+#define ACL_TCP_SEQNUM 0xFFFFFFFF
+
+#define ACL_RESERVED 0xF8
+#define ACL_PORT_MODE_M 0x3
+#define ACL_PORT_MODE_S 1
+#define ACL_PORT_MODE_DISABLE 0
+#define ACL_PORT_MODE_EITHER 1
+#define ACL_PORT_MODE_IN_RANGE 2
+#define ACL_PORT_MODE_OUT_OF_RANGE 3
+
+#define ACL_TCP_FLAG_ENABLE BIT(0)
+
+#define ACL_TCP_FLAG_M 0xFF
+
+#define ACL_TCP_FLAG 0xFF
+#define ACL_ETH_TYPE 0xFFFF
+#define ACL_IP_M 0xFFFFFFFF
+
+#define ACL_PRIO_MODE_M 0x3
+#define ACL_PRIO_MODE_S 6
+#define ACL_PRIO_MODE_DISABLE 0
+#define ACL_PRIO_MODE_HIGHER 1
+#define ACL_PRIO_MODE_LOWER 2
+#define ACL_PRIO_MODE_REPLACE 3
+#define ACL_PRIO_M 0x7
+#define ACL_PRIO_S 3
+#define ACL_VLAN_PRIO_REPLACE BIT(2)
+#define ACL_VLAN_PRIO_M 0x7
+#define ACL_VLAN_PRIO_HI_M 0x3
+
+#define ACL_VLAN_PRIO_LO_M 0x8
+#define ACL_VLAN_PRIO_S 7
+#define ACL_MAP_MODE_M 0x3
+#define ACL_MAP_MODE_S 5
+#define ACL_MAP_MODE_DISABLE 0
+#define ACL_MAP_MODE_OR 1
+#define ACL_MAP_MODE_AND 2
+#define ACL_MAP_MODE_REPLACE 3
+#define ACL_MAP_PORT_M 0x1F
+
+#define ACL_CNT_M (BIT(11) - 1)
+#define ACL_CNT_S 5
+#define ACL_MSEC_UNIT BIT(4)
+#define ACL_INTR_MODE BIT(3)
+
+#define REG_PORT_ACL_BYTE_EN_MSB 0x10
+
+#define ACL_BYTE_EN_MSB_M 0x3F
+
+#define REG_PORT_ACL_BYTE_EN_LSB 0x11
+
+#define ACL_ACTION_START 0xA
+#define ACL_ACTION_LEN 2
+#define ACL_INTR_CNT_START 0xB
+#define ACL_RULESET_START 0xC
+#define ACL_RULESET_LEN 2
+#define ACL_TABLE_LEN 14
+
+#define ACL_ACTION_ENABLE 0x000C
+#define ACL_MATCH_ENABLE 0x1FF0
+#define ACL_RULESET_ENABLE 0x2003
+#define ACL_BYTE_ENABLE ((ACL_BYTE_EN_MSB_M << 8) | 0xFF)
+#define ACL_MODE_ENABLE (0x10 << 8)
+
+#define REG_PORT_ACL_CTRL_0 0x12
+
+#define PORT_ACL_WRITE_DONE BIT(6)
+#define PORT_ACL_READ_DONE BIT(5)
+#define PORT_ACL_WRITE BIT(4)
+#define PORT_ACL_INDEX_M 0xF
+
+#define REG_PORT_ACL_CTRL_1 0x13
+
+#define PORT_ACL_FORCE_DLR_MISS BIT(0)
+
+#ifndef PHY_REG_CTRL
+#define PHY_REG_CTRL 0
+
+#define PHY_RESET BIT(15)
+#define PHY_LOOPBACK BIT(14)
+#define PHY_SPEED_100MBIT BIT(13)
+#define PHY_AUTO_NEG_ENABLE BIT(12)
+#define PHY_POWER_DOWN BIT(11)
+#define PHY_MII_DISABLE BIT(10)
+#define PHY_AUTO_NEG_RESTART BIT(9)
+#define PHY_FULL_DUPLEX BIT(8)
+#define PHY_COLLISION_TEST_NOT BIT(7)
+#define PHY_HP_MDIX BIT(5)
+#define PHY_FORCE_MDIX BIT(4)
+#define PHY_AUTO_MDIX_DISABLE BIT(3)
+#define PHY_REMOTE_FAULT_DISABLE BIT(2)
+#define PHY_TRANSMIT_DISABLE BIT(1)
+#define PHY_LED_DISABLE BIT(0)
+
+#define PHY_REG_STATUS 1
+
+#define PHY_100BT4_CAPABLE BIT(15)
+#define PHY_100BTX_FD_CAPABLE BIT(14)
+#define PHY_100BTX_CAPABLE BIT(13)
+#define PHY_10BT_FD_CAPABLE BIT(12)
+#define PHY_10BT_CAPABLE BIT(11)
+#define PHY_MII_SUPPRESS_CAPABLE_NOT BIT(6)
+#define PHY_AUTO_NEG_ACKNOWLEDGE BIT(5)
+#define PHY_REMOTE_FAULT BIT(4)
+#define PHY_AUTO_NEG_CAPABLE BIT(3)
+#define PHY_LINK_STATUS BIT(2)
+#define PHY_JABBER_DETECT_NOT BIT(1)
+#define PHY_EXTENDED_CAPABILITY BIT(0)
+
+#define PHY_REG_ID_1 2
+#define PHY_REG_ID_2 3
+
+#define PHY_REG_AUTO_NEGOTIATION 4
+
+#define PHY_AUTO_NEG_NEXT_PAGE_NOT BIT(15)
+#define PHY_AUTO_NEG_REMOTE_FAULT_NOT BIT(13)
+#define PHY_AUTO_NEG_SYM_PAUSE BIT(10)
+#define PHY_AUTO_NEG_100BT4 BIT(9)
+#define PHY_AUTO_NEG_100BTX_FD BIT(8)
+#define PHY_AUTO_NEG_100BTX BIT(7)
+#define PHY_AUTO_NEG_10BT_FD BIT(6)
+#define PHY_AUTO_NEG_10BT BIT(5)
+#define PHY_AUTO_NEG_SELECTOR 0x001F
+#define PHY_AUTO_NEG_802_3 0x0001
+
+#define PHY_REG_REMOTE_CAPABILITY 5
+
+#define PHY_REMOTE_NEXT_PAGE_NOT BIT(15)
+#define PHY_REMOTE_ACKNOWLEDGE_NOT BIT(14)
+#define PHY_REMOTE_REMOTE_FAULT_NOT BIT(13)
+#define PHY_REMOTE_SYM_PAUSE BIT(10)
+#define PHY_REMOTE_100BTX_FD BIT(8)
+#define PHY_REMOTE_100BTX BIT(7)
+#define PHY_REMOTE_10BT_FD BIT(6)
+#define PHY_REMOTE_10BT BIT(5)
+#endif
+
+#define KSZ8795_ID_HI 0x0022
+#define KSZ8795_ID_LO 0x1550
+
+#define KSZ8795_SW_ID 0x8795
+
+#define PHY_REG_LINK_MD 0x1D
+
+#define PHY_START_CABLE_DIAG BIT(15)
+#define PHY_CABLE_DIAG_RESULT 0x6000
+#define PHY_CABLE_STAT_NORMAL 0x0000
+#define PHY_CABLE_STAT_OPEN 0x2000
+#define PHY_CABLE_STAT_SHORT 0x4000
+#define PHY_CABLE_STAT_FAILED 0x6000
+#define PHY_CABLE_10M_SHORT BIT(12)
+#define PHY_CABLE_FAULT_COUNTER 0x01FF
+
+#define PHY_REG_PHY_CTRL 0x1F
+
+#define PHY_MODE_M 0x7
+#define PHY_MODE_S 8
+#define PHY_STAT_REVERSED_POLARITY BIT(5)
+#define PHY_STAT_MDIX BIT(4)
+#define PHY_FORCE_LINK BIT(3)
+#define PHY_POWER_SAVING_ENABLE BIT(2)
+#define PHY_REMOTE_LOOPBACK BIT(1)
+
+/* Chip resource */
+
+#define PRIO_QUEUES 4
+
+#define KS_PRIO_IN_REG 4
+
+#define TOTAL_PORT_NUM 5
+
+/* Host port can only be last of them. */
+#define SWITCH_PORT_NUM (TOTAL_PORT_NUM - 1)
+
+#define KSZ8795_COUNTER_NUM 0x20
+#define TOTAL_KSZ8795_COUNTER_NUM (KSZ8795_COUNTER_NUM + 4)
+
+#define SWITCH_COUNTER_NUM KSZ8795_COUNTER_NUM
+#define TOTAL_SWITCH_COUNTER_NUM TOTAL_KSZ8795_COUNTER_NUM
+
+/* Common names used by other drivers */
+
+#define P_BCAST_STORM_CTRL REG_PORT_CTRL_0
+#define P_PRIO_CTRL REG_PORT_CTRL_0
+#define P_TAG_CTRL REG_PORT_CTRL_0
+#define P_MIRROR_CTRL REG_PORT_CTRL_1
+#define P_802_1P_CTRL REG_PORT_CTRL_2
+#define P_STP_CTRL REG_PORT_CTRL_2
+#define P_LOCAL_CTRL REG_PORT_CTRL_7
+#define P_REMOTE_STATUS REG_PORT_STATUS_0
+#define P_FORCE_CTRL REG_PORT_CTRL_9
+#define P_NEG_RESTART_CTRL REG_PORT_CTRL_10
+#define P_SPEED_STATUS REG_PORT_STATUS_1
+#define P_LINK_STATUS REG_PORT_STATUS_2
+#define P_PASS_ALL_CTRL REG_PORT_CTRL_12
+#define P_INS_SRC_PVID_CTRL REG_PORT_CTRL_12
+#define P_DROP_TAG_CTRL REG_PORT_CTRL_13
+#define P_RATE_LIMIT_CTRL REG_PORT_RATE_LIMIT
+
+#define S_UNKNOWN_DA_CTRL REG_SWITCH_CTRL_12
+#define S_FORWARD_INVALID_VID_CTRL REG_FORWARD_INVALID_VID
+
+#define S_FLUSH_TABLE_CTRL REG_SW_CTRL_0
+#define S_LINK_AGING_CTRL REG_SW_CTRL_0
+#define S_HUGE_PACKET_CTRL REG_SW_CTRL_1
+#define S_MIRROR_CTRL REG_SW_CTRL_3
+#define S_REPLACE_VID_CTRL REG_SW_CTRL_4
+#define S_PASS_PAUSE_CTRL REG_SW_CTRL_10
+#define S_TAIL_TAG_CTRL REG_SW_CTRL_10
+#define S_802_1P_PRIO_CTRL REG_SW_CTRL_12
+#define S_TOS_PRIO_CTRL REG_TOS_PRIO_CTRL_0
+#define S_IPV6_MLD_CTRL REG_SW_CTRL_21
+
+#define IND_ACC_TABLE(table) ((table) << 8)
+
+/* Driver set switch broadcast storm protection at 10% rate. */
+#define BROADCAST_STORM_PROT_RATE 10
+
+/* 148,800 frames * 67 ms / 100 */
+#define BROADCAST_STORM_VALUE 9969
+
+/**
+ * STATIC_MAC_TABLE_ADDR 00-0000FFFF-FFFFFFFF
+ * STATIC_MAC_TABLE_FWD_PORTS 00-001F0000-00000000
+ * STATIC_MAC_TABLE_VALID 00-00200000-00000000
+ * STATIC_MAC_TABLE_OVERRIDE 00-00400000-00000000
+ * STATIC_MAC_TABLE_USE_FID 00-00800000-00000000
+ * STATIC_MAC_TABLE_FID 00-7F000000-00000000
+ */
+
+#define STATIC_MAC_TABLE_ADDR 0x0000FFFF
+#define STATIC_MAC_TABLE_FWD_PORTS 0x001F0000
+#define STATIC_MAC_TABLE_VALID 0x00200000
+#define STATIC_MAC_TABLE_OVERRIDE 0x00400000
+#define STATIC_MAC_TABLE_USE_FID 0x00800000
+#define STATIC_MAC_TABLE_FID 0x7F000000
+
+#define STATIC_MAC_FWD_PORTS_S 16
+#define STATIC_MAC_FID_S 24
+
+/**
+ * VLAN_TABLE_FID 00-007F007F-007F007F
+ * VLAN_TABLE_MEMBERSHIP 00-0F800F80-0F800F80
+ * VLAN_TABLE_VALID 00-10001000-10001000
+ */
+
+#define VLAN_TABLE_FID 0x007F
+#define VLAN_TABLE_MEMBERSHIP 0x0F80
+#define VLAN_TABLE_VALID 0x1000
+
+#define VLAN_TABLE_MEMBERSHIP_S 7
+#define VLAN_TABLE_S 16
+
+/**
+ * DYNAMIC_MAC_TABLE_ADDR 00-0000FFFF-FFFFFFFF
+ * DYNAMIC_MAC_TABLE_FID 00-007F0000-00000000
+ * DYNAMIC_MAC_TABLE_NOT_READY 00-00800000-00000000
+ * DYNAMIC_MAC_TABLE_SRC_PORT 00-07000000-00000000
+ * DYNAMIC_MAC_TABLE_TIMESTAMP 00-18000000-00000000
+ * DYNAMIC_MAC_TABLE_ENTRIES 7F-E0000000-00000000
+ * DYNAMIC_MAC_TABLE_MAC_EMPTY 80-00000000-00000000
+ */
+
+#define DYNAMIC_MAC_TABLE_ADDR 0x0000FFFF
+#define DYNAMIC_MAC_TABLE_FID 0x007F0000
+#define DYNAMIC_MAC_TABLE_SRC_PORT 0x07000000
+#define DYNAMIC_MAC_TABLE_TIMESTAMP 0x18000000
+#define DYNAMIC_MAC_TABLE_ENTRIES 0xE0000000
+
+#define DYNAMIC_MAC_TABLE_NOT_READY 0x80
+
+#define DYNAMIC_MAC_TABLE_ENTRIES_H 0x7F
+#define DYNAMIC_MAC_TABLE_MAC_EMPTY 0x80
+
+#define DYNAMIC_MAC_FID_S 16
+#define DYNAMIC_MAC_SRC_PORT_S 24
+#define DYNAMIC_MAC_TIMESTAMP_S 27
+#define DYNAMIC_MAC_ENTRIES_S 29
+#define DYNAMIC_MAC_ENTRIES_H_S 3
+
+/**
+ * MIB_COUNTER_VALUE 00-00000000-3FFFFFFF
+ * MIB_TOTAL_BYTES 00-0000000F-FFFFFFFF
+ * MIB_PACKET_DROPPED 00-00000000-0000FFFF
+ * MIB_COUNTER_VALID 00-00000020-00000000
+ * MIB_COUNTER_OVERFLOW 00-00000040-00000000
+ */
+
+#define MIB_COUNTER_OVERFLOW BIT(6)
+#define MIB_COUNTER_VALID BIT(5)
+
+#define MIB_COUNTER_VALUE 0x3FFFFFFF
+
+#define KS_MIB_TOTAL_RX_0 0x100
+#define KS_MIB_TOTAL_TX_0 0x101
+#define KS_MIB_PACKET_DROPPED_RX_0 0x102
+#define KS_MIB_PACKET_DROPPED_TX_0 0x103
+#define KS_MIB_TOTAL_RX_1 0x104
+#define KS_MIB_TOTAL_TX_1 0x105
+#define KS_MIB_PACKET_DROPPED_TX_1 0x106
+#define KS_MIB_PACKET_DROPPED_RX_1 0x107
+#define KS_MIB_TOTAL_RX_2 0x108
+#define KS_MIB_TOTAL_TX_2 0x109
+#define KS_MIB_PACKET_DROPPED_TX_2 0x10A
+#define KS_MIB_PACKET_DROPPED_RX_2 0x10B
+#define KS_MIB_TOTAL_RX_3 0x10C
+#define KS_MIB_TOTAL_TX_3 0x10D
+#define KS_MIB_PACKET_DROPPED_TX_3 0x10E
+#define KS_MIB_PACKET_DROPPED_RX_3 0x10F
+#define KS_MIB_TOTAL_RX_4 0x110
+#define KS_MIB_TOTAL_TX_4 0x111
+#define KS_MIB_PACKET_DROPPED_TX_4 0x112
+#define KS_MIB_PACKET_DROPPED_RX_4 0x113
+
+#define MIB_PACKET_DROPPED 0x0000FFFF
+
+#define MIB_TOTAL_BYTES_H 0x0000000F
+
+#define TAIL_TAG_OVERRIDE BIT(6)
+#define TAIL_TAG_LOOKUP BIT(7)
+
+#define VLAN_TABLE_ENTRIES (4096 / 4)
+#define FID_ENTRIES 128
+
+#endif
diff --git a/drivers/net/dsa/microchip/ksz8795_spi.c b/drivers/net/dsa/microchip/ksz8795_spi.c
new file mode 100644
index 0000000..48d0a2c
--- /dev/null
+++ b/drivers/net/dsa/microchip/ksz8795_spi.c
@@ -0,0 +1,166 @@
+/*
+ * Microchip KSZ8795 series register access through SPI
+ *
+ * Copyright (C) 2017 Microchip Technology Inc.
+ * Tristram Ha <Tristram.Ha@microchip.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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <asm/unaligned.h>
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+
+#include "ksz_priv.h"
+#include "ksz_spi.h"
+
+/* SPI frame opcodes */
+#define KS_SPIOP_RD 3
+#define KS_SPIOP_WR 2
+
+#define SPI_ADDR_S 12
+#define SPI_ADDR_M (BIT(SPI_ADDR_S) - 1)
+#define SPI_TURNAROUND_S 1
+
+/* Enough to read all switch registers. */
+#define SPI_TX_BUF_LEN 0x100
+
+static int ksz8795_spi_read_reg(struct spi_device *spi, u32 reg, u8 *val,
+ unsigned int len)
+{
+ u16 txbuf;
+ int ret;
+
+ txbuf = reg & SPI_ADDR_M;
+ txbuf |= KS_SPIOP_RD << SPI_ADDR_S;
+ txbuf <<= SPI_TURNAROUND_S;
+ txbuf = cpu_to_be16(txbuf);
+
+ ret = spi_write_then_read(spi, &txbuf, 2, val, len);
+ return ret;
+}
+
+static int ksz8795_spi_write_reg(struct spi_device *spi, u32 reg, u8 *val,
+ unsigned int len)
+{
+ u16 *txbuf = (u16 *)val;
+
+ *txbuf = reg & SPI_ADDR_M;
+ *txbuf |= (KS_SPIOP_WR << SPI_ADDR_S);
+ *txbuf <<= SPI_TURNAROUND_S;
+ *txbuf = cpu_to_be16(*txbuf);
+
+ return spi_write(spi, txbuf, 2 + len);
+}
+
+static int ksz_spi_read(struct ksz_device *dev, u32 reg, u8 *data,
+ unsigned int len)
+{
+ struct spi_device *spi = dev->priv;
+
+ return ksz8795_spi_read_reg(spi, reg, data, len);
+}
+
+static int ksz_spi_write(struct ksz_device *dev, u32 reg, void *data,
+ unsigned int len)
+{
+ struct spi_device *spi = dev->priv;
+
+ if (len > SPI_TX_BUF_LEN)
+ len = SPI_TX_BUF_LEN;
+ memcpy(&dev->txbuf[2], data, len);
+ return ksz8795_spi_write_reg(spi, reg, dev->txbuf, len);
+}
+
+static const struct ksz_io_ops ksz8795_spi_ops = {
+ .read8 = ksz_spi_read8,
+ .read16 = ksz_spi_read16,
+ .read32 = ksz_spi_read32,
+ .write8 = ksz_spi_write8,
+ .write16 = ksz_spi_write16,
+ .write32 = ksz_spi_write32,
+ .get = ksz_spi_get,
+ .set = ksz_spi_set,
+};
+
+static int ksz8795_spi_probe(struct spi_device *spi)
+{
+ struct ksz_device *dev;
+ int ret;
+
+ dev = ksz_switch_alloc(&spi->dev, &ksz8795_spi_ops, spi);
+ if (!dev)
+ return -ENOMEM;
+
+ if (spi->dev.platform_data)
+ dev->pdata = spi->dev.platform_data;
+
+ dev->txbuf = devm_kzalloc(dev->dev, 2 + SPI_TX_BUF_LEN, GFP_KERNEL);
+
+ ret = ksz8795_switch_register(dev);
+
+ /* Main DSA driver may not be started yet. */
+ if (ret)
+ return ret;
+
+ spi_set_drvdata(spi, dev);
+
+ return 0;
+}
+
+static int ksz8795_spi_remove(struct spi_device *spi)
+{
+ struct ksz_device *dev = spi_get_drvdata(spi);
+
+ if (dev)
+ ksz_switch_remove(dev);
+
+ return 0;
+}
+
+static void ksz8795_spi_shutdown(struct spi_device *spi)
+{
+ struct ksz_device *dev = spi_get_drvdata(spi);
+
+ if (dev && dev->dev_ops->shutdown)
+ dev->dev_ops->shutdown(dev);
+}
+
+static const struct of_device_id ksz8795_dt_ids[] = {
+ { .compatible = "microchip,ksz8795" },
+ { .compatible = "microchip,ksz8794" },
+ { .compatible = "microchip,ksz8765" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ksz8795_dt_ids);
+
+static struct spi_driver ksz8795_spi_driver = {
+ .driver = {
+ .name = "ksz8795-switch",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(ksz8795_dt_ids),
+ },
+ .probe = ksz8795_spi_probe,
+ .remove = ksz8795_spi_remove,
+ .shutdown = ksz8795_spi_shutdown,
+};
+
+module_spi_driver(ksz8795_spi_driver);
+
+MODULE_AUTHOR("Tristram Ha <Tristram.Ha@microchip.com>");
+MODULE_DESCRIPTION("Microchip KSZ8795 Series Switch SPI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/dsa/microchip/ksz_priv.h b/drivers/net/dsa/microchip/ksz_priv.h
index 0e1ccd3..6cc92ea 100644
--- a/drivers/net/dsa/microchip/ksz_priv.h
+++ b/drivers/net/dsa/microchip/ksz_priv.h
@@ -177,6 +177,7 @@ int ksz_switch_register(struct ksz_device *dev,
const struct ksz_dev_ops *ops);
void ksz_switch_remove(struct ksz_device *dev);
+int ksz8795_switch_register(struct ksz_device *dev);
int ksz9477_switch_register(struct ksz_device *dev);
#endif
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 28cdc5e..6261550 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -31,6 +31,7 @@ enum dsa_tag_protocol {
DSA_TAG_PROTO_BRCM,
DSA_TAG_PROTO_DSA,
DSA_TAG_PROTO_EDSA,
+ DSA_TAG_PROTO_KSZ8795,
DSA_TAG_PROTO_KSZ9477,
DSA_TAG_PROTO_LAN9303,
DSA_TAG_PROTO_MTK,
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index d2bbd21..51769ba 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -28,6 +28,10 @@ config NET_DSA_TAG_EDSA
config NET_DSA_TAG_KSZ
bool
+config NET_DSA_TAG_KSZ8795
+ bool
+ select NET_DSA_TAG_KSZ
+
config NET_DSA_TAG_KSZ9477
bool
select NET_DSA_TAG_KSZ
diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
index cc03a09..bb6afb8 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -49,6 +49,9 @@ static struct sk_buff *dsa_slave_notag_xmit(struct sk_buff *skb,
#ifdef CONFIG_NET_DSA_TAG_EDSA
[DSA_TAG_PROTO_EDSA] = &edsa_netdev_ops,
#endif
+#ifdef CONFIG_NET_DSA_TAG_KSZ8795
+ [DSA_TAG_PROTO_KSZ8795] = &ksz8795_netdev_ops,
+#endif
#ifdef CONFIG_NET_DSA_TAG_KSZ9477
[DSA_TAG_PROTO_KSZ9477] = &ksz9477_netdev_ops,
#endif
diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
index e0dc2d4..542660f 100644
--- a/net/dsa/dsa_priv.h
+++ b/net/dsa/dsa_priv.h
@@ -183,6 +183,7 @@ int dsa_port_vlan_del(struct dsa_port *dp,
extern const struct dsa_device_ops edsa_netdev_ops;
/* tag_ksz.c */
+extern const struct dsa_device_ops ksz8795_netdev_ops;
extern const struct dsa_device_ops ksz9477_netdev_ops;
/* tag_lan9303.c */
diff --git a/net/dsa/tag_ksz.c b/net/dsa/tag_ksz.c
index 1bb5b7d..bf3a19a 100644
--- a/net/dsa/tag_ksz.c
+++ b/net/dsa/tag_ksz.c
@@ -18,6 +18,9 @@
#define KSZ_EGRESS_TAG_LEN 1
#define KSZ_INGRESS_TAG_LEN 1
+#define KSZ_TAIL_TAG_OVERRIDE BIT(6)
+#define KSZ_TAIL_TAG_LOOKUP BIT(7)
+
/* Frames with following addresse may need to be sent even when the port is
* closed.
*/
@@ -90,6 +93,40 @@ static struct sk_buff *ksz_rcv(struct sk_buff *skb, struct net_device *dev,
return skb;
}
+static int ksz_get_tag(u8 *tag, int *port)
+{
+ int len = KSZ_EGRESS_TAG_LEN;
+
+ *port = tag[0] & 7;
+ return len;
+}
+
+static void ksz8795_set_tag(void *ptr, u8 *addr, int p)
+{
+ u8 *tag = (u8 *)ptr;
+
+ *tag = 1 << p;
+ if (!memcmp(addr, special_mult_addr, ETH_ALEN))
+ *tag |= KSZ_TAIL_TAG_OVERRIDE;
+}
+
+static struct sk_buff *ksz8795_xmit(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ return ksz_xmit(skb, dev, KSZ_INGRESS_TAG_LEN, ksz8795_set_tag);
+}
+
+static struct sk_buff *ksz_sw_rcv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *pt)
+{
+ return ksz_rcv(skb, dev, pt, ksz_get_tag);
+}
+
+const struct dsa_device_ops ksz8795_netdev_ops = {
+ .xmit = ksz8795_xmit,
+ .rcv = ksz_sw_rcv,
+};
+
/* For Ingress (Host -> KSZ9477), 2 bytes are added before FCS.
* ---------------------------------------------------------------------------
* DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|tag1(1byte)|FCS(4bytes)
--
1.9.1
^ permalink raw reply related
* [PATCH RFC 0/1] Add Microchip KSZ8895 DSA driver
From: Tristram.Ha @ 2017-10-06 20:33 UTC (permalink / raw)
To: Andrew Lunn, Florian Fainelli, Pavel Machek
Cc: Tristram Ha, muvarov, nathan.leigh.conrad, vivien.didelot,
UNGLinuxDriver, netdev, linux-kernel
From: Tristram Ha <Tristram.Ha@microchip.com>
This patch requires the previous patch for Microchip KSZ8795 DSA driver.
Tristram Ha (1):
Add Microchip KSZ8895 DSA driver.
drivers/net/dsa/microchip/Kconfig | 17 +
drivers/net/dsa/microchip/Makefile | 2 +
drivers/net/dsa/microchip/ksz8895.c | 1288 +++++++++++++++++++++++++++++++
drivers/net/dsa/microchip/ksz8895_reg.h | 824 ++++++++++++++++++++
drivers/net/dsa/microchip/ksz8895_spi.c | 157 ++++
drivers/net/dsa/microchip/ksz_priv.h | 1 +
6 files changed, 2289 insertions(+)
create mode 100644 drivers/net/dsa/microchip/ksz8895.c
create mode 100644 drivers/net/dsa/microchip/ksz8895_reg.h
create mode 100644 drivers/net/dsa/microchip/ksz8895_spi.c
--
1.9.1
^ permalink raw reply
* [PATCH RFC] Add Microchip KSZ8895 DSA driver
From: Tristram.Ha @ 2017-10-06 20:33 UTC (permalink / raw)
To: Andrew Lunn, Florian Fainelli, Pavel Machek
Cc: Tristram Ha, muvarov, nathan.leigh.conrad, vivien.didelot,
UNGLinuxDriver, netdev, linux-kernel
In-Reply-To: <1507322033-15222-1-git-send-email-Tristram.Ha@microchip.com>
From: Tristram Ha <Tristram.Ha@microchip.com>
Add Microchip KSZ8895 DSA driver.
Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
---
drivers/net/dsa/microchip/Kconfig | 17 +
drivers/net/dsa/microchip/Makefile | 2 +
drivers/net/dsa/microchip/ksz8895.c | 1288 +++++++++++++++++++++++++++++++
drivers/net/dsa/microchip/ksz8895_reg.h | 824 ++++++++++++++++++++
drivers/net/dsa/microchip/ksz8895_spi.c | 157 ++++
drivers/net/dsa/microchip/ksz_priv.h | 1 +
6 files changed, 2289 insertions(+)
create mode 100644 drivers/net/dsa/microchip/ksz8895.c
create mode 100644 drivers/net/dsa/microchip/ksz8895_reg.h
create mode 100644 drivers/net/dsa/microchip/ksz8895_spi.c
diff --git a/drivers/net/dsa/microchip/Kconfig b/drivers/net/dsa/microchip/Kconfig
index cb95d3d..b854c4b 100644
--- a/drivers/net/dsa/microchip/Kconfig
+++ b/drivers/net/dsa/microchip/Kconfig
@@ -27,3 +27,20 @@ config MICROCHIP_KSZ8795_SPI_DRIVER
It is required to use the KSZ8795 switch driver as the only access
is through SPI.
+
+menuconfig MICROCHIP_KSZ8895
+ tristate "Microchip KSZ8895 series switch support"
+ depends on NET_DSA
+ select NET_DSA_TAG_KSZ8795
+ help
+ This driver adds support for Microchip KSZ8895 switch chips.
+
+config MICROCHIP_KSZ8895_SPI_DRIVER
+ tristate "KSZ8895 series SPI connected switch driver"
+ depends on MICROCHIP_KSZ8895 && SPI
+ default y
+ help
+ This driver accesses KSZ8895 chip through SPI.
+
+ It is required to use the KSZ8895 switch driver as the only access
+ is through SPI.
diff --git a/drivers/net/dsa/microchip/Makefile b/drivers/net/dsa/microchip/Makefile
index 99a283e..8dd6312 100644
--- a/drivers/net/dsa/microchip/Makefile
+++ b/drivers/net/dsa/microchip/Makefile
@@ -2,3 +2,5 @@ obj-$(CONFIG_MICROCHIP_KSZ9477) += ksz9477.o ksz_common.o
obj-$(CONFIG_MICROCHIP_KSZ9477_SPI_DRIVER) += ksz9477_spi.o
obj-$(CONFIG_MICROCHIP_KSZ8795) += ksz8795.o ksz_common.o
obj-$(CONFIG_MICROCHIP_KSZ8795_SPI_DRIVER) += ksz8795_spi.o
+obj-$(CONFIG_MICROCHIP_KSZ8895) += ksz8895.o ksz_common.o
+obj-$(CONFIG_MICROCHIP_KSZ8895_SPI_DRIVER) += ksz8895_spi.o
diff --git a/drivers/net/dsa/microchip/ksz8895.c b/drivers/net/dsa/microchip/ksz8895.c
new file mode 100644
index 0000000..947638b
--- /dev/null
+++ b/drivers/net/dsa/microchip/ksz8895.c
@@ -0,0 +1,1288 @@
+/*
+ * Microchip KSZ8895 switch driver
+ *
+ * Copyright (C) 2017 Microchip Technology Inc.
+ * Tristram Ha <Tristram.Ha@microchip.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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/delay.h>
+#include <linux/export.h>
+#include <linux/gpio.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_data/microchip-ksz.h>
+#include <linux/phy.h>
+#include <linux/etherdevice.h>
+#include <linux/if_bridge.h>
+#include <net/dsa.h>
+#include <net/switchdev.h>
+
+#include "ksz_priv.h"
+#include "ksz_common.h"
+#include "ksz8895_reg.h"
+
+static const struct {
+ char string[ETH_GSTRING_LEN];
+} mib_names[TOTAL_SWITCH_COUNTER_NUM] = {
+ { "rx" },
+ { "rx_hi" },
+ { "rx_undersize" },
+ { "rx_fragments" },
+ { "rx_oversize" },
+ { "rx_jabbers" },
+ { "rx_symbol_err" },
+ { "rx_crc_err" },
+ { "rx_align_err" },
+ { "rx_mac_ctrl" },
+ { "rx_pause" },
+ { "rx_bcast" },
+ { "rx_mcast" },
+ { "rx_ucast" },
+ { "rx_64_or_less" },
+ { "rx_65_127" },
+ { "rx_128_255" },
+ { "rx_256_511" },
+ { "rx_512_1023" },
+ { "rx_1024_1522" },
+ { "tx" },
+ { "tx_hi" },
+ { "tx_late_col" },
+ { "tx_pause" },
+ { "tx_bcast" },
+ { "tx_mcast" },
+ { "tx_ucast" },
+ { "tx_deferred" },
+ { "tx_total_col" },
+ { "tx_exc_col" },
+ { "tx_single_col" },
+ { "tx_mult_col" },
+ { "rx_discards" },
+ { "tx_discards" },
+};
+
+static int ksz8895_reset_switch(struct ksz_device *dev)
+{
+ /* reset switch */
+ ksz_write8(dev, REG_POWER_MANAGEMENT_1,
+ SW_SOFTWARE_POWER_DOWN << SW_POWER_MANAGEMENT_MODE_S);
+ ksz_write8(dev, REG_POWER_MANAGEMENT_1, 0);
+
+ return 0;
+}
+
+static void ksz8895_set_prio_queue(struct ksz_device *dev, int port, int queue)
+{
+ u8 hi;
+ u8 lo;
+
+ /* Number of queues can only be 1, 2, or 4. */
+ switch (queue) {
+ case 4:
+ case 3:
+ queue = PORT_QUEUE_SPLIT_4;
+ break;
+ case 2:
+ queue = PORT_QUEUE_SPLIT_2;
+ break;
+ default:
+ queue = PORT_QUEUE_SPLIT_1;
+ }
+ ksz_pread8(dev, port, REG_PORT_CTRL_0, &lo);
+ ksz_pread8(dev, port, P_DROP_TAG_CTRL, &hi);
+ lo &= ~PORT_QUEUE_SPLIT_L;
+ if (queue & PORT_QUEUE_SPLIT_2)
+ lo |= PORT_QUEUE_SPLIT_L;
+ hi &= ~PORT_QUEUE_SPLIT_H;
+ if (queue & PORT_QUEUE_SPLIT_4)
+ hi |= PORT_QUEUE_SPLIT_H;
+ ksz_pwrite8(dev, port, REG_PORT_CTRL_0, lo);
+ ksz_pwrite8(dev, port, P_DROP_TAG_CTRL, hi);
+
+ /* Default is port based for egress rate limit. */
+ if (queue != PORT_QUEUE_SPLIT_1)
+ ksz_cfg(dev, REG_SW_CTRL_19, SW_OUT_RATE_LIMIT_QUEUE_BASED,
+ true);
+}
+
+static void ksz8895_r_mib_cnt(struct ksz_device *dev, int port, u16 addr,
+ u64 *cnt)
+{
+ u32 data;
+ u16 ctrl_addr;
+ u8 check;
+ int loop;
+
+ ctrl_addr = addr + SWITCH_COUNTER_NUM * port;
+ ctrl_addr |= IND_ACC_TABLE(TABLE_MIB | TABLE_READ);
+
+ mutex_lock(&dev->alu_mutex);
+ ksz_write16(dev, REG_IND_CTRL_0, ctrl_addr);
+
+ /* It is almost guaranteed to always read the valid bit because of
+ * slow SPI speed.
+ */
+ for (loop = 2; loop > 0; loop--) {
+ ksz_read8(dev, REG_IND_MIB_CHECK, &check);
+
+ if (check & MIB_COUNTER_VALID) {
+ ksz_read32(dev, REG_IND_DATA_LO, &data);
+ if (check & MIB_COUNTER_OVERFLOW)
+ *cnt += MIB_COUNTER_VALUE + 1;
+ *cnt += data & MIB_COUNTER_VALUE;
+ break;
+ }
+ }
+ mutex_unlock(&dev->alu_mutex);
+}
+
+static void ksz8895_r_mib_pkt(struct ksz_device *dev, int port, u16 addr,
+ u64 *dropped, u64 *cnt)
+{
+ u32 cur;
+ u32 data;
+ u16 ctrl_addr;
+ u32 *last = (u32 *)dropped;
+
+ addr -= SWITCH_COUNTER_NUM;
+ ctrl_addr = addr ? KS_MIB_PACKET_DROPPED_TX_0 :
+ KS_MIB_PACKET_DROPPED_RX_0;
+ ctrl_addr += port;
+ ctrl_addr |= IND_ACC_TABLE(TABLE_MIB | TABLE_READ);
+
+ mutex_lock(&dev->alu_mutex);
+ ksz_write16(dev, REG_IND_CTRL_0, ctrl_addr);
+ ksz_read32(dev, REG_IND_DATA_LO, &data);
+ mutex_unlock(&dev->alu_mutex);
+
+ data &= MIB_PACKET_DROPPED;
+ cur = last[addr];
+ if (data != cur) {
+ last[addr] = data;
+ if (data < cur)
+ data += MIB_PACKET_DROPPED + 1;
+ data -= cur;
+ *cnt += data;
+ }
+}
+
+static void ksz8895_port_init_cnt(struct ksz_device *dev, int port)
+{
+ struct ksz_port_mib *mib = &dev->ports[port].mib;
+ u64 *dropped;
+
+ mib->cnt_ptr = 0;
+
+ /* Some ports may not have MIB counters before SWITCH_COUNTER_NUM. */
+ while (mib->cnt_ptr < dev->reg_mib_cnt) {
+ dev->dev_ops->r_mib_cnt(dev, port, mib->cnt_ptr,
+ &mib->counters[mib->cnt_ptr]);
+ ++mib->cnt_ptr;
+ }
+
+ /* last one in storage */
+ dropped = &mib->counters[dev->mib_cnt];
+
+ /* Some ports may not have MIB counters after SWITCH_COUNTER_NUM. */
+ while (mib->cnt_ptr < dev->mib_cnt) {
+ dev->dev_ops->r_mib_pkt(dev, port, mib->cnt_ptr,
+ dropped, &mib->counters[mib->cnt_ptr]);
+ ++mib->cnt_ptr;
+ }
+ mib->cnt_ptr = 0;
+ memset(mib->counters, 0, dev->mib_cnt * sizeof(u64));
+}
+
+static void ksz8895_r_table(struct ksz_device *dev, int table, u16 addr,
+ u64 *data)
+{
+ u16 ctrl_addr;
+
+ ctrl_addr = IND_ACC_TABLE(table | TABLE_READ) | addr;
+
+ mutex_lock(&dev->alu_mutex);
+ ksz_write16(dev, REG_IND_CTRL_0, ctrl_addr);
+ ksz_get(dev, REG_IND_DATA_HI, data, sizeof(u64));
+ mutex_unlock(&dev->alu_mutex);
+ *data = be64_to_cpu(*data);
+}
+
+static void ksz8895_w_table(struct ksz_device *dev, int table, u16 addr,
+ u64 data)
+{
+ u16 ctrl_addr;
+
+ ctrl_addr = IND_ACC_TABLE(table) | addr;
+ data = cpu_to_be64(data);
+
+ mutex_lock(&dev->alu_mutex);
+ ksz_set(dev, REG_IND_DATA_HI, &data, sizeof(u64));
+ ksz_write16(dev, REG_IND_CTRL_0, ctrl_addr);
+ mutex_unlock(&dev->alu_mutex);
+}
+
+static int ksz8895_valid_dyn_entry(struct ksz_device *dev, u8 *data)
+{
+ int timeout = 100;
+
+ do {
+ ksz_read8(dev, REG_IND_DATA_CHECK, data);
+ timeout--;
+ } while ((*data & DYNAMIC_MAC_TABLE_NOT_READY) && timeout);
+
+ /* Entry is not ready for accessing. */
+ if (*data & DYNAMIC_MAC_TABLE_NOT_READY) {
+ return -EAGAIN;
+ /* Entry is ready for accessing. */
+ } else {
+ ksz_read8(dev, REG_IND_DATA_8, data);
+
+ /* There is no valid entry in the table. */
+ if (*data & DYNAMIC_MAC_TABLE_MAC_EMPTY)
+ return -ENXIO;
+ }
+ return 0;
+}
+
+static int ksz8895_r_dyn_mac_table(struct ksz_device *dev, u16 addr,
+ u8 *mac_addr, u8 *fid, u8 *src_port,
+ u8 *timestamp, u16 *entries)
+{
+ u32 data_hi;
+ u32 data_lo;
+ u16 ctrl_addr;
+ int rc;
+ u8 data;
+
+ ctrl_addr = IND_ACC_TABLE(TABLE_DYNAMIC_MAC | TABLE_READ) | addr;
+
+ mutex_lock(&dev->alu_mutex);
+ ksz_write16(dev, REG_IND_CTRL_0, ctrl_addr);
+
+ rc = ksz8895_valid_dyn_entry(dev, &data);
+ if (rc == -EAGAIN) {
+ if (addr == 0)
+ *entries = 0;
+ } else if (rc == -ENXIO) {
+ *entries = 0;
+ /* At least one valid entry in the table. */
+ } else {
+ u64 buf;
+ int cnt;
+
+ ksz_get(dev, REG_IND_DATA_HI, &buf, sizeof(buf));
+ buf = be64_to_cpu(buf);
+ data_hi = (u32)(buf >> 32);
+ data_lo = (u32)buf;
+
+ /* Check out how many valid entry in the table. */
+ cnt = data & DYNAMIC_MAC_TABLE_ENTRIES_H;
+ cnt <<= DYNAMIC_MAC_ENTRIES_H_S;
+ cnt |= (data_hi & DYNAMIC_MAC_TABLE_ENTRIES) >>
+ DYNAMIC_MAC_ENTRIES_S;
+ *entries = cnt + 1;
+
+ *fid = (data_hi & DYNAMIC_MAC_TABLE_FID) >>
+ DYNAMIC_MAC_FID_S;
+ *src_port = (data_hi & DYNAMIC_MAC_TABLE_SRC_PORT) >>
+ DYNAMIC_MAC_SRC_PORT_S;
+ *timestamp = (data_hi & DYNAMIC_MAC_TABLE_TIMESTAMP) >>
+ DYNAMIC_MAC_TIMESTAMP_S;
+
+ mac_addr[5] = (u8)data_lo;
+ mac_addr[4] = (u8)(data_lo >> 8);
+ mac_addr[3] = (u8)(data_lo >> 16);
+ mac_addr[2] = (u8)(data_lo >> 24);
+
+ mac_addr[1] = (u8)data_hi;
+ mac_addr[0] = (u8)(data_hi >> 8);
+ rc = 0;
+ }
+ mutex_unlock(&dev->alu_mutex);
+
+ return rc;
+}
+
+static int ksz8895_r_sta_mac_table(struct ksz_device *dev, u16 addr,
+ struct alu_struct *alu)
+{
+ u64 data;
+ u32 data_hi;
+ u32 data_lo;
+
+ ksz8895_r_table(dev, TABLE_STATIC_MAC, addr, &data);
+ data_hi = data >> 32;
+ data_lo = (u32)data;
+ if (data_hi & (STATIC_MAC_TABLE_VALID | STATIC_MAC_TABLE_OVERRIDE)) {
+ alu->mac[5] = (u8)data_lo;
+ alu->mac[4] = (u8)(data_lo >> 8);
+ alu->mac[3] = (u8)(data_lo >> 16);
+ alu->mac[2] = (u8)(data_lo >> 24);
+ alu->mac[1] = (u8)data_hi;
+ alu->mac[0] = (u8)(data_hi >> 8);
+ alu->port_forward = (data_hi & STATIC_MAC_TABLE_FWD_PORTS) >>
+ STATIC_MAC_FWD_PORTS_S;
+ alu->is_override =
+ (data_hi & STATIC_MAC_TABLE_OVERRIDE) ? 1 : 0;
+ data_hi >>= 1;
+ alu->is_use_fid = (data_hi & STATIC_MAC_TABLE_USE_FID) ? 1 : 0;
+ alu->fid = (data_hi & STATIC_MAC_TABLE_FID) >>
+ STATIC_MAC_FID_S;
+ return 0;
+ }
+ return -ENXIO;
+}
+
+static void ksz8895_w_sta_mac_table(struct ksz_device *dev, u16 addr,
+ struct alu_struct *alu)
+{
+ u64 data;
+ u32 data_hi;
+ u32 data_lo;
+
+ data_lo = ((u32)alu->mac[2] << 24) |
+ ((u32)alu->mac[3] << 16) |
+ ((u32)alu->mac[4] << 8) | alu->mac[5];
+ data_hi = ((u32)alu->mac[0] << 8) | alu->mac[1];
+ data_hi |= (u32)alu->port_forward << STATIC_MAC_FWD_PORTS_S;
+
+ if (alu->is_override)
+ data_hi |= STATIC_MAC_TABLE_OVERRIDE;
+ if (alu->is_use_fid) {
+ data_hi |= STATIC_MAC_TABLE_USE_FID;
+ data_hi |= (u32)alu->fid << STATIC_MAC_FID_S;
+ }
+ if (alu->is_static)
+ data_hi |= STATIC_MAC_TABLE_VALID;
+ else
+ data_hi &= ~STATIC_MAC_TABLE_OVERRIDE;
+
+ data = (u64)data_hi << 32 | data_lo;
+ ksz8895_w_table(dev, TABLE_STATIC_MAC, addr, data);
+}
+
+static inline void ksz8895_from_vlan(u16 vlan, u8 *fid, u8 *member, u8 *valid)
+{
+ *fid = vlan & VLAN_TABLE_FID;
+ *member = (vlan & VLAN_TABLE_MEMBERSHIP) >> VLAN_TABLE_MEMBERSHIP_S;
+ *valid = !!(vlan & VLAN_TABLE_VALID);
+}
+
+static inline void ksz8895_to_vlan(u8 fid, u8 member, u8 valid, u16 *vlan)
+{
+ *vlan = fid;
+ *vlan |= (u16)member << VLAN_TABLE_MEMBERSHIP_S;
+ if (valid)
+ *vlan |= VLAN_TABLE_VALID;
+}
+
+static void ksz8895_r_vlan_entries(struct ksz_device *dev, u16 addr)
+{
+ u64 data;
+ int i;
+
+ ksz8895_r_table(dev, TABLE_VLAN, addr, &data);
+ addr *= 4;
+ for (i = 0; i < 4; i++) {
+ dev->vlan_cache[addr + i].table[0] = data & VLAN_TABLE_M;
+ data >>= VLAN_TABLE_S;
+ }
+}
+
+static void ksz8895_r_vlan_table(struct ksz_device *dev, u16 vid, u16 *vlan)
+{
+ u64 buf;
+ u16 addr;
+ int index;
+
+ addr = vid / 4;
+ index = vid & 3;
+ ksz8895_r_table(dev, TABLE_VLAN, addr, &buf);
+ buf >>= VLAN_TABLE_S * index;
+ *vlan = buf & VLAN_TABLE_M;
+}
+
+static void ksz8895_w_vlan_table(struct ksz_device *dev, u16 vid, u16 vlan)
+{
+ u64 buf;
+ u16 addr;
+ int index;
+
+ addr = vid / 4;
+ index = vid & 3;
+ ksz8895_r_table(dev, TABLE_VLAN, addr, &buf);
+ index *= VLAN_TABLE_S;
+ buf &= ~(VLAN_TABLE_M << index);
+ buf |= (u64)vlan << index;
+ dev->vlan_cache[vid].table[0] = vlan;
+ ksz8895_w_table(dev, TABLE_VLAN, addr, buf);
+}
+
+static void ksz8895_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val)
+{
+ struct ksz_port *port;
+ u8 ctrl;
+ u8 restart;
+ u8 link;
+ u8 speed;
+ u8 p = phy;
+ u16 data = 0;
+ int processed = true;
+
+ port = &dev->ports[p];
+ switch (reg) {
+ case PHY_REG_CTRL:
+ ksz_pread8(dev, p, P_LOCAL_CTRL, &ctrl);
+ ksz_pread8(dev, p, P_NEG_RESTART_CTRL, &restart);
+ ksz_pread8(dev, p, P_SPEED_STATUS, &speed);
+ if (restart & PORT_PHY_LOOPBACK)
+ data |= PHY_LOOPBACK;
+ if (ctrl & PORT_FORCE_100_MBIT)
+ data |= PHY_SPEED_100MBIT;
+ if (!(ctrl & PORT_AUTO_NEG_DISABLE))
+ data |= PHY_AUTO_NEG_ENABLE;
+ if (restart & PORT_POWER_DOWN)
+ data |= PHY_POWER_DOWN;
+ if (restart & PORT_AUTO_NEG_RESTART)
+ data |= PHY_AUTO_NEG_RESTART;
+ if (ctrl & PORT_FORCE_FULL_DUPLEX)
+ data |= PHY_FULL_DUPLEX;
+ if (speed & PORT_HP_MDIX)
+ data |= PHY_HP_MDIX;
+ if (restart & PORT_FORCE_MDIX)
+ data |= PHY_FORCE_MDIX;
+ if (restart & PORT_AUTO_MDIX_DISABLE)
+ data |= PHY_AUTO_MDIX_DISABLE;
+ if (restart & PORT_TX_DISABLE)
+ data |= PHY_TRANSMIT_DISABLE;
+ if (restart & PORT_LED_OFF)
+ data |= PHY_LED_DISABLE;
+ break;
+ case PHY_REG_STATUS:
+ ksz_pread8(dev, p, P_LINK_STATUS, &link);
+ data = PHY_100BTX_FD_CAPABLE |
+ PHY_100BTX_CAPABLE |
+ PHY_10BT_FD_CAPABLE |
+ PHY_10BT_CAPABLE |
+ PHY_AUTO_NEG_CAPABLE;
+ if (link & PORT_AUTO_NEG_COMPLETE)
+ data |= PHY_AUTO_NEG_ACKNOWLEDGE;
+ if (link & PORT_STAT_LINK_GOOD)
+ data |= PHY_LINK_STATUS;
+ break;
+ case PHY_REG_ID_1:
+ data = KSZ8895_ID_HI;
+ break;
+ case PHY_REG_ID_2:
+ data = KSZ8895_ID_LO;
+ break;
+ case PHY_REG_AUTO_NEGOTIATION:
+ ksz_pread8(dev, p, P_LOCAL_CTRL, &ctrl);
+ data = PHY_AUTO_NEG_802_3;
+ if (ctrl & PORT_AUTO_NEG_SYM_PAUSE)
+ data |= PHY_AUTO_NEG_SYM_PAUSE;
+ if (ctrl & PORT_AUTO_NEG_100BTX_FD)
+ data |= PHY_AUTO_NEG_100BTX_FD;
+ if (ctrl & PORT_AUTO_NEG_100BTX)
+ data |= PHY_AUTO_NEG_100BTX;
+ if (ctrl & PORT_AUTO_NEG_10BT_FD)
+ data |= PHY_AUTO_NEG_10BT_FD;
+ if (ctrl & PORT_AUTO_NEG_10BT)
+ data |= PHY_AUTO_NEG_10BT;
+ break;
+ case PHY_REG_REMOTE_CAPABILITY:
+ ksz_pread8(dev, p, P_REMOTE_STATUS, &link);
+ data = PHY_AUTO_NEG_802_3;
+ if (link & PORT_REMOTE_SYM_PAUSE)
+ data |= PHY_AUTO_NEG_SYM_PAUSE;
+ if (link & PORT_REMOTE_100BTX_FD)
+ data |= PHY_AUTO_NEG_100BTX_FD;
+ if (link & PORT_REMOTE_100BTX)
+ data |= PHY_AUTO_NEG_100BTX;
+ if (link & PORT_REMOTE_10BT_FD)
+ data |= PHY_AUTO_NEG_10BT_FD;
+ if (link & PORT_REMOTE_10BT)
+ data |= PHY_AUTO_NEG_10BT;
+ if (data & ~PHY_AUTO_NEG_802_3)
+ data |= PHY_REMOTE_ACKNOWLEDGE_NOT;
+ break;
+ default:
+ processed = false;
+ break;
+ }
+ if (processed)
+ *val = data;
+}
+
+static void ksz8895_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val)
+{
+ u8 ctrl;
+ u8 restart;
+ u8 speed;
+ u8 data;
+ u8 p = phy;
+
+ switch (reg) {
+ case PHY_REG_CTRL:
+
+ /* Do not support PHY reset function. */
+ if (val & PHY_RESET)
+ break;
+ ksz_pread8(dev, p, P_SPEED_STATUS, &speed);
+ data = speed;
+ if (val & PHY_HP_MDIX)
+ data |= PORT_HP_MDIX;
+ else
+ data &= ~PORT_HP_MDIX;
+ if (data != speed)
+ ksz_pwrite8(dev, p, P_SPEED_STATUS, data);
+ ksz_pread8(dev, p, P_FORCE_CTRL, &ctrl);
+ data = ctrl;
+ if (!(val & PHY_AUTO_NEG_ENABLE))
+ data |= PORT_AUTO_NEG_DISABLE;
+ else
+ data &= ~PORT_AUTO_NEG_DISABLE;
+
+ /* Fiber port does not support auto-negotiation. */
+ if (dev->ports[p].fiber)
+ data |= PORT_AUTO_NEG_DISABLE;
+ if (val & PHY_SPEED_100MBIT)
+ data |= PORT_FORCE_100_MBIT;
+ else
+ data &= ~PORT_FORCE_100_MBIT;
+ if (val & PHY_FULL_DUPLEX)
+ data |= PORT_FORCE_FULL_DUPLEX;
+ else
+ data &= ~PORT_FORCE_FULL_DUPLEX;
+ if (data != ctrl)
+ ksz_pwrite8(dev, p, P_FORCE_CTRL, data);
+ ksz_pread8(dev, p, P_NEG_RESTART_CTRL, &restart);
+ data = restart;
+ if (val & PHY_LED_DISABLE)
+ data |= PORT_LED_OFF;
+ else
+ data &= ~PORT_LED_OFF;
+ if (val & PHY_TRANSMIT_DISABLE)
+ data |= PORT_TX_DISABLE;
+ else
+ data &= ~PORT_TX_DISABLE;
+ if (val & PHY_AUTO_NEG_RESTART)
+ data |= PORT_AUTO_NEG_RESTART;
+ else
+ data &= ~(PORT_AUTO_NEG_RESTART);
+ if (val & PHY_POWER_DOWN)
+ data |= PORT_POWER_DOWN;
+ else
+ data &= ~PORT_POWER_DOWN;
+ if (val & PHY_AUTO_MDIX_DISABLE)
+ data |= PORT_AUTO_MDIX_DISABLE;
+ else
+ data &= ~PORT_AUTO_MDIX_DISABLE;
+ if (val & PHY_FORCE_MDIX)
+ data |= PORT_FORCE_MDIX;
+ else
+ data &= ~PORT_FORCE_MDIX;
+ if (val & PHY_LOOPBACK)
+ data |= PORT_PHY_LOOPBACK;
+ else
+ data &= ~PORT_PHY_LOOPBACK;
+ if (data != restart)
+ ksz_pwrite8(dev, p, P_NEG_RESTART_CTRL, data);
+ break;
+ case PHY_REG_AUTO_NEGOTIATION:
+ ksz_pread8(dev, p, P_LOCAL_CTRL, &ctrl);
+ data = ctrl;
+ data &= ~(PORT_AUTO_NEG_SYM_PAUSE |
+ PORT_AUTO_NEG_100BTX_FD |
+ PORT_AUTO_NEG_100BTX |
+ PORT_AUTO_NEG_10BT_FD |
+ PORT_AUTO_NEG_10BT);
+ if (val & PHY_AUTO_NEG_SYM_PAUSE)
+ data |= PORT_AUTO_NEG_SYM_PAUSE;
+ if (val & PHY_AUTO_NEG_100BTX_FD)
+ data |= PORT_AUTO_NEG_100BTX_FD;
+ if (val & PHY_AUTO_NEG_100BTX)
+ data |= PORT_AUTO_NEG_100BTX;
+ if (val & PHY_AUTO_NEG_10BT_FD)
+ data |= PORT_AUTO_NEG_10BT_FD;
+ if (val & PHY_AUTO_NEG_10BT)
+ data |= PORT_AUTO_NEG_10BT;
+ if (data != ctrl)
+ ksz_pwrite8(dev, p, P_LOCAL_CTRL, data);
+ break;
+ default:
+ break;
+ }
+}
+
+static enum dsa_tag_protocol ksz8895_get_tag_protocol(struct dsa_switch *ds)
+{
+ /* Use KSZ8795 tail tagging code. */
+ return DSA_TAG_PROTO_KSZ8795;
+}
+
+static void ksz8895_get_strings(struct dsa_switch *ds, int port, uint8_t *buf)
+{
+ int i;
+
+ for (i = 0; i < TOTAL_SWITCH_COUNTER_NUM; i++) {
+ memcpy(buf + i * ETH_GSTRING_LEN, mib_names[i].string,
+ ETH_GSTRING_LEN);
+ }
+}
+
+static const u8 stp_multicast_addr[] = {
+ 0x01, 0x80, 0xC2, 0x00, 0x00, 0x00
+};
+
+static void ksz8895_cfg_port_member(struct ksz_device *dev, int port,
+ u8 member)
+{
+ u8 data;
+
+ ksz_pread8(dev, port, P_MIRROR_CTRL, &data);
+ data &= ~PORT_VLAN_MEMBERSHIP;
+ data |= (member & dev->port_mask);
+ ksz_pwrite8(dev, port, P_MIRROR_CTRL, data);
+ dev->ports[port].member = member;
+}
+
+static void ksz8895_port_stp_state_set(struct dsa_switch *ds, int port,
+ u8 state)
+{
+ struct ksz_device *dev = ds->priv;
+ struct ksz_port *p = &dev->ports[port];
+ u8 data;
+ int member = -1;
+
+ ksz_pread8(dev, port, P_STP_CTRL, &data);
+ data &= ~(PORT_TX_ENABLE | PORT_RX_ENABLE | PORT_LEARN_DISABLE);
+
+ switch (state) {
+ case BR_STATE_DISABLED:
+ data |= PORT_LEARN_DISABLE;
+ if (port < SWITCH_PORT_NUM)
+ member = 0;
+ break;
+ case BR_STATE_LISTENING:
+ data |= (PORT_RX_ENABLE | PORT_LEARN_DISABLE);
+ if (port < SWITCH_PORT_NUM &&
+ p->stp_state == BR_STATE_DISABLED)
+ member = dev->host_mask | p->vid_member;
+ break;
+ case BR_STATE_LEARNING:
+ data |= PORT_RX_ENABLE;
+ break;
+ case BR_STATE_FORWARDING:
+ data |= (PORT_TX_ENABLE | PORT_RX_ENABLE);
+
+ /* This function is also used internally. */
+ if (port == dev->cpu_port)
+ break;
+
+ /* Port is a member of a bridge. */
+ if (dev->br_member & (1 << port)) {
+ dev->member |= (1 << port);
+ member = dev->member;
+ } else {
+ member = dev->host_mask | p->vid_member;
+ }
+ break;
+ case BR_STATE_BLOCKING:
+ data |= PORT_LEARN_DISABLE;
+ if (port < SWITCH_PORT_NUM &&
+ p->stp_state == BR_STATE_DISABLED)
+ member = dev->host_mask | p->vid_member;
+ break;
+ default:
+ dev_err(ds->dev, "invalid STP state: %d\n", state);
+ return;
+ }
+
+ ksz_pwrite8(dev, port, P_STP_CTRL, data);
+ p->stp_state = state;
+ if (data & PORT_RX_ENABLE)
+ dev->rx_ports |= (1 << port);
+ else
+ dev->rx_ports &= ~(1 << port);
+ if (data & PORT_TX_ENABLE)
+ dev->tx_ports |= (1 << port);
+ else
+ dev->tx_ports &= ~(1 << port);
+
+ /* Port membership may share register with STP state. */
+ if (member >= 0 && member != p->member)
+ ksz8895_cfg_port_member(dev, port, (u8)member);
+
+ /* Check if forwarding needs to be updated. */
+ if (state != BR_STATE_FORWARDING) {
+ if (dev->br_member & (1 << port))
+ dev->member &= ~(1 << port);
+ }
+
+ /* When topology has changed the function ksz_update_port_member
+ * should be called to modify port forwarding behavior. However
+ * as the offload_fwd_mark indication cannot be reported here
+ * the switch forwarding function is not enabled.
+ */
+}
+
+static void ksz8895_flush_dyn_mac_table(struct ksz_device *dev, int port)
+{
+ int cnt;
+ int first;
+ int index;
+ u8 learn[TOTAL_PORT_NUM];
+
+ if ((uint)port < TOTAL_PORT_NUM) {
+ first = port;
+ cnt = port + 1;
+ } else {
+ /* Flush all ports. */
+ first = 0;
+ if (dev->chip_id == 0x8864)
+ first = 1;
+ cnt = dev->mib_port_cnt;
+ }
+ for (index = first; index < cnt; index++) {
+ ksz_pread8(dev, index, P_STP_CTRL, &learn[index]);
+ if (!(learn[index] & PORT_LEARN_DISABLE))
+ ksz_pwrite8(dev, index, P_STP_CTRL,
+ learn[index] | PORT_LEARN_DISABLE);
+ }
+ ksz_cfg(dev, S_FLUSH_TABLE_CTRL, SW_FLUSH_DYN_MAC_TABLE, true);
+ for (index = first; index < cnt; index++) {
+ if (!(learn[index] & PORT_LEARN_DISABLE))
+ ksz_pwrite8(dev, index, P_STP_CTRL, learn[index]);
+ }
+}
+
+static int ksz8895_port_vlan_filtering(struct dsa_switch *ds, int port,
+ bool flag)
+{
+ struct ksz_device *dev = ds->priv;
+
+ ksz_cfg(dev, S_MIRROR_CTRL, SW_VLAN_ENABLE, flag);
+
+ return 0;
+}
+
+static void ksz8895_port_vlan_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct switchdev_trans *trans)
+{
+ struct ksz_device *dev = ds->priv;
+ bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
+ u16 data;
+ u16 vid;
+ u8 fid;
+ u8 member;
+ u8 valid;
+ u16 new_pvid = 0;
+
+ ksz_port_cfg(dev, port, P_TAG_CTRL, PORT_REMOVE_TAG, untagged);
+
+ for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
+ ksz8895_r_vlan_table(dev, vid, &data);
+ ksz8895_from_vlan(data, &fid, &member, &valid);
+
+ /* First time to setup the VLAN entry. */
+ if (!valid) {
+ /* Need to find a way to map VID to FID. */
+ fid = 1;
+ valid = 1;
+ }
+ member |= BIT(port);
+
+ ksz8895_to_vlan(fid, member, valid, &data);
+ ksz8895_w_vlan_table(dev, vid, data);
+
+ /* change PVID */
+ if (vlan->flags & BRIDGE_VLAN_INFO_PVID)
+ new_pvid = vid;
+ }
+
+ if (new_pvid) {
+ ksz_pread16(dev, port, REG_PORT_CTRL_VID, &vid);
+ vid &= 0xfff;
+ vid |= new_pvid;
+ ksz_pwrite16(dev, port, REG_PORT_CTRL_VID, vid);
+ }
+}
+
+static int ksz8895_port_vlan_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan)
+{
+ struct ksz_device *dev = ds->priv;
+ bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
+ u16 data;
+ u16 vid;
+ u16 pvid;
+ u8 fid;
+ u8 member;
+ u8 valid;
+ u16 new_pvid = 0;
+
+ ksz_pread16(dev, port, REG_PORT_CTRL_VID, &pvid);
+ pvid = pvid & 0xFFF;
+
+ ksz_port_cfg(dev, port, P_TAG_CTRL, PORT_REMOVE_TAG, untagged);
+
+ for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
+ ksz8895_r_vlan_table(dev, vid, &data);
+ ksz8895_from_vlan(data, &fid, &member, &valid);
+
+ member &= ~BIT(port);
+
+ /* Invalidate the entry if no more member. */
+ if (!member) {
+ fid = 0;
+ valid = 0;
+ }
+
+ if (pvid == vid)
+ new_pvid = 1;
+
+ ksz8895_to_vlan(fid, member, valid, &data);
+ ksz8895_w_vlan_table(dev, vid, data);
+ }
+
+ if (new_pvid != pvid)
+ ksz_pwrite16(dev, port, REG_PORT_CTRL_VID, pvid);
+
+ return 0;
+}
+
+static int ksz8895_port_mirror_add(struct dsa_switch *ds, int port,
+ struct dsa_mall_mirror_tc_entry *mirror,
+ bool ingress)
+{
+ struct ksz_device *dev = ds->priv;
+
+ if (ingress) {
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, true);
+ dev->mirror_rx |= (1 << port);
+ } else {
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, true);
+ dev->mirror_tx |= (1 << port);
+ }
+
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_SNIFFER, false);
+
+ /* configure mirror port */
+ if (dev->mirror_rx || dev->mirror_tx)
+ ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL,
+ PORT_MIRROR_SNIFFER, true);
+
+ return 0;
+}
+
+static void ksz8895_port_mirror_del(struct dsa_switch *ds, int port,
+ struct dsa_mall_mirror_tc_entry *mirror)
+{
+ struct ksz_device *dev = ds->priv;
+ u8 data;
+
+ if (mirror->ingress) {
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, false);
+ dev->mirror_rx &= ~(1 << port);
+ } else {
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, false);
+ dev->mirror_tx &= ~(1 << port);
+ }
+
+ ksz_pread8(dev, port, P_MIRROR_CTRL, &data);
+
+ if (!dev->mirror_rx && !dev->mirror_tx)
+ ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL,
+ PORT_MIRROR_SNIFFER, false);
+}
+
+static void ksz8895_phy_setup(struct ksz_device *dev, int port,
+ struct phy_device *phy)
+{
+ /* SUPPORTED_Pause can be removed to disable flow control when
+ * rate limiting is used.
+ */
+ phy->supported &= ~SUPPORTED_Asym_Pause;
+ phy->advertising = phy->supported;
+}
+
+static void ksz8895_port_setup(struct ksz_device *dev, int port, bool cpu_port)
+{
+ u8 member;
+ struct ksz_port *p = &dev->ports[port];
+
+ /* enable broadcast storm limit */
+ ksz_port_cfg(dev, port, P_BCAST_STORM_CTRL, PORT_BROADCAST_STORM, true);
+
+ ksz8895_set_prio_queue(dev, port, 4);
+
+ /* disable DiffServ priority */
+ ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_DIFFSERV_ENABLE, false);
+
+ /* replace priority */
+ ksz_port_cfg(dev, port, P_802_1P_CTRL, PORT_802_1P_REMAPPING, false);
+
+ /* enable 802.1p priority */
+ ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_802_1P_ENABLE, true);
+
+ if (cpu_port) {
+ member = dev->port_mask;
+ dev->on_ports = dev->host_mask;
+ dev->live_ports = dev->host_mask;
+ } else {
+ member = dev->host_mask | p->vid_member;
+ dev->on_ports |= (1 << port);
+
+ /* Link was detected before port is enabled. */
+ if (p->link_up)
+ dev->live_ports |= (1 << port);
+ }
+ ksz8895_cfg_port_member(dev, port, member);
+}
+
+static void ksz8895_config_cpu_port(struct dsa_switch *ds)
+{
+ struct ksz_device *dev = ds->priv;
+ struct ksz_port *p;
+ int i;
+ u8 remote;
+
+ ds->num_ports = dev->port_cnt + 1;
+
+ ksz_cfg(dev, S_TAIL_TAG_CTRL, SW_TAIL_TAG_ENABLE, true);
+
+ p = &dev->ports[dev->cpu_port];
+ p->vid_member = dev->port_mask;
+ p->on = 1;
+
+ ksz8895_port_setup(dev, dev->cpu_port, true);
+ dev->member = dev->host_mask;
+
+ for (i = 0; i < SWITCH_PORT_NUM; i++) {
+ p = &dev->ports[i];
+
+ /* Initialize to non-zero so that ksz_cfg_port_member() will
+ * be called.
+ */
+ p->vid_member = (1 << i);
+ p->member = dev->port_mask;
+ ksz8895_port_stp_state_set(ds, i, BR_STATE_DISABLED);
+
+ /* First port is disabled in KSZ8864. */
+ if (dev->chip_id == 0x8864 && i == 0)
+ break;
+ p->on = 1;
+ p->phy = 1;
+ }
+ ksz_read8(dev, REG_SW_CFG, &remote);
+ if (remote & SW_PORT_3_FIBER)
+ dev->ports[2].fiber = 1;
+ for (i = 0; i < dev->phy_port_cnt; i++) {
+ p = &dev->ports[i];
+ if (!p->on)
+ continue;
+ if (p->fiber)
+ ksz_port_cfg(dev, i, P_STP_CTRL, PORT_FORCE_FLOW_CTRL,
+ true);
+ else
+ ksz_port_cfg(dev, i, P_STP_CTRL, PORT_FORCE_FLOW_CTRL,
+ false);
+ }
+}
+
+static int ksz8895_set_addr(struct dsa_switch *ds, u8 *addr)
+{
+ /* Setting the switch MAC address is required for the self-address
+ * filtering feature. However, there is a flaw in the feature and
+ * so it cannot be used.
+ * The dynamic MAC table also does not learn the address so unicast
+ * forwarding is not operating correctly for the host.
+ */
+ return 0;
+}
+
+static int ksz8895_setup(struct dsa_switch *ds)
+{
+ u8 data8;
+ u16 data16;
+ u32 value;
+ int i;
+ struct alu_struct alu;
+ struct ksz_device *dev = ds->priv;
+ int ret = 0;
+
+ dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table),
+ dev->num_vlans, GFP_KERNEL);
+ if (!dev->vlan_cache)
+ return -ENOMEM;
+
+ ret = ksz8895_reset_switch(dev);
+ if (ret) {
+ dev_err(ds->dev, "failed to reset switch\n");
+ return ret;
+ }
+
+ ksz_cfg(dev, S_REPLACE_VID_CTRL, SW_FLOW_CTRL, true);
+
+ /* Enable automatic fast aging when link changed detected. */
+ ksz_cfg(dev, S_LINK_AGING_CTRL, SW_LINK_AUTO_AGING, true);
+
+ ksz_read8(dev, REG_SW_CTRL_1, &data8);
+
+ /* Enable aggressive back off algorithm in half duplex mode. */
+ data8 |= SW_AGGR_BACKOFF;
+ ksz_write8(dev, REG_SW_CTRL_1, data8);
+
+ ksz_read8(dev, REG_SW_CTRL_2, &data8);
+
+ /* Make sure unicast VLAN boundary is set as default. */
+ data8 |= UNICAST_VLAN_BOUNDARY;
+
+ /* Enable no excessive collision drop. */
+ data8 |= NO_EXC_COLLISION_DROP;
+ ksz_write8(dev, REG_SW_CTRL_2, data8);
+
+ ksz8895_config_cpu_port(ds);
+
+ ksz_cfg(dev, REG_SW_CTRL_2, MULTICAST_STORM_DISABLE, true);
+
+ ksz_cfg(dev, S_REPLACE_VID_CTRL, SW_REPLACE_VID, false);
+
+ ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false);
+
+ /* set broadcast storm protection 10% rate */
+ data8 = BROADCAST_STORM_PROT_RATE;
+ value = ((u32)BROADCAST_STORM_VALUE * data8) / 100;
+ if (value > BROADCAST_STORM_RATE)
+ value = BROADCAST_STORM_RATE;
+ ksz_read16(dev, S_REPLACE_VID_CTRL, &data16);
+ data16 &= ~BROADCAST_STORM_RATE;
+ data16 |= value;
+ ksz_write16(dev, S_REPLACE_VID_CTRL, data16);
+
+ for (i = 0; i < VLAN_TABLE_ENTRIES; i++)
+ ksz8895_r_vlan_entries(dev, i);
+
+ /* Setup STP address for STP operation. */
+ memset(&alu, 0, sizeof(alu));
+ memcpy(alu.mac, stp_multicast_addr, ETH_ALEN);
+ alu.is_static = true;
+ alu.is_override = true;
+ alu.port_forward = dev->host_mask;
+
+ ksz8895_w_sta_mac_table(dev, 0, &alu);
+
+ ksz_write8(dev, REG_CHIP_ID1, SW_START);
+
+ ksz_init_mib_timer(dev);
+
+ return 0;
+}
+
+static const struct dsa_switch_ops ksz8895_switch_ops = {
+ .get_tag_protocol = ksz8895_get_tag_protocol,
+ .setup = ksz8895_setup,
+ .set_addr = ksz8895_set_addr,
+ .phy_read = ksz_phy_read16,
+ .phy_write = ksz_phy_write16,
+ .adjust_link = ksz_adjust_link,
+ .port_enable = ksz_enable_port,
+ .port_disable = ksz_disable_port,
+ .get_strings = ksz8895_get_strings,
+ .get_ethtool_stats = ksz_get_ethtool_stats,
+ .get_sset_count = ksz_sset_count,
+ .port_bridge_join = ksz_port_bridge_join,
+ .port_bridge_leave = ksz_port_bridge_leave,
+ .port_stp_state_set = ksz8895_port_stp_state_set,
+ .port_fast_age = ksz_port_fast_age,
+ .port_vlan_filtering = ksz8895_port_vlan_filtering,
+ .port_vlan_prepare = ksz_port_vlan_prepare,
+ .port_vlan_add = ksz8895_port_vlan_add,
+ .port_vlan_del = ksz8895_port_vlan_del,
+ .port_fdb_dump = ksz_port_fdb_dump,
+ .port_mdb_prepare = ksz_port_mdb_prepare,
+ .port_mdb_add = ksz_port_mdb_add,
+ .port_mdb_del = ksz_port_mdb_del,
+ .port_mirror_add = ksz8895_port_mirror_add,
+ .port_mirror_del = ksz8895_port_mirror_del,
+};
+
+static u32 ksz8895_get_port_addr(int port, int offset)
+{
+ return PORT_CTRL_ADDR(port, offset);
+}
+
+static int ksz8895_switch_detect(struct ksz_device *dev)
+{
+ u16 id16;
+ u8 id1;
+ u8 id2;
+ int ret;
+
+ /* read chip id */
+ ret = ksz_read16(dev, REG_CHIP_ID0, &id16);
+ if (ret)
+ return ret;
+
+ id1 = id16 >> 8;
+ id2 = id16 & SW_CHIP_ID_M;
+ if (id1 != FAMILY_ID ||
+ (id2 != CHIP_ID_95 && id2 != CHIP_ID_95R))
+ return -ENODEV;
+
+ dev->mib_port_cnt = TOTAL_PORT_NUM;
+ dev->phy_port_cnt = SWITCH_PORT_NUM;
+ dev->port_cnt = SWITCH_PORT_NUM;
+
+ ksz_read8(dev, REG_KSZ8864_CHIP_ID, &id2);
+ if (id2 & SW_KSZ8864) {
+ dev->port_cnt--;
+ id2 = 0x64;
+ } else {
+ id2 = 0x95;
+ }
+ id16 = 0x8800;
+ id16 |= id2;
+ dev->chip_id = id16;
+
+ dev->cpu_port = dev->mib_port_cnt - 1;
+ dev->host_mask = (1 << dev->cpu_port);
+
+ return 0;
+}
+
+struct ksz_chip_data {
+ u16 chip_id;
+ const char *dev_name;
+ int num_vlans;
+ int num_alus;
+ int num_statics;
+ int cpu_ports;
+ int port_cnt;
+};
+
+static const struct ksz_chip_data ksz8895_switch_chips[] = {
+ {
+ .chip_id = 0x8895,
+ .dev_name = "KSZ8895",
+ .num_vlans = 4096,
+ .num_alus = 0,
+ .num_statics = 8,
+ .cpu_ports = 0x10, /* can be configured as cpu port */
+ .port_cnt = 4, /* total physical port count */
+ },
+ {
+ .chip_id = 0x8864,
+ .dev_name = "KSZ8864",
+ .num_vlans = 4096,
+ .num_alus = 0,
+ .num_statics = 8,
+ .cpu_ports = 0x10, /* can be configured as cpu port */
+ .port_cnt = 3, /* total physical port count */
+ },
+};
+
+static int ksz8895_switch_init(struct ksz_device *dev)
+{
+ int i;
+
+ mutex_init(&dev->reg_mutex);
+ mutex_init(&dev->stats_mutex);
+ mutex_init(&dev->alu_mutex);
+ mutex_init(&dev->vlan_mutex);
+
+ dev->ds->ops = &ksz8895_switch_ops;
+
+ for (i = 0; i < ARRAY_SIZE(ksz8895_switch_chips); i++) {
+ const struct ksz_chip_data *chip = &ksz8895_switch_chips[i];
+
+ if (dev->chip_id == chip->chip_id) {
+ dev->name = chip->dev_name;
+ dev->num_vlans = chip->num_vlans;
+ dev->num_alus = chip->num_alus;
+ dev->num_statics = chip->num_statics;
+ dev->port_cnt = chip->port_cnt;
+ dev->cpu_ports = chip->cpu_ports;
+
+ break;
+ }
+ }
+
+ /* no switch found */
+ if (!dev->cpu_ports)
+ return -ENODEV;
+
+ dev->port_mask = (1 << dev->port_cnt) - 1;
+ dev->port_mask |= dev->host_mask;
+
+ dev->reg_mib_cnt = SWITCH_COUNTER_NUM;
+ dev->mib_cnt = TOTAL_SWITCH_COUNTER_NUM;
+
+ i = dev->mib_port_cnt;
+ dev->ports = devm_kzalloc(dev->dev, sizeof(struct ksz_port) * i,
+ GFP_KERNEL);
+ if (!dev->ports)
+ return -ENOMEM;
+ for (i = 0; i < dev->mib_port_cnt; i++) {
+ mutex_init(&dev->ports[i].mib.cnt_mutex);
+ dev->ports[i].mib.counters =
+ devm_kzalloc(dev->dev,
+ sizeof(u64) *
+ (TOTAL_SWITCH_COUNTER_NUM + 1),
+ GFP_KERNEL);
+ if (!dev->ports[i].mib.counters)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void ksz8895_switch_exit(struct ksz_device *dev)
+{
+ ksz8895_reset_switch(dev);
+ ksz_write8(dev, REG_CHIP_ID1, SW_START);
+}
+
+static const struct ksz_dev_ops ksz8895_dev_ops = {
+ .get_port_addr = ksz8895_get_port_addr,
+ .cfg_port_member = ksz8895_cfg_port_member,
+ .flush_dyn_mac_table = ksz8895_flush_dyn_mac_table,
+ .phy_setup = ksz8895_phy_setup,
+ .port_setup = ksz8895_port_setup,
+ .r_phy = ksz8895_r_phy,
+ .w_phy = ksz8895_w_phy,
+ .r_dyn_mac_table = ksz8895_r_dyn_mac_table,
+ .r_sta_mac_table = ksz8895_r_sta_mac_table,
+ .w_sta_mac_table = ksz8895_w_sta_mac_table,
+ .r_mib_cnt = ksz8895_r_mib_cnt,
+ .r_mib_pkt = ksz8895_r_mib_pkt,
+ .port_init_cnt = ksz8895_port_init_cnt,
+ .shutdown = ksz8895_reset_switch,
+ .detect = ksz8895_switch_detect,
+ .init = ksz8895_switch_init,
+ .exit = ksz8895_switch_exit,
+};
+
+int ksz8895_switch_register(struct ksz_device *dev)
+{
+ return ksz_switch_register(dev, &ksz8895_dev_ops);
+}
+EXPORT_SYMBOL(ksz8895_switch_register);
+
+MODULE_AUTHOR("Tristram Ha <Tristram.Ha@microchip.com>");
+MODULE_DESCRIPTION("Microchip KSZ8895 Series Switch DSA Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/dsa/microchip/ksz8895_reg.h b/drivers/net/dsa/microchip/ksz8895_reg.h
new file mode 100644
index 0000000..1d6ffb1
--- /dev/null
+++ b/drivers/net/dsa/microchip/ksz8895_reg.h
@@ -0,0 +1,824 @@
+/**
+ * Microchip KSZ8895 register definitions
+ *
+ * Copyright (c) 2017 Microchip Technology Inc.
+ * Tristram Ha <Tristram.Ha@microchip.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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __KSZ8895_REG_H
+#define __KSZ8895_REG_H
+
+#define KS_PORT_M 0x1F
+
+#define KS_PRIO_M 0x3
+#define KS_PRIO_S 2
+
+#define REG_CHIP_ID0 0x00
+
+#define FAMILY_ID 0x95
+
+#define REG_CHIP_ID1 0x01
+
+#define SW_CHIP_ID_M 0xF0
+#define SW_CHIP_ID_S 4
+#define SW_REVISION_M 0x0E
+#define SW_REVISION_S 1
+#define SW_START 0x01
+
+#define CHIP_ID_95 0x40
+#define CHIP_ID_95R 0x60
+
+#define REG_SW_CTRL_0 0x02
+
+#define SW_NEW_BACKOFF BIT(7)
+#define SW_FLUSH_DYN_MAC_TABLE BIT(5)
+#define SW_FLUSH_STA_MAC_TABLE BIT(4)
+#define SW_UNH_MODE BIT(1)
+#define SW_LINK_AUTO_AGING BIT(0)
+
+#define REG_SW_CTRL_1 0x03
+
+#define SW_PASS_ALL BIT(7)
+#define SW_2K_PACKET BIT(6)
+#define SW_TX_FLOW_CTRL_DISABLE BIT(5)
+#define SW_RX_FLOW_CTRL_DISABLE BIT(4)
+#define SW_CHECK_LENGTH BIT(3)
+#define SW_AGING_ENABLE BIT(2)
+#define SW_FAST_AGING BIT(1)
+#define SW_AGGR_BACKOFF BIT(0)
+
+#define REG_SW_CTRL_2 0x04
+
+#define UNICAST_VLAN_BOUNDARY BIT(7)
+#define MULTICAST_STORM_DISABLE BIT(6)
+#define SW_BACK_PRESSURE BIT(5)
+#define FAIR_FLOW_CTRL BIT(4)
+#define NO_EXC_COLLISION_DROP BIT(3)
+#define SW_HUGE_PACKET BIT(2)
+#define SW_LEGAL_PACKET BIT(1)
+
+#define REG_SW_CTRL_3 0x05
+#define SW_VLAN_ENABLE BIT(7)
+#define SW_IGMP_SNOOP BIT(6)
+#define SW_DIRECT BIT(5)
+#define SW_PRE_TAG BIT(4)
+#define SW_VLAN_TAG BIT(1)
+#define SW_MIRROR_RX_TX BIT(0)
+
+#define REG_SW_CTRL_4 0x06
+
+#define SW_HALF_DUPLEX_FLOW_CTRL BIT(7)
+#define SW_HALF_DUPLEX BIT(6)
+#define SW_FLOW_CTRL BIT(5)
+#define SW_10_MBIT BIT(4)
+#define SW_REPLACE_VID BIT(3)
+#define BROADCAST_STORM_RATE_HI 0x07
+
+#define REG_SW_CTRL_5 0x07
+
+#define BROADCAST_STORM_RATE_LO 0xFF
+#define BROADCAST_STORM_RATE 0x07FF
+
+#define REG_SW_CTRL_9 0x0B
+
+#define SW_DATA_SAMPLING_NEG BIT(6)
+#define SW_PHY_POWER_SAVE_DISABLE BIT(3)
+#define SW_LED_MODE_1 BIT(1)
+#define SW_SPI_SAMPLING_RISING BIT(0)
+
+#define REG_SW_CTRL_10 0x0C
+
+#define SPI_CLK_125_MHZ 0x20
+#define SPI_CLK_83_33_MHZ 0x10
+#define SPI_CLK_41_67_MHZ 0x00
+#define SW_TAIL_TAG_ENABLE BIT(1)
+#define SW_PASS_PAUSE BIT(0)
+
+#define REG_SW_CTRL_11 0x0D
+
+#define REG_POWER_MANAGEMENT_1 0x0E
+
+#define SW_PLL_POWER_DOWN BIT(5)
+#define SW_POWER_MANAGEMENT_MODE_M 0x3
+#define SW_POWER_MANAGEMENT_MODE_S 3
+#define SW_POWER_NORMAL 0
+#define SW_ENERGY_DETECTION 1
+#define SW_SOFTWARE_POWER_DOWN 2
+#define SW_POWER_SAVING 3
+
+#define REG_POWER_MANAGEMENT_2 0x0F
+
+#define REG_PORT_1_CTRL_0 0x10
+#define REG_PORT_2_CTRL_0 0x20
+#define REG_PORT_3_CTRL_0 0x30
+#define REG_PORT_4_CTRL_0 0x40
+#define REG_PORT_5_CTRL_0 0x50
+
+#define PORT_BROADCAST_STORM BIT(7)
+#define PORT_DIFFSERV_ENABLE BIT(6)
+#define PORT_802_1P_ENABLE BIT(5)
+#define PORT_BASED_PRIO_S 3
+#define PORT_BASED_PRIO_M KS_PRIO_M
+#define PORT_PORT_PRIO_0 0
+#define PORT_PORT_PRIO_1 1
+#define PORT_PORT_PRIO_2 2
+#define PORT_PORT_PRIO_3 3
+#define PORT_INSERT_TAG BIT(2)
+#define PORT_REMOVE_TAG BIT(1)
+#define PORT_QUEUE_SPLIT_L BIT(0)
+
+#define REG_PORT_1_CTRL_1 0x11
+#define REG_PORT_2_CTRL_1 0x21
+#define REG_PORT_3_CTRL_1 0x31
+#define REG_PORT_4_CTRL_1 0x41
+#define REG_PORT_5_CTRL_1 0x51
+
+#define PORT_MIRROR_SNIFFER BIT(7)
+#define PORT_MIRROR_RX BIT(6)
+#define PORT_MIRROR_TX BIT(5)
+#define PORT_VLAN_MEMBERSHIP KS_PORT_M
+
+#define REG_PORT_1_CTRL_2 0x12
+#define REG_PORT_2_CTRL_2 0x22
+#define REG_PORT_3_CTRL_2 0x32
+#define REG_PORT_4_CTRL_2 0x42
+#define REG_PORT_5_CTRL_2 0x52
+
+#define PORT_802_1P_REMAPPING BIT(7)
+#define PORT_INGRESS_FILTER BIT(6)
+#define PORT_DISCARD_NON_VID BIT(5)
+#define PORT_FORCE_FLOW_CTRL BIT(4)
+#define PORT_BACK_PRESSURE BIT(3)
+#define PORT_TX_ENABLE BIT(2)
+#define PORT_RX_ENABLE BIT(1)
+#define PORT_LEARN_DISABLE BIT(0)
+
+#define REG_PORT_1_CTRL_3 0x13
+#define REG_PORT_2_CTRL_3 0x23
+#define REG_PORT_3_CTRL_3 0x33
+#define REG_PORT_4_CTRL_3 0x43
+#define REG_PORT_5_CTRL_3 0x53
+#define REG_PORT_1_CTRL_4 0x14
+#define REG_PORT_2_CTRL_4 0x24
+#define REG_PORT_3_CTRL_4 0x34
+#define REG_PORT_4_CTRL_4 0x44
+#define REG_PORT_5_CTRL_4 0x54
+
+#define PORT_DEFAULT_VID 0x0001
+
+#define REG_PORT_1_STATUS_0 0x19
+#define REG_PORT_2_STATUS_0 0x29
+#define REG_PORT_3_STATUS_0 0x39
+#define REG_PORT_4_STATUS_0 0x49
+#define REG_PORT_5_STATUS_0 0x59
+
+#define PORT_HP_MDIX BIT(7)
+#define PORT_REVERSED_POLARITY BIT(5)
+#define PORT_TX_FLOW_CTRL BIT(4)
+#define PORT_RX_FLOW_CTRL BIT(3)
+#define PORT_STAT_SPEED_100MBIT BIT(2)
+#define PORT_STAT_FULL_DUPLEX BIT(1)
+
+#define REG_PORT_1_LINK_MD_CTRL 0x1A
+#define REG_PORT_2_LINK_MD_CTRL 0x2A
+#define REG_PORT_3_LINK_MD_CTRL 0x3A
+#define REG_PORT_4_LINK_MD_CTRL 0x4A
+#define REG_PORT_5_LINK_MD_CTRL 0x5A
+
+#define PORT_CABLE_10M_SHORT BIT(7)
+#define PORT_CABLE_DIAG_RESULT_M 0x3
+#define PORT_CABLE_DIAG_RESULT_S 5
+#define PORT_CABLE_STAT_NORMAL 0
+#define PORT_CABLE_STAT_OPEN 1
+#define PORT_CABLE_STAT_SHORT 2
+#define PORT_CABLE_STAT_FAILED 3
+#define PORT_START_CABLE_DIAG BIT(4)
+#define PORT_FORCE_LINK BIT(3)
+#define PORT_POWER_SAVING BIT(2)
+#define PORT_PHY_REMOTE_LOOPBACK BIT(1)
+#define PORT_CABLE_FAULT_COUNTER_H 0x01
+
+#define REG_PORT_1_LINK_MD_RESULT 0x1B
+#define REG_PORT_2_LINK_MD_RESULT 0x2B
+#define REG_PORT_3_LINK_MD_RESULT 0x3B
+#define REG_PORT_4_LINK_MD_RESULT 0x4B
+#define REG_PORT_5_LINK_MD_RESULT 0x5B
+
+#define PORT_CABLE_FAULT_COUNTER_L 0xFF
+#define PORT_CABLE_FAULT_COUNTER 0x1FF
+
+#define REG_PORT_1_CTRL_5 0x1C
+#define REG_PORT_2_CTRL_5 0x2C
+#define REG_PORT_3_CTRL_5 0x3C
+#define REG_PORT_4_CTRL_5 0x4C
+#define REG_PORT_5_CTRL_5 0x5C
+
+#define PORT_AUTO_NEG_DISABLE BIT(7)
+#define PORT_FORCE_100_MBIT BIT(6)
+#define PORT_FORCE_FULL_DUPLEX BIT(5)
+#define PORT_AUTO_NEG_SYM_PAUSE BIT(4)
+#define PORT_AUTO_NEG_100BTX_FD BIT(3)
+#define PORT_AUTO_NEG_100BTX BIT(2)
+#define PORT_AUTO_NEG_10BT_FD BIT(1)
+#define PORT_AUTO_NEG_10BT BIT(0)
+
+#define REG_PORT_1_CTRL_6 0x1D
+#define REG_PORT_2_CTRL_6 0x2D
+#define REG_PORT_3_CTRL_6 0x3D
+#define REG_PORT_4_CTRL_6 0x4D
+#define REG_PORT_5_CTRL_6 0x5D
+
+#define PORT_LED_OFF BIT(7)
+#define PORT_TX_DISABLE BIT(6)
+#define PORT_AUTO_NEG_RESTART BIT(5)
+#define PORT_POWER_DOWN BIT(3)
+#define PORT_AUTO_MDIX_DISABLE BIT(2)
+#define PORT_FORCE_MDIX BIT(1)
+#define PORT_MAC_LOOPBACK BIT(0)
+
+#define REG_PORT_1_STATUS_1 0x1E
+#define REG_PORT_2_STATUS_1 0x2E
+#define REG_PORT_3_STATUS_1 0x3E
+#define REG_PORT_4_STATUS_1 0x4E
+#define REG_PORT_5_STATUS_1 0x5E
+
+#define PORT_MDIX_STATUS BIT(7)
+#define PORT_AUTO_NEG_COMPLETE BIT(6)
+#define PORT_STAT_LINK_GOOD BIT(5)
+#define PORT_REMOTE_SYM_PAUSE BIT(4)
+#define PORT_REMOTE_100BTX_FD BIT(3)
+#define PORT_REMOTE_100BTX BIT(2)
+#define PORT_REMOTE_10BT_FD BIT(1)
+#define PORT_REMOTE_10BT BIT(0)
+
+#define REG_PORT_1_STATUS_2 0x1F
+#define REG_PORT_2_STATUS_2 0x2F
+#define REG_PORT_3_STATUS_2 0x3F
+#define REG_PORT_4_STATUS_2 0x4F
+#define REG_PORT_5_STATUS_2 0x5F
+
+#define PORT_PHY_LOOPBACK BIT(7)
+#define PORT_PHY_ISOLATE BIT(5)
+#define PORT_PHY_SOFT_RESET BIT(4)
+#define PORT_PHY_FORCE_LINK BIT(3)
+#define PORT_PHY_MODE_M 0x7
+#define PHY_MODE_IN_AUTO_NEG 1
+#define PHY_MODE_10BT_HALF 2
+#define PHY_MODE_100BT_HALF 3
+#define PHY_MODE_10BT_FULL 5
+#define PHY_MODE_100BT_FULL 6
+#define PHY_MODE_ISOLDATE 7
+
+#define REG_PORT_CTRL_0 0x00
+#define REG_PORT_CTRL_1 0x01
+#define REG_PORT_CTRL_2 0x02
+#define REG_PORT_CTRL_VID 0x03
+
+#define REG_PORT_STATUS_0 0x09
+#define REG_PORT_LINK_MD_CTRL 0x0A
+#define REG_PORT_LINK_MD_RESULT 0x0B
+#define REG_PORT_CTRL_5 0x0C
+#define REG_PORT_CTRL_6 0x0D
+#define REG_PORT_STATUS_1 0x0E
+#define REG_PORT_STATUS_2 0x0F
+
+#define REG_PORT_CTRL_8 0xA0
+#define REG_PORT_CTRL_9 0xA1
+#define REG_PORT_RATE_CTRL_3 0xA2
+#define REG_PORT_RATE_CTRL_2 0xA3
+#define REG_PORT_RATE_CTRL_1 0xA4
+#define REG_PORT_RATE_CTRL_0 0xA5
+#define REG_PORT_RATE_LIMIT 0xA6
+#define REG_PORT_IN_RATE_0 0xA7
+#define REG_PORT_IN_RATE_1 0xA8
+#define REG_PORT_IN_RATE_2 0xA9
+#define REG_PORT_IN_RATE_3 0xAA
+#define REG_PORT_OUT_RATE_0 0xAB
+#define REG_PORT_OUT_RATE_1 0xAC
+#define REG_PORT_OUT_RATE_2 0xAD
+#define REG_PORT_OUT_RATE_3 0xAE
+
+#define PORT_CTRL_ADDR(port, addr) \
+ ((addr) + REG_PORT_1_CTRL_0 + (port) * \
+ (REG_PORT_2_CTRL_0 - REG_PORT_1_CTRL_0))
+
+#define REG_SW_MAC_ADDR_0 0x68
+#define REG_SW_MAC_ADDR_1 0x69
+#define REG_SW_MAC_ADDR_2 0x6A
+#define REG_SW_MAC_ADDR_3 0x6B
+#define REG_SW_MAC_ADDR_4 0x6C
+#define REG_SW_MAC_ADDR_5 0x6D
+
+#define REG_IND_CTRL_0 0x6E
+
+#define TABLE_READ BIT(4)
+#define TABLE_SELECT_S 2
+#define TABLE_STATIC_MAC_V 0
+#define TABLE_VLAN_V 1
+#define TABLE_DYNAMIC_MAC_V 2
+#define TABLE_MIB_V 3
+#define TABLE_STATIC_MAC (TABLE_STATIC_MAC_V << TABLE_SELECT_S)
+#define TABLE_VLAN (TABLE_VLAN_V << TABLE_SELECT_S)
+#define TABLE_DYNAMIC_MAC (TABLE_DYNAMIC_MAC_V << TABLE_SELECT_S)
+#define TABLE_MIB (TABLE_MIB_V << TABLE_SELECT_S)
+
+#define REG_IND_CTRL_1 0x6F
+
+#define TABLE_ENTRY_MASK 0x03FF
+
+#define REG_IND_DATA_8 0x70
+#define REG_IND_DATA_7 0x71
+#define REG_IND_DATA_6 0x72
+#define REG_IND_DATA_5 0x73
+#define REG_IND_DATA_4 0x74
+#define REG_IND_DATA_3 0x75
+#define REG_IND_DATA_2 0x76
+#define REG_IND_DATA_1 0x77
+#define REG_IND_DATA_0 0x78
+
+#define REG_IND_DATA_CHECK REG_IND_DATA_6
+#define REG_IND_MIB_CHECK REG_IND_DATA_3
+#define REG_IND_DATA_HI REG_IND_DATA_7
+#define REG_IND_DATA_LO REG_IND_DATA_3
+
+#define REG_INT_STATUS 0x7C
+#define REG_INT_ENABLE 0x7D
+
+#define INT_PORT_5 BIT(4)
+#define INT_PORT_4 BIT(3)
+#define INT_PORT_3 BIT(2)
+#define INT_PORT_2 BIT(1)
+#define INT_PORT_1 BIT(0)
+
+#define REG_SW_CTRL_12 0x80
+#define REG_SW_CTRL_13 0x81
+
+#define SWITCH_802_1P_MASK 3
+#define SWITCH_802_1P_BASE 3
+#define SWITCH_802_1P_SHIFT 2
+
+#define SW_802_1P_MAP_M KS_PRIO_M
+#define SW_802_1P_MAP_S KS_PRIO_S
+
+#define REG_SWITCH_CTRL_14 0x82
+
+#define SW_PRIO_MAPPING_M KS_PRIO_M
+#define SW_PRIO_MAPPING_S 6
+#define SW_PRIO_MAP_3_HI 0
+#define SW_PRIO_MAP_2_HI 2
+#define SW_PRIO_MAP_0_LO 3
+
+#define REG_SW_CTRL_15 0x83
+#define REG_SW_CTRL_16 0x84
+
+#define SW_DRIVE_STRENGTH_M 0x3
+#define SW_DRIVE_STRENGTH_4MA 0
+#define SW_DRIVE_STRENGTH_8MA 1
+#define SW_DRIVE_STRENGTH_10MA 2
+#define SW_DRIVE_STRENGTH_14MA 3
+#define SW_MII_DRIVE_STRENGTH_S 6
+
+#define REG_SW_CTRL_17 0x85
+#define REG_SW_CTRL_18 0x86
+
+#define SW_SELF_ADDR_FILTER_ENABLE BIT(6)
+
+#define REG_SW_UNK_UCAST_CTRL 0x83
+#define REG_SW_UNK_MCAST_CTRL 0x84
+#define REG_SW_UNK_VID_CTRL 0x85
+#define REG_SW_UNK_IP_MCAST_CTRL 0x86
+
+#define SW_UNK_FWD_ENABLE BIT(5)
+#define SW_UNK_FWD_MAP KS_PORT_M
+
+#define REG_SW_CTRL_19 0x87
+
+#define SW_IN_RATE_LIMIT_PERIOD_M 0x3
+#define SW_IN_RATE_LIMIT_PERIOD_S 4
+#define SW_IN_RATE_LIMIT_16_MS 0
+#define SW_IN_RATE_LIMIT_64_MS 1
+#define SW_IN_RATE_LIMIT_256_MS 2
+#define SW_OUT_RATE_LIMIT_QUEUE_BASED BIT(3)
+#define SW_INS_TAG_ENABLE BIT(2)
+
+#define REG_TOS_PRIO_CTRL_0 0x90
+#define REG_TOS_PRIO_CTRL_1 0x91
+#define REG_TOS_PRIO_CTRL_2 0x92
+#define REG_TOS_PRIO_CTRL_3 0x93
+#define REG_TOS_PRIO_CTRL_4 0x94
+#define REG_TOS_PRIO_CTRL_5 0x95
+#define REG_TOS_PRIO_CTRL_6 0x96
+#define REG_TOS_PRIO_CTRL_7 0x97
+#define REG_TOS_PRIO_CTRL_8 0x98
+#define REG_TOS_PRIO_CTRL_9 0x99
+#define REG_TOS_PRIO_CTRL_10 0x9A
+#define REG_TOS_PRIO_CTRL_11 0x9B
+#define REG_TOS_PRIO_CTRL_12 0x9C
+#define REG_TOS_PRIO_CTRL_13 0x9D
+#define REG_TOS_PRIO_CTRL_14 0x9E
+#define REG_TOS_PRIO_CTRL_15 0x9F
+
+#define TOS_PRIO_M KS_PRIO_M
+#define TOS_PRIO_S KS_PRIO_S
+
+#define REG_PORT_1_CTRL_8 0xB0
+#define REG_PORT_2_CTRL_8 0xC0
+#define REG_PORT_3_CTRL_8 0xD0
+#define REG_PORT_4_CTRL_8 0xE0
+#define REG_PORT_5_CTRL_8 0xF0
+
+#define PORT_INS_TAG_FOR_PORT_5_S 3
+#define PORT_INS_TAG_FOR_PORT_5 BIT(3)
+#define PORT_INS_TAG_FOR_PORT_4 BIT(2)
+#define PORT_INS_TAG_FOR_PORT_3 BIT(1)
+#define PORT_INS_TAG_FOR_PORT_2 BIT(0)
+
+#define REG_PORT_1_CTRL_9 0xB1
+#define REG_PORT_2_CTRL_9 0xC1
+#define REG_PORT_3_CTRL_9 0xD1
+#define REG_PORT_4_CTRL_9 0xE1
+#define REG_PORT_5_CTRL_9 0xF1
+
+#define PORT_QUEUE_SPLIT_H BIT(1)
+#define PORT_QUEUE_SPLIT_1 0
+#define PORT_QUEUE_SPLIT_2 1
+#define PORT_QUEUE_SPLIT_4 2
+#define PORT_DROP_TAG BIT(0)
+
+#define REG_PORT_1_CTRL_10 0xB2
+#define REG_PORT_2_CTRL_10 0xC2
+#define REG_PORT_3_CTRL_10 0xD2
+#define REG_PORT_4_CTRL_10 0xE2
+#define REG_PORT_5_CTRL_10 0xF2
+#define REG_PORT_1_CTRL_11 0xB3
+#define REG_PORT_2_CTRL_11 0xC3
+#define REG_PORT_3_CTRL_11 0xD3
+#define REG_PORT_4_CTRL_11 0xE3
+#define REG_PORT_5_CTRL_11 0xF3
+#define REG_PORT_1_CTRL_12 0xB4
+#define REG_PORT_2_CTRL_12 0xC4
+#define REG_PORT_3_CTRL_12 0xD4
+#define REG_PORT_4_CTRL_12 0xE4
+#define REG_PORT_5_CTRL_12 0xF4
+#define REG_PORT_1_CTRL_13 0xB5
+#define REG_PORT_2_CTRL_13 0xC5
+#define REG_PORT_3_CTRL_13 0xD5
+#define REG_PORT_4_CTRL_13 0xE5
+#define REG_PORT_5_CTRL_13 0xF5
+
+#define REG_PORT_1_RATE_CTRL_3 0xB2
+#define REG_PORT_1_RATE_CTRL_2 0xB3
+#define REG_PORT_1_RATE_CTRL_1 0xB4
+#define REG_PORT_1_RATE_CTRL_0 0xB5
+#define REG_PORT_2_RATE_CTRL_3 0xC2
+#define REG_PORT_2_RATE_CTRL_2 0xC3
+#define REG_PORT_2_RATE_CTRL_1 0xC4
+#define REG_PORT_2_RATE_CTRL_0 0xC5
+#define REG_PORT_3_RATE_CTRL_3 0xD2
+#define REG_PORT_3_RATE_CTRL_2 0xD3
+#define REG_PORT_3_RATE_CTRL_1 0xD4
+#define REG_PORT_3_RATE_CTRL_0 0xD5
+#define REG_PORT_4_RATE_CTRL_3 0xE2
+#define REG_PORT_4_RATE_CTRL_2 0xE3
+#define REG_PORT_4_RATE_CTRL_1 0xE4
+#define REG_PORT_4_RATE_CTRL_0 0xE5
+#define REG_PORT_5_RATE_CTRL_3 0xF2
+#define REG_PORT_5_RATE_CTRL_2 0xF3
+#define REG_PORT_5_RATE_CTRL_1 0xF4
+#define REG_PORT_5_RATE_CTRL_0 0xF5
+
+#define RATE_CTRL_ENABLE BIT(7)
+#define RATE_RATIO_M (BIT(7) - 1)
+
+#define REG_PORT_1_RATE_LIMIT 0xB6
+#define REG_PORT_2_RATE_LIMIT 0xC6
+#define REG_PORT_3_RATE_LIMIT 0xD6
+#define REG_PORT_4_RATE_LIMIT 0xE6
+#define REG_PORT_5_RATE_LIMIT 0xF6
+
+#define PORT_IN_FLOW_CTRL_S 4
+#define PORT_IN_LIMIT_MODE_M 0x3
+#define PORT_IN_LIMIT_MODE_S 2
+#define PORT_COUNT_IFG_S 1
+#define PORT_COUNT_PREAMBLE_S 0
+#define PORT_IN_FLOW_CTRL BIT(PORT_IN_FLOW_CTRL_S)
+#define PORT_IN_ALL 0
+#define PORT_IN_UNICAST 1
+#define PORT_IN_MULTICAST 2
+#define PORT_IN_BROADCAST 3
+#define PORT_COUNT_IFG BIT(PORT_COUNT_IFG_S)
+#define PORT_COUNT_PREAMBLE BIT(PORT_COUNT_PREAMBLE_S)
+
+#define REG_PORT_1_IN_RATE_0 0xB7
+#define REG_PORT_2_IN_RATE_0 0xC7
+#define REG_PORT_3_IN_RATE_0 0xD7
+#define REG_PORT_4_IN_RATE_0 0xE7
+#define REG_PORT_5_IN_RATE_0 0xF7
+#define REG_PORT_1_IN_RATE_1 0xB8
+#define REG_PORT_2_IN_RATE_1 0xC8
+#define REG_PORT_3_IN_RATE_1 0xD8
+#define REG_PORT_4_IN_RATE_1 0xE8
+#define REG_PORT_5_IN_RATE_1 0xF8
+#define REG_PORT_1_IN_RATE_2 0xB9
+#define REG_PORT_2_IN_RATE_2 0xC9
+#define REG_PORT_3_IN_RATE_2 0xD9
+#define REG_PORT_4_IN_RATE_2 0xE9
+#define REG_PORT_5_IN_RATE_2 0xF9
+#define REG_PORT_1_IN_RATE_3 0xBA
+#define REG_PORT_2_IN_RATE_3 0xCA
+#define REG_PORT_3_IN_RATE_3 0xDA
+#define REG_PORT_4_IN_RATE_3 0xEA
+#define REG_PORT_5_IN_RATE_3 0xFA
+
+#define PORT_RATE_LIMIT_M (BIT(7) - 1)
+
+#define REG_PORT_1_OUT_RATE_0 0xBB
+#define REG_PORT_2_OUT_RATE_0 0xCB
+#define REG_PORT_3_OUT_RATE_0 0xDB
+#define REG_PORT_4_OUT_RATE_0 0xEB
+#define REG_PORT_5_OUT_RATE_0 0xFB
+#define REG_PORT_1_OUT_RATE_1 0xBC
+#define REG_PORT_2_OUT_RATE_1 0xCC
+#define REG_PORT_3_OUT_RATE_1 0xDC
+#define REG_PORT_4_OUT_RATE_1 0xEC
+#define REG_PORT_5_OUT_RATE_1 0xFC
+#define REG_PORT_1_OUT_RATE_2 0xBD
+#define REG_PORT_2_OUT_RATE_2 0xCD
+#define REG_PORT_3_OUT_RATE_2 0xDD
+#define REG_PORT_4_OUT_RATE_2 0xED
+#define REG_PORT_5_OUT_RATE_2 0xFD
+#define REG_PORT_1_OUT_RATE_3 0xBE
+#define REG_PORT_2_OUT_RATE_3 0xCE
+#define REG_PORT_3_OUT_RATE_3 0xDE
+#define REG_PORT_4_OUT_RATE_3 0xEE
+#define REG_PORT_5_OUT_RATE_3 0xFE
+
+#define REG_SW_CFG 0xEF
+
+#define SW_PORT_3_FIBER BIT(7)
+
+/* KSZ8864 */
+
+#define REG_PHY_PORT_CTRL_1 0xCF
+
+#define PORT_HALF_DUPLEX BIT(7)
+#define PORT_FLOW_CTRL BIT(6)
+#define PORT_10_MBIT BIT(5)
+
+#define REG_PHY_PORT_CTRL_2 0xDF
+
+#define PORT_MII_MAC_MODE BIT(6)
+
+#define REG_KSZ8864_CHIP_ID 0xFE
+
+#define SW_KSZ8864 BIT(7)
+
+#ifndef PHY_REG_CTRL
+#define PHY_REG_CTRL 0
+
+#define PHY_RESET BIT(15)
+#define PHY_LOOPBACK BIT(14)
+#define PHY_SPEED_100MBIT BIT(13)
+#define PHY_AUTO_NEG_ENABLE BIT(12)
+#define PHY_POWER_DOWN BIT(11)
+#define PHY_MII_DISABLE BIT(10)
+#define PHY_AUTO_NEG_RESTART BIT(9)
+#define PHY_FULL_DUPLEX BIT(8)
+#define PHY_COLLISION_TEST_NOT BIT(7)
+#define PHY_HP_MDIX BIT(5)
+#define PHY_FORCE_MDIX BIT(4)
+#define PHY_AUTO_MDIX_DISABLE BIT(3)
+#define PHY_REMOTE_FAULT_DISABLE BIT(2)
+#define PHY_TRANSMIT_DISABLE BIT(1)
+#define PHY_LED_DISABLE BIT(0)
+
+#define PHY_REG_STATUS 1
+
+#define PHY_100BT4_CAPABLE BIT(15)
+#define PHY_100BTX_FD_CAPABLE BIT(14)
+#define PHY_100BTX_CAPABLE BIT(13)
+#define PHY_10BT_FD_CAPABLE BIT(12)
+#define PHY_10BT_CAPABLE BIT(11)
+#define PHY_MII_SUPPRESS_CAPABLE_NOT BIT(6)
+#define PHY_AUTO_NEG_ACKNOWLEDGE BIT(5)
+#define PHY_REMOTE_FAULT BIT(4)
+#define PHY_AUTO_NEG_CAPABLE BIT(3)
+#define PHY_LINK_STATUS BIT(2)
+#define PHY_JABBER_DETECT_NOT BIT(1)
+#define PHY_EXTENDED_CAPABILITY BIT(0)
+
+#define PHY_REG_ID_1 2
+#define PHY_REG_ID_2 3
+
+#define PHY_REG_AUTO_NEGOTIATION 4
+
+#define PHY_AUTO_NEG_NEXT_PAGE_NOT BIT(15)
+#define PHY_AUTO_NEG_REMOTE_FAULT_NOT BIT(13)
+#define PHY_AUTO_NEG_SYM_PAUSE BIT(10)
+#define PHY_AUTO_NEG_100BT4 BIT(9)
+#define PHY_AUTO_NEG_100BTX_FD BIT(8)
+#define PHY_AUTO_NEG_100BTX BIT(7)
+#define PHY_AUTO_NEG_10BT_FD BIT(6)
+#define PHY_AUTO_NEG_10BT BIT(5)
+#define PHY_AUTO_NEG_SELECTOR 0x001F
+#define PHY_AUTO_NEG_802_3 0x0001
+
+#define PHY_REG_REMOTE_CAPABILITY 5
+
+#define PHY_REMOTE_NEXT_PAGE_NOT BIT(15)
+#define PHY_REMOTE_ACKNOWLEDGE_NOT BIT(14)
+#define PHY_REMOTE_REMOTE_FAULT_NOT BIT(13)
+#define PHY_REMOTE_SYM_PAUSE BIT(10)
+#define PHY_REMOTE_100BTX_FD BIT(8)
+#define PHY_REMOTE_100BTX BIT(7)
+#define PHY_REMOTE_10BT_FD BIT(6)
+#define PHY_REMOTE_10BT BIT(5)
+#endif
+
+#define KSZ8895_ID_HI 0x0022
+
+/* The PHY id 0x1450 is not recognized by the Micrel PHY drivers.
+ * Use 0x1550 from KSZ8795, which is also recognized as KSZ8051 PHY.
+ */
+#define KSZ8895_ID_LO 0x1550
+
+#define PHY_REG_LINK_MD 0x1D
+
+#define PHY_START_CABLE_DIAG BIT(15)
+#define PHY_CABLE_DIAG_RESULT 0x6000
+#define PHY_CABLE_STAT_NORMAL 0x0000
+#define PHY_CABLE_STAT_OPEN 0x2000
+#define PHY_CABLE_STAT_SHORT 0x4000
+#define PHY_CABLE_STAT_FAILED 0x6000
+#define PHY_CABLE_10M_SHORT BIT(12)
+#define PHY_CABLE_FAULT_COUNTER 0x01FF
+
+#define PHY_REG_PHY_CTRL 0x1F
+
+#define PHY_MODE_M 0x7
+#define PHY_MODE_S 8
+#define PHY_STAT_REVERSED_POLARITY BIT(5)
+#define PHY_STAT_MDIX BIT(4)
+#define PHY_FORCE_LINK BIT(3)
+#define PHY_POWER_SAVING_ENABLE BIT(2)
+#define PHY_REMOTE_LOOPBACK BIT(1)
+
+/* Chip resource */
+
+#define PRIO_QUEUES 4
+
+#define KS_PRIO_IN_REG 4
+
+#define TOTAL_PORT_NUM 5
+
+/* Host port can only be last of them. */
+#define SWITCH_PORT_NUM (TOTAL_PORT_NUM - 1)
+
+#define KSZ8895_COUNTER_NUM 0x20
+#define TOTAL_KSZ8895_COUNTER_NUM (KSZ8895_COUNTER_NUM + 2)
+
+#define SWITCH_COUNTER_NUM KSZ8895_COUNTER_NUM
+#define TOTAL_SWITCH_COUNTER_NUM TOTAL_KSZ8895_COUNTER_NUM
+
+/* Common names used by other drivers */
+
+#define P_BCAST_STORM_CTRL REG_PORT_CTRL_0
+#define P_PRIO_CTRL REG_PORT_CTRL_0
+#define P_TAG_CTRL REG_PORT_CTRL_0
+#define P_MIRROR_CTRL REG_PORT_CTRL_1
+#define P_802_1P_CTRL REG_PORT_CTRL_2
+#define P_STP_CTRL REG_PORT_CTRL_2
+#define P_LOCAL_CTRL REG_PORT_CTRL_5
+#define P_REMOTE_STATUS REG_PORT_STATUS_1
+#define P_FORCE_CTRL REG_PORT_CTRL_5
+#define P_NEG_RESTART_CTRL REG_PORT_CTRL_6
+#define P_SPEED_STATUS REG_PORT_STATUS_0
+#define P_LINK_STATUS REG_PORT_STATUS_1
+#define P_INS_SRC_PVID_CTRL REG_PORT_CTRL_8
+#define P_DROP_TAG_CTRL REG_PORT_CTRL_9
+#define P_RATE_LIMIT_CTRL REG_PORT_RATE_LIMIT
+
+#define S_FLUSH_TABLE_CTRL REG_SW_CTRL_0
+#define S_LINK_AGING_CTRL REG_SW_CTRL_0
+#define S_HUGE_PACKET_CTRL REG_SW_CTRL_2
+#define S_MIRROR_CTRL REG_SW_CTRL_3
+#define S_REPLACE_VID_CTRL REG_SW_CTRL_4
+#define S_PASS_PAUSE_CTRL REG_SW_CTRL_10
+#define S_TAIL_TAG_CTRL REG_SW_CTRL_10
+#define S_802_1P_PRIO_CTRL REG_SW_CTRL_12
+#define S_TOS_PRIO_CTRL REG_TOS_PRIO_CTRL_0
+#define S_IPV6_MLD_CTRL REG_SW_CTRL_21
+
+#define IND_ACC_TABLE(table) ((table) << 8)
+
+/* Driver set switch broadcast storm protection at 10% rate. */
+#define BROADCAST_STORM_PROT_RATE 10
+
+/* 148,800 frames * 67 ms / 100 */
+#define BROADCAST_STORM_VALUE 9969
+
+/**
+ * STATIC_MAC_TABLE_ADDR 00-0000FFFF-FFFFFFFF
+ * STATIC_MAC_TABLE_FWD_PORTS 00-001F0000-00000000
+ * STATIC_MAC_TABLE_VALID 00-00200000-00000000
+ * STATIC_MAC_TABLE_OVERRIDE 00-00400000-00000000
+ * STATIC_MAC_TABLE_USE_FID 00-00800000-00000000
+ * STATIC_MAC_TABLE_FID 00-7F000000-00000000
+ */
+
+#define STATIC_MAC_TABLE_ADDR 0x0000FFFF
+#define STATIC_MAC_TABLE_FWD_PORTS 0x001F0000
+#define STATIC_MAC_TABLE_VALID 0x00200000
+#define STATIC_MAC_TABLE_OVERRIDE 0x00400000
+#define STATIC_MAC_TABLE_USE_FID 0x00800000
+#define STATIC_MAC_TABLE_FID 0x7F000000
+
+#define STATIC_MAC_FWD_PORTS_S 16
+#define STATIC_MAC_FID_S 24
+
+/**
+ * VLAN_TABLE_FID 00-00003F81-FC0FE07F
+ * VLAN_TABLE_MEMBERSHIP 00-0007C03E-01F00F80
+ * VLAN_TABLE_VALID 00-00080040-02001000
+ */
+
+#define VLAN_TABLE_FID 0x007F
+#define VLAN_TABLE_MEMBERSHIP 0x0F80
+#define VLAN_TABLE_VALID 0x1000
+
+#define VLAN_TABLE_MEMBERSHIP_S 7
+#define VLAN_TABLE_S 13
+#define VLAN_TABLE_M (BIT(VLAN_TABLE_S) - 1)
+
+/**
+ * DYNAMIC_MAC_TABLE_ADDR 00-0000FFFF-FFFFFFFF
+ * DYNAMIC_MAC_TABLE_FID 00-007F0000-00000000
+ * DYNAMIC_MAC_TABLE_NOT_READY 00-00800000-00000000
+ * DYNAMIC_MAC_TABLE_SRC_PORT 00-07000000-00000000
+ * DYNAMIC_MAC_TABLE_TIMESTAMP 00-18000000-00000000
+ * DYNAMIC_MAC_TABLE_ENTRIES 7F-E0000000-00000000
+ * DYNAMIC_MAC_TABLE_MAC_EMPTY 80-00000000-00000000
+ */
+
+#define DYNAMIC_MAC_TABLE_ADDR 0x0000FFFF
+#define DYNAMIC_MAC_TABLE_FID 0x007F0000
+#define DYNAMIC_MAC_TABLE_SRC_PORT 0x07000000
+#define DYNAMIC_MAC_TABLE_TIMESTAMP 0x18000000
+#define DYNAMIC_MAC_TABLE_ENTRIES 0xE0000000
+
+#define DYNAMIC_MAC_TABLE_NOT_READY 0x80
+
+#define DYNAMIC_MAC_TABLE_ENTRIES_H 0x7F
+#define DYNAMIC_MAC_TABLE_MAC_EMPTY 0x80
+
+#define DYNAMIC_MAC_FID_S 16
+#define DYNAMIC_MAC_SRC_PORT_S 24
+#define DYNAMIC_MAC_TIMESTAMP_S 27
+#define DYNAMIC_MAC_ENTRIES_S 29
+#define DYNAMIC_MAC_ENTRIES_H_S 3
+
+/**
+ * MIB_COUNTER_VALUE 00-00000000-3FFFFFFF
+ * MIB_PACKET_DROPPED 00-00000000-0000FFFF
+ * MIB_COUNTER_VALID 00-00000000-40000000
+ * MIB_COUNTER_OVERFLOW 00-00000000-80000000
+ */
+
+#define MIB_COUNTER_OVERFLOW BIT(7)
+#define MIB_COUNTER_VALID BIT(6)
+
+#define MIB_COUNTER_VALUE 0x3FFFFFFF
+
+#define KS_MIB_PACKET_DROPPED_TX_0 0x100
+#define KS_MIB_PACKET_DROPPED_TX_1 0x101
+#define KS_MIB_PACKET_DROPPED_TX_2 0x102
+#define KS_MIB_PACKET_DROPPED_TX_3 0x103
+#define KS_MIB_PACKET_DROPPED_TX_4 0x104
+#define KS_MIB_PACKET_DROPPED_RX_0 0x105
+#define KS_MIB_PACKET_DROPPED_RX_1 0x106
+#define KS_MIB_PACKET_DROPPED_RX_2 0x107
+#define KS_MIB_PACKET_DROPPED_RX_3 0x108
+#define KS_MIB_PACKET_DROPPED_RX_4 0x109
+
+#define MIB_PACKET_DROPPED 0x0000FFFF
+
+#define TAIL_TAG_OVERRIDE BIT(6)
+#define TAIL_TAG_LOOKUP BIT(7)
+
+#define VLAN_TABLE_ENTRIES (4096 / 4)
+#define FID_ENTRIES 128
+
+#endif
diff --git a/drivers/net/dsa/microchip/ksz8895_spi.c b/drivers/net/dsa/microchip/ksz8895_spi.c
new file mode 100644
index 0000000..b9564e7
--- /dev/null
+++ b/drivers/net/dsa/microchip/ksz8895_spi.c
@@ -0,0 +1,157 @@
+/*
+ * Microchip KSZ8895 series register access through SPI
+ *
+ * Copyright (C) 2017 Microchip Technology Inc.
+ * Tristram Ha <Tristram.Ha@microchip.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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <asm/unaligned.h>
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+
+#include "ksz_priv.h"
+#include "ksz_spi.h"
+
+/* SPI frame opcodes */
+#define KS_SPIOP_RD 3
+#define KS_SPIOP_WR 2
+
+/* Enough to read all switch registers. */
+#define SPI_TX_BUF_LEN 0x100
+
+static int ksz8895_spi_read_reg(struct spi_device *spi, u8 reg, u8 *val,
+ unsigned int len)
+{
+ u8 txbuf[2];
+ int ret;
+
+ txbuf[0] = KS_SPIOP_RD;
+ txbuf[1] = (u8)reg;
+
+ ret = spi_write_then_read(spi, txbuf, 2, val, len);
+ return ret;
+}
+
+static int ksz8895_spi_write_reg(struct spi_device *spi, u32 reg, u8 *val,
+ unsigned int len)
+{
+ u8 *txbuf = (u8 *)val;
+
+ txbuf[0] = KS_SPIOP_WR;
+ txbuf[1] = (u8)reg;
+
+ return spi_write(spi, txbuf, 2 + len);
+}
+
+static int ksz_spi_read(struct ksz_device *dev, u32 reg, u8 *data,
+ unsigned int len)
+{
+ struct spi_device *spi = dev->priv;
+
+ return ksz8895_spi_read_reg(spi, reg, data, len);
+}
+
+static int ksz_spi_write(struct ksz_device *dev, u32 reg, void *data,
+ unsigned int len)
+{
+ struct spi_device *spi = dev->priv;
+
+ if (len > SPI_TX_BUF_LEN)
+ len = SPI_TX_BUF_LEN;
+ memcpy(&dev->txbuf[2], data, len);
+ return ksz8895_spi_write_reg(spi, reg, dev->txbuf, len);
+}
+
+static const struct ksz_io_ops ksz8895_spi_ops = {
+ .read8 = ksz_spi_read8,
+ .read16 = ksz_spi_read16,
+ .read32 = ksz_spi_read32,
+ .write8 = ksz_spi_write8,
+ .write16 = ksz_spi_write16,
+ .write32 = ksz_spi_write32,
+ .get = ksz_spi_get,
+ .set = ksz_spi_set,
+};
+
+static int ksz8895_spi_probe(struct spi_device *spi)
+{
+ struct ksz_device *dev;
+ int ret;
+
+ dev = ksz_switch_alloc(&spi->dev, &ksz8895_spi_ops, spi);
+ if (!dev)
+ return -ENOMEM;
+
+ if (spi->dev.platform_data)
+ dev->pdata = spi->dev.platform_data;
+
+ dev->txbuf = devm_kzalloc(dev->dev, 2 + SPI_TX_BUF_LEN, GFP_KERNEL);
+
+ ret = ksz8895_switch_register(dev);
+
+ /* Main DSA driver may not be started yet. */
+ if (ret)
+ return ret;
+
+ spi_set_drvdata(spi, dev);
+
+ return 0;
+}
+
+static int ksz8895_spi_remove(struct spi_device *spi)
+{
+ struct ksz_device *dev = spi_get_drvdata(spi);
+
+ if (dev)
+ ksz_switch_remove(dev);
+
+ return 0;
+}
+
+static void ksz8895_spi_shutdown(struct spi_device *spi)
+{
+ struct ksz_device *dev = spi_get_drvdata(spi);
+
+ if (dev && dev->dev_ops->shutdown)
+ dev->dev_ops->shutdown(dev);
+}
+
+static const struct of_device_id ksz8895_dt_ids[] = {
+ { .compatible = "microchip,ksz8895" },
+ { .compatible = "microchip,ksz8864" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ksz8895_dt_ids);
+
+static struct spi_driver ksz8895_spi_driver = {
+ .driver = {
+ .name = "ksz8895-switch",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(ksz8895_dt_ids),
+ },
+ .probe = ksz8895_spi_probe,
+ .remove = ksz8895_spi_remove,
+ .shutdown = ksz8895_spi_shutdown,
+};
+
+module_spi_driver(ksz8895_spi_driver);
+
+MODULE_AUTHOR("Tristram Ha <Tristram.Ha@microchip.com>");
+MODULE_DESCRIPTION("Microchip KSZ8895 Series Switch SPI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/dsa/microchip/ksz_priv.h b/drivers/net/dsa/microchip/ksz_priv.h
index 6cc92ea..6593cb0 100644
--- a/drivers/net/dsa/microchip/ksz_priv.h
+++ b/drivers/net/dsa/microchip/ksz_priv.h
@@ -178,6 +178,7 @@ int ksz_switch_register(struct ksz_device *dev,
void ksz_switch_remove(struct ksz_device *dev);
int ksz8795_switch_register(struct ksz_device *dev);
+int ksz8895_switch_register(struct ksz_device *dev);
int ksz9477_switch_register(struct ksz_device *dev);
#endif
--
1.9.1
^ permalink raw reply related
* Re: [PATCH net-next 4/4] selinux: bpf: Add addtional check for bpf object file receive
From: Chenbo Feng @ 2017-10-06 21:00 UTC (permalink / raw)
To: Stephen Smalley
Cc: Chenbo Feng, netdev, SELinux, linux-security-module,
Alexei Starovoitov, Daniel Borkmann, Lorenzo Colitti
In-Reply-To: <1507210621.27146.7.camel@tycho.nsa.gov>
On Thu, Oct 5, 2017 at 6:37 AM, Stephen Smalley <sds@tycho.nsa.gov> wrote:
> On Wed, 2017-10-04 at 11:29 -0700, Chenbo Feng wrote:
>> From: Chenbo Feng <fengc@google.com>
>>
>> Introduce a bpf object related check when sending and receiving files
>> through unix domain socket as well as binder. It checks if the
>> receiving
>> process have privilege to read/write the bpf map or use the bpf
>> program.
>> This check is necessary because the bpf maps and programs are using a
>> anonymous inode as their shared inode so the normal way of checking
>> the
>> files and sockets when passing between processes cannot work properly
>> on
>> eBPF object. This check only works when the BPF_SYSCALL is
>> configured.
>>
>> Signed-off-by: Chenbo Feng <fengc@google.com>
>> ---
>> include/linux/bpf.h | 3 +++
>> kernel/bpf/syscall.c | 4 ++--
>> security/selinux/hooks.c | 57
>> +++++++++++++++++++++++++++++++++++++++++++++++-
>> 3 files changed, 61 insertions(+), 3 deletions(-)
>>
>> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
>> index d757ea3f2228..ac8428a36d56 100644
>> --- a/include/linux/bpf.h
>> +++ b/include/linux/bpf.h
>> @@ -250,6 +250,9 @@ int bpf_prog_test_run_skb(struct bpf_prog *prog,
>> const union bpf_attr *kattr,
>> #ifdef CONFIG_BPF_SYSCALL
>> DECLARE_PER_CPU(int, bpf_prog_active);
>>
>> +extern const struct file_operations bpf_map_fops;
>> +extern const struct file_operations bpf_prog_fops;
>> +
>> #define BPF_PROG_TYPE(_id, _ops) \
>> extern const struct bpf_verifier_ops _ops;
>> #define BPF_MAP_TYPE(_id, _ops) \
>> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
>> index 58ff769d58ab..5789a5359f0a 100644
>> --- a/kernel/bpf/syscall.c
>> +++ b/kernel/bpf/syscall.c
>> @@ -313,7 +313,7 @@ static ssize_t bpf_dummy_write(struct file *filp,
>> const char __user *buf,
>> return -EINVAL;
>> }
>>
>> -static const struct file_operations bpf_map_fops = {
>> +const struct file_operations bpf_map_fops = {
>> #ifdef CONFIG_PROC_FS
>> .show_fdinfo = bpf_map_show_fdinfo,
>> #endif
>> @@ -965,7 +965,7 @@ static void bpf_prog_show_fdinfo(struct seq_file
>> *m, struct file *filp)
>> }
>> #endif
>>
>> -static const struct file_operations bpf_prog_fops = {
>> +const struct file_operations bpf_prog_fops = {
>> #ifdef CONFIG_PROC_FS
>> .show_fdinfo = bpf_prog_show_fdinfo,
>> #endif
>> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
>> index 41aba4e3d57c..381474ce3216 100644
>> --- a/security/selinux/hooks.c
>> +++ b/security/selinux/hooks.c
>> @@ -1847,6 +1847,7 @@ static int file_has_perm(const struct cred
>> *cred,
>>
>> /* av is zero if only checking access to the descriptor. */
>> rc = 0;
>> +
>> if (av)
>> rc = inode_has_perm(cred, inode, av, &ad);
>>
>> @@ -2142,6 +2143,10 @@ static int
>> selinux_binder_transfer_binder(struct task_struct *from,
>> NULL);
>> }
>>
>> +#ifdef CONFIG_BPF_SYSCALL
>> +static int bpf_fd_pass(struct file *file, u32 sid);
>> +#endif
>> +
>> static int selinux_binder_transfer_file(struct task_struct *from,
>> struct task_struct *to,
>> struct file *file)
>> @@ -2165,6 +2170,12 @@ static int selinux_binder_transfer_file(struct
>> task_struct *from,
>> return rc;
>> }
>>
>> +#ifdef CONFIG_BPF_SYSCALL
>> + rc = bpf_fd_pass(file, sid);
>> + if (rc)
>> + return rc;
>> +#endif
>> +
>> if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
>> return 0;
>>
>> @@ -3735,8 +3746,18 @@ static int selinux_file_send_sigiotask(struct
>> task_struct *tsk,
>> static int selinux_file_receive(struct file *file)
>> {
>> const struct cred *cred = current_cred();
>> + int rc;
>> +
>> + rc = file_has_perm(cred, file, file_to_av(file));
>> + if (rc)
>> + goto out;
>> +
>> +#ifdef CONFIG_BPF_SYSCALL
>> + rc = bpf_fd_pass(file, cred_sid(sid));
>> +#endif
>>
>> - return file_has_perm(cred, file, file_to_av(file));
>> +out:
>> + return rc;
>> }
>>
>> static int selinux_file_open(struct file *file, const struct cred
>> *cred)
>> @@ -6288,6 +6309,40 @@ static u32 bpf_map_fmode_to_av(fmode_t fmode)
>> return av;
>> }
>>
>> +/* This function will check the file pass through unix socket or
>> binder to see
>> + * if it is a bpf related object. And apply correspinding checks on
>> the bpf
>> + * object based on the type. The bpf maps and programs, not like
>> other files and
>> + * socket, are using a shared anonymous inode inside the kernel as
>> their inode.
>> + * So checking that inode cannot identify if the process have
>> privilege to
>> + * access the bpf object and that's why we have to add this
>> additional check in
>> + * selinux_file_receive and selinux_binder_transfer_files.
>> + */
>> +static int bpf_fd_pass(struct file *file, u32 sid)
>> +{
>> + struct bpf_security_struct *bpfsec;
>> + u32 sid = cred_sid(cred);
>> + struct bpf_prog *prog;
>> + struct bpf_map *map;
>> + int ret;
>> +
>> + if (file->f_op == &bpf_map_fops) {
>> + map = file->private_data;
>> + bpfsec = map->security;
>> + ret = avc_has_perm(sid, bpfsec->sid,
>> SECCLASS_BPF_MAP,
>> + bpf_map_fmode_to_av(file-
>> >f_mode), NULL);
>> + if (ret)
>> + return ret;
>> + } else if (file->f_op == &bpf_prog_fops) {
>> + prog = file->private_data;
>> + bpfsec = prog->aux->security;
>> + ret = avc_has_perm(sid, bpfsec->sid,
>> SECCLASS_BPF_PROG,
>> + BPF_PROG__USE, NULL);
>> + if (ret)
>> + return ret;
>> + }
>> + return 0;
>> +}
>
> When the struct file is allocated for the bpf map and/or prog, you
> could call a hook at that time passing both, and note the fact that it
> is a bpf map/prog in the file_security_struct. Then, on
> file_receive/binder_transfer_file, you could apply the appropriate
> checking. Further, if we know that the file is always allocated at the
> same point as the bpf map/prog, then they should have the same SID (i.e
> fsec->sid should be the same as bpfsec->sid), so we shouldn't even need
> to dereference the bpf map/prog. Unless I'm missing something.
>
Thanks for the feedback, but I am a little confused about the proposed
implementation. Do we need to add an additional field inside
file_security_struct to identify a file as bpf map/prog? Or we just
copy the sid stored inside bpf map/prog security field into
file_security_struct when we allocate the file and do checks like
following:
if (file->f_op == &bpf_map_fops) {
ret = avc_has_perm(sid, fsec->sid, SECCLASS_BPF,
bpf_map_fmode_to_av(file->f_mode), NULL);
} else if (file->f_op == &bpf_prog_fops) {
ret = avc_has_perm(sid, fsec->sid, SECCLASS_BPF,
BPF_PROG__USE, NULL);
}
> Also, are we concerned about doing the same in
> flush_unauthorized_files(), for inheriting descriptors across a
> context-changing execve? Should this checking actually go into
> file_has_perm() itself so it is always applied on any use of the struct
> file?
>
I agree moving this into file_has_perm might be a better solution.
> Lastly, do we need/want these checks if sid == bpfsec->sid? We skip
> FD__USE in the case where sid == fsec->sid, for example.
>
In this case we still have to check if the process have the right
privilege to access the map. The creator of the bpf map/prog doesn't
necessarily have all privileges to the object.
>> +
>> static int selinux_bpf_map(struct bpf_map *map, fmode_t fmode)
>> {
>> u32 sid = current_sid();
^ permalink raw reply
* Re: [PATCH net-next 4/4] selinux: bpf: Add addtional check for bpf object file receive
From: Chenbo Feng @ 2017-10-06 21:10 UTC (permalink / raw)
To: Stephen Smalley
Cc: Chenbo Feng, netdev, SELinux, linux-security-module,
Alexei Starovoitov, Daniel Borkmann, Lorenzo Colitti
In-Reply-To: <1507228011.27146.20.camel@tycho.nsa.gov>
On Thu, Oct 5, 2017 at 11:26 AM, Stephen Smalley <sds@tycho.nsa.gov> wrote:
> On Thu, 2017-10-05 at 09:37 -0400, Stephen Smalley wrote:
>> On Wed, 2017-10-04 at 11:29 -0700, Chenbo Feng wrote:
>> > From: Chenbo Feng <fengc@google.com>
>> >
>> > Introduce a bpf object related check when sending and receiving
>> > files
>> > through unix domain socket as well as binder. It checks if the
>> > receiving
>> > process have privilege to read/write the bpf map or use the bpf
>> > program.
>> > This check is necessary because the bpf maps and programs are using
>> > a
>> > anonymous inode as their shared inode so the normal way of checking
>> > the
>> > files and sockets when passing between processes cannot work
>> > properly
>> > on
>> > eBPF object. This check only works when the BPF_SYSCALL is
>> > configured.
>> >
>> > Signed-off-by: Chenbo Feng <fengc@google.com>
>> > ---
>> > include/linux/bpf.h | 3 +++
>> > kernel/bpf/syscall.c | 4 ++--
>> > security/selinux/hooks.c | 57
>> > +++++++++++++++++++++++++++++++++++++++++++++++-
>> > 3 files changed, 61 insertions(+), 3 deletions(-)
>> >
>> > diff --git a/include/linux/bpf.h b/include/linux/bpf.h
>> > index d757ea3f2228..ac8428a36d56 100644
>> > --- a/include/linux/bpf.h
>> > +++ b/include/linux/bpf.h
>> > @@ -250,6 +250,9 @@ int bpf_prog_test_run_skb(struct bpf_prog
>> > *prog,
>> > const union bpf_attr *kattr,
>> > #ifdef CONFIG_BPF_SYSCALL
>> > DECLARE_PER_CPU(int, bpf_prog_active);
>> >
>> > +extern const struct file_operations bpf_map_fops;
>> > +extern const struct file_operations bpf_prog_fops;
>> > +
>> > #define BPF_PROG_TYPE(_id, _ops) \
>> > extern const struct bpf_verifier_ops _ops;
>> > #define BPF_MAP_TYPE(_id, _ops) \
>> > diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
>> > index 58ff769d58ab..5789a5359f0a 100644
>> > --- a/kernel/bpf/syscall.c
>> > +++ b/kernel/bpf/syscall.c
>> > @@ -313,7 +313,7 @@ static ssize_t bpf_dummy_write(struct file
>> > *filp,
>> > const char __user *buf,
>> > return -EINVAL;
>> > }
>> >
>> > -static const struct file_operations bpf_map_fops = {
>> > +const struct file_operations bpf_map_fops = {
>> > #ifdef CONFIG_PROC_FS
>> > .show_fdinfo = bpf_map_show_fdinfo,
>> > #endif
>> > @@ -965,7 +965,7 @@ static void bpf_prog_show_fdinfo(struct
>> > seq_file
>> > *m, struct file *filp)
>> > }
>> > #endif
>> >
>> > -static const struct file_operations bpf_prog_fops = {
>> > +const struct file_operations bpf_prog_fops = {
>> > #ifdef CONFIG_PROC_FS
>> > .show_fdinfo = bpf_prog_show_fdinfo,
>> > #endif
>> > diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
>> > index 41aba4e3d57c..381474ce3216 100644
>> > --- a/security/selinux/hooks.c
>> > +++ b/security/selinux/hooks.c
>> > @@ -1847,6 +1847,7 @@ static int file_has_perm(const struct cred
>> > *cred,
>> >
>> > /* av is zero if only checking access to the descriptor.
>> > */
>> > rc = 0;
>> > +
>> > if (av)
>> > rc = inode_has_perm(cred, inode, av, &ad);
>> >
>> > @@ -2142,6 +2143,10 @@ static int
>> > selinux_binder_transfer_binder(struct task_struct *from,
>> > NULL);
>> > }
>> >
>> > +#ifdef CONFIG_BPF_SYSCALL
>> > +static int bpf_fd_pass(struct file *file, u32 sid);
>> > +#endif
>> > +
>> > static int selinux_binder_transfer_file(struct task_struct *from,
>> > struct task_struct *to,
>> > struct file *file)
>> > @@ -2165,6 +2170,12 @@ static int
>> > selinux_binder_transfer_file(struct
>> > task_struct *from,
>> > return rc;
>> > }
>> >
>> > +#ifdef CONFIG_BPF_SYSCALL
>> > + rc = bpf_fd_pass(file, sid);
>> > + if (rc)
>> > + return rc;
>> > +#endif
>> > +
>> > if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
>> > return 0;
>> >
>> > @@ -3735,8 +3746,18 @@ static int
>> > selinux_file_send_sigiotask(struct
>> > task_struct *tsk,
>> > static int selinux_file_receive(struct file *file)
>> > {
>> > const struct cred *cred = current_cred();
>> > + int rc;
>> > +
>> > + rc = file_has_perm(cred, file, file_to_av(file));
>> > + if (rc)
>> > + goto out;
>> > +
>> > +#ifdef CONFIG_BPF_SYSCALL
>> > + rc = bpf_fd_pass(file, cred_sid(sid));
>> > +#endif
>> >
>> > - return file_has_perm(cred, file, file_to_av(file));
>> > +out:
>> > + return rc;
>> > }
>> >
>> > static int selinux_file_open(struct file *file, const struct cred
>> > *cred)
>> > @@ -6288,6 +6309,40 @@ static u32 bpf_map_fmode_to_av(fmode_t
>> > fmode)
>> > return av;
>> > }
>> >
>> > +/* This function will check the file pass through unix socket or
>> > binder to see
>> > + * if it is a bpf related object. And apply correspinding checks
>> > on
>> > the bpf
>> > + * object based on the type. The bpf maps and programs, not like
>> > other files and
>> > + * socket, are using a shared anonymous inode inside the kernel as
>> > their inode.
>> > + * So checking that inode cannot identify if the process have
>> > privilege to
>> > + * access the bpf object and that's why we have to add this
>> > additional check in
>> > + * selinux_file_receive and selinux_binder_transfer_files.
>> > + */
>> > +static int bpf_fd_pass(struct file *file, u32 sid)
>> > +{
>> > + struct bpf_security_struct *bpfsec;
>> > + u32 sid = cred_sid(cred);
>> > + struct bpf_prog *prog;
>> > + struct bpf_map *map;
>> > + int ret;
>> > +
>> > + if (file->f_op == &bpf_map_fops) {
>> > + map = file->private_data;
>> > + bpfsec = map->security;
>> > + ret = avc_has_perm(sid, bpfsec->sid,
>> > SECCLASS_BPF_MAP,
>> > + bpf_map_fmode_to_av(file-
>> > > f_mode), NULL);
>> >
>> > + if (ret)
>> > + return ret;
>> > + } else if (file->f_op == &bpf_prog_fops) {
>> > + prog = file->private_data;
>> > + bpfsec = prog->aux->security;
>> > + ret = avc_has_perm(sid, bpfsec->sid,
>> > SECCLASS_BPF_PROG,
>> > + BPF_PROG__USE, NULL);
>> > + if (ret)
>> > + return ret;
>> > + }
>> > + return 0;
>> > +}
>>
>> When the struct file is allocated for the bpf map and/or prog, you
>> could call a hook at that time passing both, and note the fact that
>> it
>> is a bpf map/prog in the file_security_struct. Then, on
>> file_receive/binder_transfer_file, you could apply the appropriate
>> checking. Further, if we know that the file is always allocated at
>> the
>> same point as the bpf map/prog, then they should have the same SID
>> (i.e
>> fsec->sid should be the same as bpfsec->sid), so we shouldn't even
>> need
>> to dereference the bpf map/prog. Unless I'm missing something.
>>
>> Also, are we concerned about doing the same in
>> flush_unauthorized_files(), for inheriting descriptors across a
>> context-changing execve? Should this checking actually go into
>> file_has_perm() itself so it is always applied on any use of the
>> struct
>> file?
>>
>> Lastly, do we need/want these checks if sid == bpfsec->sid? We skip
>> FD__USE in the case where sid == fsec->sid, for example.
>
> BTW, the prog use check seems slightly redundant in that we will
> already check fd use permission. So we only really need it if you want
> to allow fd use but deny prog use. The map read/write checks are more
> granular than fd use, so I guess we can't get rid of those.
>
But it seems fd use doesn't check what object is behind that fd. So if
we don't want a process to run the eBPF program, then we still need a
additional check for it. Maybe use prog_run instead of prog_use should
be more appropriate.
>>
>> > +
>> > static int selinux_bpf_map(struct bpf_map *map, fmode_t fmode)
>> > {
>> > u32 sid = current_sid();
^ permalink raw reply
* Re: [PATCH net-next] ip_gre: check packet length and mtu correctly in erspan_fb_xmit
From: William Tu @ 2017-10-06 22:09 UTC (permalink / raw)
To: David Laight; +Cc: netdev@vger.kernel.org, Xin Long
In-Reply-To: <063D6719AE5E284EB5DD2968C1650D6DD008B9C8@AcuExch.aculab.com>
On Fri, Oct 6, 2017 at 2:38 AM, David Laight <David.Laight@aculab.com> wrote:
> From: William Tu
>> Sent: 05 October 2017 22:21
> ...
>> >> - if (skb->len > dev->mtu) {
>> >> + if (skb->len - dev->hard_header_len > dev->mtu) {
>> >
>> > Can you guarantee that skb->len > dev_hard_header_len?
>> > It is probably safer to check skb->len > dev->hard_header_len + dev->mtu
>> > since that addition isn't going to overflow.
>> Sure, I will fix it.
>>
>> >
>> >> pskb_trim(skb, dev->mtu);
>> >> truncate = true;
>> >
>> > Is that pskb_trim() now truncating to the correct size?
>>
>> You're right, now I should truncate to (dev->mtu + dev_hard_header_len)
>
> It might be worth caching that length in the dev structure
> to avoid the arithmetic on every packet.
>
> David
>
Thanks for the advise.
Yes, adding another field in the struct net_device can avoid the
arithmetic operation. I'm not sure it's a good idea to add new field
since there are already a lot in net_device. Let's wait to see how
others think.
William
^ permalink raw reply
* Re: [PATCH net-next] ip_gre: check packet length and mtu correctly in erspan_fb_xmit
From: David Miller @ 2017-10-06 22:22 UTC (permalink / raw)
To: u9012063; +Cc: David.Laight, netdev, lucien.xin
In-Reply-To: <CALDO+SYDGtipnsXOmcFmCRB2QcgjjzoWVB=T71=r7U834ivPjg@mail.gmail.com>
From: William Tu <u9012063@gmail.com>
Date: Fri, 6 Oct 2017 15:09:29 -0700
> Yes, adding another field in the struct net_device can avoid the
> arithmetic operation. I'm not sure it's a good idea to add new field
> since there are already a lot in net_device. Let's wait to see how
> others think.
Why would you add it to net_device when you can add it to ip_tunnel?
^ permalink raw reply
* [PATCH RFC 0/3] tun zerocopy stats
From: Willem de Bruijn @ 2017-10-06 22:25 UTC (permalink / raw)
To: netdev; +Cc: mst, jasowang, Willem de Bruijn
From: Willem de Bruijn <willemb@google.com>
Add zerocopy transfer statistics to the vhost_net/tun zerocopy path.
I've been using this to verify recent changes to zerocopy tuning [1].
Sharing more widely, as it may be useful in similar future work.
Use ethtool stats as interface, as these are defined per device
driver and can easily be extended.
Make the zerocopy release callback take an extra hop through the tun
driver to allow the driver to increment its counters.
Care must be taken to avoid adding an alloc/free to this hot path.
Since the caller already must allocate a ubuf_info, make it allocate
two at a time and grant one to the tun device.
1/3: introduce ethtool stats (`ethtool -S $DEV`) for tun devices
2/3: add zerocopy tx and tx_err counters
3/3: convert vhost_net to pass a pair of ubuf_info to tun
[1] http://patchwork.ozlabs.org/patch/822613/
Willem de Bruijn (3):
tun: ethtool stats
tun: expand ethtool stats with zerocopy
vhost_net: support tun zerocopy stats
drivers/net/tun.c | 111 ++++++++++++++++++++++++++++++++++++++++++++++++----
drivers/vhost/net.c | 7 ++--
2 files changed, 108 insertions(+), 10 deletions(-)
--
2.14.2.920.gcf0c67979c-goog
^ permalink raw reply
* [PATCH RFC 1/3] tun: ethtool stats
From: Willem de Bruijn @ 2017-10-06 22:25 UTC (permalink / raw)
To: netdev; +Cc: mst, jasowang, Willem de Bruijn
In-Reply-To: <20171006222516.90654-1-willemdebruijn.kernel@gmail.com>
From: Willem de Bruijn <willemb@google.com>
Support ethtool -S on tun devices. This interface allows exporting
device-specific stats not present in rtnl stats.
Signed-off-by: Willem de Bruijn <willemb@google.com>
---
drivers/net/tun.c | 38 ++++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index 57e4c31fa84a..df6ef9670d05 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -194,6 +194,15 @@ struct tun_flow_entry {
#define TUN_NUM_FLOW_ENTRIES 1024
+static const struct {
+ const char string[ETH_GSTRING_LEN];
+} tun_ethtool_stats_keys[] = {
+ { "rx_packets" },
+ { "tx_packets" },
+ { "rx_bytes" },
+ { "tx_bytes" },
+};
+
/* Since the socket were moved to tun_file, to preserve the behavior of persist
* device, socket filter, sndbuf and vnet header size were restore when the
* file were attached to a persist device.
@@ -2954,6 +2963,32 @@ static int tun_set_coalesce(struct net_device *dev,
return 0;
}
+static int tun_get_sset_count(struct net_device *dev, int string_set)
+{
+ if (string_set == ETH_SS_STATS)
+ return ARRAY_SIZE(tun_ethtool_stats_keys);
+
+ return -EINVAL;
+}
+
+static void tun_get_strings(struct net_device *dev, u32 stringset, u8 *data)
+{
+ if (stringset == ETH_SS_STATS)
+ memcpy(data, &tun_ethtool_stats_keys,
+ sizeof(tun_ethtool_stats_keys));
+}
+
+static void tun_get_ethtool_stats(struct net_device *dev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ const int ethtool_stats_bytelen =
+ ARRAY_SIZE(tun_ethtool_stats_keys) * sizeof(u64);
+ struct rtnl_link_stats64 link_stats64 = {0};
+
+ tun_net_get_stats64(dev, &link_stats64);
+ memcpy(data, &link_stats64, ethtool_stats_bytelen);
+}
+
static const struct ethtool_ops tun_ethtool_ops = {
.get_drvinfo = tun_get_drvinfo,
.get_msglevel = tun_get_msglevel,
@@ -2963,6 +2998,9 @@ static const struct ethtool_ops tun_ethtool_ops = {
.get_coalesce = tun_get_coalesce,
.set_coalesce = tun_set_coalesce,
.get_link_ksettings = tun_get_link_ksettings,
+ .get_sset_count = tun_get_sset_count,
+ .get_strings = tun_get_strings,
+ .get_ethtool_stats = tun_get_ethtool_stats,
};
static int tun_queue_resize(struct tun_struct *tun)
--
2.14.2.920.gcf0c67979c-goog
^ permalink raw reply related
* [PATCH RFC 2/3] tun: expand ethtool stats with zerocopy
From: Willem de Bruijn @ 2017-10-06 22:25 UTC (permalink / raw)
To: netdev; +Cc: mst, jasowang, Willem de Bruijn
In-Reply-To: <20171006222516.90654-1-willemdebruijn.kernel@gmail.com>
From: Willem de Bruijn <willemb@google.com>
Add two counters to tun ethool stats to count zerocopy completions.
- tx_zerocopy counts completions that succeeded without copy.
- tx_zerocopy_err counts those that triggered a copy.
Tun intercepts completions by replacing the zerocopy completion
handler (ubuf_info) prepared by the packet source (vhost-net)
with its own.
To avoid adding another ubuf_info alloc/free in the datapath, only
enter this mode if the packet source passed an array of two ubuf_info
to the tun device.
Pass msg_controllen to tun_get_user to detect this.
Signed-off-by: Willem de Bruijn <willemb@google.com>
---
drivers/net/tun.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 69 insertions(+), 10 deletions(-)
diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index df6ef9670d05..286787d8e875 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -149,6 +149,8 @@ struct tun_pcpu_stats {
u32 rx_dropped;
u32 tx_dropped;
u32 rx_frame_errors;
+ u64 tx_zerocopy;
+ u64 tx_zerocopy_err;
};
/* A tun_file connects an open character device to a tuntap netdevice. It
@@ -201,6 +203,8 @@ static const struct {
{ "tx_packets" },
{ "rx_bytes" },
{ "tx_bytes" },
+ { "tx_zerocopy" },
+ { "tx_zerocopy_err" },
};
/* Since the socket were moved to tun_file, to preserve the behavior of persist
@@ -1082,8 +1086,9 @@ static void tun_set_headroom(struct net_device *dev, int new_hr)
tun->align = new_hr;
}
-static void
-tun_net_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats)
+static void __tun_net_get_stats64(struct net_device *dev,
+ struct rtnl_link_stats64 *stats,
+ u64 *ethtool_stats)
{
u32 rx_dropped = 0, tx_dropped = 0, rx_frame_errors = 0;
struct tun_struct *tun = netdev_priv(dev);
@@ -1103,6 +1108,16 @@ tun_net_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats)
txbytes = p->tx_bytes;
} while (u64_stats_fetch_retry(&p->syncp, start));
+ if (ethtool_stats) {
+ ethtool_stats[0] += rxpackets;
+ ethtool_stats[1] += txpackets;
+ ethtool_stats[2] += rxbytes;
+ ethtool_stats[3] += txbytes;
+ ethtool_stats[4] += p->tx_zerocopy;
+ ethtool_stats[5] += p->tx_zerocopy_err;
+ continue;
+ }
+
stats->rx_packets += rxpackets;
stats->rx_bytes += rxbytes;
stats->tx_packets += txpackets;
@@ -1113,11 +1128,21 @@ tun_net_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats)
rx_frame_errors += p->rx_frame_errors;
tx_dropped += p->tx_dropped;
}
+
+ if (ethtool_stats)
+ return;
+
stats->rx_dropped = rx_dropped;
stats->rx_frame_errors = rx_frame_errors;
stats->tx_dropped = tx_dropped;
}
+static void tun_net_get_stats64(struct net_device *dev,
+ struct rtnl_link_stats64 *stats)
+{
+ __tun_net_get_stats64(dev, stats, NULL);
+}
+
static int tun_xdp_set(struct net_device *dev, struct bpf_prog *prog,
struct netlink_ext_ack *extack)
{
@@ -1537,10 +1562,44 @@ static struct sk_buff *tun_build_skb(struct tun_struct *tun,
return NULL;
}
+static void tun_zerocopy_callback(struct ubuf_info *ubuf, bool success)
+{
+ struct tun_struct *tun = ubuf->ctx;
+
+ if (success)
+ this_cpu_inc(tun->pcpu_stats->tx_zerocopy);
+ else
+ this_cpu_inc(tun->pcpu_stats->tx_zerocopy_err);
+
+ ubuf = ubuf - 1;
+ ubuf->callback(ubuf, success);
+}
+
+static void tun_set_zerocopy(struct tun_struct *tun, struct sk_buff *skb,
+ struct ubuf_info *ubuf, size_t len)
+{
+ const int ulen = sizeof(*ubuf);
+
+ if (len != ulen && len != ulen * 2) {
+ WARN_ON_ONCE(1);
+ return;
+ }
+
+ /* if caller passed two ubuf, one is for tun to interpose callback */
+ if (len == ulen * 2) {
+ ubuf = ubuf + 1;
+ ubuf->callback = tun_zerocopy_callback;
+ ubuf->ctx = tun;
+ refcount_set(&ubuf->refcnt, 1);
+ }
+
+ skb_shinfo(skb)->destructor_arg = ubuf;
+}
+
/* Get packet from user space buffer */
static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
- void *msg_control, struct iov_iter *from,
- int noblock, bool more)
+ void *msg_control, size_t msg_controllen,
+ struct iov_iter *from, int noblock, bool more)
{
struct tun_pi pi = { 0, cpu_to_be16(ETH_P_IP) };
struct sk_buff *skb;
@@ -1713,7 +1772,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
/* copy skb_ubuf_info for callback when skb has no error */
if (zerocopy) {
- skb_shinfo(skb)->destructor_arg = msg_control;
+ tun_set_zerocopy(tun, skb, (void *)msg_control, msg_controllen);
skb_shinfo(skb)->tx_flags |= SKBTX_DEV_ZEROCOPY;
skb_shinfo(skb)->tx_flags |= SKBTX_SHARED_FRAG;
} else if (msg_control) {
@@ -1798,7 +1857,7 @@ static ssize_t tun_chr_write_iter(struct kiocb *iocb, struct iov_iter *from)
if (!tun)
return -EBADFD;
- result = tun_get_user(tun, tfile, NULL, from,
+ result = tun_get_user(tun, tfile, NULL, 0, from,
file->f_flags & O_NONBLOCK, false);
tun_put(tun);
@@ -2058,7 +2117,8 @@ static int tun_sendmsg(struct socket *sock, struct msghdr *m, size_t total_len)
if (!tun)
return -EBADFD;
- ret = tun_get_user(tun, tfile, m->msg_control, &m->msg_iter,
+ ret = tun_get_user(tun, tfile, m->msg_control, m->msg_controllen,
+ &m->msg_iter,
m->msg_flags & MSG_DONTWAIT,
m->msg_flags & MSG_MORE);
tun_put(tun);
@@ -2983,10 +3043,9 @@ static void tun_get_ethtool_stats(struct net_device *dev,
{
const int ethtool_stats_bytelen =
ARRAY_SIZE(tun_ethtool_stats_keys) * sizeof(u64);
- struct rtnl_link_stats64 link_stats64 = {0};
- tun_net_get_stats64(dev, &link_stats64);
- memcpy(data, &link_stats64, ethtool_stats_bytelen);
+ memset(data, 0, ethtool_stats_bytelen);
+ __tun_net_get_stats64(dev, NULL, data);
}
static const struct ethtool_ops tun_ethtool_ops = {
--
2.14.2.920.gcf0c67979c-goog
^ permalink raw reply related
* [PATCH RFC 3/3] vhost_net: support tun zerocopy stats
From: Willem de Bruijn @ 2017-10-06 22:25 UTC (permalink / raw)
To: netdev; +Cc: mst, jasowang, Willem de Bruijn
In-Reply-To: <20171006222516.90654-1-willemdebruijn.kernel@gmail.com>
From: Willem de Bruijn <willemb@google.com>
Allow the tun device to intercept zerocopy completions to increment
its counters.
Pass an array of two struct ubuf_info to the device. The first is
initialized as usual and used to notify vhost_net. The second is
granted to the tun device for its callback.
Use the existing per-device ubuf_info pool, but take out two
elements at a time, so double the size.
Signed-off-by: Willem de Bruijn <willemb@google.com>
---
drivers/vhost/net.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c
index 58585ec8699e..22988a7df656 100644
--- a/drivers/vhost/net.c
+++ b/drivers/vhost/net.c
@@ -47,6 +47,7 @@ MODULE_PARM_DESC(experimental_zcopytx, "Enable Zero Copy TX;"
/* MAX number of TX used buffers for outstanding zerocopy */
#define VHOST_MAX_PEND 128
#define VHOST_GOODCOPY_LEN 256
+#define UBUFLEN 2 /* Number of consecutive ubuf_info passed to tun */
/*
* For transmit, used buffer len is unused; we override it to track buffer
@@ -254,7 +255,7 @@ static int vhost_net_set_ubuf_info(struct vhost_net *n)
if (!zcopy)
continue;
n->vqs[i].ubuf_info = kmalloc(sizeof(*n->vqs[i].ubuf_info) *
- UIO_MAXIOV, GFP_KERNEL);
+ UIO_MAXIOV * UBUFLEN, GFP_KERNEL);
if (!n->vqs[i].ubuf_info)
goto err;
}
@@ -526,7 +527,7 @@ static void handle_tx(struct vhost_net *net)
/* use msg_control to pass vhost zerocopy ubuf info to skb */
if (zcopy_used) {
struct ubuf_info *ubuf;
- ubuf = nvq->ubuf_info + nvq->upend_idx;
+ ubuf = nvq->ubuf_info + (nvq->upend_idx * UBUFLEN);
vq->heads[nvq->upend_idx].id = cpu_to_vhost32(vq, head);
vq->heads[nvq->upend_idx].len = VHOST_DMA_IN_PROGRESS;
@@ -535,7 +536,7 @@ static void handle_tx(struct vhost_net *net)
ubuf->desc = nvq->upend_idx;
refcount_set(&ubuf->refcnt, 1);
msg.msg_control = ubuf;
- msg.msg_controllen = sizeof(ubuf);
+ msg.msg_controllen = sizeof(*ubuf) * UBUFLEN;
ubufs = nvq->ubufs;
atomic_inc(&ubufs->refcount);
nvq->upend_idx = (nvq->upend_idx + 1) % UIO_MAXIOV;
--
2.14.2.920.gcf0c67979c-goog
^ permalink raw reply related
* Re: [PATCH RFC 1/3] tun: ethtool stats
From: Stephen Hemminger @ 2017-10-06 22:30 UTC (permalink / raw)
To: Willem de Bruijn; +Cc: netdev, mst, jasowang, Willem de Bruijn
In-Reply-To: <20171006222516.90654-2-willemdebruijn.kernel@gmail.com>
On Fri, 6 Oct 2017 18:25:14 -0400
Willem de Bruijn <willemdebruijn.kernel@gmail.com> wrote:
> From: Willem de Bruijn <willemb@google.com>
>
> Support ethtool -S on tun devices. This interface allows exporting
> device-specific stats not present in rtnl stats.
>
> Signed-off-by: Willem de Bruijn <willemb@google.com>
> ---
> drivers/net/tun.c | 38 ++++++++++++++++++++++++++++++++++++++
> 1 file changed, 38 insertions(+)
>
> diff --git a/drivers/net/tun.c b/drivers/net/tun.c
> index 57e4c31fa84a..df6ef9670d05 100644
> --- a/drivers/net/tun.c
> +++ b/drivers/net/tun.c
> @@ -194,6 +194,15 @@ struct tun_flow_entry {
>
> #define TUN_NUM_FLOW_ENTRIES 1024
>
> +static const struct {
> + const char string[ETH_GSTRING_LEN];
> +} tun_ethtool_stats_keys[] = {
> + { "rx_packets" },
> + { "tx_packets" },
> + { "rx_bytes" },
> + { "tx_bytes" }
It looks like you are just exporting the statistics taht are already
through normal path. The purpose of ethtool stats is to provide
statistics unique to the device, not to repeat what is available throug
ip, ifconfig, etc.
^ permalink raw reply
* Re: [PATCH RFC 2/3] tun: expand ethtool stats with zerocopy
From: Willem de Bruijn @ 2017-10-06 22:32 UTC (permalink / raw)
To: Network Development; +Cc: Michael S. Tsirkin, Jason Wang, Willem de Bruijn
In-Reply-To: <20171006222516.90654-3-willemdebruijn.kernel@gmail.com>
On Fri, Oct 6, 2017 at 6:25 PM, Willem de Bruijn
<willemdebruijn.kernel@gmail.com> wrote:
> From: Willem de Bruijn <willemb@google.com>
>
> Add two counters to tun ethool stats to count zerocopy completions.
> - tx_zerocopy counts completions that succeeded without copy.
> - tx_zerocopy_err counts those that triggered a copy.
>
> Tun intercepts completions by replacing the zerocopy completion
> handler (ubuf_info) prepared by the packet source (vhost-net)
> with its own.
>
> To avoid adding another ubuf_info alloc/free in the datapath, only
> enter this mode if the packet source passed an array of two ubuf_info
> to the tun device.
>
> Pass msg_controllen to tun_get_user to detect this.
>
> Signed-off-by: Willem de Bruijn <willemb@google.com>
> ---
> drivers/net/tun.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++-------
> 1 file changed, 69 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/net/tun.c b/drivers/net/tun.c
> index df6ef9670d05..286787d8e875 100644
> --- a/drivers/net/tun.c
> +++ b/drivers/net/tun.c
> @@ -149,6 +149,8 @@ struct tun_pcpu_stats {
> u32 rx_dropped;
> u32 tx_dropped;
> u32 rx_frame_errors;
> + u64 tx_zerocopy;
> + u64 tx_zerocopy_err;
> };
>
> /* A tun_file connects an open character device to a tuntap netdevice. It
> @@ -201,6 +203,8 @@ static const struct {
> { "tx_packets" },
> { "rx_bytes" },
> { "tx_bytes" },
> + { "tx_zerocopy" },
> + { "tx_zerocopy_err" },
> };
>
> /* Since the socket were moved to tun_file, to preserve the behavior of persist
> @@ -1082,8 +1086,9 @@ static void tun_set_headroom(struct net_device *dev, int new_hr)
> tun->align = new_hr;
> }
>
> -static void
> -tun_net_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats)
> +static void __tun_net_get_stats64(struct net_device *dev,
> + struct rtnl_link_stats64 *stats,
> + u64 *ethtool_stats)
> {
> u32 rx_dropped = 0, tx_dropped = 0, rx_frame_errors = 0;
> struct tun_struct *tun = netdev_priv(dev);
> @@ -1103,6 +1108,16 @@ tun_net_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats)
> txbytes = p->tx_bytes;
> } while (u64_stats_fetch_retry(&p->syncp, start));
>
> + if (ethtool_stats) {
> + ethtool_stats[0] += rxpackets;
> + ethtool_stats[1] += txpackets;
> + ethtool_stats[2] += rxbytes;
> + ethtool_stats[3] += txbytes;
> + ethtool_stats[4] += p->tx_zerocopy;
> + ethtool_stats[5] += p->tx_zerocopy_err;
> + continue;
> + }
> +
> stats->rx_packets += rxpackets;
> stats->rx_bytes += rxbytes;
> stats->tx_packets += txpackets;
> @@ -1113,11 +1128,21 @@ tun_net_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats)
> rx_frame_errors += p->rx_frame_errors;
> tx_dropped += p->tx_dropped;
> }
> +
> + if (ethtool_stats)
> + return;
> +
> stats->rx_dropped = rx_dropped;
> stats->rx_frame_errors = rx_frame_errors;
> stats->tx_dropped = tx_dropped;
> }
>
> +static void tun_net_get_stats64(struct net_device *dev,
> + struct rtnl_link_stats64 *stats)
> +{
> + __tun_net_get_stats64(dev, stats, NULL);
> +}
> +
> static int tun_xdp_set(struct net_device *dev, struct bpf_prog *prog,
> struct netlink_ext_ack *extack)
> {
> @@ -1537,10 +1562,44 @@ static struct sk_buff *tun_build_skb(struct tun_struct *tun,
> return NULL;
> }
>
> +static void tun_zerocopy_callback(struct ubuf_info *ubuf, bool success)
> +{
> + struct tun_struct *tun = ubuf->ctx;
This might need an explicit reference to avoid the tun device going
away. Noticed that too late.. need to take a closer look.
^ permalink raw reply
* Re: [PATCH RFC 1/3] tun: ethtool stats
From: Willem de Bruijn @ 2017-10-06 22:37 UTC (permalink / raw)
To: Stephen Hemminger
Cc: Network Development, Michael S. Tsirkin, Jason Wang,
Willem de Bruijn
In-Reply-To: <20171006153059.193688a5@xeon-e3>
On Fri, Oct 6, 2017 at 6:30 PM, Stephen Hemminger
<stephen@networkplumber.org> wrote:
> On Fri, 6 Oct 2017 18:25:14 -0400
> Willem de Bruijn <willemdebruijn.kernel@gmail.com> wrote:
>
>> From: Willem de Bruijn <willemb@google.com>
>>
>> Support ethtool -S on tun devices. This interface allows exporting
>> device-specific stats not present in rtnl stats.
>>
>> Signed-off-by: Willem de Bruijn <willemb@google.com>
>> ---
>> drivers/net/tun.c | 38 ++++++++++++++++++++++++++++++++++++++
>> 1 file changed, 38 insertions(+)
>>
>> diff --git a/drivers/net/tun.c b/drivers/net/tun.c
>> index 57e4c31fa84a..df6ef9670d05 100644
>> --- a/drivers/net/tun.c
>> +++ b/drivers/net/tun.c
>> @@ -194,6 +194,15 @@ struct tun_flow_entry {
>>
>> #define TUN_NUM_FLOW_ENTRIES 1024
>>
>> +static const struct {
>> + const char string[ETH_GSTRING_LEN];
>> +} tun_ethtool_stats_keys[] = {
>> + { "rx_packets" },
>> + { "tx_packets" },
>> + { "rx_bytes" },
>> + { "tx_bytes" }
>
> It looks like you are just exporting the statistics taht are already
> through normal path. The purpose of ethtool stats is to provide
> statistics unique to the device, not to repeat what is available throug
> ip, ifconfig, etc.
A follow-up patch adds non-standard statistics.
I included these common ones, because the new zerocopy counters
are only interesting in relation to tx_packets.
It also seems customary among existing network drivers to include
at least these base counters in the ethtool output.
^ permalink raw reply
* Re: [PATCH net-next] ip_gre: check packet length and mtu correctly in erspan_fb_xmit
From: William Tu @ 2017-10-06 22:51 UTC (permalink / raw)
To: David Miller; +Cc: David Laight, Linux Kernel Network Developers, Xin Long
In-Reply-To: <20171006.232229.74190380417429379.davem@davemloft.net>
On Fri, Oct 6, 2017 at 3:22 PM, David Miller <davem@davemloft.net> wrote:
> From: William Tu <u9012063@gmail.com>
> Date: Fri, 6 Oct 2017 15:09:29 -0700
>
>> Yes, adding another field in the struct net_device can avoid the
>> arithmetic operation. I'm not sure it's a good idea to add new field
>> since there are already a lot in net_device. Let's wait to see how
>> others think.
>
> Why would you add it to net_device when you can add it to ip_tunnel?
>
I see. I will add it to ip_tunnel and resubmit the patch. Thanks!
^ permalink raw reply
* Re: [PATCH RFC 1/3] tun: ethtool stats
From: Stephen Hemminger @ 2017-10-06 23:12 UTC (permalink / raw)
To: Willem de Bruijn
Cc: Network Development, Michael S. Tsirkin, Jason Wang,
Willem de Bruijn
In-Reply-To: <CAF=yD-JZnJ4v3KZY2O3yBKWNnLxRBZJcJDVkYNuHYUZHX5Yw-Q@mail.gmail.com>
On Fri, 6 Oct 2017 18:37:01 -0400
Willem de Bruijn <willemdebruijn.kernel@gmail.com> wrote:
> > It looks like you are just exporting the statistics taht are already
> > through normal path. The purpose of ethtool stats is to provide
> > statistics unique to the device, not to repeat what is available throug
> > ip, ifconfig, etc.
>
> A follow-up patch adds non-standard statistics.
>
> I included these common ones, because the new zerocopy counters
> are only interesting in relation to tx_packets.
>
> It also seems customary among existing network drivers to include
> at least these base counters in the ethtool output.
Only some drivers that slipped through before I started noticing.
Do Not Repeat.
^ permalink raw reply
* Re: [net-next 00/15][pull request] 40GbE Intel Wired LAN Driver Updates 2017-10-06
From: David Miller @ 2017-10-06 22:41 UTC (permalink / raw)
To: jeffrey.t.kirsher; +Cc: netdev, nhorman, sassmann, jogreene
In-Reply-To: <20171006175727.868-1-jeffrey.t.kirsher@intel.com>
From: Jeff Kirsher <jeffrey.t.kirsher@intel.com>
Date: Fri, 6 Oct 2017 10:57:12 -0700
> This series contains updates to i40e and i40evf only.
Pulled, thanks Jeff.
^ permalink raw reply
* Re: [PATCH RFC 1/3] tun: ethtool stats
From: David Miller @ 2017-10-06 23:26 UTC (permalink / raw)
To: stephen; +Cc: willemdebruijn.kernel, netdev, mst, jasowang, willemb
In-Reply-To: <20171006161245.0f4abb99@xeon-e3>
From: Stephen Hemminger <stephen@networkplumber.org>
Date: Fri, 6 Oct 2017 16:12:45 -0700
> On Fri, 6 Oct 2017 18:37:01 -0400
> Willem de Bruijn <willemdebruijn.kernel@gmail.com> wrote:
>
>> > It looks like you are just exporting the statistics taht are already
>> > through normal path. The purpose of ethtool stats is to provide
>> > statistics unique to the device, not to repeat what is available throug
>> > ip, ifconfig, etc.
>>
>> A follow-up patch adds non-standard statistics.
>>
>> I included these common ones, because the new zerocopy counters
>> are only interesting in relation to tx_packets.
>>
>> It also seems customary among existing network drivers to include
>> at least these base counters in the ethtool output.
>
> Only some drivers that slipped through before I started noticing.
> Do Not Repeat.
Agreed, that is a bad practice unless it is being done to export
per-queue versions of those standard netdev statistics.
^ permalink raw reply
* Re: [PATCH net-next 0/7] tcp: implement rb-tree based retransmit queue
From: David Miller @ 2017-10-06 23:31 UTC (permalink / raw)
To: edumazet; +Cc: ncardwell, ycheng, netdev, eric.dumazet
In-Reply-To: <20171006052127.19913-1-edumazet@google.com>
From: Eric Dumazet <edumazet@google.com>
Date: Thu, 5 Oct 2017 22:21:20 -0700
> This patch series implement RB-tree based retransmit queue for TCP,
> to better match modern BDP.
Indeed, there was a lot of resistence to this due to the overhead
for small retransmit queue sizes, but with today's scale this is
long overdue.
So, series applied, nice work!
Maybe we can look into dynamic schemes where when the queue never
goes over N entries we elide the rbtree and use a list. I'm not
so sure how practical that would be.
Thanks!
^ permalink raw reply
* [for-next 2/9] net/mlx5: Avoid NULL pointer dereference on steering cleanup
From: Saeed Mahameed @ 2017-10-06 23:37 UTC (permalink / raw)
To: David S. Miller, Doug Ledford
Cc: netdev-u79uwXL29TY76Z2rM5mHXA, linux-rdma-u79uwXL29TY76Z2rM5mHXA,
Leon Romanovsky, Maor Gottlieb, Saeed Mahameed
In-Reply-To: <20171006233749.25545-1-saeedm-VPRAkNaXOzVWk0Htik3J/w@public.gmane.org>
From: Maor Gottlieb <maorg-VPRAkNaXOzVWk0Htik3J/w@public.gmane.org>
On cleanup, when the node is the last child of parent then it calls to
tree_put_node on the parent, if the parent's reference count
is decremented to 0 (for e.g. when deleting last destination of FTE)
then we free the parent as well and vice versa. In such a case
we will try to free the parent node again.
Increment the parent reference count before cleaning it's children
will prevent implicit release of the parent object.
Fixes: 0da2d66666d3 ('net/mlx5: Properly remove all steering objects')
signed-off-by: Maor Gottlieb <maorg-VPRAkNaXOzVWk0Htik3J/w@public.gmane.org>
Signed-off-by: Saeed Mahameed <saeedm-VPRAkNaXOzVWk0Htik3J/w@public.gmane.org>
---
drivers/net/ethernet/mellanox/mlx5/core/fs_core.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/fs_core.c b/drivers/net/ethernet/mellanox/mlx5/core/fs_core.c
index 6ffe925..f390828 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/fs_core.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/fs_core.c
@@ -2068,8 +2068,10 @@ static void clean_tree(struct fs_node *node)
struct fs_node *iter;
struct fs_node *temp;
+ tree_get_node(node);
list_for_each_entry_safe(iter, temp, &node->children, list)
clean_tree(iter);
+ tree_put_node(node);
tree_remove_node(node);
}
}
--
1.8.3.1
--
To unsubscribe from this list: send the line "unsubscribe linux-rdma" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [for-next 3/9] net/mlx5: Move the entry index allocator to flow group
From: Saeed Mahameed @ 2017-10-06 23:37 UTC (permalink / raw)
To: David S. Miller, Doug Ledford
Cc: netdev-u79uwXL29TY76Z2rM5mHXA, linux-rdma-u79uwXL29TY76Z2rM5mHXA,
Leon Romanovsky, Maor Gottlieb, Saeed Mahameed
In-Reply-To: <20171006233749.25545-1-saeedm-VPRAkNaXOzVWk0Htik3J/w@public.gmane.org>
From: Maor Gottlieb <maorg-VPRAkNaXOzVWk0Htik3J/w@public.gmane.org>
When new flow table entry is added, we search for free index
in the flow group and not in the flow table, therefore we can move
the allocator from flow table to flow group.
In downstream patches it will enable us to lock smaller part
of the steering tree.
Signed-off-by: Maor Gottlieb <maorg-VPRAkNaXOzVWk0Htik3J/w@public.gmane.org>
Signed-off-by: Saeed Mahameed <saeedm-VPRAkNaXOzVWk0Htik3J/w@public.gmane.org>
---
drivers/net/ethernet/mellanox/mlx5/core/fs_core.c | 18 +++++++++---------
drivers/net/ethernet/mellanox/mlx5/core/fs_core.h | 2 +-
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/fs_core.c b/drivers/net/ethernet/mellanox/mlx5/core/fs_core.c
index f390828..2a0b556 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/fs_core.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/fs_core.c
@@ -384,7 +384,6 @@ static void del_flow_table(struct fs_node *node)
err = mlx5_cmd_destroy_flow_table(dev, ft);
if (err)
mlx5_core_warn(dev, "flow steering can't destroy ft\n");
- ida_destroy(&ft->fte_allocator);
rhltable_destroy(&ft->fgs_hash);
fs_get_obj(prio, ft->node.parent);
prio->num_ft--;
@@ -445,7 +444,7 @@ static void destroy_fte(struct fs_fte *fte, struct mlx5_flow_group *fg)
WARN_ON(ret);
fte->status = 0;
fs_get_obj(ft, fg->node.parent);
- ida_simple_remove(&ft->fte_allocator, fte->index);
+ ida_simple_remove(&fg->fte_allocator, fte->index - fg->start_index);
}
static void del_fte(struct fs_node *node)
@@ -488,6 +487,7 @@ static void del_flow_group(struct fs_node *node)
ft->autogroup.num_groups--;
rhashtable_destroy(&fg->ftes_hash);
+ ida_destroy(&fg->fte_allocator);
err = rhltable_remove(&ft->fgs_hash,
&fg->hash,
rhash_fg);
@@ -537,6 +537,7 @@ static struct mlx5_flow_group *alloc_flow_group(u32 *create_fg_in)
kfree(fg);
return ERR_PTR(ret);
}
+ ida_init(&fg->fte_allocator);
fg->mask.match_criteria_enable = match_criteria_enable;
memcpy(&fg->mask.match_criteria, match_criteria,
sizeof(fg->mask.match_criteria));
@@ -575,7 +576,6 @@ static struct mlx5_flow_table *alloc_flow_table(int level, u16 vport, int max_ft
ft->flags = flags;
INIT_LIST_HEAD(&ft->fwd_rules);
mutex_init(&ft->lock);
- ida_init(&ft->fte_allocator);
return ft;
}
@@ -892,7 +892,6 @@ static struct mlx5_flow_table *__mlx5_create_flow_table(struct mlx5_flow_namespa
destroy_ft:
mlx5_cmd_destroy_flow_table(root->dev, ft);
free_ft:
- ida_destroy(&ft->fte_allocator);
kfree(ft);
unlock_root:
mutex_unlock(&root->chain_lock);
@@ -1003,6 +1002,7 @@ static struct mlx5_flow_group *create_flow_group_common(struct mlx5_flow_table *
rhash_fg));
err_free_fg:
rhashtable_destroy(&fg->ftes_hash);
+ ida_destroy(&fg->fte_allocator);
kfree(fg);
return ERR_PTR(err);
@@ -1181,18 +1181,18 @@ static struct fs_fte *create_fte(struct mlx5_flow_group *fg,
u32 *match_value,
struct mlx5_flow_act *flow_act)
{
- struct mlx5_flow_table *ft;
struct fs_fte *fte;
int index;
int ret;
- fs_get_obj(ft, fg->node.parent);
- index = ida_simple_get(&ft->fte_allocator, fg->start_index,
- fg->start_index + fg->max_ftes,
+ index = ida_simple_get(&fg->fte_allocator, 0,
+ fg->max_ftes,
GFP_KERNEL);
if (index < 0)
return ERR_PTR(index);
+ index += fg->start_index;
+
fte = alloc_fte(flow_act, match_value, index);
if (IS_ERR(fte)) {
ret = PTR_ERR(fte);
@@ -1207,7 +1207,7 @@ static struct fs_fte *create_fte(struct mlx5_flow_group *fg,
err_hash:
kfree(fte);
err_alloc:
- ida_simple_remove(&ft->fte_allocator, index);
+ ida_simple_remove(&fg->fte_allocator, index - fg->start_index);
return ERR_PTR(ret);
}
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/fs_core.h b/drivers/net/ethernet/mellanox/mlx5/core/fs_core.h
index 5509a752..02c969c 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/fs_core.h
+++ b/drivers/net/ethernet/mellanox/mlx5/core/fs_core.h
@@ -119,7 +119,6 @@ struct mlx5_flow_table {
/* FWD rules that point on this flow table */
struct list_head fwd_rules;
u32 flags;
- struct ida fte_allocator;
struct rhltable fgs_hash;
};
@@ -199,6 +198,7 @@ struct mlx5_flow_group {
struct mlx5_flow_group_mask mask;
u32 start_index;
u32 max_ftes;
+ struct ida fte_allocator;
u32 id;
struct rhashtable ftes_hash;
struct rhlist_head hash;
--
1.8.3.1
--
To unsubscribe from this list: send the line "unsubscribe linux-rdma" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox