| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (C) 2023, Intel Corporation. */ |
| |
| #include "ice.h" |
| #include "ice_eswitch_br.h" |
| #include "ice_repr.h" |
| #include "ice_switch.h" |
| #include "ice_vlan.h" |
| #include "ice_vf_vsi_vlan_ops.h" |
| #include "ice_trace.h" |
| |
| #define ICE_ESW_BRIDGE_UPDATE_INTERVAL msecs_to_jiffies(1000) |
| |
| static const struct rhashtable_params ice_fdb_ht_params = { |
| .key_offset = offsetof(struct ice_esw_br_fdb_entry, data), |
| .key_len = sizeof(struct ice_esw_br_fdb_data), |
| .head_offset = offsetof(struct ice_esw_br_fdb_entry, ht_node), |
| .automatic_shrinking = true, |
| }; |
| |
| static bool ice_eswitch_br_is_dev_valid(const struct net_device *dev) |
| { |
| /* Accept only PF netdev, PRs and LAG */ |
| return ice_is_port_repr_netdev(dev) || netif_is_ice(dev) || |
| netif_is_lag_master(dev); |
| } |
| |
| static struct net_device * |
| ice_eswitch_br_get_uplink_from_lag(struct net_device *lag_dev) |
| { |
| struct net_device *lower; |
| struct list_head *iter; |
| |
| netdev_for_each_lower_dev(lag_dev, lower, iter) { |
| if (netif_is_ice(lower)) |
| return lower; |
| } |
| |
| return NULL; |
| } |
| |
| static struct ice_esw_br_port * |
| ice_eswitch_br_netdev_to_port(struct net_device *dev) |
| { |
| if (ice_is_port_repr_netdev(dev)) { |
| struct ice_repr *repr = ice_netdev_to_repr(dev); |
| |
| return repr->br_port; |
| } else if (netif_is_ice(dev) || netif_is_lag_master(dev)) { |
| struct net_device *ice_dev; |
| struct ice_pf *pf; |
| |
| if (netif_is_lag_master(dev)) |
| ice_dev = ice_eswitch_br_get_uplink_from_lag(dev); |
| else |
| ice_dev = dev; |
| |
| if (!ice_dev) |
| return NULL; |
| |
| pf = ice_netdev_to_pf(ice_dev); |
| |
| return pf->br_port; |
| } |
| |
| return NULL; |
| } |
| |
| static void |
| ice_eswitch_br_ingress_rule_setup(struct ice_adv_rule_info *rule_info, |
| u8 pf_id, u16 vf_vsi_idx) |
| { |
| rule_info->sw_act.vsi_handle = vf_vsi_idx; |
| rule_info->sw_act.flag |= ICE_FLTR_RX; |
| rule_info->sw_act.src = pf_id; |
| rule_info->priority = 2; |
| } |
| |
| static void |
| ice_eswitch_br_egress_rule_setup(struct ice_adv_rule_info *rule_info, |
| u16 pf_vsi_idx) |
| { |
| rule_info->sw_act.vsi_handle = pf_vsi_idx; |
| rule_info->sw_act.flag |= ICE_FLTR_TX; |
| rule_info->flags_info.act = ICE_SINGLE_ACT_LAN_ENABLE; |
| rule_info->flags_info.act_valid = true; |
| rule_info->priority = 2; |
| } |
| |
| static int |
| ice_eswitch_br_rule_delete(struct ice_hw *hw, struct ice_rule_query_data *rule) |
| { |
| int err; |
| |
| if (!rule) |
| return -EINVAL; |
| |
| err = ice_rem_adv_rule_by_id(hw, rule); |
| kfree(rule); |
| |
| return err; |
| } |
| |
| static u16 |
| ice_eswitch_br_get_lkups_cnt(u16 vid) |
| { |
| return ice_eswitch_br_is_vid_valid(vid) ? 2 : 1; |
| } |
| |
| static void |
| ice_eswitch_br_add_vlan_lkup(struct ice_adv_lkup_elem *list, u16 vid) |
| { |
| if (ice_eswitch_br_is_vid_valid(vid)) { |
| list[1].type = ICE_VLAN_OFOS; |
| list[1].h_u.vlan_hdr.vlan = cpu_to_be16(vid & VLAN_VID_MASK); |
| list[1].m_u.vlan_hdr.vlan = cpu_to_be16(0xFFFF); |
| } |
| } |
| |
| static struct ice_rule_query_data * |
| ice_eswitch_br_fwd_rule_create(struct ice_hw *hw, int vsi_idx, int port_type, |
| const unsigned char *mac, u16 vid) |
| { |
| struct ice_adv_rule_info rule_info = { 0 }; |
| struct ice_rule_query_data *rule; |
| struct ice_adv_lkup_elem *list; |
| u16 lkups_cnt; |
| int err; |
| |
| lkups_cnt = ice_eswitch_br_get_lkups_cnt(vid); |
| |
| rule = kzalloc(sizeof(*rule), GFP_KERNEL); |
| if (!rule) |
| return ERR_PTR(-ENOMEM); |
| |
| list = kcalloc(lkups_cnt, sizeof(*list), GFP_ATOMIC); |
| if (!list) { |
| err = -ENOMEM; |
| goto err_list_alloc; |
| } |
| |
| switch (port_type) { |
| case ICE_ESWITCH_BR_UPLINK_PORT: |
| ice_eswitch_br_egress_rule_setup(&rule_info, vsi_idx); |
| break; |
| case ICE_ESWITCH_BR_VF_REPR_PORT: |
| ice_eswitch_br_ingress_rule_setup(&rule_info, hw->pf_id, |
| vsi_idx); |
| break; |
| default: |
| err = -EINVAL; |
| goto err_add_rule; |
| } |
| |
| list[0].type = ICE_MAC_OFOS; |
| ether_addr_copy(list[0].h_u.eth_hdr.dst_addr, mac); |
| eth_broadcast_addr(list[0].m_u.eth_hdr.dst_addr); |
| |
| ice_eswitch_br_add_vlan_lkup(list, vid); |
| |
| rule_info.need_pass_l2 = true; |
| |
| rule_info.sw_act.fltr_act = ICE_FWD_TO_VSI; |
| |
| err = ice_add_adv_rule(hw, list, lkups_cnt, &rule_info, rule); |
| if (err) |
| goto err_add_rule; |
| |
| kfree(list); |
| |
| return rule; |
| |
| err_add_rule: |
| kfree(list); |
| err_list_alloc: |
| kfree(rule); |
| |
| return ERR_PTR(err); |
| } |
| |
| static struct ice_rule_query_data * |
| ice_eswitch_br_guard_rule_create(struct ice_hw *hw, u16 vsi_idx, |
| const unsigned char *mac, u16 vid) |
| { |
| struct ice_adv_rule_info rule_info = { 0 }; |
| struct ice_rule_query_data *rule; |
| struct ice_adv_lkup_elem *list; |
| int err = -ENOMEM; |
| u16 lkups_cnt; |
| |
| lkups_cnt = ice_eswitch_br_get_lkups_cnt(vid); |
| |
| rule = kzalloc(sizeof(*rule), GFP_KERNEL); |
| if (!rule) |
| goto err_exit; |
| |
| list = kcalloc(lkups_cnt, sizeof(*list), GFP_ATOMIC); |
| if (!list) |
| goto err_list_alloc; |
| |
| list[0].type = ICE_MAC_OFOS; |
| ether_addr_copy(list[0].h_u.eth_hdr.src_addr, mac); |
| eth_broadcast_addr(list[0].m_u.eth_hdr.src_addr); |
| |
| ice_eswitch_br_add_vlan_lkup(list, vid); |
| |
| rule_info.allow_pass_l2 = true; |
| rule_info.sw_act.vsi_handle = vsi_idx; |
| rule_info.sw_act.fltr_act = ICE_NOP; |
| rule_info.priority = 2; |
| |
| err = ice_add_adv_rule(hw, list, lkups_cnt, &rule_info, rule); |
| if (err) |
| goto err_add_rule; |
| |
| kfree(list); |
| |
| return rule; |
| |
| err_add_rule: |
| kfree(list); |
| err_list_alloc: |
| kfree(rule); |
| err_exit: |
| return ERR_PTR(err); |
| } |
| |
| static struct ice_esw_br_flow * |
| ice_eswitch_br_flow_create(struct device *dev, struct ice_hw *hw, int vsi_idx, |
| int port_type, const unsigned char *mac, u16 vid) |
| { |
| struct ice_rule_query_data *fwd_rule, *guard_rule; |
| struct ice_esw_br_flow *flow; |
| int err; |
| |
| flow = kzalloc(sizeof(*flow), GFP_KERNEL); |
| if (!flow) |
| return ERR_PTR(-ENOMEM); |
| |
| fwd_rule = ice_eswitch_br_fwd_rule_create(hw, vsi_idx, port_type, mac, |
| vid); |
| err = PTR_ERR_OR_ZERO(fwd_rule); |
| if (err) { |
| dev_err(dev, "Failed to create eswitch bridge %sgress forward rule, err: %d\n", |
| port_type == ICE_ESWITCH_BR_UPLINK_PORT ? "e" : "in", |
| err); |
| goto err_fwd_rule; |
| } |
| |
| guard_rule = ice_eswitch_br_guard_rule_create(hw, vsi_idx, mac, vid); |
| err = PTR_ERR_OR_ZERO(guard_rule); |
| if (err) { |
| dev_err(dev, "Failed to create eswitch bridge %sgress guard rule, err: %d\n", |
| port_type == ICE_ESWITCH_BR_UPLINK_PORT ? "e" : "in", |
| err); |
| goto err_guard_rule; |
| } |
| |
| flow->fwd_rule = fwd_rule; |
| flow->guard_rule = guard_rule; |
| |
| return flow; |
| |
| err_guard_rule: |
| ice_eswitch_br_rule_delete(hw, fwd_rule); |
| err_fwd_rule: |
| kfree(flow); |
| |
| return ERR_PTR(err); |
| } |
| |
| static struct ice_esw_br_fdb_entry * |
| ice_eswitch_br_fdb_find(struct ice_esw_br *bridge, const unsigned char *mac, |
| u16 vid) |
| { |
| struct ice_esw_br_fdb_data data = { |
| .vid = vid, |
| }; |
| |
| ether_addr_copy(data.addr, mac); |
| return rhashtable_lookup_fast(&bridge->fdb_ht, &data, |
| ice_fdb_ht_params); |
| } |
| |
| static void |
| ice_eswitch_br_flow_delete(struct ice_pf *pf, struct ice_esw_br_flow *flow) |
| { |
| struct device *dev = ice_pf_to_dev(pf); |
| int err; |
| |
| err = ice_eswitch_br_rule_delete(&pf->hw, flow->fwd_rule); |
| if (err) |
| dev_err(dev, "Failed to delete FDB forward rule, err: %d\n", |
| err); |
| |
| err = ice_eswitch_br_rule_delete(&pf->hw, flow->guard_rule); |
| if (err) |
| dev_err(dev, "Failed to delete FDB guard rule, err: %d\n", |
| err); |
| |
| kfree(flow); |
| } |
| |
| static struct ice_esw_br_vlan * |
| ice_esw_br_port_vlan_lookup(struct ice_esw_br *bridge, u16 vsi_idx, u16 vid) |
| { |
| struct ice_pf *pf = bridge->br_offloads->pf; |
| struct device *dev = ice_pf_to_dev(pf); |
| struct ice_esw_br_port *port; |
| struct ice_esw_br_vlan *vlan; |
| |
| port = xa_load(&bridge->ports, vsi_idx); |
| if (!port) { |
| dev_info(dev, "Bridge port lookup failed (vsi=%u)\n", vsi_idx); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| vlan = xa_load(&port->vlans, vid); |
| if (!vlan) { |
| dev_info(dev, "Bridge port vlan metadata lookup failed (vsi=%u)\n", |
| vsi_idx); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| return vlan; |
| } |
| |
| static void |
| ice_eswitch_br_fdb_entry_delete(struct ice_esw_br *bridge, |
| struct ice_esw_br_fdb_entry *fdb_entry) |
| { |
| struct ice_pf *pf = bridge->br_offloads->pf; |
| |
| rhashtable_remove_fast(&bridge->fdb_ht, &fdb_entry->ht_node, |
| ice_fdb_ht_params); |
| list_del(&fdb_entry->list); |
| |
| ice_eswitch_br_flow_delete(pf, fdb_entry->flow); |
| |
| kfree(fdb_entry); |
| } |
| |
| static void |
| ice_eswitch_br_fdb_offload_notify(struct net_device *dev, |
| const unsigned char *mac, u16 vid, |
| unsigned long val) |
| { |
| struct switchdev_notifier_fdb_info fdb_info = { |
| .addr = mac, |
| .vid = vid, |
| .offloaded = true, |
| }; |
| |
| call_switchdev_notifiers(val, dev, &fdb_info.info, NULL); |
| } |
| |
| static void |
| ice_eswitch_br_fdb_entry_notify_and_cleanup(struct ice_esw_br *bridge, |
| struct ice_esw_br_fdb_entry *entry) |
| { |
| if (!(entry->flags & ICE_ESWITCH_BR_FDB_ADDED_BY_USER)) |
| ice_eswitch_br_fdb_offload_notify(entry->dev, entry->data.addr, |
| entry->data.vid, |
| SWITCHDEV_FDB_DEL_TO_BRIDGE); |
| ice_eswitch_br_fdb_entry_delete(bridge, entry); |
| } |
| |
| static void |
| ice_eswitch_br_fdb_entry_find_and_delete(struct ice_esw_br *bridge, |
| const unsigned char *mac, u16 vid) |
| { |
| struct ice_pf *pf = bridge->br_offloads->pf; |
| struct ice_esw_br_fdb_entry *fdb_entry; |
| struct device *dev = ice_pf_to_dev(pf); |
| |
| fdb_entry = ice_eswitch_br_fdb_find(bridge, mac, vid); |
| if (!fdb_entry) { |
| dev_err(dev, "FDB entry with mac: %pM and vid: %u not found\n", |
| mac, vid); |
| return; |
| } |
| |
| trace_ice_eswitch_br_fdb_entry_find_and_delete(fdb_entry); |
| ice_eswitch_br_fdb_entry_notify_and_cleanup(bridge, fdb_entry); |
| } |
| |
| static void |
| ice_eswitch_br_fdb_entry_create(struct net_device *netdev, |
| struct ice_esw_br_port *br_port, |
| bool added_by_user, |
| const unsigned char *mac, u16 vid) |
| { |
| struct ice_esw_br *bridge = br_port->bridge; |
| struct ice_pf *pf = bridge->br_offloads->pf; |
| struct device *dev = ice_pf_to_dev(pf); |
| struct ice_esw_br_fdb_entry *fdb_entry; |
| struct ice_esw_br_flow *flow; |
| struct ice_esw_br_vlan *vlan; |
| struct ice_hw *hw = &pf->hw; |
| unsigned long event; |
| int err; |
| |
| /* untagged filtering is not yet supported */ |
| if (!(bridge->flags & ICE_ESWITCH_BR_VLAN_FILTERING) && vid) |
| return; |
| |
| if ((bridge->flags & ICE_ESWITCH_BR_VLAN_FILTERING)) { |
| vlan = ice_esw_br_port_vlan_lookup(bridge, br_port->vsi_idx, |
| vid); |
| if (IS_ERR(vlan)) { |
| dev_err(dev, "Failed to find vlan lookup, err: %ld\n", |
| PTR_ERR(vlan)); |
| return; |
| } |
| } |
| |
| fdb_entry = ice_eswitch_br_fdb_find(bridge, mac, vid); |
| if (fdb_entry) |
| ice_eswitch_br_fdb_entry_notify_and_cleanup(bridge, fdb_entry); |
| |
| fdb_entry = kzalloc(sizeof(*fdb_entry), GFP_KERNEL); |
| if (!fdb_entry) { |
| err = -ENOMEM; |
| goto err_exit; |
| } |
| |
| flow = ice_eswitch_br_flow_create(dev, hw, br_port->vsi_idx, |
| br_port->type, mac, vid); |
| if (IS_ERR(flow)) { |
| err = PTR_ERR(flow); |
| goto err_add_flow; |
| } |
| |
| ether_addr_copy(fdb_entry->data.addr, mac); |
| fdb_entry->data.vid = vid; |
| fdb_entry->br_port = br_port; |
| fdb_entry->flow = flow; |
| fdb_entry->dev = netdev; |
| fdb_entry->last_use = jiffies; |
| event = SWITCHDEV_FDB_ADD_TO_BRIDGE; |
| |
| if (added_by_user) { |
| fdb_entry->flags |= ICE_ESWITCH_BR_FDB_ADDED_BY_USER; |
| event = SWITCHDEV_FDB_OFFLOADED; |
| } |
| |
| err = rhashtable_insert_fast(&bridge->fdb_ht, &fdb_entry->ht_node, |
| ice_fdb_ht_params); |
| if (err) |
| goto err_fdb_insert; |
| |
| list_add(&fdb_entry->list, &bridge->fdb_list); |
| trace_ice_eswitch_br_fdb_entry_create(fdb_entry); |
| |
| ice_eswitch_br_fdb_offload_notify(netdev, mac, vid, event); |
| |
| return; |
| |
| err_fdb_insert: |
| ice_eswitch_br_flow_delete(pf, flow); |
| err_add_flow: |
| kfree(fdb_entry); |
| err_exit: |
| dev_err(dev, "Failed to create fdb entry, err: %d\n", err); |
| } |
| |
| static void |
| ice_eswitch_br_fdb_work_dealloc(struct ice_esw_br_fdb_work *fdb_work) |
| { |
| kfree(fdb_work->fdb_info.addr); |
| kfree(fdb_work); |
| } |
| |
| static void |
| ice_eswitch_br_fdb_event_work(struct work_struct *work) |
| { |
| struct ice_esw_br_fdb_work *fdb_work = ice_work_to_fdb_work(work); |
| bool added_by_user = fdb_work->fdb_info.added_by_user; |
| const unsigned char *mac = fdb_work->fdb_info.addr; |
| u16 vid = fdb_work->fdb_info.vid; |
| struct ice_esw_br_port *br_port; |
| |
| rtnl_lock(); |
| |
| br_port = ice_eswitch_br_netdev_to_port(fdb_work->dev); |
| if (!br_port) |
| goto err_exit; |
| |
| switch (fdb_work->event) { |
| case SWITCHDEV_FDB_ADD_TO_DEVICE: |
| ice_eswitch_br_fdb_entry_create(fdb_work->dev, br_port, |
| added_by_user, mac, vid); |
| break; |
| case SWITCHDEV_FDB_DEL_TO_DEVICE: |
| ice_eswitch_br_fdb_entry_find_and_delete(br_port->bridge, |
| mac, vid); |
| break; |
| default: |
| goto err_exit; |
| } |
| |
| err_exit: |
| rtnl_unlock(); |
| dev_put(fdb_work->dev); |
| ice_eswitch_br_fdb_work_dealloc(fdb_work); |
| } |
| |
| static struct ice_esw_br_fdb_work * |
| ice_eswitch_br_fdb_work_alloc(struct switchdev_notifier_fdb_info *fdb_info, |
| struct net_device *dev, |
| unsigned long event) |
| { |
| struct ice_esw_br_fdb_work *work; |
| unsigned char *mac; |
| |
| work = kzalloc(sizeof(*work), GFP_ATOMIC); |
| if (!work) |
| return ERR_PTR(-ENOMEM); |
| |
| INIT_WORK(&work->work, ice_eswitch_br_fdb_event_work); |
| memcpy(&work->fdb_info, fdb_info, sizeof(work->fdb_info)); |
| |
| mac = kzalloc(ETH_ALEN, GFP_ATOMIC); |
| if (!mac) { |
| kfree(work); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| ether_addr_copy(mac, fdb_info->addr); |
| work->fdb_info.addr = mac; |
| work->event = event; |
| work->dev = dev; |
| |
| return work; |
| } |
| |
| static int |
| ice_eswitch_br_switchdev_event(struct notifier_block *nb, |
| unsigned long event, void *ptr) |
| { |
| struct net_device *dev = switchdev_notifier_info_to_dev(ptr); |
| struct switchdev_notifier_fdb_info *fdb_info; |
| struct switchdev_notifier_info *info = ptr; |
| struct ice_esw_br_offloads *br_offloads; |
| struct ice_esw_br_fdb_work *work; |
| struct netlink_ext_ack *extack; |
| struct net_device *upper; |
| |
| br_offloads = ice_nb_to_br_offloads(nb, switchdev_nb); |
| extack = switchdev_notifier_info_to_extack(ptr); |
| |
| upper = netdev_master_upper_dev_get_rcu(dev); |
| if (!upper) |
| return NOTIFY_DONE; |
| |
| if (!netif_is_bridge_master(upper)) |
| return NOTIFY_DONE; |
| |
| if (!ice_eswitch_br_is_dev_valid(dev)) |
| return NOTIFY_DONE; |
| |
| if (!ice_eswitch_br_netdev_to_port(dev)) |
| return NOTIFY_DONE; |
| |
| switch (event) { |
| case SWITCHDEV_FDB_ADD_TO_DEVICE: |
| case SWITCHDEV_FDB_DEL_TO_DEVICE: |
| fdb_info = container_of(info, typeof(*fdb_info), info); |
| |
| work = ice_eswitch_br_fdb_work_alloc(fdb_info, dev, event); |
| if (IS_ERR(work)) { |
| NL_SET_ERR_MSG_MOD(extack, "Failed to init switchdev fdb work"); |
| return notifier_from_errno(PTR_ERR(work)); |
| } |
| dev_hold(dev); |
| |
| queue_work(br_offloads->wq, &work->work); |
| break; |
| default: |
| break; |
| } |
| return NOTIFY_DONE; |
| } |
| |
| void ice_eswitch_br_fdb_flush(struct ice_esw_br *bridge) |
| { |
| struct ice_esw_br_fdb_entry *entry, *tmp; |
| |
| if (!bridge) |
| return; |
| |
| list_for_each_entry_safe(entry, tmp, &bridge->fdb_list, list) |
| ice_eswitch_br_fdb_entry_notify_and_cleanup(bridge, entry); |
| } |
| |
| static void |
| ice_eswitch_br_vlan_filtering_set(struct ice_esw_br *bridge, bool enable) |
| { |
| if (enable == !!(bridge->flags & ICE_ESWITCH_BR_VLAN_FILTERING)) |
| return; |
| |
| ice_eswitch_br_fdb_flush(bridge); |
| if (enable) |
| bridge->flags |= ICE_ESWITCH_BR_VLAN_FILTERING; |
| else |
| bridge->flags &= ~ICE_ESWITCH_BR_VLAN_FILTERING; |
| } |
| |
| static void |
| ice_eswitch_br_clear_pvid(struct ice_esw_br_port *port) |
| { |
| struct ice_vlan port_vlan = ICE_VLAN(ETH_P_8021Q, port->pvid, 0); |
| struct ice_vsi_vlan_ops *vlan_ops; |
| |
| vlan_ops = ice_get_compat_vsi_vlan_ops(port->vsi); |
| |
| vlan_ops->del_vlan(port->vsi, &port_vlan); |
| vlan_ops->clear_port_vlan(port->vsi); |
| |
| ice_vf_vsi_disable_port_vlan(port->vsi); |
| |
| port->pvid = 0; |
| } |
| |
| static void |
| ice_eswitch_br_vlan_cleanup(struct ice_esw_br_port *port, |
| struct ice_esw_br_vlan *vlan) |
| { |
| struct ice_esw_br_fdb_entry *fdb_entry, *tmp; |
| struct ice_esw_br *bridge = port->bridge; |
| |
| trace_ice_eswitch_br_vlan_cleanup(vlan); |
| |
| list_for_each_entry_safe(fdb_entry, tmp, &bridge->fdb_list, list) { |
| if (vlan->vid == fdb_entry->data.vid) |
| ice_eswitch_br_fdb_entry_delete(bridge, fdb_entry); |
| } |
| |
| xa_erase(&port->vlans, vlan->vid); |
| if (port->pvid == vlan->vid) |
| ice_eswitch_br_clear_pvid(port); |
| kfree(vlan); |
| } |
| |
| static void ice_eswitch_br_port_vlans_flush(struct ice_esw_br_port *port) |
| { |
| struct ice_esw_br_vlan *vlan; |
| unsigned long index; |
| |
| xa_for_each(&port->vlans, index, vlan) |
| ice_eswitch_br_vlan_cleanup(port, vlan); |
| } |
| |
| static int |
| ice_eswitch_br_set_pvid(struct ice_esw_br_port *port, |
| struct ice_esw_br_vlan *vlan) |
| { |
| struct ice_vlan port_vlan = ICE_VLAN(ETH_P_8021Q, vlan->vid, 0); |
| struct device *dev = ice_pf_to_dev(port->vsi->back); |
| struct ice_vsi_vlan_ops *vlan_ops; |
| int err; |
| |
| if (port->pvid == vlan->vid || vlan->vid == 1) |
| return 0; |
| |
| /* Setting port vlan on uplink isn't supported by hw */ |
| if (port->type == ICE_ESWITCH_BR_UPLINK_PORT) |
| return -EOPNOTSUPP; |
| |
| if (port->pvid) { |
| dev_info(dev, |
| "Port VLAN (vsi=%u, vid=%u) already exists on the port, remove it before adding new one\n", |
| port->vsi_idx, port->pvid); |
| return -EEXIST; |
| } |
| |
| ice_vf_vsi_enable_port_vlan(port->vsi); |
| |
| vlan_ops = ice_get_compat_vsi_vlan_ops(port->vsi); |
| err = vlan_ops->set_port_vlan(port->vsi, &port_vlan); |
| if (err) |
| return err; |
| |
| err = vlan_ops->add_vlan(port->vsi, &port_vlan); |
| if (err) |
| return err; |
| |
| ice_eswitch_br_port_vlans_flush(port); |
| port->pvid = vlan->vid; |
| |
| return 0; |
| } |
| |
| static struct ice_esw_br_vlan * |
| ice_eswitch_br_vlan_create(u16 vid, u16 flags, struct ice_esw_br_port *port) |
| { |
| struct device *dev = ice_pf_to_dev(port->vsi->back); |
| struct ice_esw_br_vlan *vlan; |
| int err; |
| |
| vlan = kzalloc(sizeof(*vlan), GFP_KERNEL); |
| if (!vlan) |
| return ERR_PTR(-ENOMEM); |
| |
| vlan->vid = vid; |
| vlan->flags = flags; |
| if ((flags & BRIDGE_VLAN_INFO_PVID) && |
| (flags & BRIDGE_VLAN_INFO_UNTAGGED)) { |
| err = ice_eswitch_br_set_pvid(port, vlan); |
| if (err) |
| goto err_set_pvid; |
| } else if ((flags & BRIDGE_VLAN_INFO_PVID) || |
| (flags & BRIDGE_VLAN_INFO_UNTAGGED)) { |
| dev_info(dev, "VLAN push and pop are supported only simultaneously\n"); |
| err = -EOPNOTSUPP; |
| goto err_set_pvid; |
| } |
| |
| err = xa_insert(&port->vlans, vlan->vid, vlan, GFP_KERNEL); |
| if (err) |
| goto err_insert; |
| |
| trace_ice_eswitch_br_vlan_create(vlan); |
| |
| return vlan; |
| |
| err_insert: |
| if (port->pvid) |
| ice_eswitch_br_clear_pvid(port); |
| err_set_pvid: |
| kfree(vlan); |
| return ERR_PTR(err); |
| } |
| |
| static int |
| ice_eswitch_br_port_vlan_add(struct ice_esw_br *bridge, u16 vsi_idx, u16 vid, |
| u16 flags, struct netlink_ext_ack *extack) |
| { |
| struct ice_esw_br_port *port; |
| struct ice_esw_br_vlan *vlan; |
| |
| port = xa_load(&bridge->ports, vsi_idx); |
| if (!port) |
| return -EINVAL; |
| |
| if (port->pvid) { |
| dev_info(ice_pf_to_dev(port->vsi->back), |
| "Port VLAN (vsi=%u, vid=%d) exists on the port, remove it to add trunk VLANs\n", |
| port->vsi_idx, port->pvid); |
| return -EEXIST; |
| } |
| |
| vlan = xa_load(&port->vlans, vid); |
| if (vlan) { |
| if (vlan->flags == flags) |
| return 0; |
| |
| ice_eswitch_br_vlan_cleanup(port, vlan); |
| } |
| |
| vlan = ice_eswitch_br_vlan_create(vid, flags, port); |
| if (IS_ERR(vlan)) { |
| NL_SET_ERR_MSG_FMT_MOD(extack, "Failed to create VLAN entry, vid: %u, vsi: %u", |
| vid, vsi_idx); |
| return PTR_ERR(vlan); |
| } |
| |
| return 0; |
| } |
| |
| static void |
| ice_eswitch_br_port_vlan_del(struct ice_esw_br *bridge, u16 vsi_idx, u16 vid) |
| { |
| struct ice_esw_br_port *port; |
| struct ice_esw_br_vlan *vlan; |
| |
| port = xa_load(&bridge->ports, vsi_idx); |
| if (!port) |
| return; |
| |
| vlan = xa_load(&port->vlans, vid); |
| if (!vlan) |
| return; |
| |
| ice_eswitch_br_vlan_cleanup(port, vlan); |
| } |
| |
| static int |
| ice_eswitch_br_port_obj_add(struct net_device *netdev, const void *ctx, |
| const struct switchdev_obj *obj, |
| struct netlink_ext_ack *extack) |
| { |
| struct ice_esw_br_port *br_port = ice_eswitch_br_netdev_to_port(netdev); |
| struct switchdev_obj_port_vlan *vlan; |
| int err; |
| |
| if (!br_port) |
| return -EINVAL; |
| |
| switch (obj->id) { |
| case SWITCHDEV_OBJ_ID_PORT_VLAN: |
| vlan = SWITCHDEV_OBJ_PORT_VLAN(obj); |
| err = ice_eswitch_br_port_vlan_add(br_port->bridge, |
| br_port->vsi_idx, vlan->vid, |
| vlan->flags, extack); |
| return err; |
| default: |
| return -EOPNOTSUPP; |
| } |
| } |
| |
| static int |
| ice_eswitch_br_port_obj_del(struct net_device *netdev, const void *ctx, |
| const struct switchdev_obj *obj) |
| { |
| struct ice_esw_br_port *br_port = ice_eswitch_br_netdev_to_port(netdev); |
| struct switchdev_obj_port_vlan *vlan; |
| |
| if (!br_port) |
| return -EINVAL; |
| |
| switch (obj->id) { |
| case SWITCHDEV_OBJ_ID_PORT_VLAN: |
| vlan = SWITCHDEV_OBJ_PORT_VLAN(obj); |
| ice_eswitch_br_port_vlan_del(br_port->bridge, br_port->vsi_idx, |
| vlan->vid); |
| return 0; |
| default: |
| return -EOPNOTSUPP; |
| } |
| } |
| |
| static int |
| ice_eswitch_br_port_obj_attr_set(struct net_device *netdev, const void *ctx, |
| const struct switchdev_attr *attr, |
| struct netlink_ext_ack *extack) |
| { |
| struct ice_esw_br_port *br_port = ice_eswitch_br_netdev_to_port(netdev); |
| |
| if (!br_port) |
| return -EINVAL; |
| |
| switch (attr->id) { |
| case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING: |
| ice_eswitch_br_vlan_filtering_set(br_port->bridge, |
| attr->u.vlan_filtering); |
| return 0; |
| case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME: |
| br_port->bridge->ageing_time = |
| clock_t_to_jiffies(attr->u.ageing_time); |
| return 0; |
| default: |
| return -EOPNOTSUPP; |
| } |
| } |
| |
| static int |
| ice_eswitch_br_event_blocking(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 = switchdev_handle_port_obj_add(dev, ptr, |
| ice_eswitch_br_is_dev_valid, |
| ice_eswitch_br_port_obj_add); |
| break; |
| case SWITCHDEV_PORT_OBJ_DEL: |
| err = switchdev_handle_port_obj_del(dev, ptr, |
| ice_eswitch_br_is_dev_valid, |
| ice_eswitch_br_port_obj_del); |
| break; |
| case SWITCHDEV_PORT_ATTR_SET: |
| err = switchdev_handle_port_attr_set(dev, ptr, |
| ice_eswitch_br_is_dev_valid, |
| ice_eswitch_br_port_obj_attr_set); |
| break; |
| default: |
| err = 0; |
| } |
| |
| return notifier_from_errno(err); |
| } |
| |
| static void |
| ice_eswitch_br_port_deinit(struct ice_esw_br *bridge, |
| struct ice_esw_br_port *br_port) |
| { |
| struct ice_esw_br_fdb_entry *fdb_entry, *tmp; |
| struct ice_vsi *vsi = br_port->vsi; |
| |
| list_for_each_entry_safe(fdb_entry, tmp, &bridge->fdb_list, list) { |
| if (br_port == fdb_entry->br_port) |
| ice_eswitch_br_fdb_entry_delete(bridge, fdb_entry); |
| } |
| |
| if (br_port->type == ICE_ESWITCH_BR_UPLINK_PORT && vsi->back) { |
| vsi->back->br_port = NULL; |
| } else { |
| struct ice_repr *repr = |
| ice_repr_get(vsi->back, br_port->repr_id); |
| |
| if (repr) |
| repr->br_port = NULL; |
| } |
| |
| xa_erase(&bridge->ports, br_port->vsi_idx); |
| ice_eswitch_br_port_vlans_flush(br_port); |
| kfree(br_port); |
| } |
| |
| static struct ice_esw_br_port * |
| ice_eswitch_br_port_init(struct ice_esw_br *bridge) |
| { |
| struct ice_esw_br_port *br_port; |
| |
| br_port = kzalloc(sizeof(*br_port), GFP_KERNEL); |
| if (!br_port) |
| return ERR_PTR(-ENOMEM); |
| |
| xa_init(&br_port->vlans); |
| |
| br_port->bridge = bridge; |
| |
| return br_port; |
| } |
| |
| static int |
| ice_eswitch_br_vf_repr_port_init(struct ice_esw_br *bridge, |
| struct ice_repr *repr) |
| { |
| struct ice_esw_br_port *br_port; |
| int err; |
| |
| br_port = ice_eswitch_br_port_init(bridge); |
| if (IS_ERR(br_port)) |
| return PTR_ERR(br_port); |
| |
| br_port->vsi = repr->src_vsi; |
| br_port->vsi_idx = br_port->vsi->idx; |
| br_port->type = ICE_ESWITCH_BR_VF_REPR_PORT; |
| br_port->repr_id = repr->id; |
| repr->br_port = br_port; |
| |
| err = xa_insert(&bridge->ports, br_port->vsi_idx, br_port, GFP_KERNEL); |
| if (err) { |
| ice_eswitch_br_port_deinit(bridge, br_port); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| ice_eswitch_br_uplink_port_init(struct ice_esw_br *bridge, struct ice_pf *pf) |
| { |
| struct ice_vsi *vsi = pf->eswitch.uplink_vsi; |
| struct ice_esw_br_port *br_port; |
| int err; |
| |
| br_port = ice_eswitch_br_port_init(bridge); |
| if (IS_ERR(br_port)) |
| return PTR_ERR(br_port); |
| |
| br_port->vsi = vsi; |
| br_port->vsi_idx = br_port->vsi->idx; |
| br_port->type = ICE_ESWITCH_BR_UPLINK_PORT; |
| pf->br_port = br_port; |
| |
| err = xa_insert(&bridge->ports, br_port->vsi_idx, br_port, GFP_KERNEL); |
| if (err) { |
| ice_eswitch_br_port_deinit(bridge, br_port); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static void |
| ice_eswitch_br_ports_flush(struct ice_esw_br *bridge) |
| { |
| struct ice_esw_br_port *port; |
| unsigned long i; |
| |
| xa_for_each(&bridge->ports, i, port) |
| ice_eswitch_br_port_deinit(bridge, port); |
| } |
| |
| static void |
| ice_eswitch_br_deinit(struct ice_esw_br_offloads *br_offloads, |
| struct ice_esw_br *bridge) |
| { |
| if (!bridge) |
| return; |
| |
| /* Cleanup all the ports that were added asynchronously |
| * through NETDEV_CHANGEUPPER event. |
| */ |
| ice_eswitch_br_ports_flush(bridge); |
| WARN_ON(!xa_empty(&bridge->ports)); |
| xa_destroy(&bridge->ports); |
| rhashtable_destroy(&bridge->fdb_ht); |
| |
| br_offloads->bridge = NULL; |
| kfree(bridge); |
| } |
| |
| static struct ice_esw_br * |
| ice_eswitch_br_init(struct ice_esw_br_offloads *br_offloads, int ifindex) |
| { |
| struct ice_esw_br *bridge; |
| int err; |
| |
| bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); |
| if (!bridge) |
| return ERR_PTR(-ENOMEM); |
| |
| err = rhashtable_init(&bridge->fdb_ht, &ice_fdb_ht_params); |
| if (err) { |
| kfree(bridge); |
| return ERR_PTR(err); |
| } |
| |
| INIT_LIST_HEAD(&bridge->fdb_list); |
| bridge->br_offloads = br_offloads; |
| bridge->ifindex = ifindex; |
| bridge->ageing_time = clock_t_to_jiffies(BR_DEFAULT_AGEING_TIME); |
| xa_init(&bridge->ports); |
| br_offloads->bridge = bridge; |
| |
| return bridge; |
| } |
| |
| static struct ice_esw_br * |
| ice_eswitch_br_get(struct ice_esw_br_offloads *br_offloads, int ifindex, |
| struct netlink_ext_ack *extack) |
| { |
| struct ice_esw_br *bridge = br_offloads->bridge; |
| |
| if (bridge) { |
| if (bridge->ifindex != ifindex) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Only one bridge is supported per eswitch"); |
| return ERR_PTR(-EOPNOTSUPP); |
| } |
| return bridge; |
| } |
| |
| /* Create the bridge if it doesn't exist yet */ |
| bridge = ice_eswitch_br_init(br_offloads, ifindex); |
| if (IS_ERR(bridge)) |
| NL_SET_ERR_MSG_MOD(extack, "Failed to init the bridge"); |
| |
| return bridge; |
| } |
| |
| static void |
| ice_eswitch_br_verify_deinit(struct ice_esw_br_offloads *br_offloads, |
| struct ice_esw_br *bridge) |
| { |
| /* Remove the bridge if it exists and there are no ports left */ |
| if (!bridge || !xa_empty(&bridge->ports)) |
| return; |
| |
| ice_eswitch_br_deinit(br_offloads, bridge); |
| } |
| |
| static int |
| ice_eswitch_br_port_unlink(struct ice_esw_br_offloads *br_offloads, |
| struct net_device *dev, int ifindex, |
| struct netlink_ext_ack *extack) |
| { |
| struct ice_esw_br_port *br_port = ice_eswitch_br_netdev_to_port(dev); |
| struct ice_esw_br *bridge; |
| |
| if (!br_port) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Port representor is not attached to any bridge"); |
| return -EINVAL; |
| } |
| |
| if (br_port->bridge->ifindex != ifindex) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Port representor is attached to another bridge"); |
| return -EINVAL; |
| } |
| |
| bridge = br_port->bridge; |
| |
| trace_ice_eswitch_br_port_unlink(br_port); |
| ice_eswitch_br_port_deinit(br_port->bridge, br_port); |
| ice_eswitch_br_verify_deinit(br_offloads, bridge); |
| |
| return 0; |
| } |
| |
| static int |
| ice_eswitch_br_port_link(struct ice_esw_br_offloads *br_offloads, |
| struct net_device *dev, int ifindex, |
| struct netlink_ext_ack *extack) |
| { |
| struct ice_esw_br *bridge; |
| int err; |
| |
| if (ice_eswitch_br_netdev_to_port(dev)) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Port is already attached to the bridge"); |
| return -EINVAL; |
| } |
| |
| bridge = ice_eswitch_br_get(br_offloads, ifindex, extack); |
| if (IS_ERR(bridge)) |
| return PTR_ERR(bridge); |
| |
| if (ice_is_port_repr_netdev(dev)) { |
| struct ice_repr *repr = ice_netdev_to_repr(dev); |
| |
| err = ice_eswitch_br_vf_repr_port_init(bridge, repr); |
| trace_ice_eswitch_br_port_link(repr->br_port); |
| } else { |
| struct net_device *ice_dev; |
| struct ice_pf *pf; |
| |
| if (netif_is_lag_master(dev)) |
| ice_dev = ice_eswitch_br_get_uplink_from_lag(dev); |
| else |
| ice_dev = dev; |
| |
| if (!ice_dev) |
| return 0; |
| |
| pf = ice_netdev_to_pf(ice_dev); |
| |
| err = ice_eswitch_br_uplink_port_init(bridge, pf); |
| trace_ice_eswitch_br_port_link(pf->br_port); |
| } |
| if (err) { |
| NL_SET_ERR_MSG_MOD(extack, "Failed to init bridge port"); |
| goto err_port_init; |
| } |
| |
| return 0; |
| |
| err_port_init: |
| ice_eswitch_br_verify_deinit(br_offloads, bridge); |
| return err; |
| } |
| |
| static int |
| ice_eswitch_br_port_changeupper(struct notifier_block *nb, void *ptr) |
| { |
| struct net_device *dev = netdev_notifier_info_to_dev(ptr); |
| struct netdev_notifier_changeupper_info *info = ptr; |
| struct ice_esw_br_offloads *br_offloads; |
| struct netlink_ext_ack *extack; |
| struct net_device *upper; |
| |
| br_offloads = ice_nb_to_br_offloads(nb, netdev_nb); |
| |
| if (!ice_eswitch_br_is_dev_valid(dev)) |
| return 0; |
| |
| upper = info->upper_dev; |
| if (!netif_is_bridge_master(upper)) |
| return 0; |
| |
| extack = netdev_notifier_info_to_extack(&info->info); |
| |
| if (info->linking) |
| return ice_eswitch_br_port_link(br_offloads, dev, |
| upper->ifindex, extack); |
| else |
| return ice_eswitch_br_port_unlink(br_offloads, dev, |
| upper->ifindex, extack); |
| } |
| |
| static int |
| ice_eswitch_br_port_event(struct notifier_block *nb, |
| unsigned long event, void *ptr) |
| { |
| int err = 0; |
| |
| switch (event) { |
| case NETDEV_CHANGEUPPER: |
| err = ice_eswitch_br_port_changeupper(nb, ptr); |
| break; |
| } |
| |
| return notifier_from_errno(err); |
| } |
| |
| static void |
| ice_eswitch_br_offloads_dealloc(struct ice_pf *pf) |
| { |
| struct ice_esw_br_offloads *br_offloads = pf->eswitch.br_offloads; |
| |
| ASSERT_RTNL(); |
| |
| if (!br_offloads) |
| return; |
| |
| ice_eswitch_br_deinit(br_offloads, br_offloads->bridge); |
| |
| pf->eswitch.br_offloads = NULL; |
| kfree(br_offloads); |
| } |
| |
| static struct ice_esw_br_offloads * |
| ice_eswitch_br_offloads_alloc(struct ice_pf *pf) |
| { |
| struct ice_esw_br_offloads *br_offloads; |
| |
| ASSERT_RTNL(); |
| |
| if (pf->eswitch.br_offloads) |
| return ERR_PTR(-EEXIST); |
| |
| br_offloads = kzalloc(sizeof(*br_offloads), GFP_KERNEL); |
| if (!br_offloads) |
| return ERR_PTR(-ENOMEM); |
| |
| pf->eswitch.br_offloads = br_offloads; |
| br_offloads->pf = pf; |
| |
| return br_offloads; |
| } |
| |
| void |
| ice_eswitch_br_offloads_deinit(struct ice_pf *pf) |
| { |
| struct ice_esw_br_offloads *br_offloads; |
| |
| br_offloads = pf->eswitch.br_offloads; |
| if (!br_offloads) |
| return; |
| |
| cancel_delayed_work_sync(&br_offloads->update_work); |
| unregister_netdevice_notifier(&br_offloads->netdev_nb); |
| unregister_switchdev_blocking_notifier(&br_offloads->switchdev_blk); |
| unregister_switchdev_notifier(&br_offloads->switchdev_nb); |
| destroy_workqueue(br_offloads->wq); |
| /* Although notifier block is unregistered just before, |
| * so we don't get any new events, some events might be |
| * already in progress. Hold the rtnl lock and wait for |
| * them to finished. |
| */ |
| rtnl_lock(); |
| ice_eswitch_br_offloads_dealloc(pf); |
| rtnl_unlock(); |
| } |
| |
| static void ice_eswitch_br_update(struct ice_esw_br_offloads *br_offloads) |
| { |
| struct ice_esw_br *bridge = br_offloads->bridge; |
| struct ice_esw_br_fdb_entry *entry, *tmp; |
| |
| if (!bridge) |
| return; |
| |
| rtnl_lock(); |
| list_for_each_entry_safe(entry, tmp, &bridge->fdb_list, list) { |
| if (entry->flags & ICE_ESWITCH_BR_FDB_ADDED_BY_USER) |
| continue; |
| |
| if (time_is_after_eq_jiffies(entry->last_use + |
| bridge->ageing_time)) |
| continue; |
| |
| ice_eswitch_br_fdb_entry_notify_and_cleanup(bridge, entry); |
| } |
| rtnl_unlock(); |
| } |
| |
| static void ice_eswitch_br_update_work(struct work_struct *work) |
| { |
| struct ice_esw_br_offloads *br_offloads; |
| |
| br_offloads = ice_work_to_br_offloads(work); |
| |
| ice_eswitch_br_update(br_offloads); |
| |
| queue_delayed_work(br_offloads->wq, &br_offloads->update_work, |
| ICE_ESW_BRIDGE_UPDATE_INTERVAL); |
| } |
| |
| int |
| ice_eswitch_br_offloads_init(struct ice_pf *pf) |
| { |
| struct ice_esw_br_offloads *br_offloads; |
| struct device *dev = ice_pf_to_dev(pf); |
| int err; |
| |
| rtnl_lock(); |
| br_offloads = ice_eswitch_br_offloads_alloc(pf); |
| rtnl_unlock(); |
| if (IS_ERR(br_offloads)) { |
| dev_err(dev, "Failed to init eswitch bridge\n"); |
| return PTR_ERR(br_offloads); |
| } |
| |
| br_offloads->wq = alloc_ordered_workqueue("ice_bridge_wq", 0); |
| if (!br_offloads->wq) { |
| err = -ENOMEM; |
| dev_err(dev, "Failed to allocate bridge workqueue\n"); |
| goto err_alloc_wq; |
| } |
| |
| br_offloads->switchdev_nb.notifier_call = |
| ice_eswitch_br_switchdev_event; |
| err = register_switchdev_notifier(&br_offloads->switchdev_nb); |
| if (err) { |
| dev_err(dev, |
| "Failed to register switchdev notifier\n"); |
| goto err_reg_switchdev_nb; |
| } |
| |
| br_offloads->switchdev_blk.notifier_call = |
| ice_eswitch_br_event_blocking; |
| err = register_switchdev_blocking_notifier(&br_offloads->switchdev_blk); |
| if (err) { |
| dev_err(dev, |
| "Failed to register bridge blocking switchdev notifier\n"); |
| goto err_reg_switchdev_blk; |
| } |
| |
| br_offloads->netdev_nb.notifier_call = ice_eswitch_br_port_event; |
| err = register_netdevice_notifier(&br_offloads->netdev_nb); |
| if (err) { |
| dev_err(dev, |
| "Failed to register bridge port event notifier\n"); |
| goto err_reg_netdev_nb; |
| } |
| |
| INIT_DELAYED_WORK(&br_offloads->update_work, |
| ice_eswitch_br_update_work); |
| queue_delayed_work(br_offloads->wq, &br_offloads->update_work, |
| ICE_ESW_BRIDGE_UPDATE_INTERVAL); |
| |
| return 0; |
| |
| err_reg_netdev_nb: |
| unregister_switchdev_blocking_notifier(&br_offloads->switchdev_blk); |
| err_reg_switchdev_blk: |
| unregister_switchdev_notifier(&br_offloads->switchdev_nb); |
| err_reg_switchdev_nb: |
| destroy_workqueue(br_offloads->wq); |
| err_alloc_wq: |
| rtnl_lock(); |
| ice_eswitch_br_offloads_dealloc(pf); |
| rtnl_unlock(); |
| |
| return err; |
| } |