From 9536cc094503ba369d86e36cb9ea09bc3c83bdfd Mon Sep 17 00:00:00 2001 From: Julius Bairaktaris Date: Mon, 24 Aug 2026 11:16:11 +0200 Subject: [PATCH] qualcommax: qca_ppe: add tc-flower and ethtool n-tuple ACL offload The ACL engine has 512 entries, and the driver uses two at fixed indices with no way to install a rule from userspace. Add an allocator that places a rule on the entry chains the hardware can merge, with range compares on even entries, move the two small-packet rules onto it, and implement cls_flower_add and cls_flower_del for MAC, IPv4, IPv6, L4 port and port range, ICMP, IP protocol, DSCP, TCP flag and fragment keys, refusing any other key. The actions are pass, drop, trap, skbedit priority, redirect, mirred mirror, which claims both of the port mirror's analyzers, pedit of the DSCP byte and police on one of the 512 ACL meters, and set_rxnfc gives ethtool -N the queue action, refusing queues 12 to 143, the CPU port queues the scheduler does not serve, and a VF or RSS context, which the switch does not have. The engine matches the highest PRI, so a rule's priority is the 9-bit span minus its tc preference. The address family comes from the control key, since cls_flower registers both address keys over one union, and from the registered address key where ethtool_rx_flow_rule_create() leaves the control key out. On IPQ8074 every accepted action installs in_hw, an ICMP drop filter drops in the switch, and a pedit to 0x88 moves the datagrams to another egress queue. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Julius Bairaktaris Link: https://github.com/openwrt/openwrt/pull/25700 Signed-off-by: Robert Marko --- .../drivers/net/ethernet/qualcomm/qca_ppe.h | 133 +- .../net/ethernet/qualcomm/qca_ppe_acl.c | 1294 +++++++++++++++++ .../net/ethernet/qualcomm/qca_ppe_main.c | 25 +- .../net/ethernet/qualcomm/qca_ppe_scheduler.c | 6 +- .../0952-net-ethernet-qca-add-ppe.patch | 2 +- .../0953-net-ethernet-qca-add-EDMA.patch | 2 +- 6 files changed, 1442 insertions(+), 20 deletions(-) create mode 100644 target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_acl.c diff --git a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe.h b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe.h index 864a86b3727..5a6e176852e 100644 --- a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe.h +++ b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe.h @@ -271,39 +271,95 @@ #define PPE_RSS_HASH_MIX_IPV4(i) (0x0b0000 + 0x4390 + (i) * 0x4) #define PPE_RSS_HASH_FIN_IPV4(i) (0x0b0000 + 0x43b0 + (i) * 0x4) -/* --- ACL (rules in the IPO block, actions beside the L2 tables) --- - * A rule, its mask and its action share one flat index. A rule whose RANGE_EN - * is set must sit at an even index. There is no valid bit and no global - * enable: a rule with an empty source bitmap matches nothing, one with ports - * in it is live. +/* --- ACL: rules and masks in the IPO block, actions beside the L2 tables, + * hit counters in the ingress policer --- + * A rule, its mask and its action share one flat index. The 512 indices are + * 64 hardware lists of eight: a rule whose key is wider than one entry takes + * several entries of one list and the list's RULE_EXT bits merge them into a + * single match. A rule whose RANGE_EN is set compares one of its fields + * against a range - that field's mask slot carries the maximum, the rest of + * the mask stays a mask - and must sit at an even index. There is no valid + * bit and no global enable: a rule with an empty source bitmap matches + * nothing, one with ports in it is live. */ +#define PPE_ACL_LISTS 64 +#define PPE_ACL_LIST_ENTRIES 8 + #define PPE_ACL_RULE(i) (0x0b0000 + (i) * 0x10) /* 3 implemented words in a 4-word slot; whole slots are written so the * latch on the slot's last word always fires (same for the mask below). + * Words 0 and 1 carry a 53-bit key whose fields belong to the rule type. */ #define PPE_ACL_RULE_WORDS 4 -#define PPE_ACL_L3_LEN GENMASK(15, 0) /* word 0; range min */ -#define PPE_ACL_IS_IPV6 BIT(17) /* word 1 */ -#define PPE_ACL_RANGE_EN BIT(21) /* word 1 */ -#define PPE_ACL_RULE_TYPE GENMASK(26, 23) /* word 1 */ +#define PPE_ACL_MAC_HI GENMASK(15, 0) /* w1: bytes 1-0 */ +#define PPE_ACL_IP_PORT GENMASK(15, 0) /* w0: L4 or ICMP */ +#define PPE_ACL_IP_LO GENMASK(31, 16) /* w0 */ +#define PPE_ACL_IP_HI GENMASK(15, 0) /* w1 */ +#define PPE_ACL_L3_LEN GENMASK(15, 0) /* w0; range min */ +#define PPE_ACL_L3_PROT GENMASK(23, 16) /* w0 */ +#define PPE_ACL_L3_DSCP GENMASK(31, 24) /* w0 */ +#define PPE_ACL_TCP_FLAGS GENMASK(6, 1) /* w1 */ +#define PPE_ACL_L3_FRAG BIT(16) /* w1 */ +#define PPE_ACL_IS_IPV6 BIT(17) /* w1 */ +#define PPE_ACL_RANGE_EN BIT(21) /* w1 */ +#define PPE_ACL_RULE_TYPE GENMASK(26, 23) /* w1 */ +#define PPE_ACL_TYPE_MAC_DA 0 +#define PPE_ACL_TYPE_MAC_SA 1 +#define PPE_ACL_TYPE_IPV4_DIP 4 +#define PPE_ACL_TYPE_IPV4_SIP 5 +#define PPE_ACL_TYPE_IPV6_DIP0 6 /* +1, +2 for the rest */ +#define PPE_ACL_TYPE_IPV6_SIP0 9 #define PPE_ACL_TYPE_IPMISC 12 -#define PPE_ACL_SRC_LO GENMASK(31, 29) /* word 1: ports 0-2 */ -#define PPE_ACL_SRC_HI GENMASK(4, 0) /* word 2: ports 3-7 */ +#define PPE_ACL_SRC_TYPE GENMASK(28, 27) /* w1 */ +#define PPE_ACL_SRC_PORT_BMP 0 +#define PPE_ACL_SRC_LO GENMASK(31, 29) /* w1: ports 0-2 */ +#define PPE_ACL_SRC_HI GENMASK(4, 0) /* w2: ports 3-7 */ +#define PPE_ACL_RULE_PRI GENMASK(13, 5) /* w2 */ #define PPE_ACL_MASK(i) (0x0b2000 + (i) * 0x10) #define PPE_ACL_MASK_WORDS 4 /* 2 implemented; same field layout * as the rule, and under RANGE_EN - * the length field carries the - * range max + * the ranged field's slot carries + * its maximum */ +/* One register per hardware list, one bit per mergeable pair of its entries: + * EXT1 merges (0,1) (2,3) (4,5) (6,7), EXT2 merges (0,2) (4,6), EXT4 (0,4). + * No other pairing exists, which is what limits a wide rule's placement. + */ +#define PPE_ACL_RULE_EXT1(l) (0x0b4000 + (l) * 0x4) +#define PPE_ACL_RULE_EXT2(l) (0x0b4100 + (l) * 0x4) +#define PPE_ACL_RULE_EXT4(l) (0x0b4200 + (l) * 0x4) #define PPE_ACL_ACTION(i) (0x068000 + (i) * 0x20) /* 5 implemented words in an 8-word slot; the entry latches on the write to * the slot's last word, so all 8 are written. */ #define PPE_ACL_ACTION_WORDS 8 +#define PPE_ACL_DEST_CHANGE_EN BIT(0) /* word 0 */ +#define PPE_ACL_FWD_CMD GENMASK(2, 1) /* word 0 */ +#define PPE_ACL_FWD_FORWARD 0 +#define PPE_ACL_FWD_DROP 1 +#define PPE_ACL_FWD_RDT_CPU 3 +/* The destination that replaces the one the lookup found: a twelve-bit + * value under a two-bit type. + */ +#define PPE_ACL_DEST_VALUE GENMASK(14, 3) /* word 0 */ +#define PPE_ACL_DEST_TYPE GENMASK(16, 15) /* word 0 */ +#define PPE_ACL_DEST_PORT_BMP 3 +#define PPE_ACL_MIRROR_EN BIT(17) /* word 0 */ +#define PPE_ACL_DSCP_TC_CHANGE_EN BIT(14) /* word 2 */ +#define PPE_ACL_DSCP_TC GENMASK(22, 15) /* word 2 */ #define PPE_ACL_PRI_CHANGE_EN BIT(3) /* word 3 */ #define PPE_ACL_PRI GENMASK(7, 4) /* word 3 */ +#define PPE_ACL_POLICER_EN BIT(11) /* word 3 */ +#define PPE_ACL_POLICER_INDEX GENMASK(20, 12) /* word 3 */ +#define PPE_ACL_QID_EN BIT(21) /* word 3 */ +#define PPE_ACL_QID GENMASK(29, 22) /* word 3 */ +/* The counters live in the ingress policer block, not the IPO: the vendor + * reaches them at its 0x100000 base plus the table's own 0x74000. Packets in + * word 0, the low 32 bits of the byte count in word 1, its top 8 in word 2. + */ #define PPE_ACL_CNT(i) (0x174000 + (i) * 0x10) #define PPE_ACL_CNT_ENTRIES 512 +#define PPE_ACL_CNT_WORDS 3 /* Read-only, and counting whether or not any rule is installed. */ #define PPE_ACL_GLB_HIT_CNT (0x0b0000 + 0x430c) #define PPE_ACL_GLB_MISS_CNT (0x0b0000 + 0x4310) @@ -466,6 +522,29 @@ #define PPE_POLICER_TIME_SLOT (PPE_POLICER_BASE + 0x40) #define PPE_POLICER_SLOT_TIME GENMASK(9, 0) +/* The refresh period every meter in the block shares, in units of 8 PPE + * clocks, as ppe_rate_limit_init programs it. + */ +#define PPE_POLICER_SLOT 600 + +/* One meter per classifier rule, bound by the rule's action. The fields are + * the port meter's in a different order and without its frame-type filter, + * and the two words past the rate carry the exceed and violate actions: left + * at zero, a violation drops. + */ +#define PPE_ACL_METER(i) (PPE_POLICER_BASE + 0x4000 + (i) * 0x10) +#define PPE_ACL_METER_ENTRIES 512 +#define PPE_ACL_METER_EN BIT(0) /* word 0 */ +#define PPE_ACL_METER_MODE BIT(3) /* word 0 */ +#define PPE_ACL_METER_TOKEN_UNIT GENMASK(6, 4) /* word 0 */ +#define PPE_ACL_METER_CBS GENMASK(23, 8) /* word 0 */ +#define PPE_ACL_METER_CIR_LO GENMASK(31, 24) /* word 0 */ +#define PPE_ACL_METER_CIR_HI GENMASK(9, 0) /* word 1 */ + +/* The meter's token buckets, committed and excess, two words that commit on + * the second - the port meter's table one block over, per meter index. + */ +#define PPE_ACL_METER_CRDT(i) (PPE_POLICER_BASE + 0x8000 + (i) * 0x10) #define PPE_PORT_METER_W0(p) (PPE_POLICER_BASE + 0xc000 + (p) * 0x10) #define PPE_METER_EN BIT(0) @@ -701,6 +780,10 @@ #define PPE_MAX_SP_PRI 8 #define PPE_L0_QUEUES 300 #define PPE_L0_UCAST_QUEUES 256 +/* The CPU port's unicast queues the L0 scheduler serves (l0_port0[]). */ +#define PPE_CPU_UCAST_QUEUES 12 +/* First unicast queue of a user port. */ +#define PPE_PORT_UCAST_BASE 144 #define PPE_BM_BUF_SIZE 256 #define PPE_BM_PORTS 15 @@ -825,6 +908,14 @@ struct qca_ppe_priv { * holds none. */ struct mutex vlan_lock; + /* Free entries of every ACL hardware list. Guarded by its own lock + * because the small-packet module parameters reach the table from a + * writer that holds no rtnl. + */ + u8 acl_free[PPE_ACL_LISTS]; + DECLARE_BITMAP(acl_meter_used, PPE_ACL_METER_ENTRIES); + struct list_head acl_rules; + struct mutex acl_lock; /* Indexed by direction: egress 0, ingress 1. */ u8 mirror_port[2]; u16 mirror_ref[2]; @@ -907,6 +998,7 @@ struct tc_ets_qopt_offload; struct tc_prio_qopt_offload; struct tc_query_caps_base; struct tc_mqprio_qopt_offload; +struct flow_cls_offload; void ppe_scheduler_init(struct qca_ppe_priv *priv); int qca_ppe_setup_tc(struct dsa_switch *ds, int port, enum tc_setup_type type, @@ -938,6 +1030,8 @@ int qca_ppe_port_policer_add(struct dsa_switch *ds, int port, const struct flow_action_police *policer, struct netlink_ext_ack *extack); void qca_ppe_port_policer_del(struct dsa_switch *ds, int port); +int ppe_token_bucket(unsigned long clk, u32 slot, u64 rate_bps, u32 burst, + u32 cir_max, u32 cbs_max, u32 *cir, u32 *cbs); int ppe_vsi_alloc(struct qca_ppe_priv *priv); void ppe_vsi_free(struct qca_ppe_priv *priv, u32 vsi); @@ -959,4 +1053,17 @@ unsigned long ppe_clk_rate(struct qca_ppe_priv *priv); void ppe_debugfs_init(struct qca_ppe_priv *priv); void ppe_debugfs_exit(struct qca_ppe_priv *priv); +void ppe_acl_init(struct qca_ppe_priv *priv); +void ppe_acl_exit(struct qca_ppe_priv *priv); +int qca_ppe_cls_flower_add(struct dsa_switch *ds, int port, + struct flow_cls_offload *cls, bool ingress); +int qca_ppe_cls_flower_del(struct dsa_switch *ds, int port, + struct flow_cls_offload *cls, bool ingress); +int qca_ppe_set_rxnfc(struct dsa_switch *ds, int port, + struct ethtool_rxnfc *nfc); +int qca_ppe_get_rxnfc(struct dsa_switch *ds, int port, + struct ethtool_rxnfc *nfc, u32 *rule_locs); +int ppe_mirror_analyzer_get(struct qca_ppe_priv *priv, int to_port); +void ppe_mirror_analyzer_put(struct qca_ppe_priv *priv); + #endif diff --git a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_acl.c b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_acl.c new file mode 100644 index 00000000000..7ef0d10c8c5 --- /dev/null +++ b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_acl.c @@ -0,0 +1,1294 @@ +// SPDX-License-Identifier: GPL-2.0-or-later OR MIT +/* Copyright (c) 2026 Julius Bairaktaris */ +/* Hardware classifier (ACL/IPO) for the Qualcomm PPE. + * + * The engine holds 512 entries as 64 lists of eight. One entry carries a + * 53-bit key, a mask and an action, and a rule needing more key than that + * takes several entries of one list which the list's RULE_EXT bits merge into + * a single match. Only the pairs (0,1) (2,3) (4,5) (6,7), (0,2) (4,6) and + * (0,4) can be merged, so a rule of n entries has to land on one of the shapes + * those pairings build - which is what the allocator here is for. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "qca_ppe.h" + +/* One entry of a rule: the key, its mask, and the rule type that tells the + * engine which bits of the packet the key is compared against. A range + * compare takes the mask slot of the field it names for the range maximum, + * leaving the entry's other masks alone, and the entry it lands on has to be + * an even one. + */ +struct ppe_acl_slice { + u8 type; + bool range; + u32 key[2]; + u32 mask[2]; +}; + +/* The entries of one list a rule may occupy, and the RULE_EXT bits that chain + * them. Taken from qca-ssdk's table in its order: the single-entry shapes + * name the odd entries first, which leaves the even ones - the only place a + * range compare may sit - free for longer. + */ +static const struct { + u8 num; + u8 ext1; + u8 ext2; + u8 ext4; + u8 entries; +} ppe_acl_shape[] = { + { 1, 0x0, 0x0, 0x0, 0x02 }, + { 1, 0x0, 0x0, 0x0, 0x08 }, + { 1, 0x0, 0x0, 0x0, 0x20 }, + { 1, 0x0, 0x0, 0x0, 0x80 }, + { 1, 0x0, 0x0, 0x0, 0x01 }, + { 1, 0x0, 0x0, 0x0, 0x04 }, + { 1, 0x0, 0x0, 0x0, 0x10 }, + { 1, 0x0, 0x0, 0x0, 0x40 }, + { 2, 0x1, 0x0, 0x0, 0x03 }, + { 2, 0x2, 0x0, 0x0, 0x0c }, + { 2, 0x4, 0x0, 0x0, 0x30 }, + { 2, 0x8, 0x0, 0x0, 0xc0 }, + { 2, 0x0, 0x1, 0x0, 0x05 }, + { 2, 0x0, 0x2, 0x0, 0x50 }, + { 2, 0x0, 0x0, 0x1, 0x11 }, + { 3, 0x1, 0x1, 0x0, 0x07 }, + { 3, 0x1, 0x0, 0x1, 0x13 }, + { 3, 0x2, 0x1, 0x0, 0x0d }, + { 3, 0x4, 0x2, 0x0, 0x70 }, + { 3, 0x4, 0x0, 0x1, 0x31 }, + { 3, 0x8, 0x2, 0x0, 0xd0 }, + { 3, 0x0, 0x1, 0x1, 0x15 }, + { 3, 0x0, 0x2, 0x1, 0x51 }, + { 4, 0x3, 0x1, 0x0, 0x0f }, + { 4, 0x5, 0x0, 0x1, 0x33 }, + { 4, 0x2, 0x1, 0x1, 0x1d }, + { 4, 0xc, 0x2, 0x0, 0xf0 }, + { 4, 0x4, 0x1, 0x1, 0x35 }, + { 4, 0x8, 0x2, 0x1, 0xd1 }, + { 4, 0x0, 0x3, 0x1, 0x55 }, + { 5, 0x3, 0x1, 0x1, 0x1f }, + { 5, 0x6, 0x1, 0x1, 0x3d }, + { 5, 0xc, 0x2, 0x1, 0xf1 }, + { 5, 0x8, 0x3, 0x1, 0xd5 }, + { 6, 0x7, 0x1, 0x1, 0x3f }, + { 6, 0x6, 0x3, 0x1, 0x7d }, + { 6, 0xc, 0x3, 0x1, 0xf5 }, + { 7, 0x7, 0x3, 0x1, 0x7f }, + { 7, 0xe, 0x3, 0x1, 0xfd }, + { 8, 0xf, 0x3, 0x1, 0xff }, +}; + +/* Entries of a list a range compare may sit on. */ +#define PPE_ACL_EVEN_ENTRIES 0x55 + +/* One rule's placement: which list, which of its entries, and the RULE_EXT + * bits this rule set in that list. + */ +struct ppe_acl_group { + u8 list; + u8 entries; + u8 ext1; + u8 ext2; + u8 ext4; + u8 index[PPE_ACL_LIST_ENTRIES]; + u8 nslices; +}; + +static void ppe_acl_ext_write(struct qca_ppe_priv *priv, + const struct ppe_acl_group *g, bool set) +{ + regmap_update_bits(priv->regmap, PPE_ACL_RULE_EXT1(g->list), g->ext1, + set ? g->ext1 : 0); + regmap_update_bits(priv->regmap, PPE_ACL_RULE_EXT2(g->list), g->ext2, + set ? g->ext2 : 0); + regmap_update_bits(priv->regmap, PPE_ACL_RULE_EXT4(g->list), g->ext4, + set ? g->ext4 : 0); +} + +/* Hand one entry of the shape to a slice. A range compare has to have an even + * entry; everything else takes an odd one first, which leaves the even ones to + * the ranges of the same rule - the shape was picked with enough of them, and + * with exactly one entry per slice, so there is always something to hand out. + */ +static u8 ppe_acl_entry_take(u8 *avail, bool range) +{ + u8 want = range ? PPE_ACL_EVEN_ENTRIES : ~PPE_ACL_EVEN_ENTRIES; + u8 pick = *avail & want ? *avail & want : *avail; + + pick &= -pick; + *avail &= ~pick; + + return __ffs(pick); +} + +/* Place a rule on the first list carrying a free shape of the right width with + * an even entry left for each of the rule's range compares. + */ +static int ppe_acl_alloc(struct qca_ppe_priv *priv, + const struct ppe_acl_slice *slice, int nslices, + struct ppe_acl_group *g) +{ + int i, l, n, nranges = 0; + u8 avail; + + for (i = 0; i < nslices; i++) + nranges += slice[i].range; + + for (l = 0; l < PPE_ACL_LISTS; l++) { + for (i = 0; i < ARRAY_SIZE(ppe_acl_shape); i++) { + u8 e = ppe_acl_shape[i].entries; + + if (ppe_acl_shape[i].num != nslices || + (priv->acl_free[l] & e) != e || + hweight8(e & PPE_ACL_EVEN_ENTRIES) < nranges) + continue; + + priv->acl_free[l] &= ~e; + g->list = l; + g->entries = e; + g->ext1 = ppe_acl_shape[i].ext1; + g->ext2 = ppe_acl_shape[i].ext2; + g->ext4 = ppe_acl_shape[i].ext4; + g->nslices = nslices; + + avail = e; + for (n = 0; n < nslices; n++) + g->index[n] = l * PPE_ACL_LIST_ENTRIES + + ppe_acl_entry_take(&avail, + slice[n].range); + + /* The chain has to exist before any of its entries + * goes live: an armed entry whose RULE_EXT bit is + * still clear is a rule of its own, matching far more + * than what was asked for and carrying the action. + */ + ppe_acl_ext_write(priv, g, true); + + return 0; + } + } + + return -ENOSPC; +} + +/* Zeroing the rule words clears the source bitmap, which is the only thing + * that makes an entry live; the chain goes last for the same reason it was + * built first. + */ +static void ppe_acl_free(struct qca_ppe_priv *priv, struct ppe_acl_group *g) +{ + int i, j; + + for (i = 0; i < g->nslices; i++) + for (j = 0; j < PPE_ACL_RULE_WORDS; j++) + regmap_write(priv->regmap, + PPE_ACL_RULE(g->index[i]) + j * 4, 0); + + ppe_acl_ext_write(priv, g, false); + priv->acl_free[g->list] |= g->entries; + g->nslices = 0; +} + +/* Arm one entry. The engine commits an entry on the write to its last word, so + * the action and the mask are in place before the rule word that carries the + * source bitmap lands, and the rule is disarmed first in case it was live. + */ +static void ppe_acl_slice_write(struct qca_ppe_priv *priv, u32 index, + const struct ppe_acl_slice *s, const u32 *act, + u32 ports, u16 pri) +{ + u32 w[PPE_ACL_ACTION_WORDS]; + int i; + + for (i = 0; i < PPE_ACL_RULE_WORDS; i++) + regmap_write(priv->regmap, PPE_ACL_RULE(index) + i * 4, 0); + + for (i = 0; i < PPE_ACL_ACTION_WORDS; i++) + regmap_write(priv->regmap, PPE_ACL_ACTION(index) + i * 4, + act[i]); + + memset(w, 0, sizeof(w)); + w[0] = s->mask[0]; + w[1] = s->mask[1]; + for (i = 0; i < PPE_ACL_MASK_WORDS; i++) + regmap_write(priv->regmap, PPE_ACL_MASK(index) + i * 4, w[i]); + + memset(w, 0, sizeof(w)); + w[0] = s->key[0]; + w[1] = s->key[1] | + FIELD_PREP(PPE_ACL_RULE_TYPE, s->type) | + (s->range ? PPE_ACL_RANGE_EN : 0) | + FIELD_PREP(PPE_ACL_SRC_TYPE, PPE_ACL_SRC_PORT_BMP) | + FIELD_PREP(PPE_ACL_SRC_LO, ports & 0x7); + w[2] = FIELD_PREP(PPE_ACL_SRC_HI, ports >> 3) | + FIELD_PREP(PPE_ACL_RULE_PRI, pri); + for (i = 0; i < PPE_ACL_RULE_WORDS; i++) + regmap_write(priv->regmap, PPE_ACL_RULE(index) + i * 4, w[i]); +} + +/* The keys the rule types below cover. A filter carrying anything else is + * declined rather than offloaded without it: a key the driver drops makes the + * rule match a superset of what was asked for, and the action goes with it. + */ +#define PPE_ACL_MATCH_KEYS \ + (BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL) | \ + BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) | \ + BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS) | \ + BIT_ULL(FLOW_DISSECTOR_KEY_IPV4_ADDRS) | \ + BIT_ULL(FLOW_DISSECTOR_KEY_IPV6_ADDRS) | \ + BIT_ULL(FLOW_DISSECTOR_KEY_PORTS) | \ + BIT_ULL(FLOW_DISSECTOR_KEY_PORTS_RANGE) | \ + BIT_ULL(FLOW_DISSECTOR_KEY_ICMP) | \ + BIT_ULL(FLOW_DISSECTOR_KEY_IP) | \ + BIT_ULL(FLOW_DISSECTOR_KEY_TCP)) + +/* TCP flags as the header carries them, FIN first. */ +#define PPE_ACL_TCP_FLAGS_MAX GENMASK(5, 0) + +/* A filter needs at most one entry per rule type, which is more than one + * hardware list holds; the width is checked once the whole filter is parsed. + */ +#define PPE_ACL_MAX_SLICES (PPE_ACL_TYPE_IPMISC + 1) + +struct ppe_acl_rule { + struct list_head list; + unsigned long cookie; + /* The ethtool location this rule was inserted at, or -1 for one that + * came from tc. ethtool has to hand the whole spec back on a get, so + * the rules it owns keep theirs. + */ + int loc; + struct ethtool_rx_flow_spec *fs; + int port; + int meter; + bool mirror; + struct ppe_acl_group group; + u32 act[PPE_ACL_ACTION_WORDS]; +}; + +/* One entry per rule type, reused as more of the filter is parsed into it. */ +static struct ppe_acl_slice *ppe_acl_slice_get(struct ppe_acl_slice *slice, + int *n, u8 type) +{ + int i; + + for (i = 0; i < *n; i++) + if (slice[i].type == type) + return &slice[i]; + + slice[*n].type = type; + + return &slice[(*n)++]; +} + +/* A MAC address sits low byte first: bytes 5 to 2 in word 0, bytes 1 and 0 in + * word 1. + */ +static void ppe_acl_mac_words(u32 *w, const u8 *mac) +{ + w[0] = mac[5] | mac[4] << 8 | mac[3] << 16 | mac[2] << 24; + w[1] = FIELD_PREP(PPE_ACL_MAC_HI, mac[1] | mac[0] << 8); +} + +/* An IPv6 address needs three entries: its low 32 bits and the 16 above them + * in the first, the next 48 in the second, the top 32 in the third - which + * leaves the third entry's port field free for the L4 port. + */ +static void ppe_acl_ip6_words(u32 w[3][2], const __be32 *addr) +{ + u32 v[4]; + int i; + + for (i = 0; i < 4; i++) + v[i] = ntohl(addr[i]); + + w[0][0] = FIELD_PREP(PPE_ACL_IP_PORT, v[3]) | + FIELD_PREP(PPE_ACL_IP_LO, v[3] >> 16); + w[0][1] = FIELD_PREP(PPE_ACL_IP_HI, v[2]); + w[1][0] = FIELD_PREP(PPE_ACL_IP_PORT, v[2] >> 16) | + FIELD_PREP(PPE_ACL_IP_LO, v[1]); + w[1][1] = FIELD_PREP(PPE_ACL_IP_HI, v[1] >> 16); + w[2][0] = FIELD_PREP(PPE_ACL_IP_LO, v[0]); + w[2][1] = FIELD_PREP(PPE_ACL_IP_HI, v[0] >> 16); +} + +static void ppe_acl_key_ip6(struct ppe_acl_slice *slice, int *n, u8 type, + const struct in6_addr *key, + const struct in6_addr *mask) +{ + u32 kw[3][2], mw[3][2]; + int i; + + ppe_acl_ip6_words(kw, key->s6_addr32); + ppe_acl_ip6_words(mw, mask->s6_addr32); + + for (i = 0; i < 3; i++) { + struct ppe_acl_slice *s; + + /* A prefix shorter than the address leaves whole entries with + * nothing to compare, and an entry that compares nothing still + * costs one of the list's eight. The vendor gates each rule + * type on its own half of the mask; so does this. + */ + if (!mw[i][0] && !mw[i][1]) + continue; + + s = ppe_acl_slice_get(slice, n, type + i); + s->key[0] |= kw[i][0]; + s->key[1] |= kw[i][1]; + s->mask[0] |= mw[i][0]; + s->mask[1] |= mw[i][1]; + } +} + +/* Turn the filter into entries: one per rule type it needs, each carrying the + * part of the key that rule type compares. Returns how many. + */ +static int ppe_acl_parse_key(struct flow_rule *rule, + struct netlink_ext_ack *extack, + struct ppe_acl_slice *slice, __be16 *family) +{ + struct ppe_acl_slice *s; + u8 sip_type, dip_type; + u16 addr_type = 0; + __be16 proto = 0; + int n = 0; + + if (rule->match.dissector->used_keys & ~PPE_ACL_MATCH_KEYS) { + NL_SET_ERR_MSG_MOD(extack, "match key the classifier has no field for"); + return -EOPNOTSUPP; + } + + if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) { + struct flow_match_control match; + + flow_rule_match_control(rule, &match); + addr_type = match.key->addr_type; + if (!flow_rule_is_supp_control_flags(FLOW_DIS_IS_FRAGMENT, + match.mask->flags, extack)) + return -EOPNOTSUPP; + + if (match.mask->flags & FLOW_DIS_IS_FRAGMENT) { + s = ppe_acl_slice_get(slice, &n, PPE_ACL_TYPE_IPMISC); + if (match.key->flags & FLOW_DIS_IS_FRAGMENT) + s->key[1] |= PPE_ACL_L3_FRAG; + s->mask[1] |= PPE_ACL_L3_FRAG; + } + } else if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV4_ADDRS)) { + /* ethtool registers the address key without the control key */ + addr_type = FLOW_DISSECTOR_KEY_IPV4_ADDRS; + } else if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV6_ADDRS)) { + addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS; + } + + /* The engine tells the families apart by one bit rather than by the + * ethertype, which is also what picks the IPv4 or IPv6 rule types for + * everything below, so an L3 or L4 match needs the family named. + */ + if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) { + struct flow_match_basic match; + + flow_rule_match_basic(rule, &match); + if (match.mask->n_proto) { + if (match.mask->n_proto != htons(0xffff)) { + NL_SET_ERR_MSG_MOD(extack, "the protocol is matched whole or not at all"); + return -EOPNOTSUPP; + } + proto = match.key->n_proto; + if (proto != htons(ETH_P_IP) && + proto != htons(ETH_P_IPV6)) { + NL_SET_ERR_MSG_MOD(extack, "only IPv4 and IPv6 are matched by protocol"); + return -EOPNOTSUPP; + } + s = ppe_acl_slice_get(slice, &n, PPE_ACL_TYPE_IPMISC); + if (proto == htons(ETH_P_IPV6)) + s->key[1] |= PPE_ACL_IS_IPV6; + s->mask[1] |= PPE_ACL_IS_IPV6; + } + if (match.mask->ip_proto) { + s = ppe_acl_slice_get(slice, &n, PPE_ACL_TYPE_IPMISC); + s->key[0] |= FIELD_PREP(PPE_ACL_L3_PROT, + match.key->ip_proto); + s->mask[0] |= FIELD_PREP(PPE_ACL_L3_PROT, + match.mask->ip_proto); + } + } + + if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IP)) { + struct flow_match_ip match; + + flow_rule_match_ip(rule, &match); + if (match.mask->ttl) { + NL_SET_ERR_MSG_MOD(extack, "the TTL field is two encoded bits, not a value"); + return -EOPNOTSUPP; + } + if (match.mask->tos) { + s = ppe_acl_slice_get(slice, &n, PPE_ACL_TYPE_IPMISC); + s->key[0] |= FIELD_PREP(PPE_ACL_L3_DSCP, + match.key->tos); + s->mask[0] |= FIELD_PREP(PPE_ACL_L3_DSCP, + match.mask->tos); + } + } + + if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_TCP)) { + struct flow_match_tcp match; + u16 flags, mask; + + flow_rule_match_tcp(rule, &match); + flags = ntohs(match.key->flags); + mask = ntohs(match.mask->flags); + if (mask & ~PPE_ACL_TCP_FLAGS_MAX) { + NL_SET_ERR_MSG_MOD(extack, "the classifier stops at the URG flag"); + return -EOPNOTSUPP; + } + if (mask) { + s = ppe_acl_slice_get(slice, &n, PPE_ACL_TYPE_IPMISC); + s->key[1] |= FIELD_PREP(PPE_ACL_TCP_FLAGS, flags); + s->mask[1] |= FIELD_PREP(PPE_ACL_TCP_FLAGS, mask); + } + } + + if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) { + struct flow_match_eth_addrs match; + u32 kw[2], mw[2]; + + flow_rule_match_eth_addrs(rule, &match); + if (!is_zero_ether_addr(match.mask->dst)) { + s = ppe_acl_slice_get(slice, &n, PPE_ACL_TYPE_MAC_DA); + ppe_acl_mac_words(kw, match.key->dst); + ppe_acl_mac_words(mw, match.mask->dst); + s->key[0] |= kw[0]; + s->key[1] |= kw[1]; + s->mask[0] |= mw[0]; + s->mask[1] |= mw[1]; + } + if (!is_zero_ether_addr(match.mask->src)) { + s = ppe_acl_slice_get(slice, &n, PPE_ACL_TYPE_MAC_SA); + ppe_acl_mac_words(kw, match.key->src); + ppe_acl_mac_words(mw, match.mask->src); + s->key[0] |= kw[0]; + s->key[1] |= kw[1]; + s->mask[0] |= mw[0]; + s->mask[1] |= mw[1]; + } + } + + if (proto == htons(ETH_P_IPV6)) { + sip_type = PPE_ACL_TYPE_IPV6_SIP0 + 2; + dip_type = PPE_ACL_TYPE_IPV6_DIP0 + 2; + } else { + sip_type = PPE_ACL_TYPE_IPV4_SIP; + dip_type = PPE_ACL_TYPE_IPV4_DIP; + } + + /* Which of the two address keys carries the filter's addresses is the + * control key's to say. A dissector that offers both answers for the + * one it does not hold out of the memory the other occupies, and the + * entries that come back match no frame of either family. + */ + if (addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS && + flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV4_ADDRS)) { + struct flow_match_ipv4_addrs match; + + flow_rule_match_ipv4_addrs(rule, &match); + if (match.mask->src) { + s = ppe_acl_slice_get(slice, &n, PPE_ACL_TYPE_IPV4_SIP); + s->key[0] |= FIELD_PREP(PPE_ACL_IP_LO, + ntohl(match.key->src)); + s->key[1] |= FIELD_PREP(PPE_ACL_IP_HI, + ntohl(match.key->src) >> 16); + s->mask[0] |= FIELD_PREP(PPE_ACL_IP_LO, + ntohl(match.mask->src)); + s->mask[1] |= FIELD_PREP(PPE_ACL_IP_HI, + ntohl(match.mask->src) >> 16); + } + if (match.mask->dst) { + s = ppe_acl_slice_get(slice, &n, PPE_ACL_TYPE_IPV4_DIP); + s->key[0] |= FIELD_PREP(PPE_ACL_IP_LO, + ntohl(match.key->dst)); + s->key[1] |= FIELD_PREP(PPE_ACL_IP_HI, + ntohl(match.key->dst) >> 16); + s->mask[0] |= FIELD_PREP(PPE_ACL_IP_LO, + ntohl(match.mask->dst)); + s->mask[1] |= FIELD_PREP(PPE_ACL_IP_HI, + ntohl(match.mask->dst) >> 16); + } + } + + if (addr_type == FLOW_DISSECTOR_KEY_IPV6_ADDRS && + flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV6_ADDRS)) { + struct flow_match_ipv6_addrs match; + + flow_rule_match_ipv6_addrs(rule, &match); + if (!ipv6_addr_any(&match.mask->src)) + ppe_acl_key_ip6(slice, &n, PPE_ACL_TYPE_IPV6_SIP0, + &match.key->src, &match.mask->src); + if (!ipv6_addr_any(&match.mask->dst)) + ppe_acl_key_ip6(slice, &n, PPE_ACL_TYPE_IPV6_DIP0, + &match.key->dst, &match.mask->dst); + } + + /* The port and the ICMP type/code share one field of the address + * entry, which is why a filter may not ask for both. + */ + if ((flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS) || + flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS_RANGE)) && + flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ICMP)) { + NL_SET_ERR_MSG_MOD(extack, "ports and ICMP share one field"); + return -EOPNOTSUPP; + } + + if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) { + struct flow_match_ports match; + + if (!proto) { + NL_SET_ERR_MSG_MOD(extack, "a port match needs the IP version named"); + return -EOPNOTSUPP; + } + flow_rule_match_ports(rule, &match); + if (match.mask->src) { + s = ppe_acl_slice_get(slice, &n, sip_type); + s->key[0] |= FIELD_PREP(PPE_ACL_IP_PORT, + ntohs(match.key->src)); + s->mask[0] |= FIELD_PREP(PPE_ACL_IP_PORT, + ntohs(match.mask->src)); + } + if (match.mask->dst) { + s = ppe_acl_slice_get(slice, &n, dip_type); + s->key[0] |= FIELD_PREP(PPE_ACL_IP_PORT, + ntohs(match.key->dst)); + s->mask[0] |= FIELD_PREP(PPE_ACL_IP_PORT, + ntohs(match.mask->dst)); + } + } + + /* A range compare turns the same field's mask slot into the maximum, + * so the entry carrying it has no mask left for the port and cannot + * also match one exactly. The address the entry carries keeps its own + * mask - only the field the range names is repurposed - and the + * allocator gives a ranged entry an even slot. + */ + if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS_RANGE)) { + struct flow_match_ports_range match; + + if (!proto) { + NL_SET_ERR_MSG_MOD(extack, "a port match needs the IP version named"); + return -EOPNOTSUPP; + } + flow_rule_match_ports_range(rule, &match); + if (match.mask->tp_min.src) { + s = ppe_acl_slice_get(slice, &n, sip_type); + if (s->mask[0] & PPE_ACL_IP_PORT) + goto both; + s->range = true; + s->key[0] |= FIELD_PREP(PPE_ACL_IP_PORT, + ntohs(match.key->tp_min.src)); + s->mask[0] |= FIELD_PREP(PPE_ACL_IP_PORT, + ntohs(match.key->tp_max.src)); + } + if (match.mask->tp_min.dst) { + s = ppe_acl_slice_get(slice, &n, dip_type); + if (s->mask[0] & PPE_ACL_IP_PORT) + goto both; + s->range = true; + s->key[0] |= FIELD_PREP(PPE_ACL_IP_PORT, + ntohs(match.key->tp_min.dst)); + s->mask[0] |= FIELD_PREP(PPE_ACL_IP_PORT, + ntohs(match.key->tp_max.dst)); + } + } + + if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ICMP)) { + struct flow_match_icmp match; + + if (!proto) { + NL_SET_ERR_MSG_MOD(extack, "an ICMP match needs the IP version named"); + return -EOPNOTSUPP; + } + flow_rule_match_icmp(rule, &match); + if (match.mask->type || match.mask->code) { + s = ppe_acl_slice_get(slice, &n, dip_type); + s->key[0] |= FIELD_PREP(PPE_ACL_IP_PORT, + match.key->type << 8 | + match.key->code); + s->mask[0] |= FIELD_PREP(PPE_ACL_IP_PORT, + match.mask->type << 8 | + match.mask->code); + } + } + + *family = proto; + + return n; + +both: + NL_SET_ERR_MSG_MOD(extack, "a port is matched exactly or as a range, not both"); + + return -EOPNOTSUPP; +} + +/* One of the 512 ingress meters. The rate maths is the port policer's - same + * block, same refresh period - and metering mode 1 is RFC 2697: one rate, one + * bucket, everything the committed burst cannot hold is red. The two words + * past the rate leave the excess bucket empty and the violate command at zero, + * which is drop, so red means "over the rate" and is dropped. + */ +static int ppe_acl_meter_set(struct qca_ppe_priv *priv, u32 index, + u64 rate_bps, u32 burst) +{ + u32 cir = 0, cbs = 0; + unsigned long clk; + int sel = 0; + + if (rate_bps) { + clk = ppe_clk_rate(priv); + if (!clk) + return -ENODEV; + + sel = ppe_token_bucket(clk, PPE_POLICER_SLOT, rate_bps, burst, + FIELD_MAX(PPE_ACL_METER_CIR_HI) << 8 | + FIELD_MAX(PPE_ACL_METER_CIR_LO), + FIELD_MAX(PPE_ACL_METER_CBS), + &cir, &cbs); + if (sel < 0) + return sel; + } + + /* Four words, latching on the last, like every other PPE table. */ + regmap_write(priv->regmap, PPE_ACL_METER(index), + (rate_bps ? PPE_ACL_METER_EN : 0) | + PPE_ACL_METER_MODE | + FIELD_PREP(PPE_ACL_METER_TOKEN_UNIT, sel) | + FIELD_PREP(PPE_ACL_METER_CBS, cbs) | + FIELD_PREP(PPE_ACL_METER_CIR_LO, cir)); + regmap_write(priv->regmap, PPE_ACL_METER(index) + 0x4, + FIELD_PREP(PPE_ACL_METER_CIR_HI, cir >> 8)); + regmap_write(priv->regmap, PPE_ACL_METER(index) + 0x8, 0); + regmap_write(priv->regmap, PPE_ACL_METER(index) + 0xc, 0); + + /* A meter index outlives the rule that held it, so the bucket goes back + * with it: tokens the old rule bought at its own rate would otherwise + * be the first thing the next rule to take this index spends. + */ + if (!rate_bps) { + regmap_write(priv->regmap, PPE_ACL_METER_CRDT(index), 0); + regmap_write(priv->regmap, PPE_ACL_METER_CRDT(index) + 0x4, 0); + } + + return 0; +} + +/* The resources a rule holds outside its own entries. */ +static void ppe_acl_rule_free(struct qca_ppe_priv *priv, + struct ppe_acl_rule *r) +{ + if (r->mirror) + ppe_mirror_analyzer_put(priv); + if (r->meter >= 0) { + ppe_acl_meter_set(priv, r->meter, 0, 0); + clear_bit(r->meter, priv->acl_meter_used); + } + kfree(r->fs); + kfree(r); +} + +static int ppe_acl_parse_action(struct qca_ppe_priv *priv, + struct flow_rule *rule, + struct netlink_ext_ack *extack, __be16 family, + struct ppe_acl_rule *r) +{ + const struct flow_action_entry *a; + u32 *act = r->act; + bool fwd = false; + int i, ret; + + flow_action_for_each(i, a, &rule->action) { + switch (a->id) { + case FLOW_ACTION_ACCEPT: + case FLOW_ACTION_DROP: + case FLOW_ACTION_TRAP: + if (fwd) { + NL_SET_ERR_MSG_MOD(extack, "one forward command per rule"); + return -EOPNOTSUPP; + } + fwd = true; + act[0] |= PPE_ACL_DEST_CHANGE_EN | + FIELD_PREP(PPE_ACL_FWD_CMD, + a->id == FLOW_ACTION_DROP ? + PPE_ACL_FWD_DROP : + a->id == FLOW_ACTION_TRAP ? + PPE_ACL_FWD_RDT_CPU : + PPE_ACL_FWD_FORWARD); + break; + case FLOW_ACTION_REDIRECT: { + struct dsa_port *to = dsa_port_from_netdev(a->dev); + + if (IS_ERR(to) || to->ds != &priv->ds) { + NL_SET_ERR_MSG_MOD(extack, "the destination has to name a port of this switch"); + return -EOPNOTSUPP; + } + if (fwd) { + NL_SET_ERR_MSG_MOD(extack, "one forward command per rule"); + return -EOPNOTSUPP; + } + fwd = true; + /* The forward command stays at pass: what changes is + * the destination it passes the frame to. + */ + act[0] |= PPE_ACL_DEST_CHANGE_EN | + FIELD_PREP(PPE_ACL_DEST_TYPE, + PPE_ACL_DEST_PORT_BMP) | + FIELD_PREP(PPE_ACL_DEST_VALUE, + BIT(to->index)); + break; + } + case FLOW_ACTION_PRIORITY: + if (a->priority > FIELD_MAX(PPE_ACL_PRI)) { + NL_SET_ERR_MSG_MOD(extack, "internal priority is four bits"); + return -EOPNOTSUPP; + } + act[3] |= PPE_ACL_PRI_CHANGE_EN | + FIELD_PREP(PPE_ACL_PRI, a->priority); + break; + case FLOW_ACTION_QUEUE: + /* The index is a hardware queue of the whole switch: + * which port it drains is the queue's business, not + * the classifier's. + */ + if (a->queue.vf) { + NL_SET_ERR_MSG_MOD(extack, "no VF to steer to"); + return -EOPNOTSUPP; + } + if (a->queue.index > FIELD_MAX(PPE_ACL_QID) || + (a->queue.index >= PPE_CPU_UCAST_QUEUES && + a->queue.index < PPE_PORT_UCAST_BASE)) { + NL_SET_ERR_MSG_MOD(extack, "no such hardware queue"); + return -EOPNOTSUPP; + } + act[3] |= PPE_ACL_QID_EN | + FIELD_PREP(PPE_ACL_QID, a->queue.index); + break; + case FLOW_ACTION_MANGLE: { + enum flow_action_mangle_base htype = a->mangle.htype; + u32 mask = ntohl(a->mangle.mask); + u32 val = ntohl(a->mangle.val); + int shift; + + /* Where the byte sits in the header's first word, + * and which family that header is: pedit resolves + * IPv4, IPv6 and unspecified alike to the network + * header, so only the filter's own protocol tells the + * two apart, and the engine rewrites per family. + */ + if (family == htons(ETH_P_IP) && + htype == FLOW_ACT_MANGLE_HDR_TYPE_IP4) { + shift = 16; + } else if (family == htons(ETH_P_IPV6) && + htype == FLOW_ACT_MANGLE_HDR_TYPE_IP6) { + shift = 20; + } else { + NL_SET_ERR_MSG_MOD(extack, "only the traffic class of the IP version the filter names is rewritten"); + return -EOPNOTSUPP; + } + /* The field has no mask on this generation, so the + * rewrite has to be the whole byte and nothing else - + * and what the kernel keeps of the word is what the + * value is xored into, so it may not reach outside. + */ + if (a->mangle.offset || ~mask != (u32)0xff << shift || + val & mask) { + NL_SET_ERR_MSG_MOD(extack, "the traffic class is rewritten whole or not at all"); + return -EOPNOTSUPP; + } + act[2] |= PPE_ACL_DSCP_TC_CHANGE_EN | + FIELD_PREP(PPE_ACL_DSCP_TC, val >> shift); + break; + } + case FLOW_ACTION_POLICE: { + unsigned long index; + + /* One byte rate whose excess is dropped: the meter's + * second bucket only re-marks a frame past the + * committed rate, where a peak rate drops it, the + * meter counts bytes rather than packets, compensates + * frame length by the block's own constant rather than + * by a per frame overhead, and the colour a lesser + * exceed action would set is read by nothing here. + */ + if (!a->police.rate_bytes_ps || + a->police.peakrate_bytes_ps || a->police.avrate || + a->police.rate_pkt_ps || a->police.overhead || + a->police.exceed.act_id != FLOW_ACTION_DROP || + (a->police.notexceed.act_id != FLOW_ACTION_ACCEPT && + a->police.notexceed.act_id != FLOW_ACTION_PIPE)) { + NL_SET_ERR_MSG_MOD(extack, "the meter is one byte rate and drops what exceeds it"); + return -EOPNOTSUPP; + } + /* Accepting what conforms ends the filter, so a later + * action would run in the hardware and nowhere else. + */ + if (a->police.notexceed.act_id == FLOW_ACTION_ACCEPT && + !flow_action_is_last_entry(&rule->action, a)) { + NL_SET_ERR_MSG_MOD(extack, "nothing may follow an action that accepts what conforms"); + return -EOPNOTSUPP; + } + if (r->meter >= 0) { + NL_SET_ERR_MSG_MOD(extack, "one meter per rule"); + return -EOPNOTSUPP; + } + index = find_first_zero_bit(priv->acl_meter_used, + PPE_ACL_METER_ENTRIES); + if (index >= PPE_ACL_METER_ENTRIES) { + NL_SET_ERR_MSG_MOD(extack, "every meter is taken"); + return -ENOSPC; + } + ret = ppe_acl_meter_set(priv, index, + a->police.rate_bytes_ps * + BITS_PER_BYTE, + a->police.burst); + if (ret) { + NL_SET_ERR_MSG_MOD(extack, "the rate and burst are outside the meter's range"); + return ret; + } + set_bit(index, priv->acl_meter_used); + r->meter = index; + act[3] |= PPE_ACL_POLICER_EN | + FIELD_PREP(PPE_ACL_POLICER_INDEX, index); + break; + } + case FLOW_ACTION_MIRRED: { + struct dsa_port *to = dsa_port_from_netdev(a->dev); + + if (IS_ERR(to) || to->ds != &priv->ds) { + NL_SET_ERR_MSG_MOD(extack, "the mirror has to name a port of this switch"); + return -EOPNOTSUPP; + } + if (r->mirror) { + NL_SET_ERR_MSG_MOD(extack, "one mirror per rule"); + return -EOPNOTSUPP; + } + ret = ppe_mirror_analyzer_get(priv, to->index); + if (ret) { + NL_SET_ERR_MSG_MOD(extack, "another port is already mirrored elsewhere"); + return ret; + } + r->mirror = true; + act[0] |= PPE_ACL_MIRROR_EN; + break; + } + default: + NL_SET_ERR_MSG_MOD(extack, "action the classifier cannot take"); + return -EOPNOTSUPP; + } + } + + return 0; +} + +static struct ppe_acl_rule *ppe_acl_rule_find(struct qca_ppe_priv *priv, + unsigned long cookie, int port) +{ + struct ppe_acl_rule *r; + + list_for_each_entry(r, &priv->acl_rules, list) + if (r->cookie == cookie && r->port == port) + return r; + + return NULL; +} + +/* Place one parsed rule in the engine. Both uAPIs land here: the preference + * is tc's for a filter and the location for an ethtool entry, and in each the + * lower number is the stronger rule, which the engine expresses as the higher + * priority. + */ +static int ppe_acl_rule_add(struct qca_ppe_priv *priv, int port, + struct flow_rule *rule, unsigned long cookie, + int loc, const struct ethtool_rx_flow_spec *fs, + u16 prio, struct netlink_ext_ack *extack) +{ + struct ppe_acl_slice slice[PPE_ACL_MAX_SLICES] = {}; + struct ppe_acl_rule *r; + int nslices, ret, i; + __be16 family; + u16 pri; + + nslices = ppe_acl_parse_key(rule, extack, slice, &family); + if (nslices < 0) + return nslices; + if (!nslices) { + NL_SET_ERR_MSG_MOD(extack, "a rule with no key would match every frame"); + return -EOPNOTSUPP; + } + if (nslices > PPE_ACL_LIST_ENTRIES) { + NL_SET_ERR_MSG_MOD(extack, "the key needs more entries than one list holds"); + return -EOPNOTSUPP; + } + + r = kzalloc(sizeof(*r), GFP_KERNEL); + if (!r) + return -ENOMEM; + r->meter = -1; + r->loc = loc; + if (fs) { + r->fs = kmemdup(fs, sizeof(*fs), GFP_KERNEL); + if (!r->fs) { + kfree(r); + return -ENOMEM; + } + } + + /* The meter index comes out of the same lock the entries do: the + * small-packet parameters reach the table without rtnl, and so does + * ethtool's n-tuple insert. + */ + mutex_lock(&priv->acl_lock); + ret = ppe_acl_parse_action(priv, rule, extack, family, r); + if (ret) + goto err; + + ret = ppe_acl_alloc(priv, slice, nslices, &r->group); + if (ret) { + NL_SET_ERR_MSG_MOD(extack, "no room left in the classifier"); + goto err; + } + + /* Every entry of one rule carries the rule's priority. */ + pri = FIELD_MAX(PPE_ACL_RULE_PRI) - prio; + for (i = 0; i < nslices; i++) + ppe_acl_slice_write(priv, r->group.index[i], &slice[i], r->act, + BIT(port), pri); + + r->cookie = cookie; + r->port = port; + list_add_tail(&r->list, &priv->acl_rules); + mutex_unlock(&priv->acl_lock); + + return 0; + +err: + ppe_acl_rule_free(priv, r); + mutex_unlock(&priv->acl_lock); + + return ret; +} + +int qca_ppe_cls_flower_add(struct dsa_switch *ds, int port, + struct flow_cls_offload *cls, bool ingress) +{ + struct flow_rule *rule = flow_cls_offload_flow_rule(cls); + struct netlink_ext_ack *extack = cls->common.extack; + struct qca_ppe_priv *priv = ds_to_priv(ds); + + if (!ingress) { + NL_SET_ERR_MSG_MOD(extack, "the classifier only sees ingress"); + return -EOPNOTSUPP; + } + if (cls->common.chain_index) { + NL_SET_ERR_MSG_MOD(extack, "only chain 0 reaches the classifier"); + return -EOPNOTSUPP; + } + /* The engine matches the highest priority it holds, where tc gives + * precedence to the lowest preference, so the rule's standing is the + * field's span less the preference. Nine bits carry it, and a + * preference past that is refused rather than tied to another rule's: + * the filter tc reports as offloaded has to be the one that wins. + */ + if (cls->common.prio > FIELD_MAX(PPE_ACL_RULE_PRI)) { + NL_SET_ERR_MSG_MOD(extack, "filter preference is above what the classifier can order; use one below 512"); + return -EOPNOTSUPP; + } + + return ppe_acl_rule_add(priv, port, rule, cls->cookie, -1, NULL, + cls->common.prio, extack); +} + +int qca_ppe_cls_flower_del(struct dsa_switch *ds, int port, + struct flow_cls_offload *cls, bool ingress) +{ + struct qca_ppe_priv *priv = ds_to_priv(ds); + struct ppe_acl_rule *r; + + mutex_lock(&priv->acl_lock); + r = ppe_acl_rule_find(priv, cls->cookie, port); + if (r) { + ppe_acl_free(priv, &r->group); + list_del(&r->list); + ppe_acl_rule_free(priv, r); + } + mutex_unlock(&priv->acl_lock); + + return 0; +} + +/* Small packets jump the bulk queue. One rule per IP family classifies every + * frame whose L3 length is at most small_pkt_len into internal priority + * small_pkt_prio, whose queue the scheduler serves past a standing bulk queue + * on a shaped port. Length is the one property the silicon can read that + * separates acks, handshakes, DNS, VoIP and game traffic from full-size bulk + * without any marking on the packet; the deep queue a shaper wants for + * throughput then costs its latency only to the bulk traffic inside it. + * The default takes the 200-byte G.711 voice frame, the largest of the + * common voice codecs on the wire, with room for its tunnelled forms; + * every bulk protocol's data rides at the MTU. + * + * The priority is capped at 7: the slots above sit on the port's second SP + * beside the multicast queues. + */ +static ushort ppe_small_pkt_len = 256; +static ushort ppe_small_pkt_prio = 5; + +/* One PPE per SoC; the parameter store needs the instance back. */ +static struct qca_ppe_priv *ppe_acl_priv; + +static struct ppe_acl_group ppe_small_pkt_group[2]; + +/* ethtool's n-tuple table is the second way into the same engine, and the only + * one that reaches the queue action: its ring cookie names a queue of the whole + * switch, where tc can ask for a priority class and leave the hash to pick + * which of that class's queues the flow lands in. + */ +static struct ppe_acl_rule *ppe_acl_rule_at(struct qca_ppe_priv *priv, + int port, u32 loc) +{ + struct ppe_acl_rule *r; + + lockdep_assert_held(&priv->acl_lock); + + /* Only a rule ethtool inserted has a location. Without the sign test + * the -1 of a tc rule promotes to 0xffffffff and a delete at that + * location takes the tc rule's hardware entry away under it. + */ + list_for_each_entry(r, &priv->acl_rules, list) + if (r->port == port && r->loc >= 0 && r->loc == loc) + return r; + + return NULL; +} + +static int ppe_acl_rxnfc_ins(struct qca_ppe_priv *priv, int port, + struct ethtool_rx_flow_spec *fs) +{ + struct ethtool_rx_flow_spec_input input = { .fs = fs }; + struct ethtool_rx_flow_rule *flow; + bool taken; + int ret; + + /* The location names the rule and orders it, so it shares the span the + * priority field has. + */ + if (fs->location > FIELD_MAX(PPE_ACL_RULE_PRI)) + return -EINVAL; + /* No RSS context exists for the queue to be taken relative to. */ + if (fs->flow_type & FLOW_RSS) + return -EINVAL; + + mutex_lock(&priv->acl_lock); + taken = ppe_acl_rule_at(priv, port, fs->location); + mutex_unlock(&priv->acl_lock); + if (taken) + return -EEXIST; + + flow = ethtool_rx_flow_rule_create(&input); + if (IS_ERR(flow)) + return PTR_ERR(flow); + + ret = ppe_acl_rule_add(priv, port, flow->rule, 0, fs->location, fs, + fs->location, NULL); + ethtool_rx_flow_rule_destroy(flow); + + return ret; +} + +int qca_ppe_set_rxnfc(struct dsa_switch *ds, int port, + struct ethtool_rxnfc *nfc) +{ + struct qca_ppe_priv *priv = ds_to_priv(ds); + struct ppe_acl_rule *r; + + if (nfc->cmd == ETHTOOL_SRXCLSRLINS) + return ppe_acl_rxnfc_ins(priv, port, &nfc->fs); + if (nfc->cmd != ETHTOOL_SRXCLSRLDEL) + return -EOPNOTSUPP; + + guard(mutex)(&priv->acl_lock); + + r = ppe_acl_rule_at(priv, port, nfc->fs.location); + if (!r) + return -ENOENT; + + ppe_acl_free(priv, &r->group); + list_del(&r->list); + ppe_acl_rule_free(priv, r); + + return 0; +} + +int qca_ppe_get_rxnfc(struct dsa_switch *ds, int port, + struct ethtool_rxnfc *nfc, u32 *rule_locs) +{ + struct qca_ppe_priv *priv = ds_to_priv(ds); + struct ppe_acl_rule *r; + u32 cnt = 0; + + guard(mutex)(&priv->acl_lock); + + switch (nfc->cmd) { + case ETHTOOL_GRXRINGS: + /* What the action may name: a queue of the whole switch, not a + * receive ring of this port. + */ + nfc->data = FIELD_MAX(PPE_ACL_QID) + 1; + return 0; + case ETHTOOL_GRXCLSRLCNT: + list_for_each_entry(r, &priv->acl_rules, list) + if (r->port == port && r->loc >= 0) + cnt++; + nfc->rule_cnt = cnt; + nfc->data = FIELD_MAX(PPE_ACL_RULE_PRI) + 1; + return 0; + case ETHTOOL_GRXCLSRULE: + r = ppe_acl_rule_at(priv, port, nfc->fs.location); + if (!r || !r->fs) + return -ENOENT; + + nfc->fs = *r->fs; + return 0; + case ETHTOOL_GRXCLSRLALL: + list_for_each_entry(r, &priv->acl_rules, list) { + if (r->port != port || r->loc < 0) + continue; + if (cnt >= nfc->rule_cnt) + return -EMSGSIZE; + + rule_locs[cnt++] = r->loc; + } + nfc->rule_cnt = cnt; + nfc->data = FIELD_MAX(PPE_ACL_RULE_PRI) + 1; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static void ppe_acl_small_pkt_apply(struct qca_ppe_priv *priv) +{ + u32 ports = GENMASK(priv->data->num_ports - 1, 0); + u32 act[PPE_ACL_ACTION_WORDS] = {}; + struct ppe_acl_slice s = {}; + int i; + + act[3] = PPE_ACL_PRI_CHANGE_EN | + FIELD_PREP(PPE_ACL_PRI, ppe_small_pkt_prio); + + s.type = PPE_ACL_TYPE_IPMISC; + s.range = true; + /* Range compare: minimum in the rule, maximum in the mask slot. */ + s.mask[0] = FIELD_PREP(PPE_ACL_L3_LEN, ppe_small_pkt_len); + s.mask[1] = PPE_ACL_IS_IPV6; + + /* Length 0 disarms by handing the rule an empty source bitmap, which + * keeps its entry so the parameter can be turned back on. + * + * Priority 1, not the floor: a tc filter at the last preference + * lands on priority 0, which is the one place a rule that has to + * lose to this promotion for a small frame can stand. + */ + for (i = 0; i < ARRAY_SIZE(ppe_small_pkt_group); i++) { + struct ppe_acl_group *g = &ppe_small_pkt_group[i]; + + if (!g->nslices && ppe_acl_alloc(priv, &s, 1, g)) + continue; + + s.key[1] = i ? PPE_ACL_IS_IPV6 : 0; + ppe_acl_slice_write(priv, g->index[0], &s, act, + ppe_small_pkt_len ? ports : 0, 1); + } +} + +static int ppe_small_pkt_param_set(const char *val, + const struct kernel_param *kp) +{ + struct qca_ppe_priv *priv = ppe_acl_priv; + int ret = param_set_ushort(val, kp); + + if (ret) + return ret; + if (ppe_small_pkt_prio > 7) + ppe_small_pkt_prio = 7; + if (!priv) + return 0; + + mutex_lock(&priv->acl_lock); + ppe_acl_small_pkt_apply(priv); + mutex_unlock(&priv->acl_lock); + + return 0; +} + +static const struct kernel_param_ops ppe_small_pkt_param_ops = { + .set = ppe_small_pkt_param_set, + .get = param_get_ushort, +}; + +module_param_cb(small_pkt_len, &ppe_small_pkt_param_ops, + &ppe_small_pkt_len, 0644); +MODULE_PARM_DESC(small_pkt_len, + "Classify frames up to this L3 length into small_pkt_prio (0 disables)"); +module_param_cb(small_pkt_prio, &ppe_small_pkt_param_ops, + &ppe_small_pkt_prio, 0644); +MODULE_PARM_DESC(small_pkt_prio, + "Internal priority for small frames (0-7)"); + +void ppe_acl_init(struct qca_ppe_priv *priv) +{ + int i; + + mutex_init(&priv->acl_lock); + INIT_LIST_HEAD(&priv->acl_rules); + for (i = 0; i < PPE_ACL_LISTS; i++) + priv->acl_free[i] = GENMASK(PPE_ACL_LIST_ENTRIES - 1, 0); + + mutex_lock(&priv->acl_lock); + ppe_acl_priv = priv; + ppe_acl_small_pkt_apply(priv); + mutex_unlock(&priv->acl_lock); +} + +void ppe_acl_exit(struct qca_ppe_priv *priv) +{ + struct ppe_acl_rule *r, *tmp; + int i; + + /* The parameter writer finds this driver through the global and then + * takes the lock that lives inside it, so the lock cannot be what + * guards the pointer. param_attr_store() holds the parameter lock + * across the whole set, so clearing the global under it lets any + * writer already inside finish before this teardown frees what it is + * writing through. + */ + kernel_param_lock(THIS_MODULE); + ppe_acl_priv = NULL; + kernel_param_unlock(THIS_MODULE); + + mutex_lock(&priv->acl_lock); + list_for_each_entry_safe(r, tmp, &priv->acl_rules, list) { + ppe_acl_free(priv, &r->group); + list_del(&r->list); + ppe_acl_rule_free(priv, r); + } + for (i = 0; i < ARRAY_SIZE(ppe_small_pkt_group); i++) + if (ppe_small_pkt_group[i].nslices) + ppe_acl_free(priv, &ppe_small_pkt_group[i]); + mutex_unlock(&priv->acl_lock); +} diff --git a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_main.c b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_main.c index 18e2851b959..81654988f9f 100644 --- a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_main.c +++ b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_main.c @@ -1952,6 +1952,19 @@ static void ppe_mirror_put(struct qca_ppe_priv *priv, u8 dirs) ppe_mirror_analyzer_write(priv); } +/* Which analyzer a classifier rule's mirror uses is not known, so it claims + * both. + */ +int ppe_mirror_analyzer_get(struct qca_ppe_priv *priv, int to_port) +{ + return ppe_mirror_get(priv, to_port, BIT(0) | BIT(1)); +} + +void ppe_mirror_analyzer_put(struct qca_ppe_priv *priv) +{ + ppe_mirror_put(priv, BIT(0) | BIT(1)); +} + int qca_ppe_port_mirror_add(struct dsa_switch *ds, int port, struct dsa_mall_mirror_tc_entry *mirror, bool ingress, struct netlink_ext_ack *extack) @@ -1995,6 +2008,10 @@ void qca_ppe_port_mirror_del(struct dsa_switch *ds, int port, static const struct dsa_switch_ops qca_ppe_ops = { .port_setup_tc = qca_ppe_setup_tc, + .cls_flower_add = qca_ppe_cls_flower_add, + .cls_flower_del = qca_ppe_cls_flower_del, + .get_rxnfc = qca_ppe_get_rxnfc, + .set_rxnfc = qca_ppe_set_rxnfc, .port_mirror_add = qca_ppe_port_mirror_add, .port_mirror_del = qca_ppe_port_mirror_del, .port_policer_add = qca_ppe_port_policer_add, @@ -2250,19 +2267,24 @@ static int qca_ppe_probe(struct platform_device *pdev) ppe_mac_hw_init(priv); ppe_ctrlpkt_init(priv); + ppe_acl_init(priv); if (data->type == PPE_TYPE_IPQ6018) ppe_ipq6018_mux_setup(priv); ret = dsa_register_switch(ds); if (ret) - return ret; + goto err_acl; ppe_debugfs_init(priv); platform_set_drvdata(pdev, priv); return 0; + +err_acl: + ppe_acl_exit(priv); + return ret; } static void qca_ppe_remove(struct platform_device *pdev) @@ -2271,6 +2293,7 @@ static void qca_ppe_remove(struct platform_device *pdev) ppe_debugfs_exit(priv); dsa_unregister_switch(&priv->ds); + ppe_acl_exit(priv); } static const struct ppe_data ipq6018_ppe_data = { diff --git a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_scheduler.c b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_scheduler.c index eb5faa0c089..1203b2442d5 100644 --- a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_scheduler.c +++ b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_scheduler.c @@ -1066,7 +1066,6 @@ static void ppe_rss_hash_init(struct qca_ppe_priv *priv) * the one that gets programmed, exactly as qca-ssdk picks it. */ #define PPE_SHAPER_SLOT 8 -#define PPE_POLICER_SLOT 600 #define PPE_TOKEN_UNIT_MAX 16384 #define PPE_BUCKET_UNIT 65536 /* 12 byte inter-packet gap plus the 8 byte preamble and start delimiter: what @@ -1083,9 +1082,8 @@ unsigned long ppe_clk_rate(struct qca_ppe_priv *priv) return clk_get_rate(priv->ppe_clk); } -static int ppe_token_bucket(unsigned long clk, u32 slot, u64 rate_bps, - u32 burst, u32 cir_max, u32 cbs_max, - u32 *cir, u32 *cbs) +int ppe_token_bucket(unsigned long clk, u32 slot, u64 rate_bps, u32 burst, + u32 cir_max, u32 cbs_max, u32 *cir, u32 *cbs) { int sel; diff --git a/target/linux/qualcommax/patches-6.18/0952-net-ethernet-qca-add-ppe.patch b/target/linux/qualcommax/patches-6.18/0952-net-ethernet-qca-add-ppe.patch index 5a727769342..4cf08190f41 100644 --- a/target/linux/qualcommax/patches-6.18/0952-net-ethernet-qca-add-ppe.patch +++ b/target/linux/qualcommax/patches-6.18/0952-net-ethernet-qca-add-ppe.patch @@ -21,7 +21,7 @@ obj-$(CONFIG_QCA7000_UART) += qcauart.o qcauart-objs := qca_uart.o +obj-$(CONFIG_QCOM_80211AX_PPE) += qca_ppe.o -+qca_ppe-objs := qca_ppe_main.o qca_ppe_vlan.o qca_ppe_scheduler.o qca_ppe_debugfs.o ++qca_ppe-objs := qca_ppe_main.o qca_ppe_vlan.o qca_ppe_scheduler.o qca_ppe_debugfs.o qca_ppe_acl.o obj-y += emac/ diff --git a/target/linux/qualcommax/patches-6.18/0953-net-ethernet-qca-add-EDMA.patch b/target/linux/qualcommax/patches-6.18/0953-net-ethernet-qca-add-EDMA.patch index 4802d533b95..65c0a66b03a 100644 --- a/target/linux/qualcommax/patches-6.18/0953-net-ethernet-qca-add-EDMA.patch +++ b/target/linux/qualcommax/patches-6.18/0953-net-ethernet-qca-add-EDMA.patch @@ -17,7 +17,7 @@ @@ -10,6 +10,7 @@ obj-$(CONFIG_QCA7000_UART) += qcauart.o qcauart-objs := qca_uart.o obj-$(CONFIG_QCOM_80211AX_PPE) += qca_ppe.o - qca_ppe-objs := qca_ppe_main.o qca_ppe_vlan.o qca_ppe_scheduler.o qca_ppe_debugfs.o + qca_ppe-objs := qca_ppe_main.o qca_ppe_vlan.o qca_ppe_scheduler.o qca_ppe_debugfs.o qca_ppe_acl.o +obj-$(CONFIG_QCOM_EDMA) += qca_edma.o obj-y += emac/