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