|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* Copyright (C) 2018-2020, Intel Corporation. */ | 
|  |  | 
|  | #include "ice.h" | 
|  |  | 
|  | /** | 
|  | * ice_is_arfs_active - helper to check is aRFS is active | 
|  | * @vsi: VSI to check | 
|  | */ | 
|  | static bool ice_is_arfs_active(struct ice_vsi *vsi) | 
|  | { | 
|  | return !!vsi->arfs_fltr_list; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ice_is_arfs_using_perfect_flow - check if aRFS has active perfect filters | 
|  | * @hw: pointer to the HW structure | 
|  | * @flow_type: flow type as Flow Director understands it | 
|  | * | 
|  | * Flow Director will query this function to see if aRFS is currently using | 
|  | * the specified flow_type for perfect (4-tuple) filters. | 
|  | */ | 
|  | bool | 
|  | ice_is_arfs_using_perfect_flow(struct ice_hw *hw, enum ice_fltr_ptype flow_type) | 
|  | { | 
|  | struct ice_arfs_active_fltr_cntrs *arfs_fltr_cntrs; | 
|  | struct ice_pf *pf = hw->back; | 
|  | struct ice_vsi *vsi; | 
|  |  | 
|  | vsi = ice_get_main_vsi(pf); | 
|  | if (!vsi) | 
|  | return false; | 
|  |  | 
|  | arfs_fltr_cntrs = vsi->arfs_fltr_cntrs; | 
|  |  | 
|  | /* active counters can be updated by multiple CPUs */ | 
|  | smp_mb__before_atomic(); | 
|  | switch (flow_type) { | 
|  | case ICE_FLTR_PTYPE_NONF_IPV4_UDP: | 
|  | return atomic_read(&arfs_fltr_cntrs->active_udpv4_cnt) > 0; | 
|  | case ICE_FLTR_PTYPE_NONF_IPV6_UDP: | 
|  | return atomic_read(&arfs_fltr_cntrs->active_udpv6_cnt) > 0; | 
|  | case ICE_FLTR_PTYPE_NONF_IPV4_TCP: | 
|  | return atomic_read(&arfs_fltr_cntrs->active_tcpv4_cnt) > 0; | 
|  | case ICE_FLTR_PTYPE_NONF_IPV6_TCP: | 
|  | return atomic_read(&arfs_fltr_cntrs->active_tcpv6_cnt) > 0; | 
|  | default: | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ice_arfs_update_active_fltr_cntrs - update active filter counters for aRFS | 
|  | * @vsi: VSI that aRFS is active on | 
|  | * @entry: aRFS entry used to change counters | 
|  | * @add: true to increment counter, false to decrement | 
|  | */ | 
|  | static void | 
|  | ice_arfs_update_active_fltr_cntrs(struct ice_vsi *vsi, | 
|  | struct ice_arfs_entry *entry, bool add) | 
|  | { | 
|  | struct ice_arfs_active_fltr_cntrs *fltr_cntrs = vsi->arfs_fltr_cntrs; | 
|  |  | 
|  | switch (entry->fltr_info.flow_type) { | 
|  | case ICE_FLTR_PTYPE_NONF_IPV4_TCP: | 
|  | if (add) | 
|  | atomic_inc(&fltr_cntrs->active_tcpv4_cnt); | 
|  | else | 
|  | atomic_dec(&fltr_cntrs->active_tcpv4_cnt); | 
|  | break; | 
|  | case ICE_FLTR_PTYPE_NONF_IPV6_TCP: | 
|  | if (add) | 
|  | atomic_inc(&fltr_cntrs->active_tcpv6_cnt); | 
|  | else | 
|  | atomic_dec(&fltr_cntrs->active_tcpv6_cnt); | 
|  | break; | 
|  | case ICE_FLTR_PTYPE_NONF_IPV4_UDP: | 
|  | if (add) | 
|  | atomic_inc(&fltr_cntrs->active_udpv4_cnt); | 
|  | else | 
|  | atomic_dec(&fltr_cntrs->active_udpv4_cnt); | 
|  | break; | 
|  | case ICE_FLTR_PTYPE_NONF_IPV6_UDP: | 
|  | if (add) | 
|  | atomic_inc(&fltr_cntrs->active_udpv6_cnt); | 
|  | else | 
|  | atomic_dec(&fltr_cntrs->active_udpv6_cnt); | 
|  | break; | 
|  | default: | 
|  | dev_err(ice_pf_to_dev(vsi->back), "aRFS: Failed to update filter counters, invalid filter type %d\n", | 
|  | entry->fltr_info.flow_type); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ice_arfs_del_flow_rules - delete the rules passed in from HW | 
|  | * @vsi: VSI for the flow rules that need to be deleted | 
|  | * @del_list_head: head of the list of ice_arfs_entry(s) for rule deletion | 
|  | * | 
|  | * Loop through the delete list passed in and remove the rules from HW. After | 
|  | * each rule is deleted, disconnect and free the ice_arfs_entry because it is no | 
|  | * longer being referenced by the aRFS hash table. | 
|  | */ | 
|  | static void | 
|  | ice_arfs_del_flow_rules(struct ice_vsi *vsi, struct hlist_head *del_list_head) | 
|  | { | 
|  | struct ice_arfs_entry *e; | 
|  | struct hlist_node *n; | 
|  | struct device *dev; | 
|  |  | 
|  | dev = ice_pf_to_dev(vsi->back); | 
|  |  | 
|  | hlist_for_each_entry_safe(e, n, del_list_head, list_entry) { | 
|  | int result; | 
|  |  | 
|  | result = ice_fdir_write_fltr(vsi->back, &e->fltr_info, false, | 
|  | false); | 
|  | if (!result) | 
|  | ice_arfs_update_active_fltr_cntrs(vsi, e, false); | 
|  | else | 
|  | dev_dbg(dev, "Unable to delete aRFS entry, err %d fltr_state %d fltr_id %d flow_id %d Q %d\n", | 
|  | result, e->fltr_state, e->fltr_info.fltr_id, | 
|  | e->flow_id, e->fltr_info.q_index); | 
|  |  | 
|  | /* The aRFS hash table is no longer referencing this entry */ | 
|  | hlist_del(&e->list_entry); | 
|  | devm_kfree(dev, e); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ice_arfs_add_flow_rules - add the rules passed in from HW | 
|  | * @vsi: VSI for the flow rules that need to be added | 
|  | * @add_list_head: head of the list of ice_arfs_entry_ptr(s) for rule addition | 
|  | * | 
|  | * Loop through the add list passed in and remove the rules from HW. After each | 
|  | * rule is added, disconnect and free the ice_arfs_entry_ptr node. Don't free | 
|  | * the ice_arfs_entry(s) because they are still being referenced in the aRFS | 
|  | * hash table. | 
|  | */ | 
|  | static void | 
|  | ice_arfs_add_flow_rules(struct ice_vsi *vsi, struct hlist_head *add_list_head) | 
|  | { | 
|  | struct ice_arfs_entry_ptr *ep; | 
|  | struct hlist_node *n; | 
|  | struct device *dev; | 
|  |  | 
|  | dev = ice_pf_to_dev(vsi->back); | 
|  |  | 
|  | hlist_for_each_entry_safe(ep, n, add_list_head, list_entry) { | 
|  | int result; | 
|  |  | 
|  | result = ice_fdir_write_fltr(vsi->back, | 
|  | &ep->arfs_entry->fltr_info, true, | 
|  | false); | 
|  | if (!result) | 
|  | ice_arfs_update_active_fltr_cntrs(vsi, ep->arfs_entry, | 
|  | true); | 
|  | else | 
|  | dev_dbg(dev, "Unable to add aRFS entry, err %d fltr_state %d fltr_id %d flow_id %d Q %d\n", | 
|  | result, ep->arfs_entry->fltr_state, | 
|  | ep->arfs_entry->fltr_info.fltr_id, | 
|  | ep->arfs_entry->flow_id, | 
|  | ep->arfs_entry->fltr_info.q_index); | 
|  |  | 
|  | hlist_del(&ep->list_entry); | 
|  | devm_kfree(dev, ep); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ice_arfs_is_flow_expired - check if the aRFS entry has expired | 
|  | * @vsi: VSI containing the aRFS entry | 
|  | * @arfs_entry: aRFS entry that's being checked for expiration | 
|  | * | 
|  | * Return true if the flow has expired, else false. This function should be used | 
|  | * to determine whether or not an aRFS entry should be removed from the hardware | 
|  | * and software structures. | 
|  | */ | 
|  | static bool | 
|  | ice_arfs_is_flow_expired(struct ice_vsi *vsi, struct ice_arfs_entry *arfs_entry) | 
|  | { | 
|  | #define ICE_ARFS_TIME_DELTA_EXPIRATION	msecs_to_jiffies(5000) | 
|  | if (rps_may_expire_flow(vsi->netdev, arfs_entry->fltr_info.q_index, | 
|  | arfs_entry->flow_id, | 
|  | arfs_entry->fltr_info.fltr_id)) | 
|  | return true; | 
|  |  | 
|  | /* expiration timer only used for UDP filters */ | 
|  | if (arfs_entry->fltr_info.flow_type != ICE_FLTR_PTYPE_NONF_IPV4_UDP && | 
|  | arfs_entry->fltr_info.flow_type != ICE_FLTR_PTYPE_NONF_IPV6_UDP) | 
|  | return false; | 
|  |  | 
|  | return time_in_range64(arfs_entry->time_activated + | 
|  | ICE_ARFS_TIME_DELTA_EXPIRATION, | 
|  | arfs_entry->time_activated, get_jiffies_64()); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ice_arfs_update_flow_rules - add/delete aRFS rules in HW | 
|  | * @vsi: the VSI to be forwarded to | 
|  | * @idx: index into the table of aRFS filter lists. Obtained from skb->hash | 
|  | * @add_list: list to populate with filters to be added to Flow Director | 
|  | * @del_list: list to populate with filters to be deleted from Flow Director | 
|  | * | 
|  | * Iterate over the hlist at the index given in the aRFS hash table and | 
|  | * determine if there are any aRFS entries that need to be either added or | 
|  | * deleted in the HW. If the aRFS entry is marked as ICE_ARFS_INACTIVE the | 
|  | * filter needs to be added to HW, else if it's marked as ICE_ARFS_ACTIVE and | 
|  | * the flow has expired delete the filter from HW. The caller of this function | 
|  | * is expected to add/delete rules on the add_list/del_list respectively. | 
|  | */ | 
|  | static void | 
|  | ice_arfs_update_flow_rules(struct ice_vsi *vsi, u16 idx, | 
|  | struct hlist_head *add_list, | 
|  | struct hlist_head *del_list) | 
|  | { | 
|  | struct ice_arfs_entry *e; | 
|  | struct hlist_node *n; | 
|  | struct device *dev; | 
|  |  | 
|  | dev = ice_pf_to_dev(vsi->back); | 
|  |  | 
|  | /* go through the aRFS hlist at this idx and check for needed updates */ | 
|  | hlist_for_each_entry_safe(e, n, &vsi->arfs_fltr_list[idx], list_entry) | 
|  | /* check if filter needs to be added to HW */ | 
|  | if (e->fltr_state == ICE_ARFS_INACTIVE) { | 
|  | enum ice_fltr_ptype flow_type = e->fltr_info.flow_type; | 
|  | struct ice_arfs_entry_ptr *ep = | 
|  | devm_kzalloc(dev, sizeof(*ep), GFP_ATOMIC); | 
|  |  | 
|  | if (!ep) | 
|  | continue; | 
|  | INIT_HLIST_NODE(&ep->list_entry); | 
|  | /* reference aRFS entry to add HW filter */ | 
|  | ep->arfs_entry = e; | 
|  | hlist_add_head(&ep->list_entry, add_list); | 
|  | e->fltr_state = ICE_ARFS_ACTIVE; | 
|  | /* expiration timer only used for UDP flows */ | 
|  | if (flow_type == ICE_FLTR_PTYPE_NONF_IPV4_UDP || | 
|  | flow_type == ICE_FLTR_PTYPE_NONF_IPV6_UDP) | 
|  | e->time_activated = get_jiffies_64(); | 
|  | } else if (e->fltr_state == ICE_ARFS_ACTIVE) { | 
|  | /* check if filter needs to be removed from HW */ | 
|  | if (ice_arfs_is_flow_expired(vsi, e)) { | 
|  | /* remove aRFS entry from hash table for delete | 
|  | * and to prevent referencing it the next time | 
|  | * through this hlist index | 
|  | */ | 
|  | hlist_del(&e->list_entry); | 
|  | e->fltr_state = ICE_ARFS_TODEL; | 
|  | /* save reference to aRFS entry for delete */ | 
|  | hlist_add_head(&e->list_entry, del_list); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ice_sync_arfs_fltrs - update all aRFS filters | 
|  | * @pf: board private structure | 
|  | */ | 
|  | void ice_sync_arfs_fltrs(struct ice_pf *pf) | 
|  | { | 
|  | HLIST_HEAD(tmp_del_list); | 
|  | HLIST_HEAD(tmp_add_list); | 
|  | struct ice_vsi *pf_vsi; | 
|  | unsigned int i; | 
|  |  | 
|  | pf_vsi = ice_get_main_vsi(pf); | 
|  | if (!pf_vsi) | 
|  | return; | 
|  |  | 
|  | if (!ice_is_arfs_active(pf_vsi)) | 
|  | return; | 
|  |  | 
|  | spin_lock_bh(&pf_vsi->arfs_lock); | 
|  | /* Once we process aRFS for the PF VSI get out */ | 
|  | for (i = 0; i < ICE_MAX_ARFS_LIST; i++) | 
|  | ice_arfs_update_flow_rules(pf_vsi, i, &tmp_add_list, | 
|  | &tmp_del_list); | 
|  | spin_unlock_bh(&pf_vsi->arfs_lock); | 
|  |  | 
|  | /* use list of ice_arfs_entry(s) for delete */ | 
|  | ice_arfs_del_flow_rules(pf_vsi, &tmp_del_list); | 
|  |  | 
|  | /* use list of ice_arfs_entry_ptr(s) for add */ | 
|  | ice_arfs_add_flow_rules(pf_vsi, &tmp_add_list); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ice_arfs_build_entry - builds an aRFS entry based on input | 
|  | * @vsi: destination VSI for this flow | 
|  | * @fk: flow dissector keys for creating the tuple | 
|  | * @rxq_idx: Rx queue to steer this flow to | 
|  | * @flow_id: passed down from the stack and saved for flow expiration | 
|  | * | 
|  | * returns an aRFS entry on success and NULL on failure | 
|  | */ | 
|  | static struct ice_arfs_entry * | 
|  | ice_arfs_build_entry(struct ice_vsi *vsi, const struct flow_keys *fk, | 
|  | u16 rxq_idx, u32 flow_id) | 
|  | { | 
|  | struct ice_arfs_entry *arfs_entry; | 
|  | struct ice_fdir_fltr *fltr_info; | 
|  | u8 ip_proto; | 
|  |  | 
|  | arfs_entry = devm_kzalloc(ice_pf_to_dev(vsi->back), | 
|  | sizeof(*arfs_entry), | 
|  | GFP_ATOMIC | __GFP_NOWARN); | 
|  | if (!arfs_entry) | 
|  | return NULL; | 
|  |  | 
|  | fltr_info = &arfs_entry->fltr_info; | 
|  | fltr_info->q_index = rxq_idx; | 
|  | fltr_info->dest_ctl = ICE_FLTR_PRGM_DESC_DEST_DIRECT_PKT_QINDEX; | 
|  | fltr_info->dest_vsi = vsi->idx; | 
|  | ip_proto = fk->basic.ip_proto; | 
|  |  | 
|  | if (fk->basic.n_proto == htons(ETH_P_IP)) { | 
|  | fltr_info->ip.v4.proto = ip_proto; | 
|  | fltr_info->flow_type = (ip_proto == IPPROTO_TCP) ? | 
|  | ICE_FLTR_PTYPE_NONF_IPV4_TCP : | 
|  | ICE_FLTR_PTYPE_NONF_IPV4_UDP; | 
|  | fltr_info->ip.v4.src_ip = fk->addrs.v4addrs.src; | 
|  | fltr_info->ip.v4.dst_ip = fk->addrs.v4addrs.dst; | 
|  | fltr_info->ip.v4.src_port = fk->ports.src; | 
|  | fltr_info->ip.v4.dst_port = fk->ports.dst; | 
|  | } else { /* ETH_P_IPV6 */ | 
|  | fltr_info->ip.v6.proto = ip_proto; | 
|  | fltr_info->flow_type = (ip_proto == IPPROTO_TCP) ? | 
|  | ICE_FLTR_PTYPE_NONF_IPV6_TCP : | 
|  | ICE_FLTR_PTYPE_NONF_IPV6_UDP; | 
|  | memcpy(&fltr_info->ip.v6.src_ip, &fk->addrs.v6addrs.src, | 
|  | sizeof(struct in6_addr)); | 
|  | memcpy(&fltr_info->ip.v6.dst_ip, &fk->addrs.v6addrs.dst, | 
|  | sizeof(struct in6_addr)); | 
|  | fltr_info->ip.v6.src_port = fk->ports.src; | 
|  | fltr_info->ip.v6.dst_port = fk->ports.dst; | 
|  | } | 
|  |  | 
|  | arfs_entry->flow_id = flow_id; | 
|  | fltr_info->fltr_id = | 
|  | atomic_inc_return(vsi->arfs_last_fltr_id) % RPS_NO_FILTER; | 
|  |  | 
|  | return arfs_entry; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ice_arfs_is_perfect_flow_set - Check to see if perfect flow is set | 
|  | * @hw: pointer to HW structure | 
|  | * @l3_proto: ETH_P_IP or ETH_P_IPV6 in network order | 
|  | * @l4_proto: IPPROTO_UDP or IPPROTO_TCP | 
|  | * | 
|  | * We only support perfect (4-tuple) filters for aRFS. This function allows aRFS | 
|  | * to check if perfect (4-tuple) flow rules are currently in place by Flow | 
|  | * Director. | 
|  | */ | 
|  | static bool | 
|  | ice_arfs_is_perfect_flow_set(struct ice_hw *hw, __be16 l3_proto, u8 l4_proto) | 
|  | { | 
|  | unsigned long *perfect_fltr = hw->fdir_perfect_fltr; | 
|  |  | 
|  | /* advanced Flow Director disabled, perfect filters always supported */ | 
|  | if (!perfect_fltr) | 
|  | return true; | 
|  |  | 
|  | if (l3_proto == htons(ETH_P_IP) && l4_proto == IPPROTO_UDP) | 
|  | return test_bit(ICE_FLTR_PTYPE_NONF_IPV4_UDP, perfect_fltr); | 
|  | else if (l3_proto == htons(ETH_P_IP) && l4_proto == IPPROTO_TCP) | 
|  | return test_bit(ICE_FLTR_PTYPE_NONF_IPV4_TCP, perfect_fltr); | 
|  | else if (l3_proto == htons(ETH_P_IPV6) && l4_proto == IPPROTO_UDP) | 
|  | return test_bit(ICE_FLTR_PTYPE_NONF_IPV6_UDP, perfect_fltr); | 
|  | else if (l3_proto == htons(ETH_P_IPV6) && l4_proto == IPPROTO_TCP) | 
|  | return test_bit(ICE_FLTR_PTYPE_NONF_IPV6_TCP, perfect_fltr); | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ice_arfs_cmp - Check if aRFS filter matches this flow. | 
|  | * @fltr_info: filter info of the saved ARFS entry. | 
|  | * @fk: flow dissector keys. | 
|  | * @n_proto:  One of htons(ETH_P_IP) or htons(ETH_P_IPV6). | 
|  | * @ip_proto: One of IPPROTO_TCP or IPPROTO_UDP. | 
|  | * | 
|  | * Since this function assumes limited values for n_proto and ip_proto, it | 
|  | * is meant to be called only from ice_rx_flow_steer(). | 
|  | * | 
|  | * Return: | 
|  | * * true	- fltr_info refers to the same flow as fk. | 
|  | * * false	- fltr_info and fk refer to different flows. | 
|  | */ | 
|  | static bool | 
|  | ice_arfs_cmp(const struct ice_fdir_fltr *fltr_info, const struct flow_keys *fk, | 
|  | __be16 n_proto, u8 ip_proto) | 
|  | { | 
|  | /* Determine if the filter is for IPv4 or IPv6 based on flow_type, | 
|  | * which is one of ICE_FLTR_PTYPE_NONF_IPV{4,6}_{TCP,UDP}. | 
|  | */ | 
|  | bool is_v4 = fltr_info->flow_type == ICE_FLTR_PTYPE_NONF_IPV4_TCP || | 
|  | fltr_info->flow_type == ICE_FLTR_PTYPE_NONF_IPV4_UDP; | 
|  |  | 
|  | /* Following checks are arranged in the quickest and most discriminative | 
|  | * fields first for early failure. | 
|  | */ | 
|  | if (is_v4) | 
|  | return n_proto == htons(ETH_P_IP) && | 
|  | fltr_info->ip.v4.src_port == fk->ports.src && | 
|  | fltr_info->ip.v4.dst_port == fk->ports.dst && | 
|  | fltr_info->ip.v4.src_ip == fk->addrs.v4addrs.src && | 
|  | fltr_info->ip.v4.dst_ip == fk->addrs.v4addrs.dst && | 
|  | fltr_info->ip.v4.proto == ip_proto; | 
|  |  | 
|  | return fltr_info->ip.v6.src_port == fk->ports.src && | 
|  | fltr_info->ip.v6.dst_port == fk->ports.dst && | 
|  | fltr_info->ip.v6.proto == ip_proto && | 
|  | !memcmp(&fltr_info->ip.v6.src_ip, &fk->addrs.v6addrs.src, | 
|  | sizeof(struct in6_addr)) && | 
|  | !memcmp(&fltr_info->ip.v6.dst_ip, &fk->addrs.v6addrs.dst, | 
|  | sizeof(struct in6_addr)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ice_rx_flow_steer - steer the Rx flow to where application is being run | 
|  | * @netdev: ptr to the netdev being adjusted | 
|  | * @skb: buffer with required header information | 
|  | * @rxq_idx: queue to which the flow needs to move | 
|  | * @flow_id: flow identifier provided by the netdev | 
|  | * | 
|  | * Based on the skb, rxq_idx, and flow_id passed in add/update an entry in the | 
|  | * aRFS hash table. Iterate over one of the hlists in the aRFS hash table and | 
|  | * if the flow_id already exists in the hash table but the rxq_idx has changed | 
|  | * mark the entry as ICE_ARFS_INACTIVE so it can get updated in HW, else | 
|  | * if the entry is marked as ICE_ARFS_TODEL delete it from the aRFS hash table. | 
|  | * If neither of the previous conditions are true then add a new entry in the | 
|  | * aRFS hash table, which gets set to ICE_ARFS_INACTIVE by default so it can be | 
|  | * added to HW. | 
|  | */ | 
|  | int | 
|  | ice_rx_flow_steer(struct net_device *netdev, const struct sk_buff *skb, | 
|  | u16 rxq_idx, u32 flow_id) | 
|  | { | 
|  | struct ice_netdev_priv *np = netdev_priv(netdev); | 
|  | struct ice_arfs_entry *arfs_entry; | 
|  | struct ice_vsi *vsi = np->vsi; | 
|  | struct flow_keys fk; | 
|  | struct ice_pf *pf; | 
|  | __be16 n_proto; | 
|  | u8 ip_proto; | 
|  | u16 idx; | 
|  | int ret; | 
|  |  | 
|  | /* failed to allocate memory for aRFS so don't crash */ | 
|  | if (unlikely(!vsi->arfs_fltr_list)) | 
|  | return -ENODEV; | 
|  |  | 
|  | pf = vsi->back; | 
|  |  | 
|  | if (skb->encapsulation) | 
|  | return -EPROTONOSUPPORT; | 
|  |  | 
|  | if (!skb_flow_dissect_flow_keys(skb, &fk, 0)) | 
|  | return -EPROTONOSUPPORT; | 
|  |  | 
|  | n_proto = fk.basic.n_proto; | 
|  | /* Support only IPV4 and IPV6 */ | 
|  | if ((n_proto == htons(ETH_P_IP) && !ip_is_fragment(ip_hdr(skb))) || | 
|  | n_proto == htons(ETH_P_IPV6)) | 
|  | ip_proto = fk.basic.ip_proto; | 
|  | else | 
|  | return -EPROTONOSUPPORT; | 
|  |  | 
|  | /* Support only TCP and UDP */ | 
|  | if (ip_proto != IPPROTO_TCP && ip_proto != IPPROTO_UDP) | 
|  | return -EPROTONOSUPPORT; | 
|  |  | 
|  | /* only support 4-tuple filters for aRFS */ | 
|  | if (!ice_arfs_is_perfect_flow_set(&pf->hw, n_proto, ip_proto)) | 
|  | return -EOPNOTSUPP; | 
|  |  | 
|  | /* choose the aRFS list bucket based on skb hash */ | 
|  | idx = skb_get_hash_raw(skb) & ICE_ARFS_LST_MASK; | 
|  | /* search for entry in the bucket */ | 
|  | spin_lock_bh(&vsi->arfs_lock); | 
|  | hlist_for_each_entry(arfs_entry, &vsi->arfs_fltr_list[idx], | 
|  | list_entry) { | 
|  | struct ice_fdir_fltr *fltr_info; | 
|  |  | 
|  | /* keep searching for the already existing arfs_entry flow */ | 
|  | if (arfs_entry->flow_id != flow_id) | 
|  | continue; | 
|  |  | 
|  | fltr_info = &arfs_entry->fltr_info; | 
|  |  | 
|  | if (!ice_arfs_cmp(fltr_info, &fk, n_proto, ip_proto)) | 
|  | continue; | 
|  |  | 
|  | ret = fltr_info->fltr_id; | 
|  |  | 
|  | if (fltr_info->q_index == rxq_idx || | 
|  | arfs_entry->fltr_state != ICE_ARFS_ACTIVE) | 
|  | goto out; | 
|  |  | 
|  | /* update the queue to forward to on an already existing flow */ | 
|  | fltr_info->q_index = rxq_idx; | 
|  | arfs_entry->fltr_state = ICE_ARFS_INACTIVE; | 
|  | ice_arfs_update_active_fltr_cntrs(vsi, arfs_entry, false); | 
|  | goto out_schedule_service_task; | 
|  | } | 
|  |  | 
|  | arfs_entry = ice_arfs_build_entry(vsi, &fk, rxq_idx, flow_id); | 
|  | if (!arfs_entry) { | 
|  | ret = -ENOMEM; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | ret = arfs_entry->fltr_info.fltr_id; | 
|  | INIT_HLIST_NODE(&arfs_entry->list_entry); | 
|  | hlist_add_head(&arfs_entry->list_entry, &vsi->arfs_fltr_list[idx]); | 
|  | out_schedule_service_task: | 
|  | ice_service_task_schedule(pf); | 
|  | out: | 
|  | spin_unlock_bh(&vsi->arfs_lock); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ice_init_arfs_cntrs - initialize aRFS counter values | 
|  | * @vsi: VSI that aRFS counters need to be initialized on | 
|  | */ | 
|  | static int ice_init_arfs_cntrs(struct ice_vsi *vsi) | 
|  | { | 
|  | if (!vsi || vsi->type != ICE_VSI_PF) | 
|  | return -EINVAL; | 
|  |  | 
|  | vsi->arfs_fltr_cntrs = kzalloc(sizeof(*vsi->arfs_fltr_cntrs), | 
|  | GFP_KERNEL); | 
|  | if (!vsi->arfs_fltr_cntrs) | 
|  | return -ENOMEM; | 
|  |  | 
|  | vsi->arfs_last_fltr_id = kzalloc(sizeof(*vsi->arfs_last_fltr_id), | 
|  | GFP_KERNEL); | 
|  | if (!vsi->arfs_last_fltr_id) { | 
|  | kfree(vsi->arfs_fltr_cntrs); | 
|  | vsi->arfs_fltr_cntrs = NULL; | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ice_init_arfs - initialize aRFS resources | 
|  | * @vsi: the VSI to be forwarded to | 
|  | */ | 
|  | void ice_init_arfs(struct ice_vsi *vsi) | 
|  | { | 
|  | struct hlist_head *arfs_fltr_list; | 
|  | unsigned int i; | 
|  |  | 
|  | if (!vsi || vsi->type != ICE_VSI_PF || ice_is_arfs_active(vsi)) | 
|  | return; | 
|  |  | 
|  | arfs_fltr_list = kcalloc(ICE_MAX_ARFS_LIST, sizeof(*arfs_fltr_list), | 
|  | GFP_KERNEL); | 
|  | if (!arfs_fltr_list) | 
|  | return; | 
|  |  | 
|  | if (ice_init_arfs_cntrs(vsi)) | 
|  | goto free_arfs_fltr_list; | 
|  |  | 
|  | for (i = 0; i < ICE_MAX_ARFS_LIST; i++) | 
|  | INIT_HLIST_HEAD(&arfs_fltr_list[i]); | 
|  |  | 
|  | spin_lock_init(&vsi->arfs_lock); | 
|  |  | 
|  | vsi->arfs_fltr_list = arfs_fltr_list; | 
|  |  | 
|  | return; | 
|  |  | 
|  | free_arfs_fltr_list: | 
|  | kfree(arfs_fltr_list); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ice_clear_arfs - clear the aRFS hash table and any memory used for aRFS | 
|  | * @vsi: the VSI to be forwarded to | 
|  | */ | 
|  | void ice_clear_arfs(struct ice_vsi *vsi) | 
|  | { | 
|  | struct device *dev; | 
|  | unsigned int i; | 
|  |  | 
|  | if (!vsi || vsi->type != ICE_VSI_PF || !vsi->back || | 
|  | !vsi->arfs_fltr_list) | 
|  | return; | 
|  |  | 
|  | dev = ice_pf_to_dev(vsi->back); | 
|  | for (i = 0; i < ICE_MAX_ARFS_LIST; i++) { | 
|  | struct ice_arfs_entry *r; | 
|  | struct hlist_node *n; | 
|  |  | 
|  | spin_lock_bh(&vsi->arfs_lock); | 
|  | hlist_for_each_entry_safe(r, n, &vsi->arfs_fltr_list[i], | 
|  | list_entry) { | 
|  | hlist_del(&r->list_entry); | 
|  | devm_kfree(dev, r); | 
|  | } | 
|  | spin_unlock_bh(&vsi->arfs_lock); | 
|  | } | 
|  |  | 
|  | kfree(vsi->arfs_fltr_list); | 
|  | vsi->arfs_fltr_list = NULL; | 
|  | kfree(vsi->arfs_last_fltr_id); | 
|  | vsi->arfs_last_fltr_id = NULL; | 
|  | kfree(vsi->arfs_fltr_cntrs); | 
|  | vsi->arfs_fltr_cntrs = NULL; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ice_free_cpu_rx_rmap - free setup CPU reverse map | 
|  | * @vsi: the VSI to be forwarded to | 
|  | */ | 
|  | void ice_free_cpu_rx_rmap(struct ice_vsi *vsi) | 
|  | { | 
|  | struct net_device *netdev; | 
|  |  | 
|  | if (!vsi || vsi->type != ICE_VSI_PF) | 
|  | return; | 
|  |  | 
|  | netdev = vsi->netdev; | 
|  | if (!netdev || !netdev->rx_cpu_rmap) | 
|  | return; | 
|  |  | 
|  | free_irq_cpu_rmap(netdev->rx_cpu_rmap); | 
|  | netdev->rx_cpu_rmap = NULL; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ice_set_cpu_rx_rmap - setup CPU reverse map for each queue | 
|  | * @vsi: the VSI to be forwarded to | 
|  | */ | 
|  | int ice_set_cpu_rx_rmap(struct ice_vsi *vsi) | 
|  | { | 
|  | struct net_device *netdev; | 
|  | struct ice_pf *pf; | 
|  | int i; | 
|  |  | 
|  | if (!vsi || vsi->type != ICE_VSI_PF) | 
|  | return 0; | 
|  |  | 
|  | pf = vsi->back; | 
|  | netdev = vsi->netdev; | 
|  | if (!pf || !netdev || !vsi->num_q_vectors) | 
|  | return -EINVAL; | 
|  |  | 
|  | netdev_dbg(netdev, "Setup CPU RMAP: vsi type 0x%x, ifname %s, q_vectors %d\n", | 
|  | vsi->type, netdev->name, vsi->num_q_vectors); | 
|  |  | 
|  | netdev->rx_cpu_rmap = alloc_irq_cpu_rmap(vsi->num_q_vectors); | 
|  | if (unlikely(!netdev->rx_cpu_rmap)) | 
|  | return -EINVAL; | 
|  |  | 
|  | ice_for_each_q_vector(vsi, i) | 
|  | if (irq_cpu_rmap_add(netdev->rx_cpu_rmap, | 
|  | vsi->q_vectors[i]->irq.virq)) { | 
|  | ice_free_cpu_rx_rmap(vsi); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ice_remove_arfs - remove/clear all aRFS resources | 
|  | * @pf: device private structure | 
|  | */ | 
|  | void ice_remove_arfs(struct ice_pf *pf) | 
|  | { | 
|  | struct ice_vsi *pf_vsi; | 
|  |  | 
|  | pf_vsi = ice_get_main_vsi(pf); | 
|  | if (!pf_vsi) | 
|  | return; | 
|  |  | 
|  | ice_clear_arfs(pf_vsi); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ice_rebuild_arfs - remove/clear all aRFS resources and rebuild after reset | 
|  | * @pf: device private structure | 
|  | */ | 
|  | void ice_rebuild_arfs(struct ice_pf *pf) | 
|  | { | 
|  | struct ice_vsi *pf_vsi; | 
|  |  | 
|  | pf_vsi = ice_get_main_vsi(pf); | 
|  | if (!pf_vsi) | 
|  | return; | 
|  |  | 
|  | ice_remove_arfs(pf); | 
|  | ice_init_arfs(pf_vsi); | 
|  | } |