Commit 4b430f5c authored by David S. Miller's avatar David S. Miller

Merge branch 'lan966x-switchdev-and-vlan'

Horatiu Vultur says:

====================
net: lan966x: Add switchdev and vlan support

This patch series extends lan966x with switchdev and vlan support.
The first patches just adds new registers and extend the MAC table to
handle the interrupts when a new address is learn/forget.

v7->v8:
- remove extra mac learn when the port leaves the bridge
- replace memcpy with ether_addr_copy
- change the order of operations in lan966x_switch_driver_init/exit
- refactor lan966x_port_bridge_flags

v6->v7:
- fix build issues when compiling as a module

v5->v6:
- fix issues with the singletones, they were not really singletons
- simplify the case where lan966x ports are added to bridges with foreign
  ports
- drop the cases NETDEV_PRE_UP and NETDEV_DOWN
- fix the change of MAC address
- drop the callbacks .ndo_set_features, .ndo_vlan_rx_add_vid,
  .ndo_vlan_rx_kill_vid
- remove duplicate code when port was added in a vlan, the MAC entries
  will be added by the fdb

v4->v5:
- make the notifier_block from lan966x to be singletones
- use switchdev_handle_port_obj_add and switchdev_handle_fdb_event_to_device
  when getting callbacks in the lan966x
- merge the two vlan patches in a single one

v3->v4:
- split the last patch in multiple patches
- replace spin_lock_irqsave/restore with spin_lock/spin_unlock
- remove lan966x_port_change_rx_flags because it was copying all the frames to
  the CPU instead of removing all RX filters.
- implement SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS
- remove calls to __dev_mc_unsync/sync as they are not needed
- replace 0/1 with false/true
- make sure that the lan966x ports are not added to bridges that have other
  interfaces except lan966x
- and allow the lan966x ports to be part of only the same bridge.

v2->v3:
- separate the PVID used when the port is in host mode or vlan unaware
- fix issue when the port was leaving the bridge

v1->v2:
- when allocating entries for the mac table use kzalloc instead of
  devm_kzalloc
- also use GFP_KERNEL instead of GFP_ATOMIC, because is never called
  in atomic context
- when deleting an mac table entry, the order of operations was wrong
- if ana irq is enabled make sure it gets disabled when the driver is
  removed
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 5f89b389 811ba277
......@@ -37,12 +37,14 @@ properties:
items:
- description: register based extraction
- description: frame dma based extraction
- description: analyzer interrupt
interrupt-names:
minItems: 1
items:
- const: xtr
- const: fdma
- const: ana
resets:
items:
......
......@@ -2,6 +2,7 @@ config LAN966X_SWITCH
tristate "Lan966x switch driver"
depends on HAS_IOMEM
depends on OF
depends on NET_SWITCHDEV
select PHYLINK
select PACKING
help
......
......@@ -6,4 +6,5 @@
obj-$(CONFIG_LAN966X_SWITCH) += lan966x-switch.o
lan966x-switch-objs := lan966x_main.o lan966x_phylink.o lan966x_port.o \
lan966x_mac.o lan966x_ethtool.o
lan966x_mac.o lan966x_ethtool.o lan966x_switchdev.o \
lan966x_vlan.o lan966x_fdb.o
// SPDX-License-Identifier: GPL-2.0+
#include <net/switchdev.h>
#include "lan966x_main.h"
struct lan966x_fdb_event_work {
struct work_struct work;
struct switchdev_notifier_fdb_info fdb_info;
struct net_device *dev;
struct lan966x *lan966x;
unsigned long event;
};
struct lan966x_fdb_entry {
struct list_head list;
unsigned char mac[ETH_ALEN] __aligned(2);
u16 vid;
u32 references;
};
static struct lan966x_fdb_entry *
lan966x_fdb_find_entry(struct lan966x *lan966x,
struct switchdev_notifier_fdb_info *fdb_info)
{
struct lan966x_fdb_entry *fdb_entry;
list_for_each_entry(fdb_entry, &lan966x->fdb_entries, list) {
if (fdb_entry->vid == fdb_info->vid &&
ether_addr_equal(fdb_entry->mac, fdb_info->addr))
return fdb_entry;
}
return NULL;
}
static void lan966x_fdb_add_entry(struct lan966x *lan966x,
struct switchdev_notifier_fdb_info *fdb_info)
{
struct lan966x_fdb_entry *fdb_entry;
fdb_entry = lan966x_fdb_find_entry(lan966x, fdb_info);
if (fdb_entry) {
fdb_entry->references++;
return;
}
fdb_entry = kzalloc(sizeof(*fdb_entry), GFP_KERNEL);
if (!fdb_entry)
return;
ether_addr_copy(fdb_entry->mac, fdb_info->addr);
fdb_entry->vid = fdb_info->vid;
fdb_entry->references = 1;
list_add_tail(&fdb_entry->list, &lan966x->fdb_entries);
}
static bool lan966x_fdb_del_entry(struct lan966x *lan966x,
struct switchdev_notifier_fdb_info *fdb_info)
{
struct lan966x_fdb_entry *fdb_entry, *tmp;
list_for_each_entry_safe(fdb_entry, tmp, &lan966x->fdb_entries,
list) {
if (fdb_entry->vid == fdb_info->vid &&
ether_addr_equal(fdb_entry->mac, fdb_info->addr)) {
fdb_entry->references--;
if (!fdb_entry->references) {
list_del(&fdb_entry->list);
kfree(fdb_entry);
return true;
}
break;
}
}
return false;
}
void lan966x_fdb_write_entries(struct lan966x *lan966x, u16 vid)
{
struct lan966x_fdb_entry *fdb_entry;
list_for_each_entry(fdb_entry, &lan966x->fdb_entries, list) {
if (fdb_entry->vid != vid)
continue;
lan966x_mac_cpu_learn(lan966x, fdb_entry->mac, fdb_entry->vid);
}
}
void lan966x_fdb_erase_entries(struct lan966x *lan966x, u16 vid)
{
struct lan966x_fdb_entry *fdb_entry;
list_for_each_entry(fdb_entry, &lan966x->fdb_entries, list) {
if (fdb_entry->vid != vid)
continue;
lan966x_mac_cpu_forget(lan966x, fdb_entry->mac, fdb_entry->vid);
}
}
static void lan966x_fdb_purge_entries(struct lan966x *lan966x)
{
struct lan966x_fdb_entry *fdb_entry, *tmp;
list_for_each_entry_safe(fdb_entry, tmp, &lan966x->fdb_entries, list) {
list_del(&fdb_entry->list);
kfree(fdb_entry);
}
}
int lan966x_fdb_init(struct lan966x *lan966x)
{
INIT_LIST_HEAD(&lan966x->fdb_entries);
lan966x->fdb_work = alloc_ordered_workqueue("lan966x_order", 0);
if (!lan966x->fdb_work)
return -ENOMEM;
return 0;
}
void lan966x_fdb_deinit(struct lan966x *lan966x)
{
destroy_workqueue(lan966x->fdb_work);
lan966x_fdb_purge_entries(lan966x);
}
static void lan966x_fdb_event_work(struct work_struct *work)
{
struct lan966x_fdb_event_work *fdb_work =
container_of(work, struct lan966x_fdb_event_work, work);
struct switchdev_notifier_fdb_info *fdb_info;
struct net_device *dev = fdb_work->dev;
struct lan966x_port *port;
struct lan966x *lan966x;
int ret;
fdb_info = &fdb_work->fdb_info;
lan966x = fdb_work->lan966x;
if (lan966x_netdevice_check(dev)) {
port = netdev_priv(dev);
switch (fdb_work->event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
if (!fdb_info->added_by_user)
break;
lan966x_mac_add_entry(lan966x, port, fdb_info->addr,
fdb_info->vid);
break;
case SWITCHDEV_FDB_DEL_TO_DEVICE:
if (!fdb_info->added_by_user)
break;
lan966x_mac_del_entry(lan966x, fdb_info->addr,
fdb_info->vid);
break;
}
} else {
if (!netif_is_bridge_master(dev))
goto out;
/* In case the bridge is called */
switch (fdb_work->event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
/* If there is no front port in this vlan, there is no
* point to copy the frame to CPU because it would be
* just dropped at later point. So add it only if
* there is a port but it is required to store the fdb
* entry for later point when a port actually gets in
* the vlan.
*/
lan966x_fdb_add_entry(lan966x, fdb_info);
if (!lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x,
fdb_info->vid))
break;
lan966x_mac_cpu_learn(lan966x, fdb_info->addr,
fdb_info->vid);
break;
case SWITCHDEV_FDB_DEL_TO_DEVICE:
ret = lan966x_fdb_del_entry(lan966x, fdb_info);
if (!lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x,
fdb_info->vid))
break;
if (ret)
lan966x_mac_cpu_forget(lan966x, fdb_info->addr,
fdb_info->vid);
break;
}
}
out:
kfree(fdb_work->fdb_info.addr);
kfree(fdb_work);
dev_put(dev);
}
int lan966x_handle_fdb(struct net_device *dev,
struct net_device *orig_dev,
unsigned long event, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info)
{
struct lan966x_port *port = netdev_priv(dev);
struct lan966x *lan966x = port->lan966x;
struct lan966x_fdb_event_work *fdb_work;
if (ctx && ctx != port)
return 0;
switch (event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
case SWITCHDEV_FDB_DEL_TO_DEVICE:
if (lan966x_netdevice_check(orig_dev) &&
!fdb_info->added_by_user)
break;
fdb_work = kzalloc(sizeof(*fdb_work), GFP_ATOMIC);
if (!fdb_work)
return -ENOMEM;
fdb_work->dev = orig_dev;
fdb_work->lan966x = lan966x;
fdb_work->event = event;
INIT_WORK(&fdb_work->work, lan966x_fdb_event_work);
memcpy(&fdb_work->fdb_info, fdb_info, sizeof(fdb_work->fdb_info));
fdb_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
if (!fdb_work->fdb_info.addr)
goto err_addr_alloc;
ether_addr_copy((u8 *)fdb_work->fdb_info.addr, fdb_info->addr);
dev_hold(orig_dev);
queue_work(lan966x->fdb_work, &fdb_work->work);
break;
}
return 0;
err_addr_alloc:
kfree(fdb_work);
return -ENOMEM;
}
......@@ -108,12 +108,12 @@ static int lan966x_port_set_mac_address(struct net_device *dev, void *p)
int ret;
/* Learn the new net device MAC address in the mac table. */
ret = lan966x_mac_cpu_learn(lan966x, addr->sa_data, port->pvid);
ret = lan966x_mac_cpu_learn(lan966x, addr->sa_data, HOST_PVID);
if (ret)
return ret;
/* Then forget the previous one. */
ret = lan966x_mac_cpu_forget(lan966x, dev->dev_addr, port->pvid);
ret = lan966x_mac_cpu_forget(lan966x, dev->dev_addr, HOST_PVID);
if (ret)
return ret;
......@@ -283,6 +283,12 @@ static void lan966x_ifh_set_ipv(void *ifh, u64 bypass)
IFH_POS_IPV, IFH_LEN * 4, PACK, 0);
}
static void lan966x_ifh_set_vid(void *ifh, u64 vid)
{
packing(ifh, &vid, IFH_POS_TCI + IFH_WID_TCI - 1,
IFH_POS_TCI, IFH_LEN * 4, PACK, 0);
}
static int lan966x_port_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct lan966x_port *port = netdev_priv(dev);
......@@ -294,32 +300,11 @@ static int lan966x_port_xmit(struct sk_buff *skb, struct net_device *dev)
lan966x_ifh_set_port(ifh, BIT_ULL(port->chip_port));
lan966x_ifh_set_qos_class(ifh, skb->priority >= 7 ? 0x7 : skb->priority);
lan966x_ifh_set_ipv(ifh, skb->priority >= 7 ? 0x7 : skb->priority);
lan966x_ifh_set_vid(ifh, skb_vlan_tag_get(skb));
return lan966x_port_ifh_xmit(skb, ifh, dev);
}
static void lan966x_set_promisc(struct lan966x_port *port, bool enable)
{
struct lan966x *lan966x = port->lan966x;
lan_rmw(ANA_CPU_FWD_CFG_SRC_COPY_ENA_SET(enable),
ANA_CPU_FWD_CFG_SRC_COPY_ENA,
lan966x, ANA_CPU_FWD_CFG(port->chip_port));
}
static void lan966x_port_change_rx_flags(struct net_device *dev, int flags)
{
struct lan966x_port *port = netdev_priv(dev);
if (!(flags & IFF_PROMISC))
return;
if (dev->flags & IFF_PROMISC)
lan966x_set_promisc(port, true);
else
lan966x_set_promisc(port, false);
}
static int lan966x_port_change_mtu(struct net_device *dev, int new_mtu)
{
struct lan966x_port *port = netdev_priv(dev);
......@@ -369,7 +354,6 @@ static const struct net_device_ops lan966x_port_netdev_ops = {
.ndo_open = lan966x_port_open,
.ndo_stop = lan966x_port_stop,
.ndo_start_xmit = lan966x_port_xmit,
.ndo_change_rx_flags = lan966x_port_change_rx_flags,
.ndo_change_mtu = lan966x_port_change_mtu,
.ndo_set_rx_mode = lan966x_port_set_rx_mode,
.ndo_get_phys_port_name = lan966x_port_get_phys_port_name,
......@@ -378,6 +362,11 @@ static const struct net_device_ops lan966x_port_netdev_ops = {
.ndo_get_port_parent_id = lan966x_port_get_parent_id,
};
bool lan966x_netdevice_check(const struct net_device *dev)
{
return dev->netdev_ops == &lan966x_port_netdev_ops;
}
static int lan966x_port_xtr_status(struct lan966x *lan966x, u8 grp)
{
return lan_rd(lan966x, QS_XTR_RD(grp));
......@@ -514,6 +503,9 @@ static irqreturn_t lan966x_xtr_irq_handler(int irq, void *args)
skb->protocol = eth_type_trans(skb, dev);
if (lan966x->bridge_mask & BIT(src_port))
skb->offload_fwd_mark = 1;
netif_rx_ni(skb);
dev->stats.rx_bytes += len;
dev->stats.rx_packets++;
......@@ -527,6 +519,13 @@ static irqreturn_t lan966x_xtr_irq_handler(int irq, void *args)
return IRQ_HANDLED;
}
static irqreturn_t lan966x_ana_irq_handler(int irq, void *args)
{
struct lan966x *lan966x = args;
return lan966x_mac_irq_handler(lan966x);
}
static void lan966x_cleanup_ports(struct lan966x *lan966x)
{
struct lan966x_port *port;
......@@ -554,6 +553,11 @@ static void lan966x_cleanup_ports(struct lan966x *lan966x)
disable_irq(lan966x->xtr_irq);
lan966x->xtr_irq = -ENXIO;
if (lan966x->ana_irq) {
disable_irq(lan966x->ana_irq);
lan966x->ana_irq = -ENXIO;
}
}
static int lan966x_probe_port(struct lan966x *lan966x, u32 p,
......@@ -578,13 +582,14 @@ static int lan966x_probe_port(struct lan966x *lan966x, u32 p,
port->dev = dev;
port->lan966x = lan966x;
port->chip_port = p;
port->pvid = PORT_PVID;
lan966x->ports[p] = port;
dev->max_mtu = ETH_MAX_MTU;
dev->netdev_ops = &lan966x_port_netdev_ops;
dev->ethtool_ops = &lan966x_ethtool_ops;
dev->features |= NETIF_F_HW_VLAN_CTAG_TX |
NETIF_F_HW_VLAN_STAG_TX;
dev->needed_headroom = IFH_LEN * sizeof(u32);
eth_hw_addr_gen(dev, lan966x->base_mac, p + 1);
......@@ -631,6 +636,10 @@ static int lan966x_probe_port(struct lan966x *lan966x, u32 p,
return err;
}
lan966x_vlan_port_set_vlan_aware(port, 0);
lan966x_vlan_port_set_vid(port, HOST_PVID, false, false);
lan966x_vlan_port_apply(port);
return 0;
}
......@@ -641,6 +650,8 @@ static void lan966x_init(struct lan966x *lan966x)
/* MAC table initialization */
lan966x_mac_init(lan966x);
lan966x_vlan_init(lan966x);
/* Flush queues */
lan_wr(lan_rd(lan966x, QS_XTR_FLUSH) |
GENMASK(1, 0),
......@@ -870,6 +881,15 @@ static int lan966x_probe(struct platform_device *pdev)
return -ENODEV;
}
lan966x->ana_irq = platform_get_irq_byname(pdev, "ana");
if (lan966x->ana_irq) {
err = devm_request_threaded_irq(&pdev->dev, lan966x->ana_irq, NULL,
lan966x_ana_irq_handler, IRQF_ONESHOT,
"ana irq", lan966x);
if (err)
return dev_err_probe(&pdev->dev, err, "Unable to use ana irq");
}
/* init switch */
lan966x_init(lan966x);
lan966x_stats_init(lan966x);
......@@ -899,6 +919,10 @@ static int lan966x_probe(struct platform_device *pdev)
lan966x_port_init(lan966x->ports[p]);
}
err = lan966x_fdb_init(lan966x);
if (err)
goto cleanup_ports;
return 0;
cleanup_ports:
......@@ -923,6 +947,9 @@ static int lan966x_remove(struct platform_device *pdev)
destroy_workqueue(lan966x->stats_queue);
mutex_destroy(&lan966x->stats_lock);
lan966x_mac_purge_entries(lan966x);
lan966x_fdb_deinit(lan966x);
return 0;
}
......@@ -934,7 +961,32 @@ static struct platform_driver lan966x_driver = {
.of_match_table = lan966x_match,
},
};
module_platform_driver(lan966x_driver);
static int __init lan966x_switch_driver_init(void)
{
int ret;
lan966x_register_notifier_blocks();
ret = platform_driver_register(&lan966x_driver);
if (ret)
goto err;
return 0;
err:
lan966x_unregister_notifier_blocks();
return ret;
}
static void __exit lan966x_switch_driver_exit(void)
{
platform_driver_unregister(&lan966x_driver);
lan966x_unregister_notifier_blocks();
}
module_init(lan966x_switch_driver_init);
module_exit(lan966x_switch_driver_exit);
MODULE_DESCRIPTION("Microchip LAN966X switch driver");
MODULE_AUTHOR("Horatiu Vultur <horatiu.vultur@microchip.com>");
......
......@@ -4,9 +4,11 @@
#define __LAN966X_MAIN_H__
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
#include <linux/jiffies.h>
#include <linux/phy.h>
#include <linux/phylink.h>
#include <net/switchdev.h>
#include "lan966x_regs.h"
#include "lan966x_ifh.h"
......@@ -22,7 +24,8 @@
#define PGID_SRC 80
#define PGID_ENTRIES 89
#define PORT_PVID 0
#define UNAWARE_PVID 0
#define HOST_PVID 4095
/* Reserved amount for (SRC, PRIO) at index 8*SRC + PRIO */
#define QSYS_Q_RSRV 95
......@@ -75,6 +78,16 @@ struct lan966x {
u8 base_mac[ETH_ALEN];
struct net_device *bridge;
u16 bridge_mask;
u16 bridge_fwd_mask;
struct list_head mac_entries;
spinlock_t mac_lock; /* lock for mac_entries list */
u16 vlan_mask[VLAN_N_VID];
DECLARE_BITMAP(cpu_vlan_mask, VLAN_N_VID);
/* stats */
const struct lan966x_stat_layout *stats_layout;
u32 num_stats;
......@@ -87,6 +100,11 @@ struct lan966x {
/* interrupts */
int xtr_irq;
int ana_irq;
/* worqueue for fdb */
struct workqueue_struct *fdb_work;
struct list_head fdb_entries;
};
struct lan966x_port_config {
......@@ -105,6 +123,8 @@ struct lan966x_port {
u8 chip_port;
u16 pvid;
u16 vid;
bool vlan_aware;
struct phylink_config phylink_config;
struct phylink_pcs phylink_pcs;
......@@ -118,6 +138,11 @@ extern const struct phylink_mac_ops lan966x_phylink_mac_ops;
extern const struct phylink_pcs_ops lan966x_phylink_pcs_ops;
extern const struct ethtool_ops lan966x_ethtool_ops;
bool lan966x_netdevice_check(const struct net_device *dev);
void lan966x_register_notifier_blocks(void);
void lan966x_unregister_notifier_blocks(void);
void lan966x_stats_get(struct net_device *dev,
struct rtnl_link_stats64 *stats);
int lan966x_stats_init(struct lan966x *lan966x);
......@@ -141,6 +166,43 @@ int lan966x_mac_forget(struct lan966x *lan966x,
int lan966x_mac_cpu_learn(struct lan966x *lan966x, const char *addr, u16 vid);
int lan966x_mac_cpu_forget(struct lan966x *lan966x, const char *addr, u16 vid);
void lan966x_mac_init(struct lan966x *lan966x);
void lan966x_mac_set_ageing(struct lan966x *lan966x,
u32 ageing);
int lan966x_mac_del_entry(struct lan966x *lan966x,
const unsigned char *addr,
u16 vid);
int lan966x_mac_add_entry(struct lan966x *lan966x,
struct lan966x_port *port,
const unsigned char *addr,
u16 vid);
void lan966x_mac_purge_entries(struct lan966x *lan966x);
irqreturn_t lan966x_mac_irq_handler(struct lan966x *lan966x);
void lan966x_vlan_init(struct lan966x *lan966x);
void lan966x_vlan_port_apply(struct lan966x_port *port);
bool lan966x_vlan_cpu_member_cpu_vlan_mask(struct lan966x *lan966x, u16 vid);
void lan966x_vlan_port_set_vlan_aware(struct lan966x_port *port,
bool vlan_aware);
int lan966x_vlan_port_set_vid(struct lan966x_port *port,
u16 vid,
bool pvid,
bool untagged);
void lan966x_vlan_port_add_vlan(struct lan966x_port *port,
u16 vid,
bool pvid,
bool untagged);
void lan966x_vlan_port_del_vlan(struct lan966x_port *port, u16 vid);
void lan966x_vlan_cpu_add_vlan(struct lan966x *lan966x, u16 vid);
void lan966x_vlan_cpu_del_vlan(struct lan966x *lan966x, u16 vid);
void lan966x_fdb_write_entries(struct lan966x *lan966x, u16 vid);
void lan966x_fdb_erase_entries(struct lan966x *lan966x, u16 vid);
int lan966x_fdb_init(struct lan966x *lan966x);
void lan966x_fdb_deinit(struct lan966x *lan966x);
int lan966x_handle_fdb(struct net_device *dev,
struct net_device *orig_dev,
unsigned long event, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info);
static inline void __iomem *lan_addr(void __iomem *base[],
int id, int tinst, int tcnt,
......
......@@ -61,6 +61,9 @@ enum lan966x_target {
#define ANA_ADVLEARN_VLAN_CHK_GET(x)\
FIELD_GET(ANA_ADVLEARN_VLAN_CHK, x)
/* ANA:ANA:VLANMASK */
#define ANA_VLANMASK __REG(TARGET_ANA, 0, 1, 29824, 0, 1, 244, 8, 0, 1, 4)
/* ANA:ANA:ANAINTR */
#define ANA_ANAINTR __REG(TARGET_ANA, 0, 1, 29824, 0, 1, 244, 16, 0, 1, 4)
......@@ -184,6 +187,102 @@ enum lan966x_target {
#define ANA_MACACCESS_MAC_TABLE_CMD_GET(x)\
FIELD_GET(ANA_MACACCESS_MAC_TABLE_CMD, x)
/* ANA:ANA_TABLES:MACTINDX */
#define ANA_MACTINDX __REG(TARGET_ANA, 0, 1, 27520, 0, 1, 128, 52, 0, 1, 4)
#define ANA_MACTINDX_BUCKET GENMASK(12, 11)
#define ANA_MACTINDX_BUCKET_SET(x)\
FIELD_PREP(ANA_MACTINDX_BUCKET, x)
#define ANA_MACTINDX_BUCKET_GET(x)\
FIELD_GET(ANA_MACTINDX_BUCKET, x)
#define ANA_MACTINDX_M_INDEX GENMASK(10, 0)
#define ANA_MACTINDX_M_INDEX_SET(x)\
FIELD_PREP(ANA_MACTINDX_M_INDEX, x)
#define ANA_MACTINDX_M_INDEX_GET(x)\
FIELD_GET(ANA_MACTINDX_M_INDEX, x)
/* ANA:ANA_TABLES:VLAN_PORT_MASK */
#define ANA_VLAN_PORT_MASK __REG(TARGET_ANA, 0, 1, 27520, 0, 1, 128, 56, 0, 1, 4)
#define ANA_VLAN_PORT_MASK_VLAN_PORT_MASK GENMASK(8, 0)
#define ANA_VLAN_PORT_MASK_VLAN_PORT_MASK_SET(x)\
FIELD_PREP(ANA_VLAN_PORT_MASK_VLAN_PORT_MASK, x)
#define ANA_VLAN_PORT_MASK_VLAN_PORT_MASK_GET(x)\
FIELD_GET(ANA_VLAN_PORT_MASK_VLAN_PORT_MASK, x)
/* ANA:ANA_TABLES:VLANACCESS */
#define ANA_VLANACCESS __REG(TARGET_ANA, 0, 1, 27520, 0, 1, 128, 60, 0, 1, 4)
#define ANA_VLANACCESS_VLAN_TBL_CMD GENMASK(1, 0)
#define ANA_VLANACCESS_VLAN_TBL_CMD_SET(x)\
FIELD_PREP(ANA_VLANACCESS_VLAN_TBL_CMD, x)
#define ANA_VLANACCESS_VLAN_TBL_CMD_GET(x)\
FIELD_GET(ANA_VLANACCESS_VLAN_TBL_CMD, x)
/* ANA:ANA_TABLES:VLANTIDX */
#define ANA_VLANTIDX __REG(TARGET_ANA, 0, 1, 27520, 0, 1, 128, 64, 0, 1, 4)
#define ANA_VLANTIDX_VLAN_PGID_CPU_DIS BIT(18)
#define ANA_VLANTIDX_VLAN_PGID_CPU_DIS_SET(x)\
FIELD_PREP(ANA_VLANTIDX_VLAN_PGID_CPU_DIS, x)
#define ANA_VLANTIDX_VLAN_PGID_CPU_DIS_GET(x)\
FIELD_GET(ANA_VLANTIDX_VLAN_PGID_CPU_DIS, x)
#define ANA_VLANTIDX_V_INDEX GENMASK(11, 0)
#define ANA_VLANTIDX_V_INDEX_SET(x)\
FIELD_PREP(ANA_VLANTIDX_V_INDEX, x)
#define ANA_VLANTIDX_V_INDEX_GET(x)\
FIELD_GET(ANA_VLANTIDX_V_INDEX, x)
/* ANA:PORT:VLAN_CFG */
#define ANA_VLAN_CFG(g) __REG(TARGET_ANA, 0, 1, 28672, g, 9, 128, 0, 0, 1, 4)
#define ANA_VLAN_CFG_VLAN_AWARE_ENA BIT(20)
#define ANA_VLAN_CFG_VLAN_AWARE_ENA_SET(x)\
FIELD_PREP(ANA_VLAN_CFG_VLAN_AWARE_ENA, x)
#define ANA_VLAN_CFG_VLAN_AWARE_ENA_GET(x)\
FIELD_GET(ANA_VLAN_CFG_VLAN_AWARE_ENA, x)
#define ANA_VLAN_CFG_VLAN_POP_CNT GENMASK(19, 18)
#define ANA_VLAN_CFG_VLAN_POP_CNT_SET(x)\
FIELD_PREP(ANA_VLAN_CFG_VLAN_POP_CNT, x)
#define ANA_VLAN_CFG_VLAN_POP_CNT_GET(x)\
FIELD_GET(ANA_VLAN_CFG_VLAN_POP_CNT, x)
#define ANA_VLAN_CFG_VLAN_VID GENMASK(11, 0)
#define ANA_VLAN_CFG_VLAN_VID_SET(x)\
FIELD_PREP(ANA_VLAN_CFG_VLAN_VID, x)
#define ANA_VLAN_CFG_VLAN_VID_GET(x)\
FIELD_GET(ANA_VLAN_CFG_VLAN_VID, x)
/* ANA:PORT:DROP_CFG */
#define ANA_DROP_CFG(g) __REG(TARGET_ANA, 0, 1, 28672, g, 9, 128, 4, 0, 1, 4)
#define ANA_DROP_CFG_DROP_UNTAGGED_ENA BIT(6)
#define ANA_DROP_CFG_DROP_UNTAGGED_ENA_SET(x)\
FIELD_PREP(ANA_DROP_CFG_DROP_UNTAGGED_ENA, x)
#define ANA_DROP_CFG_DROP_UNTAGGED_ENA_GET(x)\
FIELD_GET(ANA_DROP_CFG_DROP_UNTAGGED_ENA, x)
#define ANA_DROP_CFG_DROP_PRIO_S_TAGGED_ENA BIT(3)
#define ANA_DROP_CFG_DROP_PRIO_S_TAGGED_ENA_SET(x)\
FIELD_PREP(ANA_DROP_CFG_DROP_PRIO_S_TAGGED_ENA, x)
#define ANA_DROP_CFG_DROP_PRIO_S_TAGGED_ENA_GET(x)\
FIELD_GET(ANA_DROP_CFG_DROP_PRIO_S_TAGGED_ENA, x)
#define ANA_DROP_CFG_DROP_PRIO_C_TAGGED_ENA BIT(2)
#define ANA_DROP_CFG_DROP_PRIO_C_TAGGED_ENA_SET(x)\
FIELD_PREP(ANA_DROP_CFG_DROP_PRIO_C_TAGGED_ENA, x)
#define ANA_DROP_CFG_DROP_PRIO_C_TAGGED_ENA_GET(x)\
FIELD_GET(ANA_DROP_CFG_DROP_PRIO_C_TAGGED_ENA, x)
#define ANA_DROP_CFG_DROP_MC_SMAC_ENA BIT(0)
#define ANA_DROP_CFG_DROP_MC_SMAC_ENA_SET(x)\
FIELD_PREP(ANA_DROP_CFG_DROP_MC_SMAC_ENA, x)
#define ANA_DROP_CFG_DROP_MC_SMAC_ENA_GET(x)\
FIELD_GET(ANA_DROP_CFG_DROP_MC_SMAC_ENA, x)
/* ANA:PORT:CPU_FWD_CFG */
#define ANA_CPU_FWD_CFG(g) __REG(TARGET_ANA, 0, 1, 28672, g, 9, 128, 96, 0, 1, 4)
......@@ -589,6 +688,36 @@ enum lan966x_target {
/* QSYS:RES_CTRL:RES_CFG */
#define QSYS_RES_CFG(g) __REG(TARGET_QSYS, 0, 1, 32768, g, 1024, 8, 0, 0, 1, 4)
/* REW:PORT:PORT_VLAN_CFG */
#define REW_PORT_VLAN_CFG(g) __REG(TARGET_REW, 0, 1, 0, g, 10, 128, 0, 0, 1, 4)
#define REW_PORT_VLAN_CFG_PORT_TPID GENMASK(31, 16)
#define REW_PORT_VLAN_CFG_PORT_TPID_SET(x)\
FIELD_PREP(REW_PORT_VLAN_CFG_PORT_TPID, x)
#define REW_PORT_VLAN_CFG_PORT_TPID_GET(x)\
FIELD_GET(REW_PORT_VLAN_CFG_PORT_TPID, x)
#define REW_PORT_VLAN_CFG_PORT_VID GENMASK(11, 0)
#define REW_PORT_VLAN_CFG_PORT_VID_SET(x)\
FIELD_PREP(REW_PORT_VLAN_CFG_PORT_VID, x)
#define REW_PORT_VLAN_CFG_PORT_VID_GET(x)\
FIELD_GET(REW_PORT_VLAN_CFG_PORT_VID, x)
/* REW:PORT:TAG_CFG */
#define REW_TAG_CFG(g) __REG(TARGET_REW, 0, 1, 0, g, 10, 128, 4, 0, 1, 4)
#define REW_TAG_CFG_TAG_CFG GENMASK(8, 7)
#define REW_TAG_CFG_TAG_CFG_SET(x)\
FIELD_PREP(REW_TAG_CFG_TAG_CFG, x)
#define REW_TAG_CFG_TAG_CFG_GET(x)\
FIELD_GET(REW_TAG_CFG_TAG_CFG, x)
#define REW_TAG_CFG_TAG_TPID_CFG GENMASK(6, 5)
#define REW_TAG_CFG_TAG_TPID_CFG_SET(x)\
FIELD_PREP(REW_TAG_CFG_TAG_TPID_CFG, x)
#define REW_TAG_CFG_TAG_TPID_CFG_GET(x)\
FIELD_GET(REW_TAG_CFG_TAG_TPID_CFG, x)
/* REW:PORT:PORT_CFG */
#define REW_PORT_CFG(g) __REG(TARGET_REW, 0, 1, 0, g, 10, 128, 8, 0, 1, 4)
......
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0+
#include "lan966x_main.h"
#define VLANACCESS_CMD_IDLE 0
#define VLANACCESS_CMD_READ 1
#define VLANACCESS_CMD_WRITE 2
#define VLANACCESS_CMD_INIT 3
static int lan966x_vlan_get_status(struct lan966x *lan966x)
{
return lan_rd(lan966x, ANA_VLANACCESS);
}
static int lan966x_vlan_wait_for_completion(struct lan966x *lan966x)
{
u32 val;
return readx_poll_timeout(lan966x_vlan_get_status,
lan966x, val,
(val & ANA_VLANACCESS_VLAN_TBL_CMD) ==
VLANACCESS_CMD_IDLE,
TABLE_UPDATE_SLEEP_US, TABLE_UPDATE_TIMEOUT_US);
}
static void lan966x_vlan_set_mask(struct lan966x *lan966x, u16 vid)
{
u16 mask = lan966x->vlan_mask[vid];
bool cpu_dis;
cpu_dis = !(mask & BIT(CPU_PORT));
/* Set flags and the VID to configure */
lan_rmw(ANA_VLANTIDX_VLAN_PGID_CPU_DIS_SET(cpu_dis) |
ANA_VLANTIDX_V_INDEX_SET(vid),
ANA_VLANTIDX_VLAN_PGID_CPU_DIS |
ANA_VLANTIDX_V_INDEX,
lan966x, ANA_VLANTIDX);
/* Set the vlan port members mask */
lan_rmw(ANA_VLAN_PORT_MASK_VLAN_PORT_MASK_SET(mask),
ANA_VLAN_PORT_MASK_VLAN_PORT_MASK,
lan966x, ANA_VLAN_PORT_MASK);
/* Issue a write command */
lan_rmw(ANA_VLANACCESS_VLAN_TBL_CMD_SET(VLANACCESS_CMD_WRITE),
ANA_VLANACCESS_VLAN_TBL_CMD,
lan966x, ANA_VLANACCESS);
if (lan966x_vlan_wait_for_completion(lan966x))
dev_err(lan966x->dev, "Vlan set mask failed\n");
}
static void lan966x_vlan_port_add_vlan_mask(struct lan966x_port *port, u16 vid)
{
struct lan966x *lan966x = port->lan966x;
u8 p = port->chip_port;
lan966x->vlan_mask[vid] |= BIT(p);
lan966x_vlan_set_mask(lan966x, vid);
}
static void lan966x_vlan_port_del_vlan_mask(struct lan966x_port *port, u16 vid)
{
struct lan966x *lan966x = port->lan966x;
u8 p = port->chip_port;
lan966x->vlan_mask[vid] &= ~BIT(p);
lan966x_vlan_set_mask(lan966x, vid);
}
static bool lan966x_vlan_port_any_vlan_mask(struct lan966x *lan966x, u16 vid)
{
return !!(lan966x->vlan_mask[vid] & ~BIT(CPU_PORT));
}
static void lan966x_vlan_cpu_add_vlan_mask(struct lan966x *lan966x, u16 vid)
{
lan966x->vlan_mask[vid] |= BIT(CPU_PORT);
lan966x_vlan_set_mask(lan966x, vid);
}
static void lan966x_vlan_cpu_del_vlan_mask(struct lan966x *lan966x, u16 vid)
{
lan966x->vlan_mask[vid] &= ~BIT(CPU_PORT);
lan966x_vlan_set_mask(lan966x, vid);
}
static void lan966x_vlan_cpu_add_cpu_vlan_mask(struct lan966x *lan966x, u16 vid)
{
__set_bit(vid, lan966x->cpu_vlan_mask);
}
static void lan966x_vlan_cpu_del_cpu_vlan_mask(struct lan966x *lan966x, u16 vid)
{
__clear_bit(vid, lan966x->cpu_vlan_mask);
}
bool lan966x_vlan_cpu_member_cpu_vlan_mask(struct lan966x *lan966x, u16 vid)
{
return test_bit(vid, lan966x->cpu_vlan_mask);
}
static u16 lan966x_vlan_port_get_pvid(struct lan966x_port *port)
{
struct lan966x *lan966x = port->lan966x;
if (!(lan966x->bridge_mask & BIT(port->chip_port)))
return HOST_PVID;
return port->vlan_aware ? port->pvid : UNAWARE_PVID;
}
int lan966x_vlan_port_set_vid(struct lan966x_port *port, u16 vid,
bool pvid, bool untagged)
{
struct lan966x *lan966x = port->lan966x;
/* Egress vlan classification */
if (untagged && port->vid != vid) {
if (port->vid) {
dev_err(lan966x->dev,
"Port already has a native VLAN: %d\n",
port->vid);
return -EBUSY;
}
port->vid = vid;
}
/* Default ingress vlan classification */
if (pvid)
port->pvid = vid;
return 0;
}
static void lan966x_vlan_port_remove_vid(struct lan966x_port *port, u16 vid)
{
if (port->pvid == vid)
port->pvid = 0;
if (port->vid == vid)
port->vid = 0;
}
void lan966x_vlan_port_set_vlan_aware(struct lan966x_port *port,
bool vlan_aware)
{
port->vlan_aware = vlan_aware;
}
void lan966x_vlan_port_apply(struct lan966x_port *port)
{
struct lan966x *lan966x = port->lan966x;
u16 pvid;
u32 val;
pvid = lan966x_vlan_port_get_pvid(port);
/* Ingress clasification (ANA_PORT_VLAN_CFG) */
/* Default vlan to classify for untagged frames (may be zero) */
val = ANA_VLAN_CFG_VLAN_VID_SET(pvid);
if (port->vlan_aware)
val |= ANA_VLAN_CFG_VLAN_AWARE_ENA_SET(1) |
ANA_VLAN_CFG_VLAN_POP_CNT_SET(1);
lan_rmw(val,
ANA_VLAN_CFG_VLAN_VID | ANA_VLAN_CFG_VLAN_AWARE_ENA |
ANA_VLAN_CFG_VLAN_POP_CNT,
lan966x, ANA_VLAN_CFG(port->chip_port));
/* Drop frames with multicast source address */
val = ANA_DROP_CFG_DROP_MC_SMAC_ENA_SET(1);
if (port->vlan_aware && !pvid)
/* If port is vlan-aware and tagged, drop untagged and priority
* tagged frames.
*/
val |= ANA_DROP_CFG_DROP_UNTAGGED_ENA_SET(1) |
ANA_DROP_CFG_DROP_PRIO_S_TAGGED_ENA_SET(1) |
ANA_DROP_CFG_DROP_PRIO_C_TAGGED_ENA_SET(1);
lan_wr(val, lan966x, ANA_DROP_CFG(port->chip_port));
/* Egress configuration (REW_TAG_CFG): VLAN tag type to 8021Q */
val = REW_TAG_CFG_TAG_TPID_CFG_SET(0);
if (port->vlan_aware) {
if (port->vid)
/* Tag all frames except when VID == DEFAULT_VLAN */
val |= REW_TAG_CFG_TAG_CFG_SET(1);
else
val |= REW_TAG_CFG_TAG_CFG_SET(3);
}
/* Update only some bits in the register */
lan_rmw(val,
REW_TAG_CFG_TAG_TPID_CFG | REW_TAG_CFG_TAG_CFG,
lan966x, REW_TAG_CFG(port->chip_port));
/* Set default VLAN and tag type to 8021Q */
lan_rmw(REW_PORT_VLAN_CFG_PORT_TPID_SET(ETH_P_8021Q) |
REW_PORT_VLAN_CFG_PORT_VID_SET(port->vid),
REW_PORT_VLAN_CFG_PORT_TPID |
REW_PORT_VLAN_CFG_PORT_VID,
lan966x, REW_PORT_VLAN_CFG(port->chip_port));
}
void lan966x_vlan_port_add_vlan(struct lan966x_port *port,
u16 vid,
bool pvid,
bool untagged)
{
struct lan966x *lan966x = port->lan966x;
/* If the CPU(br) is already part of the vlan then add the fdb
* entries in MAC table to copy the frames to the CPU(br).
* If the CPU(br) is not part of the vlan then it would
* just drop the frames.
*/
if (lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x, vid)) {
lan966x_vlan_cpu_add_vlan_mask(lan966x, vid);
lan966x_fdb_write_entries(lan966x, vid);
}
lan966x_vlan_port_set_vid(port, vid, pvid, untagged);
lan966x_vlan_port_add_vlan_mask(port, vid);
lan966x_vlan_port_apply(port);
}
void lan966x_vlan_port_del_vlan(struct lan966x_port *port, u16 vid)
{
struct lan966x *lan966x = port->lan966x;
lan966x_vlan_port_remove_vid(port, vid);
lan966x_vlan_port_del_vlan_mask(port, vid);
lan966x_vlan_port_apply(port);
/* In case there are no other ports in vlan then remove the CPU from
* that vlan but still keep it in the mask because it may be needed
* again then another port gets added in that vlan
*/
if (!lan966x_vlan_port_any_vlan_mask(lan966x, vid)) {
lan966x_vlan_cpu_del_vlan_mask(lan966x, vid);
lan966x_fdb_erase_entries(lan966x, vid);
}
}
void lan966x_vlan_cpu_add_vlan(struct lan966x *lan966x, u16 vid)
{
/* Add an entry in the MAC table for the CPU
* Add the CPU part of the vlan only if there is another port in that
* vlan otherwise all the broadcast frames in that vlan will go to CPU
* even if none of the ports are in the vlan and then the CPU will just
* need to discard these frames. It is required to store this
* information so when a front port is added then it would add also the
* CPU port.
*/
if (lan966x_vlan_port_any_vlan_mask(lan966x, vid))
lan966x_vlan_cpu_add_vlan_mask(lan966x, vid);
lan966x_vlan_cpu_add_cpu_vlan_mask(lan966x, vid);
lan966x_fdb_write_entries(lan966x, vid);
}
void lan966x_vlan_cpu_del_vlan(struct lan966x *lan966x, u16 vid)
{
/* Remove the CPU part of the vlan */
lan966x_vlan_cpu_del_cpu_vlan_mask(lan966x, vid);
lan966x_vlan_cpu_del_vlan_mask(lan966x, vid);
lan966x_fdb_erase_entries(lan966x, vid);
}
void lan966x_vlan_init(struct lan966x *lan966x)
{
u16 port, vid;
/* Clear VLAN table, by default all ports are members of all VLANS */
lan_rmw(ANA_VLANACCESS_VLAN_TBL_CMD_SET(VLANACCESS_CMD_INIT),
ANA_VLANACCESS_VLAN_TBL_CMD,
lan966x, ANA_VLANACCESS);
lan966x_vlan_wait_for_completion(lan966x);
for (vid = 1; vid < VLAN_N_VID; vid++) {
lan966x->vlan_mask[vid] = 0;
lan966x_vlan_set_mask(lan966x, vid);
}
/* Set all the ports + cpu to be part of HOST_PVID and UNAWARE_PVID */
lan966x->vlan_mask[HOST_PVID] =
GENMASK(lan966x->num_phys_ports - 1, 0) | BIT(CPU_PORT);
lan966x_vlan_set_mask(lan966x, HOST_PVID);
lan966x->vlan_mask[UNAWARE_PVID] =
GENMASK(lan966x->num_phys_ports - 1, 0) | BIT(CPU_PORT);
lan966x_vlan_set_mask(lan966x, UNAWARE_PVID);
lan966x_vlan_cpu_add_cpu_vlan_mask(lan966x, UNAWARE_PVID);
/* Configure the CPU port to be vlan aware */
lan_wr(ANA_VLAN_CFG_VLAN_VID_SET(0) |
ANA_VLAN_CFG_VLAN_AWARE_ENA_SET(1) |
ANA_VLAN_CFG_VLAN_POP_CNT_SET(1),
lan966x, ANA_VLAN_CFG(CPU_PORT));
/* Set vlan ingress filter mask to all ports */
lan_wr(GENMASK(lan966x->num_phys_ports, 0),
lan966x, ANA_VLANMASK);
for (port = 0; port < lan966x->num_phys_ports; port++) {
lan_wr(0, lan966x, REW_PORT_VLAN_CFG(port));
lan_wr(0, lan966x, REW_TAG_CFG(port));
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment