#include "ice.h"
 #include "ice_lib.h"
+#include "ice_fdir.h"
 #include "ice_flow.h"
 
 static struct in6_addr full_ipv6_addr_mask = {
        if (rule->dest_ctl == ICE_FLTR_PRGM_DESC_DEST_DROP_PKT)
                fsp->ring_cookie = RX_CLS_FLOW_DISC;
        else
-               fsp->ring_cookie = rule->q_index;
+               fsp->ring_cookie = rule->orig_q_index;
 
        idx = ice_ethtool_flow_to_fltr(fsp->flow_type);
        if (idx == ICE_FLTR_PTYPE_NONF_NONE) {
        return val;
 }
 
+/**
+ * ice_fdir_remap_entries - update the FDir entries in profile
+ * @prof: FDir structure pointer
+ * @tun: tunneled or non-tunneled packet
+ * @idx: FDir entry index
+ */
+static void
+ice_fdir_remap_entries(struct ice_fd_hw_prof *prof, int tun, int idx)
+{
+       if (idx != prof->cnt && tun < ICE_FD_HW_SEG_MAX) {
+               int i;
+
+               for (i = idx; i < (prof->cnt - 1); i++) {
+                       u64 old_entry_h;
+
+                       old_entry_h = prof->entry_h[i + 1][tun];
+                       prof->entry_h[i][tun] = old_entry_h;
+                       prof->vsi_h[i] = prof->vsi_h[i + 1];
+               }
+
+               prof->entry_h[i][tun] = 0;
+               prof->vsi_h[i] = 0;
+       }
+}
+
+/**
+ * ice_fdir_rem_adq_chnl - remove an ADQ channel from HW filter rules
+ * @hw: hardware structure containing filter list
+ * @vsi_idx: VSI handle
+ */
+void ice_fdir_rem_adq_chnl(struct ice_hw *hw, u16 vsi_idx)
+{
+       int status, flow;
+
+       if (!hw->fdir_prof)
+               return;
+
+       for (flow = 0; flow < ICE_FLTR_PTYPE_MAX; flow++) {
+               struct ice_fd_hw_prof *prof = hw->fdir_prof[flow];
+               int tun, i;
+
+               if (!prof || !prof->cnt)
+                       continue;
+
+               for (tun = 0; tun < ICE_FD_HW_SEG_MAX; tun++) {
+                       u64 prof_id;
+
+                       prof_id = flow + tun * ICE_FLTR_PTYPE_MAX;
+
+                       for (i = 0; i < prof->cnt; i++) {
+                               if (prof->vsi_h[i] != vsi_idx)
+                                       continue;
+
+                               prof->entry_h[i][tun] = 0;
+                               prof->vsi_h[i] = 0;
+                               break;
+                       }
+
+                       /* after clearing FDir entries update the remaining */
+                       ice_fdir_remap_entries(prof, tun, i);
+
+                       /* find flow profile corresponding to prof_id and clear
+                        * vsi_idx from bitmap.
+                        */
+                       status = ice_flow_rem_vsi_prof(hw, vsi_idx, prof_id);
+                       if (status) {
+                               dev_err(ice_hw_to_dev(hw), "ice_flow_rem_vsi_prof() failed status=%d\n",
+                                       status);
+                       }
+               }
+               prof->cnt--;
+       }
+}
+
 /**
  * ice_fdir_get_hw_prof - return the ice_fd_hw_proc associated with a flow
  * @hw: hardware structure containing the filter list
        return 0;
 }
 
+/**
+ * ice_fdir_prof_vsi_idx - find or insert a vsi_idx in structure
+ * @prof: pointer to flow director HW profile
+ * @vsi_idx: vsi_idx to locate
+ *
+ * return the index of the vsi_idx. if vsi_idx is not found insert it
+ * into the vsi_h table.
+ */
+static u16
+ice_fdir_prof_vsi_idx(struct ice_fd_hw_prof *prof, int vsi_idx)
+{
+       u16 idx = 0;
+
+       for (idx = 0; idx < prof->cnt; idx++)
+               if (prof->vsi_h[idx] == vsi_idx)
+                       return idx;
+
+       if (idx == prof->cnt)
+               prof->vsi_h[prof->cnt++] = vsi_idx;
+       return idx;
+}
+
 /**
  * ice_fdir_set_hw_fltr_rule - Configure HW tables to generate a FDir rule
  * @pf: pointer to the PF structure
        struct ice_hw *hw = &pf->hw;
        u64 entry1_h = 0;
        u64 entry2_h = 0;
+       bool del_last;
        u64 prof_id;
        int err;
+       int idx;
 
        main_vsi = ice_get_main_vsi(pf);
        if (!main_vsi)
        if (!hw_prof->cnt)
                hw_prof->cnt = 2;
 
+       for (idx = 1; idx < ICE_CHNL_MAX_TC; idx++) {
+               u16 vsi_idx;
+               u16 vsi_h;
+
+               if (!ice_is_adq_active(pf) || !main_vsi->tc_map_vsi[idx])
+                       continue;
+
+               entry1_h = 0;
+               vsi_h = main_vsi->tc_map_vsi[idx]->idx;
+               err = ice_flow_add_entry(hw, ICE_BLK_FD, prof_id,
+                                        main_vsi->idx, vsi_h,
+                                        ICE_FLOW_PRIO_NORMAL, seg,
+                                        &entry1_h);
+               if (err) {
+                       dev_err(dev, "Could not add Channel VSI %d to flow group\n",
+                               idx);
+                       goto err_unroll;
+               }
+
+               vsi_idx = ice_fdir_prof_vsi_idx(hw_prof,
+                                               main_vsi->tc_map_vsi[idx]->idx);
+               hw_prof->entry_h[vsi_idx][tun] = entry1_h;
+       }
+
        return 0;
 
+err_unroll:
+       entry1_h = 0;
+       hw_prof->fdir_seg[tun] = NULL;
+
+       /* The variable del_last will be used to determine when to clean up
+        * the VSI group data. The VSI data is not needed if there are no
+        * segments.
+        */
+       del_last = true;
+       for (idx = 0; idx < ICE_FD_HW_SEG_MAX; idx++)
+               if (hw_prof->fdir_seg[idx]) {
+                       del_last = false;
+                       break;
+               }
+
+       for (idx = 0; idx < hw_prof->cnt; idx++) {
+               u16 vsi_num = ice_get_hw_vsi_num(hw, hw_prof->vsi_h[idx]);
+
+               if (!hw_prof->entry_h[idx][tun])
+                       continue;
+               ice_rem_prof_id_flow(hw, ICE_BLK_FD, vsi_num, prof_id);
+               ice_flow_rem_entry(hw, ICE_BLK_FD, hw_prof->entry_h[idx][tun]);
+               hw_prof->entry_h[idx][tun] = 0;
+               if (del_last)
+                       hw_prof->vsi_h[idx] = 0;
+       }
+       if (del_last)
+               hw_prof->cnt = 0;
 err_entry:
        ice_rem_prof_id_flow(hw, ICE_BLK_FD,
                             ice_get_hw_vsi_num(hw, main_vsi->idx), prof_id);
        return -EOPNOTSUPP;
 }
 
+/**
+ * ice_update_per_q_fltr
+ * @vsi: ptr to VSI
+ * @q_index: queue index
+ * @inc: true to increment or false to decrement per queue filter count
+ *
+ * This function is used to keep track of per queue sideband filters
+ */
+static void ice_update_per_q_fltr(struct ice_vsi *vsi, u32 q_index, bool inc)
+{
+       struct ice_rx_ring *rx_ring;
+
+       if (!vsi->num_rxq || q_index >= vsi->num_rxq)
+               return;
+
+       rx_ring = vsi->rx_rings[q_index];
+       if (!rx_ring || !rx_ring->ch)
+               return;
+
+       if (inc)
+               atomic_inc(&rx_ring->ch->num_sb_fltr);
+       else
+               atomic_dec_if_positive(&rx_ring->ch->num_sb_fltr);
+}
+
 /**
  * ice_fdir_write_fltr - send a flow director filter to the hardware
  * @pf: PF data structure
        return err;
 }
 
+/**
+ * ice_fdir_del_all_fltrs - Delete all flow director filters
+ * @vsi: the VSI being changed
+ *
+ * This function needs to be called while holding hw->fdir_fltr_lock
+ */
+void ice_fdir_del_all_fltrs(struct ice_vsi *vsi)
+{
+       struct ice_fdir_fltr *f_rule, *tmp;
+       struct ice_pf *pf = vsi->back;
+       struct ice_hw *hw = &pf->hw;
+
+       list_for_each_entry_safe(f_rule, tmp, &hw->fdir_list_head, fltr_node) {
+               ice_fdir_write_all_fltr(pf, f_rule, false);
+               ice_fdir_update_cntrs(hw, f_rule->flow_type, false);
+               list_del(&f_rule->fltr_node);
+               devm_kfree(ice_pf_to_dev(pf), f_rule);
+       }
+}
+
 /**
  * ice_vsi_manage_fdir - turn on/off flow director
  * @vsi: the VSI being changed
  */
 void ice_vsi_manage_fdir(struct ice_vsi *vsi, bool ena)
 {
-       struct ice_fdir_fltr *f_rule, *tmp;
        struct ice_pf *pf = vsi->back;
        struct ice_hw *hw = &pf->hw;
        enum ice_fltr_ptype flow;
        mutex_lock(&hw->fdir_fltr_lock);
        if (!test_and_clear_bit(ICE_FLAG_FD_ENA, pf->flags))
                goto release_lock;
-       list_for_each_entry_safe(f_rule, tmp, &hw->fdir_list_head, fltr_node) {
-               /* ignore return value */
-               ice_fdir_write_all_fltr(pf, f_rule, false);
-               ice_fdir_update_cntrs(hw, f_rule->flow_type, false);
-               list_del(&f_rule->fltr_node);
-               devm_kfree(ice_hw_to_dev(hw), f_rule);
-       }
+
+       ice_fdir_del_all_fltrs(vsi);
 
        if (hw->fdir_prof)
                for (flow = ICE_FLTR_PTYPE_NONF_NONE; flow < ICE_FLTR_PTYPE_MAX;
 {
        struct ice_fdir_fltr *old_fltr;
        struct ice_hw *hw = &pf->hw;
+       struct ice_vsi *vsi;
        int err = -ENOENT;
 
        /* Do not update filters during reset */
        if (ice_is_reset_in_progress(pf->state))
                return -EBUSY;
 
+       vsi = ice_get_main_vsi(pf);
+       if (!vsi)
+               return -EINVAL;
+
        old_fltr = ice_fdir_find_fltr_by_idx(hw, fltr_idx);
        if (old_fltr) {
                err = ice_fdir_write_all_fltr(pf, old_fltr, false);
                if (err)
                        return err;
                ice_fdir_update_cntrs(hw, old_fltr->flow_type, false);
+               /* update sb-filters count, specific to ring->channel */
+               ice_update_per_q_fltr(vsi, old_fltr->orig_q_index, false);
                if (!input && !hw->fdir_fltr_cnt[old_fltr->flow_type])
                        /* we just deleted the last filter of flow_type so we
                         * should also delete the HW filter info.
        if (!input)
                return err;
        ice_fdir_list_add_fltr(hw, input);
+       /* update sb-filters count, specific to ring->channel */
+       ice_update_per_q_fltr(vsi, input->orig_q_index, true);
        ice_fdir_update_cntrs(hw, input->flow_type, true);
        return 0;
 }
        return val;
 }
 
+/**
+ * ice_update_ring_dest_vsi - update dest ring and dest VSI
+ * @vsi: pointer to target VSI
+ * @dest_vsi: ptr to dest VSI index
+ * @ring: ptr to dest ring
+ *
+ * This function updates destination VSI and queue if user specifies
+ * target queue which falls in channel's (aka ADQ) queue region
+ */
+static void
+ice_update_ring_dest_vsi(struct ice_vsi *vsi, u16 *dest_vsi, u32 *ring)
+{
+       struct ice_channel *ch;
+
+       list_for_each_entry(ch, &vsi->ch_list, list) {
+               if (!ch->ch_vsi)
+                       continue;
+
+               /* make sure to locate corresponding channel based on "queue"
+                * specified
+                */
+               if ((*ring < ch->base_q) ||
+                   (*ring >= (ch->base_q + ch->num_rxq)))
+                       continue;
+
+               /* update the dest_vsi based on channel */
+               *dest_vsi = ch->ch_vsi->idx;
+
+               /* update the "ring" to be correct based on channel */
+               *ring -= ch->base_q;
+       }
+}
+
 /**
  * ice_set_fdir_input_set - Set the input set for Flow Director
  * @vsi: pointer to target VSI
                       struct ice_fdir_fltr *input)
 {
        u16 dest_vsi, q_index = 0;
+       u16 orig_q_index = 0;
        struct ice_pf *pf;
        struct ice_hw *hw;
        int flow_type;
                if (ring >= vsi->num_rxq)
                        return -EINVAL;
 
+               orig_q_index = ring;
+               ice_update_ring_dest_vsi(vsi, &dest_vsi, &ring);
                dest_ctl = ICE_FLTR_PRGM_DESC_DEST_DIRECT_PKT_QINDEX;
                q_index = ring;
        }
        input->q_index = q_index;
        flow_type = fsp->flow_type & ~FLOW_EXT;
 
+       /* Record the original queue index as specified by user.
+        * with channel configuration 'q_index' becomes relative
+        * to TC (channel).
+        */
+       input->orig_q_index = orig_q_index;
        input->dest_vsi = dest_vsi;
        input->dest_ctl = dest_ctl;
        input->fltr_status = ICE_FLTR_PRGM_DESC_FD_STATUS_FD_ID;
 
 remove_sw_rule:
        ice_fdir_update_cntrs(hw, input->flow_type, false);
+       /* update sb-filters count, specific to ring->channel */
+       ice_update_per_q_fltr(vsi, input->orig_q_index, false);
        list_del(&input->fltr_node);
 release_lock:
        mutex_unlock(&hw->fdir_fltr_lock);
 
        struct ice_pf *pf = vsi->back;
        u32 g_val, b_val;
 
-       /* Flow Director filters are only allocated/assigned to the PF VSI which
-        * passes the traffic. The CTRL VSI is only used to add/delete filters
-        * so we don't allocate resources to it
+       /* Flow Director filters are only allocated/assigned to the PF VSI or
+        * CHNL VSI which passes the traffic. The CTRL VSI is only used to
+        * add/delete filters so resources are not allocated to it
         */
+       if (!test_bit(ICE_FLAG_FD_ENA, pf->flags))
+               return -EPERM;
+
+       if (!(vsi->type == ICE_VSI_PF || vsi->type == ICE_VSI_VF ||
+             vsi->type == ICE_VSI_CHNL))
+               return -EPERM;
 
        /* FD filters from guaranteed pool per VSI */
        g_val = pf->hw.func_caps.fd_fltr_guar;
        if (!b_val)
                return -EPERM;
 
-       if (!(vsi->type == ICE_VSI_PF || vsi->type == ICE_VSI_VF))
-               return -EPERM;
+       /* PF main VSI gets only 64 FD resources from guaranteed pool
+        * when ADQ is configured.
+        */
+#define ICE_PF_VSI_GFLTR       64
 
-       if (!test_bit(ICE_FLAG_FD_ENA, pf->flags))
-               return -EPERM;
+       /* determine FD filter resources per VSI from shared(best effort) and
+        * dedicated pool
+        */
+       if (vsi->type == ICE_VSI_PF) {
+               vsi->num_gfltr = g_val;
+               /* if MQPRIO is configured, main VSI doesn't get all FD
+                * resources from guaranteed pool. PF VSI gets 64 FD resources
+                */
+               if (test_bit(ICE_FLAG_TC_MQPRIO, pf->flags)) {
+                       if (g_val < ICE_PF_VSI_GFLTR)
+                               return -EPERM;
+                       /* allow bare minimum entries for PF VSI */
+                       vsi->num_gfltr = ICE_PF_VSI_GFLTR;
+               }
+
+               /* each VSI gets same "best_effort" quota */
+               vsi->num_bfltr = b_val;
+       } else if (vsi->type == ICE_VSI_VF) {
+               vsi->num_gfltr = 0;
+
+               /* each VSI gets same "best_effort" quota */
+               vsi->num_bfltr = b_val;
+       } else {
+               struct ice_vsi *main_vsi;
+               int numtc;
 
-       vsi->num_gfltr = g_val / pf->num_alloc_vsi;
+               main_vsi = ice_get_main_vsi(pf);
+               if (!main_vsi)
+                       return -EPERM;
 
-       /* each VSI gets same "best_effort" quota */
-       vsi->num_bfltr = b_val;
+               if (!main_vsi->all_numtc)
+                       return -EINVAL;
 
-       if (vsi->type == ICE_VSI_VF) {
-               vsi->num_gfltr = 0;
+               /* figure out ADQ numtc */
+               numtc = main_vsi->all_numtc - ICE_CHNL_START_TC;
+
+               /* only one TC but still asking resources for channels,
+                * invalid config
+                */
+               if (numtc < ICE_CHNL_START_TC)
+                       return -EPERM;
+
+               g_val -= ICE_PF_VSI_GFLTR;
+               /* channel VSIs gets equal share from guaranteed pool */
+               vsi->num_gfltr = g_val / numtc;
 
                /* each VSI gets same "best_effort" quota */
                vsi->num_bfltr = b_val;
        u16 dflt_q, report_q, val;
 
        if (vsi->type != ICE_VSI_PF && vsi->type != ICE_VSI_CTRL &&
-           vsi->type != ICE_VSI_VF)
+           vsi->type != ICE_VSI_VF && vsi->type != ICE_VSI_CHNL)
                return;
 
        val = ICE_AQ_VSI_PROP_FLOW_DIR_VALID;
 
        return 0;
 }
 
+/**
+ * ice_add_vsi_to_fdir - add a VSI to the flow director group for PF
+ * @pf: ptr to PF device
+ * @vsi: ptr to VSI
+ */
+static int ice_add_vsi_to_fdir(struct ice_pf *pf, struct ice_vsi *vsi)
+{
+       struct device *dev = ice_pf_to_dev(pf);
+       bool added = false;
+       struct ice_hw *hw;
+       int flow;
+
+       if (!(vsi->num_gfltr || vsi->num_bfltr))
+               return -EINVAL;
+
+       hw = &pf->hw;
+       for (flow = 0; flow < ICE_FLTR_PTYPE_MAX; flow++) {
+               struct ice_fd_hw_prof *prof;
+               int tun, status;
+               u64 entry_h;
+
+               if (!(hw->fdir_prof && hw->fdir_prof[flow] &&
+                     hw->fdir_prof[flow]->cnt))
+                       continue;
+
+               for (tun = 0; tun < ICE_FD_HW_SEG_MAX; tun++) {
+                       enum ice_flow_priority prio;
+                       u64 prof_id;
+
+                       /* add this VSI to FDir profile for this flow */
+                       prio = ICE_FLOW_PRIO_NORMAL;
+                       prof = hw->fdir_prof[flow];
+                       prof_id = flow + tun * ICE_FLTR_PTYPE_MAX;
+                       status = ice_flow_add_entry(hw, ICE_BLK_FD, prof_id,
+                                                   prof->vsi_h[0], vsi->idx,
+                                                   prio, prof->fdir_seg[tun],
+                                                   &entry_h);
+                       if (status) {
+                               dev_err(dev, "channel VSI idx %d, not able to add to group %d\n",
+                                       vsi->idx, flow);
+                               continue;
+                       }
+
+                       prof->entry_h[prof->cnt][tun] = entry_h;
+               }
+
+               /* store VSI for filter replay and delete */
+               prof->vsi_h[prof->cnt] = vsi->idx;
+               prof->cnt++;
+
+               added = true;
+               dev_dbg(dev, "VSI idx %d added to fdir group %d\n", vsi->idx,
+                       flow);
+       }
+
+       if (!added)
+               dev_dbg(dev, "VSI idx %d not added to fdir groups\n", vsi->idx);
+
+       return 0;
+}
+
 /**
  * ice_add_channel - add a channel by adding VSI
  * @pf: ptr to PF device
                return -EINVAL;
        }
 
+       ice_add_vsi_to_fdir(pf, vsi);
+
        ch->sw_id = sw_id;
        ch->vsi_num = vsi->vsi_num;
        ch->info.mapping_flags = vsi->info.mapping_flags;
        if (rem_fltr)
                ice_rem_all_chnl_fltrs(pf);
 
+       /* remove ntuple filters since queue configuration is being changed */
+       if  (vsi->netdev->features & NETIF_F_NTUPLE) {
+               struct ice_hw *hw = &pf->hw;
+
+               mutex_lock(&hw->fdir_fltr_lock);
+               ice_fdir_del_all_fltrs(vsi);
+               mutex_unlock(&hw->fdir_fltr_lock);
+       }
+
        /* perform cleanup for channels if they exist */
        list_for_each_entry_safe(ch, ch_tmp, &vsi->ch_list, list) {
                struct ice_vsi *ch_vsi;
                        }
                }
 
+               /* Release FD resources for the channel VSI */
+               ice_fdir_rem_adq_chnl(&pf->hw, ch->ch_vsi->idx);
+
                /* clear the VSI from scheduler tree */
                ice_rm_vsi_lan_cfg(ch->ch_vsi->port_info, ch->ch_vsi->idx);