Commit 17a73f6b authored by Joseph Gasparakis's avatar Joseph Gasparakis Committed by Jeff Kirsher
Browse files

i40e: Flow Director sideband accounting



This patch completes implementation of the ethtool ntuple
rule management interface.  It adds the get, update and delete
interface reset.

Change-ID: Ida7f481d9ee4e405ed91340b858eabb18a52fdb5
Signed-off-by: default avatarJoseph Gasparakis <joseph.gasparakis@intel.com>
Signed-off-by: default avatarAnjali Singhai Jain <anjali.singhai@intel.com>
Signed-off-by: default avatarJesse Brandeburg <jesse.brandeburg@intel.com>
Signed-off-by: default avatarCatherine Sullivan <catherine.sullivan@intel.com>
Tested-by: default avatarKavindya Deegala <kavindya.s.deegala@intel.com>
Signed-off-by: default avatarJeff Kirsher <jeffrey.t.kirsher@intel.com>
parent 3415e8ce
......@@ -152,8 +152,18 @@ struct i40e_lump_tracking {
};
#define I40E_DEFAULT_ATR_SAMPLE_RATE 20
#define I40E_FDIR_MAX_RAW_PACKET_LOOKUP 512
struct i40e_fdir_data {
#define I40E_FDIR_MAX_RAW_PACKET_SIZE 512
struct i40e_fdir_filter {
struct hlist_node fdir_node;
/* filter ipnut set */
u8 flow_type;
u8 ip4_proto;
__be32 dst_ip[4];
__be32 src_ip[4];
__be16 src_port;
__be16 dst_port;
__be32 sctp_v_tag;
/* filter control */
u16 q_index;
u8 flex_off;
u8 pctype;
......@@ -162,7 +172,6 @@ struct i40e_fdir_data {
u8 fd_status;
u16 cnt_index;
u32 fd_id;
u8 *raw_packet;
};
#define I40E_ETH_P_LLDP 0x88cc
......@@ -210,6 +219,9 @@ struct i40e_pf {
u8 atr_sample_rate;
bool wol_en;
struct hlist_head fdir_filter_list;
u16 fdir_pf_active_filters;
#ifdef CONFIG_I40E_VXLAN
__be16 vxlan_ports[I40E_MAX_PF_UDP_OFFLOAD_PORTS];
u16 pending_vxlan_bitmap;
......@@ -534,9 +546,10 @@ struct rtnl_link_stats64 *i40e_get_vsi_stats_struct(struct i40e_vsi *vsi);
int i40e_fetch_switch_configuration(struct i40e_pf *pf,
bool printconfig);
int i40e_program_fdir_filter(struct i40e_fdir_data *fdir_data,
int i40e_program_fdir_filter(struct i40e_fdir_filter *fdir_data, u8 *raw_packet,
struct i40e_pf *pf, bool add);
int i40e_add_del_fdir(struct i40e_vsi *vsi,
struct i40e_fdir_filter *input, bool add);
void i40e_set_ethtool_ops(struct net_device *netdev);
struct i40e_mac_filter *i40e_add_filter(struct i40e_vsi *vsi,
u8 *macaddr, s16 vlan,
......
......@@ -1663,21 +1663,22 @@ static ssize_t i40e_dbg_command_write(struct file *filp,
desc = NULL;
} else if ((strncmp(cmd_buf, "add fd_filter", 13) == 0) ||
(strncmp(cmd_buf, "rem fd_filter", 13) == 0)) {
struct i40e_fdir_data fd_data;
struct i40e_fdir_filter fd_data;
u16 packet_len, i, j = 0;
char *asc_packet;
u8 *raw_packet;
bool add = false;
int ret;
asc_packet = kzalloc(I40E_FDIR_MAX_RAW_PACKET_LOOKUP,
asc_packet = kzalloc(I40E_FDIR_MAX_RAW_PACKET_SIZE,
GFP_KERNEL);
if (!asc_packet)
goto command_write_done;
fd_data.raw_packet = kzalloc(I40E_FDIR_MAX_RAW_PACKET_LOOKUP,
GFP_KERNEL);
raw_packet = kzalloc(I40E_FDIR_MAX_RAW_PACKET_SIZE,
GFP_KERNEL);
if (!fd_data.raw_packet) {
if (!raw_packet) {
kfree(asc_packet);
asc_packet = NULL;
goto command_write_done;
......@@ -1698,36 +1699,36 @@ static ssize_t i40e_dbg_command_write(struct file *filp,
cnt);
kfree(asc_packet);
asc_packet = NULL;
kfree(fd_data.raw_packet);
kfree(raw_packet);
goto command_write_done;
}
/* fix packet length if user entered 0 */
if (packet_len == 0)
packet_len = I40E_FDIR_MAX_RAW_PACKET_LOOKUP;
packet_len = I40E_FDIR_MAX_RAW_PACKET_SIZE;
/* make sure to check the max as well */
packet_len = min_t(u16,
packet_len, I40E_FDIR_MAX_RAW_PACKET_LOOKUP);
packet_len, I40E_FDIR_MAX_RAW_PACKET_SIZE);
for (i = 0; i < packet_len; i++) {
sscanf(&asc_packet[j], "%2hhx ",
&fd_data.raw_packet[i]);
&raw_packet[i]);
j += 3;
}
dev_info(&pf->pdev->dev, "FD raw packet dump\n");
print_hex_dump(KERN_INFO, "FD raw packet: ",
DUMP_PREFIX_OFFSET, 16, 1,
fd_data.raw_packet, packet_len, true);
ret = i40e_program_fdir_filter(&fd_data, pf, add);
raw_packet, packet_len, true);
ret = i40e_program_fdir_filter(&fd_data, raw_packet, pf, add);
if (!ret) {
dev_info(&pf->pdev->dev, "Filter command send Status : Success\n");
} else {
dev_info(&pf->pdev->dev,
"Filter command send failed %d\n", ret);
}
kfree(fd_data.raw_packet);
fd_data.raw_packet = NULL;
kfree(raw_packet);
raw_packet = NULL;
kfree(asc_packet);
asc_packet = NULL;
} else if (strncmp(cmd_buf, "fd-atr off", 10) == 0) {
......
......@@ -62,6 +62,9 @@ static const struct i40e_stats i40e_gstrings_net_stats[] = {
I40E_NETDEV_STAT(rx_crc_errors),
};
static int i40e_add_del_fdir_ethtool(struct i40e_vsi *vsi,
struct ethtool_rxnfc *cmd, bool add);
/* These PF_STATs might look like duplicates of some NETDEV_STATs,
* but they are separate. This device supports Virtualization, and
* as such might have several netdevs supporting VMDq and FCoE going
......@@ -1111,6 +1114,84 @@ static int i40e_get_rss_hash_opts(struct i40e_pf *pf, struct ethtool_rxnfc *cmd)
return 0;
}
/**
* i40e_get_ethtool_fdir_all - Populates the rule count of a command
* @pf: Pointer to the physical function struct
* @cmd: The command to get or set Rx flow classification rules
* @rule_locs: Array of used rule locations
*
* This function populates both the total and actual rule count of
* the ethtool flow classification command
*
* Returns 0 on success or -EMSGSIZE if entry not found
**/
static int i40e_get_ethtool_fdir_all(struct i40e_pf *pf,
struct ethtool_rxnfc *cmd,
u32 *rule_locs)
{
struct i40e_fdir_filter *rule;
struct hlist_node *node2;
int cnt = 0;
/* report total rule count */
cmd->data = pf->hw.fdir_shared_filter_count +
pf->fdir_pf_filter_count;
hlist_for_each_entry_safe(rule, node2,
&pf->fdir_filter_list, fdir_node) {
if (cnt == cmd->rule_cnt)
return -EMSGSIZE;
rule_locs[cnt] = rule->fd_id;
cnt++;
}
cmd->rule_cnt = cnt;
return 0;
}
/**
* i40e_get_ethtool_fdir_entry - Look up a filter based on Rx flow
* @pf: Pointer to the physical function struct
* @cmd: The command to get or set Rx flow classification rules
*
* This function looks up a filter based on the Rx flow classification
* command and fills the flow spec info for it if found
*
* Returns 0 on success or -EINVAL if filter not found
**/
static int i40e_get_ethtool_fdir_entry(struct i40e_pf *pf,
struct ethtool_rxnfc *cmd)
{
struct ethtool_rx_flow_spec *fsp =
(struct ethtool_rx_flow_spec *)&cmd->fs;
struct i40e_fdir_filter *rule = NULL;
struct hlist_node *node2;
/* report total rule count */
cmd->data = pf->hw.fdir_shared_filter_count +
pf->fdir_pf_filter_count;
hlist_for_each_entry_safe(rule, node2,
&pf->fdir_filter_list, fdir_node) {
if (fsp->location <= rule->fd_id)
break;
}
if (!rule || fsp->location != rule->fd_id)
return -EINVAL;
fsp->flow_type = rule->flow_type;
fsp->h_u.tcp_ip4_spec.psrc = rule->src_port;
fsp->h_u.tcp_ip4_spec.pdst = rule->dst_port;
fsp->h_u.tcp_ip4_spec.ip4src = rule->src_ip[0];
fsp->h_u.tcp_ip4_spec.ip4dst = rule->dst_ip[0];
fsp->ring_cookie = rule->q_index;
return 0;
}
/**
* i40e_get_rxnfc - command to get RX flow classification rules
* @netdev: network interface device structure
......@@ -1135,15 +1216,15 @@ static int i40e_get_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd,
ret = i40e_get_rss_hash_opts(pf, cmd);
break;
case ETHTOOL_GRXCLSRLCNT:
cmd->rule_cnt = 10;
cmd->rule_cnt = pf->fdir_pf_active_filters;
ret = 0;
break;
case ETHTOOL_GRXCLSRULE:
ret = 0;
ret = i40e_get_ethtool_fdir_entry(pf, cmd);
break;
case ETHTOOL_GRXCLSRLALL:
cmd->data = 500;
ret = 0;
ret = i40e_get_ethtool_fdir_all(pf, cmd, rule_locs);
break;
default:
break;
}
......@@ -1274,289 +1355,158 @@ static int i40e_set_rss_hash_opt(struct i40e_pf *pf, struct ethtool_rxnfc *nfc)
return 0;
}
#define IP_HEADER_OFFSET 14
#define I40E_UDPIP_DUMMY_PACKET_LEN 42
/**
* i40e_add_del_fdir_udpv4 - Add/Remove UDPv4 Flow Director filters for
* a specific flow spec
* @vsi: pointer to the targeted VSI
* @fd_data: the flow director data required from the FDir descriptor
* @ethtool_rx_flow_spec: the flow spec
* @add: true adds a filter, false removes it
* i40e_update_ethtool_fdir_entry - Updates the fdir filter entry
* @vsi: Pointer to the targeted VSI
* @input: The filter to update or NULL to indicate deletion
* @sw_idx: Software index to the filter
* @cmd: The command to get or set Rx flow classification rules
*
* Returns 0 if the filters were successfully added or removed
* This function updates (or deletes) a Flow Director entry from
* the hlist of the corresponding PF
*
* Returns 0 on success
**/
static int i40e_add_del_fdir_udpv4(struct i40e_vsi *vsi,
struct i40e_fdir_data *fd_data,
struct ethtool_rx_flow_spec *fsp, bool add)
static int i40e_update_ethtool_fdir_entry(struct i40e_vsi *vsi,
struct i40e_fdir_filter *input,
u16 sw_idx,
struct ethtool_rxnfc *cmd)
{
struct i40e_fdir_filter *rule, *parent;
struct i40e_pf *pf = vsi->back;
struct udphdr *udp;
struct iphdr *ip;
bool err = false;
int ret;
int i;
char packet[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x08, 0,
0x45, 0, 0, 0x1c, 0, 0, 0x40, 0, 0x40, 0x11,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0};
memcpy(fd_data->raw_packet, packet, I40E_UDPIP_DUMMY_PACKET_LEN);
ip = (struct iphdr *)(fd_data->raw_packet + IP_HEADER_OFFSET);
udp = (struct udphdr *)(fd_data->raw_packet + IP_HEADER_OFFSET
+ sizeof(struct iphdr));
struct hlist_node *node2;
int err = -EINVAL;
ip->saddr = fsp->h_u.tcp_ip4_spec.ip4src;
ip->daddr = fsp->h_u.tcp_ip4_spec.ip4dst;
udp->source = fsp->h_u.tcp_ip4_spec.psrc;
udp->dest = fsp->h_u.tcp_ip4_spec.pdst;
parent = NULL;
rule = NULL;
for (i = I40E_FILTER_PCTYPE_NONF_UNICAST_IPV4_UDP;
i <= I40E_FILTER_PCTYPE_NONF_IPV4_UDP; i++) {
fd_data->pctype = i;
ret = i40e_program_fdir_filter(fd_data, pf, add);
if (ret) {
dev_info(&pf->pdev->dev,
"Filter command send failed for PCTYPE %d (ret = %d)\n",
fd_data->pctype, ret);
err = true;
} else {
dev_info(&pf->pdev->dev,
"Filter OK for PCTYPE %d (ret = %d)\n",
fd_data->pctype, ret);
}
hlist_for_each_entry_safe(rule, node2,
&pf->fdir_filter_list, fdir_node) {
/* hash found, or no matching entry */
if (rule->fd_id >= sw_idx)
break;
parent = rule;
}
return err ? -EOPNOTSUPP : 0;
}
#define I40E_TCPIP_DUMMY_PACKET_LEN 54
/**
* i40e_add_del_fdir_tcpv4 - Add/Remove TCPv4 Flow Director filters for
* a specific flow spec
* @vsi: pointer to the targeted VSI
* @fd_data: the flow director data required from the FDir descriptor
* @ethtool_rx_flow_spec: the flow spec
* @add: true adds a filter, false removes it
*
* Returns 0 if the filters were successfully added or removed
**/
static int i40e_add_del_fdir_tcpv4(struct i40e_vsi *vsi,
struct i40e_fdir_data *fd_data,
struct ethtool_rx_flow_spec *fsp, bool add)
{
struct i40e_pf *pf = vsi->back;
struct tcphdr *tcp;
struct iphdr *ip;
bool err = false;
int ret;
/* Dummy packet */
char packet[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x08, 0,
0x45, 0, 0, 0x28, 0, 0, 0x40, 0, 0x40, 0x6,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0x80, 0x11, 0x0, 0x72, 0, 0, 0, 0};
memcpy(fd_data->raw_packet, packet, I40E_TCPIP_DUMMY_PACKET_LEN);
ip = (struct iphdr *)(fd_data->raw_packet + IP_HEADER_OFFSET);
tcp = (struct tcphdr *)(fd_data->raw_packet + IP_HEADER_OFFSET
+ sizeof(struct iphdr));
ip->daddr = fsp->h_u.tcp_ip4_spec.ip4dst;
tcp->dest = fsp->h_u.tcp_ip4_spec.pdst;
ip->saddr = fsp->h_u.tcp_ip4_spec.ip4src;
tcp->source = fsp->h_u.tcp_ip4_spec.psrc;
if (add) {
if (pf->flags & I40E_FLAG_FD_ATR_ENABLED) {
dev_info(&pf->pdev->dev, "Forcing ATR off, sideband rules for TCP/IPv4 flow being applied\n");
pf->flags &= ~I40E_FLAG_FD_ATR_ENABLED;
/* if there is an old rule occupying our place remove it */
if (rule && (rule->fd_id == sw_idx)) {
if (!input || (rule->fd_id != input->fd_id)) {
cmd->fs.flow_type = rule->flow_type;
err = i40e_add_del_fdir_ethtool(vsi, cmd, false);
}
hlist_del(&rule->fdir_node);
kfree(rule);
pf->fdir_pf_active_filters--;
}
fd_data->pctype = I40E_FILTER_PCTYPE_NONF_IPV4_TCP_SYN;
ret = i40e_program_fdir_filter(fd_data, pf, add);
/* If no input this was a delete, err should be 0 if a rule was
* successfully found and removed from the list else -EINVAL
*/
if (!input)
return err;
if (ret) {
dev_info(&pf->pdev->dev,
"Filter command send failed for PCTYPE %d (ret = %d)\n",
fd_data->pctype, ret);
err = true;
} else {
dev_info(&pf->pdev->dev, "Filter OK for PCTYPE %d (ret = %d)\n",
fd_data->pctype, ret);
}
/* initialize node and set software index */
INIT_HLIST_NODE(&input->fdir_node);
fd_data->pctype = I40E_FILTER_PCTYPE_NONF_IPV4_TCP;
/* add filter to the list */
if (parent)
hlist_add_after(&parent->fdir_node, &input->fdir_node);
else
hlist_add_head(&input->fdir_node,
&pf->fdir_filter_list);
ret = i40e_program_fdir_filter(fd_data, pf, add);
if (ret) {
dev_info(&pf->pdev->dev,
"Filter command send failed for PCTYPE %d (ret = %d)\n",
fd_data->pctype, ret);
err = true;
} else {
dev_info(&pf->pdev->dev, "Filter OK for PCTYPE %d (ret = %d)\n",
fd_data->pctype, ret);
}
/* update counts */
pf->fdir_pf_active_filters++;
return err ? -EOPNOTSUPP : 0;
return 0;
}
/**
* i40e_add_del_fdir_sctpv4 - Add/Remove SCTPv4 Flow Director filters for
* a specific flow spec
* @vsi: pointer to the targeted VSI
* @fd_data: the flow director data required from the FDir descriptor
* @ethtool_rx_flow_spec: the flow spec
* @add: true adds a filter, false removes it
* i40e_del_fdir_entry - Deletes a Flow Director filter entry
* @vsi: Pointer to the targeted VSI
* @cmd: The command to get or set Rx flow classification rules
*
* Returns 0 if the filters were successfully added or removed
**/
static int i40e_add_del_fdir_sctpv4(struct i40e_vsi *vsi,
struct i40e_fdir_data *fd_data,
struct ethtool_rx_flow_spec *fsp, bool add)
{
return -EOPNOTSUPP;
}
#define I40E_IP_DUMMY_PACKET_LEN 34
/**
* i40e_add_del_fdir_ipv4 - Add/Remove IPv4 Flow Director filters for
* a specific flow spec
* @vsi: pointer to the targeted VSI
* @fd_data: the flow director data required for the FDir descriptor
* @fsp: the ethtool flow spec
* @add: true adds a filter, false removes it
* The function removes a Flow Director filter entry from the
* hlist of the corresponding PF
*
* Returns 0 if the filters were successfully added or removed
**/
static int i40e_add_del_fdir_ipv4(struct i40e_vsi *vsi,
struct i40e_fdir_data *fd_data,
struct ethtool_rx_flow_spec *fsp, bool add)
* Returns 0 on success
*/
static int i40e_del_fdir_entry(struct i40e_vsi *vsi,
struct ethtool_rxnfc *cmd)
{
struct ethtool_rx_flow_spec *fsp =
(struct ethtool_rx_flow_spec *)&cmd->fs;
struct i40e_pf *pf = vsi->back;
struct iphdr *ip;
bool err = false;
int ret;
int i;
char packet[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x08, 0,
0x45, 0, 0, 0x14, 0, 0, 0x40, 0, 0x40, 0x10,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int ret = 0;
memcpy(fd_data->raw_packet, packet, I40E_IP_DUMMY_PACKET_LEN);
ip = (struct iphdr *)(fd_data->raw_packet + IP_HEADER_OFFSET);
ip->saddr = fsp->h_u.usr_ip4_spec.ip4src;
ip->daddr = fsp->h_u.usr_ip4_spec.ip4dst;
ip->protocol = fsp->h_u.usr_ip4_spec.proto;
for (i = I40E_FILTER_PCTYPE_NONF_IPV4_OTHER;
i <= I40E_FILTER_PCTYPE_FRAG_IPV4; i++) {
fd_data->pctype = i;
ret = i40e_program_fdir_filter(fd_data, pf, add);
if (ret) {
dev_info(&pf->pdev->dev,
"Filter command send failed for PCTYPE %d (ret = %d)\n",
fd_data->pctype, ret);
err = true;
} else {
dev_info(&pf->pdev->dev,
"Filter OK for PCTYPE %d (ret = %d)\n",
fd_data->pctype, ret);
}
}
ret = i40e_update_ethtool_fdir_entry(vsi, NULL, fsp->location, cmd);
return err ? -EOPNOTSUPP : 0;
return ret;
}
/**
* i40e_add_del_fdir_ethtool - Add/Remove Flow Director filters for
* a specific flow spec based on their protocol
* i40e_add_del_fdir_ethtool - Add/Remove Flow Director filters
* @vsi: pointer to the targeted VSI
* @cmd: command to get or set RX flow classification rules
* @add: true adds a filter, false removes it
*
* Returns 0 if the filters were successfully added or removed
* Add/Remove Flow Director filters for a specific flow spec based on their
* protocol. Returns 0 if the filters were successfully added or removed.
**/
static int i40e_add_del_fdir_ethtool(struct i40e_vsi *vsi,
struct ethtool_rxnfc *cmd, bool add)
struct ethtool_rxnfc *cmd, bool add)
{
struct i40e_fdir_data fd_data;
int ret = -EINVAL;
struct ethtool_rx_flow_spec *fsp;
struct i40e_fdir_filter *input;
struct i40e_pf *pf;
struct ethtool_rx_flow_spec *fsp =
(struct ethtool_rx_flow_spec *)&cmd->fs;
int ret = -EINVAL;
if (!vsi)
return -EINVAL;
fsp = (struct ethtool_rx_flow_spec *)&cmd->fs;
pf = vsi->back;
if ((fsp->ring_cookie != RX_CLS_FLOW_DISC) &&
(fsp->ring_cookie >= vsi->num_queue_pairs))
if (fsp->location >= (pf->hw.func_caps.fd_filters_best_effort +
pf->hw.func_caps.fd_filters_guaranteed)) {
return -EINVAL;
}
/* Populate the Flow Director that we have at the moment
* and allocate the raw packet buffer for the calling functions
*/
fd_data.raw_packet = kzalloc(I40E_FDIR_MAX_RAW_PACKET_LOOKUP,
GFP_KERNEL);
if ((fsp->ring_cookie >= vsi->num_queue_pairs) && add)
return -EINVAL;
if (!fd_data.raw_packet) {
dev_info(&pf->pdev->dev, "Could not allocate memory\n");
return -ENOMEM;
}
input = kzalloc(sizeof(*input), GFP_KERNEL);
fd_data.q_index = fsp->ring_cookie;
fd_data.flex_off = 0;
fd_data.pctype = 0;
fd_data.dest_vsi = vsi->id;
fd_data.dest_ctl = I40E_FILTER_PROGRAM_DESC_DEST_DIRECT_PACKET_QINDEX;
fd_data.fd_status = I40E_FILTER_PROGRAM_DESC_FD_STATUS_FD_ID;
fd_data.cnt_index = 0;
fd_data.fd_id = 0;
if (!input)
return -ENOMEM;
switch (fsp->flow_type & ~FLOW_EXT) {
case TCP_V4_FLOW:
ret = i40e_add_del_fdir_tcpv4(vsi, &fd_data, fsp, add);
break;
case UDP_V4_FLOW:
ret = i40e_add_del_fdir_udpv4(vsi, &fd_data, fsp, add);
break;
case SCTP_V4_FLOW:
ret = i40e_add_del_fdir_sctpv4(vsi, &fd_data, fsp, add);
break;
case IPV4_FLOW:
ret = i40e_add_del_fdir_ipv4(vsi, &fd_data, fsp, add);
break;
case IP_USER_FLOW:
switch (fsp->h_u.usr_ip4_spec.proto) {
case IPPROTO_TCP:
ret = i40e_add_del_fdir_tcpv4(vsi, &fd_data, fsp, add);
break;
case IPPROTO_UDP:
ret = i40e_add_del_fdir_udpv4(vsi, &fd_data, fsp, add);