// SPDX-License-Identifier: (GPL-2.0 OR MIT) /* Google virtual Ethernet (gve) driver * * Copyright (C) 2015-2024 Google LLC */ #include "gve.h" #include "gve_adminq.h" static int gve_fill_ethtool_flow_spec(struct ethtool_rx_flow_spec *fsp, struct gve_adminq_queried_flow_rule *rule) { struct gve_adminq_flow_rule *flow_rule = &rule->flow_rule; static const u16 flow_type_lut[] = { [GVE_FLOW_TYPE_TCPV4] = TCP_V4_FLOW, [GVE_FLOW_TYPE_UDPV4] = UDP_V4_FLOW, [GVE_FLOW_TYPE_SCTPV4] = SCTP_V4_FLOW, [GVE_FLOW_TYPE_AHV4] = AH_V4_FLOW, [GVE_FLOW_TYPE_ESPV4] = ESP_V4_FLOW, [GVE_FLOW_TYPE_TCPV6] = TCP_V6_FLOW, [GVE_FLOW_TYPE_UDPV6] = UDP_V6_FLOW, [GVE_FLOW_TYPE_SCTPV6] = SCTP_V6_FLOW, [GVE_FLOW_TYPE_AHV6] = AH_V6_FLOW, [GVE_FLOW_TYPE_ESPV6] = ESP_V6_FLOW, }; if (be16_to_cpu(flow_rule->flow_type) >= ARRAY_SIZE(flow_type_lut)) return -EINVAL; fsp->flow_type = flow_type_lut[be16_to_cpu(flow_rule->flow_type)]; memset(&fsp->h_u, 0, sizeof(fsp->h_u)); memset(&fsp->h_ext, 0, sizeof(fsp->h_ext)); memset(&fsp->m_u, 0, sizeof(fsp->m_u)); memset(&fsp->m_ext, 0, sizeof(fsp->m_ext)); switch (fsp->flow_type) { case TCP_V4_FLOW: case UDP_V4_FLOW: case SCTP_V4_FLOW: fsp->h_u.tcp_ip4_spec.ip4src = flow_rule->key.src_ip[0]; fsp->h_u.tcp_ip4_spec.ip4dst = flow_rule->key.dst_ip[0]; fsp->h_u.tcp_ip4_spec.psrc = flow_rule->key.src_port; fsp->h_u.tcp_ip4_spec.pdst = flow_rule->key.dst_port; fsp->h_u.tcp_ip4_spec.tos = flow_rule->key.tos; fsp->m_u.tcp_ip4_spec.ip4src = flow_rule->mask.src_ip[0]; fsp->m_u.tcp_ip4_spec.ip4dst = flow_rule->mask.dst_ip[0]; fsp->m_u.tcp_ip4_spec.psrc = flow_rule->mask.src_port; fsp->m_u.tcp_ip4_spec.pdst = flow_rule->mask.dst_port; fsp->m_u.tcp_ip4_spec.tos = flow_rule->mask.tos; break; case AH_V4_FLOW: case ESP_V4_FLOW: fsp->h_u.ah_ip4_spec.ip4src = flow_rule->key.src_ip[0]; fsp->h_u.ah_ip4_spec.ip4dst = flow_rule->key.dst_ip[0]; fsp->h_u.ah_ip4_spec.spi = flow_rule->key.spi; fsp->h_u.ah_ip4_spec.tos = flow_rule->key.tos; fsp->m_u.ah_ip4_spec.ip4src = flow_rule->mask.src_ip[0]; fsp->m_u.ah_ip4_spec.ip4dst = flow_rule->mask.dst_ip[0]; fsp->m_u.ah_ip4_spec.spi = flow_rule->mask.spi; fsp->m_u.ah_ip4_spec.tos = flow_rule->mask.tos; break; case TCP_V6_FLOW: case UDP_V6_FLOW: case SCTP_V6_FLOW: memcpy(fsp->h_u.tcp_ip6_spec.ip6src, &flow_rule->key.src_ip, sizeof(struct in6_addr)); memcpy(fsp->h_u.tcp_ip6_spec.ip6dst, &flow_rule->key.dst_ip, sizeof(struct in6_addr)); fsp->h_u.tcp_ip6_spec.psrc = flow_rule->key.src_port; fsp->h_u.tcp_ip6_spec.pdst = flow_rule->key.dst_port; fsp->h_u.tcp_ip6_spec.tclass = flow_rule->key.tclass; memcpy(fsp->m_u.tcp_ip6_spec.ip6src, &flow_rule->mask.src_ip, sizeof(struct in6_addr)); memcpy(fsp->m_u.tcp_ip6_spec.ip6dst, &flow_rule->mask.dst_ip, sizeof(struct in6_addr)); fsp->m_u.tcp_ip6_spec.psrc = flow_rule->mask.src_port; fsp->m_u.tcp_ip6_spec.pdst = flow_rule->mask.dst_port; fsp->m_u.tcp_ip6_spec.tclass = flow_rule->mask.tclass; break; case AH_V6_FLOW: case ESP_V6_FLOW: memcpy(fsp->h_u.ah_ip6_spec.ip6src, &flow_rule->key.src_ip, sizeof(struct in6_addr)); memcpy(fsp->h_u.ah_ip6_spec.ip6dst, &flow_rule->key.dst_ip, sizeof(struct in6_addr)); fsp->h_u.ah_ip6_spec.spi = flow_rule->key.spi; fsp->h_u.ah_ip6_spec.tclass = flow_rule->key.tclass; memcpy(fsp->m_u.ah_ip6_spec.ip6src, &flow_rule->mask.src_ip, sizeof(struct in6_addr)); memcpy(fsp->m_u.ah_ip6_spec.ip6dst, &flow_rule->mask.dst_ip, sizeof(struct in6_addr)); fsp->m_u.ah_ip6_spec.spi = flow_rule->mask.spi; fsp->m_u.ah_ip6_spec.tclass = flow_rule->mask.tclass; break; default: return -EINVAL; } fsp->ring_cookie = be16_to_cpu(flow_rule->action); return 0; } static int gve_generate_flow_rule(struct gve_priv *priv, struct ethtool_rx_flow_spec *fsp, struct gve_adminq_flow_rule *rule) { static const u16 flow_type_lut[] = { [TCP_V4_FLOW] = GVE_FLOW_TYPE_TCPV4, [UDP_V4_FLOW] = GVE_FLOW_TYPE_UDPV4, [SCTP_V4_FLOW] = GVE_FLOW_TYPE_SCTPV4, [AH_V4_FLOW] = GVE_FLOW_TYPE_AHV4, [ESP_V4_FLOW] = GVE_FLOW_TYPE_ESPV4, [TCP_V6_FLOW] = GVE_FLOW_TYPE_TCPV6, [UDP_V6_FLOW] = GVE_FLOW_TYPE_UDPV6, [SCTP_V6_FLOW] = GVE_FLOW_TYPE_SCTPV6, [AH_V6_FLOW] = GVE_FLOW_TYPE_AHV6, [ESP_V6_FLOW] = GVE_FLOW_TYPE_ESPV6, }; u32 flow_type; if (fsp->ring_cookie == RX_CLS_FLOW_DISC) return -EOPNOTSUPP; if (fsp->ring_cookie >= priv->rx_cfg.num_queues) return -EINVAL; rule->action = cpu_to_be16(fsp->ring_cookie); flow_type = fsp->flow_type & ~(FLOW_EXT | FLOW_MAC_EXT | FLOW_RSS); if (!flow_type || flow_type >= ARRAY_SIZE(flow_type_lut)) return -EINVAL; rule->flow_type = cpu_to_be16(flow_type_lut[flow_type]); switch (flow_type) { case TCP_V4_FLOW: case UDP_V4_FLOW: case SCTP_V4_FLOW: rule->key.src_ip[0] = fsp->h_u.tcp_ip4_spec.ip4src; rule->key.dst_ip[0] = fsp->h_u.tcp_ip4_spec.ip4dst; rule->key.src_port = fsp->h_u.tcp_ip4_spec.psrc; rule->key.dst_port = fsp->h_u.tcp_ip4_spec.pdst; rule->mask.src_ip[0] = fsp->m_u.tcp_ip4_spec.ip4src; rule->mask.dst_ip[0] = fsp->m_u.tcp_ip4_spec.ip4dst; rule->mask.src_port = fsp->m_u.tcp_ip4_spec.psrc; rule->mask.dst_port = fsp->m_u.tcp_ip4_spec.pdst; break; case AH_V4_FLOW: case ESP_V4_FLOW: rule->key.src_ip[0] = fsp->h_u.tcp_ip4_spec.ip4src; rule->key.dst_ip[0] = fsp->h_u.tcp_ip4_spec.ip4dst; rule->key.spi = fsp->h_u.ah_ip4_spec.spi; rule->mask.src_ip[0] = fsp->m_u.tcp_ip4_spec.ip4src; rule->mask.dst_ip[0] = fsp->m_u.tcp_ip4_spec.ip4dst; rule->mask.spi = fsp->m_u.ah_ip4_spec.spi; break; case TCP_V6_FLOW: case UDP_V6_FLOW: case SCTP_V6_FLOW: memcpy(&rule->key.src_ip, fsp->h_u.tcp_ip6_spec.ip6src, sizeof(struct in6_addr)); memcpy(&rule->key.dst_ip, fsp->h_u.tcp_ip6_spec.ip6dst, sizeof(struct in6_addr)); rule->key.src_port = fsp->h_u.tcp_ip6_spec.psrc; rule->key.dst_port = fsp->h_u.tcp_ip6_spec.pdst; memcpy(&rule->mask.src_ip, fsp->m_u.tcp_ip6_spec.ip6src, sizeof(struct in6_addr)); memcpy(&rule->mask.dst_ip, fsp->m_u.tcp_ip6_spec.ip6dst, sizeof(struct in6_addr)); rule->mask.src_port = fsp->m_u.tcp_ip6_spec.psrc; rule->mask.dst_port = fsp->m_u.tcp_ip6_spec.pdst; break; case AH_V6_FLOW: case ESP_V6_FLOW: memcpy(&rule->key.src_ip, fsp->h_u.usr_ip6_spec.ip6src, sizeof(struct in6_addr)); memcpy(&rule->key.dst_ip, fsp->h_u.usr_ip6_spec.ip6dst, sizeof(struct in6_addr)); rule->key.spi = fsp->h_u.ah_ip6_spec.spi; memcpy(&rule->mask.src_ip, fsp->m_u.usr_ip6_spec.ip6src, sizeof(struct in6_addr)); memcpy(&rule->mask.dst_ip, fsp->m_u.usr_ip6_spec.ip6dst, sizeof(struct in6_addr)); rule->key.spi = fsp->h_u.ah_ip6_spec.spi; break; default: /* not doing un-parsed flow types */ return -EINVAL; } return 0; } int gve_get_flow_rule_entry(struct gve_priv *priv, struct ethtool_rxnfc *cmd) { struct gve_adminq_queried_flow_rule *rules_cache = priv->flow_rules_cache.rules_cache; struct ethtool_rx_flow_spec *fsp = (struct ethtool_rx_flow_spec *)&cmd->fs; u32 *cache_num = &priv->flow_rules_cache.rules_cache_num; struct gve_adminq_queried_flow_rule *rule = NULL; int err = 0; u32 i; if (!priv->max_flow_rules) return -EOPNOTSUPP; if (!priv->flow_rules_cache.rules_cache_synced || fsp->location < be32_to_cpu(rules_cache[0].location) || fsp->location > be32_to_cpu(rules_cache[*cache_num - 1].location)) { err = gve_adminq_query_flow_rules(priv, GVE_FLOW_RULE_QUERY_RULES, fsp->location); if (err) return err; priv->flow_rules_cache.rules_cache_synced = true; } for (i = 0; i < *cache_num; i++) { if (fsp->location == be32_to_cpu(rules_cache[i].location)) { rule = &rules_cache[i]; break; } } if (!rule) return -EINVAL; err = gve_fill_ethtool_flow_spec(fsp, rule); return err; } int gve_get_flow_rule_ids(struct gve_priv *priv, struct ethtool_rxnfc *cmd, u32 *rule_locs) { __be32 *rule_ids_cache = priv->flow_rules_cache.rule_ids_cache; u32 *cache_num = &priv->flow_rules_cache.rule_ids_cache_num; u32 starting_rule_id = 0; u32 i = 0, j = 0; int err = 0; if (!priv->max_flow_rules) return -EOPNOTSUPP; do { err = gve_adminq_query_flow_rules(priv, GVE_FLOW_RULE_QUERY_IDS, starting_rule_id); if (err) return err; for (i = 0; i < *cache_num; i++) { if (j >= cmd->rule_cnt) return -EMSGSIZE; rule_locs[j++] = be32_to_cpu(rule_ids_cache[i]); starting_rule_id = be32_to_cpu(rule_ids_cache[i]) + 1; } } while (*cache_num != 0); cmd->data = priv->max_flow_rules; return err; } int gve_add_flow_rule(struct gve_priv *priv, struct ethtool_rxnfc *cmd) { struct ethtool_rx_flow_spec *fsp = &cmd->fs; struct gve_adminq_flow_rule *rule = NULL; int err; if (!priv->max_flow_rules) return -EOPNOTSUPP; rule = kvzalloc(sizeof(*rule), GFP_KERNEL); if (!rule) return -ENOMEM; err = gve_generate_flow_rule(priv, fsp, rule); if (err) goto out; err = gve_adminq_add_flow_rule(priv, rule, fsp->location); out: kvfree(rule); if (err) dev_err(&priv->pdev->dev, "Failed to add the flow rule: %u", fsp->location); return err; } int gve_del_flow_rule(struct gve_priv *priv, struct ethtool_rxnfc *cmd) { struct ethtool_rx_flow_spec *fsp = (struct ethtool_rx_flow_spec *)&cmd->fs; if (!priv->max_flow_rules) return -EOPNOTSUPP; return gve_adminq_del_flow_rule(priv, fsp->location); }