diff mbox

[OpenWrt-Devel,v2] ar8327: add IGMP Snooping support

Message ID 1452442039-11901-1-git-send-email-noltari@gmail.com
State Changes Requested
Headers show

Commit Message

Álvaro Fernández Rojas Jan. 10, 2016, 4:07 p.m. UTC
This add support for IGMP Snooping on atheros switches (disabled by default),
which avoids flooding the network with multicast data.

Tested on TL-WDR4300: disabling IGMP Snooping results in multicast flooding
on each specific port, enabling it back again prevents each port from
receiving all multicast packets.

Partially based on: http://patchwork.ozlabs.org/patch/418122/

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
---
 v2: introduce changes suggested by Linus Lüssing
 - switch to disabled by default
 - add MLD snooping support
 - allow enabling/disabling IGMP v3, join and leave
 - add functions for controlling settings on all ports
 At least igmp_snooping, igmp_fast_join and igmp_fast_leave must be enabled
 in order to get IGMP working. MLD Snooping and IGMP v3 are optional.

 .../linux/generic/files/drivers/net/phy/ar8216.h   |   7 +
 .../linux/generic/files/drivers/net/phy/ar8327.c   | 557 +++++++++++++++++++++
 .../linux/generic/files/drivers/net/phy/ar8327.h   |  76 +++
 3 files changed, 640 insertions(+)

Comments

Felix Fietkau Jan. 10, 2016, 4:42 p.m. UTC | #1
On 2016-01-10 17:07, Álvaro Fernández Rojas wrote:
> This add support for IGMP Snooping on atheros switches (disabled by default),
> which avoids flooding the network with multicast data.
> 
> Tested on TL-WDR4300: disabling IGMP Snooping results in multicast flooding
> on each specific port, enabling it back again prevents each port from
> receiving all multicast packets.
> 
> Partially based on: http://patchwork.ozlabs.org/patch/418122/
> 
> Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
> ---
>  v2: introduce changes suggested by Linus Lüssing
>  - switch to disabled by default
>  - add MLD snooping support
>  - allow enabling/disabling IGMP v3, join and leave
>  - add functions for controlling settings on all ports
>  At least igmp_snooping, igmp_fast_join and igmp_fast_leave must be enabled
>  in order to get IGMP working. MLD Snooping and IGMP v3 are optional.
I'd prefer to expose as few knobs as possible for this. If fast_join and
fast_leave are required for IGMP snooping to work, there is no point in
making them configurable separately.

Also it's weird to have MLD support and IGMP v3 configurable both
globally and per-port. Please derive the per-port config from the global
one instead of having separate attributes.

Also, you can simplify the callback structure by storing the state of
the various parameters in the struct and then just having one function
for applying the configured IGMP snooping state.

- Felix
diff mbox

Patch

diff --git a/target/linux/generic/files/drivers/net/phy/ar8216.h b/target/linux/generic/files/drivers/net/phy/ar8216.h
index 14fe928..6ebfcc7 100644
--- a/target/linux/generic/files/drivers/net/phy/ar8216.h
+++ b/target/linux/generic/files/drivers/net/phy/ar8216.h
@@ -407,6 +407,13 @@  struct ar8xxx_chip {
 			      u32 *status, enum arl_op op);
 	int (*sw_hw_apply)(struct switch_dev *dev);
 
+	int (*get_igmp_v3)(struct ar8xxx_priv *priv);
+	void (*set_igmp_v3)(struct ar8xxx_priv *priv, int enable);
+	int (*get_port_igmp)(struct ar8xxx_priv *priv, int port);
+	void (*set_port_igmp)(struct ar8xxx_priv *priv, int port, int enable);
+	int (*get_port_cfg)(struct ar8xxx_priv *priv, int port, u32 cfg);
+	void (*set_port_cfg)(struct ar8xxx_priv *priv, int port, u32 cfg, int enable);
+
 	const struct ar8xxx_mib_desc *mib_decs;
 	unsigned num_mibs;
 	unsigned mib_func;
diff --git a/target/linux/generic/files/drivers/net/phy/ar8327.c b/target/linux/generic/files/drivers/net/phy/ar8327.c
index 90ee411..c885761 100644
--- a/target/linux/generic/files/drivers/net/phy/ar8327.c
+++ b/target/linux/generic/files/drivers/net/phy/ar8327.c
@@ -33,6 +33,86 @@ 
 extern const struct ar8xxx_mib_desc ar8236_mibs[39];
 extern const struct switch_attr ar8xxx_sw_attr_vlan[1];
 
+const struct ar8327_port_data ar8327_ports_cfg[AR8327_NUM_PORTS] = {
+	{
+		/* Port 0 */
+		.reg = AR8327_REG_FRAM_ACK_CTRL0,
+		.igmp_mld = AR8327_IGMP_MLD_EN0,
+		.igmp_join = AR8327_IGMP_JOIN_EN0,
+		.igmp_leave = AR8327_IGMP_LEAVE_EN0,
+		.eapol = AR8327_EAPOL_EN0,
+		.dhcp = AR8327_DHCP_EN0,
+		.arp_ack = AR8327_ARP_ACK_EN0,
+		.arp_req = AR8327_ARP_REQ_EN0
+	},
+	{
+		/* Port 1 */
+		.reg = AR8327_REG_FRAM_ACK_CTRL0,
+		.igmp_mld = AR8327_IGMP_MLD_EN1,
+		.igmp_join = AR8327_IGMP_JOIN_EN1,
+		.igmp_leave = AR8327_IGMP_LEAVE_EN1,
+		.eapol = AR8327_EAPOL_EN1,
+		.dhcp = AR8327_DHCP_EN1,
+		.arp_ack = AR8327_ARP_ACK_EN1,
+		.arp_req = AR8327_ARP_REQ_EN1
+	},
+	{
+		/* Port 2 */
+		.reg = AR8327_REG_FRAM_ACK_CTRL0,
+		.igmp_mld = AR8327_IGMP_MLD_EN2,
+		.igmp_join = AR8327_IGMP_JOIN_EN2,
+		.igmp_leave = AR8327_IGMP_LEAVE_EN2,
+		.eapol = AR8327_EAPOL_EN2,
+		.dhcp = AR8327_DHCP_EN2,
+		.arp_ack = AR8327_ARP_ACK_EN2,
+		.arp_req = AR8327_ARP_REQ_EN2
+	},
+	{
+		/* Port 3 */
+		.reg = AR8327_REG_FRAM_ACK_CTRL0,
+		.igmp_mld = AR8327_IGMP_MLD_EN3,
+		.igmp_join = AR8327_IGMP_JOIN_EN3,
+		.igmp_leave = AR8327_IGMP_LEAVE_EN3,
+		.eapol = AR8327_EAPOL_EN3,
+		.dhcp = AR8327_DHCP_EN3,
+		.arp_ack = AR8327_ARP_ACK_EN3,
+		.arp_req = AR8327_ARP_REQ_EN3
+	},
+	{
+		/* Port 4 */
+		.reg = AR8327_REG_FRAM_ACK_CTRL1,
+		.igmp_mld = AR8327_IGMP_MLD_EN4,
+		.igmp_join = AR8327_IGMP_JOIN_EN4,
+		.igmp_leave = AR8327_IGMP_LEAVE_EN4,
+		.eapol = AR8327_EAPOL_EN4,
+		.dhcp = AR8327_DHCP_EN4,
+		.arp_ack = AR8327_ARP_ACK_EN4,
+		.arp_req = AR8327_ARP_REQ_EN4
+	},
+	{
+		/* Port 5 */
+		.reg = AR8327_REG_FRAM_ACK_CTRL1,
+		.igmp_mld = AR8327_IGMP_MLD_EN5,
+		.igmp_join = AR8327_IGMP_JOIN_EN5,
+		.igmp_leave = AR8327_IGMP_LEAVE_EN5,
+		.eapol = AR8327_EAPOL_EN5,
+		.dhcp = AR8327_DHCP_EN5,
+		.arp_ack = AR8327_ARP_ACK_EN5,
+		.arp_req = AR8327_ARP_REQ_EN5
+	},
+	{
+		/* Port 6 */
+		.reg = AR8327_REG_FRAM_ACK_CTRL1,
+		.igmp_mld = AR8327_IGMP_MLD_EN6,
+		.igmp_join = AR8327_IGMP_JOIN_EN6,
+		.igmp_leave = AR8327_IGMP_LEAVE_EN6,
+		.eapol = AR8327_EAPOL_EN6,
+		.dhcp = AR8327_DHCP_EN6,
+		.arp_ack = AR8327_ARP_ACK_EN6,
+		.arp_req = AR8327_ARP_REQ_EN6
+	},
+};
+
 static u32
 ar8327_get_pad_cfg(struct ar8327_pad_cfg *cfg)
 {
@@ -648,6 +728,174 @@  ar8327_cleanup(struct ar8xxx_priv *priv)
 	ar8327_leds_cleanup(priv);
 }
 
+int
+ar8327_sw_get_igmp_snooping(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port;
+
+	mutex_lock(&priv->reg_mutex);
+	for (port = 0; port < dev->ports; port++) {
+		val->value.i = priv->chip->get_port_igmp(priv, port);
+		if (!val->value.i)
+			break;
+	}
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_set_igmp_snooping(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port;
+
+	mutex_lock(&priv->reg_mutex);
+	for (port = 0; port < dev->ports; port++)
+		priv->chip->set_port_igmp(priv, port, val->value.i);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_get_mld_snooping(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port;
+
+	mutex_lock(&priv->reg_mutex);
+	for (port = 0; port < dev->ports; port++) {
+		val->value.i = priv->chip->get_port_cfg(priv, port, AR8327_PORT_IGMP_MLD);
+		if (!val->value.i)
+			break;
+	}
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_set_mld_snooping(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port;
+
+	mutex_lock(&priv->reg_mutex);
+	for (port = 0; port < dev->ports; port++)
+		priv->chip->set_port_cfg(priv, port, AR8327_PORT_IGMP_MLD, val->value.i);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_get_igmp_fastjoin(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port;
+
+	mutex_lock(&priv->reg_mutex);
+	for (port = 0; port < dev->ports; port++) {
+		val->value.i = priv->chip->get_port_cfg(priv, port, AR8327_PORT_IGMP_JOIN);
+		if (!val->value.i)
+			break;
+	}
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_set_igmp_fastjoin(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port;
+
+	mutex_lock(&priv->reg_mutex);
+	for (port = 0; port < dev->ports; port++)
+		priv->chip->set_port_cfg(priv, port, AR8327_PORT_IGMP_JOIN, val->value.i);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_get_igmp_fastleave(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port;
+
+	mutex_lock(&priv->reg_mutex);
+	for (port = 0; port < dev->ports; port++) {
+		val->value.i = priv->chip->get_port_cfg(priv, port, AR8327_PORT_IGMP_LEAVE);
+		if (!val->value.i)
+			break;
+	}
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_set_igmp_fastleave(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port;
+
+	mutex_lock(&priv->reg_mutex);
+	for (port = 0; port < dev->ports; port++)
+		priv->chip->set_port_cfg(priv, port, AR8327_PORT_IGMP_LEAVE, val->value.i);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_get_igmp_v3(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+
+	mutex_lock(&priv->reg_mutex);
+	val->value.i = priv->chip->get_igmp_v3(priv);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_set_igmp_v3(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+
+	mutex_lock(&priv->reg_mutex);
+	priv->chip->set_igmp_v3(priv, val->value.i);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
 static void
 ar8327_init_globals(struct ar8xxx_priv *priv)
 {
@@ -783,6 +1031,85 @@  ar8327_atu_flush_port(struct ar8xxx_priv *priv, int port)
 	return ret;
 }
 
+int
+ar8327_get_igmp_v3(struct ar8xxx_priv *priv)
+{
+	u32 val = ar8xxx_read(priv, AR8327_REG_FRAM_ACK_CTRL1);
+
+	return (val & AR8327_IGMP_V3_EN) != 0;
+}
+
+void
+ar8327_set_igmp_v3(struct ar8xxx_priv *priv, int enable)
+{
+	if (enable)
+		ar8xxx_reg_set(priv, AR8327_REG_FRAM_ACK_CTRL1, AR8327_IGMP_V3_EN);
+	else
+		ar8xxx_reg_clear(priv, AR8327_REG_FRAM_ACK_CTRL1, AR8327_IGMP_V3_EN);
+}
+
+static int
+ar8327_get_port_igmp(struct ar8xxx_priv *priv, int port)
+{
+	u32 val = ar8xxx_read(priv, AR8327_REG_FWD_CTRL1) >>
+			      AR8327_FWD_CTRL1_IGMP_S;
+
+	return (val & BIT(port)) != 0;
+}
+
+static void
+ar8327_set_port_igmp(struct ar8xxx_priv *priv, int port, int enable)
+{
+	if (enable)
+		ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL1,
+			   BIT(port) << AR8327_FWD_CTRL1_MC_FLOOD_S,
+			   BIT(port) << AR8327_FWD_CTRL1_IGMP_S);
+	else
+		ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL1,
+			   BIT(port) << AR8327_FWD_CTRL1_IGMP_S,
+			   BIT(port) << AR8327_FWD_CTRL1_MC_FLOOD_S);
+}
+
+static u32
+ar8327_port_cfg_values(int port, u32 cfg_values)
+{
+	u32 val = 0;
+
+	if (cfg_values & AR8327_PORT_IGMP_MLD)
+		val |= ar8327_ports_cfg[port].igmp_mld;
+	if (cfg_values & AR8327_PORT_IGMP_JOIN)
+		val |= ar8327_ports_cfg[port].igmp_join;
+	if (cfg_values & AR8327_PORT_IGMP_LEAVE)
+		val |= ar8327_ports_cfg[port].igmp_leave;
+	if (cfg_values & AR8327_PORT_EAPOL)
+		val |= ar8327_ports_cfg[port].eapol;
+	if (cfg_values & AR8327_PORT_DHCP)
+		val |= ar8327_ports_cfg[port].dhcp;
+	if (cfg_values & AR8327_PORT_ARP_ACK)
+		val |= ar8327_ports_cfg[port].arp_ack;
+	if (cfg_values & AR8327_PORT_ARP_REQ)
+		val |= ar8327_ports_cfg[port].arp_req;
+
+	return val;
+}
+
+static int
+ar8327_get_port_cfg(struct ar8xxx_priv *priv, int port, u32 cfg)
+{
+	u32 val = ar8xxx_read(priv, ar8327_ports_cfg[port].reg);
+
+	return (val & ar8327_port_cfg_values(port, cfg)) != 0;
+}
+
+static void
+ar8327_set_port_cfg(struct ar8xxx_priv *priv, int port, u32 cfg, int enable)
+{
+	if (enable)
+		ar8xxx_reg_set(priv, ar8327_ports_cfg[port].reg, ar8327_port_cfg_values(port, cfg));
+	else
+		ar8xxx_reg_clear(priv, ar8327_ports_cfg[port].reg, ar8327_port_cfg_values(port, cfg));
+}
+
 static void
 ar8327_vtu_op(struct ar8xxx_priv *priv, u32 op, u32 val)
 {
@@ -1084,6 +1411,150 @@  ar8327_sw_hw_apply(struct switch_dev *dev)
 	return 0;
 }
 
+int
+ar8327_sw_get_port_igmp_snooping(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port = val->port_vlan;
+
+	if (port >= dev->ports)
+		return -EINVAL;
+
+	mutex_lock(&priv->reg_mutex);
+	val->value.i = priv->chip->get_port_igmp(priv, port);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_set_port_igmp_snooping(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port = val->port_vlan;
+
+	if (port >= dev->ports)
+		return -EINVAL;
+
+	mutex_lock(&priv->reg_mutex);
+	priv->chip->set_port_igmp(priv, port, val->value.i);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_get_port_mld_snooping(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port = val->port_vlan;
+
+	if (port >= dev->ports)
+		return -EINVAL;
+
+	mutex_lock(&priv->reg_mutex);
+	val->value.i = priv->chip->get_port_cfg(priv, port, AR8327_PORT_IGMP_MLD);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_set_port_mld_snooping(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port = val->port_vlan;
+
+	if (port >= dev->ports)
+		return -EINVAL;
+
+	mutex_lock(&priv->reg_mutex);
+	priv->chip->set_port_cfg(priv, port, AR8327_PORT_IGMP_MLD, val->value.i);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_get_port_igmp_fastjoin(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port = val->port_vlan;
+
+	if (port >= dev->ports)
+		return -EINVAL;
+
+	mutex_lock(&priv->reg_mutex);
+	val->value.i = priv->chip->get_port_cfg(priv, port, AR8327_PORT_IGMP_JOIN);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_set_port_igmp_fastjoin(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port = val->port_vlan;
+
+	if (port >= dev->ports)
+		return -EINVAL;
+
+	mutex_lock(&priv->reg_mutex);
+	priv->chip->set_port_cfg(priv, port, AR8327_PORT_IGMP_JOIN, val->value.i);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_get_port_igmp_fastleave(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port = val->port_vlan;
+
+	if (port >= dev->ports)
+		return -EINVAL;
+
+	mutex_lock(&priv->reg_mutex);
+	val->value.i = priv->chip->get_port_cfg(priv, port, AR8327_PORT_IGMP_LEAVE);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_set_port_igmp_fastleave(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port = val->port_vlan;
+
+	if (port >= dev->ports)
+		return -EINVAL;
+
+	mutex_lock(&priv->reg_mutex);
+	priv->chip->set_port_cfg(priv, port, AR8327_PORT_IGMP_LEAVE, val->value.i);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
 static const struct switch_attr ar8327_sw_attr_globals[] = {
 	{
 		.type = SWITCH_TYPE_INT,
@@ -1144,6 +1615,46 @@  static const struct switch_attr ar8327_sw_attr_globals[] = {
 		.description = "Flush ARL table",
 		.set = ar8xxx_sw_set_flush_arl_table,
 	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "igmp_snooping",
+		.description = "Enable IGMP Snooping",
+		.set = ar8327_sw_set_igmp_snooping,
+		.get = ar8327_sw_get_igmp_snooping,
+		.max = 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "igmp_v3",
+		.description = "Enable IGMP V3",
+		.set = ar8327_sw_set_igmp_v3,
+		.get = ar8327_sw_get_igmp_v3,
+		.max = 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "mld_snooping",
+		.description = "Enable MLD Snooping",
+		.set = ar8327_sw_set_mld_snooping,
+		.get = ar8327_sw_get_mld_snooping,
+		.max = 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "igmp_fast_join",
+		.description = "Enable IGMP Fast Join",
+		.set = ar8327_sw_set_igmp_fastjoin,
+		.get = ar8327_sw_get_igmp_fastjoin,
+		.max = 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "igmp_fast_leave",
+		.description = "Enable IGMP Fast Leave",
+		.set = ar8327_sw_set_igmp_fastleave,
+		.get = ar8327_sw_get_igmp_fastleave,
+		.max = 1
+	},
 };
 
 static const struct switch_attr ar8327_sw_attr_port[] = {
@@ -1174,6 +1685,38 @@  static const struct switch_attr ar8327_sw_attr_port[] = {
 		.description = "Flush port's ARL table entries",
 		.set = ar8xxx_sw_set_flush_port_arl_table,
 	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "igmp_snooping",
+		.description = "Enable port's IGMP Snooping",
+		.set = ar8327_sw_set_port_igmp_snooping,
+		.get = ar8327_sw_get_port_igmp_snooping,
+		.max = 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "mld_snooping",
+		.description = "Enable port's MLD Snooping",
+		.set = ar8327_sw_set_port_mld_snooping,
+		.get = ar8327_sw_get_port_mld_snooping,
+		.max = 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "igmp_fast_join",
+		.description = "Enable port's IGMP Fast Join",
+		.set = ar8327_sw_set_port_igmp_fastjoin,
+		.get = ar8327_sw_get_port_igmp_fastjoin,
+		.max = 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "igmp_fast_leave",
+		.description = "Enable port's IGMP Fast Leave",
+		.set = ar8327_sw_set_port_igmp_fastjoin,
+		.get = ar8327_sw_get_port_igmp_fastleave,
+		.max = 1
+	},
 };
 
 static const struct switch_dev_ops ar8327_sw_ops = {
@@ -1227,6 +1770,13 @@  const struct ar8xxx_chip ar8327_chip = {
 	.get_arl_entry = ar8327_get_arl_entry,
 	.sw_hw_apply = ar8327_sw_hw_apply,
 
+	.get_igmp_v3 = ar8327_get_igmp_v3,
+	.set_igmp_v3 = ar8327_set_igmp_v3,
+	.get_port_igmp = ar8327_get_port_igmp,
+	.set_port_igmp = ar8327_set_port_igmp,
+	.get_port_cfg = ar8327_get_port_cfg,
+	.set_port_cfg = ar8327_set_port_cfg,
+
 	.num_mibs = ARRAY_SIZE(ar8236_mibs),
 	.mib_decs = ar8236_mibs,
 	.mib_func = AR8327_REG_MIB_FUNC
@@ -1261,6 +1811,13 @@  const struct ar8xxx_chip ar8337_chip = {
 	.get_arl_entry = ar8327_get_arl_entry,
 	.sw_hw_apply = ar8327_sw_hw_apply,
 
+	.get_igmp_v3 = ar8327_get_igmp_v3,
+	.set_igmp_v3 = ar8327_set_igmp_v3,
+	.get_port_igmp = ar8327_get_port_igmp,
+	.set_port_igmp = ar8327_set_port_igmp,
+	.get_port_cfg = ar8327_get_port_cfg,
+	.set_port_cfg = ar8327_set_port_cfg,
+
 	.num_mibs = ARRAY_SIZE(ar8236_mibs),
 	.mib_decs = ar8236_mibs,
 	.mib_func = AR8327_REG_MIB_FUNC
diff --git a/target/linux/generic/files/drivers/net/phy/ar8327.h b/target/linux/generic/files/drivers/net/phy/ar8327.h
index 8d1fb3b..340ec82 100644
--- a/target/linux/generic/files/drivers/net/phy/ar8327.h
+++ b/target/linux/generic/files/drivers/net/phy/ar8327.h
@@ -98,6 +98,61 @@ 
 #define AR8327_REG_EEE_CTRL			0x100
 #define   AR8327_EEE_CTRL_DISABLE_PHY(_i)	BIT(4 + (_i) * 2)
 
+#define AR8327_REG_FRAM_ACK_CTRL0		0x210
+#define   AR8327_IGMP_MLD_EN0			BIT(0)
+#define   AR8327_IGMP_JOIN_EN0			BIT(1)
+#define   AR8327_IGMP_LEAVE_EN0			BIT(2)
+#define   AR8327_EAPOL_EN0			BIT(3)
+#define   AR8327_DHCP_EN0			BIT(4)
+#define   AR8327_ARP_ACK_EN0			BIT(5)
+#define   AR8327_ARP_REQ_EN0			BIT(6)
+#define   AR8327_IGMP_MLD_EN1			BIT(8)
+#define   AR8327_IGMP_JOIN_EN1			BIT(9)
+#define   AR8327_IGMP_LEAVE_EN1			BIT(10)
+#define   AR8327_EAPOL_EN1			BIT(11)
+#define   AR8327_DHCP_EN1			BIT(12)
+#define   AR8327_ARP_ACK_EN1			BIT(13)
+#define   AR8327_ARP_REQ_EN1			BIT(14)
+#define   AR8327_IGMP_MLD_EN2			BIT(16)
+#define   AR8327_IGMP_JOIN_EN2			BIT(17)
+#define   AR8327_IGMP_LEAVE_EN2			BIT(18)
+#define   AR8327_EAPOL_EN2			BIT(19)
+#define   AR8327_DHCP_EN2			BIT(20)
+#define   AR8327_ARP_ACK_EN2			BIT(21)
+#define   AR8327_ARP_REQ_EN2			BIT(22)
+#define   AR8327_IGMP_MLD_EN3			BIT(24)
+#define   AR8327_IGMP_JOIN_EN3			BIT(25)
+#define   AR8327_IGMP_LEAVE_EN3			BIT(26)
+#define   AR8327_EAPOL_EN3			BIT(27)
+#define   AR8327_DHCP_EN3			BIT(28)
+#define   AR8327_ARP_ACK_EN3			BIT(29)
+#define   AR8327_ARP_REQ_EN3			BIT(30)
+
+#define AR8327_REG_FRAM_ACK_CTRL1		0x214
+#define   AR8327_IGMP_MLD_EN4			BIT(0)
+#define   AR8327_IGMP_JOIN_EN4			BIT(1)
+#define   AR8327_IGMP_LEAVE_EN4			BIT(2)
+#define   AR8327_EAPOL_EN4			BIT(3)
+#define   AR8327_DHCP_EN4			BIT(4)
+#define   AR8327_ARP_ACK_EN4			BIT(5)
+#define   AR8327_ARP_REQ_EN4			BIT(6)
+#define   AR8327_IGMP_MLD_EN5			BIT(8)
+#define   AR8327_IGMP_JOIN_EN5			BIT(9)
+#define   AR8327_IGMP_LEAVE_EN5			BIT(10)
+#define   AR8327_EAPOL_EN5			BIT(11)
+#define   AR8327_DHCP_EN5			BIT(12)
+#define   AR8327_ARP_ACK_EN5			BIT(13)
+#define   AR8327_ARP_REQ_EN5			BIT(14)
+#define   AR8327_IGMP_MLD_EN6			BIT(16)
+#define   AR8327_IGMP_JOIN_EN6			BIT(17)
+#define   AR8327_IGMP_LEAVE_EN6			BIT(18)
+#define   AR8327_EAPOL_EN6			BIT(19)
+#define   AR8327_DHCP_EN6			BIT(20)
+#define   AR8327_ARP_ACK_EN6			BIT(21)
+#define   AR8327_ARP_REQ_EN6			BIT(22)
+#define   AR8327_IGMP_V3_EN			BIT(24)
+#define   AR8327_PPPOE_EN			BIT(25)
+
 #define AR8327_REG_PORT_VLAN0(_i)		(0x420 + (_i) * 0x8)
 #define   AR8327_PORT_VLAN0_DEF_SVID		BITS(0, 12)
 #define   AR8327_PORT_VLAN0_DEF_SVID_S		0
@@ -217,6 +272,16 @@  enum ar8327_led_pattern {
 	AR8327_LED_PATTERN_RULE,
 };
 
+enum ar8327_port_vals {
+	AR8327_PORT_IGMP_MLD = BIT(0),
+	AR8327_PORT_IGMP_JOIN = BIT(1),
+	AR8327_PORT_IGMP_LEAVE = BIT(2),
+	AR8327_PORT_EAPOL = BIT(3),
+	AR8327_PORT_DHCP = BIT(4),
+	AR8327_PORT_ARP_ACK = BIT(5),
+	AR8327_PORT_ARP_REQ = BIT(6)
+};
+
 struct ar8327_led_entry {
 	unsigned reg;
 	unsigned shift;
@@ -249,4 +314,15 @@  struct ar8327_data {
 	bool eee[AR8XXX_NUM_PHYS];
 };
 
+struct ar8327_port_data {
+	int reg;
+	u32 igmp_mld;
+	u32 igmp_join;
+	u32 igmp_leave;
+	u32 eapol;
+	u32 dhcp;
+	u32 arp_ack;
+	u32 arp_req;
+};
+
 #endif