// SPDX-License-Identifier: GPL-2.0+ /* Microchip Sparx5 Switch driver * * Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries. */ #include #include #include "sparx5_main_regs.h" #include "sparx5_main.h" static struct workqueue_struct *sparx5_owq; struct sparx5_switchdev_event_work { struct work_struct work; struct switchdev_notifier_fdb_info fdb_info; struct net_device *dev; unsigned long event; }; static void sparx5_port_attr_bridge_flags(struct sparx5_port *port, struct switchdev_brport_flags flags) { if (flags.mask & BR_MCAST_FLOOD) sparx5_pgid_update_mask(port, PGID_MC_FLOOD, true); } static void sparx5_attr_stp_state_set(struct sparx5_port *port, u8 state) { struct sparx5 *sparx5 = port->sparx5; if (!test_bit(port->portno, sparx5->bridge_mask)) { netdev_err(port->ndev, "Controlling non-bridged port %d?\n", port->portno); return; } switch (state) { case BR_STATE_FORWARDING: set_bit(port->portno, sparx5->bridge_fwd_mask); fallthrough; case BR_STATE_LEARNING: set_bit(port->portno, sparx5->bridge_lrn_mask); break; default: /* All other states treated as blocking */ clear_bit(port->portno, sparx5->bridge_fwd_mask); clear_bit(port->portno, sparx5->bridge_lrn_mask); break; } /* apply the bridge_fwd_mask to all the ports */ sparx5_update_fwd(sparx5); } static void sparx5_port_attr_ageing_set(struct sparx5_port *port, unsigned long ageing_clock_t) { unsigned long ageing_jiffies = clock_t_to_jiffies(ageing_clock_t); u32 ageing_time = jiffies_to_msecs(ageing_jiffies); sparx5_set_ageing(port->sparx5, ageing_time); } static int sparx5_port_attr_set(struct net_device *dev, const void *ctx, const struct switchdev_attr *attr, struct netlink_ext_ack *extack) { struct sparx5_port *port = netdev_priv(dev); switch (attr->id) { case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS: sparx5_port_attr_bridge_flags(port, attr->u.brport_flags); break; case SWITCHDEV_ATTR_ID_PORT_STP_STATE: sparx5_attr_stp_state_set(port, attr->u.stp_state); break; case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME: sparx5_port_attr_ageing_set(port, attr->u.ageing_time); break; case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING: port->vlan_aware = attr->u.vlan_filtering; sparx5_vlan_port_apply(port->sparx5, port); break; default: return -EOPNOTSUPP; } return 0; } static int sparx5_port_bridge_join(struct sparx5_port *port, struct net_device *bridge, struct netlink_ext_ack *extack) { struct sparx5 *sparx5 = port->sparx5; struct net_device *ndev = port->ndev; int err; if (bitmap_empty(sparx5->bridge_mask, SPX5_PORTS)) /* First bridged port */ sparx5->hw_bridge_dev = bridge; else if (sparx5->hw_bridge_dev != bridge) /* This is adding the port to a second bridge, this is * unsupported */ return -ENODEV; set_bit(port->portno, sparx5->bridge_mask); err = switchdev_bridge_port_offload(ndev, ndev, NULL, NULL, NULL, false, extack); if (err) goto err_switchdev_offload; /* Port enters in bridge mode therefor don't need to copy to CPU * frames for multicast in case the bridge is not requesting them */ __dev_mc_unsync(ndev, sparx5_mc_unsync); return 0; err_switchdev_offload: clear_bit(port->portno, sparx5->bridge_mask); return err; } static void sparx5_port_bridge_leave(struct sparx5_port *port, struct net_device *bridge) { struct sparx5 *sparx5 = port->sparx5; switchdev_bridge_port_unoffload(port->ndev, NULL, NULL, NULL); clear_bit(port->portno, sparx5->bridge_mask); if (bitmap_empty(sparx5->bridge_mask, SPX5_PORTS)) sparx5->hw_bridge_dev = NULL; /* Clear bridge vlan settings before updating the port settings */ port->vlan_aware = 0; port->pvid = NULL_VID; port->vid = NULL_VID; /* Port enters in host more therefore restore mc list */ __dev_mc_sync(port->ndev, sparx5_mc_sync, sparx5_mc_unsync); } static int sparx5_port_changeupper(struct net_device *dev, struct netdev_notifier_changeupper_info *info) { struct sparx5_port *port = netdev_priv(dev); struct netlink_ext_ack *extack; int err = 0; extack = netdev_notifier_info_to_extack(&info->info); if (netif_is_bridge_master(info->upper_dev)) { if (info->linking) err = sparx5_port_bridge_join(port, info->upper_dev, extack); else sparx5_port_bridge_leave(port, info->upper_dev); sparx5_vlan_port_apply(port->sparx5, port); } return err; } static int sparx5_port_add_addr(struct net_device *dev, bool up) { struct sparx5_port *port = netdev_priv(dev); struct sparx5 *sparx5 = port->sparx5; u16 vid = port->pvid; if (up) sparx5_mact_learn(sparx5, PGID_CPU, port->ndev->dev_addr, vid); else sparx5_mact_forget(sparx5, port->ndev->dev_addr, vid); return 0; } static int sparx5_netdevice_port_event(struct net_device *dev, struct notifier_block *nb, unsigned long event, void *ptr) { int err = 0; if (!sparx5_netdevice_check(dev)) return 0; switch (event) { case NETDEV_CHANGEUPPER: err = sparx5_port_changeupper(dev, ptr); break; case NETDEV_PRE_UP: err = sparx5_port_add_addr(dev, true); break; case NETDEV_DOWN: err = sparx5_port_add_addr(dev, false); break; } return err; } static int sparx5_netdevice_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *dev = netdev_notifier_info_to_dev(ptr); int ret = 0; ret = sparx5_netdevice_port_event(dev, nb, event, ptr); return notifier_from_errno(ret); } static void sparx5_switchdev_bridge_fdb_event_work(struct work_struct *work) { struct sparx5_switchdev_event_work *switchdev_work = container_of(work, struct sparx5_switchdev_event_work, work); struct net_device *dev = switchdev_work->dev; struct switchdev_notifier_fdb_info *fdb_info; struct sparx5_port *port; struct sparx5 *sparx5; rtnl_lock(); if (!sparx5_netdevice_check(dev)) goto out; port = netdev_priv(dev); sparx5 = port->sparx5; fdb_info = &switchdev_work->fdb_info; switch (switchdev_work->event) { case SWITCHDEV_FDB_ADD_TO_DEVICE: if (!fdb_info->added_by_user) break; sparx5_add_mact_entry(sparx5, port, fdb_info->addr, fdb_info->vid); break; case SWITCHDEV_FDB_DEL_TO_DEVICE: if (!fdb_info->added_by_user) break; sparx5_del_mact_entry(sparx5, fdb_info->addr, fdb_info->vid); break; } out: rtnl_unlock(); kfree(switchdev_work->fdb_info.addr); kfree(switchdev_work); dev_put(dev); } static void sparx5_schedule_work(struct work_struct *work) { queue_work(sparx5_owq, work); } static int sparx5_switchdev_event(struct notifier_block *unused, unsigned long event, void *ptr) { struct net_device *dev = switchdev_notifier_info_to_dev(ptr); struct sparx5_switchdev_event_work *switchdev_work; struct switchdev_notifier_fdb_info *fdb_info; struct switchdev_notifier_info *info = ptr; int err; switch (event) { case SWITCHDEV_PORT_ATTR_SET: err = switchdev_handle_port_attr_set(dev, ptr, sparx5_netdevice_check, sparx5_port_attr_set); return notifier_from_errno(err); case SWITCHDEV_FDB_ADD_TO_DEVICE: fallthrough; case SWITCHDEV_FDB_DEL_TO_DEVICE: switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC); if (!switchdev_work) return NOTIFY_BAD; switchdev_work->dev = dev; switchdev_work->event = event; fdb_info = container_of(info, struct switchdev_notifier_fdb_info, info); INIT_WORK(&switchdev_work->work, sparx5_switchdev_bridge_fdb_event_work); memcpy(&switchdev_work->fdb_info, ptr, sizeof(switchdev_work->fdb_info)); switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC); if (!switchdev_work->fdb_info.addr) goto err_addr_alloc; ether_addr_copy((u8 *)switchdev_work->fdb_info.addr, fdb_info->addr); dev_hold(dev); sparx5_schedule_work(&switchdev_work->work); break; } return NOTIFY_DONE; err_addr_alloc: kfree(switchdev_work); return NOTIFY_BAD; } static void sparx5_sync_port_dev_addr(struct sparx5 *sparx5, struct sparx5_port *port, u16 vid, bool add) { if (!port || !test_bit(port->portno, sparx5->bridge_mask)) return; /* Skip null/host interfaces */ /* Bridge connects to vid? */ if (add) { /* Add port MAC address from the VLAN */ sparx5_mact_learn(sparx5, PGID_CPU, port->ndev->dev_addr, vid); } else { /* Control port addr visibility depending on * port VLAN connectivity. */ if (test_bit(port->portno, sparx5->vlan_mask[vid])) sparx5_mact_learn(sparx5, PGID_CPU, port->ndev->dev_addr, vid); else sparx5_mact_forget(sparx5, port->ndev->dev_addr, vid); } } static void sparx5_sync_bridge_dev_addr(struct net_device *dev, struct sparx5 *sparx5, u16 vid, bool add) { int i; /* First, handle bridge address'es */ if (add) { sparx5_mact_learn(sparx5, PGID_CPU, dev->dev_addr, vid); sparx5_mact_learn(sparx5, PGID_BCAST, dev->broadcast, vid); } else { sparx5_mact_forget(sparx5, dev->dev_addr, vid); sparx5_mact_forget(sparx5, dev->broadcast, vid); } /* Now look at bridged ports */ for (i = 0; i < SPX5_PORTS; i++) sparx5_sync_port_dev_addr(sparx5, sparx5->ports[i], vid, add); } static int sparx5_handle_port_vlan_add(struct net_device *dev, struct notifier_block *nb, const struct switchdev_obj_port_vlan *v) { struct sparx5_port *port = netdev_priv(dev); if (netif_is_bridge_master(dev)) { if (v->flags & BRIDGE_VLAN_INFO_BRENTRY) { struct sparx5 *sparx5 = container_of(nb, struct sparx5, switchdev_blocking_nb); sparx5_sync_bridge_dev_addr(dev, sparx5, v->vid, true); } return 0; } if (!sparx5_netdevice_check(dev)) return -EOPNOTSUPP; return sparx5_vlan_vid_add(port, v->vid, v->flags & BRIDGE_VLAN_INFO_PVID, v->flags & BRIDGE_VLAN_INFO_UNTAGGED); } static int sparx5_handle_port_obj_add(struct net_device *dev, struct notifier_block *nb, struct switchdev_notifier_port_obj_info *info) { const struct switchdev_obj *obj = info->obj; int err; switch (obj->id) { case SWITCHDEV_OBJ_ID_PORT_VLAN: err = sparx5_handle_port_vlan_add(dev, nb, SWITCHDEV_OBJ_PORT_VLAN(obj)); break; default: err = -EOPNOTSUPP; break; } info->handled = true; return err; } static int sparx5_handle_port_vlan_del(struct net_device *dev, struct notifier_block *nb, u16 vid) { struct sparx5_port *port = netdev_priv(dev); int ret; /* Master bridge? */ if (netif_is_bridge_master(dev)) { struct sparx5 *sparx5 = container_of(nb, struct sparx5, switchdev_blocking_nb); sparx5_sync_bridge_dev_addr(dev, sparx5, vid, false); return 0; } if (!sparx5_netdevice_check(dev)) return -EOPNOTSUPP; ret = sparx5_vlan_vid_del(port, vid); if (ret) return ret; /* Delete the port MAC address with the matching VLAN information */ sparx5_mact_forget(port->sparx5, port->ndev->dev_addr, vid); return 0; } static int sparx5_handle_port_obj_del(struct net_device *dev, struct notifier_block *nb, struct switchdev_notifier_port_obj_info *info) { const struct switchdev_obj *obj = info->obj; int err; switch (obj->id) { case SWITCHDEV_OBJ_ID_PORT_VLAN: err = sparx5_handle_port_vlan_del(dev, nb, SWITCHDEV_OBJ_PORT_VLAN(obj)->vid); break; default: err = -EOPNOTSUPP; break; } info->handled = true; return err; } static int sparx5_switchdev_blocking_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *dev = switchdev_notifier_info_to_dev(ptr); int err; switch (event) { case SWITCHDEV_PORT_OBJ_ADD: err = sparx5_handle_port_obj_add(dev, nb, ptr); return notifier_from_errno(err); case SWITCHDEV_PORT_OBJ_DEL: err = sparx5_handle_port_obj_del(dev, nb, ptr); return notifier_from_errno(err); case SWITCHDEV_PORT_ATTR_SET: err = switchdev_handle_port_attr_set(dev, ptr, sparx5_netdevice_check, sparx5_port_attr_set); return notifier_from_errno(err); } return NOTIFY_DONE; } int sparx5_register_notifier_blocks(struct sparx5 *s5) { int err; s5->netdevice_nb.notifier_call = sparx5_netdevice_event; err = register_netdevice_notifier(&s5->netdevice_nb); if (err) return err; s5->switchdev_nb.notifier_call = sparx5_switchdev_event; err = register_switchdev_notifier(&s5->switchdev_nb); if (err) goto err_switchdev_nb; s5->switchdev_blocking_nb.notifier_call = sparx5_switchdev_blocking_event; err = register_switchdev_blocking_notifier(&s5->switchdev_blocking_nb); if (err) goto err_switchdev_blocking_nb; sparx5_owq = alloc_ordered_workqueue("sparx5_order", 0); if (!sparx5_owq) { err = -ENOMEM; goto err_switchdev_blocking_nb; } return 0; err_switchdev_blocking_nb: unregister_switchdev_notifier(&s5->switchdev_nb); err_switchdev_nb: unregister_netdevice_notifier(&s5->netdevice_nb); return err; } void sparx5_unregister_notifier_blocks(struct sparx5 *s5) { destroy_workqueue(sparx5_owq); unregister_switchdev_blocking_notifier(&s5->switchdev_blocking_nb); unregister_switchdev_notifier(&s5->switchdev_nb); unregister_netdevice_notifier(&s5->netdevice_nb); }