linux-loongson/drivers/net/ethernet/intel/iavf/iavf_fdir.c
Ahmed Zaki 623122ac1c iavf: add support for offloading tc U32 cls filters
Add support for offloading cls U32 filters. Only "skbedit queue_mapping"
and "drop" actions are supported. Also, only "ip" and "802_3" tc
protocols are allowed. The PF must advertise the VIRTCHNL_VF_OFFLOAD_TC_U32
capability flag.

Since the filters will be enabled via the FD stage at the PF, a new type
of FDIR filters is added and the existing list and state machine are used.

The new filters can be used to configure flow directors based on raw
(binary) pattern in the rx packet.

Examples:

0. # tc qdisc add dev enp175s0v0  ingress

1. Redirect UDP from src IP 192.168.2.1 to queue 12:

    # tc filter add dev <dev> protocol ip ingress u32 \
	match u32 0x45000000 0xff000000 at 0  \
	match u32 0x00110000 0x00ff0000 at 8  \
	match u32 0xC0A80201 0xffffffff at 12 \
	match u32 0x00000000 0x00000000 at 24 \
	action skbedit queue_mapping 12 skip_sw

2. Drop all ICMP:

    # tc filter add dev <dev> protocol ip ingress u32 \
	match u32 0x45000000 0xff000000 at 0  \
	match u32 0x00010000 0x00ff0000 at 8  \
	match u32 0x00000000 0x00000000 at 24 \
	action drop skip_sw

3. Redirect ICMP traffic from MAC 3c:fd:fe:a5:47:e0 to queue 7
   (note proto: 802_3):

   # tc filter add dev <dev> protocol 802_3 ingress u32 \
	match u32 0x00003CFD 0x0000ffff at 4   \
	match u32 0xFEA547E0 0xffffffff at 8   \
	match u32 0x08004500 0xffffff00 at 12  \
	match u32 0x00000001 0x000000ff at 20  \
	match u32 0x0000 0x0000 at 40          \
	action skbedit queue_mapping 7 skip_sw

Notes on matches:
1 - All intermediate fields that are needed to parse the correct PTYPE
    must be provided (in e.g. 3: Ethernet Type 0x0800 in MAC, IP version
    and IP length: 0x45 and protocol: 0x01 (ICMP)).
2 - The last match must provide an offset that guarantees all required
    headers are accounted for, even if the last header is not matched.
    For example, in #2, the last match is 4 bytes at offset 24 starting
    from IP header, so the total is 14 (MAC) + 24 + 4 = 42, which is the
    sum of MAC+IP+ICMP headers.

Reviewed-by: Sridhar Samudrala <sridhar.samudrala@intel.com>
Reviewed-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Signed-off-by: Ahmed Zaki <ahmed.zaki@intel.com>
Tested-by: Rafal Romanowski <rafal.romanowski@intel.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
2024-08-13 14:51:29 -07:00

928 lines
26 KiB
C

// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2020, Intel Corporation. */
/* flow director ethtool support for iavf */
#include <linux/bitfield.h>
#include "iavf.h"
#define GTPU_PORT 2152
#define NAT_T_ESP_PORT 4500
#define PFCP_PORT 8805
static const struct in6_addr ipv6_addr_full_mask = {
.in6_u = {
.u6_addr8 = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
}
}
};
static const struct in6_addr ipv6_addr_zero_mask = {
.in6_u = {
.u6_addr8 = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
}
}
};
/**
* iavf_validate_fdir_fltr_masks - validate Flow Director filter fields masks
* @adapter: pointer to the VF adapter structure
* @fltr: Flow Director filter data structure
*
* Returns 0 if all masks of packet fields are either full or empty. Returns
* error on at least one partial mask.
*/
int iavf_validate_fdir_fltr_masks(struct iavf_adapter *adapter,
struct iavf_fdir_fltr *fltr)
{
if (fltr->eth_mask.etype && fltr->eth_mask.etype != htons(U16_MAX))
goto partial_mask;
if (fltr->ip_ver == 4) {
if (fltr->ip_mask.v4_addrs.src_ip &&
fltr->ip_mask.v4_addrs.src_ip != htonl(U32_MAX))
goto partial_mask;
if (fltr->ip_mask.v4_addrs.dst_ip &&
fltr->ip_mask.v4_addrs.dst_ip != htonl(U32_MAX))
goto partial_mask;
if (fltr->ip_mask.tos && fltr->ip_mask.tos != U8_MAX)
goto partial_mask;
} else if (fltr->ip_ver == 6) {
if (memcmp(&fltr->ip_mask.v6_addrs.src_ip, &ipv6_addr_zero_mask,
sizeof(struct in6_addr)) &&
memcmp(&fltr->ip_mask.v6_addrs.src_ip, &ipv6_addr_full_mask,
sizeof(struct in6_addr)))
goto partial_mask;
if (memcmp(&fltr->ip_mask.v6_addrs.dst_ip, &ipv6_addr_zero_mask,
sizeof(struct in6_addr)) &&
memcmp(&fltr->ip_mask.v6_addrs.dst_ip, &ipv6_addr_full_mask,
sizeof(struct in6_addr)))
goto partial_mask;
if (fltr->ip_mask.tclass && fltr->ip_mask.tclass != U8_MAX)
goto partial_mask;
}
if (fltr->ip_mask.proto && fltr->ip_mask.proto != U8_MAX)
goto partial_mask;
if (fltr->ip_mask.src_port && fltr->ip_mask.src_port != htons(U16_MAX))
goto partial_mask;
if (fltr->ip_mask.dst_port && fltr->ip_mask.dst_port != htons(U16_MAX))
goto partial_mask;
if (fltr->ip_mask.spi && fltr->ip_mask.spi != htonl(U32_MAX))
goto partial_mask;
if (fltr->ip_mask.l4_header &&
fltr->ip_mask.l4_header != htonl(U32_MAX))
goto partial_mask;
return 0;
partial_mask:
dev_err(&adapter->pdev->dev, "Failed to add Flow Director filter, partial masks are not supported\n");
return -EOPNOTSUPP;
}
/**
* iavf_pkt_udp_no_pay_len - the length of UDP packet without payload
* @fltr: Flow Director filter data structure
*/
static u16 iavf_pkt_udp_no_pay_len(struct iavf_fdir_fltr *fltr)
{
return sizeof(struct ethhdr) +
(fltr->ip_ver == 4 ? sizeof(struct iphdr) : sizeof(struct ipv6hdr)) +
sizeof(struct udphdr);
}
/**
* iavf_fill_fdir_gtpu_hdr - fill the GTP-U protocol header
* @fltr: Flow Director filter data structure
* @proto_hdrs: Flow Director protocol headers data structure
*
* Returns 0 if the GTP-U protocol header is set successfully
*/
static int
iavf_fill_fdir_gtpu_hdr(struct iavf_fdir_fltr *fltr,
struct virtchnl_proto_hdrs *proto_hdrs)
{
struct virtchnl_proto_hdr *uhdr = &proto_hdrs->proto_hdr[proto_hdrs->count - 1];
struct virtchnl_proto_hdr *ghdr = &proto_hdrs->proto_hdr[proto_hdrs->count++];
struct virtchnl_proto_hdr *ehdr = NULL; /* Extension Header if it exists */
u16 adj_offs, hdr_offs;
int i;
VIRTCHNL_SET_PROTO_HDR_TYPE(ghdr, GTPU_IP);
adj_offs = iavf_pkt_udp_no_pay_len(fltr);
for (i = 0; i < fltr->flex_cnt; i++) {
#define IAVF_GTPU_HDR_TEID_OFFS0 4
#define IAVF_GTPU_HDR_TEID_OFFS1 6
#define IAVF_GTPU_HDR_N_PDU_AND_NEXT_EXTHDR_OFFS 10
#define IAVF_GTPU_HDR_NEXT_EXTHDR_TYPE_MASK 0x00FF /* skip N_PDU */
/* PDU Session Container Extension Header (PSC) */
#define IAVF_GTPU_PSC_EXTHDR_TYPE 0x85
#define IAVF_GTPU_HDR_PSC_PDU_TYPE_AND_QFI_OFFS 13
#define IAVF_GTPU_HDR_PSC_PDU_QFI_MASK 0x3F /* skip Type */
#define IAVF_GTPU_EH_QFI_IDX 1
if (fltr->flex_words[i].offset < adj_offs)
return -EINVAL;
hdr_offs = fltr->flex_words[i].offset - adj_offs;
switch (hdr_offs) {
case IAVF_GTPU_HDR_TEID_OFFS0:
case IAVF_GTPU_HDR_TEID_OFFS1: {
__be16 *pay_word = (__be16 *)ghdr->buffer;
pay_word[hdr_offs >> 1] = htons(fltr->flex_words[i].word);
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(ghdr, GTPU_IP, TEID);
}
break;
case IAVF_GTPU_HDR_N_PDU_AND_NEXT_EXTHDR_OFFS:
if ((fltr->flex_words[i].word &
IAVF_GTPU_HDR_NEXT_EXTHDR_TYPE_MASK) !=
IAVF_GTPU_PSC_EXTHDR_TYPE)
return -EOPNOTSUPP;
if (!ehdr)
ehdr = &proto_hdrs->proto_hdr[proto_hdrs->count++];
VIRTCHNL_SET_PROTO_HDR_TYPE(ehdr, GTPU_EH);
break;
case IAVF_GTPU_HDR_PSC_PDU_TYPE_AND_QFI_OFFS:
if (!ehdr)
return -EINVAL;
ehdr->buffer[IAVF_GTPU_EH_QFI_IDX] =
fltr->flex_words[i].word &
IAVF_GTPU_HDR_PSC_PDU_QFI_MASK;
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(ehdr, GTPU_EH, QFI);
break;
default:
return -EINVAL;
}
}
uhdr->field_selector = 0; /* The PF ignores the UDP header fields */
return 0;
}
/**
* iavf_fill_fdir_pfcp_hdr - fill the PFCP protocol header
* @fltr: Flow Director filter data structure
* @proto_hdrs: Flow Director protocol headers data structure
*
* Returns 0 if the PFCP protocol header is set successfully
*/
static int
iavf_fill_fdir_pfcp_hdr(struct iavf_fdir_fltr *fltr,
struct virtchnl_proto_hdrs *proto_hdrs)
{
struct virtchnl_proto_hdr *uhdr = &proto_hdrs->proto_hdr[proto_hdrs->count - 1];
struct virtchnl_proto_hdr *hdr = &proto_hdrs->proto_hdr[proto_hdrs->count++];
u16 adj_offs, hdr_offs;
int i;
VIRTCHNL_SET_PROTO_HDR_TYPE(hdr, PFCP);
adj_offs = iavf_pkt_udp_no_pay_len(fltr);
for (i = 0; i < fltr->flex_cnt; i++) {
#define IAVF_PFCP_HDR_SFIELD_AND_MSG_TYPE_OFFS 0
if (fltr->flex_words[i].offset < adj_offs)
return -EINVAL;
hdr_offs = fltr->flex_words[i].offset - adj_offs;
switch (hdr_offs) {
case IAVF_PFCP_HDR_SFIELD_AND_MSG_TYPE_OFFS:
hdr->buffer[0] = (fltr->flex_words[i].word >> 8) & 0xff;
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, PFCP, S_FIELD);
break;
default:
return -EINVAL;
}
}
uhdr->field_selector = 0; /* The PF ignores the UDP header fields */
return 0;
}
/**
* iavf_fill_fdir_nat_t_esp_hdr - fill the NAT-T-ESP protocol header
* @fltr: Flow Director filter data structure
* @proto_hdrs: Flow Director protocol headers data structure
*
* Returns 0 if the NAT-T-ESP protocol header is set successfully
*/
static int
iavf_fill_fdir_nat_t_esp_hdr(struct iavf_fdir_fltr *fltr,
struct virtchnl_proto_hdrs *proto_hdrs)
{
struct virtchnl_proto_hdr *uhdr = &proto_hdrs->proto_hdr[proto_hdrs->count - 1];
struct virtchnl_proto_hdr *hdr = &proto_hdrs->proto_hdr[proto_hdrs->count++];
u16 adj_offs, hdr_offs;
u32 spi = 0;
int i;
VIRTCHNL_SET_PROTO_HDR_TYPE(hdr, ESP);
adj_offs = iavf_pkt_udp_no_pay_len(fltr);
for (i = 0; i < fltr->flex_cnt; i++) {
#define IAVF_NAT_T_ESP_SPI_OFFS0 0
#define IAVF_NAT_T_ESP_SPI_OFFS1 2
if (fltr->flex_words[i].offset < adj_offs)
return -EINVAL;
hdr_offs = fltr->flex_words[i].offset - adj_offs;
switch (hdr_offs) {
case IAVF_NAT_T_ESP_SPI_OFFS0:
spi |= fltr->flex_words[i].word << 16;
break;
case IAVF_NAT_T_ESP_SPI_OFFS1:
spi |= fltr->flex_words[i].word;
break;
default:
return -EINVAL;
}
}
if (!spi)
return -EOPNOTSUPP; /* Not support IKE Header Format with SPI 0 */
*(__be32 *)hdr->buffer = htonl(spi);
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, ESP, SPI);
uhdr->field_selector = 0; /* The PF ignores the UDP header fields */
return 0;
}
/**
* iavf_fill_fdir_udp_flex_pay_hdr - fill the UDP payload header
* @fltr: Flow Director filter data structure
* @proto_hdrs: Flow Director protocol headers data structure
*
* Returns 0 if the UDP payload defined protocol header is set successfully
*/
static int
iavf_fill_fdir_udp_flex_pay_hdr(struct iavf_fdir_fltr *fltr,
struct virtchnl_proto_hdrs *proto_hdrs)
{
int err;
switch (ntohs(fltr->ip_data.dst_port)) {
case GTPU_PORT:
err = iavf_fill_fdir_gtpu_hdr(fltr, proto_hdrs);
break;
case NAT_T_ESP_PORT:
err = iavf_fill_fdir_nat_t_esp_hdr(fltr, proto_hdrs);
break;
case PFCP_PORT:
err = iavf_fill_fdir_pfcp_hdr(fltr, proto_hdrs);
break;
default:
err = -EOPNOTSUPP;
break;
}
return err;
}
/**
* iavf_fill_fdir_ip4_hdr - fill the IPv4 protocol header
* @fltr: Flow Director filter data structure
* @proto_hdrs: Flow Director protocol headers data structure
*
* Returns 0 if the IPv4 protocol header is set successfully
*/
static int
iavf_fill_fdir_ip4_hdr(struct iavf_fdir_fltr *fltr,
struct virtchnl_proto_hdrs *proto_hdrs)
{
struct virtchnl_proto_hdr *hdr = &proto_hdrs->proto_hdr[proto_hdrs->count++];
struct iphdr *iph = (struct iphdr *)hdr->buffer;
VIRTCHNL_SET_PROTO_HDR_TYPE(hdr, IPV4);
if (fltr->ip_mask.tos == U8_MAX) {
iph->tos = fltr->ip_data.tos;
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, IPV4, DSCP);
}
if (fltr->ip_mask.proto == U8_MAX) {
iph->protocol = fltr->ip_data.proto;
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, IPV4, PROT);
}
if (fltr->ip_mask.v4_addrs.src_ip == htonl(U32_MAX)) {
iph->saddr = fltr->ip_data.v4_addrs.src_ip;
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, IPV4, SRC);
}
if (fltr->ip_mask.v4_addrs.dst_ip == htonl(U32_MAX)) {
iph->daddr = fltr->ip_data.v4_addrs.dst_ip;
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, IPV4, DST);
}
return 0;
}
/**
* iavf_fill_fdir_ip6_hdr - fill the IPv6 protocol header
* @fltr: Flow Director filter data structure
* @proto_hdrs: Flow Director protocol headers data structure
*
* Returns 0 if the IPv6 protocol header is set successfully
*/
static int
iavf_fill_fdir_ip6_hdr(struct iavf_fdir_fltr *fltr,
struct virtchnl_proto_hdrs *proto_hdrs)
{
struct virtchnl_proto_hdr *hdr = &proto_hdrs->proto_hdr[proto_hdrs->count++];
struct ipv6hdr *iph = (struct ipv6hdr *)hdr->buffer;
VIRTCHNL_SET_PROTO_HDR_TYPE(hdr, IPV6);
if (fltr->ip_mask.tclass == U8_MAX) {
iph->priority = (fltr->ip_data.tclass >> 4) & 0xF;
iph->flow_lbl[0] = FIELD_PREP(0xF0, fltr->ip_data.tclass);
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, IPV6, TC);
}
if (fltr->ip_mask.proto == U8_MAX) {
iph->nexthdr = fltr->ip_data.proto;
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, IPV6, PROT);
}
if (!memcmp(&fltr->ip_mask.v6_addrs.src_ip, &ipv6_addr_full_mask,
sizeof(struct in6_addr))) {
memcpy(&iph->saddr, &fltr->ip_data.v6_addrs.src_ip,
sizeof(struct in6_addr));
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, IPV6, SRC);
}
if (!memcmp(&fltr->ip_mask.v6_addrs.dst_ip, &ipv6_addr_full_mask,
sizeof(struct in6_addr))) {
memcpy(&iph->daddr, &fltr->ip_data.v6_addrs.dst_ip,
sizeof(struct in6_addr));
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, IPV6, DST);
}
return 0;
}
/**
* iavf_fill_fdir_tcp_hdr - fill the TCP protocol header
* @fltr: Flow Director filter data structure
* @proto_hdrs: Flow Director protocol headers data structure
*
* Returns 0 if the TCP protocol header is set successfully
*/
static int
iavf_fill_fdir_tcp_hdr(struct iavf_fdir_fltr *fltr,
struct virtchnl_proto_hdrs *proto_hdrs)
{
struct virtchnl_proto_hdr *hdr = &proto_hdrs->proto_hdr[proto_hdrs->count++];
struct tcphdr *tcph = (struct tcphdr *)hdr->buffer;
VIRTCHNL_SET_PROTO_HDR_TYPE(hdr, TCP);
if (fltr->ip_mask.src_port == htons(U16_MAX)) {
tcph->source = fltr->ip_data.src_port;
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, TCP, SRC_PORT);
}
if (fltr->ip_mask.dst_port == htons(U16_MAX)) {
tcph->dest = fltr->ip_data.dst_port;
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, TCP, DST_PORT);
}
return 0;
}
/**
* iavf_fill_fdir_udp_hdr - fill the UDP protocol header
* @fltr: Flow Director filter data structure
* @proto_hdrs: Flow Director protocol headers data structure
*
* Returns 0 if the UDP protocol header is set successfully
*/
static int
iavf_fill_fdir_udp_hdr(struct iavf_fdir_fltr *fltr,
struct virtchnl_proto_hdrs *proto_hdrs)
{
struct virtchnl_proto_hdr *hdr = &proto_hdrs->proto_hdr[proto_hdrs->count++];
struct udphdr *udph = (struct udphdr *)hdr->buffer;
VIRTCHNL_SET_PROTO_HDR_TYPE(hdr, UDP);
if (fltr->ip_mask.src_port == htons(U16_MAX)) {
udph->source = fltr->ip_data.src_port;
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, UDP, SRC_PORT);
}
if (fltr->ip_mask.dst_port == htons(U16_MAX)) {
udph->dest = fltr->ip_data.dst_port;
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, UDP, DST_PORT);
}
if (!fltr->flex_cnt)
return 0;
return iavf_fill_fdir_udp_flex_pay_hdr(fltr, proto_hdrs);
}
/**
* iavf_fill_fdir_sctp_hdr - fill the SCTP protocol header
* @fltr: Flow Director filter data structure
* @proto_hdrs: Flow Director protocol headers data structure
*
* Returns 0 if the SCTP protocol header is set successfully
*/
static int
iavf_fill_fdir_sctp_hdr(struct iavf_fdir_fltr *fltr,
struct virtchnl_proto_hdrs *proto_hdrs)
{
struct virtchnl_proto_hdr *hdr = &proto_hdrs->proto_hdr[proto_hdrs->count++];
struct sctphdr *sctph = (struct sctphdr *)hdr->buffer;
VIRTCHNL_SET_PROTO_HDR_TYPE(hdr, SCTP);
if (fltr->ip_mask.src_port == htons(U16_MAX)) {
sctph->source = fltr->ip_data.src_port;
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, SCTP, SRC_PORT);
}
if (fltr->ip_mask.dst_port == htons(U16_MAX)) {
sctph->dest = fltr->ip_data.dst_port;
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, SCTP, DST_PORT);
}
return 0;
}
/**
* iavf_fill_fdir_ah_hdr - fill the AH protocol header
* @fltr: Flow Director filter data structure
* @proto_hdrs: Flow Director protocol headers data structure
*
* Returns 0 if the AH protocol header is set successfully
*/
static int
iavf_fill_fdir_ah_hdr(struct iavf_fdir_fltr *fltr,
struct virtchnl_proto_hdrs *proto_hdrs)
{
struct virtchnl_proto_hdr *hdr = &proto_hdrs->proto_hdr[proto_hdrs->count++];
struct ip_auth_hdr *ah = (struct ip_auth_hdr *)hdr->buffer;
VIRTCHNL_SET_PROTO_HDR_TYPE(hdr, AH);
if (fltr->ip_mask.spi == htonl(U32_MAX)) {
ah->spi = fltr->ip_data.spi;
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, AH, SPI);
}
return 0;
}
/**
* iavf_fill_fdir_esp_hdr - fill the ESP protocol header
* @fltr: Flow Director filter data structure
* @proto_hdrs: Flow Director protocol headers data structure
*
* Returns 0 if the ESP protocol header is set successfully
*/
static int
iavf_fill_fdir_esp_hdr(struct iavf_fdir_fltr *fltr,
struct virtchnl_proto_hdrs *proto_hdrs)
{
struct virtchnl_proto_hdr *hdr = &proto_hdrs->proto_hdr[proto_hdrs->count++];
struct ip_esp_hdr *esph = (struct ip_esp_hdr *)hdr->buffer;
VIRTCHNL_SET_PROTO_HDR_TYPE(hdr, ESP);
if (fltr->ip_mask.spi == htonl(U32_MAX)) {
esph->spi = fltr->ip_data.spi;
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, ESP, SPI);
}
return 0;
}
/**
* iavf_fill_fdir_l4_hdr - fill the L4 protocol header
* @fltr: Flow Director filter data structure
* @proto_hdrs: Flow Director protocol headers data structure
*
* Returns 0 if the L4 protocol header is set successfully
*/
static int
iavf_fill_fdir_l4_hdr(struct iavf_fdir_fltr *fltr,
struct virtchnl_proto_hdrs *proto_hdrs)
{
struct virtchnl_proto_hdr *hdr;
__be32 *l4_4_data;
if (!fltr->ip_mask.proto) /* IPv4/IPv6 header only */
return 0;
hdr = &proto_hdrs->proto_hdr[proto_hdrs->count++];
l4_4_data = (__be32 *)hdr->buffer;
/* L2TPv3 over IP with 'Session ID' */
if (fltr->ip_data.proto == 115 && fltr->ip_mask.l4_header == htonl(U32_MAX)) {
VIRTCHNL_SET_PROTO_HDR_TYPE(hdr, L2TPV3);
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, L2TPV3, SESS_ID);
*l4_4_data = fltr->ip_data.l4_header;
} else {
return -EOPNOTSUPP;
}
return 0;
}
/**
* iavf_fill_fdir_eth_hdr - fill the Ethernet protocol header
* @fltr: Flow Director filter data structure
* @proto_hdrs: Flow Director protocol headers data structure
*
* Returns 0 if the Ethernet protocol header is set successfully
*/
static int
iavf_fill_fdir_eth_hdr(struct iavf_fdir_fltr *fltr,
struct virtchnl_proto_hdrs *proto_hdrs)
{
struct virtchnl_proto_hdr *hdr = &proto_hdrs->proto_hdr[proto_hdrs->count++];
struct ethhdr *ehdr = (struct ethhdr *)hdr->buffer;
VIRTCHNL_SET_PROTO_HDR_TYPE(hdr, ETH);
if (fltr->eth_mask.etype == htons(U16_MAX)) {
if (fltr->eth_data.etype == htons(ETH_P_IP) ||
fltr->eth_data.etype == htons(ETH_P_IPV6))
return -EOPNOTSUPP;
ehdr->h_proto = fltr->eth_data.etype;
VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, ETH, ETHERTYPE);
}
return 0;
}
/**
* iavf_fill_fdir_add_msg - fill the Flow Director filter into virtchnl message
* @adapter: pointer to the VF adapter structure
* @fltr: Flow Director filter data structure
*
* Returns 0 if the add Flow Director virtchnl message is filled successfully
*/
int iavf_fill_fdir_add_msg(struct iavf_adapter *adapter, struct iavf_fdir_fltr *fltr)
{
struct virtchnl_fdir_add *vc_msg = &fltr->vc_add_msg;
struct virtchnl_proto_hdrs *proto_hdrs;
int err;
proto_hdrs = &vc_msg->rule_cfg.proto_hdrs;
err = iavf_fill_fdir_eth_hdr(fltr, proto_hdrs); /* L2 always exists */
if (err)
return err;
switch (fltr->flow_type) {
case IAVF_FDIR_FLOW_IPV4_TCP:
err = iavf_fill_fdir_ip4_hdr(fltr, proto_hdrs) |
iavf_fill_fdir_tcp_hdr(fltr, proto_hdrs);
break;
case IAVF_FDIR_FLOW_IPV4_UDP:
err = iavf_fill_fdir_ip4_hdr(fltr, proto_hdrs) |
iavf_fill_fdir_udp_hdr(fltr, proto_hdrs);
break;
case IAVF_FDIR_FLOW_IPV4_SCTP:
err = iavf_fill_fdir_ip4_hdr(fltr, proto_hdrs) |
iavf_fill_fdir_sctp_hdr(fltr, proto_hdrs);
break;
case IAVF_FDIR_FLOW_IPV4_AH:
err = iavf_fill_fdir_ip4_hdr(fltr, proto_hdrs) |
iavf_fill_fdir_ah_hdr(fltr, proto_hdrs);
break;
case IAVF_FDIR_FLOW_IPV4_ESP:
err = iavf_fill_fdir_ip4_hdr(fltr, proto_hdrs) |
iavf_fill_fdir_esp_hdr(fltr, proto_hdrs);
break;
case IAVF_FDIR_FLOW_IPV4_OTHER:
err = iavf_fill_fdir_ip4_hdr(fltr, proto_hdrs) |
iavf_fill_fdir_l4_hdr(fltr, proto_hdrs);
break;
case IAVF_FDIR_FLOW_IPV6_TCP:
err = iavf_fill_fdir_ip6_hdr(fltr, proto_hdrs) |
iavf_fill_fdir_tcp_hdr(fltr, proto_hdrs);
break;
case IAVF_FDIR_FLOW_IPV6_UDP:
err = iavf_fill_fdir_ip6_hdr(fltr, proto_hdrs) |
iavf_fill_fdir_udp_hdr(fltr, proto_hdrs);
break;
case IAVF_FDIR_FLOW_IPV6_SCTP:
err = iavf_fill_fdir_ip6_hdr(fltr, proto_hdrs) |
iavf_fill_fdir_sctp_hdr(fltr, proto_hdrs);
break;
case IAVF_FDIR_FLOW_IPV6_AH:
err = iavf_fill_fdir_ip6_hdr(fltr, proto_hdrs) |
iavf_fill_fdir_ah_hdr(fltr, proto_hdrs);
break;
case IAVF_FDIR_FLOW_IPV6_ESP:
err = iavf_fill_fdir_ip6_hdr(fltr, proto_hdrs) |
iavf_fill_fdir_esp_hdr(fltr, proto_hdrs);
break;
case IAVF_FDIR_FLOW_IPV6_OTHER:
err = iavf_fill_fdir_ip6_hdr(fltr, proto_hdrs) |
iavf_fill_fdir_l4_hdr(fltr, proto_hdrs);
break;
case IAVF_FDIR_FLOW_NON_IP_L2:
break;
default:
err = -EINVAL;
break;
}
if (err)
return err;
vc_msg->vsi_id = adapter->vsi.id;
vc_msg->rule_cfg.action_set.count = 1;
vc_msg->rule_cfg.action_set.actions[0].type = fltr->action;
vc_msg->rule_cfg.action_set.actions[0].act_conf.queue.index = fltr->q_index;
return 0;
}
/**
* iavf_fdir_flow_proto_name - get the flow protocol name
* @flow_type: Flow Director filter flow type
**/
static const char *iavf_fdir_flow_proto_name(enum iavf_fdir_flow_type flow_type)
{
switch (flow_type) {
case IAVF_FDIR_FLOW_IPV4_TCP:
case IAVF_FDIR_FLOW_IPV6_TCP:
return "TCP";
case IAVF_FDIR_FLOW_IPV4_UDP:
case IAVF_FDIR_FLOW_IPV6_UDP:
return "UDP";
case IAVF_FDIR_FLOW_IPV4_SCTP:
case IAVF_FDIR_FLOW_IPV6_SCTP:
return "SCTP";
case IAVF_FDIR_FLOW_IPV4_AH:
case IAVF_FDIR_FLOW_IPV6_AH:
return "AH";
case IAVF_FDIR_FLOW_IPV4_ESP:
case IAVF_FDIR_FLOW_IPV6_ESP:
return "ESP";
case IAVF_FDIR_FLOW_IPV4_OTHER:
case IAVF_FDIR_FLOW_IPV6_OTHER:
return "Other";
case IAVF_FDIR_FLOW_NON_IP_L2:
return "Ethernet";
default:
return NULL;
}
}
/**
* iavf_print_fdir_fltr
* @adapter: adapter structure
* @fltr: Flow Director filter to print
*
* Print the Flow Director filter
**/
void iavf_print_fdir_fltr(struct iavf_adapter *adapter, struct iavf_fdir_fltr *fltr)
{
const char *proto = iavf_fdir_flow_proto_name(fltr->flow_type);
if (!proto)
return;
switch (fltr->flow_type) {
case IAVF_FDIR_FLOW_IPV4_TCP:
case IAVF_FDIR_FLOW_IPV4_UDP:
case IAVF_FDIR_FLOW_IPV4_SCTP:
dev_info(&adapter->pdev->dev, "Rule ID: %u dst_ip: %pI4 src_ip %pI4 %s: dst_port %hu src_port %hu\n",
fltr->loc,
&fltr->ip_data.v4_addrs.dst_ip,
&fltr->ip_data.v4_addrs.src_ip,
proto,
ntohs(fltr->ip_data.dst_port),
ntohs(fltr->ip_data.src_port));
break;
case IAVF_FDIR_FLOW_IPV4_AH:
case IAVF_FDIR_FLOW_IPV4_ESP:
dev_info(&adapter->pdev->dev, "Rule ID: %u dst_ip: %pI4 src_ip %pI4 %s: SPI %u\n",
fltr->loc,
&fltr->ip_data.v4_addrs.dst_ip,
&fltr->ip_data.v4_addrs.src_ip,
proto,
ntohl(fltr->ip_data.spi));
break;
case IAVF_FDIR_FLOW_IPV4_OTHER:
dev_info(&adapter->pdev->dev, "Rule ID: %u dst_ip: %pI4 src_ip %pI4 proto: %u L4_bytes: 0x%x\n",
fltr->loc,
&fltr->ip_data.v4_addrs.dst_ip,
&fltr->ip_data.v4_addrs.src_ip,
fltr->ip_data.proto,
ntohl(fltr->ip_data.l4_header));
break;
case IAVF_FDIR_FLOW_IPV6_TCP:
case IAVF_FDIR_FLOW_IPV6_UDP:
case IAVF_FDIR_FLOW_IPV6_SCTP:
dev_info(&adapter->pdev->dev, "Rule ID: %u dst_ip: %pI6 src_ip %pI6 %s: dst_port %hu src_port %hu\n",
fltr->loc,
&fltr->ip_data.v6_addrs.dst_ip,
&fltr->ip_data.v6_addrs.src_ip,
proto,
ntohs(fltr->ip_data.dst_port),
ntohs(fltr->ip_data.src_port));
break;
case IAVF_FDIR_FLOW_IPV6_AH:
case IAVF_FDIR_FLOW_IPV6_ESP:
dev_info(&adapter->pdev->dev, "Rule ID: %u dst_ip: %pI6 src_ip %pI6 %s: SPI %u\n",
fltr->loc,
&fltr->ip_data.v6_addrs.dst_ip,
&fltr->ip_data.v6_addrs.src_ip,
proto,
ntohl(fltr->ip_data.spi));
break;
case IAVF_FDIR_FLOW_IPV6_OTHER:
dev_info(&adapter->pdev->dev, "Rule ID: %u dst_ip: %pI6 src_ip %pI6 proto: %u L4_bytes: 0x%x\n",
fltr->loc,
&fltr->ip_data.v6_addrs.dst_ip,
&fltr->ip_data.v6_addrs.src_ip,
fltr->ip_data.proto,
ntohl(fltr->ip_data.l4_header));
break;
case IAVF_FDIR_FLOW_NON_IP_L2:
dev_info(&adapter->pdev->dev, "Rule ID: %u eth_type: 0x%x\n",
fltr->loc,
ntohs(fltr->eth_data.etype));
break;
default:
break;
}
}
/**
* iavf_fdir_is_dup_fltr - test if filter is already in list
* @adapter: pointer to the VF adapter structure
* @fltr: Flow Director filter data structure
*
* Returns true if the filter is found in the list
*/
bool iavf_fdir_is_dup_fltr(struct iavf_adapter *adapter, struct iavf_fdir_fltr *fltr)
{
struct iavf_fdir_fltr *tmp;
bool ret = false;
spin_lock_bh(&adapter->fdir_fltr_lock);
list_for_each_entry(tmp, &adapter->fdir_list_head, list) {
if (iavf_is_raw_fdir(fltr))
continue;
if (tmp->flow_type != fltr->flow_type)
continue;
if (!memcmp(&tmp->eth_data, &fltr->eth_data,
sizeof(fltr->eth_data)) &&
!memcmp(&tmp->ip_data, &fltr->ip_data,
sizeof(fltr->ip_data)) &&
!memcmp(&tmp->ext_data, &fltr->ext_data,
sizeof(fltr->ext_data))) {
ret = true;
break;
}
}
spin_unlock_bh(&adapter->fdir_fltr_lock);
return ret;
}
/**
* iavf_find_fdir_fltr - find FDIR filter
* @adapter: pointer to the VF adapter structure
* @is_raw: filter type, is raw (tc u32) or not (ethtool)
* @data: data to ID the filter, type dependent
*
* Returns: pointer to Flow Director filter if found or NULL. Lock must be held.
*/
struct iavf_fdir_fltr *iavf_find_fdir_fltr(struct iavf_adapter *adapter,
bool is_raw, u32 data)
{
struct iavf_fdir_fltr *rule;
list_for_each_entry(rule, &adapter->fdir_list_head, list) {
if ((is_raw && rule->cls_u32_handle == data) ||
(!is_raw && rule->loc == data))
return rule;
}
return NULL;
}
/**
* iavf_fdir_add_fltr - add a new node to the flow director filter list
* @adapter: pointer to the VF adapter structure
* @fltr: filter node to add to structure
*
* Return: 0 on success or negative errno on failure.
*/
int iavf_fdir_add_fltr(struct iavf_adapter *adapter,
struct iavf_fdir_fltr *fltr)
{
struct iavf_fdir_fltr *rule, *parent = NULL;
spin_lock_bh(&adapter->fdir_fltr_lock);
if (iavf_fdir_max_reached(adapter)) {
spin_unlock_bh(&adapter->fdir_fltr_lock);
dev_err(&adapter->pdev->dev,
"Unable to add Flow Director filter (limit (%u) reached)\n",
IAVF_MAX_FDIR_FILTERS);
return -ENOSPC;
}
list_for_each_entry(rule, &adapter->fdir_list_head, list) {
if (iavf_is_raw_fdir(fltr))
break;
if (rule->loc >= fltr->loc)
break;
parent = rule;
}
if (parent)
list_add(&fltr->list, &parent->list);
else
list_add(&fltr->list, &adapter->fdir_list_head);
iavf_inc_fdir_active_fltr(adapter, fltr);
if (adapter->link_up)
fltr->state = IAVF_FDIR_FLTR_ADD_REQUEST;
else
fltr->state = IAVF_FDIR_FLTR_INACTIVE;
spin_unlock_bh(&adapter->fdir_fltr_lock);
if (adapter->link_up)
iavf_schedule_aq_request(adapter, IAVF_FLAG_AQ_ADD_FDIR_FILTER);
return 0;
}
/**
* iavf_fdir_del_fltr - delete a flow director filter from the list
* @adapter: pointer to the VF adapter structure
* @is_raw: filter type, is raw (tc u32) or not (ethtool)
* @data: data to ID the filter, type dependent
*
* Return: 0 on success or negative errno on failure.
*/
int iavf_fdir_del_fltr(struct iavf_adapter *adapter, bool is_raw, u32 data)
{
struct iavf_fdir_fltr *fltr = NULL;
int err = 0;
spin_lock_bh(&adapter->fdir_fltr_lock);
fltr = iavf_find_fdir_fltr(adapter, is_raw, data);
if (fltr) {
if (fltr->state == IAVF_FDIR_FLTR_ACTIVE) {
fltr->state = IAVF_FDIR_FLTR_DEL_REQUEST;
} else if (fltr->state == IAVF_FDIR_FLTR_INACTIVE) {
list_del(&fltr->list);
iavf_dec_fdir_active_fltr(adapter, fltr);
kfree(fltr);
fltr = NULL;
} else {
err = -EBUSY;
}
} else if (adapter->fdir_active_fltr) {
err = -EINVAL;
}
if (fltr && fltr->state == IAVF_FDIR_FLTR_DEL_REQUEST)
iavf_schedule_aq_request(adapter, IAVF_FLAG_AQ_DEL_FDIR_FILTER);
spin_unlock_bh(&adapter->fdir_fltr_lock);
return err;
}