[OpenWrt-Devel,4/5] ar8216: add swconfig attribute to display ARL table on AR8327/AR8337
diff mbox

Message ID 54C1725A.90207@gmail.com
State Accepted
Headers show

Commit Message

Heiner Kallweit Jan. 22, 2015, 9:57 p.m. UTC
Add global read-only swconfig attribute "arl_table" to display the
address resolution table.
So far the chip-specific part is implemented for AR8327/AR8337 only
as I don't have the datasheets for the other AR8XXX chips.

Successfully tested on TL-WDR4300 (AR8327rev2)
and TL-WDR4900 (AR8327rev4).

Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
---
 .../linux/generic/files/drivers/net/phy/ar8216.c   | 96 ++++++++++++++++++----
 .../linux/generic/files/drivers/net/phy/ar8216.h   | 37 +++++++++
 .../linux/generic/files/drivers/net/phy/ar8327.c   | 81 ++++++++++++++++++
 .../linux/generic/files/drivers/net/phy/ar8327.h   | 21 +++++
 4 files changed, 220 insertions(+), 15 deletions(-)

Patch
diff mbox

diff --git a/target/linux/generic/files/drivers/net/phy/ar8216.c b/target/linux/generic/files/drivers/net/phy/ar8216.c
index 8a40a32..9272e75 100644
--- a/target/linux/generic/files/drivers/net/phy/ar8216.c
+++ b/target/linux/generic/files/drivers/net/phy/ar8216.c
@@ -134,19 +134,6 @@  const struct ar8xxx_mib_desc ar8236_mibs[39] = {
 static DEFINE_MUTEX(ar8xxx_dev_list_lock);
 static LIST_HEAD(ar8xxx_dev_list);
 
-static inline void
-split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
-{
-	regaddr >>= 1;
-	*r1 = regaddr & 0x1e;
-
-	regaddr >>= 5;
-	*r2 = regaddr & 0x7;
-
-	regaddr >>= 3;
-	*page = regaddr & 0x1ff;
-}
-
 /* inspired by phy_poll_reset in drivers/net/phy/phy_device.c */
 static int
 ar8xxx_phy_poll_reset(struct mii_bus *bus)
@@ -217,7 +204,7 @@  ar8xxx_phy_init(struct ar8xxx_priv *priv)
 	ar8xxx_phy_poll_reset(bus);
 }
 
-static u32
+u32
 mii_read32(struct ar8xxx_priv *priv, int phy_id, int regnum)
 {
 	struct mii_bus *bus = priv->mii_bus;
@@ -229,7 +216,7 @@  mii_read32(struct ar8xxx_priv *priv, int phy_id, int regnum)
 	return (hi << 16) | lo;
 }
 
-static void
+void
 mii_write32(struct ar8xxx_priv *priv, int phy_id, int regnum, u32 val)
 {
 	struct mii_bus *bus = priv->mii_bus;
@@ -1291,6 +1278,78 @@  unlock:
 	return ret;
 }
 
+int
+ar8xxx_sw_get_arl_table(struct switch_dev *dev,
+			const struct switch_attr *attr,
+			struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	struct mii_bus *bus = priv->mii_bus;
+	const struct ar8xxx_chip *chip = priv->chip;
+	char *buf = priv->arl_buf;
+	int i, j, k, len = 0;
+	struct arl_entry *a, *a1;
+	u32 status;
+
+	if (!chip->get_arl_entry)
+		return -EOPNOTSUPP;
+
+	mutex_lock(&priv->reg_mutex);
+	mutex_lock(&bus->mdio_lock);
+
+	chip->get_arl_entry(priv, NULL, NULL, AR8XXX_ARL_INITIALIZE);
+
+	for(i = 0; i < AR8XXX_NUM_ARL_RECORDS; ++i) {
+		a = &priv->arl_table[i];
+		duplicate:
+		chip->get_arl_entry(priv, a, &status, AR8XXX_ARL_GET_NEXT);
+
+		if (!status)
+			break;
+
+		/* avoid duplicates
+		 * ARL table can include multiple valid entries
+		 * per MAC, just with differing status codes
+		 */
+		for (j = 0; j < i; ++j) {
+			a1 = &priv->arl_table[j];
+			if (a->port == a1->port && !memcmp(a->mac, a1->mac, sizeof(a->mac)))
+				goto duplicate;
+		}
+	}
+
+	mutex_unlock(&bus->mdio_lock);
+
+	len += snprintf(buf + len, sizeof(priv->arl_buf) - len,
+                        "address resolution table\n");
+
+	if (i == AR8XXX_NUM_ARL_RECORDS)
+		len += snprintf(buf + len, sizeof(priv->arl_buf) - len,
+				"Too many entries found, displaying the first %d only!\n",
+				AR8XXX_NUM_ARL_RECORDS);
+
+	for (j = 0; j < priv->dev.ports; ++j) {
+		for (k = 0; k < i; ++k) {
+			a = &priv->arl_table[k];
+			if (a->port != j)
+				continue;
+			len += snprintf(buf + len, sizeof(priv->arl_buf) - len,
+					"Port %d: MAC %02x:%02x:%02x:%02x:%02x:%02x\n",
+					j,
+					a->mac[5], a->mac[4], a->mac[3],
+					a->mac[2], a->mac[1], a->mac[0]);
+		}
+	}
+
+	val->value.s = buf;
+	val->len = len;
+
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+
 static const struct switch_attr ar8xxx_sw_attr_globals[] = {
 	{
 		.type = SWITCH_TYPE_INT,
@@ -1338,6 +1397,13 @@  static const struct switch_attr ar8xxx_sw_attr_globals[] = {
 		.get = ar8xxx_sw_get_mirror_source_port,
 		.max = AR8216_NUM_PORTS - 1
  	},
+	{
+		.type = SWITCH_TYPE_STRING,
+		.name = "arl_table",
+		.description = "Get ARL table",
+		.set = NULL,
+		.get = ar8xxx_sw_get_arl_table,
+	},
 };
 
 const struct switch_attr ar8xxx_sw_attr_port[2] = {
diff --git a/target/linux/generic/files/drivers/net/phy/ar8216.h b/target/linux/generic/files/drivers/net/phy/ar8216.h
index 77df506..ec640dd 100644
--- a/target/linux/generic/files/drivers/net/phy/ar8216.h
+++ b/target/linux/generic/files/drivers/net/phy/ar8216.h
@@ -337,6 +337,18 @@  enum {
 	AR8XXX_VER_AR8337 = 0x13,
 };
 
+#define AR8XXX_NUM_ARL_RECORDS	100
+
+enum arl_op {
+	AR8XXX_ARL_INITIALIZE,
+	AR8XXX_ARL_GET_NEXT
+};
+
+struct arl_entry {
+	u8 port;
+	u8 mac[6];
+};
+
 struct ar8xxx_priv;
 
 struct ar8xxx_mib_desc {
@@ -372,6 +384,8 @@  struct ar8xxx_chip {
 	void (*vtu_load_vlan)(struct ar8xxx_priv *priv, u32 vid, u32 port_mask);
 	void (*phy_fixup)(struct ar8xxx_priv *priv, int phy);
 	void (*set_mirror_regs)(struct ar8xxx_priv *priv);
+	void (*get_arl_entry)(struct ar8xxx_priv *priv, struct arl_entry *a,
+			      u32 *status, enum arl_op op);
 	int (*sw_hw_apply)(struct switch_dev *dev);
 
 	const struct ar8xxx_mib_desc *mib_decs;
@@ -396,6 +410,8 @@  struct ar8xxx_priv {
 	bool initialized;
 	bool port4_phy;
 	char buf[2048];
+	struct arl_entry arl_table[AR8XXX_NUM_ARL_RECORDS];
+	char arl_buf[AR8XXX_NUM_ARL_RECORDS * 32 + 256];
 	bool link_up[AR8X16_MAX_PORTS];
 
 	bool init;
@@ -423,6 +439,10 @@  struct ar8xxx_priv {
 };
 
 u32
+mii_read32(struct ar8xxx_priv *priv, int phy_id, int regnum);
+void
+mii_write32(struct ar8xxx_priv *priv, int phy_id, int regnum, u32 val);
+u32
 ar8xxx_read(struct ar8xxx_priv *priv, int reg);
 void
 ar8xxx_write(struct ar8xxx_priv *priv, int reg, u32 val);
@@ -500,6 +520,10 @@  ar8xxx_sw_get_port_mib(struct switch_dev *dev,
                        const struct switch_attr *attr,
                        struct switch_val *val);
 int
+ar8xxx_sw_get_arl_table(struct switch_dev *dev,
+			const struct switch_attr *attr,
+			struct switch_val *val);
+int
 ar8216_wait_bit(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val);
 
 static inline struct ar8xxx_priv *
@@ -556,6 +580,19 @@  ar8xxx_reg_clear(struct ar8xxx_priv *priv, int reg, u32 val)
 }
 
 static inline void
+split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
+{
+	regaddr >>= 1;
+	*r1 = regaddr & 0x1e;
+
+	regaddr >>= 5;
+	*r2 = regaddr & 0x7;
+
+	regaddr >>= 3;
+	*page = regaddr & 0x1ff;
+}
+
+static inline void
 wait_for_page_switch(void)
 {
 	udelay(5);
diff --git a/target/linux/generic/files/drivers/net/phy/ar8327.c b/target/linux/generic/files/drivers/net/phy/ar8327.c
index fd5bcdc..6444c39 100644
--- a/target/linux/generic/files/drivers/net/phy/ar8327.c
+++ b/target/linux/generic/files/drivers/net/phy/ar8327.c
@@ -971,6 +971,78 @@  ar8327_sw_get_eee(struct switch_dev *dev,
 	return 0;
 }
 
+static void
+ar8327_wait_atu_ready(struct ar8xxx_priv *priv, u16 r2, u16 r1)
+{
+	int timeout = 20;
+
+	while (mii_read32(priv, r2, r1) & AR8327_ATU_FUNC_BUSY && --timeout)
+                udelay(10);
+
+	if (!timeout)
+		pr_err("ar8327: timeout waiting for atu to become ready\n");
+}
+
+static void ar8327_get_arl_entry(struct ar8xxx_priv *priv,
+				 struct arl_entry *a, u32 *status, enum arl_op op)
+{
+	struct mii_bus *bus = priv->mii_bus;
+	u16 r2, page;
+	u16 r1_data0, r1_data1, r1_data2, r1_func;
+	u32 t, val0, val1, val2;
+	int i;
+
+	split_addr(AR8327_REG_ATU_DATA0, &r1_data0, &r2, &page);
+	r2 |= 0x10;
+
+	r1_data1 = (AR8327_REG_ATU_DATA1 >> 1) & 0x1e;
+	r1_data2 = (AR8327_REG_ATU_DATA2 >> 1) & 0x1e;
+	r1_func  = (AR8327_REG_ATU_FUNC >> 1) & 0x1e;
+
+	switch (op) {
+	case AR8XXX_ARL_INITIALIZE:
+		/* all ATU registers are on the same page
+		* therefore set page only once
+		*/
+		bus->write(bus, 0x18, 0, page);
+		wait_for_page_switch();
+
+		ar8327_wait_atu_ready(priv, r2, r1_func);
+
+		mii_write32(priv, r2, r1_data0, 0);
+		mii_write32(priv, r2, r1_data1, 0);
+		mii_write32(priv, r2, r1_data2, 0);
+		break;
+	case AR8XXX_ARL_GET_NEXT:
+		mii_write32(priv, r2, r1_func,
+			    AR8327_ATU_FUNC_OP_GET_NEXT |
+			    AR8327_ATU_FUNC_BUSY);
+		ar8327_wait_atu_ready(priv, r2, r1_func);
+
+		val0 = mii_read32(priv, r2, r1_data0);
+		val1 = mii_read32(priv, r2, r1_data1);
+		val2 = mii_read32(priv, r2, r1_data2);
+
+		*status = val2 & AR8327_ATU_STATUS;
+		if (!*status)
+			break;
+
+		i = 0;
+		t = AR8327_ATU_PORT0;
+		while (!(val1 & t) && ++i < AR8327_NUM_PORTS)
+			t <<= 1;
+
+		a->port = i;
+		a->mac[0] = (val0 & AR8327_ATU_ADDR0) >> AR8327_ATU_ADDR0_S;
+		a->mac[1] = (val0 & AR8327_ATU_ADDR1) >> AR8327_ATU_ADDR1_S;
+		a->mac[2] = (val0 & AR8327_ATU_ADDR2) >> AR8327_ATU_ADDR2_S;
+		a->mac[3] = (val0 & AR8327_ATU_ADDR3) >> AR8327_ATU_ADDR3_S;
+		a->mac[4] = (val1 & AR8327_ATU_ADDR4) >> AR8327_ATU_ADDR4_S;
+		a->mac[5] = (val1 & AR8327_ATU_ADDR5) >> AR8327_ATU_ADDR5_S;
+		break;
+	}
+}
+
 static int
 ar8327_sw_hw_apply(struct switch_dev *dev)
 {
@@ -1041,6 +1113,13 @@  static const struct switch_attr ar8327_sw_attr_globals[] = {
 		.get = ar8xxx_sw_get_mirror_source_port,
 		.max = AR8327_NUM_PORTS - 1
  	},
+	{
+		.type = SWITCH_TYPE_STRING,
+		.name = "arl_table",
+		.description = "Get ARL table",
+		.set = NULL,
+		.get = ar8xxx_sw_get_arl_table,
+	},
 };
 
 static const struct switch_attr ar8327_sw_attr_port[] = {
@@ -1114,6 +1193,7 @@  const struct ar8xxx_chip ar8327_chip = {
 	.vtu_load_vlan = ar8327_vtu_load_vlan,
 	.phy_fixup = ar8327_phy_fixup,
 	.set_mirror_regs = ar8327_set_mirror_regs,
+	.get_arl_entry = ar8327_get_arl_entry,
 	.sw_hw_apply = ar8327_sw_hw_apply,
 
 	.num_mibs = ARRAY_SIZE(ar8236_mibs),
@@ -1146,6 +1226,7 @@  const struct ar8xxx_chip ar8337_chip = {
 	.vtu_load_vlan = ar8327_vtu_load_vlan,
 	.phy_fixup = ar8327_phy_fixup,
 	.set_mirror_regs = ar8327_set_mirror_regs,
+	.get_arl_entry = ar8327_get_arl_entry,
 	.sw_hw_apply = ar8327_sw_hw_apply,
 
 	.num_mibs = ARRAY_SIZE(ar8236_mibs),
diff --git a/target/linux/generic/files/drivers/net/phy/ar8327.h b/target/linux/generic/files/drivers/net/phy/ar8327.h
index f0ef013..29eeb62 100644
--- a/target/linux/generic/files/drivers/net/phy/ar8327.h
+++ b/target/linux/generic/files/drivers/net/phy/ar8327.h
@@ -114,8 +114,29 @@ 
 #define   AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH	3
 
 #define AR8327_REG_ATU_DATA0			0x600
+#define   AR8327_ATU_ADDR0			BITS(0, 8)
+#define   AR8327_ATU_ADDR0_S			0
+#define   AR8327_ATU_ADDR1			BITS(8, 8)
+#define   AR8327_ATU_ADDR1_S			8
+#define   AR8327_ATU_ADDR2			BITS(16, 8)
+#define   AR8327_ATU_ADDR2_S			16
+#define   AR8327_ATU_ADDR3			BITS(24, 8)
+#define   AR8327_ATU_ADDR3_S			24
 #define AR8327_REG_ATU_DATA1			0x604
+#define   AR8327_ATU_ADDR4			BITS(0, 8)
+#define   AR8327_ATU_ADDR4_S			0
+#define   AR8327_ATU_ADDR5			BITS(8, 8)
+#define   AR8327_ATU_ADDR5_S			8
+#define   AR8327_ATU_PORTS			BITS(16, 7)
+#define   AR8327_ATU_PORT0			BIT(16)
+#define   AR8327_ATU_PORT1			BIT(17)
+#define   AR8327_ATU_PORT2			BIT(18)
+#define   AR8327_ATU_PORT3			BIT(19)
+#define   AR8327_ATU_PORT4			BIT(20)
+#define   AR8327_ATU_PORT5			BIT(21)
+#define   AR8327_ATU_PORT6			BIT(22)
 #define AR8327_REG_ATU_DATA2			0x608
+#define   AR8327_ATU_STATUS			BITS(0, 4)
 
 #define AR8327_REG_ATU_FUNC			0x60c
 #define   AR8327_ATU_FUNC_OP			BITS(0, 4)