| /* |
| * 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, |
| }; |