/* calls to ice_flow_add_prof require the number of segments in the array * for segs_cnt. In this code that is one more than the index.
*/ #define TNL_SEG_CNT(_TNL_) ((_TNL_) + 1)
/** * ice_fltr_to_ethtool_flow - convert filter type values to ethtool * flow type values * @flow: filter type to be converted * * Returns the corresponding ethtool flow type.
*/ staticint ice_fltr_to_ethtool_flow(enum ice_fltr_ptype flow)
{ switch (flow) { case ICE_FLTR_PTYPE_NONF_ETH: return ETHER_FLOW; case ICE_FLTR_PTYPE_NONF_IPV4_TCP: return TCP_V4_FLOW; case ICE_FLTR_PTYPE_NONF_IPV4_UDP: return UDP_V4_FLOW; case ICE_FLTR_PTYPE_NONF_IPV4_SCTP: return SCTP_V4_FLOW; case ICE_FLTR_PTYPE_NONF_IPV4_OTHER: return IPV4_USER_FLOW; case ICE_FLTR_PTYPE_NONF_IPV6_TCP: return TCP_V6_FLOW; case ICE_FLTR_PTYPE_NONF_IPV6_UDP: return UDP_V6_FLOW; case ICE_FLTR_PTYPE_NONF_IPV6_SCTP: return SCTP_V6_FLOW; case ICE_FLTR_PTYPE_NONF_IPV6_OTHER: return IPV6_USER_FLOW; default: /* 0 is undefined ethtool flow */ return 0;
}
}
/** * ice_ethtool_flow_to_fltr - convert ethtool flow type to filter enum * @eth: Ethtool flow type to be converted * * Returns flow enum
*/ staticenum ice_fltr_ptype ice_ethtool_flow_to_fltr(int eth)
{ switch (eth) { case ETHER_FLOW: return ICE_FLTR_PTYPE_NONF_ETH; case TCP_V4_FLOW: return ICE_FLTR_PTYPE_NONF_IPV4_TCP; case UDP_V4_FLOW: return ICE_FLTR_PTYPE_NONF_IPV4_UDP; case SCTP_V4_FLOW: return ICE_FLTR_PTYPE_NONF_IPV4_SCTP; case IPV4_USER_FLOW: return ICE_FLTR_PTYPE_NONF_IPV4_OTHER; case TCP_V6_FLOW: return ICE_FLTR_PTYPE_NONF_IPV6_TCP; case UDP_V6_FLOW: return ICE_FLTR_PTYPE_NONF_IPV6_UDP; case SCTP_V6_FLOW: return ICE_FLTR_PTYPE_NONF_IPV6_SCTP; case IPV6_USER_FLOW: return ICE_FLTR_PTYPE_NONF_IPV6_OTHER; default: return ICE_FLTR_PTYPE_NONF_NONE;
}
}
/** * ice_is_mask_valid - check mask field set * @mask: full mask to check * @field: field for which mask should be valid * * If the mask is fully set return true. If it is not valid for field return * false.
*/ staticbool ice_is_mask_valid(u64 mask, u64 field)
{ return (mask & field) == field;
}
/** * ice_get_ethtool_fdir_entry - fill ethtool structure with fdir filter data * @hw: hardware structure that contains filter list * @cmd: ethtool command data structure to receive the filter data * * Returns 0 on success and -EINVAL on failure
*/ int ice_get_ethtool_fdir_entry(struct ice_hw *hw, struct ethtool_rxnfc *cmd)
{ struct ethtool_rx_flow_spec *fsp; struct ice_fdir_fltr *rule; int ret = 0;
u16 idx;
idx = ice_ethtool_flow_to_fltr(fsp->flow_type); if (idx == ICE_FLTR_PTYPE_NONF_NONE) {
dev_err(ice_hw_to_dev(hw), "Missing input index for flow_type %d\n",
rule->flow_type);
ret = -EINVAL;
}
/** * ice_get_fdir_fltr_ids - fill buffer with filter IDs of active filters * @hw: hardware structure containing the filter list * @cmd: ethtool command data structure * @rule_locs: ethtool array passed in from OS to receive filter IDs * * Returns 0 as expected for success by ethtool
*/ int
ice_get_fdir_fltr_ids(struct ice_hw *hw, struct ethtool_rxnfc *cmd,
u32 *rule_locs)
{ struct ice_fdir_fltr *f_rule; unsignedint cnt = 0; int val = 0;
/* report total rule count */
cmd->data = ice_get_fdir_cnt_all(hw);
mutex_lock(&hw->fdir_fltr_lock);
list_for_each_entry(f_rule, &hw->fdir_list_head, fltr_node) { if (cnt == cmd->rule_cnt) {
val = -EMSGSIZE; goto release_lock;
}
rule_locs[cnt] = f_rule->fltr_id;
cnt++;
}
release_lock:
mutex_unlock(&hw->fdir_fltr_lock); if (!val)
cmd->rule_cnt = cnt; 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
*/ staticvoid
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;
/** * ice_fdir_rem_flow - release the ice_flow structures for a filter type * @hw: hardware structure containing the filter list * @blk: hardware block * @flow_type: FDir flow type to release
*/ staticvoid
ice_fdir_rem_flow(struct ice_hw *hw, enum ice_block blk, enum ice_fltr_ptype flow_type)
{ int flow = (int)flow_type & ~FLOW_EXT; struct ice_fd_hw_prof *prof; int tun, i;
prof = ice_fdir_get_hw_prof(hw, blk, flow); if (!prof) return;
ice_fdir_erase_flow_from_hw(hw, blk, flow); for (i = 0; i < prof->cnt; i++)
prof->vsi_h[i] = 0; for (tun = 0; tun < ICE_FD_HW_SEG_MAX; tun++) { if (!prof->fdir_seg[tun]) continue;
devm_kfree(ice_hw_to_dev(hw), prof->fdir_seg[tun]);
prof->fdir_seg[tun] = NULL;
}
prof->cnt = 0;
}
/** * ice_fdir_release_flows - release all flows in use for later replay * @hw: pointer to HW instance
*/ void ice_fdir_release_flows(struct ice_hw *hw)
{ int flow;
/* release Flow Director HW table entries */ for (flow = 0; flow < ICE_FLTR_PTYPE_MAX; flow++)
ice_fdir_erase_flow_from_hw(hw, ICE_BLK_FD, flow);
}
/** * ice_fdir_replay_flows - replay HW Flow Director filter info * @hw: pointer to HW instance
*/ void ice_fdir_replay_flows(struct ice_hw *hw)
{ int flow;
for (flow = 0; flow < ICE_FLTR_PTYPE_MAX; flow++) { int tun;
if (!hw->fdir_prof[flow] || !hw->fdir_prof[flow]->cnt) continue; for (tun = 0; tun < ICE_FD_HW_SEG_MAX; tun++) { struct ice_flow_prof *hw_prof; struct ice_fd_hw_prof *prof; int j;
/* 0x1fe is the maximum value for offsets stored in the internal * filtering tables.
*/ #define ICE_USERDEF_FLEX_MAX_OFFS_VAL 0x1fe
if (!ice_is_mask_valid(mask, ICE_USERDEF_FLEX_FLTR_M) ||
value > ICE_USERDEF_FLEX_FLTR_M) return -EINVAL;
data->flex_word = value & ICE_USERDEF_FLEX_WORD_M;
data->flex_offset = FIELD_GET(ICE_USERDEF_FLEX_OFFS_M, value); if (data->flex_offset > ICE_USERDEF_FLEX_MAX_OFFS_VAL) return -EINVAL;
data->flex_fltr = true;
return 0;
}
/** * ice_fdir_num_avail_fltr - return the number of unused flow director filters * @hw: pointer to hardware structure * @vsi: software VSI structure * * There are 2 filter pools: guaranteed and best effort(shared). Each VSI can * use filters from either pool. The guaranteed pool is divided between VSIs. * The best effort filter pool is common to all VSIs and is a device shared * resource pool. The number of filters available to this VSI is the sum of * the VSIs guaranteed filter pool and the global available best effort * filter pool. * * Returns the number of available flow director filters to this VSI
*/ int ice_fdir_num_avail_fltr(struct ice_hw *hw, struct ice_vsi *vsi)
{
u16 vsi_num = ice_get_hw_vsi_num(hw, vsi->idx);
u16 num_guar;
u16 num_be;
/* total guaranteed filters assigned to this VSI */
num_guar = vsi->num_gfltr;
/* total global best effort filters */
num_be = hw->func_caps.fd_fltr_best_effort;
/* Subtract the number of programmed filters from the global values */ switch (hw->mac_type) { case ICE_MAC_E830:
num_guar -= FIELD_GET(E830_VSIQF_FD_CNT_FD_GCNT_M,
rd32(hw, VSIQF_FD_CNT(vsi_num)));
num_be -= FIELD_GET(E830_GLQF_FD_CNT_FD_BCNT_M,
rd32(hw, GLQF_FD_CNT)); break; case ICE_MAC_E810: default:
num_guar -= FIELD_GET(E800_VSIQF_FD_CNT_FD_GCNT_M,
rd32(hw, VSIQF_FD_CNT(vsi_num)));
num_be -= FIELD_GET(E800_GLQF_FD_CNT_FD_BCNT_M,
rd32(hw, GLQF_FD_CNT));
}
return num_guar + num_be;
}
/** * ice_fdir_alloc_flow_prof - allocate FDir flow profile structure(s) * @hw: HW structure containing the FDir flow profile structure(s) * @flow: flow type to allocate the flow profile for * * Allocate the fdir_prof and fdir_prof[flow] if not already created. Return 0 * on success and negative on error.
*/ staticint
ice_fdir_alloc_flow_prof(struct ice_hw *hw, enum ice_fltr_ptype flow)
{ if (!hw) return -EINVAL;
if (!hw->fdir_prof) {
hw->fdir_prof = devm_kcalloc(ice_hw_to_dev(hw),
ICE_FLTR_PTYPE_MAX, sizeof(*hw->fdir_prof),
GFP_KERNEL); if (!hw->fdir_prof) return -ENOMEM;
}
if (!hw->fdir_prof[flow]) {
hw->fdir_prof[flow] = devm_kzalloc(ice_hw_to_dev(hw), sizeof(**hw->fdir_prof),
GFP_KERNEL); if (!hw->fdir_prof[flow]) return -ENOMEM;
}
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;
}
main_vsi = ice_get_main_vsi(pf); if (!main_vsi) return -EINVAL;
ctrl_vsi = ice_get_ctrl_vsi(pf); if (!ctrl_vsi) return -EINVAL;
err = ice_fdir_alloc_flow_prof(hw, flow); if (err) return err;
hw_prof = hw->fdir_prof[flow];
old_seg = hw_prof->fdir_seg[tun]; if (old_seg) { /* This flow_type already has a changed input set. * If it matches the requested input set then we are * done. Or, if it's different then it's an error.
*/ if (!memcmp(old_seg, seg, sizeof(*seg))) return -EEXIST;
/* if there are FDir filters using this flow, * then return error.
*/ if (hw->fdir_fltr_cnt[flow]) {
dev_err(dev, "Failed to add filter. Flow director filters on each port must have the same input set.\n"); return -EINVAL;
}
if (ice_is_arfs_using_perfect_flow(hw, flow)) {
dev_err(dev, "aRFS using perfect flow type %d, cannot change input set\n",
flow); return -EINVAL;
}
/* Adding a profile, but there is only one header supported. * That is the final parameters are 1 header (segment), no * actions (NULL) and zero actions 0.
*/
err = ice_flow_add_prof(hw, ICE_BLK_FD, ICE_FLOW_RX, seg,
TNL_SEG_CNT(tun), false, &prof); if (err) return err;
err = ice_flow_add_entry(hw, ICE_BLK_FD, prof->id, main_vsi->idx,
main_vsi->idx, ICE_FLOW_PRIO_NORMAL,
seg, &entry1_h); if (err) goto err_prof;
err = ice_flow_add_entry(hw, ICE_BLK_FD, prof->id, main_vsi->idx,
ctrl_vsi->idx, ICE_FLOW_PRIO_NORMAL,
seg, &entry2_h); if (err) goto err_entry;
/* 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;
}
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);
ice_flow_rem_entry(hw, ICE_BLK_FD, entry1_h);
err_prof:
ice_flow_rem_prof(hw, ICE_BLK_FD, prof->id);
dev_err(dev, "Failed to add filter. Flow director filters on each port must have the same input set.\n");
return err;
}
/** * ice_set_init_fdir_seg * @seg: flow segment for programming * @l3_proto: ICE_FLOW_SEG_HDR_IPV4 or ICE_FLOW_SEG_HDR_IPV6 * @l4_proto: ICE_FLOW_SEG_HDR_TCP or ICE_FLOW_SEG_HDR_UDP * * Set the configuration for perfect filters to the provided flow segment for * programming the HW filter. This is to be called only when initializing * filters as this function it assumes no filters exist.
*/ staticint
ice_set_init_fdir_seg(struct ice_flow_seg_info *seg, enum ice_flow_seg_hdr l3_proto, enum ice_flow_seg_hdr l4_proto)
{ enum ice_flow_field src_addr, dst_addr, src_port, dst_port;
/* if there is already a filter rule for kind return -EINVAL */ if (hw->fdir_prof && hw->fdir_prof[flow] &&
hw->fdir_prof[flow]->fdir_seg[0]) return -EINVAL;
seg = devm_kzalloc(dev, sizeof(*seg), GFP_KERNEL); if (!seg) return -ENOMEM;
if (flow == ICE_FLTR_PTYPE_NONF_IPV4_TCP)
ret = ice_set_init_fdir_seg(seg, ICE_FLOW_SEG_HDR_IPV4,
ICE_FLOW_SEG_HDR_TCP); elseif (flow == ICE_FLTR_PTYPE_NONF_IPV4_UDP)
ret = ice_set_init_fdir_seg(seg, ICE_FLOW_SEG_HDR_IPV4,
ICE_FLOW_SEG_HDR_UDP); elseif (flow == ICE_FLTR_PTYPE_NONF_IPV6_TCP)
ret = ice_set_init_fdir_seg(seg, ICE_FLOW_SEG_HDR_IPV6,
ICE_FLOW_SEG_HDR_TCP); elseif (flow == ICE_FLTR_PTYPE_NONF_IPV6_UDP)
ret = ice_set_init_fdir_seg(seg, ICE_FLOW_SEG_HDR_IPV6,
ICE_FLOW_SEG_HDR_UDP); else
ret = -EINVAL; if (ret) goto err_exit;
/* add filter for outer headers */
ret = ice_fdir_set_hw_fltr_rule(pf, seg, flow, ICE_FD_HW_SEG_NON_TUN); if (ret) /* could not write filter, free memory */ goto err_exit;
/* make tunneled filter HW entries if possible */
memcpy(&tun_seg[1], seg, sizeof(*seg));
ret = ice_fdir_set_hw_fltr_rule(pf, tun_seg, flow, ICE_FD_HW_SEG_TUN); if (ret) /* could not write tunnel filter, but outer header filter * exists
*/
devm_kfree(dev, tun_seg);
/** * ice_set_fdir_ip4_seg * @seg: flow segment for programming * @tcp_ip4_spec: mask data from ethtool * @l4_proto: Layer 4 protocol to program * @perfect_fltr: only valid on success; returns true if perfect filter, * false if not * * Set the mask data into the flow segment to be used to program HW * table based on provided L4 protocol for IPv4
*/ staticint
ice_set_fdir_ip4_seg(struct ice_flow_seg_info *seg, struct ethtool_tcpip4_spec *tcp_ip4_spec, enum ice_flow_seg_hdr l4_proto, bool *perfect_fltr)
{ enum ice_flow_field src_port, dst_port;
/* make sure we don't have any empty rule */ if (!tcp_ip4_spec->psrc && !tcp_ip4_spec->ip4src &&
!tcp_ip4_spec->pdst && !tcp_ip4_spec->ip4dst) return -EINVAL;
/* filtering on TOS not supported */ if (tcp_ip4_spec->tos) return -EOPNOTSUPP;
/** * ice_set_fdir_ip4_usr_seg * @seg: flow segment for programming * @usr_ip4_spec: ethtool userdef packet offset * @perfect_fltr: only valid on success; returns true if perfect filter, * false if not * * Set the offset data into the flow segment to be used to program HW * table for IPv4
*/ staticint
ice_set_fdir_ip4_usr_seg(struct ice_flow_seg_info *seg, struct ethtool_usrip4_spec *usr_ip4_spec, bool *perfect_fltr)
{ /* first 4 bytes of Layer 4 header */ if (usr_ip4_spec->l4_4_bytes) return -EINVAL; if (usr_ip4_spec->tos) return -EINVAL; if (usr_ip4_spec->ip_ver) return -EINVAL; /* Filtering on Layer 4 protocol not supported */ if (usr_ip4_spec->proto) return -EOPNOTSUPP; /* empty rules are not valid */ if (!usr_ip4_spec->ip4src && !usr_ip4_spec->ip4dst) return -EINVAL;
/** * ice_set_fdir_ip6_seg * @seg: flow segment for programming * @tcp_ip6_spec: mask data from ethtool * @l4_proto: Layer 4 protocol to program * @perfect_fltr: only valid on success; returns true if perfect filter, * false if not * * Set the mask data into the flow segment to be used to program HW * table based on provided L4 protocol for IPv6
*/ staticint
ice_set_fdir_ip6_seg(struct ice_flow_seg_info *seg, struct ethtool_tcpip6_spec *tcp_ip6_spec, enum ice_flow_seg_hdr l4_proto, bool *perfect_fltr)
{ enum ice_flow_field src_port, dst_port;
/* make sure we don't have any empty rule */ if (!memcmp(tcp_ip6_spec->ip6src, &zero_ipv6_addr_mask, sizeof(struct in6_addr)) &&
!memcmp(tcp_ip6_spec->ip6dst, &zero_ipv6_addr_mask, sizeof(struct in6_addr)) &&
!tcp_ip6_spec->psrc && !tcp_ip6_spec->pdst) return -EINVAL;
/* filtering on TC not supported */ if (tcp_ip6_spec->tclass) return -EOPNOTSUPP;
/** * ice_set_fdir_ip6_usr_seg * @seg: flow segment for programming * @usr_ip6_spec: ethtool userdef packet offset * @perfect_fltr: only valid on success; returns true if perfect filter, * false if not * * Set the offset data into the flow segment to be used to program HW * table for IPv6
*/ staticint
ice_set_fdir_ip6_usr_seg(struct ice_flow_seg_info *seg, struct ethtool_usrip6_spec *usr_ip6_spec, bool *perfect_fltr)
{ /* filtering on Layer 4 bytes not supported */ if (usr_ip6_spec->l4_4_bytes) return -EOPNOTSUPP; /* filtering on TC not supported */ if (usr_ip6_spec->tclass) return -EOPNOTSUPP; /* filtering on Layer 4 protocol not supported */ if (usr_ip6_spec->l4_proto) return -EOPNOTSUPP; /* empty rules are not valid */ if (!memcmp(usr_ip6_spec->ip6src, &zero_ipv6_addr_mask, sizeof(struct in6_addr)) &&
!memcmp(usr_ip6_spec->ip6dst, &zero_ipv6_addr_mask, sizeof(struct in6_addr))) return -EINVAL;
/** * ice_fdir_vlan_valid - validate VLAN data for Flow Director rule * @dev: network interface device structure * @fsp: pointer to ethtool Rx flow specification * * Return: true if vlan data is valid, false otherwise
*/ staticbool ice_fdir_vlan_valid(struct device *dev, struct ethtool_rx_flow_spec *fsp)
{ if (fsp->m_ext.vlan_etype && !eth_type_vlan(fsp->h_ext.vlan_etype)) returnfalse;
if (fsp->m_ext.vlan_tci && ntohs(fsp->h_ext.vlan_tci) >= VLAN_N_VID) returnfalse;
/* proto and vlan must have vlan-etype defined */ if (fsp->m_u.ether_spec.h_proto && fsp->m_ext.vlan_tci &&
!fsp->m_ext.vlan_etype) {
dev_warn(dev, "Filter with proto and vlan require also vlan-etype"); returnfalse;
}
returntrue;
}
/** * ice_set_ether_flow_seg - set address and protocol segments for ether flow * @dev: network interface device structure * @seg: flow segment for programming * @eth_spec: mask data from ethtool * * Return: 0 on success and errno in case of error.
*/ staticint ice_set_ether_flow_seg(struct device *dev, struct ice_flow_seg_info *seg, struct ethhdr *eth_spec)
{
ICE_FLOW_SET_HDRS(seg, ICE_FLOW_SEG_HDR_ETH);
/* empty rules are not valid */ if (is_zero_ether_addr(eth_spec->h_source) &&
is_zero_ether_addr(eth_spec->h_dest) &&
!eth_spec->h_proto) return -EINVAL;
/* Ethertype */ if (eth_spec->h_proto == htons(0xFFFF)) {
ice_flow_set_fld(seg, ICE_FLOW_FIELD_IDX_ETH_TYPE,
ICE_FLOW_FLD_OFF_INVAL,
ICE_FLOW_FLD_OFF_INVAL,
ICE_FLOW_FLD_OFF_INVAL, false);
} elseif (eth_spec->h_proto) {
dev_warn(dev, "Only 0x0000 or 0xffff proto mask is allowed for flow-type ether"); return -EOPNOTSUPP;
}
/* Source MAC address */ if (is_broadcast_ether_addr(eth_spec->h_source))
ice_flow_set_fld(seg, ICE_FLOW_FIELD_IDX_ETH_SA,
ICE_FLOW_FLD_OFF_INVAL, ICE_FLOW_FLD_OFF_INVAL,
ICE_FLOW_FLD_OFF_INVAL, false); elseif (!is_zero_ether_addr(eth_spec->h_source)) goto err_mask;
/* Destination MAC address */ if (is_broadcast_ether_addr(eth_spec->h_dest))
ice_flow_set_fld(seg, ICE_FLOW_FIELD_IDX_ETH_DA,
ICE_FLOW_FLD_OFF_INVAL, ICE_FLOW_FLD_OFF_INVAL,
ICE_FLOW_FLD_OFF_INVAL, false); elseif (!is_zero_ether_addr(eth_spec->h_dest)) goto err_mask;
return 0;
err_mask:
dev_warn(dev, "Only 00:00:00:00:00:00 or ff:ff:ff:ff:ff:ff MAC address mask is allowed for flow-type ether"); return -EOPNOTSUPP;
}
/** * ice_set_fdir_vlan_seg - set vlan segments for ether flow * @seg: flow segment for programming * @ext_masks: masks for additional RX flow fields * * Return: 0 on success and errno in case of error.
*/ staticint
ice_set_fdir_vlan_seg(struct ice_flow_seg_info *seg, struct ethtool_flow_ext *ext_masks)
{
ICE_FLOW_SET_HDRS(seg, ICE_FLOW_SEG_HDR_VLAN);
if (ext_masks->vlan_etype) { if (ext_masks->vlan_etype != htons(0xFFFF)) return -EOPNOTSUPP;
switch (fsp->flow_type & ~FLOW_EXT) { case TCP_V4_FLOW:
ret = ice_set_fdir_ip4_seg(seg, &fsp->m_u.tcp_ip4_spec,
ICE_FLOW_SEG_HDR_TCP,
&perfect_filter); break; case UDP_V4_FLOW:
ret = ice_set_fdir_ip4_seg(seg, &fsp->m_u.tcp_ip4_spec,
ICE_FLOW_SEG_HDR_UDP,
&perfect_filter); break; case SCTP_V4_FLOW:
ret = ice_set_fdir_ip4_seg(seg, &fsp->m_u.tcp_ip4_spec,
ICE_FLOW_SEG_HDR_SCTP,
&perfect_filter); break; case IPV4_USER_FLOW:
ret = ice_set_fdir_ip4_usr_seg(seg, &fsp->m_u.usr_ip4_spec,
&perfect_filter); break; case TCP_V6_FLOW:
ret = ice_set_fdir_ip6_seg(seg, &fsp->m_u.tcp_ip6_spec,
ICE_FLOW_SEG_HDR_TCP,
&perfect_filter); break; case UDP_V6_FLOW:
ret = ice_set_fdir_ip6_seg(seg, &fsp->m_u.tcp_ip6_spec,
ICE_FLOW_SEG_HDR_UDP,
&perfect_filter); break; case SCTP_V6_FLOW:
ret = ice_set_fdir_ip6_seg(seg, &fsp->m_u.tcp_ip6_spec,
ICE_FLOW_SEG_HDR_SCTP,
&perfect_filter); break; case IPV6_USER_FLOW:
ret = ice_set_fdir_ip6_usr_seg(seg, &fsp->m_u.usr_ip6_spec,
&perfect_filter); break; case ETHER_FLOW:
ret = ice_set_ether_flow_seg(dev, seg, &fsp->m_u.ether_spec); if (!ret && (fsp->m_ext.vlan_etype || fsp->m_ext.vlan_tci)) { if (!ice_fdir_vlan_valid(dev, fsp)) {
ret = -EINVAL; break;
}
ret = ice_set_fdir_vlan_seg(seg, &fsp->m_ext);
} break; default:
ret = -EINVAL;
} if (ret) goto err_exit;
/* tunnel segments are shifted up one. */
memcpy(&tun_seg[1], seg, sizeof(*seg));
/** * 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
*/ staticvoid 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 * @input: filter structure * @add: true adds filter and false removed filter * @is_tun: true adds inner filter on tunnel and false outer headers * * returns 0 on success and negative value on error
*/ int
ice_fdir_write_fltr(struct ice_pf *pf, struct ice_fdir_fltr *input, bool add, bool is_tun)
{ struct device *dev = ice_pf_to_dev(pf); struct ice_hw *hw = &pf->hw; struct ice_fltr_desc desc; struct ice_vsi *ctrl_vsi;
u8 *pkt, *frag_pkt; bool has_frag; int err;
ctrl_vsi = ice_get_ctrl_vsi(pf); if (!ctrl_vsi) return -EINVAL;
/** * ice_fdir_replay_fltrs - replay filters from the HW filter list * @pf: board private structure
*/ void ice_fdir_replay_fltrs(struct ice_pf *pf)
{ struct ice_fdir_fltr *f_rule; struct ice_hw *hw = &pf->hw;
list_for_each_entry(f_rule, &hw->fdir_list_head, fltr_node) { int err = ice_fdir_write_all_fltr(pf, f_rule, true);
if (err)
dev_dbg(ice_pf_to_dev(pf), "Flow Director error %d, could not reprogram filter %d\n",
err, f_rule->fltr_id);
}
}
/** * ice_fdir_create_dflt_rules - create default perfect filters * @pf: PF data structure * * Returns 0 for success or error.
*/ int ice_fdir_create_dflt_rules(struct ice_pf *pf)
{ staticconstenum ice_fltr_ptype dflt_rules[] = {
ICE_FLTR_PTYPE_NONF_IPV4_TCP, ICE_FLTR_PTYPE_NONF_IPV4_UDP,
ICE_FLTR_PTYPE_NONF_IPV6_TCP, ICE_FLTR_PTYPE_NONF_IPV6_UDP,
}; int err;
/* Create perfect TCP and UDP rules in hardware. */ for (int i = 0; i < ARRAY_SIZE(dflt_rules); i++) {
err = ice_create_init_fdir_rule(pf, dflt_rules[i]);
if (err) break;
}
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;
/** * ice_vsi_manage_fdir - turn on/off flow director * @vsi: the VSI being changed * @ena: boolean value indicating if this is an enable or disable request
*/ void ice_vsi_manage_fdir(struct ice_vsi *vsi, bool ena)
{ struct ice_pf *pf = vsi->back; struct ice_hw *hw = &pf->hw; enum ice_fltr_ptype flow;
if (ena) {
set_bit(ICE_FLAG_FD_ENA, pf->flags);
ice_fdir_create_dflt_rules(pf); return;
}
mutex_lock(&hw->fdir_fltr_lock); if (!test_and_clear_bit(ICE_FLAG_FD_ENA, pf->flags)) goto release_lock;
ice_fdir_del_all_fltrs(vsi);
if (hw->fdir_prof) for (flow = ICE_FLTR_PTYPE_NONF_NONE; flow < ICE_FLTR_PTYPE_MAX;
flow++) if (hw->fdir_prof[flow])
ice_fdir_rem_flow(hw, ICE_BLK_FD, flow);
if (need_perfect && test_bit(flow_type, hw->fdir_perfect_fltr)) return;
ice_fdir_rem_flow(hw, ICE_BLK_FD, flow_type); if (need_perfect)
ice_create_init_fdir_rule(pf, flow_type);
}
/** * ice_fdir_update_list_entry - add or delete a filter from the filter list * @pf: PF structure * @input: filter structure * @fltr_idx: ethtool index of filter to modify * * returns 0 on success and negative on errors
*/ staticint
ice_fdir_update_list_entry(struct ice_pf *pf, struct ice_fdir_fltr *input, int fltr_idx)
{ 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.
*/
ice_fdir_do_rem_flow(pf, old_fltr->flow_type);
list_del(&old_fltr->fltr_node);
devm_kfree(ice_hw_to_dev(hw), old_fltr);
} 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;
}
/** * ice_del_fdir_ethtool - delete Flow Director filter * @vsi: pointer to target VSI * @cmd: command to add or delete Flow Director filter * * Returns 0 on success and negative values for failure
*/ int ice_del_fdir_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
{ struct ethtool_rx_flow_spec *fsp =
(struct ethtool_rx_flow_spec *)&cmd->fs; struct ice_pf *pf = vsi->back; struct ice_hw *hw = &pf->hw; int val;
if (!test_bit(ICE_FLAG_FD_ENA, pf->flags)) return -EOPNOTSUPP;
/* Do not delete filters during reset */ if (ice_is_reset_in_progress(pf->state)) {
dev_err(ice_pf_to_dev(pf), "Device is resetting - deleting Flow Director filters not supported during reset\n"); return -EBUSY;
}
if (test_bit(ICE_FD_FLUSH_REQ, pf->state)) return -EBUSY;
mutex_lock(&hw->fdir_fltr_lock);
val = ice_fdir_update_list_entry(pf, NULL, fsp->location);
mutex_unlock(&hw->fdir_fltr_lock);
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
*/ staticvoid
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 * @fsp: pointer to ethtool Rx flow specification * @input: filter structure
*/ staticint
ice_set_fdir_input_set(struct ice_vsi *vsi, struct ethtool_rx_flow_spec *fsp, struct ice_fdir_fltr *input)
{
s16 q_index = ICE_FDIR_NO_QUEUE_IDX;
u16 orig_q_index = 0; struct ice_pf *pf; struct ice_hw *hw; int flow_type;
u16 dest_vsi;
u8 dest_ctl;
/* 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;
input->cnt_index = ICE_FD_SB_STAT_IDX(hw->fd_ctr_base);
input->flow_type = ice_ethtool_flow_to_fltr(flow_type);
/* if no protocol requested, use IPPROTO_NONE */ if (!fsp->m_u.usr_ip6_spec.l4_proto)
input->ip.v6.proto = IPPROTO_NONE; else
input->ip.v6.proto = fsp->h_u.usr_ip6_spec.l4_proto;
/** * ice_add_fdir_ethtool - Add/Remove Flow Director filter * @vsi: pointer to target VSI * @cmd: command to add or delete Flow Director filter * * Returns 0 on success and negative values for failure
*/ int ice_add_fdir_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
{ struct ice_rx_flow_userdef userdata; struct ethtool_rx_flow_spec *fsp; struct ice_fdir_fltr *input; struct device *dev; struct ice_pf *pf; struct ice_hw *hw; int fltrs_needed;
u32 max_location;
u16 tunnel_port; int ret;
if (!vsi) return -EINVAL;
pf = vsi->back;
hw = &pf->hw;
dev = ice_pf_to_dev(pf);
if (!test_bit(ICE_FLAG_FD_ENA, pf->flags)) return -EOPNOTSUPP;
/* Do not program filters during reset */ if (ice_is_reset_in_progress(pf->state)) {
dev_err(dev, "Device is resetting - adding Flow Director filters not supported during reset\n"); return -EBUSY;
}
fsp = (struct ethtool_rx_flow_spec *)&cmd->fs;
if (ice_parse_rx_flow_user_data(fsp, &userdata)) return -EINVAL;
if (fsp->flow_type & FLOW_MAC_EXT) return -EINVAL;
ret = ice_cfg_fdir_xtrct_seq(pf, fsp, &userdata); if (ret) return ret;
max_location = ice_get_fdir_cnt_all(hw); if (fsp->location >= max_location) {
dev_err(dev, "Failed to add filter. The number of ntuple filters or provided location exceed max %d.\n",
max_location); return -ENOSPC;
}
/* return error if not an update and no available filters */
fltrs_needed = ice_get_open_tunnel_port(hw, &tunnel_port, TNL_ALL) ? 2 : 1; if (!ice_fdir_find_fltr_by_idx(hw, fsp->location) &&
ice_fdir_num_avail_fltr(hw, pf->vsi[vsi->idx]) < fltrs_needed) {
dev_err(dev, "Failed to add filter. The maximum number of flow director filters has been reached.\n"); return -ENOSPC;
}
input = devm_kzalloc(dev, sizeof(*input), GFP_KERNEL); if (!input) return -ENOMEM;
ret = ice_set_fdir_input_set(vsi, fsp, input); if (ret) goto free_input;
mutex_lock(&hw->fdir_fltr_lock); if (ice_fdir_is_dup_fltr(hw, input)) {
ret = -EINVAL; goto release_lock;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.