--- /dev/null
+From 6408cdca652b1f85e5b8582c283203d11f4dedcb Mon Sep 17 00:00:00 2001
+Message-ID: <6408cdca652b1f85e5b8582c283203d11f4dedcb.1779086987.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sun, 17 May 2026 21:11:27 +0200
+Subject: [PATCH net-next 1/2] net: netfilter: flowtable: Add the capability to
+ offload dscp field
+
+Introduce the capability to hw offload via netfilter flowtable APIs the
+IP TOS info. Implement the sw offloading for DSCP field via the
+netfilter flowtable APIs.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ include/net/netfilter/nf_flow_table.h | 2 ++
+ net/netfilter/nf_flow_table_ip.c | 12 ++++++++++++
+ net/netfilter/nf_flow_table_offload.c | 5 +++++
+ net/netfilter/nft_flow_offload.c | 22 ++++++++++++++++++++++
+ 4 files changed, 41 insertions(+)
+
+--- a/include/net/netfilter/nf_flow_table.h
++++ b/include/net/netfilter/nf_flow_table.h
+@@ -29,6 +29,7 @@ struct nf_flow_key {
+ struct flow_dissector_key_ipv4_addrs ipv4;
+ struct flow_dissector_key_ipv6_addrs ipv6;
+ };
++ struct flow_dissector_key_ip ip;
+ struct flow_dissector_key_keyid enc_key_id;
+ union {
+ struct flow_dissector_key_ipv4_addrs enc_ipv4;
+@@ -138,6 +139,7 @@ struct flow_offload_tuple {
+ encap_num:2,
+ in_vlan_ingress:2;
+ u16 mtu;
++ u8 dscp;
+ union {
+ struct {
+ struct dst_entry *dst_cache;
+--- a/net/netfilter/nf_flow_table_ip.c
++++ b/net/netfilter/nf_flow_table_ip.c
+@@ -372,6 +372,7 @@ static int nf_flow_offload_forward(struc
+ struct flow_offload *flow;
+ unsigned int thoff, mtu;
+ struct iphdr *iph;
++ u8 dscp;
+
+ dir = tuplehash->tuple.dir;
+ flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
+@@ -401,6 +402,12 @@ static int nf_flow_offload_forward(struc
+ iph = ip_hdr(skb);
+ nf_flow_nat_ip(flow, skb, thoff, dir, iph);
+
++ dscp = FIELD_GET(INET_DSCP_MASK, ipv4_get_dsfield(iph));
++ if (tuplehash->tuple.dscp != dscp)
++ ipv4_change_dsfield(iph, INET_ECN_MASK,
++ FIELD_PREP(INET_DSCP_MASK,
++ tuplehash->tuple.dscp));
++
+ ip_decrease_ttl(iph);
+ skb_clear_tstamp(skb);
+
+@@ -651,6 +658,7 @@ static int nf_flow_offload_ipv6_forward(
+ struct flow_offload *flow;
+ unsigned int thoff, mtu;
+ struct ipv6hdr *ip6h;
++ u8 dscp;
+
+ dir = tuplehash->tuple.dir;
+ flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
+@@ -679,6 +687,12 @@ static int nf_flow_offload_ipv6_forward(
+ ip6h = ipv6_hdr(skb);
+ nf_flow_nat_ipv6(flow, skb, dir, ip6h);
+
++ dscp = FIELD_GET(INET_DSCP_MASK, ipv6_get_dsfield(ip6h));
++ if (tuplehash->tuple.dscp != dscp)
++ ipv6_change_dsfield(ip6h, INET_ECN_MASK,
++ FIELD_PREP(INET_DSCP_MASK,
++ tuplehash->tuple.dscp));
++
+ ip6h->hop_limit--;
+ skb_clear_tstamp(skb);
+
+--- a/net/netfilter/nf_flow_table_offload.c
++++ b/net/netfilter/nf_flow_table_offload.c
+@@ -103,6 +103,7 @@ static int nf_flow_rule_match(struct nf_
+ NF_FLOW_DISSECTOR(match, FLOW_DISSECTOR_KEY_BASIC, basic);
+ NF_FLOW_DISSECTOR(match, FLOW_DISSECTOR_KEY_IPV4_ADDRS, ipv4);
+ NF_FLOW_DISSECTOR(match, FLOW_DISSECTOR_KEY_IPV6_ADDRS, ipv6);
++ NF_FLOW_DISSECTOR(match, FLOW_DISSECTOR_KEY_IP, ip);
+ NF_FLOW_DISSECTOR(match, FLOW_DISSECTOR_KEY_TCP, tcp);
+ NF_FLOW_DISSECTOR(match, FLOW_DISSECTOR_KEY_PORTS, tp);
+
+@@ -168,6 +169,10 @@ static int nf_flow_rule_match(struct nf_
+ match->dissector.used_keys |= BIT_ULL(key->control.addr_type);
+ mask->basic.n_proto = 0xffff;
+
++ key->ip.tos = FIELD_PREP(INET_DSCP_MASK, tuple->dscp);
++ mask->ip.tos = 0xff;
++ match->dissector.used_keys |= BIT_ULL(FLOW_DISSECTOR_KEY_IP);
++
+ switch (tuple->l4proto) {
+ case IPPROTO_TCP:
+ key->tcp.flags = 0;
+--- a/net/netfilter/nft_flow_offload.c
++++ b/net/netfilter/nft_flow_offload.c
+@@ -8,6 +8,7 @@
+ #include <linux/spinlock.h>
+ #include <linux/netfilter/nf_conntrack_common.h>
+ #include <linux/netfilter/nf_tables.h>
++#include <net/dsfield.h>
+ #include <net/ip.h> /* for ipv4 options. */
+ #include <net/inet_dscp.h>
+ #include <net/netfilter/nf_tables.h>
+@@ -279,6 +280,27 @@ static int nft_flow_route(const struct n
+ return 0;
+ }
+
++static void nft_flow_set_dscp(const struct nft_pktinfo *pkt,
++ struct flow_offload *flow,
++ enum ip_conntrack_dir dir)
++{
++ struct flow_offload_tuple *tuple = &flow->tuplehash[dir].tuple;
++ struct sk_buff *skb = pkt->skb;
++
++ switch (skb->protocol) {
++ case htons(ETH_P_IP):
++ tuple->dscp = FIELD_GET(INET_DSCP_MASK,
++ ipv4_get_dsfield(ip_hdr(skb)));
++ break;
++ case htons(ETH_P_IPV6):
++ tuple->dscp = FIELD_GET(INET_DSCP_MASK,
++ ipv6_get_dsfield(ipv6_hdr(skb)));
++ break;
++ default:
++ break;
++ }
++}
++
+ static bool nft_flow_offload_skip(struct sk_buff *skb, int family)
+ {
+ if (skb_sec_path(skb))
+@@ -371,6 +393,9 @@ static void nft_flow_offload_eval(const
+ if (!flow)
+ goto err_flow_alloc;
+
++ nft_flow_set_dscp(pkt, flow, dir);
++ nft_flow_set_dscp(pkt, flow, !dir);
++
+ flow_offload_route_init(flow, &route);
+ if (tcph)
+ flow_offload_ct_tcp(ct);
--- /dev/null
+From b9870ade9498f4119d3f8f8368fcd13e1fa0c7c9 Mon Sep 17 00:00:00 2001
+Message-ID: <b9870ade9498f4119d3f8f8368fcd13e1fa0c7c9.1779086987.git.lorenzo@kernel.org>
+In-Reply-To: <6408cdca652b1f85e5b8582c283203d11f4dedcb.1779086987.git.lorenzo@kernel.org>
+References: <6408cdca652b1f85e5b8582c283203d11f4dedcb.1779086987.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Mon, 18 May 2026 08:36:20 +0200
+Subject: [PATCH net-next 2/2] net: airoha: Set hw QoS parameter according to
+ the packet dscp
+
+Introduce the capability to hw offload via netfilter flowtable APIs the
+IP TOS info in order to configure hw queue and dscp field.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_ppe.c | 24 ++++++++++++++++++------
+ 1 file changed, 18 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -11,6 +11,7 @@
+ #include <linux/rhashtable.h>
+ #include <net/ipv6.h>
+ #include <net/pkt_cls.h>
++#include <net/route.h>
+
+ #include "airoha_regs.h"
+ #include "airoha_eth.h"
+@@ -298,7 +299,7 @@ static int airoha_ppe_foe_entry_prepare(
+ struct airoha_foe_entry *hwe,
+ struct net_device *dev, int type,
+ struct airoha_flow_data *data,
+- int l4proto)
++ int l4proto, u8 dsfield)
+ {
+ u32 qdata = FIELD_PREP(AIROHA_FOE_SHAPER_ID, 0x7f), ports_pad, val;
+ int wlan_etype = -EINVAL, dsa_port = airoha_get_dsa_port(&dev);
+@@ -331,7 +332,7 @@ static int airoha_ppe_foe_entry_prepare(
+ info.wcid);
+ } else {
+ struct airoha_gdm_port *port = netdev_priv(dev);
+- u8 pse_port, channel;
++ u8 pse_port, channel, priority;
+
+ if (!airoha_is_valid_gdm_port(eth, port))
+ return -EINVAL;
+@@ -350,9 +351,13 @@ static int airoha_ppe_foe_entry_prepare(
+ */
+ channel = dsa_port >= 0 ? dsa_port : port->id;
+ channel = channel % AIROHA_NUM_QOS_CHANNELS;
+- qdata |= FIELD_PREP(AIROHA_FOE_CHANNEL, channel);
++ priority = rt_tos2priority(dsfield);
++ priority = priority % AIROHA_NUM_QOS_QUEUES;
++ qdata |= FIELD_PREP(AIROHA_FOE_CHANNEL, channel) |
++ FIELD_PREP(AIROHA_FOE_QID, priority);
+
+ val |= FIELD_PREP(AIROHA_FOE_IB2_PSE_PORT, pse_port) |
++ FIELD_PREP(AIROHA_FOE_IB2_DSCP, dsfield) |
+ AIROHA_FOE_IB2_PSE_QOS;
+ /* For downlink traffic consume SRAM memory for hw
+ * forwarding descriptors queue.
+@@ -1044,9 +1049,9 @@ static int airoha_ppe_flow_offload_repla
+ struct net_device *odev = NULL;
+ struct flow_action_entry *act;
+ struct airoha_foe_entry hwe;
++ u8 dsfield = 0, l4proto = 0;
+ int err, i, offload_type;
+ u16 addr_type = 0;
+- u8 l4proto = 0;
+
+ if (rhashtable_lookup(ð->flow_table, &f->cookie,
+ airoha_flow_table_params))
+@@ -1076,6 +1081,13 @@ static int airoha_ppe_flow_offload_repla
+ return -EOPNOTSUPP;
+ }
+
++ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IP)) {
++ struct flow_match_ip match;
++
++ flow_rule_match_ip(rule, &match);
++ dsfield = match.key->tos;
++ }
++
+ switch (addr_type) {
+ case 0:
+ offload_type = PPE_PKT_TYPE_BRIDGE;
+@@ -1141,7 +1153,7 @@ static int airoha_ppe_flow_offload_repla
+ return -EINVAL;
+
+ err = airoha_ppe_foe_entry_prepare(eth, &hwe, odev, offload_type,
+- &data, l4proto);
++ &data, l4proto, dsfield);
+ if (err)
+ return err;
+