Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Jiri Pirko | 13160 | 98.27% | 4 | 26.67% |
Arkadi Sharshevsky | 143 | 1.07% | 3 | 20.00% |
Ido Schimmel | 51 | 0.38% | 3 | 20.00% |
Arnd Bergmann | 14 | 0.10% | 1 | 6.67% |
Kees Cook | 13 | 0.10% | 1 | 6.67% |
Dan Carpenter | 6 | 0.04% | 2 | 13.33% |
Zahari Doychev | 4 | 0.03% | 1 | 6.67% |
Total | 13391 | 15 |
/* * drivers/net/ethernet/rocker/rocker_ofdpa.c - Rocker switch OF-DPA-like * implementation * Copyright (c) 2014 Scott Feldman <sfeldma@gmail.com> * Copyright (c) 2014-2016 Jiri Pirko <jiri@mellanox.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include <linux/kernel.h> #include <linux/types.h> #include <linux/spinlock.h> #include <linux/hashtable.h> #include <linux/crc32.h> #include <linux/netdevice.h> #include <linux/inetdevice.h> #include <linux/if_vlan.h> #include <linux/if_bridge.h> #include <net/neighbour.h> #include <net/switchdev.h> #include <net/ip_fib.h> #include <net/arp.h> #include "rocker.h" #include "rocker_tlv.h" struct ofdpa_flow_tbl_key { u32 priority; enum rocker_of_dpa_table_id tbl_id; union { struct { u32 in_pport; u32 in_pport_mask; enum rocker_of_dpa_table_id goto_tbl; } ig_port; struct { u32 in_pport; __be16 vlan_id; __be16 vlan_id_mask; enum rocker_of_dpa_table_id goto_tbl; bool untagged; __be16 new_vlan_id; } vlan; struct { u32 in_pport; u32 in_pport_mask; __be16 eth_type; u8 eth_dst[ETH_ALEN]; u8 eth_dst_mask[ETH_ALEN]; __be16 vlan_id; __be16 vlan_id_mask; enum rocker_of_dpa_table_id goto_tbl; bool copy_to_cpu; } term_mac; struct { __be16 eth_type; __be32 dst4; __be32 dst4_mask; enum rocker_of_dpa_table_id goto_tbl; u32 group_id; } ucast_routing; struct { u8 eth_dst[ETH_ALEN]; u8 eth_dst_mask[ETH_ALEN]; int has_eth_dst; int has_eth_dst_mask; __be16 vlan_id; u32 tunnel_id; enum rocker_of_dpa_table_id goto_tbl; u32 group_id; bool copy_to_cpu; } bridge; struct { u32 in_pport; u32 in_pport_mask; u8 eth_src[ETH_ALEN]; u8 eth_src_mask[ETH_ALEN]; u8 eth_dst[ETH_ALEN]; u8 eth_dst_mask[ETH_ALEN]; __be16 eth_type; __be16 vlan_id; __be16 vlan_id_mask; u8 ip_proto; u8 ip_proto_mask; u8 ip_tos; u8 ip_tos_mask; u32 group_id; } acl; }; }; struct ofdpa_flow_tbl_entry { struct hlist_node entry; u32 cmd; u64 cookie; struct ofdpa_flow_tbl_key key; size_t key_len; u32 key_crc32; /* key */ struct fib_info *fi; }; struct ofdpa_group_tbl_entry { struct hlist_node entry; u32 cmd; u32 group_id; /* key */ u16 group_count; u32 *group_ids; union { struct { u8 pop_vlan; } l2_interface; struct { u8 eth_src[ETH_ALEN]; u8 eth_dst[ETH_ALEN]; __be16 vlan_id; u32 group_id; } l2_rewrite; struct { u8 eth_src[ETH_ALEN]; u8 eth_dst[ETH_ALEN]; __be16 vlan_id; bool ttl_check; u32 group_id; } l3_unicast; }; }; struct ofdpa_fdb_tbl_entry { struct hlist_node entry; u32 key_crc32; /* key */ bool learned; unsigned long touched; struct ofdpa_fdb_tbl_key { struct ofdpa_port *ofdpa_port; u8 addr[ETH_ALEN]; __be16 vlan_id; } key; }; struct ofdpa_internal_vlan_tbl_entry { struct hlist_node entry; int ifindex; /* key */ u32 ref_count; __be16 vlan_id; }; struct ofdpa_neigh_tbl_entry { struct hlist_node entry; __be32 ip_addr; /* key */ struct net_device *dev; u32 ref_count; u32 index; u8 eth_dst[ETH_ALEN]; bool ttl_check; }; enum { OFDPA_CTRL_LINK_LOCAL_MCAST, OFDPA_CTRL_LOCAL_ARP, OFDPA_CTRL_IPV4_MCAST, OFDPA_CTRL_IPV6_MCAST, OFDPA_CTRL_DFLT_BRIDGING, OFDPA_CTRL_DFLT_OVS, OFDPA_CTRL_MAX, }; #define OFDPA_INTERNAL_VLAN_ID_BASE 0x0f00 #define OFDPA_N_INTERNAL_VLANS 255 #define OFDPA_VLAN_BITMAP_LEN BITS_TO_LONGS(VLAN_N_VID) #define OFDPA_INTERNAL_VLAN_BITMAP_LEN BITS_TO_LONGS(OFDPA_N_INTERNAL_VLANS) #define OFDPA_UNTAGGED_VID 0 struct ofdpa { struct rocker *rocker; DECLARE_HASHTABLE(flow_tbl, 16); spinlock_t flow_tbl_lock; /* for flow tbl accesses */ u64 flow_tbl_next_cookie; DECLARE_HASHTABLE(group_tbl, 16); spinlock_t group_tbl_lock; /* for group tbl accesses */ struct timer_list fdb_cleanup_timer; DECLARE_HASHTABLE(fdb_tbl, 16); spinlock_t fdb_tbl_lock; /* for fdb tbl accesses */ unsigned long internal_vlan_bitmap[OFDPA_INTERNAL_VLAN_BITMAP_LEN]; DECLARE_HASHTABLE(internal_vlan_tbl, 8); spinlock_t internal_vlan_tbl_lock; /* for vlan tbl accesses */ DECLARE_HASHTABLE(neigh_tbl, 16); spinlock_t neigh_tbl_lock; /* for neigh tbl accesses */ u32 neigh_tbl_next_index; unsigned long ageing_time; bool fib_aborted; }; struct ofdpa_port { struct ofdpa *ofdpa; struct rocker_port *rocker_port; struct net_device *dev; u32 pport; struct net_device *bridge_dev; __be16 internal_vlan_id; int stp_state; u32 brport_flags; unsigned long ageing_time; bool ctrls[OFDPA_CTRL_MAX]; unsigned long vlan_bitmap[OFDPA_VLAN_BITMAP_LEN]; }; static const u8 zero_mac[ETH_ALEN] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static const u8 ff_mac[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; static const u8 ll_mac[ETH_ALEN] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 }; static const u8 ll_mask[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0 }; static const u8 mcast_mac[ETH_ALEN] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }; static const u8 ipv4_mcast[ETH_ALEN] = { 0x01, 0x00, 0x5e, 0x00, 0x00, 0x00 }; static const u8 ipv4_mask[ETH_ALEN] = { 0xff, 0xff, 0xff, 0x80, 0x00, 0x00 }; static const u8 ipv6_mcast[ETH_ALEN] = { 0x33, 0x33, 0x00, 0x00, 0x00, 0x00 }; static const u8 ipv6_mask[ETH_ALEN] = { 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 }; /* Rocker priority levels for flow table entries. Higher * priority match takes precedence over lower priority match. */ enum { OFDPA_PRIORITY_UNKNOWN = 0, OFDPA_PRIORITY_IG_PORT = 1, OFDPA_PRIORITY_VLAN = 1, OFDPA_PRIORITY_TERM_MAC_UCAST = 0, OFDPA_PRIORITY_TERM_MAC_MCAST = 1, OFDPA_PRIORITY_BRIDGING_VLAN_DFLT_EXACT = 1, OFDPA_PRIORITY_BRIDGING_VLAN_DFLT_WILD = 2, OFDPA_PRIORITY_BRIDGING_VLAN = 3, OFDPA_PRIORITY_BRIDGING_TENANT_DFLT_EXACT = 1, OFDPA_PRIORITY_BRIDGING_TENANT_DFLT_WILD = 2, OFDPA_PRIORITY_BRIDGING_TENANT = 3, OFDPA_PRIORITY_ACL_CTRL = 3, OFDPA_PRIORITY_ACL_NORMAL = 2, OFDPA_PRIORITY_ACL_DFLT = 1, }; static bool ofdpa_vlan_id_is_internal(__be16 vlan_id) { u16 start = OFDPA_INTERNAL_VLAN_ID_BASE; u16 end = 0xffe; u16 _vlan_id = ntohs(vlan_id); return (_vlan_id >= start && _vlan_id <= end); } static __be16 ofdpa_port_vid_to_vlan(const struct ofdpa_port *ofdpa_port, u16 vid, bool *pop_vlan) { __be16 vlan_id; if (pop_vlan) *pop_vlan = false; vlan_id = htons(vid); if (!vlan_id) { vlan_id = ofdpa_port->internal_vlan_id; if (pop_vlan) *pop_vlan = true; } return vlan_id; } static u16 ofdpa_port_vlan_to_vid(const struct ofdpa_port *ofdpa_port, __be16 vlan_id) { if (ofdpa_vlan_id_is_internal(vlan_id)) return 0; return ntohs(vlan_id); } static bool ofdpa_port_is_slave(const struct ofdpa_port *ofdpa_port, const char *kind) { return ofdpa_port->bridge_dev && !strcmp(ofdpa_port->bridge_dev->rtnl_link_ops->kind, kind); } static bool ofdpa_port_is_bridged(const struct ofdpa_port *ofdpa_port) { return ofdpa_port_is_slave(ofdpa_port, "bridge"); } static bool ofdpa_port_is_ovsed(const struct ofdpa_port *ofdpa_port) { return ofdpa_port_is_slave(ofdpa_port, "openvswitch"); } #define OFDPA_OP_FLAG_REMOVE BIT(0) #define OFDPA_OP_FLAG_NOWAIT BIT(1) #define OFDPA_OP_FLAG_LEARNED BIT(2) #define OFDPA_OP_FLAG_REFRESH BIT(3) static bool ofdpa_flags_nowait(int flags) { return flags & OFDPA_OP_FLAG_NOWAIT; } /************************************************************* * Flow, group, FDB, internal VLAN and neigh command prepares *************************************************************/ static int ofdpa_cmd_flow_tbl_add_ig_port(struct rocker_desc_info *desc_info, const struct ofdpa_flow_tbl_entry *entry) { if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_IN_PPORT, entry->key.ig_port.in_pport)) return -EMSGSIZE; if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_IN_PPORT_MASK, entry->key.ig_port.in_pport_mask)) return -EMSGSIZE; if (rocker_tlv_put_u16(desc_info, ROCKER_TLV_OF_DPA_GOTO_TABLE_ID, entry->key.ig_port.goto_tbl)) return -EMSGSIZE; return 0; } static int ofdpa_cmd_flow_tbl_add_vlan(struct rocker_desc_info *desc_info, const struct ofdpa_flow_tbl_entry *entry) { if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_IN_PPORT, entry->key.vlan.in_pport)) return -EMSGSIZE; if (rocker_tlv_put_be16(desc_info, ROCKER_TLV_OF_DPA_VLAN_ID, entry->key.vlan.vlan_id)) return -EMSGSIZE; if (rocker_tlv_put_be16(desc_info, ROCKER_TLV_OF_DPA_VLAN_ID_MASK, entry->key.vlan.vlan_id_mask)) return -EMSGSIZE; if (rocker_tlv_put_u16(desc_info, ROCKER_TLV_OF_DPA_GOTO_TABLE_ID, entry->key.vlan.goto_tbl)) return -EMSGSIZE; if (entry->key.vlan.untagged && rocker_tlv_put_be16(desc_info, ROCKER_TLV_OF_DPA_NEW_VLAN_ID, entry->key.vlan.new_vlan_id)) return -EMSGSIZE; return 0; } static int ofdpa_cmd_flow_tbl_add_term_mac(struct rocker_desc_info *desc_info, const struct ofdpa_flow_tbl_entry *entry) { if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_IN_PPORT, entry->key.term_mac.in_pport)) return -EMSGSIZE; if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_IN_PPORT_MASK, entry->key.term_mac.in_pport_mask)) return -EMSGSIZE; if (rocker_tlv_put_be16(desc_info, ROCKER_TLV_OF_DPA_ETHERTYPE, entry->key.term_mac.eth_type)) return -EMSGSIZE; if (rocker_tlv_put(desc_info, ROCKER_TLV_OF_DPA_DST_MAC, ETH_ALEN, entry->key.term_mac.eth_dst)) return -EMSGSIZE; if (rocker_tlv_put(desc_info, ROCKER_TLV_OF_DPA_DST_MAC_MASK, ETH_ALEN, entry->key.term_mac.eth_dst_mask)) return -EMSGSIZE; if (rocker_tlv_put_be16(desc_info, ROCKER_TLV_OF_DPA_VLAN_ID, entry->key.term_mac.vlan_id)) return -EMSGSIZE; if (rocker_tlv_put_be16(desc_info, ROCKER_TLV_OF_DPA_VLAN_ID_MASK, entry->key.term_mac.vlan_id_mask)) return -EMSGSIZE; if (rocker_tlv_put_u16(desc_info, ROCKER_TLV_OF_DPA_GOTO_TABLE_ID, entry->key.term_mac.goto_tbl)) return -EMSGSIZE; if (entry->key.term_mac.copy_to_cpu && rocker_tlv_put_u8(desc_info, ROCKER_TLV_OF_DPA_COPY_CPU_ACTION, entry->key.term_mac.copy_to_cpu)) return -EMSGSIZE; return 0; } static int ofdpa_cmd_flow_tbl_add_ucast_routing(struct rocker_desc_info *desc_info, const struct ofdpa_flow_tbl_entry *entry) { if (rocker_tlv_put_be16(desc_info, ROCKER_TLV_OF_DPA_ETHERTYPE, entry->key.ucast_routing.eth_type)) return -EMSGSIZE; if (rocker_tlv_put_be32(desc_info, ROCKER_TLV_OF_DPA_DST_IP, entry->key.ucast_routing.dst4)) return -EMSGSIZE; if (rocker_tlv_put_be32(desc_info, ROCKER_TLV_OF_DPA_DST_IP_MASK, entry->key.ucast_routing.dst4_mask)) return -EMSGSIZE; if (rocker_tlv_put_u16(desc_info, ROCKER_TLV_OF_DPA_GOTO_TABLE_ID, entry->key.ucast_routing.goto_tbl)) return -EMSGSIZE; if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_GROUP_ID, entry->key.ucast_routing.group_id)) return -EMSGSIZE; return 0; } static int ofdpa_cmd_flow_tbl_add_bridge(struct rocker_desc_info *desc_info, const struct ofdpa_flow_tbl_entry *entry) { if (entry->key.bridge.has_eth_dst && rocker_tlv_put(desc_info, ROCKER_TLV_OF_DPA_DST_MAC, ETH_ALEN, entry->key.bridge.eth_dst)) return -EMSGSIZE; if (entry->key.bridge.has_eth_dst_mask && rocker_tlv_put(desc_info, ROCKER_TLV_OF_DPA_DST_MAC_MASK, ETH_ALEN, entry->key.bridge.eth_dst_mask)) return -EMSGSIZE; if (entry->key.bridge.vlan_id && rocker_tlv_put_be16(desc_info, ROCKER_TLV_OF_DPA_VLAN_ID, entry->key.bridge.vlan_id)) return -EMSGSIZE; if (entry->key.bridge.tunnel_id && rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_TUNNEL_ID, entry->key.bridge.tunnel_id)) return -EMSGSIZE; if (rocker_tlv_put_u16(desc_info, ROCKER_TLV_OF_DPA_GOTO_TABLE_ID, entry->key.bridge.goto_tbl)) return -EMSGSIZE; if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_GROUP_ID, entry->key.bridge.group_id)) return -EMSGSIZE; if (entry->key.bridge.copy_to_cpu && rocker_tlv_put_u8(desc_info, ROCKER_TLV_OF_DPA_COPY_CPU_ACTION, entry->key.bridge.copy_to_cpu)) return -EMSGSIZE; return 0; } static int ofdpa_cmd_flow_tbl_add_acl(struct rocker_desc_info *desc_info, const struct ofdpa_flow_tbl_entry *entry) { if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_IN_PPORT, entry->key.acl.in_pport)) return -EMSGSIZE; if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_IN_PPORT_MASK, entry->key.acl.in_pport_mask)) return -EMSGSIZE; if (rocker_tlv_put(desc_info, ROCKER_TLV_OF_DPA_SRC_MAC, ETH_ALEN, entry->key.acl.eth_src)) return -EMSGSIZE; if (rocker_tlv_put(desc_info, ROCKER_TLV_OF_DPA_SRC_MAC_MASK, ETH_ALEN, entry->key.acl.eth_src_mask)) return -EMSGSIZE; if (rocker_tlv_put(desc_info, ROCKER_TLV_OF_DPA_DST_MAC, ETH_ALEN, entry->key.acl.eth_dst)) return -EMSGSIZE; if (rocker_tlv_put(desc_info, ROCKER_TLV_OF_DPA_DST_MAC_MASK, ETH_ALEN, entry->key.acl.eth_dst_mask)) return -EMSGSIZE; if (rocker_tlv_put_be16(desc_info, ROCKER_TLV_OF_DPA_ETHERTYPE, entry->key.acl.eth_type)) return -EMSGSIZE; if (rocker_tlv_put_be16(desc_info, ROCKER_TLV_OF_DPA_VLAN_ID, entry->key.acl.vlan_id)) return -EMSGSIZE; if (rocker_tlv_put_be16(desc_info, ROCKER_TLV_OF_DPA_VLAN_ID_MASK, entry->key.acl.vlan_id_mask)) return -EMSGSIZE; switch (ntohs(entry->key.acl.eth_type)) { case ETH_P_IP: case ETH_P_IPV6: if (rocker_tlv_put_u8(desc_info, ROCKER_TLV_OF_DPA_IP_PROTO, entry->key.acl.ip_proto)) return -EMSGSIZE; if (rocker_tlv_put_u8(desc_info, ROCKER_TLV_OF_DPA_IP_PROTO_MASK, entry->key.acl.ip_proto_mask)) return -EMSGSIZE; if (rocker_tlv_put_u8(desc_info, ROCKER_TLV_OF_DPA_IP_DSCP, entry->key.acl.ip_tos & 0x3f)) return -EMSGSIZE; if (rocker_tlv_put_u8(desc_info, ROCKER_TLV_OF_DPA_IP_DSCP_MASK, entry->key.acl.ip_tos_mask & 0x3f)) return -EMSGSIZE; if (rocker_tlv_put_u8(desc_info, ROCKER_TLV_OF_DPA_IP_ECN, (entry->key.acl.ip_tos & 0xc0) >> 6)) return -EMSGSIZE; if (rocker_tlv_put_u8(desc_info, ROCKER_TLV_OF_DPA_IP_ECN_MASK, (entry->key.acl.ip_tos_mask & 0xc0) >> 6)) return -EMSGSIZE; break; } if (entry->key.acl.group_id != ROCKER_GROUP_NONE && rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_GROUP_ID, entry->key.acl.group_id)) return -EMSGSIZE; return 0; } static int ofdpa_cmd_flow_tbl_add(const struct rocker_port *rocker_port, struct rocker_desc_info *desc_info, void *priv) { const struct ofdpa_flow_tbl_entry *entry = priv; struct rocker_tlv *cmd_info; int err = 0; if (rocker_tlv_put_u16(desc_info, ROCKER_TLV_CMD_TYPE, entry->cmd)) return -EMSGSIZE; cmd_info = rocker_tlv_nest_start(desc_info, ROCKER_TLV_CMD_INFO); if (!cmd_info) return -EMSGSIZE; if (rocker_tlv_put_u16(desc_info, ROCKER_TLV_OF_DPA_TABLE_ID, entry->key.tbl_id)) return -EMSGSIZE; if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_PRIORITY, entry->key.priority)) return -EMSGSIZE; if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_HARDTIME, 0)) return -EMSGSIZE; if (rocker_tlv_put_u64(desc_info, ROCKER_TLV_OF_DPA_COOKIE, entry->cookie)) return -EMSGSIZE; switch (entry->key.tbl_id) { case ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT: err = ofdpa_cmd_flow_tbl_add_ig_port(desc_info, entry); break; case ROCKER_OF_DPA_TABLE_ID_VLAN: err = ofdpa_cmd_flow_tbl_add_vlan(desc_info, entry); break; case ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC: err = ofdpa_cmd_flow_tbl_add_term_mac(desc_info, entry); break; case ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING: err = ofdpa_cmd_flow_tbl_add_ucast_routing(desc_info, entry); break; case ROCKER_OF_DPA_TABLE_ID_BRIDGING: err = ofdpa_cmd_flow_tbl_add_bridge(desc_info, entry); break; case ROCKER_OF_DPA_TABLE_ID_ACL_POLICY: err = ofdpa_cmd_flow_tbl_add_acl(desc_info, entry); break; default: err = -ENOTSUPP; break; } if (err) return err; rocker_tlv_nest_end(desc_info, cmd_info); return 0; } static int ofdpa_cmd_flow_tbl_del(const struct rocker_port *rocker_port, struct rocker_desc_info *desc_info, void *priv) { const struct ofdpa_flow_tbl_entry *entry = priv; struct rocker_tlv *cmd_info; if (rocker_tlv_put_u16(desc_info, ROCKER_TLV_CMD_TYPE, entry->cmd)) return -EMSGSIZE; cmd_info = rocker_tlv_nest_start(desc_info, ROCKER_TLV_CMD_INFO); if (!cmd_info) return -EMSGSIZE; if (rocker_tlv_put_u64(desc_info, ROCKER_TLV_OF_DPA_COOKIE, entry->cookie)) return -EMSGSIZE; rocker_tlv_nest_end(desc_info, cmd_info); return 0; } static int ofdpa_cmd_group_tbl_add_l2_interface(struct rocker_desc_info *desc_info, struct ofdpa_group_tbl_entry *entry) { if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_OUT_PPORT, ROCKER_GROUP_PORT_GET(entry->group_id))) return -EMSGSIZE; if (rocker_tlv_put_u8(desc_info, ROCKER_TLV_OF_DPA_POP_VLAN, entry->l2_interface.pop_vlan)) return -EMSGSIZE; return 0; } static int ofdpa_cmd_group_tbl_add_l2_rewrite(struct rocker_desc_info *desc_info, const struct ofdpa_group_tbl_entry *entry) { if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_GROUP_ID_LOWER, entry->l2_rewrite.group_id)) return -EMSGSIZE; if (!is_zero_ether_addr(entry->l2_rewrite.eth_src) && rocker_tlv_put(desc_info, ROCKER_TLV_OF_DPA_SRC_MAC, ETH_ALEN, entry->l2_rewrite.eth_src)) return -EMSGSIZE; if (!is_zero_ether_addr(entry->l2_rewrite.eth_dst) && rocker_tlv_put(desc_info, ROCKER_TLV_OF_DPA_DST_MAC, ETH_ALEN, entry->l2_rewrite.eth_dst)) return -EMSGSIZE; if (entry->l2_rewrite.vlan_id && rocker_tlv_put_be16(desc_info, ROCKER_TLV_OF_DPA_VLAN_ID, entry->l2_rewrite.vlan_id)) return -EMSGSIZE; return 0; } static int ofdpa_cmd_group_tbl_add_group_ids(struct rocker_desc_info *desc_info, const struct ofdpa_group_tbl_entry *entry) { int i; struct rocker_tlv *group_ids; if (rocker_tlv_put_u16(desc_info, ROCKER_TLV_OF_DPA_GROUP_COUNT, entry->group_count)) return -EMSGSIZE; group_ids = rocker_tlv_nest_start(desc_info, ROCKER_TLV_OF_DPA_GROUP_IDS); if (!group_ids) return -EMSGSIZE; for (i = 0; i < entry->group_count; i++) /* Note TLV array is 1-based */ if (rocker_tlv_put_u32(desc_info, i + 1, entry->group_ids[i])) return -EMSGSIZE; rocker_tlv_nest_end(desc_info, group_ids); return 0; } static int ofdpa_cmd_group_tbl_add_l3_unicast(struct rocker_desc_info *desc_info, const struct ofdpa_group_tbl_entry *entry) { if (!is_zero_ether_addr(entry->l3_unicast.eth_src) && rocker_tlv_put(desc_info, ROCKER_TLV_OF_DPA_SRC_MAC, ETH_ALEN, entry->l3_unicast.eth_src)) return -EMSGSIZE; if (!is_zero_ether_addr(entry->l3_unicast.eth_dst) && rocker_tlv_put(desc_info, ROCKER_TLV_OF_DPA_DST_MAC, ETH_ALEN, entry->l3_unicast.eth_dst)) return -EMSGSIZE; if (entry->l3_unicast.vlan_id && rocker_tlv_put_be16(desc_info, ROCKER_TLV_OF_DPA_VLAN_ID, entry->l3_unicast.vlan_id)) return -EMSGSIZE; if (rocker_tlv_put_u8(desc_info, ROCKER_TLV_OF_DPA_TTL_CHECK, entry->l3_unicast.ttl_check)) return -EMSGSIZE; if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_GROUP_ID_LOWER, entry->l3_unicast.group_id)) return -EMSGSIZE; return 0; } static int ofdpa_cmd_group_tbl_add(const struct rocker_port *rocker_port, struct rocker_desc_info *desc_info, void *priv) { struct ofdpa_group_tbl_entry *entry = priv; struct rocker_tlv *cmd_info; int err = 0; if (rocker_tlv_put_u16(desc_info, ROCKER_TLV_CMD_TYPE, entry->cmd)) return -EMSGSIZE; cmd_info = rocker_tlv_nest_start(desc_info, ROCKER_TLV_CMD_INFO); if (!cmd_info) return -EMSGSIZE; if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_GROUP_ID, entry->group_id)) return -EMSGSIZE; switch (ROCKER_GROUP_TYPE_GET(entry->group_id)) { case ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE: err = ofdpa_cmd_group_tbl_add_l2_interface(desc_info, entry); break; case ROCKER_OF_DPA_GROUP_TYPE_L2_REWRITE: err = ofdpa_cmd_group_tbl_add_l2_rewrite(desc_info, entry); break; case ROCKER_OF_DPA_GROUP_TYPE_L2_FLOOD: case ROCKER_OF_DPA_GROUP_TYPE_L2_MCAST: err = ofdpa_cmd_group_tbl_add_group_ids(desc_info, entry); break; case ROCKER_OF_DPA_GROUP_TYPE_L3_UCAST: err = ofdpa_cmd_group_tbl_add_l3_unicast(desc_info, entry); break; default: err = -ENOTSUPP; break; } if (err) return err; rocker_tlv_nest_end(desc_info, cmd_info); return 0; } static int ofdpa_cmd_group_tbl_del(const struct rocker_port *rocker_port, struct rocker_desc_info *desc_info, void *priv) { const struct ofdpa_group_tbl_entry *entry = priv; struct rocker_tlv *cmd_info; if (rocker_tlv_put_u16(desc_info, ROCKER_TLV_CMD_TYPE, entry->cmd)) return -EMSGSIZE; cmd_info = rocker_tlv_nest_start(desc_info, ROCKER_TLV_CMD_INFO); if (!cmd_info) return -EMSGSIZE; if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_GROUP_ID, entry->group_id)) return -EMSGSIZE; rocker_tlv_nest_end(desc_info, cmd_info); return 0; } /*************************************************** * Flow, group, FDB, internal VLAN and neigh tables ***************************************************/ static struct ofdpa_flow_tbl_entry * ofdpa_flow_tbl_find(const struct ofdpa *ofdpa, const struct ofdpa_flow_tbl_entry *match) { struct ofdpa_flow_tbl_entry *found; size_t key_len = match->key_len ? match->key_len : sizeof(found->key); hash_for_each_possible(ofdpa->flow_tbl, found, entry, match->key_crc32) { if (memcmp(&found->key, &match->key, key_len) == 0) return found; } return NULL; } static int ofdpa_flow_tbl_add(struct ofdpa_port *ofdpa_port, int flags, struct ofdpa_flow_tbl_entry *match) { struct ofdpa *ofdpa = ofdpa_port->ofdpa; struct ofdpa_flow_tbl_entry *found; size_t key_len = match->key_len ? match->key_len : sizeof(found->key); unsigned long lock_flags; match->key_crc32 = crc32(~0, &match->key, key_len); spin_lock_irqsave(&ofdpa->flow_tbl_lock, lock_flags); found = ofdpa_flow_tbl_find(ofdpa, match); if (found) { match->cookie = found->cookie; hash_del(&found->entry); kfree(found); found = match; found->cmd = ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_MOD; } else { found = match; found->cookie = ofdpa->flow_tbl_next_cookie++; found->cmd = ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_ADD; } hash_add(ofdpa->flow_tbl, &found->entry, found->key_crc32); spin_unlock_irqrestore(&ofdpa->flow_tbl_lock, lock_flags); return rocker_cmd_exec(ofdpa_port->rocker_port, ofdpa_flags_nowait(flags), ofdpa_cmd_flow_tbl_add, found, NULL, NULL); } static int ofdpa_flow_tbl_del(struct ofdpa_port *ofdpa_port, int flags, struct ofdpa_flow_tbl_entry *match) { struct ofdpa *ofdpa = ofdpa_port->ofdpa; struct ofdpa_flow_tbl_entry *found; size_t key_len = match->key_len ? match->key_len : sizeof(found->key); unsigned long lock_flags; int err = 0; match->key_crc32 = crc32(~0, &match->key, key_len); spin_lock_irqsave(&ofdpa->flow_tbl_lock, lock_flags); found = ofdpa_flow_tbl_find(ofdpa, match); if (found) { hash_del(&found->entry); found->cmd = ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_DEL; } spin_unlock_irqrestore(&ofdpa->flow_tbl_lock, lock_flags); kfree(match); if (found) { err = rocker_cmd_exec(ofdpa_port->rocker_port, ofdpa_flags_nowait(flags), ofdpa_cmd_flow_tbl_del, found, NULL, NULL); kfree(found); } return err; } static int ofdpa_flow_tbl_do(struct ofdpa_port *ofdpa_port, int flags, struct ofdpa_flow_tbl_entry *entry) { if (flags & OFDPA_OP_FLAG_REMOVE) return ofdpa_flow_tbl_del(ofdpa_port, flags, entry); else return ofdpa_flow_tbl_add(ofdpa_port, flags, entry); } static int ofdpa_flow_tbl_ig_port(struct ofdpa_port *ofdpa_port, int flags, u32 in_pport, u32 in_pport_mask, enum rocker_of_dpa_table_id goto_tbl) { struct ofdpa_flow_tbl_entry *entry; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return -ENOMEM; entry->key.priority = OFDPA_PRIORITY_IG_PORT; entry->key.tbl_id = ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT; entry->key.ig_port.in_pport = in_pport; entry->key.ig_port.in_pport_mask = in_pport_mask; entry->key.ig_port.goto_tbl = goto_tbl; return ofdpa_flow_tbl_do(ofdpa_port, flags, entry); } static int ofdpa_flow_tbl_vlan(struct ofdpa_port *ofdpa_port, int flags, u32 in_pport, __be16 vlan_id, __be16 vlan_id_mask, enum rocker_of_dpa_table_id goto_tbl, bool untagged, __be16 new_vlan_id) { struct ofdpa_flow_tbl_entry *entry; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return -ENOMEM; entry->key.priority = OFDPA_PRIORITY_VLAN; entry->key.tbl_id = ROCKER_OF_DPA_TABLE_ID_VLAN; entry->key.vlan.in_pport = in_pport; entry->key.vlan.vlan_id = vlan_id; entry->key.vlan.vlan_id_mask = vlan_id_mask; entry->key.vlan.goto_tbl = goto_tbl; entry->key.vlan.untagged = untagged; entry->key.vlan.new_vlan_id = new_vlan_id; return ofdpa_flow_tbl_do(ofdpa_port, flags, entry); } static int ofdpa_flow_tbl_term_mac(struct ofdpa_port *ofdpa_port, u32 in_pport, u32 in_pport_mask, __be16 eth_type, const u8 *eth_dst, const u8 *eth_dst_mask, __be16 vlan_id, __be16 vlan_id_mask, bool copy_to_cpu, int flags) { struct ofdpa_flow_tbl_entry *entry; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return -ENOMEM; if (is_multicast_ether_addr(eth_dst)) { entry->key.priority = OFDPA_PRIORITY_TERM_MAC_MCAST; entry->key.term_mac.goto_tbl = ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING; } else { entry->key.priority = OFDPA_PRIORITY_TERM_MAC_UCAST; entry->key.term_mac.goto_tbl = ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING; } entry->key.tbl_id = ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC; entry->key.term_mac.in_pport = in_pport; entry->key.term_mac.in_pport_mask = in_pport_mask; entry->key.term_mac.eth_type = eth_type; ether_addr_copy(entry->key.term_mac.eth_dst, eth_dst); ether_addr_copy(entry->key.term_mac.eth_dst_mask, eth_dst_mask); entry->key.term_mac.vlan_id = vlan_id; entry->key.term_mac.vlan_id_mask = vlan_id_mask; entry->key.term_mac.copy_to_cpu = copy_to_cpu; return ofdpa_flow_tbl_do(ofdpa_port, flags, entry); } static int ofdpa_flow_tbl_bridge(struct ofdpa_port *ofdpa_port, int flags, const u8 *eth_dst, const u8 *eth_dst_mask, __be16 vlan_id, u32 tunnel_id, enum rocker_of_dpa_table_id goto_tbl, u32 group_id, bool copy_to_cpu) { struct ofdpa_flow_tbl_entry *entry; u32 priority; bool vlan_bridging = !!vlan_id; bool dflt = !eth_dst || (eth_dst && eth_dst_mask); bool wild = false; entry = kzalloc(sizeof(*entry), GFP_ATOMIC); if (!entry) return -ENOMEM; entry->key.tbl_id = ROCKER_OF_DPA_TABLE_ID_BRIDGING; if (eth_dst) { entry->key.bridge.has_eth_dst = 1; ether_addr_copy(entry->key.bridge.eth_dst, eth_dst); } if (eth_dst_mask) { entry->key.bridge.has_eth_dst_mask = 1; ether_addr_copy(entry->key.bridge.eth_dst_mask, eth_dst_mask); if (!ether_addr_equal(eth_dst_mask, ff_mac)) wild = true; } priority = OFDPA_PRIORITY_UNKNOWN; if (vlan_bridging && dflt && wild) priority = OFDPA_PRIORITY_BRIDGING_VLAN_DFLT_WILD; else if (vlan_bridging && dflt && !wild) priority = OFDPA_PRIORITY_BRIDGING_VLAN_DFLT_EXACT; else if (vlan_bridging && !dflt) priority = OFDPA_PRIORITY_BRIDGING_VLAN; else if (!vlan_bridging && dflt && wild) priority = OFDPA_PRIORITY_BRIDGING_TENANT_DFLT_WILD; else if (!vlan_bridging && dflt && !wild) priority = OFDPA_PRIORITY_BRIDGING_TENANT_DFLT_EXACT; else if (!vlan_bridging && !dflt) priority = OFDPA_PRIORITY_BRIDGING_TENANT; entry->key.priority = priority; entry->key.bridge.vlan_id = vlan_id; entry->key.bridge.tunnel_id = tunnel_id; entry->key.bridge.goto_tbl = goto_tbl; entry->key.bridge.group_id = group_id; entry->key.bridge.copy_to_cpu = copy_to_cpu; return ofdpa_flow_tbl_do(ofdpa_port, flags, entry); } static int ofdpa_flow_tbl_ucast4_routing(struct ofdpa_port *ofdpa_port, __be16 eth_type, __be32 dst, __be32 dst_mask, u32 priority, enum rocker_of_dpa_table_id goto_tbl, u32 group_id, struct fib_info *fi, int flags) { struct ofdpa_flow_tbl_entry *entry; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return -ENOMEM; entry->key.tbl_id = ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING; entry->key.priority = priority; entry->key.ucast_routing.eth_type = eth_type; entry->key.ucast_routing.dst4 = dst; entry->key.ucast_routing.dst4_mask = dst_mask; entry->key.ucast_routing.goto_tbl = goto_tbl; entry->key.ucast_routing.group_id = group_id; entry->key_len = offsetof(struct ofdpa_flow_tbl_key, ucast_routing.group_id); entry->fi = fi; return ofdpa_flow_tbl_do(ofdpa_port, flags, entry); } static int ofdpa_flow_tbl_acl(struct ofdpa_port *ofdpa_port, int flags, u32 in_pport, u32 in_pport_mask, const u8 *eth_src, const u8 *eth_src_mask, const u8 *eth_dst, const u8 *eth_dst_mask, __be16 eth_type, __be16 vlan_id, __be16 vlan_id_mask, u8 ip_proto, u8 ip_proto_mask, u8 ip_tos, u8 ip_tos_mask, u32 group_id) { u32 priority; struct ofdpa_flow_tbl_entry *entry; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return -ENOMEM; priority = OFDPA_PRIORITY_ACL_NORMAL; if (eth_dst && eth_dst_mask) { if (ether_addr_equal(eth_dst_mask, mcast_mac)) priority = OFDPA_PRIORITY_ACL_DFLT; else if (is_link_local_ether_addr(eth_dst)) priority = OFDPA_PRIORITY_ACL_CTRL; } entry->key.priority = priority; entry->key.tbl_id = ROCKER_OF_DPA_TABLE_ID_ACL_POLICY; entry->key.acl.in_pport = in_pport; entry->key.acl.in_pport_mask = in_pport_mask; if (eth_src) ether_addr_copy(entry->key.acl.eth_src, eth_src); if (eth_src_mask) ether_addr_copy(entry->key.acl.eth_src_mask, eth_src_mask); if (eth_dst) ether_addr_copy(entry->key.acl.eth_dst, eth_dst); if (eth_dst_mask) ether_addr_copy(entry->key.acl.eth_dst_mask, eth_dst_mask); entry->key.acl.eth_type = eth_type; entry->key.acl.vlan_id = vlan_id; entry->key.acl.vlan_id_mask = vlan_id_mask; entry->key.acl.ip_proto = ip_proto; entry->key.acl.ip_proto_mask = ip_proto_mask; entry->key.acl.ip_tos = ip_tos; entry->key.acl.ip_tos_mask = ip_tos_mask; entry->key.acl.group_id = group_id; return ofdpa_flow_tbl_do(ofdpa_port, flags, entry); } static struct ofdpa_group_tbl_entry * ofdpa_group_tbl_find(const struct ofdpa *ofdpa, const struct ofdpa_group_tbl_entry *match) { struct ofdpa_group_tbl_entry *found; hash_for_each_possible(ofdpa->group_tbl, found, entry, match->group_id) { if (found->group_id == match->group_id) return found; } return NULL; } static void ofdpa_group_tbl_entry_free(struct ofdpa_group_tbl_entry *entry) { switch (ROCKER_GROUP_TYPE_GET(entry->group_id)) { case ROCKER_OF_DPA_GROUP_TYPE_L2_FLOOD: case ROCKER_OF_DPA_GROUP_TYPE_L2_MCAST: kfree(entry->group_ids); break; default: break; } kfree(entry); } static int ofdpa_group_tbl_add(struct ofdpa_port *ofdpa_port, int flags, struct ofdpa_group_tbl_entry *match) { struct ofdpa *ofdpa = ofdpa_port->ofdpa; struct ofdpa_group_tbl_entry *found; unsigned long lock_flags; spin_lock_irqsave(&ofdpa->group_tbl_lock, lock_flags); found = ofdpa_group_tbl_find(ofdpa, match); if (found) { hash_del(&found->entry); ofdpa_group_tbl_entry_free(found); found = match; found->cmd = ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_MOD; } else { found = match; found->cmd = ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_ADD; } hash_add(ofdpa->group_tbl, &found->entry, found->group_id); spin_unlock_irqrestore(&ofdpa->group_tbl_lock, lock_flags); return rocker_cmd_exec(ofdpa_port->rocker_port, ofdpa_flags_nowait(flags), ofdpa_cmd_group_tbl_add, found, NULL, NULL); } static int ofdpa_group_tbl_del(struct ofdpa_port *ofdpa_port, int flags, struct ofdpa_group_tbl_entry *match) { struct ofdpa *ofdpa = ofdpa_port->ofdpa; struct ofdpa_group_tbl_entry *found; unsigned long lock_flags; int err = 0; spin_lock_irqsave(&ofdpa->group_tbl_lock, lock_flags); found = ofdpa_group_tbl_find(ofdpa, match); if (found) { hash_del(&found->entry); found->cmd = ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_DEL; } spin_unlock_irqrestore(&ofdpa->group_tbl_lock, lock_flags); ofdpa_group_tbl_entry_free(match); if (found) { err = rocker_cmd_exec(ofdpa_port->rocker_port, ofdpa_flags_nowait(flags), ofdpa_cmd_group_tbl_del, found, NULL, NULL); ofdpa_group_tbl_entry_free(found); } return err; } static int ofdpa_group_tbl_do(struct ofdpa_port *ofdpa_port, int flags, struct ofdpa_group_tbl_entry *entry) { if (flags & OFDPA_OP_FLAG_REMOVE) return ofdpa_group_tbl_del(ofdpa_port, flags, entry); else return ofdpa_group_tbl_add(ofdpa_port, flags, entry); } static int ofdpa_group_l2_interface(struct ofdpa_port *ofdpa_port, int flags, __be16 vlan_id, u32 out_pport, int pop_vlan) { struct ofdpa_group_tbl_entry *entry; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return -ENOMEM; entry->group_id = ROCKER_GROUP_L2_INTERFACE(vlan_id, out_pport); entry->l2_interface.pop_vlan = pop_vlan; return ofdpa_group_tbl_do(ofdpa_port, flags, entry); } static int ofdpa_group_l2_fan_out(struct ofdpa_port *ofdpa_port, int flags, u8 group_count, const u32 *group_ids, u32 group_id) { struct ofdpa_group_tbl_entry *entry; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return -ENOMEM; entry->group_id = group_id; entry->group_count = group_count; entry->group_ids = kcalloc(group_count, sizeof(u32), GFP_KERNEL); if (!entry->group_ids) { kfree(entry); return -ENOMEM; } memcpy(entry->group_ids, group_ids, group_count * sizeof(u32)); return ofdpa_group_tbl_do(ofdpa_port, flags, entry); } static int ofdpa_group_l2_flood(struct ofdpa_port *ofdpa_port, int flags, __be16 vlan_id, u8 group_count, const u32 *group_ids, u32 group_id) { return ofdpa_group_l2_fan_out(ofdpa_port, flags, group_count, group_ids, group_id); } static int ofdpa_group_l3_unicast(struct ofdpa_port *ofdpa_port, int flags, u32 index, const u8 *src_mac, const u8 *dst_mac, __be16 vlan_id, bool ttl_check, u32 pport) { struct ofdpa_group_tbl_entry *entry; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return -ENOMEM; entry->group_id = ROCKER_GROUP_L3_UNICAST(index); if (src_mac) ether_addr_copy(entry->l3_unicast.eth_src, src_mac); if (dst_mac) ether_addr_copy(entry->l3_unicast.eth_dst, dst_mac); entry->l3_unicast.vlan_id = vlan_id; entry->l3_unicast.ttl_check = ttl_check; entry->l3_unicast.group_id = ROCKER_GROUP_L2_INTERFACE(vlan_id, pport); return ofdpa_group_tbl_do(ofdpa_port, flags, entry); } static struct ofdpa_neigh_tbl_entry * ofdpa_neigh_tbl_find(const struct ofdpa *ofdpa, __be32 ip_addr) { struct ofdpa_neigh_tbl_entry *found; hash_for_each_possible(ofdpa->neigh_tbl, found, entry, be32_to_cpu(ip_addr)) if (found->ip_addr == ip_addr) return found; return NULL; } static void ofdpa_neigh_add(struct ofdpa *ofdpa, struct ofdpa_neigh_tbl_entry *entry) { entry->index = ofdpa->neigh_tbl_next_index++; entry->ref_count++; hash_add(ofdpa->neigh_tbl, &entry->entry, be32_to_cpu(entry->ip_addr)); } static void ofdpa_neigh_del(struct ofdpa_neigh_tbl_entry *entry) { if (--entry->ref_count == 0) { hash_del(&entry->entry); kfree(entry); } } static void ofdpa_neigh_update(struct ofdpa_neigh_tbl_entry *entry, const u8 *eth_dst, bool ttl_check) { if (eth_dst) { ether_addr_copy(entry->eth_dst, eth_dst); entry->ttl_check = ttl_check; } else { entry->ref_count++; } } static int ofdpa_port_ipv4_neigh(struct ofdpa_port *ofdpa_port, int flags, __be32 ip_addr, const u8 *eth_dst) { struct ofdpa *ofdpa = ofdpa_port->ofdpa; struct ofdpa_neigh_tbl_entry *entry; struct ofdpa_neigh_tbl_entry *found; unsigned long lock_flags; __be16 eth_type = htons(ETH_P_IP); enum rocker_of_dpa_table_id goto_tbl = ROCKER_OF_DPA_TABLE_ID_ACL_POLICY; u32 group_id; u32 priority = 0; bool adding = !(flags & OFDPA_OP_FLAG_REMOVE); bool updating; bool removing; int err = 0; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return -ENOMEM; spin_lock_irqsave(&ofdpa->neigh_tbl_lock, lock_flags); found = ofdpa_neigh_tbl_find(ofdpa, ip_addr); updating = found && adding; removing = found && !adding; adding = !found && adding; if (adding) { entry->ip_addr = ip_addr; entry->dev = ofdpa_port->dev; ether_addr_copy(entry->eth_dst, eth_dst); entry->ttl_check = true; ofdpa_neigh_add(ofdpa, entry); } else if (removing) { memcpy(entry, found, sizeof(*entry)); ofdpa_neigh_del(found); } else if (updating) { ofdpa_neigh_update(found, eth_dst, true); memcpy(entry, found, sizeof(*entry)); } else { err = -ENOENT; } spin_unlock_irqrestore(&ofdpa->neigh_tbl_lock, lock_flags); if (err) goto err_out; /* For each active neighbor, we have an L3 unicast group and * a /32 route to the neighbor, which uses the L3 unicast * group. The L3 unicast group can also be referred to by * other routes' nexthops. */ err = ofdpa_group_l3_unicast(ofdpa_port, flags, entry->index, ofdpa_port->dev->dev_addr, entry->eth_dst, ofdpa_port->internal_vlan_id, entry->ttl_check, ofdpa_port->pport); if (err) { netdev_err(ofdpa_port->dev, "Error (%d) L3 unicast group index %d\n", err, entry->index); goto err_out; } if (adding || removing) { group_id = ROCKER_GROUP_L3_UNICAST(entry->index); err = ofdpa_flow_tbl_ucast4_routing(ofdpa_port, eth_type, ip_addr, inet_make_mask(32), priority, goto_tbl, group_id, NULL, flags); if (err) netdev_err(ofdpa_port->dev, "Error (%d) /32 unicast route %pI4 group 0x%08x\n", err, &entry->ip_addr, group_id); } err_out: if (!adding) kfree(entry); return err; } static int ofdpa_port_ipv4_resolve(struct ofdpa_port *ofdpa_port, __be32 ip_addr) { struct net_device *dev = ofdpa_port->dev; struct neighbour *n = __ipv4_neigh_lookup(dev, (__force u32)ip_addr); int err = 0; if (!n) { n = neigh_create(&arp_tbl, &ip_addr, dev); if (IS_ERR(n)) return PTR_ERR(n); } /* If the neigh is already resolved, then go ahead and * install the entry, otherwise start the ARP process to * resolve the neigh. */ if (n->nud_state & NUD_VALID) err = ofdpa_port_ipv4_neigh(ofdpa_port, 0, ip_addr, n->ha); else neigh_event_send(n, NULL); neigh_release(n); return err; } static int ofdpa_port_ipv4_nh(struct ofdpa_port *ofdpa_port, int flags, __be32 ip_addr, u32 *index) { struct ofdpa *ofdpa = ofdpa_port->ofdpa; struct ofdpa_neigh_tbl_entry *entry; struct ofdpa_neigh_tbl_entry *found; unsigned long lock_flags; bool adding = !(flags & OFDPA_OP_FLAG_REMOVE); bool updating; bool removing; bool resolved = true; int err = 0; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return -ENOMEM; spin_lock_irqsave(&ofdpa->neigh_tbl_lock, lock_flags); found = ofdpa_neigh_tbl_find(ofdpa, ip_addr); updating = found && adding; removing = found && !adding; adding = !found && adding; if (adding) { entry->ip_addr = ip_addr; entry->dev = ofdpa_port->dev; ofdpa_neigh_add(ofdpa, entry); *index = entry->index; resolved = false; } else if (removing) { *index = found->index; ofdpa_neigh_del(found); } else if (updating) { ofdpa_neigh_update(found, NULL, false); resolved = !is_zero_ether_addr(found->eth_dst); *index = found->index; } else { err = -ENOENT; } spin_unlock_irqrestore(&ofdpa->neigh_tbl_lock, lock_flags); if (!adding) kfree(entry); if (err) return err; /* Resolved means neigh ip_addr is resolved to neigh mac. */ if (!resolved) err = ofdpa_port_ipv4_resolve(ofdpa_port, ip_addr); return err; } static struct ofdpa_port *ofdpa_port_get(const struct ofdpa *ofdpa, int port_index) { struct rocker_port *rocker_port; rocker_port = ofdpa->rocker->ports[port_index]; return rocker_port ? rocker_port->wpriv : NULL; } static int ofdpa_port_vlan_flood_group(struct ofdpa_port *ofdpa_port, int flags, __be16 vlan_id) { struct ofdpa_port *p; const struct ofdpa *ofdpa = ofdpa_port->ofdpa; unsigned int port_count = ofdpa->rocker->port_count; u32 group_id = ROCKER_GROUP_L2_FLOOD(vlan_id, 0); u32 *group_ids; u8 group_count = 0; int err = 0; int i; group_ids = kcalloc(port_count, sizeof(u32), GFP_KERNEL); if (!group_ids) return -ENOMEM; /* Adjust the flood group for this VLAN. The flood group * references an L2 interface group for each port in this * VLAN. */ for (i = 0; i < port_count; i++) { p = ofdpa_port_get(ofdpa, i); if (!p) continue; if (!ofdpa_port_is_bridged(p)) continue; if (test_bit(ntohs(vlan_id), p->vlan_bitmap)) { group_ids[group_count++] = ROCKER_GROUP_L2_INTERFACE(vlan_id, p->pport); } } /* If there are no bridged ports in this VLAN, we're done */ if (group_count == 0) goto no_ports_in_vlan; err = ofdpa_group_l2_flood(ofdpa_port, flags, vlan_id, group_count, group_ids, group_id); if (err) netdev_err(ofdpa_port->dev, "Error (%d) port VLAN l2 flood group\n", err); no_ports_in_vlan: kfree(group_ids); return err; } static int ofdpa_port_vlan_l2_groups(struct ofdpa_port *ofdpa_port, int flags, __be16 vlan_id, bool pop_vlan) { const struct ofdpa *ofdpa = ofdpa_port->ofdpa; unsigned int port_count = ofdpa->rocker->port_count; struct ofdpa_port *p; bool adding = !(flags & OFDPA_OP_FLAG_REMOVE); u32 out_pport; int ref = 0; int err; int i; /* An L2 interface group for this port in this VLAN, but * only when port STP state is LEARNING|FORWARDING. */ if (ofdpa_port->stp_state == BR_STATE_LEARNING || ofdpa_port->stp_state == BR_STATE_FORWARDING) { out_pport = ofdpa_port->pport; err = ofdpa_group_l2_interface(ofdpa_port, flags, vlan_id, out_pport, pop_vlan); if (err) { netdev_err(ofdpa_port->dev, "Error (%d) port VLAN l2 group for pport %d\n", err, out_pport); return err; } } /* An L2 interface group for this VLAN to CPU port. * Add when first port joins this VLAN and destroy when * last port leaves this VLAN. */ for (i = 0; i < port_count; i++) { p = ofdpa_port_get(ofdpa, i); if (p && test_bit(ntohs(vlan_id), p->vlan_bitmap)) ref++; } if ((!adding || ref != 1) && (adding || ref != 0)) return 0; out_pport = 0; err = ofdpa_group_l2_interface(ofdpa_port, flags, vlan_id, out_pport, pop_vlan); if (err) { netdev_err(ofdpa_port->dev, "Error (%d) port VLAN l2 group for CPU port\n", err); return err; } return 0; } static struct ofdpa_ctrl { const u8 *eth_dst; const u8 *eth_dst_mask; __be16 eth_type; bool acl; bool bridge; bool term; bool copy_to_cpu; } ofdpa_ctrls[] = { [OFDPA_CTRL_LINK_LOCAL_MCAST] = { /* pass link local multicast pkts up to CPU for filtering */ .eth_dst = ll_mac, .eth_dst_mask = ll_mask, .acl = true, }, [OFDPA_CTRL_LOCAL_ARP] = { /* pass local ARP pkts up to CPU */ .eth_dst = zero_mac, .eth_dst_mask = zero_mac, .eth_type = htons(ETH_P_ARP), .acl = true, }, [OFDPA_CTRL_IPV4_MCAST] = { /* pass IPv4 mcast pkts up to CPU, RFC 1112 */ .eth_dst = ipv4_mcast, .eth_dst_mask = ipv4_mask, .eth_type = htons(ETH_P_IP), .term = true, .copy_to_cpu = true, }, [OFDPA_CTRL_IPV6_MCAST] = { /* pass IPv6 mcast pkts up to CPU, RFC 2464 */ .eth_dst = ipv6_mcast, .eth_dst_mask = ipv6_mask, .eth_type = htons(ETH_P_IPV6), .term = true, .copy_to_cpu = true, }, [OFDPA_CTRL_DFLT_BRIDGING] = { /* flood any pkts on vlan */ .bridge = true, .copy_to_cpu = true, }, [OFDPA_CTRL_DFLT_OVS] = { /* pass all pkts up to CPU */ .eth_dst = zero_mac, .eth_dst_mask = zero_mac, .acl = true, }, }; static int ofdpa_port_ctrl_vlan_acl(struct ofdpa_port *ofdpa_port, int flags, const struct ofdpa_ctrl *ctrl, __be16 vlan_id) { u32 in_pport = ofdpa_port->pport; u32 in_pport_mask = 0xffffffff; u32 out_pport = 0; const u8 *eth_src = NULL; const u8 *eth_src_mask = NULL; __be16 vlan_id_mask = htons(0xffff); u8 ip_proto = 0; u8 ip_proto_mask = 0; u8 ip_tos = 0; u8 ip_tos_mask = 0; u32 group_id = ROCKER_GROUP_L2_INTERFACE(vlan_id, out_pport); int err; err = ofdpa_flow_tbl_acl(ofdpa_port, flags, in_pport, in_pport_mask, eth_src, eth_src_mask, ctrl->eth_dst, ctrl->eth_dst_mask, ctrl->eth_type, vlan_id, vlan_id_mask, ip_proto, ip_proto_mask, ip_tos, ip_tos_mask, group_id); if (err) netdev_err(ofdpa_port->dev, "Error (%d) ctrl ACL\n", err); return err; } static int ofdpa_port_ctrl_vlan_bridge(struct ofdpa_port *ofdpa_port, int flags, const struct ofdpa_ctrl *ctrl, __be16 vlan_id) { enum rocker_of_dpa_table_id goto_tbl = ROCKER_OF_DPA_TABLE_ID_ACL_POLICY; u32 group_id = ROCKER_GROUP_L2_FLOOD(vlan_id, 0); u32 tunnel_id = 0; int err; if (!ofdpa_port_is_bridged(ofdpa_port)) return 0; err = ofdpa_flow_tbl_bridge(ofdpa_port, flags, ctrl->eth_dst, ctrl->eth_dst_mask, vlan_id, tunnel_id, goto_tbl, group_id, ctrl->copy_to_cpu); if (err) netdev_err(ofdpa_port->dev, "Error (%d) ctrl FLOOD\n", err); return err; } static int ofdpa_port_ctrl_vlan_term(struct ofdpa_port *ofdpa_port, int flags, const struct ofdpa_ctrl *ctrl, __be16 vlan_id) { u32 in_pport_mask = 0xffffffff; __be16 vlan_id_mask = htons(0xffff); int err; if (ntohs(vlan_id) == 0) vlan_id = ofdpa_port->internal_vlan_id; err = ofdpa_flow_tbl_term_mac(ofdpa_port, ofdpa_port->pport, in_pport_mask, ctrl->eth_type, ctrl->eth_dst, ctrl->eth_dst_mask, vlan_id, vlan_id_mask, ctrl->copy_to_cpu, flags); if (err) netdev_err(ofdpa_port->dev, "Error (%d) ctrl term\n", err); return err; } static int ofdpa_port_ctrl_vlan(struct ofdpa_port *ofdpa_port, int flags, const struct ofdpa_ctrl *ctrl, __be16 vlan_id) { if (ctrl->acl) return ofdpa_port_ctrl_vlan_acl(ofdpa_port, flags, ctrl, vlan_id); if (ctrl->bridge) return ofdpa_port_ctrl_vlan_bridge(ofdpa_port, flags, ctrl, vlan_id); if (ctrl->term) return ofdpa_port_ctrl_vlan_term(ofdpa_port, flags, ctrl, vlan_id); return -EOPNOTSUPP; } static int ofdpa_port_ctrl_vlan_add(struct ofdpa_port *ofdpa_port, int flags, __be16 vlan_id) { int err = 0; int i; for (i = 0; i < OFDPA_CTRL_MAX; i++) { if (ofdpa_port->ctrls[i]) { err = ofdpa_port_ctrl_vlan(ofdpa_port, flags, &ofdpa_ctrls[i], vlan_id); if (err) return err; } } return err; } static int ofdpa_port_ctrl(struct ofdpa_port *ofdpa_port, int flags, const struct ofdpa_ctrl *ctrl) { u16 vid; int err = 0; for (vid = 1; vid < VLAN_N_VID; vid++) { if (!test_bit(vid, ofdpa_port->vlan_bitmap)) continue; err = ofdpa_port_ctrl_vlan(ofdpa_port, flags, ctrl, htons(vid)); if (err) break; } return err; } static int ofdpa_port_vlan(struct ofdpa_port *ofdpa_port, int flags, u16 vid) { enum rocker_of_dpa_table_id goto_tbl = ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC; u32 in_pport = ofdpa_port->pport; __be16 vlan_id = htons(vid); __be16 vlan_id_mask = htons(0xffff); __be16 internal_vlan_id; bool untagged; bool adding = !(flags & OFDPA_OP_FLAG_REMOVE); int err; internal_vlan_id = ofdpa_port_vid_to_vlan(ofdpa_port, vid, &untagged); if (adding && test_bit(ntohs(internal_vlan_id), ofdpa_port->vlan_bitmap)) return 0; /* already added */ else if (!adding && !test_bit(ntohs(internal_vlan_id), ofdpa_port->vlan_bitmap)) return 0; /* already removed */ change_bit(ntohs(internal_vlan_id), ofdpa_port->vlan_bitmap); if (adding) { err = ofdpa_port_ctrl_vlan_add(ofdpa_port, flags, internal_vlan_id); if (err) { netdev_err(ofdpa_port->dev, "Error (%d) port ctrl vlan add\n", err); goto err_vlan_add; } } err = ofdpa_port_vlan_l2_groups(ofdpa_port, flags, internal_vlan_id, untagged); if (err) { netdev_err(ofdpa_port->dev, "Error (%d) port VLAN l2 groups\n", err); goto err_vlan_l2_groups; } err = ofdpa_port_vlan_flood_group(ofdpa_port, flags, internal_vlan_id); if (err) { netdev_err(ofdpa_port->dev, "Error (%d) port VLAN l2 flood group\n", err); goto err_flood_group; } err = ofdpa_flow_tbl_vlan(ofdpa_port, flags, in_pport, vlan_id, vlan_id_mask, goto_tbl, untagged, internal_vlan_id); if (err) netdev_err(ofdpa_port->dev, "Error (%d) port VLAN table\n", err); return 0; err_vlan_add: err_vlan_l2_groups: err_flood_group: change_bit(ntohs(internal_vlan_id), ofdpa_port->vlan_bitmap); return err; } static int ofdpa_port_ig_tbl(struct ofdpa_port *ofdpa_port, int flags) { enum rocker_of_dpa_table_id goto_tbl; u32 in_pport; u32 in_pport_mask; int err; /* Normal Ethernet Frames. Matches pkts from any local physical * ports. Goto VLAN tbl. */ in_pport = 0; in_pport_mask = 0xffff0000; goto_tbl = ROCKER_OF_DPA_TABLE_ID_VLAN; err = ofdpa_flow_tbl_ig_port(ofdpa_port, flags, in_pport, in_pport_mask, goto_tbl); if (err) netdev_err(ofdpa_port->dev, "Error (%d) ingress port table entry\n", err); return err; } struct ofdpa_fdb_learn_work { struct work_struct work; struct ofdpa_port *ofdpa_port; int flags; u8 addr[ETH_ALEN]; u16 vid; }; static void ofdpa_port_fdb_learn_work(struct work_struct *work) { const struct ofdpa_fdb_learn_work *lw = container_of(work, struct ofdpa_fdb_learn_work, work); bool removing = (lw->flags & OFDPA_OP_FLAG_REMOVE); bool learned = (lw->flags & OFDPA_OP_FLAG_LEARNED); struct switchdev_notifier_fdb_info info; info.addr = lw->addr; info.vid = lw->vid; rtnl_lock(); if (learned && removing) call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_BRIDGE, lw->ofdpa_port->dev, &info.info); else if (learned && !removing) call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE, lw->ofdpa_port->dev, &info.info); rtnl_unlock(); kfree(work); } static int ofdpa_port_fdb_learn(struct ofdpa_port *ofdpa_port, int flags, const u8 *addr, __be16 vlan_id) { struct ofdpa_fdb_learn_work *lw; enum rocker_of_dpa_table_id goto_tbl = ROCKER_OF_DPA_TABLE_ID_ACL_POLICY; u32 out_pport = ofdpa_port->pport; u32 tunnel_id = 0; u32 group_id = ROCKER_GROUP_NONE; bool copy_to_cpu = false; int err; if (ofdpa_port_is_bridged(ofdpa_port)) group_id = ROCKER_GROUP_L2_INTERFACE(vlan_id, out_pport); if (!(flags & OFDPA_OP_FLAG_REFRESH)) { err = ofdpa_flow_tbl_bridge(ofdpa_port, flags, addr, NULL, vlan_id, tunnel_id, goto_tbl, group_id, copy_to_cpu); if (err) return err; } if (!ofdpa_port_is_bridged(ofdpa_port)) return 0; lw = kzalloc(sizeof(*lw), GFP_ATOMIC); if (!lw) return -ENOMEM; INIT_WORK(&lw->work, ofdpa_port_fdb_learn_work); lw->ofdpa_port = ofdpa_port; lw->flags = flags; ether_addr_copy(lw->addr, addr); lw->vid = ofdpa_port_vlan_to_vid(ofdpa_port, vlan_id); schedule_work(&lw->work); return 0; } static struct ofdpa_fdb_tbl_entry * ofdpa_fdb_tbl_find(const struct ofdpa *ofdpa, const struct ofdpa_fdb_tbl_entry *match) { struct ofdpa_fdb_tbl_entry *found; hash_for_each_possible(ofdpa->fdb_tbl, found, entry, match->key_crc32) if (memcmp(&found->key, &match->key, sizeof(found->key)) == 0) return found; return NULL; } static int ofdpa_port_fdb(struct ofdpa_port *ofdpa_port, const unsigned char *addr, __be16 vlan_id, int flags) { struct ofdpa *ofdpa = ofdpa_port->ofdpa; struct ofdpa_fdb_tbl_entry *fdb; struct ofdpa_fdb_tbl_entry *found; bool removing = (flags & OFDPA_OP_FLAG_REMOVE); unsigned long lock_flags; fdb = kzalloc(sizeof(*fdb), GFP_KERNEL); if (!fdb) return -ENOMEM; fdb->learned = (flags & OFDPA_OP_FLAG_LEARNED); fdb->touched = jiffies; fdb->key.ofdpa_port = ofdpa_port; ether_addr_copy(fdb->key.addr, addr); fdb->key.vlan_id = vlan_id; fdb->key_crc32 = crc32(~0, &fdb->key, sizeof(fdb->key)); spin_lock_irqsave(&ofdpa->fdb_tbl_lock, lock_flags); found = ofdpa_fdb_tbl_find(ofdpa, fdb); if (found) { found->touched = jiffies; if (removing) { kfree(fdb); hash_del(&found->entry); } } else if (!removing) { hash_add(ofdpa->fdb_tbl, &fdb->entry, fdb->key_crc32); } spin_unlock_irqrestore(&ofdpa->fdb_tbl_lock, lock_flags); /* Check if adding and already exists, or removing and can't find */ if (!found != !removing) { kfree(fdb); if (!found && removing) return 0; /* Refreshing existing to update aging timers */ flags |= OFDPA_OP_FLAG_REFRESH; } return ofdpa_port_fdb_learn(ofdpa_port, flags, addr, vlan_id); } static int ofdpa_port_fdb_flush(struct ofdpa_port *ofdpa_port, int flags) { struct ofdpa *ofdpa = ofdpa_port->ofdpa; struct ofdpa_fdb_tbl_entry *found; unsigned long lock_flags; struct hlist_node *tmp; int bkt; int err = 0; if (ofdpa_port->stp_state == BR_STATE_LEARNING || ofdpa_port->stp_state == BR_STATE_FORWARDING) return 0; flags |= OFDPA_OP_FLAG_NOWAIT | OFDPA_OP_FLAG_REMOVE; spin_lock_irqsave(&ofdpa->fdb_tbl_lock, lock_flags); hash_for_each_safe(ofdpa->fdb_tbl, bkt, tmp, found, entry) { if (found->key.ofdpa_port != ofdpa_port) continue; if (!found->learned) continue; err = ofdpa_port_fdb_learn(ofdpa_port, flags, found->key.addr, found->key.vlan_id); if (err) goto err_out; hash_del(&found->entry); } err_out: spin_unlock_irqrestore(&ofdpa->fdb_tbl_lock, lock_flags); return err; } static void ofdpa_fdb_cleanup(struct timer_list *t) { struct ofdpa *ofdpa = from_timer(ofdpa, t, fdb_cleanup_timer); struct ofdpa_port *ofdpa_port; struct ofdpa_fdb_tbl_entry *entry; struct hlist_node *tmp; unsigned long next_timer = jiffies + ofdpa->ageing_time; unsigned long expires; unsigned long lock_flags; int flags = OFDPA_OP_FLAG_NOWAIT | OFDPA_OP_FLAG_REMOVE | OFDPA_OP_FLAG_LEARNED; int bkt; spin_lock_irqsave(&ofdpa->fdb_tbl_lock, lock_flags); hash_for_each_safe(ofdpa->fdb_tbl, bkt, tmp, entry, entry) { if (!entry->learned) continue; ofdpa_port = entry->key.ofdpa_port; expires = entry->touched + ofdpa_port->ageing_time; if (time_before_eq(expires, jiffies)) { ofdpa_port_fdb_learn(ofdpa_port, flags, entry->key.addr, entry->key.vlan_id); hash_del(&entry->entry); } else if (time_before(expires, next_timer)) { next_timer = expires; } } spin_unlock_irqrestore(&ofdpa->fdb_tbl_lock, lock_flags); mod_timer(&ofdpa->fdb_cleanup_timer, round_jiffies_up(next_timer)); } static int ofdpa_port_router_mac(struct ofdpa_port *ofdpa_port, int flags, __be16 vlan_id) { u32 in_pport_mask = 0xffffffff; __be16 eth_type; const u8 *dst_mac_mask = ff_mac; __be16 vlan_id_mask = htons(0xffff); bool copy_to_cpu = false; int err; if (ntohs(vlan_id) == 0) vlan_id = ofdpa_port->internal_vlan_id; eth_type = htons(ETH_P_IP); err = ofdpa_flow_tbl_term_mac(ofdpa_port, ofdpa_port->pport, in_pport_mask, eth_type, ofdpa_port->dev->dev_addr, dst_mac_mask, vlan_id, vlan_id_mask, copy_to_cpu, flags); if (err) return err; eth_type = htons(ETH_P_IPV6); err = ofdpa_flow_tbl_term_mac(ofdpa_port, ofdpa_port->pport, in_pport_mask, eth_type, ofdpa_port->dev->dev_addr, dst_mac_mask, vlan_id, vlan_id_mask, copy_to_cpu, flags); return err; } static int ofdpa_port_fwding(struct ofdpa_port *ofdpa_port, int flags) { bool pop_vlan; u32 out_pport; __be16 vlan_id; u16 vid; int err; /* Port will be forwarding-enabled if its STP state is LEARNING * or FORWARDING. Traffic from CPU can still egress, regardless of * port STP state. Use L2 interface group on port VLANs as a way * to toggle port forwarding: if forwarding is disabled, L2 * interface group will not exist. */ if (ofdpa_port->stp_state != BR_STATE_LEARNING && ofdpa_port->stp_state != BR_STATE_FORWARDING) flags |= OFDPA_OP_FLAG_REMOVE; out_pport = ofdpa_port->pport; for (vid = 1; vid < VLAN_N_VID; vid++) { if (!test_bit(vid, ofdpa_port->vlan_bitmap)) continue; vlan_id = htons(vid); pop_vlan = ofdpa_vlan_id_is_internal(vlan_id); err = ofdpa_group_l2_interface(ofdpa_port, flags, vlan_id, out_pport, pop_vlan); if (err) { netdev_err(ofdpa_port->dev, "Error (%d) port VLAN l2 group for pport %d\n", err, out_pport); return err; } } return 0; } static int ofdpa_port_stp_update(struct ofdpa_port *ofdpa_port, int flags, u8 state) { bool want[OFDPA_CTRL_MAX] = { 0, }; bool prev_ctrls[OFDPA_CTRL_MAX]; u8 prev_state; int err; int i; memcpy(prev_ctrls, ofdpa_port->ctrls, sizeof(prev_ctrls)); prev_state = ofdpa_port->stp_state; if (ofdpa_port->stp_state == state) return 0; ofdpa_port->stp_state = state; switch (state) { case BR_STATE_DISABLED: /* port is completely disabled */ break; case BR_STATE_LISTENING: case BR_STATE_BLOCKING: want[OFDPA_CTRL_LINK_LOCAL_MCAST] = true; break; case BR_STATE_LEARNING: case BR_STATE_FORWARDING: if (!ofdpa_port_is_ovsed(ofdpa_port)) want[OFDPA_CTRL_LINK_LOCAL_MCAST] = true; want[OFDPA_CTRL_IPV4_MCAST] = true; want[OFDPA_CTRL_IPV6_MCAST] = true; if (ofdpa_port_is_bridged(ofdpa_port)) want[OFDPA_CTRL_DFLT_BRIDGING] = true; else if (ofdpa_port_is_ovsed(ofdpa_port)) want[OFDPA_CTRL_DFLT_OVS] = true; else want[OFDPA_CTRL_LOCAL_ARP] = true; break; } for (i = 0; i < OFDPA_CTRL_MAX; i++) { if (want[i] != ofdpa_port->ctrls[i]) { int ctrl_flags = flags | (want[i] ? 0 : OFDPA_OP_FLAG_REMOVE); err = ofdpa_port_ctrl(ofdpa_port, ctrl_flags, &ofdpa_ctrls[i]); if (err) goto err_port_ctrl; ofdpa_port->ctrls[i] = want[i]; } } err = ofdpa_port_fdb_flush(ofdpa_port, flags); if (err) goto err_fdb_flush; err = ofdpa_port_fwding(ofdpa_port, flags); if (err) goto err_port_fwding; return 0; err_port_ctrl: err_fdb_flush: err_port_fwding: memcpy(ofdpa_port->ctrls, prev_ctrls, sizeof(prev_ctrls)); ofdpa_port->stp_state = prev_state; return err; } static int ofdpa_port_fwd_enable(struct ofdpa_port *ofdpa_port, int flags) { if (ofdpa_port_is_bridged(ofdpa_port)) /* bridge STP will enable port */ return 0; /* port is not bridged, so simulate going to FORWARDING state */ return ofdpa_port_stp_update(ofdpa_port, flags, BR_STATE_FORWARDING); } static int ofdpa_port_fwd_disable(struct ofdpa_port *ofdpa_port, int flags) { if (ofdpa_port_is_bridged(ofdpa_port)) /* bridge STP will disable port */ return 0; /* port is not bridged, so simulate going to DISABLED state */ return ofdpa_port_stp_update(ofdpa_port, flags, BR_STATE_DISABLED); } static int ofdpa_port_vlan_add(struct ofdpa_port *ofdpa_port, u16 vid, u16 flags) { int err; /* XXX deal with flags for PVID and untagged */ err = ofdpa_port_vlan(ofdpa_port, 0, vid); if (err) return err; err = ofdpa_port_router_mac(ofdpa_port, 0, htons(vid)); if (err) ofdpa_port_vlan(ofdpa_port, OFDPA_OP_FLAG_REMOVE, vid); return err; } static int ofdpa_port_vlan_del(struct ofdpa_port *ofdpa_port, u16 vid, u16 flags) { int err; err = ofdpa_port_router_mac(ofdpa_port, OFDPA_OP_FLAG_REMOVE, htons(vid)); if (err) return err; return ofdpa_port_vlan(ofdpa_port, OFDPA_OP_FLAG_REMOVE, vid); } static struct ofdpa_internal_vlan_tbl_entry * ofdpa_internal_vlan_tbl_find(const struct ofdpa *ofdpa, int ifindex) { struct ofdpa_internal_vlan_tbl_entry *found; hash_for_each_possible(ofdpa->internal_vlan_tbl, found, entry, ifindex) { if (found->ifindex == ifindex) return found; } return NULL; } static __be16 ofdpa_port_internal_vlan_id_get(struct ofdpa_port *ofdpa_port, int ifindex) { struct ofdpa *ofdpa = ofdpa_port->ofdpa; struct ofdpa_internal_vlan_tbl_entry *entry; struct ofdpa_internal_vlan_tbl_entry *found; unsigned long lock_flags; int i; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return 0; entry->ifindex = ifindex; spin_lock_irqsave(&ofdpa->internal_vlan_tbl_lock, lock_flags); found = ofdpa_internal_vlan_tbl_find(ofdpa, ifindex); if (found) { kfree(entry); goto found; } found = entry; hash_add(ofdpa->internal_vlan_tbl, &found->entry, found->ifindex); for (i = 0; i < OFDPA_N_INTERNAL_VLANS; i++) { if (test_and_set_bit(i, ofdpa->internal_vlan_bitmap)) continue; found->vlan_id = htons(OFDPA_INTERNAL_VLAN_ID_BASE + i); goto found; } netdev_err(ofdpa_port->dev, "Out of internal VLAN IDs\n"); found: found->ref_count++; spin_unlock_irqrestore(&ofdpa->internal_vlan_tbl_lock, lock_flags); return found->vlan_id; } static int ofdpa_port_fib_ipv4(struct ofdpa_port *ofdpa_port, __be32 dst, int dst_len, struct fib_info *fi, u32 tb_id, int flags) { const struct fib_nh *nh; __be16 eth_type = htons(ETH_P_IP); __be32 dst_mask = inet_make_mask(dst_len); __be16 internal_vlan_id = ofdpa_port->internal_vlan_id; u32 priority = fi->fib_priority; enum rocker_of_dpa_table_id goto_tbl = ROCKER_OF_DPA_TABLE_ID_ACL_POLICY; u32 group_id; bool nh_on_port; bool has_gw; u32 index; int err; /* XXX support ECMP */ nh = fi->fib_nh; nh_on_port = (fi->fib_dev == ofdpa_port->dev); has_gw = !!nh->nh_gw; if (has_gw && nh_on_port) { err = ofdpa_port_ipv4_nh(ofdpa_port, flags, nh->nh_gw, &index); if (err) return err; group_id = ROCKER_GROUP_L3_UNICAST(index); } else { /* Send to CPU for processing */ group_id = ROCKER_GROUP_L2_INTERFACE(internal_vlan_id, 0); } err = ofdpa_flow_tbl_ucast4_routing(ofdpa_port, eth_type, dst, dst_mask, priority, goto_tbl, group_id, fi, flags); if (err) netdev_err(ofdpa_port->dev, "Error (%d) IPv4 route %pI4\n", err, &dst); return err; } static void ofdpa_port_internal_vlan_id_put(const struct ofdpa_port *ofdpa_port, int ifindex) { struct ofdpa *ofdpa = ofdpa_port->ofdpa; struct ofdpa_internal_vlan_tbl_entry *found; unsigned long lock_flags; unsigned long bit; spin_lock_irqsave(&ofdpa->internal_vlan_tbl_lock, lock_flags); found = ofdpa_internal_vlan_tbl_find(ofdpa, ifindex); if (!found) { netdev_err(ofdpa_port->dev, "ifindex (%d) not found in internal VLAN tbl\n", ifindex); goto not_found; } if (--found->ref_count <= 0) { bit = ntohs(found->vlan_id) - OFDPA_INTERNAL_VLAN_ID_BASE; clear_bit(bit, ofdpa->internal_vlan_bitmap); hash_del(&found->entry); kfree(found); } not_found: spin_unlock_irqrestore(&ofdpa->internal_vlan_tbl_lock, lock_flags); } /********************************** * Rocker world ops implementation **********************************/ static int ofdpa_init(struct rocker *rocker) { struct ofdpa *ofdpa = rocker->wpriv; ofdpa->rocker = rocker; hash_init(ofdpa->flow_tbl); spin_lock_init(&ofdpa->flow_tbl_lock); hash_init(ofdpa->group_tbl); spin_lock_init(&ofdpa->group_tbl_lock); hash_init(ofdpa->fdb_tbl); spin_lock_init(&ofdpa->fdb_tbl_lock); hash_init(ofdpa->internal_vlan_tbl); spin_lock_init(&ofdpa->internal_vlan_tbl_lock); hash_init(ofdpa->neigh_tbl); spin_lock_init(&ofdpa->neigh_tbl_lock); timer_setup(&ofdpa->fdb_cleanup_timer, ofdpa_fdb_cleanup, 0); mod_timer(&ofdpa->fdb_cleanup_timer, jiffies); ofdpa->ageing_time = BR_DEFAULT_AGEING_TIME; return 0; } static void ofdpa_fini(struct rocker *rocker) { struct ofdpa *ofdpa = rocker->wpriv; unsigned long flags; struct ofdpa_flow_tbl_entry *flow_entry; struct ofdpa_group_tbl_entry *group_entry; struct ofdpa_fdb_tbl_entry *fdb_entry; struct ofdpa_internal_vlan_tbl_entry *internal_vlan_entry; struct ofdpa_neigh_tbl_entry *neigh_entry; struct hlist_node *tmp; int bkt; del_timer_sync(&ofdpa->fdb_cleanup_timer); flush_workqueue(rocker->rocker_owq); spin_lock_irqsave(&ofdpa->flow_tbl_lock, flags); hash_for_each_safe(ofdpa->flow_tbl, bkt, tmp, flow_entry, entry) hash_del(&flow_entry->entry); spin_unlock_irqrestore(&ofdpa->flow_tbl_lock, flags); spin_lock_irqsave(&ofdpa->group_tbl_lock, flags); hash_for_each_safe(ofdpa->group_tbl, bkt, tmp, group_entry, entry) hash_del(&group_entry->entry); spin_unlock_irqrestore(&ofdpa->group_tbl_lock, flags); spin_lock_irqsave(&ofdpa->fdb_tbl_lock, flags); hash_for_each_safe(ofdpa->fdb_tbl, bkt, tmp, fdb_entry, entry) hash_del(&fdb_entry->entry); spin_unlock_irqrestore(&ofdpa->fdb_tbl_lock, flags); spin_lock_irqsave(&ofdpa->internal_vlan_tbl_lock, flags); hash_for_each_safe(ofdpa->internal_vlan_tbl, bkt, tmp, internal_vlan_entry, entry) hash_del(&internal_vlan_entry->entry); spin_unlock_irqrestore(&ofdpa->internal_vlan_tbl_lock, flags); spin_lock_irqsave(&ofdpa->neigh_tbl_lock, flags); hash_for_each_safe(ofdpa->neigh_tbl, bkt, tmp, neigh_entry, entry) hash_del(&neigh_entry->entry); spin_unlock_irqrestore(&ofdpa->neigh_tbl_lock, flags); } static int ofdpa_port_pre_init(struct rocker_port *rocker_port) { struct ofdpa_port *ofdpa_port = rocker_port->wpriv; ofdpa_port->ofdpa = rocker_port->rocker->wpriv; ofdpa_port->rocker_port = rocker_port; ofdpa_port->dev = rocker_port->dev; ofdpa_port->pport = rocker_port->pport; ofdpa_port->brport_flags = BR_LEARNING; ofdpa_port->ageing_time = BR_DEFAULT_AGEING_TIME; return 0; } static int ofdpa_port_init(struct rocker_port *rocker_port) { struct ofdpa_port *ofdpa_port = rocker_port->wpriv; int err; rocker_port_set_learning(rocker_port, !!(ofdpa_port->brport_flags & BR_LEARNING)); err = ofdpa_port_ig_tbl(ofdpa_port, 0); if (err) { netdev_err(ofdpa_port->dev, "install ig port table failed\n"); return err; } ofdpa_port->internal_vlan_id = ofdpa_port_internal_vlan_id_get(ofdpa_port, ofdpa_port->dev->ifindex); err = ofdpa_port_vlan_add(ofdpa_port, OFDPA_UNTAGGED_VID, 0); if (err) { netdev_err(ofdpa_port->dev, "install untagged VLAN failed\n"); goto err_untagged_vlan; } return 0; err_untagged_vlan: ofdpa_port_ig_tbl(ofdpa_port, OFDPA_OP_FLAG_REMOVE); return err; } static void ofdpa_port_fini(struct rocker_port *rocker_port) { struct ofdpa_port *ofdpa_port = rocker_port->wpriv; ofdpa_port_ig_tbl(ofdpa_port, OFDPA_OP_FLAG_REMOVE); } static int ofdpa_port_open(struct rocker_port *rocker_port) { struct ofdpa_port *ofdpa_port = rocker_port->wpriv; return ofdpa_port_fwd_enable(ofdpa_port, 0); } static void ofdpa_port_stop(struct rocker_port *rocker_port) { struct ofdpa_port *ofdpa_port = rocker_port->wpriv; ofdpa_port_fwd_disable(ofdpa_port, OFDPA_OP_FLAG_NOWAIT); } static int ofdpa_port_attr_stp_state_set(struct rocker_port *rocker_port, u8 state) { struct ofdpa_port *ofdpa_port = rocker_port->wpriv; return ofdpa_port_stp_update(ofdpa_port, 0, state); } static int ofdpa_port_attr_bridge_flags_set(struct rocker_port *rocker_port, unsigned long brport_flags, struct switchdev_trans *trans) { struct ofdpa_port *ofdpa_port = rocker_port->wpriv; unsigned long orig_flags; int err = 0; orig_flags = ofdpa_port->brport_flags; ofdpa_port->brport_flags = brport_flags; if ((orig_flags ^ ofdpa_port->brport_flags) & BR_LEARNING && !switchdev_trans_ph_prepare(trans)) err = rocker_port_set_learning(ofdpa_port->rocker_port, !!(ofdpa_port->brport_flags & BR_LEARNING)); if (switchdev_trans_ph_prepare(trans)) ofdpa_port->brport_flags = orig_flags; return err; } static int ofdpa_port_attr_bridge_flags_get(const struct rocker_port *rocker_port, unsigned long *p_brport_flags) { const struct ofdpa_port *ofdpa_port = rocker_port->wpriv; *p_brport_flags = ofdpa_port->brport_flags; return 0; } static int ofdpa_port_attr_bridge_flags_support_get(const struct rocker_port * rocker_port, unsigned long * p_brport_flags_support) { *p_brport_flags_support = BR_LEARNING; return 0; } static int ofdpa_port_attr_bridge_ageing_time_set(struct rocker_port *rocker_port, u32 ageing_time, struct switchdev_trans *trans) { struct ofdpa_port *ofdpa_port = rocker_port->wpriv; struct ofdpa *ofdpa = ofdpa_port->ofdpa; if (!switchdev_trans_ph_prepare(trans)) { ofdpa_port->ageing_time = clock_t_to_jiffies(ageing_time); if (ofdpa_port->ageing_time < ofdpa->ageing_time) ofdpa->ageing_time = ofdpa_port->ageing_time; mod_timer(&ofdpa_port->ofdpa->fdb_cleanup_timer, jiffies); } return 0; } static int ofdpa_port_obj_vlan_add(struct rocker_port *rocker_port, const struct switchdev_obj_port_vlan *vlan) { struct ofdpa_port *ofdpa_port = rocker_port->wpriv; u16 vid; int err; for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) { err = ofdpa_port_vlan_add(ofdpa_port, vid, vlan->flags); if (err) return err; } return 0; } static int ofdpa_port_obj_vlan_del(struct rocker_port *rocker_port, const struct switchdev_obj_port_vlan *vlan) { struct ofdpa_port *ofdpa_port = rocker_port->wpriv; u16 vid; int err; for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) { err = ofdpa_port_vlan_del(ofdpa_port, vid, vlan->flags); if (err) return err; } return 0; } static int ofdpa_port_obj_fdb_add(struct rocker_port *rocker_port, u16 vid, const unsigned char *addr) { struct ofdpa_port *ofdpa_port = rocker_port->wpriv; __be16 vlan_id = ofdpa_port_vid_to_vlan(ofdpa_port, vid, NULL); if (!ofdpa_port_is_bridged(ofdpa_port)) return -EINVAL; return ofdpa_port_fdb(ofdpa_port, addr, vlan_id, 0); } static int ofdpa_port_obj_fdb_del(struct rocker_port *rocker_port, u16 vid, const unsigned char *addr) { struct ofdpa_port *ofdpa_port = rocker_port->wpriv; __be16 vlan_id = ofdpa_port_vid_to_vlan(ofdpa_port, vid, NULL); int flags = OFDPA_OP_FLAG_REMOVE; if (!ofdpa_port_is_bridged(ofdpa_port)) return -EINVAL; return ofdpa_port_fdb(ofdpa_port, addr, vlan_id, flags); } static int ofdpa_port_bridge_join(struct ofdpa_port *ofdpa_port, struct net_device *bridge) { int err; /* Port is joining bridge, so the internal VLAN for the * port is going to change to the bridge internal VLAN. * Let's remove untagged VLAN (vid=0) from port and * re-add once internal VLAN has changed. */ err = ofdpa_port_vlan_del(ofdpa_port, OFDPA_UNTAGGED_VID, 0); if (err) return err; ofdpa_port_internal_vlan_id_put(ofdpa_port, ofdpa_port->dev->ifindex); ofdpa_port->internal_vlan_id = ofdpa_port_internal_vlan_id_get(ofdpa_port, bridge->ifindex); ofdpa_port->bridge_dev = bridge; return ofdpa_port_vlan_add(ofdpa_port, OFDPA_UNTAGGED_VID, 0); } static int ofdpa_port_bridge_leave(struct ofdpa_port *ofdpa_port) { int err; err = ofdpa_port_vlan_del(ofdpa_port, OFDPA_UNTAGGED_VID, 0); if (err) return err; ofdpa_port_internal_vlan_id_put(ofdpa_port, ofdpa_port->bridge_dev->ifindex); ofdpa_port->internal_vlan_id = ofdpa_port_internal_vlan_id_get(ofdpa_port, ofdpa_port->dev->ifindex); ofdpa_port->bridge_dev = NULL; err = ofdpa_port_vlan_add(ofdpa_port, OFDPA_UNTAGGED_VID, 0); if (err) return err; if (ofdpa_port->dev->flags & IFF_UP) err = ofdpa_port_fwd_enable(ofdpa_port, 0); return err; } static int ofdpa_port_ovs_changed(struct ofdpa_port *ofdpa_port, struct net_device *master) { int err; ofdpa_port->bridge_dev = master; err = ofdpa_port_fwd_disable(ofdpa_port, 0); if (err) return err; err = ofdpa_port_fwd_enable(ofdpa_port, 0); return err; } static int ofdpa_port_master_linked(struct rocker_port *rocker_port, struct net_device *master) { struct ofdpa_port *ofdpa_port = rocker_port->wpriv; int err = 0; if (netif_is_bridge_master(master)) err = ofdpa_port_bridge_join(ofdpa_port, master); else if (netif_is_ovs_master(master)) err = ofdpa_port_ovs_changed(ofdpa_port, master); return err; } static int ofdpa_port_master_unlinked(struct rocker_port *rocker_port, struct net_device *master) { struct ofdpa_port *ofdpa_port = rocker_port->wpriv; int err = 0; if (ofdpa_port_is_bridged(ofdpa_port)) err = ofdpa_port_bridge_leave(ofdpa_port); else if (ofdpa_port_is_ovsed(ofdpa_port)) err = ofdpa_port_ovs_changed(ofdpa_port, NULL); return err; } static int ofdpa_port_neigh_update(struct rocker_port *rocker_port, struct neighbour *n) { struct ofdpa_port *ofdpa_port = rocker_port->wpriv; int flags = (n->nud_state & NUD_VALID ? 0 : OFDPA_OP_FLAG_REMOVE) | OFDPA_OP_FLAG_NOWAIT; __be32 ip_addr = *(__be32 *) n->primary_key; return ofdpa_port_ipv4_neigh(ofdpa_port, flags, ip_addr, n->ha); } static int ofdpa_port_neigh_destroy(struct rocker_port *rocker_port, struct neighbour *n) { struct ofdpa_port *ofdpa_port = rocker_port->wpriv; int flags = OFDPA_OP_FLAG_REMOVE | OFDPA_OP_FLAG_NOWAIT; __be32 ip_addr = *(__be32 *) n->primary_key; return ofdpa_port_ipv4_neigh(ofdpa_port, flags, ip_addr, n->ha); } static int ofdpa_port_ev_mac_vlan_seen(struct rocker_port *rocker_port, const unsigned char *addr, __be16 vlan_id) { struct ofdpa_port *ofdpa_port = rocker_port->wpriv; int flags = OFDPA_OP_FLAG_NOWAIT | OFDPA_OP_FLAG_LEARNED; if (ofdpa_port->stp_state != BR_STATE_LEARNING && ofdpa_port->stp_state != BR_STATE_FORWARDING) return 0; return ofdpa_port_fdb(ofdpa_port, addr, vlan_id, flags); } static struct ofdpa_port *ofdpa_port_dev_lower_find(struct net_device *dev, struct rocker *rocker) { struct rocker_port *rocker_port; rocker_port = rocker_port_dev_lower_find(dev, rocker); return rocker_port ? rocker_port->wpriv : NULL; } static int ofdpa_fib4_add(struct rocker *rocker, const struct fib_entry_notifier_info *fen_info) { struct ofdpa *ofdpa = rocker->wpriv; struct ofdpa_port *ofdpa_port; int err; if (ofdpa->fib_aborted) return 0; ofdpa_port = ofdpa_port_dev_lower_find(fen_info->fi->fib_dev, rocker); if (!ofdpa_port) return 0; err = ofdpa_port_fib_ipv4(ofdpa_port, htonl(fen_info->dst), fen_info->dst_len, fen_info->fi, fen_info->tb_id, 0); if (err) return err; fen_info->fi->fib_nh->nh_flags |= RTNH_F_OFFLOAD; return 0; } static int ofdpa_fib4_del(struct rocker *rocker, const struct fib_entry_notifier_info *fen_info) { struct ofdpa *ofdpa = rocker->wpriv; struct ofdpa_port *ofdpa_port; if (ofdpa->fib_aborted) return 0; ofdpa_port = ofdpa_port_dev_lower_find(fen_info->fi->fib_dev, rocker); if (!ofdpa_port) return 0; fen_info->fi->fib_nh->nh_flags &= ~RTNH_F_OFFLOAD; return ofdpa_port_fib_ipv4(ofdpa_port, htonl(fen_info->dst), fen_info->dst_len, fen_info->fi, fen_info->tb_id, OFDPA_OP_FLAG_REMOVE); } static void ofdpa_fib4_abort(struct rocker *rocker) { struct ofdpa *ofdpa = rocker->wpriv; struct ofdpa_port *ofdpa_port; struct ofdpa_flow_tbl_entry *flow_entry; struct hlist_node *tmp; unsigned long flags; int bkt; if (ofdpa->fib_aborted) return; spin_lock_irqsave(&ofdpa->flow_tbl_lock, flags); hash_for_each_safe(ofdpa->flow_tbl, bkt, tmp, flow_entry, entry) { if (flow_entry->key.tbl_id != ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING) continue; ofdpa_port = ofdpa_port_dev_lower_find(flow_entry->fi->fib_dev, rocker); if (!ofdpa_port) continue; flow_entry->fi->fib_nh->nh_flags &= ~RTNH_F_OFFLOAD; ofdpa_flow_tbl_del(ofdpa_port, OFDPA_OP_FLAG_REMOVE, flow_entry); } spin_unlock_irqrestore(&ofdpa->flow_tbl_lock, flags); ofdpa->fib_aborted = true; } struct rocker_world_ops rocker_ofdpa_ops = { .kind = "ofdpa", .priv_size = sizeof(struct ofdpa), .port_priv_size = sizeof(struct ofdpa_port), .mode = ROCKER_PORT_MODE_OF_DPA, .init = ofdpa_init, .fini = ofdpa_fini, .port_pre_init = ofdpa_port_pre_init, .port_init = ofdpa_port_init, .port_fini = ofdpa_port_fini, .port_open = ofdpa_port_open, .port_stop = ofdpa_port_stop, .port_attr_stp_state_set = ofdpa_port_attr_stp_state_set, .port_attr_bridge_flags_set = ofdpa_port_attr_bridge_flags_set, .port_attr_bridge_flags_get = ofdpa_port_attr_bridge_flags_get, .port_attr_bridge_flags_support_get = ofdpa_port_attr_bridge_flags_support_get, .port_attr_bridge_ageing_time_set = ofdpa_port_attr_bridge_ageing_time_set, .port_obj_vlan_add = ofdpa_port_obj_vlan_add, .port_obj_vlan_del = ofdpa_port_obj_vlan_del, .port_obj_fdb_add = ofdpa_port_obj_fdb_add, .port_obj_fdb_del = ofdpa_port_obj_fdb_del, .port_master_linked = ofdpa_port_master_linked, .port_master_unlinked = ofdpa_port_master_unlinked, .port_neigh_update = ofdpa_port_neigh_update, .port_neigh_destroy = ofdpa_port_neigh_destroy, .port_ev_mac_vlan_seen = ofdpa_port_ev_mac_vlan_seen, .fib4_add = ofdpa_fib4_add, .fib4_del = ofdpa_fib4_del, .fib4_abort = ofdpa_fib4_abort, };
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1