* [PATCH net-next v2 0/2] selftests: openvswitch: add pop_vlan test
From: Minxi Hou @ 2026-05-01 13:39 UTC (permalink / raw)
To: netdev; +Cc: linux-kselftest, dev, Minxi Hou
This series extends the ovs-dpctl.py flow parser to support vlan() and
encap() match strings, then adds a test for the OVS datapath POP_VLAN
action.
The ovs-dpctl.py flow parser currently cannot express VLAN-tagged flow
matches. The kernel unconditionally forces an exact match on VLAN TCI
when parsing flow keys, and requires both OVS_KEY_ATTR_VLAN and
OVS_KEY_ATTR_ENCAP for VLAN frame validation. Without vlan()/encap()
parsing, no VLAN flow match is possible from ovs-dpctl.py.
Patch 1 fixes two pre-existing nla_map type bugs: OVS_KEY_ATTR_VLAN
was mapped to "uint16" instead of the correct big-endian "be16", and
OVS_KEY_ATTR_ENCAP was mapped to "none" instead of "nested". It then
adds the vlan() and encap() flow string parsers, dpstr() display
support, and proper ovskey instance returns for encap.
Patch 2 adds a test that uses kernel VLAN sub-interfaces to generate
802.1Q tagged frames and verifies pop_vlan via pcap at the egress
veth. A negative check confirms tagged frames arrive intact without
pop_vlan, then the positive check confirms the tag is stripped.
No regression in the existing OVS selftests.
Signed-off-by: Minxi Hou <houminxi@gmail.com>
---
v1 -> v2: rebase to latest net-next/main, drop --base=auto
v1: https://lore.kernel.org/netdev/20260501122756.3081754-1-houminxi@gmail.com/
Minxi Hou (2):
selftests: openvswitch: add vlan() and encap() flow string parsing
selftests: openvswitch: add pop_vlan test
.../selftests/net/openvswitch/openvswitch.sh | 132 ++++++++++++
.../selftests/net/openvswitch/ovs-dpctl.py | 190 +++++++++++++++++-
2 files changed, 320 insertions(+), 2 deletions(-)
--
2.53.0
^ permalink raw reply
* [PATCH net-next v2 1/2] selftests: openvswitch: add vlan() and encap() flow string parsing
From: Minxi Hou @ 2026-05-01 13:39 UTC (permalink / raw)
To: netdev
Cc: linux-kselftest, dev, Minxi Hou, Aaron Conole, Eelco Chaudron,
Ilya Maximets, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Shuah Khan, linux-kernel
In-Reply-To: <20260501133924.3100680-1-houminxi@gmail.com>
Extend the ovs-dpctl.py flow parser to support vlan() and encap()
match strings. vlan() accepts tci=, vid=, pcp=, and cfi=
parameters and generates the OVS_KEY_ATTR_VLAN attribute with a TCI
value in network byte order. encap() parses nested flow strings
and returns OVS_KEY_ATTR_ENCAP with inner key attributes as a
recursive NLA container.
The encap nla_map type is changed from "none" to "nested" so that
pyroute2 recursively encodes the inner flow key attributes. The
VLAN nla_map type is changed from "uint16" to "be16" to match the
kernel's big-endian wire format.
Signed-off-by: Minxi Hou <houminxi@gmail.com>
---
v1 -> v2: rebase to latest net-next/main, drop --base=auto
.../selftests/net/openvswitch/ovs-dpctl.py | 190 +++++++++++++++++-
1 file changed, 188 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
index 848f61fdcee0..317be7878937 100644
--- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
+++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
@@ -901,11 +901,11 @@ class ovskey(nla):
nla_flags = NLA_F_NESTED
nla_map = (
("OVS_KEY_ATTR_UNSPEC", "none"),
- ("OVS_KEY_ATTR_ENCAP", "none"),
+ ("OVS_KEY_ATTR_ENCAP", "nested"),
("OVS_KEY_ATTR_PRIORITY", "uint32"),
("OVS_KEY_ATTR_IN_PORT", "uint32"),
("OVS_KEY_ATTR_ETHERNET", "ethaddr"),
- ("OVS_KEY_ATTR_VLAN", "uint16"),
+ ("OVS_KEY_ATTR_VLAN", "be16"),
("OVS_KEY_ATTR_ETHERTYPE", "be16"),
("OVS_KEY_ATTR_IPV4", "ovs_key_ipv4"),
("OVS_KEY_ATTR_IPV6", "ovs_key_ipv6"),
@@ -1636,6 +1636,180 @@ class ovskey(nla):
class ovs_key_mpls(nla):
fields = (("lse", ">I"),)
+ # 802.1Q CFI (Canonical Format Indicator) bit, always set for Ethernet
+ _VLAN_CFI_MASK = 0x1000
+ _MAX_ENCAP_DEPTH = 4
+ _encap_depth = 0 # single-threaded usage assumed
+
+ @staticmethod
+ def _parse_vlan_from_flowstr(flowstr):
+ """Parse vlan(tci=X) or vlan(vid=X[,pcp=Y,cfi=Z]) from flowstr.
+
+ Returns (remaining_flowstr, key_tci, mask_tci).
+ TCI values use standard bit layout (VID bits 0-11,
+ CFI bit 12, PCP bits 13-15); byte order conversion to
+ big-endian happens in pyroute2 be16 NLA serialization.
+ The mask covers only the fields the caller specified:
+ vid -> 0x0FFF, pcp -> 0xE000, cfi -> 0x1000, tci -> 0xFFFF.
+
+ The tci= key sets the raw TCI bitfield (no CFI validation) to allow
+ non-Ethernet use cases. Use cfi=1 for standard Ethernet VLAN matching.
+ """
+ tci = 0
+ mask = 0
+ has_tci = False
+ has_vid = has_pcp = has_cfi = False
+ _tci_mix_err = "vlan(): 'tci' cannot be mixed " \
+ "with 'vid'/'pcp'/'cfi'"
+ first = True
+ while True:
+ flowstr = flowstr.lstrip()
+ if not flowstr:
+ raise ValueError("vlan(): missing ')'")
+ if flowstr[0] == ')':
+ break
+ if not first:
+ flowstr = flowstr[1:] # skip ','
+ if not flowstr:
+ raise ValueError("vlan(): missing ')' after trailing comma")
+ flowstr = flowstr.lstrip()
+ if flowstr and flowstr[0] == ')':
+ break
+ if flowstr and flowstr[0] == ',':
+ raise ValueError(
+ "vlan(): empty or extra comma in field list")
+ first = False
+
+ eq = flowstr.find('=')
+ if eq == -1:
+ raise ValueError("vlan(): expected key=value, got '%s'" % flowstr)
+ key = flowstr[:eq].strip()
+ flowstr = flowstr[eq + 1:]
+
+ end = flowstr.find(',')
+ end2 = flowstr.find(')')
+ if end == -1 or (end2 != -1 and end2 < end):
+ end = end2
+ val = flowstr[:end].strip()
+ flowstr = flowstr[end:]
+
+ if not val:
+ raise ValueError("vlan(): empty value for key '%s'" % key)
+ try:
+ v = int(val, 16) if val.startswith(('0x', '0X')) else int(val)
+ except ValueError:
+ raise ValueError("vlan(): invalid value '%s' for key '%s'" %
+ (val, key))
+
+ if key == 'tci':
+ if has_tci:
+ raise ValueError("vlan(): duplicate 'tci'")
+ if has_vid or has_pcp or has_cfi:
+ raise ValueError(_tci_mix_err)
+ if v > 0xFFFF or v < 0:
+ raise ValueError("vlan(): tci=0x%x out of range" % v)
+ tci = v
+ mask = 0xFFFF
+ has_tci = True
+ elif key == 'vid':
+ if has_tci:
+ raise ValueError(_tci_mix_err)
+ if has_vid:
+ raise ValueError("vlan(): duplicate 'vid'")
+ if v < 0 or v > 0xFFF:
+ raise ValueError("vlan(): vid=%d out of range (0-4095)" % v)
+ tci |= v
+ mask |= 0x0FFF
+ has_vid = True
+ elif key == 'pcp':
+ if has_tci:
+ raise ValueError(_tci_mix_err)
+ if has_pcp:
+ raise ValueError("vlan(): duplicate 'pcp'")
+ if v < 0 or v > 7:
+ raise ValueError("vlan(): pcp=%d out of range (0-7)" % v)
+ tci |= (v & 0x7) << 13
+ mask |= 0xE000
+ has_pcp = True
+ elif key == 'cfi':
+ if has_tci:
+ raise ValueError(_tci_mix_err)
+ if has_cfi:
+ raise ValueError("vlan(): duplicate 'cfi'")
+ if v != 1:
+ raise ValueError("vlan(): cfi must be 1 for Ethernet")
+ tci |= ovskey._VLAN_CFI_MASK
+ mask |= ovskey._VLAN_CFI_MASK
+ has_cfi = True
+ else:
+ raise ValueError("vlan(): unknown key '%s'" % key)
+
+ flowstr = flowstr[1:] # skip ')'
+ # Catch immediate '))' (user error). A ')' after ',' is consumed
+ # by parse()'s strspn(flowstr, "), ") inter-field separator stripping.
+ if flowstr.lstrip().startswith(')'):
+ raise ValueError("vlan(): unmatched ')'")
+ # parse() strips trailing ',', ')', ' ' as inter-field separators,
+ # so we do not need to call strspn here.
+
+ if mask == 0:
+ raise ValueError("vlan(): no fields specified, "
+ "use vlan(vid=X[,pcp=Y,cfi=Z]) or vlan(tci=X)")
+ if not has_tci:
+ tci |= ovskey._VLAN_CFI_MASK
+ mask |= ovskey._VLAN_CFI_MASK
+ return flowstr, tci, mask
+
+ @staticmethod
+ def _parse_encap_from_flowstr(flowstr):
+ """Parse encap(inner_flow) from flowstr.
+
+ Returns (remaining_flowstr, inner_key_dict, inner_mask_dict)
+ where each dict has an 'attrs' key for recursive NLA encoding.
+ Parenthesis-depth tracking handles nested encap() calls but not
+ quoted strings containing literal parentheses.
+ """
+ if ovskey._encap_depth >= ovskey._MAX_ENCAP_DEPTH:
+ raise ValueError("encap(): max nesting depth %d exceeded" %
+ ovskey._MAX_ENCAP_DEPTH)
+ try:
+ ovskey._encap_depth += 1
+ depth = 1
+ end = -1
+ for i, c in enumerate(flowstr):
+ if c == '(':
+ depth += 1
+ elif c == ')':
+ depth -= 1
+ if depth < 0:
+ raise ValueError("encap(): unmatched ')' at position %d" % i)
+ if depth == 0:
+ end = i
+ break
+
+ if end == -1:
+ if depth > 1:
+ raise ValueError("encap(): missing ')' at end")
+ raise ValueError("encap(): missing closing ')'")
+
+ inner_str = flowstr[:end].strip()
+ if not inner_str:
+ raise ValueError("encap(): empty inner flow")
+
+ flowstr = flowstr[end + 1:]
+ if flowstr.lstrip().startswith(')'):
+ raise ValueError("encap(): unmatched ')' after encap()")
+ # parse() strips trailing ',', ')', ' ' as inter-field separators,
+ # so we do not need to call strspn here.
+
+ inner_key = ovskey()
+ inner_mask = ovskey()
+ inner_key.parse(inner_str, inner_mask)
+
+ return flowstr, inner_key, inner_mask
+ finally:
+ ovskey._encap_depth -= 1
+
def parse(self, flowstr, mask=None):
for field in (
("OVS_KEY_ATTR_PRIORITY", "skb_priority", intparse),
@@ -1657,6 +1831,16 @@ class ovskey(nla):
"eth_type",
lambda x: intparse(x, "0xffff"),
),
+ (
+ "OVS_KEY_ATTR_VLAN",
+ "vlan",
+ ovskey._parse_vlan_from_flowstr,
+ ),
+ (
+ "OVS_KEY_ATTR_ENCAP",
+ "encap",
+ ovskey._parse_encap_from_flowstr,
+ ),
(
"OVS_KEY_ATTR_IPV4",
"ipv4",
@@ -1794,6 +1978,8 @@ class ovskey(nla):
True,
),
("OVS_KEY_ATTR_ETHERNET", None, None, False, False),
+ ("OVS_KEY_ATTR_VLAN", "vlan", "0x%04x", lambda x: False, True),
+ ("OVS_KEY_ATTR_ENCAP", None, None, False, False),
(
"OVS_KEY_ATTR_ETHERTYPE",
"eth_type",
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v2 2/2] selftests: openvswitch: add pop_vlan test
From: Minxi Hou @ 2026-05-01 13:39 UTC (permalink / raw)
To: netdev
Cc: linux-kselftest, dev, Minxi Hou, Aaron Conole, Eelco Chaudron,
Ilya Maximets, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Shuah Khan, linux-kernel
In-Reply-To: <20260501133924.3100680-1-houminxi@gmail.com>
Add a test for the OVS datapath POP_VLAN action. The test uses
the VLAN sub-interface of a veth pair to generate 802.1Q tagged
frames and verifies that pop_vlan correctly strips the VLAN tag
before delivery via pcap frame capture.
The test has two verifications:
- Negative: forward tagged frames without pop_vlan, verify the
VLAN tag is still present on the egress interface.
- Positive: apply pop_vlan, verify the tag is removed and an
untagged ICMP echo request arrives at the egress interface.
Static ARP entries avoid the complexity of VLAN-tagged ARP
resolution (the egress side has no VLAN sub-interface and cannot
process tagged ARP).
Signed-off-by: Minxi Hou <houminxi@gmail.com>
---
v1 -> v2: rebase to latest net-next/main, drop --base=auto
.../selftests/net/openvswitch/openvswitch.sh | 132 ++++++++++++++++++
1 file changed, 132 insertions(+)
diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh b/tools/testing/selftests/net/openvswitch/openvswitch.sh
index b327d3061ed5..cd614ff7b740 100755
--- a/tools/testing/selftests/net/openvswitch/openvswitch.sh
+++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh
@@ -27,6 +27,7 @@ tests="
upcall_interfaces ovs: test the upcall interfaces
tunnel_metadata ovs: test extraction of tunnel metadata
drop_reason drop: test drop reasons are emitted
+ pop_vlan vlan-pop: POP_VLAN action strips 802.1Q tag
psample psample: Sampling packets with psample"
info() {
@@ -830,6 +831,137 @@ test_tunnel_metadata() {
return 0
}
+test_pop_vlan() {
+ modprobe -q openvswitch 2>/dev/null || true
+ [ -d /sys/module/openvswitch ] || return $ksft_skip
+ ip netns add __test_pop_vlan_netns__ 2>/dev/null || \
+ { info "CONFIG_NET_NS missing"; return $ksft_skip; }
+ ip netns del __test_pop_vlan_netns__ 2>/dev/null
+ modprobe -q 8021q 2>/dev/null || true
+ [ -d /sys/module/8021q ] || { info "CONFIG_VLAN_8021Q missing"; return $ksft_skip; }
+
+ local sbx="test_pop_vlan"
+ sbx_add "$sbx" || return $ksft_skip
+ ovs_add_dp "$sbx" vlandp || return 1
+
+ # --- baseline: untagged forwarding ---
+ ovs_add_netns_and_veths "$sbx" vlandp ns1 veth1 ns1veth 192.0.2.1/24 || return 1
+ ovs_add_netns_and_veths "$sbx" vlandp ns2 veth2 ns2veth 192.0.2.2/24 || return 1
+
+ # ARP + IPv4 bidirectional (all untagged)
+ ovs_add_flow "$sbx" vlandp \
+ 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1
+ ovs_add_flow "$sbx" vlandp \
+ 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1
+ ovs_add_flow "$sbx" vlandp \
+ 'in_port(1),eth(),eth_type(0x0800),ipv4()' '2' || return 1
+ ovs_add_flow "$sbx" vlandp \
+ 'in_port(2),eth(),eth_type(0x0800),ipv4()' '1' || return 1
+ ip netns exec ns1 ping -c 3 -W 2 192.0.2.2 || return 1
+
+ # --- POP_VLAN test ---
+ # Register cleanup before creating resources (safe on failure paths)
+ on_exit "ip -n ns1 link del ns1veth.10 2>/dev/null || true
+ ip -n ns2 addr del 198.51.100.2/24 dev ns2veth 2>/dev/null || true"
+
+ # ns1: VLAN sub-interface generates tagged frames
+ ip -n ns1 link add link ns1veth name ns1veth.10 type vlan id 10
+ ip -n ns1 addr add 198.51.100.1/24 dev ns1veth.10
+ ip -n ns1 link set ns1veth.10 up
+
+ # ns2: no VLAN sub-interface. POP delivers untagged frames to ns2veth
+ ip -n ns2 addr add 198.51.100.2/24 dev ns2veth
+
+ # veth disable VLAN offload + GRO (ensure kernel software tag processing)
+ if command -v ethtool >/dev/null 2>&1; then
+ ip netns exec ns1 ethtool -k ns1veth 2>/dev/null | grep -q vlan-offload && \
+ ip netns exec ns1 ethtool -K ns1veth rx-vlan-offload off \
+ tx-vlan-offload off gro off 2>/dev/null || true
+ ip netns exec ns2 ethtool -k ns2veth 2>/dev/null | grep -q vlan-offload && \
+ ip netns exec ns2 ethtool -K ns2veth rx-vlan-offload off \
+ tx-vlan-offload off gro off 2>/dev/null || true
+ fi
+
+ ovs_del_flows "$sbx" vlandp
+
+ # Static ARP avoids VLAN-tagged ARP complexity (ns2 has no VLAN
+ # sub-interface, so tagged ARP would be invisible to ns2).
+ local ns1veth10mac ns2mac
+ ns1veth10mac=$(ip -n ns1 link show ns1veth.10 | \
+ awk '/link\/ether/ {print $2}')
+ ns2mac=$(ip -n ns2 link show ns2veth | \
+ awk '/link\/ether/ {print $2}')
+ [ -n "$ns1veth10mac" ] && echo "$ns1veth10mac" | \
+ grep -qE "^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$" || return 1
+ [ -n "$ns2mac" ] && echo "$ns2mac" | \
+ grep -qE "^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$" || return 1
+ ip -n ns1 neigh replace 198.51.100.2 lladdr "$ns2mac" \
+ dev ns1veth.10 nud permanent || return 1
+ ip -n ns2 neigh replace 198.51.100.1 lladdr "$ns1veth10mac" \
+ dev ns2veth nud permanent || return 1
+
+ # --- Negative check: fwd without pop_vlan, VLAN tag stays ---
+ ovs_add_flow "$sbx" vlandp \
+ 'in_port(1),eth(),eth_type(0x8100),vlan(vid=10),encap(eth_type(0x0800),ipv4(src=198.51.100.1,proto=1),icmp())' \
+ '2' || return 1
+
+ local pcap_no_pop
+ pcap_no_pop=$(mktemp --suffix=.pcap)
+ on_exit "rm -f $pcap_no_pop"
+ ip netns exec ns2 tcpdump -nei ns2veth -w "$pcap_no_pop" -U &
+ local tpid_no_pop=$!
+ on_exit "kill $tpid_no_pop 2>/dev/null || true"
+ sleep $((WAIT_TIMEOUT / 5 < 2 ? 2 : WAIT_TIMEOUT / 5))
+
+ ip netns exec ns1 ping -I ns1veth.10 -c 3 -W 1 198.51.100.2 \
+ >/dev/null 2>&1 || true
+ kill $tpid_no_pop 2>/dev/null || true; wait $tpid_no_pop 2>/dev/null || true
+
+ # assert: VLAN tag still present (no pop_vlan in action)
+ tcpdump -nr "$pcap_no_pop" 'vlan' 2>/dev/null | grep -q . || {
+ info "FAIL: negative check: no VLAN tag (expected tag present)"; return 1
+ }
+
+ ovs_del_flows "$sbx" vlandp
+
+ # --- Positive: pop_vlan strips tag ---
+ ovs_add_flow "$sbx" vlandp \
+ 'in_port(1),eth(),eth_type(0x8100),vlan(vid=10),encap(eth_type(0x0800),ipv4(src=198.51.100.1,proto=1),icmp())' \
+ 'pop_vlan,2' || return 1
+ ovs_add_flow "$sbx" vlandp \
+ 'in_port(2),eth(),eth_type(0x0800),ipv4()' '1' || return 1
+
+ local pcap
+ pcap=$(mktemp --suffix=.pcap)
+ on_exit "rm -f $pcap"
+ ip netns exec ns2 tcpdump -nei ns2veth -w "$pcap" -U &
+ local tpid=$!
+ on_exit "kill $tpid 2>/dev/null || true"
+ sleep $((WAIT_TIMEOUT / 5 < 2 ? 2 : WAIT_TIMEOUT / 5))
+
+ # ping reply unreachable: ns1veth.10 only accepts tagged frames,
+ # ns2 sends untagged reply -> dropped by ns1veth.10.
+ local ping_rc=0
+ ip netns exec ns1 ping -I ns1veth.10 -c 3 -W 1 198.51.100.2 \
+ >/dev/null 2>&1 || ping_rc=$?
+ kill $tpid 2>/dev/null || true; wait $tpid 2>/dev/null || true
+
+ # ping failure is expected (reply path asymmetric)
+ [ "$ping_rc" -ne 0 ] || \
+ info "NOTE: ping succeeded unexpectedly (reply reached ns1veth.10)"
+
+ # assert: no VLAN tag (POP succeeded), untagged ICMP echo request arrived
+ tcpdump -nr "$pcap" 'vlan' 2>/dev/null | grep -q . && {
+ info "FAIL: POP_VLAN: VLAN tag still present"; return 1
+ }
+ tcpdump -nr "$pcap" 'icmp and icmp[icmptype]=8' \
+ 2>/dev/null | grep -q . || {
+ info "FAIL: POP_VLAN: no untagged ICMP echo request"; return 1
+ }
+
+ return 0
+}
+
run_test() {
(
tname="$1"
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v4 03/15] firmware: qcom_scm: Migrate to generic PAS service
From: Sumit Garg @ 2026-05-01 13:41 UTC (permalink / raw)
To: Mukesh Ojha
Cc: andersson, konradybcio, linux-arm-msm, devicetree, dri-devel,
freedreno, linux-media, netdev, linux-wireless, ath12k,
linux-remoteproc, robh, krzk+dt, conor+dt, robin.clark, sean,
akhilpo, lumag, abhinav.kumar, jesszhan0024, marijn.suijten,
airlied, simona, vikash.garodia, dikshita.agarwal, bod, mchehab,
elder, andrew+netdev, davem, edumazet, kuba, pabeni, jjohnson,
mathieu.poirier, trilokkumar.soni, pavan.kondeti, jorge.ramirez,
tonyh, vignesh.viswanathan, srinivas.kandagatla, amirreza.zarrabi,
jens.wiklander, op-tee, apurupa, skare, linux-kernel, Sumit Garg
In-Reply-To: <20260429104841.l555xd2izoyrqjp4@hu-mojha-hyd.qualcomm.com>
On Wed, Apr 29, 2026 at 04:18:41PM +0530, Mukesh Ojha wrote:
> On Mon, Apr 27, 2026 at 03:25:51PM +0530, Sumit Garg wrote:
> > From: Sumit Garg <sumit.garg@oss.qualcomm.com>
> >
> > With the availability of generic PAS service, let's add SCM calls as
> > a backend to keep supporting legacy QTEE interfaces. The exported
> > qcom_scm* wrappers will get dropped once all the client drivers get
> > migrated as part of future patches.
> >
> > Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
> > ---
> > drivers/firmware/qcom/Kconfig | 1 +
> > drivers/firmware/qcom/qcom_scm.c | 335 ++++++++++++++-----------------
> > 2 files changed, 155 insertions(+), 181 deletions(-)
> >
> > diff --git a/drivers/firmware/qcom/Kconfig b/drivers/firmware/qcom/Kconfig
> > index 8653639d06db..9a12ae2b639d 100644
> > --- a/drivers/firmware/qcom/Kconfig
> > +++ b/drivers/firmware/qcom/Kconfig
> > @@ -15,6 +15,7 @@ config QCOM_PAS
> > TEE bus based PAS service implementation.
> >
> > config QCOM_SCM
> > + select QCOM_PAS
> > select QCOM_TZMEM
> > tristate
> >
> > diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c
> > index 9b06a69d3a6d..d87a962e93da 100644
> > --- a/drivers/firmware/qcom/qcom_scm.c
> > +++ b/drivers/firmware/qcom/qcom_scm.c
> > @@ -13,6 +13,7 @@
> > #include <linux/dma-mapping.h>
> > #include <linux/err.h>
> > #include <linux/export.h>
> > +#include <linux/firmware/qcom/qcom_pas.h>
> > #include <linux/firmware/qcom/qcom_scm.h>
> > #include <linux/firmware/qcom/qcom_tzmem.h>
> > #include <linux/init.h>
> > @@ -33,6 +34,7 @@
> >
> > #include <dt-bindings/interrupt-controller/arm-gic.h>
> >
> > +#include "qcom_pas.h"
> > #include "qcom_scm.h"
> > #include "qcom_tzmem.h"
> >
> > @@ -479,25 +481,6 @@ void qcom_scm_cpu_power_down(u32 flags)
> > }
> > EXPORT_SYMBOL_GPL(qcom_scm_cpu_power_down);
> >
> > -int qcom_scm_set_remote_state(u32 state, u32 id)
> > -{
> > - struct qcom_scm_desc desc = {
> > - .svc = QCOM_SCM_SVC_BOOT,
> > - .cmd = QCOM_SCM_BOOT_SET_REMOTE_STATE,
> > - .arginfo = QCOM_SCM_ARGS(2),
> > - .args[0] = state,
> > - .args[1] = id,
> > - .owner = ARM_SMCCC_OWNER_SIP,
> > - };
> > - struct qcom_scm_res res;
> > - int ret;
> > -
> > - ret = qcom_scm_call(__scm->dev, &desc, &res);
> > -
> > - return ret ? : res.result[0];
> > -}
> > -EXPORT_SYMBOL_GPL(qcom_scm_set_remote_state);
> > -
> > static int qcom_scm_disable_sdi(void)
> > {
> > int ret;
> > @@ -570,26 +553,12 @@ static void qcom_scm_set_download_mode(u32 dload_mode)
> > dev_err(__scm->dev, "failed to set download mode: %d\n", ret);
> > }
> >
> > -/**
> > - * devm_qcom_scm_pas_context_alloc() - Allocate peripheral authentication service
> > - * context for a given peripheral
> > - *
> > - * PAS context is device-resource managed, so the caller does not need
> > - * to worry about freeing the context memory.
> > - *
> > - * @dev: PAS firmware device
> > - * @pas_id: peripheral authentication service id
> > - * @mem_phys: Subsystem reserve memory start address
> > - * @mem_size: Subsystem reserve memory size
> > - *
> > - * Returns: The new PAS context, or ERR_PTR() on failure.
> > - */
> > struct qcom_scm_pas_context *devm_qcom_scm_pas_context_alloc(struct device *dev,
> > u32 pas_id,
> > phys_addr_t mem_phys,
> > size_t mem_size)
> > {
> > - struct qcom_scm_pas_context *ctx;
> > + struct qcom_pas_context *ctx;
> >
> > ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
> > if (!ctx)
> > @@ -600,11 +569,12 @@ struct qcom_scm_pas_context *devm_qcom_scm_pas_context_alloc(struct device *dev,
> > ctx->mem_phys = mem_phys;
> > ctx->mem_size = mem_size;
> >
> > - return ctx;
> > + return (struct qcom_scm_pas_context *)ctx;
> > }
> > EXPORT_SYMBOL_GPL(devm_qcom_scm_pas_context_alloc);
> >
> > -static int __qcom_scm_pas_init_image(u32 pas_id, dma_addr_t mdata_phys,
> > +static int __qcom_scm_pas_init_image(struct device *dev, u32 pas_id,
> > + dma_addr_t mdata_phys,
> > struct qcom_scm_res *res)
> > {
> > struct qcom_scm_desc desc = {
> > @@ -626,7 +596,7 @@ static int __qcom_scm_pas_init_image(u32 pas_id, dma_addr_t mdata_phys,
> >
> > desc.args[1] = mdata_phys;
> >
> > - ret = qcom_scm_call(__scm->dev, &desc, res);
> > + ret = qcom_scm_call(dev, &desc, res);
> > qcom_scm_bw_disable();
> >
> > disable_clk:
> > @@ -635,7 +605,8 @@ static int __qcom_scm_pas_init_image(u32 pas_id, dma_addr_t mdata_phys,
> > return ret;
> > }
> >
> > -static int qcom_scm_pas_prep_and_init_image(struct qcom_scm_pas_context *ctx,
> > +static int qcom_scm_pas_prep_and_init_image(struct device *dev,
> > + struct qcom_pas_context *ctx,
> > const void *metadata, size_t size)
> > {
> > struct qcom_scm_res res;
> > @@ -650,7 +621,7 @@ static int qcom_scm_pas_prep_and_init_image(struct qcom_scm_pas_context *ctx,
> > memcpy(mdata_buf, metadata, size);
> > mdata_phys = qcom_tzmem_to_phys(mdata_buf);
> >
> > - ret = __qcom_scm_pas_init_image(ctx->pas_id, mdata_phys, &res);
> > + ret = __qcom_scm_pas_init_image(dev, ctx->pas_id, mdata_phys, &res);
> > if (ret < 0)
> > qcom_tzmem_free(mdata_buf);
> > else
> > @@ -659,25 +630,9 @@ static int qcom_scm_pas_prep_and_init_image(struct qcom_scm_pas_context *ctx,
> > return ret ? : res.result[0];
> > }
> >
> > -/**
> > - * qcom_scm_pas_init_image() - Initialize peripheral authentication service
> > - * state machine for a given peripheral, using the
> > - * metadata
> > - * @pas_id: peripheral authentication service id
> > - * @metadata: pointer to memory containing ELF header, program header table
> > - * and optional blob of data used for authenticating the metadata
> > - * and the rest of the firmware
> > - * @size: size of the metadata
> > - * @ctx: optional pas context
> > - *
> > - * Return: 0 on success.
> > - *
> > - * Upon successful return, the PAS metadata context (@ctx) will be used to
> > - * track the metadata allocation, this needs to be released by invoking
> > - * qcom_scm_pas_metadata_release() by the caller.
> > - */
> > -int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size,
> > - struct qcom_scm_pas_context *ctx)
> > +static int __qcom_scm_pas_init_image2(struct device *dev, u32 pas_id,
> > + const void *metadata, size_t size,
> > + struct qcom_pas_context *ctx)
> > {
> > struct qcom_scm_res res;
> > dma_addr_t mdata_phys;
> > @@ -685,7 +640,7 @@ int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size,
> > int ret;
> >
> > if (ctx && ctx->use_tzmem)
> > - return qcom_scm_pas_prep_and_init_image(ctx, metadata, size);
> > + return qcom_scm_pas_prep_and_init_image(dev, ctx, metadata, size);
> >
> > /*
> > * During the scm call memory protection will be enabled for the meta
> > @@ -699,16 +654,15 @@ int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size,
> > * If we pass a buffer that is already part of an SHM Bridge to this
> > * call, it will fail.
> > */
> > - mdata_buf = dma_alloc_coherent(__scm->dev, size, &mdata_phys,
> > - GFP_KERNEL);
> > + mdata_buf = dma_alloc_coherent(dev, size, &mdata_phys, GFP_KERNEL);
> > if (!mdata_buf)
> > return -ENOMEM;
> >
> > memcpy(mdata_buf, metadata, size);
> >
> > - ret = __qcom_scm_pas_init_image(pas_id, mdata_phys, &res);
> > + ret = __qcom_scm_pas_init_image(dev, pas_id, mdata_phys, &res);
> > if (ret < 0 || !ctx) {
> > - dma_free_coherent(__scm->dev, size, mdata_buf, mdata_phys);
> > + dma_free_coherent(dev, size, mdata_buf, mdata_phys);
> > } else if (ctx) {
> > ctx->ptr = mdata_buf;
> > ctx->phys = mdata_phys;
> > @@ -717,36 +671,35 @@ int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size,
> >
> > return ret ? : res.result[0];
> > }
> > -EXPORT_SYMBOL_GPL(qcom_scm_pas_init_image);
> >
> > -/**
> > - * qcom_scm_pas_metadata_release() - release metadata context
> > - * @ctx: pas context
> > - */
> > -void qcom_scm_pas_metadata_release(struct qcom_scm_pas_context *ctx)
> > +int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size,
> > + struct qcom_scm_pas_context *ctx)
> > {
> > - if (!ctx->ptr)
> > - return;
> > + return __qcom_scm_pas_init_image2(__scm->dev, pas_id, metadata, size,
> > + (struct qcom_pas_context *)ctx);
> > +}
> > +EXPORT_SYMBOL_GPL(qcom_scm_pas_init_image);
> >
> > +static void __qcom_scm_pas_metadata_release(struct device *dev,
> > + struct qcom_pas_context *ctx)
> > +{
> > if (ctx->use_tzmem)
> > qcom_tzmem_free(ctx->ptr);
> > else
> > - dma_free_coherent(__scm->dev, ctx->size, ctx->ptr, ctx->phys);
> > + dma_free_coherent(dev, ctx->size, ctx->ptr, ctx->phys);
> >
> > ctx->ptr = NULL;
> > }
> > +
> > +void qcom_scm_pas_metadata_release(struct qcom_scm_pas_context *ctx)
> > +{
> > + __qcom_scm_pas_metadata_release(__scm->dev,
> > + (struct qcom_pas_context *)ctx);
> > +}
> > EXPORT_SYMBOL_GPL(qcom_scm_pas_metadata_release);
> >
> > -/**
> > - * qcom_scm_pas_mem_setup() - Prepare the memory related to a given peripheral
> > - * for firmware loading
> > - * @pas_id: peripheral authentication service id
> > - * @addr: start address of memory area to prepare
> > - * @size: size of the memory area to prepare
> > - *
> > - * Returns 0 on success.
> > - */
> > -int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size)
> > +static int __qcom_scm_pas_mem_setup(struct device *dev, u32 pas_id,
> > + phys_addr_t addr, phys_addr_t size)
> > {
> > int ret;
> > struct qcom_scm_desc desc = {
> > @@ -768,7 +721,7 @@ int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size)
> > if (ret)
> > goto disable_clk;
> >
> > - ret = qcom_scm_call(__scm->dev, &desc, &res);
> > + ret = qcom_scm_call(dev, &desc, &res);
> > qcom_scm_bw_disable();
> >
> > disable_clk:
> > @@ -776,9 +729,15 @@ int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size)
> >
> > return ret ? : res.result[0];
> > }
> > +
> > +int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size)
> > +{
> > + return __qcom_scm_pas_mem_setup(__scm->dev, pas_id, addr, size);
> > +}
> > EXPORT_SYMBOL_GPL(qcom_scm_pas_mem_setup);
> >
> > -static void *__qcom_scm_pas_get_rsc_table(u32 pas_id, void *input_rt_tzm,
> > +static void *__qcom_scm_pas_get_rsc_table(struct device *dev, u32 pas_id,
> > + void *input_rt_tzm,
> > size_t input_rt_size,
> > size_t *output_rt_size)
> > {
> > @@ -813,7 +772,7 @@ static void *__qcom_scm_pas_get_rsc_table(u32 pas_id, void *input_rt_tzm,
> > * with output_rt_tzm buffer with res.result[2] size however, It should not
> > * be of unresonable size.
> > */
> > - ret = qcom_scm_call(__scm->dev, &desc, &res);
> > + ret = qcom_scm_call(dev, &desc, &res);
> > if (!ret && res.result[2] > SZ_1G) {
> > ret = -E2BIG;
> > goto free_output_rt;
> > @@ -830,51 +789,11 @@ static void *__qcom_scm_pas_get_rsc_table(u32 pas_id, void *input_rt_tzm,
> > return ret ? ERR_PTR(ret) : output_rt_tzm;
> > }
> >
> > -/**
> > - * qcom_scm_pas_get_rsc_table() - Retrieve the resource table in passed output buffer
> > - * for a given peripheral.
> > - *
> > - * Qualcomm remote processor may rely on both static and dynamic resources for
> > - * its functionality. Static resources typically refer to memory-mapped addresses
> > - * required by the subsystem and are often embedded within the firmware binary
> > - * and dynamic resources, such as shared memory in DDR etc., are determined at
> > - * runtime during the boot process.
> > - *
> > - * On Qualcomm Technologies devices, it's possible that static resources are not
> > - * embedded in the firmware binary and instead are provided by TrustZone However,
> > - * dynamic resources are always expected to come from TrustZone. This indicates
> > - * that for Qualcomm devices, all resources (static and dynamic) will be provided
> > - * by TrustZone via the SMC call.
> > - *
> > - * If the remote processor firmware binary does contain static resources, they
> > - * should be passed in input_rt. These will be forwarded to TrustZone for
> > - * authentication. TrustZone will then append the dynamic resources and return
> > - * the complete resource table in output_rt_tzm.
> > - *
> > - * If the remote processor firmware binary does not include a resource table,
> > - * the caller of this function should set input_rt as NULL and input_rt_size
> > - * as zero respectively.
> > - *
> > - * More about documentation on resource table data structures can be found in
> > - * include/linux/remoteproc.h
> > - *
> > - * @ctx: PAS context
> > - * @pas_id: peripheral authentication service id
> > - * @input_rt: resource table buffer which is present in firmware binary
> > - * @input_rt_size: size of the resource table present in firmware binary
> > - * @output_rt_size: TrustZone expects caller should pass worst case size for
> > - * the output_rt_tzm.
> > - *
> > - * Return:
> > - * On success, returns a pointer to the allocated buffer containing the final
> > - * resource table and output_rt_size will have actual resource table size from
> > - * TrustZone. The caller is responsible for freeing the buffer. On failure,
> > - * returns ERR_PTR(-errno).
> > - */
> > -struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *ctx,
> > - void *input_rt,
> > - size_t input_rt_size,
> > - size_t *output_rt_size)
> > +static void *__qcom_scm_pas_get_rsc_table2(struct device *dev,
> > + struct qcom_pas_context *ctx,
> > + void *input_rt,
> > + size_t input_rt_size,
> > + size_t *output_rt_size)
> > {
> > struct resource_table empty_rsc = {};
> > size_t size = SZ_16K;
> > @@ -909,11 +828,12 @@ struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *c
> >
> > memcpy(input_rt_tzm, input_rt, input_rt_size);
> >
> > - output_rt_tzm = __qcom_scm_pas_get_rsc_table(ctx->pas_id, input_rt_tzm,
> > + output_rt_tzm = __qcom_scm_pas_get_rsc_table(dev, ctx->pas_id,
> > + input_rt_tzm,
> > input_rt_size, &size);
> > if (PTR_ERR(output_rt_tzm) == -EOVERFLOW)
> > /* Try again with the size requested by the TZ */
> > - output_rt_tzm = __qcom_scm_pas_get_rsc_table(ctx->pas_id,
> > + output_rt_tzm = __qcom_scm_pas_get_rsc_table(dev, ctx->pas_id,
> > input_rt_tzm,
> > input_rt_size,
> > &size);
> > @@ -943,16 +863,20 @@ struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *c
> >
> > return ret ? ERR_PTR(ret) : tbl_ptr;
> > }
> > +
> > +struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *ctx,
> > + void *input_rt,
> > + size_t input_rt_size,
> > + size_t *output_rt_size)
> > +{
> > + return __qcom_scm_pas_get_rsc_table2(__scm->dev,
> > + (struct qcom_pas_context *)ctx,
> > + input_rt, input_rt_size,
> > + output_rt_size);
> > +}
> > EXPORT_SYMBOL_GPL(qcom_scm_pas_get_rsc_table);
> >
> > -/**
> > - * qcom_scm_pas_auth_and_reset() - Authenticate the given peripheral firmware
> > - * and reset the remote processor
> > - * @pas_id: peripheral authentication service id
> > - *
> > - * Return 0 on success.
> > - */
> > -int qcom_scm_pas_auth_and_reset(u32 pas_id)
> > +static int __qcom_scm_pas_auth_and_reset(struct device *dev, u32 pas_id)
> > {
> > int ret;
> > struct qcom_scm_desc desc = {
> > @@ -972,7 +896,7 @@ int qcom_scm_pas_auth_and_reset(u32 pas_id)
> > if (ret)
> > goto disable_clk;
> >
> > - ret = qcom_scm_call(__scm->dev, &desc, &res);
> > + ret = qcom_scm_call(dev, &desc, &res);
> > qcom_scm_bw_disable();
> >
> > disable_clk:
> > @@ -980,28 +904,15 @@ int qcom_scm_pas_auth_and_reset(u32 pas_id)
> >
> > return ret ? : res.result[0];
> > }
> > +
> > +int qcom_scm_pas_auth_and_reset(u32 pas_id)
> > +{
> > + return __qcom_scm_pas_auth_and_reset(__scm->dev, pas_id);
> > +}
> > EXPORT_SYMBOL_GPL(qcom_scm_pas_auth_and_reset);
> >
> > -/**
> > - * qcom_scm_pas_prepare_and_auth_reset() - Prepare, authenticate, and reset the
> > - * remote processor
> > - *
> > - * @ctx: Context saved during call to qcom_scm_pas_context_init()
> > - *
> > - * This function performs the necessary steps to prepare a PAS subsystem,
> > - * authenticate it using the provided metadata, and initiate a reset sequence.
> > - *
> > - * It should be used when Linux is in control setting up the IOMMU hardware
> > - * for remote subsystem during secure firmware loading processes. The preparation
> > - * step sets up a shmbridge over the firmware memory before TrustZone accesses the
> > - * firmware memory region for authentication. The authentication step verifies
> > - * the integrity and authenticity of the firmware or configuration using secure
> > - * metadata. Finally, the reset step ensures the subsystem starts in a clean and
> > - * sane state.
> > - *
> > - * Return: 0 on success, negative errno on failure.
> > - */
> > -int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx)
> > +static int __qcom_scm_pas_prepare_and_auth_reset(struct device *dev,
> > + struct qcom_pas_context *ctx)
> > {
> > u64 handle;
> > int ret;
> > @@ -1012,7 +923,7 @@ int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx)
> > * memory region and then invokes a call to TrustZone to authenticate.
> > */
> > if (!ctx->use_tzmem)
> > - return qcom_scm_pas_auth_and_reset(ctx->pas_id);
> > + return __qcom_scm_pas_auth_and_reset(dev, ctx->pas_id);
> >
> > /*
> > * When Linux runs @ EL2 Linux must create the shmbridge itself and then
> > @@ -1022,20 +933,45 @@ int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx)
> > if (ret)
> > return ret;
> >
> > - ret = qcom_scm_pas_auth_and_reset(ctx->pas_id);
> > + ret = __qcom_scm_pas_auth_and_reset(dev, ctx->pas_id);
> > qcom_tzmem_shm_bridge_delete(handle);
> >
> > return ret;
> > }
> > +
> > +int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx)
> > +{
> > + return __qcom_scm_pas_prepare_and_auth_reset(__scm->dev,
> > + (struct qcom_pas_context *)ctx);
> > +}
> > EXPORT_SYMBOL_GPL(qcom_scm_pas_prepare_and_auth_reset);
> >
> > -/**
> > - * qcom_scm_pas_shutdown() - Shut down the remote processor
> > - * @pas_id: peripheral authentication service id
> > - *
> > - * Returns 0 on success.
> > - */
> > -int qcom_scm_pas_shutdown(u32 pas_id)
> > +static int __qcom_scm_pas_set_remote_state(struct device *dev, u32 state,
> > + u32 pas_id)
> > +{
> > + struct qcom_scm_desc desc = {
> > + .svc = QCOM_SCM_SVC_BOOT,
> > + .cmd = QCOM_SCM_BOOT_SET_REMOTE_STATE,
> > + .arginfo = QCOM_SCM_ARGS(2),
> > + .args[0] = state,
> > + .args[1] = pas_id,
> > + .owner = ARM_SMCCC_OWNER_SIP,
> > + };
> > + struct qcom_scm_res res;
> > + int ret;
> > +
> > + ret = qcom_scm_call(dev, &desc, &res);
> > +
> > + return ret ? : res.result[0];
> > +}
> > +
> > +int qcom_scm_set_remote_state(u32 state, u32 id)
>
> s/id/pas_id
Ack.
>
> > +{
> > + return __qcom_scm_pas_set_remote_state(__scm->dev, state, id);
> > +}
> > +EXPORT_SYMBOL_GPL(qcom_scm_set_remote_state);
> > +
> > +static int __qcom_scm_pas_shutdown(struct device *dev, u32 pas_id)
> > {
> > int ret;
> > struct qcom_scm_desc desc = {
> > @@ -1055,7 +991,7 @@ int qcom_scm_pas_shutdown(u32 pas_id)
> > if (ret)
> > goto disable_clk;
> >
> > - ret = qcom_scm_call(__scm->dev, &desc, &res);
> > + ret = qcom_scm_call(dev, &desc, &res);
> > qcom_scm_bw_disable();
> >
> > disable_clk:
> > @@ -1063,16 +999,14 @@ int qcom_scm_pas_shutdown(u32 pas_id)
> >
> > return ret ? : res.result[0];
> > }
> > +
> > +int qcom_scm_pas_shutdown(u32 pas_id)
> > +{
> > + return __qcom_scm_pas_shutdown(__scm->dev, pas_id);
> > +}
> > EXPORT_SYMBOL_GPL(qcom_scm_pas_shutdown);
> >
> > -/**
> > - * qcom_scm_pas_supported() - Check if the peripheral authentication service is
> > - * available for the given peripherial
> > - * @pas_id: peripheral authentication service id
> > - *
> > - * Returns true if PAS is supported for this peripheral, otherwise false.
> > - */
> > -bool qcom_scm_pas_supported(u32 pas_id)
> > +static bool __qcom_scm_pas_supported(struct device *dev, u32 pas_id)
> > {
> > int ret;
> > struct qcom_scm_desc desc = {
> > @@ -1084,16 +1018,49 @@ bool qcom_scm_pas_supported(u32 pas_id)
> > };
> > struct qcom_scm_res res;
> >
> > - if (!__qcom_scm_is_call_available(__scm->dev, QCOM_SCM_SVC_PIL,
> > + if (!__qcom_scm_is_call_available(dev, QCOM_SCM_SVC_PIL,
> > QCOM_SCM_PIL_PAS_IS_SUPPORTED))
> > return false;
> >
> > - ret = qcom_scm_call(__scm->dev, &desc, &res);
> > + ret = qcom_scm_call(dev, &desc, &res);
> >
> > return ret ? false : !!res.result[0];
> > }
> > +
> > +bool qcom_scm_pas_supported(u32 pas_id)
> > +{
> > + return __qcom_scm_pas_supported(__scm->dev, pas_id);
> > +}
> > EXPORT_SYMBOL_GPL(qcom_scm_pas_supported);
> >
> > +static struct qcom_pas_ops qcom_pas_ops_scm = {
> > + .drv_name = "qcom_scm",
> > + .supported = __qcom_scm_pas_supported,
> > + .init_image = __qcom_scm_pas_init_image2,
> > + .mem_setup = __qcom_scm_pas_mem_setup,
> > + .get_rsc_table = __qcom_scm_pas_get_rsc_table2,
> > + .auth_and_reset = __qcom_scm_pas_auth_and_reset,
> > + .prepare_and_auth_reset = __qcom_scm_pas_prepare_and_auth_reset,
> > + .set_remote_state = __qcom_scm_pas_set_remote_state,
> > + .shutdown = __qcom_scm_pas_shutdown,
> > + .metadata_release = __qcom_scm_pas_metadata_release,
> > +};
> > +
> > +/**
> > + * qcom_scm_is_pas_available() - Check if the peripheral authentication service
> > + * is available via SCM or not
> > + *
> > + * Returns true if PAS is available, otherwise false.
> > + */
> > +static bool qcom_scm_is_pas_available(void)
> > +{
> > + if (!__qcom_scm_is_call_available(__scm->dev, QCOM_SCM_SVC_PIL,
> > + QCOM_SCM_PIL_PAS_AUTH_AND_RESET))
>
> QCOM_SCM_PIL_PAS_IS_SUPPORTED ?
This SCM call is there to rather check if PAS is supported for a
particular co-processor ID but not the overall SCM PAS functionality.
And this API isn't used by every client driver for status check.
The PAS auth and reset SCM call is surely supported on every QTEE/TZ
implementation, that's why I used it as a reference for checking SCM PAS
support.
-Sumit
>
>
> > + return false;
> > +
> > + return true;
> > +}
> > +
> > static int __qcom_scm_pas_mss_reset(struct device *dev, bool reset)
> > {
> > struct qcom_scm_desc desc = {
> > @@ -2836,6 +2803,11 @@ static int qcom_scm_probe(struct platform_device *pdev)
> >
> > __get_convention();
> >
> > + if (qcom_scm_is_pas_available()) {
>
> Use qcom_scm_pas_supported() and remove qcom_scm_is_pas_available()..
>
> > + qcom_pas_ops_scm.dev = scm->dev;
> > + qcom_pas_ops_register(&qcom_pas_ops_scm);
> > + }
> > +
> > /*
> > * If "download mode" is requested, from this point on warmboot
> > * will cause the boot stages to enter download mode, unless
> > @@ -2875,6 +2847,7 @@ static void qcom_scm_shutdown(struct platform_device *pdev)
> > {
> > /* Clean shutdown, disable download mode to allow normal restart */
> > qcom_scm_set_download_mode(QCOM_DLOAD_NODUMP);
> > + qcom_pas_ops_unregister();
> > }
> >
> > static const struct of_device_id qcom_scm_dt_match[] = {
> > --
> > 2.51.0
> >
>
> With above changes,
>
> Reviewed-by: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com>
>
> --
> -Mukesh Ojha
>
^ permalink raw reply
* Re: [PATCH net] net: eth: fbnic: Fix addr validation in pcs write
From: Simon Horman @ 2026-05-01 13:46 UTC (permalink / raw)
To: mike.marciniszyn
Cc: Alexander Duyck, Jakub Kicinski, kernel-team, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, netdev, linux-kernel,
stable
In-Reply-To: <20260429150049.1643-1-mike.marciniszyn@gmail.com>
On Wed, Apr 29, 2026 at 11:00:49AM -0400, mike.marciniszyn@gmail.com wrote:
> From: "Mike Marciniszyn (Meta)" <mike.marciniszyn@gmail.com>
>
> This patch contains a fix for addr validation in fbnic_mdio_write_pcs().
Hi Mike,
I think this warrants a bit more explanation: Why should addr 2 be
accepted? What happens from a user-perspective when it is not?
>
> Cc: stable@vger.kernel.org
> Fixes: d0ce9fd7eae0 ("fbnic: Add SW shim for MDIO interface to PMD and PCS")
> Signed-off-by: Mike Marciniszyn (Meta) <mike.marciniszyn@gmail.com>
...
--
pw-bot: changes-requested
^ permalink raw reply
* Re: [PATCH net v2] psp: strip variable-length PSP header in psp_dev_rcv()
From: Willem de Bruijn @ 2026-05-01 13:53 UTC (permalink / raw)
To: David Carlier, daniel.zahka, kuba
Cc: willemdebruijn.kernel, davem, edumazet, pabeni, horms, raeds,
kees, cratiu, netdev, linux-kernel, David Carlier, stable
In-Reply-To: <20260501130046.16008-1-devnexen@gmail.com>
David Carlier wrote:
> psp_dev_rcv() unconditionally removes a fixed PSP_ENCAP_HLEN, even
> when psph->hdrlen indicates that the PSP header carries optional
> fields. A frame whose PSP header advertises a non-zero VC or any
> extension would therefore be silently mis-decapsulated: option bytes
> would spill into the inner packet head and downstream parsing would
> fail on a corrupted skb.
>
> Compute the full PSP header length from psph->hdrlen, pull the
> optional bytes into the linear region, and strip the whole header
> when decapsulating. Optional fields (VC, ...) are still ignored,
> just discarded with the rest of the header instead of leaking.
> crypt_offset and the VIRT flag are intentionally not validated here
> - callers know their device's PSP implementation and can decide.
>
> Both in-tree callers gate on hardware-validated PSP, so this is a
> correctness fix rather than a reachable corruption path under
> current configurations.
>
> Fixes: 0eddb8023cee ("psp: provide decapsulation and receive helper for drivers")
> Suggested-by: Daniel Zahka <daniel.zahka@gmail.com>
> Cc: stable@vger.kernel.org
> Signed-off-by: David Carlier <devnexen@gmail.com>
Reviewed-by: Willem de Bruijn <willemb@google.com>
^ permalink raw reply
* Re: [PATCH v4 04/15] firmware: qcom: Add a PAS TEE service
From: Sumit Garg @ 2026-05-01 13:54 UTC (permalink / raw)
To: Mukesh Ojha
Cc: andersson, konradybcio, linux-arm-msm, devicetree, dri-devel,
freedreno, linux-media, netdev, linux-wireless, ath12k,
linux-remoteproc, robh, krzk+dt, conor+dt, robin.clark, sean,
akhilpo, lumag, abhinav.kumar, jesszhan0024, marijn.suijten,
airlied, simona, vikash.garodia, dikshita.agarwal, bod, mchehab,
elder, andrew+netdev, davem, edumazet, kuba, pabeni, jjohnson,
mathieu.poirier, trilokkumar.soni, pavan.kondeti, jorge.ramirez,
tonyh, vignesh.viswanathan, srinivas.kandagatla, amirreza.zarrabi,
jens.wiklander, op-tee, apurupa, skare, linux-kernel, Sumit Garg
In-Reply-To: <20260429132021.xk2xtll545o256oz@hu-mojha-hyd.qualcomm.com>
On Wed, Apr 29, 2026 at 06:50:21PM +0530, Mukesh Ojha wrote:
> On Mon, Apr 27, 2026 at 03:25:52PM +0530, Sumit Garg wrote:
> > From: Sumit Garg <sumit.garg@oss.qualcomm.com>
> >
> > Add support for Peripheral Authentication Service (PAS) driver based
> > on TEE bus with OP-TEE providing the backend PAS service implementation.
> >
> > The TEE PAS service ABI is designed to be extensible with additional API
> > as PTA_QCOM_PAS_CAPABILITIES. This allows to accommodate any future
> > extensions of the PAS service needed while still maintaining backwards
> > compatibility.
> >
> > Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
> > ---
> > drivers/firmware/qcom/Kconfig | 10 +
> > drivers/firmware/qcom/Makefile | 1 +
> > drivers/firmware/qcom/qcom_pas_tee.c | 479 +++++++++++++++++++++++++++
> > 3 files changed, 490 insertions(+)
> > create mode 100644 drivers/firmware/qcom/qcom_pas_tee.c
> >
> > diff --git a/drivers/firmware/qcom/Kconfig b/drivers/firmware/qcom/Kconfig
> > index 9a12ae2b639d..300b3a1bd178 100644
> > --- a/drivers/firmware/qcom/Kconfig
> > +++ b/drivers/firmware/qcom/Kconfig
> > @@ -14,6 +14,16 @@ config QCOM_PAS
> > backends plugged in whether it's an SCM implementation or a proper
> > TEE bus based PAS service implementation.
> >
> > +config QCOM_PAS_TEE
> > + tristate
> > + select QCOM_PAS
> > + depends on TEE
> > + depends on !CPU_BIG_ENDIAN
> > + default m if ARCH_QCOM
> > + help
> > + Enable the generic Peripheral Authentication Service (PAS) provided
> > + by the firmware TEE implementation as the backend.
> > +
> > config QCOM_SCM
> > select QCOM_PAS
> > select QCOM_TZMEM
> > diff --git a/drivers/firmware/qcom/Makefile b/drivers/firmware/qcom/Makefile
> > index dc5ab45f906a..48801d18f37b 100644
> > --- a/drivers/firmware/qcom/Makefile
> > +++ b/drivers/firmware/qcom/Makefile
> > @@ -9,3 +9,4 @@ obj-$(CONFIG_QCOM_TZMEM) += qcom_tzmem.o
> > obj-$(CONFIG_QCOM_QSEECOM) += qcom_qseecom.o
> > obj-$(CONFIG_QCOM_QSEECOM_UEFISECAPP) += qcom_qseecom_uefisecapp.o
> > obj-$(CONFIG_QCOM_PAS) += qcom_pas.o
> > +obj-$(CONFIG_QCOM_PAS_TEE) += qcom_pas_tee.o
> > diff --git a/drivers/firmware/qcom/qcom_pas_tee.c b/drivers/firmware/qcom/qcom_pas_tee.c
> > new file mode 100644
> > index 000000000000..af73d0a68525
> > --- /dev/null
> > +++ b/drivers/firmware/qcom/qcom_pas_tee.c
> > @@ -0,0 +1,479 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> > + */
> > +
> > +#include <linux/delay.h>
> > +#include <linux/of.h>
> > +#include <linux/firmware/qcom/qcom_pas.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/slab.h>
> > +#include <linux/tee_drv.h>
> > +#include <linux/uuid.h>
> > +
> > +#include "qcom_pas.h"
> > +
> > +/*
> > + * Peripheral Authentication Service (PAS) supported.
> > + *
> > + * [in] params[0].value.a: Unique 32bit remote processor identifier
> > + */
> > +#define TA_QCOM_PAS_IS_SUPPORTED 1
> > +
> > +/*
> > + * PAS capabilities.
> > + *
> > + * [in] params[0].value.a: Unique 32bit remote processor identifier
> > + * [out] params[1].value.a: PAS capability flags
> > + */
> > +#define TA_QCOM_PAS_CAPABILITIES 2
> > +
> > +/*
> > + * PAS image initialization.
> > + *
> > + * [in] params[0].value.a: Unique 32bit remote processor identifier
> > + * [in] params[1].memref: Loadable firmware metadata
> > + */
> > +#define TA_QCOM_PAS_INIT_IMAGE 3
> > +
> > +/*
> > + * PAS memory setup.
> > + *
> > + * [in] params[0].value.a: Unique 32bit remote processor identifier
> > + * [in] params[0].value.b: Relocatable firmware size
> > + * [in] params[1].value.a: 32bit LSB relocatable firmware memory address
> > + * [in] params[1].value.b: 32bit MSB relocatable firmware memory address
> > + */
> > +#define TA_QCOM_PAS_MEM_SETUP 4
> > +
> > +/*
> > + * PAS get resource table.
> > + *
> > + * [in] params[0].value.a: Unique 32bit remote processor identifier
> > + * [inout] params[1].memref: Resource table config
> > + */
> > +#define TA_QCOM_PAS_GET_RESOURCE_TABLE 5
> > +
> > +/*
> > + * PAS image authentication and co-processor reset.
> > + *
> > + * [in] params[0].value.a: Unique 32bit remote processor identifier
> > + * [in] params[0].value.b: Firmware size
> > + * [in] params[1].value.a: 32bit LSB firmware memory address
> > + * [in] params[1].value.b: 32bit MSB firmware memory address
> > + * [in] params[2].memref: Optional fw memory space shared/lent
> > + */
> > +#define TA_QCOM_PAS_AUTH_AND_RESET 6
> > +
> > +/*
> > + * PAS co-processor set suspend/resume state.
> > + *
> > + * [in] params[0].value.a: Unique 32bit remote processor identifier
> > + * [in] params[0].value.b: Co-processor state identifier
> > + */
> > +#define TA_QCOM_PAS_SET_REMOTE_STATE 7
> > +
> > +/*
> > + * PAS co-processor shutdown.
> > + *
> > + * [in] params[0].value.a: Unique 32bit remote processor identifier
> > + */
> > +#define TA_QCOM_PAS_SHUTDOWN 8
> > +
> > +#define TEE_NUM_PARAMS 4
> > +
> > +/**
> > + * struct qcom_pas_tee_private - PAS service private data
> > + * @dev: PAS service device.
> > + * @ctx: TEE context handler.
> > + * @session_id: PAS TA session identifier.
> > + */
> > +struct qcom_pas_tee_private {
> > + struct device *dev;
> > + struct tee_context *ctx;
> > + u32 session_id;
> > +};
> > +
> > +static bool qcom_pas_tee_supported(struct device *dev, u32 pas_id)
> > +{
> > + struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
> > + struct tee_ioctl_invoke_arg inv_arg = {
> > + .func = TA_QCOM_PAS_IS_SUPPORTED,
> > + .session = data->session_id,
> > + .num_params = TEE_NUM_PARAMS
> > + };
> > + struct tee_param param[4] = {
> > + [0] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
> > + .u.value.a = pas_id
> > + }
> > + };
> > + int ret;
> > +
> > + ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
> > + if (ret < 0 || inv_arg.ret != 0) {
> > + dev_err(dev, "PAS not supported, pas_id: %d, ret: %d, err: 0x%x\n",
> > + pas_id, ret, inv_arg.ret);
> > + return false;
> > + }
> > +
> > + return true;
> > +}
> > +
> > +static int qcom_pas_tee_init_image(struct device *dev, u32 pas_id,
> > + const void *metadata, size_t size,
> > + struct qcom_pas_context *ctx)
> > +{
> > + struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
> > + struct tee_ioctl_invoke_arg inv_arg = {
> > + .func = TA_QCOM_PAS_INIT_IMAGE,
> > + .session = data->session_id,
> > + .num_params = TEE_NUM_PARAMS
> > + };
> > + struct tee_param param[4] = {
> > + [0] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
> > + .u.value.a = pas_id
> > + },
> > + [1] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT,
> > + }
> > + };
> > + struct tee_shm *mdata_shm;
> > + u8 *mdata_buf = NULL;
> > + int ret;
> > +
> > + mdata_shm = tee_shm_alloc_kernel_buf(data->ctx, size);
> > + if (IS_ERR(mdata_shm)) {
> > + dev_err(dev, "mdata_shm allocation failed\n");
> > + return PTR_ERR(mdata_shm);
> > + }
> > +
> > + mdata_buf = tee_shm_get_va(mdata_shm, 0);
> > + if (IS_ERR(mdata_buf)) {
> > + dev_err(dev, "mdata_buf get VA failed\n");
> > + tee_shm_free(mdata_shm);
> > + return PTR_ERR(mdata_buf);
> > + }
> > + memcpy(mdata_buf, metadata, size);
> > +
> > + param[1].u.memref.shm = mdata_shm;
> > + param[1].u.memref.size = size;
> > +
> > + ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
> > + if (ret < 0 || inv_arg.ret != 0) {
> > + dev_err(dev, "PAS init image failed, pas_id: %d, ret: %d, err: 0x%x\n",
> > + pas_id, ret, inv_arg.ret);
> > + tee_shm_free(mdata_shm);
> > + return ret ?: -EINVAL;
> > + }
> > +
> > + if (ctx)
> > + ctx->ptr = (void *)mdata_shm;
> > + else
> > + tee_shm_free(mdata_shm);
> > +
> > + return ret;
> > +}
> > +
> > +static int qcom_pas_tee_mem_setup(struct device *dev, u32 pas_id,
> > + phys_addr_t addr, phys_addr_t size)
> > +{
> > + struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
> > + struct tee_ioctl_invoke_arg inv_arg = {
> > + .func = TA_QCOM_PAS_MEM_SETUP,
> > + .session = data->session_id,
> > + .num_params = TEE_NUM_PARAMS
> > + };
> > + struct tee_param param[4] = {
> > + [0] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
> > + .u.value.a = pas_id,
> > + .u.value.b = size,
> > + },
> > + [1] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
> > + .u.value.a = lower_32_bits(addr),
> > + .u.value.b = upper_32_bits(addr),
> > + }
> > + };
> > + int ret;
> > +
> > + ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
> > + if (ret < 0 || inv_arg.ret != 0) {
> > + dev_err(dev, "PAS mem setup failed, pas_id: %d, ret: %d, err: 0x%x\n",
> > + pas_id, ret, inv_arg.ret);
> > + return ret ?: -EINVAL;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +DEFINE_FREE(shm_free, struct tee_shm *, tee_shm_free(_T))
> > +
> > +static void *qcom_pas_tee_get_rsc_table(struct device *dev,
> > + struct qcom_pas_context *ctx,
> > + void *input_rt, size_t input_rt_size,
> > + size_t *output_rt_size)
> > +{
> > + struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
> > + struct tee_ioctl_invoke_arg inv_arg = {
> > + .func = TA_QCOM_PAS_GET_RESOURCE_TABLE,
> > + .session = data->session_id,
> > + .num_params = TEE_NUM_PARAMS
> > + };
> > + struct tee_param param[4] = {
> > + [0] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
> > + .u.value.a = ctx->pas_id,
> > + },
> > + [1] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT,
> > + .u.memref.size = input_rt_size,
> > + }
> > + };
> > + void *rt_buf = NULL;
> > + int ret;
> > +
> > + ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
> > + if (ret < 0 || inv_arg.ret != 0) {
> > + dev_err(dev, "PAS get RT failed, pas_id: %d, ret: %d, err: 0x%x\n",
> > + ctx->pas_id, ret, inv_arg.ret);
> > + return ret ? ERR_PTR(ret) : ERR_PTR(-EINVAL);
> > + }
> > +
> > + if (param[1].u.memref.size) {
> > + struct tee_shm *rt_shm __free(shm_free) =
> > + tee_shm_alloc_kernel_buf(data->ctx,
> > + param[1].u.memref.size);
> > + void *rt_shm_va;
> > +
> > + if (IS_ERR(rt_shm)) {
> > + dev_err(dev, "rt_shm allocation failed\n");
> > + return rt_shm;
> > + }
> > +
> > + rt_shm_va = tee_shm_get_va(rt_shm, 0);
> > + if (IS_ERR(rt_shm_va)) {
> > + dev_err(dev, "rt_shm get VA failed\n");
> > + return ERR_CAST(rt_shm_va);
> > + }
> > + memcpy(rt_shm_va, input_rt, input_rt_size);
>
> It is very obvious that every existing user will pass NULL as input_rt
> and 0 as input_rt_size.
>
> Are you not getting NULL pointer on this input_rt ? Ok, you may be not
> getting because, input_rt_size == 0.
>
> I hope, your backend implementation checks for this num == 0 and then
> ignore input rt.
rt_shm_va is an INOUT buffer with OP-TEE, if Linux has any RT
information to pass then it can pass in this buffer. For current targets
nothing is provided by Linux and rt_shm_va will only get populated by
OP-TEE with RT information to be consumed by Linux for SMMU
configuration.
The total size of rt_shm_va buffer is sum of input RT and output RT
sizes. This framework is supported by GP TEE APIs to avoid double memory
buffer usage.
>
> > +
> > + param[1].u.memref.shm = rt_shm;
> > + ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
> > + if (ret < 0 || inv_arg.ret != 0) {
> > + dev_err(dev, "PAS get RT failed, pas_id: %d, ret: %d, err: 0x%x\n",
> > + ctx->pas_id, ret, inv_arg.ret);
> > + return ret ? ERR_PTR(ret) : ERR_PTR(-EINVAL);
> > + }
> > +
> > + if (param[1].u.memref.size) {
> > + *output_rt_size = param[1].u.memref.size;
> > + rt_buf = kmemdup(rt_shm_va, *output_rt_size, GFP_KERNEL);
> > + if (!rt_buf)
> > + return ERR_PTR(-ENOMEM);
> > + }
> > + }
> > +
> > + return rt_buf;
> > +}
> > +
> > +static int __qcom_pas_tee_auth_and_reset(struct device *dev, u32 pas_id,
> > + phys_addr_t mem_phys, size_t mem_size)
> > +{
> > + struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
> > + struct tee_ioctl_invoke_arg inv_arg = {
> > + .func = TA_QCOM_PAS_AUTH_AND_RESET,
> > + .session = data->session_id,
> > + .num_params = TEE_NUM_PARAMS
> > + };
> > + struct tee_param param[4] = {
> > + [0] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
> > + .u.value.a = pas_id,
> > + .u.value.b = mem_size,
> > + },
> > + [1] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
> > + .u.value.a = lower_32_bits(mem_phys),
> > + .u.value.b = upper_32_bits(mem_phys),
> > + },
> > + /* Reserved for fw memory space to be shared or lent */
> > + [2] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT,
> > + }
> > + };
> > + int ret;
> > +
> > + ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
> > + if (ret < 0 || inv_arg.ret != 0) {
> > + dev_err(dev, "PAS auth reset failed, pas_id: %d, ret: %d, err: 0x%x\n",
> > + pas_id, ret, inv_arg.ret);
> > + return ret ?: -EINVAL;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static int qcom_pas_tee_auth_and_reset(struct device *dev, u32 pas_id)
> > +{
> > + return __qcom_pas_tee_auth_and_reset(dev, pas_id, 0, 0);
> > +}
> > +
> > +static int qcom_pas_tee_prepare_and_auth_reset(struct device *dev,
> > + struct qcom_pas_context *ctx)
> > +{
> > + return __qcom_pas_tee_auth_and_reset(dev, ctx->pas_id, ctx->mem_phys,
> > + ctx->mem_size);
> > +}
> > +
> > +static int qcom_pas_tee_set_remote_state(struct device *dev, u32 state,
> > + u32 pas_id)
> > +{
> > + struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
> > + struct tee_ioctl_invoke_arg inv_arg = {
> > + .func = TA_QCOM_PAS_SET_REMOTE_STATE,
> > + .session = data->session_id,
> > + .num_params = TEE_NUM_PARAMS
> > + };
> > + struct tee_param param[4] = {
> > + [0] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
> > + .u.value.a = pas_id,
> > + .u.value.b = state,
> > + }
> > + };
> > + int ret;
> > +
> > + ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
> > + if (ret < 0 || inv_arg.ret != 0) {
> > + dev_err(dev, "PAS shutdown failed, pas_id: %d, ret: %d, err: 0x%x\n",
> > + pas_id, ret, inv_arg.ret);
>
> should be "PAS set remote state failed .."
Ack.
>
> > + return ret ?: -EINVAL;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static int qcom_pas_tee_shutdown(struct device *dev, u32 pas_id)
> > +{
> > + struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
> > + struct tee_ioctl_invoke_arg inv_arg = {
> > + .func = TA_QCOM_PAS_SHUTDOWN,
> > + .session = data->session_id,
> > + .num_params = TEE_NUM_PARAMS
> > + };
> > + struct tee_param param[4] = {
> > + [0] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
> > + .u.value.a = pas_id
> > + }
> > + };
> > + int ret;
> > +
> > + ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
> > + if (ret < 0 || inv_arg.ret != 0) {
> > + dev_err(dev, "PAS shutdown failed, pas_id: %d, ret: %d, err: 0x%x\n",
> > + pas_id, ret, inv_arg.ret);
> > + return ret ?: -EINVAL;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static void qcom_pas_tee_metadata_release(struct device *dev,
> > + struct qcom_pas_context *ctx)
> > +{
> > + struct tee_shm *mdata_shm = ctx->ptr;
> > +
> > + tee_shm_free(mdata_shm);
> > +}
> > +
> > +static struct qcom_pas_ops qcom_pas_ops_tee = {
> > + .drv_name = "qcom-pas-tee",
> > + .supported = qcom_pas_tee_supported,
> > + .init_image = qcom_pas_tee_init_image,
> > + .mem_setup = qcom_pas_tee_mem_setup,
> > + .get_rsc_table = qcom_pas_tee_get_rsc_table,
> > + .auth_and_reset = qcom_pas_tee_auth_and_reset,
> > + .prepare_and_auth_reset = qcom_pas_tee_prepare_and_auth_reset,
> > + .set_remote_state = qcom_pas_tee_set_remote_state,
> > + .shutdown = qcom_pas_tee_shutdown,
> > + .metadata_release = qcom_pas_tee_metadata_release,
> > +};
> > +
> > +static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data)
> > +{
> > + return ver->impl_id == TEE_IMPL_ID_OPTEE;
> > +}
> > +
> > +static int qcom_pas_tee_probe(struct tee_client_device *pas_dev)
> > +{
> > + struct device *dev = &pas_dev->dev;
> > + struct qcom_pas_tee_private *data;
> > + struct tee_ioctl_open_session_arg sess_arg = {
> > + .clnt_login = TEE_IOCTL_LOGIN_REE_KERNEL
> > + };
> > + int ret, err = -ENODEV;
>
> Most people prefer one line per variable..
Ack.
>
>
> > +
> > + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
> > + if (!data)
> > + return -ENOMEM;
> > +
> > + data->ctx = tee_client_open_context(NULL, optee_ctx_match, NULL, NULL);
> > + if (IS_ERR(data->ctx))
> > + return -ENODEV;
> > +
> > + export_uuid(sess_arg.uuid, &pas_dev->id.uuid);
> > + ret = tee_client_open_session(data->ctx, &sess_arg, NULL);
> > + if (ret < 0 || sess_arg.ret != 0) {
> > + dev_err(dev, "tee_client_open_session failed, ret: %d, err: 0x%x\n",
> > + ret, sess_arg.ret);
> > + err = ret ?: -EINVAL;
>
> Only user of goto, we can close the context and return from here and
> 'err' not used.
Ack.
>
> tee_client_close_context(data->ctx);
> return ret ?: -EINVAL;
>
> > + goto out_ctx;
>
>
> > + }
> > +
> > + data->session_id = sess_arg.session;
> > + dev_set_drvdata(dev, data);
> > + qcom_pas_ops_tee.dev = dev;
> > + qcom_pas_ops_register(&qcom_pas_ops_tee);
> > +
> > + return ret;
> > +out_ctx:
> > + tee_client_close_context(data->ctx);
>
> Return after two line does not look nice.
>
> > +
> > + return err;
> > +}
> > +
> > +static void qcom_pas_tee_remove(struct tee_client_device *pas_dev)
> > +{
> > + struct device *dev = &pas_dev->dev;
> > + struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
> > +
> > + qcom_pas_ops_unregister();
> > + tee_client_close_session(data->ctx, data->session_id);
> > + tee_client_close_context(data->ctx);
> > +}
> > +
> > +static const struct tee_client_device_id qcom_pas_tee_id_table[] = {
> > + {UUID_INIT(0xcff7d191, 0x7ca0, 0x4784,
> > + 0xaf, 0x13, 0x48, 0x22, 0x3b, 0x9a, 0x4f, 0xbe)},
> > + {}
> > +};
> > +MODULE_DEVICE_TABLE(tee, qcom_pas_tee_id_table);
> > +
> > +static struct tee_client_driver optee_pas_tee_driver = {
> > + .probe = qcom_pas_tee_probe,
> > + .remove = qcom_pas_tee_remove,
> > + .id_table = qcom_pas_tee_id_table,
> > + .driver = {
> > + .name = "qcom-pas-tee",
> > + },
> > +};
> > +
> > +module_tee_client_driver(optee_pas_tee_driver);
> > +
> > +MODULE_LICENSE("GPL");
> > +MODULE_DESCRIPTION("Qualcomm PAS TEE driver");
> > --
> > 2.51.0
> >
>
>
> With above change,
>
> Reviewed-by: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com>
>
Thanks.
-Sumit
^ permalink raw reply
* Re: [PATCH v4 05/15] remoteproc: qcom_q6v5_pas: Switch over to generic PAS TZ APIs
From: Sumit Garg @ 2026-05-01 13:57 UTC (permalink / raw)
To: Mukesh Ojha
Cc: andersson, konradybcio, linux-arm-msm, devicetree, dri-devel,
freedreno, linux-media, netdev, linux-wireless, ath12k,
linux-remoteproc, robh, krzk+dt, conor+dt, robin.clark, sean,
akhilpo, lumag, abhinav.kumar, jesszhan0024, marijn.suijten,
airlied, simona, vikash.garodia, dikshita.agarwal, bod, mchehab,
elder, andrew+netdev, davem, edumazet, kuba, pabeni, jjohnson,
mathieu.poirier, trilokkumar.soni, pavan.kondeti, jorge.ramirez,
tonyh, vignesh.viswanathan, srinivas.kandagatla, amirreza.zarrabi,
jens.wiklander, op-tee, apurupa, skare, linux-kernel, Sumit Garg
In-Reply-To: <20260429132512.aki4vqkhpil7awc3@hu-mojha-hyd.qualcomm.com>
On Wed, Apr 29, 2026 at 06:55:12PM +0530, Mukesh Ojha wrote:
> On Mon, Apr 27, 2026 at 03:25:53PM +0530, Sumit Garg wrote:
> > From: Sumit Garg <sumit.garg@oss.qualcomm.com>
> >
> > Switch qcom_q6v5_pas client driver over to generic PAS TZ APIs. Generic PAS
> > TZ service allows to support multiple TZ implementation backends like QTEE
> > based SCM PAS service, OP-TEE based PAS service and any further future TZ
> > backend service.
> >
> > Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
> > ---
> > drivers/remoteproc/qcom_q6v5_pas.c | 51 +++++++++++++++---------------
> > 1 file changed, 26 insertions(+), 25 deletions(-)
> >
> > diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c
> > index da27d1d3c9da..847249c28c1b 100644
> > --- a/drivers/remoteproc/qcom_q6v5_pas.c
> > +++ b/drivers/remoteproc/qcom_q6v5_pas.c
> > @@ -20,6 +20,7 @@
> > #include <linux/platform_device.h>
> > #include <linux/pm_domain.h>
> > #include <linux/pm_runtime.h>
> > +#include <linux/firmware/qcom/qcom_pas.h>
> > #include <linux/firmware/qcom/qcom_scm.h>
>
> Can you check do we really need this header ?
Yeah it's needed for qcom_scm_assign_mem() API.
>
> > #include <linux/regulator/consumer.h>
> > #include <linux/remoteproc.h>
> > @@ -118,8 +119,8 @@ struct qcom_pas {
> > struct qcom_rproc_ssr ssr_subdev;
> > struct qcom_sysmon *sysmon;
> >
> > - struct qcom_scm_pas_context *pas_ctx;
> > - struct qcom_scm_pas_context *dtb_pas_ctx;
> > + struct qcom_pas_context *pas_ctx;
> > + struct qcom_pas_context *dtb_pas_ctx;
> > };
> >
> > static void qcom_pas_segment_dump(struct rproc *rproc,
> > @@ -196,7 +197,7 @@ static int qcom_pas_shutdown_poll_decrypt(struct qcom_pas *pas)
> >
> > do {
> > msleep(QCOM_PAS_DECRYPT_SHUTDOWN_DELAY_MS);
> > - ret = qcom_scm_pas_shutdown(pas->pas_id);
> > + ret = qcom_pas_shutdown(pas->pas_id);
> > } while (ret == -EINVAL && --retry_num);
> >
> > return ret;
> > @@ -212,9 +213,9 @@ static int qcom_pas_unprepare(struct rproc *rproc)
> > * auth_and_reset() was successful, but in other cases clean it up
> > * here.
> > */
> > - qcom_scm_pas_metadata_release(pas->pas_ctx);
> > + qcom_pas_metadata_release(pas->pas_ctx);
> > if (pas->dtb_pas_id)
> > - qcom_scm_pas_metadata_release(pas->dtb_pas_ctx);
> > + qcom_pas_metadata_release(pas->dtb_pas_ctx);
> >
> > return 0;
> > }
> > @@ -228,9 +229,9 @@ static int qcom_pas_load(struct rproc *rproc, const struct firmware *fw)
> > pas->firmware = fw;
> >
> > if (pas->lite_pas_id)
> > - qcom_scm_pas_shutdown(pas->lite_pas_id);
> > + qcom_pas_shutdown(pas->lite_pas_id);
> > if (pas->lite_dtb_pas_id)
> > - qcom_scm_pas_shutdown(pas->lite_dtb_pas_id);
> > + qcom_pas_shutdown(pas->lite_dtb_pas_id);
> >
> > if (pas->dtb_pas_id) {
> > ret = request_firmware(&pas->dtb_firmware, pas->dtb_firmware_name, pas->dev);
> > @@ -250,7 +251,7 @@ static int qcom_pas_load(struct rproc *rproc, const struct firmware *fw)
> > return 0;
> >
> > release_dtb_metadata:
> > - qcom_scm_pas_metadata_release(pas->dtb_pas_ctx);
> > + qcom_pas_metadata_release(pas->dtb_pas_ctx);
> > release_firmware(pas->dtb_firmware);
> >
> > return ret;
> > @@ -310,7 +311,7 @@ static int qcom_pas_start(struct rproc *rproc)
> > if (ret)
> > goto disable_px_supply;
> >
> > - ret = qcom_scm_pas_prepare_and_auth_reset(pas->dtb_pas_ctx);
> > + ret = qcom_pas_prepare_and_auth_reset(pas->dtb_pas_ctx);
> > if (ret) {
> > dev_err(pas->dev,
> > "failed to authenticate dtb image and release reset\n");
> > @@ -329,7 +330,7 @@ static int qcom_pas_start(struct rproc *rproc)
> > if (ret)
> > goto release_pas_metadata;
> >
> > - ret = qcom_scm_pas_prepare_and_auth_reset(pas->pas_ctx);
> > + ret = qcom_pas_prepare_and_auth_reset(pas->pas_ctx);
> > if (ret) {
> > dev_err(pas->dev,
> > "failed to authenticate image and release reset\n");
> > @@ -339,13 +340,13 @@ static int qcom_pas_start(struct rproc *rproc)
> > ret = qcom_q6v5_wait_for_start(&pas->q6v5, msecs_to_jiffies(5000));
> > if (ret == -ETIMEDOUT) {
> > dev_err(pas->dev, "start timed out\n");
> > - qcom_scm_pas_shutdown(pas->pas_id);
> > + qcom_pas_shutdown(pas->pas_id);
> > goto unmap_carveout;
> > }
> >
> > - qcom_scm_pas_metadata_release(pas->pas_ctx);
> > + qcom_pas_metadata_release(pas->pas_ctx);
> > if (pas->dtb_pas_id)
> > - qcom_scm_pas_metadata_release(pas->dtb_pas_ctx);
> > + qcom_pas_metadata_release(pas->dtb_pas_ctx);
> >
> > /* firmware is used to pass reference from qcom_pas_start(), drop it now */
> > pas->firmware = NULL;
> > @@ -355,9 +356,9 @@ static int qcom_pas_start(struct rproc *rproc)
> > unmap_carveout:
> > qcom_pas_unmap_carveout(rproc, pas->mem_phys, pas->mem_size);
> > release_pas_metadata:
> > - qcom_scm_pas_metadata_release(pas->pas_ctx);
> > + qcom_pas_metadata_release(pas->pas_ctx);
> > if (pas->dtb_pas_id)
> > - qcom_scm_pas_metadata_release(pas->dtb_pas_ctx);
> > + qcom_pas_metadata_release(pas->dtb_pas_ctx);
> >
> > unmap_dtb_carveout:
> > if (pas->dtb_pas_id)
> > @@ -406,7 +407,7 @@ static int qcom_pas_stop(struct rproc *rproc)
> > if (ret == -ETIMEDOUT)
> > dev_err(pas->dev, "timed out on wait\n");
> >
> > - ret = qcom_scm_pas_shutdown(pas->pas_id);
> > + ret = qcom_pas_shutdown(pas->pas_id);
> > if (ret && pas->decrypt_shutdown)
> > ret = qcom_pas_shutdown_poll_decrypt(pas);
> >
> > @@ -414,7 +415,7 @@ static int qcom_pas_stop(struct rproc *rproc)
> > dev_err(pas->dev, "failed to shutdown: %d\n", ret);
> >
> > if (pas->dtb_pas_id) {
> > - ret = qcom_scm_pas_shutdown(pas->dtb_pas_id);
> > + ret = qcom_pas_shutdown(pas->dtb_pas_id);
> > if (ret)
> > dev_err(pas->dev, "failed to shutdown dtb: %d\n", ret);
> >
> > @@ -484,11 +485,11 @@ static int qcom_pas_parse_firmware(struct rproc *rproc, const struct firmware *f
> > *
> > * Here, we call rproc_elf_load_rsc_table() to check firmware binary has resources
> > * or not and if it is not having then we pass NULL and zero as input resource
> > - * table pointer and size respectively to the argument of qcom_scm_pas_get_rsc_table()
> > + * table pointer and size respectively to the argument of qcom_pas_get_rsc_table()
> > * and this is even true for Qualcomm remote processor who does follow remoteproc
> > * framework.
> > */
> > - output_rt = qcom_scm_pas_get_rsc_table(pas->pas_ctx, table, table_sz, &output_rt_size);
> > + output_rt = qcom_pas_get_rsc_table(pas->pas_ctx, table, table_sz, &output_rt_size);
> > ret = IS_ERR(output_rt) ? PTR_ERR(output_rt) : 0;
> > if (ret) {
> > dev_err(pas->dev, "Error in getting resource table: %d\n", ret);
> > @@ -746,7 +747,7 @@ static int qcom_pas_probe(struct platform_device *pdev)
> > if (!desc)
> > return -EINVAL;
> >
> > - if (!qcom_scm_is_available())
> > + if (!qcom_pas_is_available())
> > return -EPROBE_DEFER;
> >
> > fw_name = desc->firmware_name;
> > @@ -838,16 +839,16 @@ static int qcom_pas_probe(struct platform_device *pdev)
> >
> > qcom_add_ssr_subdev(rproc, &pas->ssr_subdev, desc->ssr_name);
> >
> > - pas->pas_ctx = devm_qcom_scm_pas_context_alloc(pas->dev, pas->pas_id,
> > - pas->mem_phys, pas->mem_size);
> > + pas->pas_ctx = devm_qcom_pas_context_alloc(pas->dev, pas->pas_id,
> > + pas->mem_phys, pas->mem_size);
> > if (IS_ERR(pas->pas_ctx)) {
> > ret = PTR_ERR(pas->pas_ctx);
> > goto remove_ssr_sysmon;
> > }
> >
> > - pas->dtb_pas_ctx = devm_qcom_scm_pas_context_alloc(pas->dev, pas->dtb_pas_id,
> > - pas->dtb_mem_phys,
> > - pas->dtb_mem_size);
> > + pas->dtb_pas_ctx = devm_qcom_pas_context_alloc(pas->dev, pas->dtb_pas_id,
> > + pas->dtb_mem_phys,
> > + pas->dtb_mem_size);
> > if (IS_ERR(pas->dtb_pas_ctx)) {
> > ret = PTR_ERR(pas->dtb_pas_ctx);
> > goto remove_ssr_sysmon;
> > --
> > 2.51.0
> >
>
> Reviewed-by: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com>
Thanks.
-Sumit
^ permalink raw reply
* Re: [PATCH v4 06/15] remoteproc: qcom_q6v5_mss: Switch to generic PAS TZ APIs
From: Sumit Garg @ 2026-05-01 13:58 UTC (permalink / raw)
To: Mukesh Ojha
Cc: andersson, konradybcio, linux-arm-msm, devicetree, dri-devel,
freedreno, linux-media, netdev, linux-wireless, ath12k,
linux-remoteproc, robh, krzk+dt, conor+dt, robin.clark, sean,
akhilpo, lumag, abhinav.kumar, jesszhan0024, marijn.suijten,
airlied, simona, vikash.garodia, dikshita.agarwal, bod, mchehab,
elder, andrew+netdev, davem, edumazet, kuba, pabeni, jjohnson,
mathieu.poirier, trilokkumar.soni, pavan.kondeti, jorge.ramirez,
tonyh, vignesh.viswanathan, srinivas.kandagatla, amirreza.zarrabi,
jens.wiklander, op-tee, apurupa, skare, linux-kernel, Sumit Garg
In-Reply-To: <20260429133157.lopr6n5vaik36466@hu-mojha-hyd.qualcomm.com>
On Wed, Apr 29, 2026 at 07:01:57PM +0530, Mukesh Ojha wrote:
> On Mon, Apr 27, 2026 at 03:25:54PM +0530, Sumit Garg wrote:
> > From: Sumit Garg <sumit.garg@oss.qualcomm.com>
> >
> > Switch qcom_q6v5_mss client driver over to generic PAS TZ APIs. Generic PAS
> > TZ service allows to support multiple TZ implementation backends like QTEE
> > based SCM PAS service, OP-TEE based PAS service and any further future TZ
> > backend service.
> >
> > Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
> > ---
> > drivers/remoteproc/qcom_q6v5_mss.c | 5 +++--
> > 1 file changed, 3 insertions(+), 2 deletions(-)
> >
> > diff --git a/drivers/remoteproc/qcom_q6v5_mss.c b/drivers/remoteproc/qcom_q6v5_mss.c
> > index ae78f5c7c1b6..96888007faa8 100644
> > --- a/drivers/remoteproc/qcom_q6v5_mss.c
> > +++ b/drivers/remoteproc/qcom_q6v5_mss.c
> > @@ -34,6 +34,7 @@
> > #include "qcom_pil_info.h"
> > #include "qcom_q6v5.h"
> >
> > +#include <linux/firmware/qcom/qcom_pas.h>
> > #include <linux/firmware/qcom/qcom_scm.h>
>
> same here., whether it is needed now ?
Ditto, needed for qcom_scm_assign_mem().
>
> >
> > #define MPSS_CRASH_REASON_SMEM 421
> > @@ -1480,7 +1481,7 @@ static int q6v5_mpss_load(struct q6v5 *qproc)
> > }
> >
> > if (qproc->need_pas_mem_setup) {
> > - ret = qcom_scm_pas_mem_setup(MPSS_PAS_ID, qproc->mpss_phys, qproc->mpss_size);
> > + ret = qcom_pas_mem_setup(MPSS_PAS_ID, qproc->mpss_phys, qproc->mpss_size);
> > if (ret) {
> > dev_err(qproc->dev,
> > "setting up mpss memory failed: %d\n", ret);
> > @@ -2077,7 +2078,7 @@ static int q6v5_probe(struct platform_device *pdev)
> > if (!desc)
> > return -EINVAL;
> >
> > - if (desc->need_mem_protection && !qcom_scm_is_available())
> > + if (desc->need_mem_protection && !qcom_pas_is_available())
> > return -EPROBE_DEFER;
> >
> > mba_image = desc->hexagon_mba_image;
>
> Reviewed-by: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com>
Thanks.
-Sumit
^ permalink raw reply
* [PATCH net-next] net/sched: add qstats_cpu_drop_inc() helper
From: Eric Dumazet @ 2026-05-01 13:59 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Jamal Hadi Salim, Jiri Pirko, netdev, eric.dumazet,
Eric Dumazet
1) Using this_cpu_inc() is better than going through this_cpu_ptr():
- Single instruction on x86.
- Store tearing prevention.
2) Change tcf_action_update_stats() to use this_cpu_add().
3) Add WRITE_ONCE() to __qdisc_qstats_drop() and qstats_drop_inc()
in preparation for lockless "tc qdisc show".
$ scripts/bloat-o-meter -t vmlinux.old vmlinux.new
add/remove: 0/0 grow/shrink: 3/17 up/down: 72/-216 (-144)
Function old new delta
dualpi2_enqueue_skb 462 511 +49
tcf_ife_act 1061 1077 +16
taprio_enqueue 613 620 +7
codel_qdisc_enqueue 149 143 -6
tcf_vlan_act 684 676 -8
tcf_skbedit_act 626 618 -8
tcf_police_act 725 717 -8
tcf_mpls_act 1297 1289 -8
tcf_gate_act 310 302 -8
tcf_gact_act 222 214 -8
tcf_csum_act 2438 2430 -8
tcf_bpf_act 709 701 -8
tcf_action_update_stats 124 115 -9
pie_qdisc_enqueue 865 856 -9
pfifo_enqueue 116 107 -9
choke_enqueue 2069 2059 -10
plug_enqueue 139 128 -11
bfifo_enqueue 121 110 -11
tcf_nat_act 1501 1489 -12
gred_enqueue 1743 1668 -75
Total: Before=24388609, After=24388465, chg -0.00%
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Jamal Hadi Salim <jhs@mojatatu.com>
---
include/net/act_api.h | 2 +-
include/net/sch_generic.h | 9 +++++++--
net/sched/act_api.c | 2 +-
net/sched/act_bpf.c | 2 +-
net/sched/act_ife.c | 8 ++++----
net/sched/act_mpls.c | 2 +-
net/sched/act_police.c | 2 +-
net/sched/act_skbedit.c | 2 +-
net/sched/sch_cake.c | 2 +-
net/sched/sch_fq_codel.c | 2 +-
net/sched/sch_gred.c | 2 +-
11 files changed, 20 insertions(+), 15 deletions(-)
diff --git a/include/net/act_api.h b/include/net/act_api.h
index 2ec4ef9a5d0c8e9110f92f135cc3c31a38af0479..167435c5615e09f491a05d01ec86b0c9f9f4fd5b 100644
--- a/include/net/act_api.h
+++ b/include/net/act_api.h
@@ -241,7 +241,7 @@ static inline void tcf_action_update_bstats(struct tc_action *a,
static inline void tcf_action_inc_drop_qstats(struct tc_action *a)
{
if (likely(a->cpu_qstats)) {
- qstats_drop_inc(this_cpu_ptr(a->cpu_qstats));
+ qstats_cpu_drop_inc(a->cpu_qstats);
return;
}
atomic_inc(&a->tcfa_drops);
diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h
index cbfe9ed435fd77422a181074980e5779190ab9c3..ccfabfac674ef8617faeabd2fcb15daf8a1ea17f 100644
--- a/include/net/sch_generic.h
+++ b/include/net/sch_generic.h
@@ -996,12 +996,17 @@ static inline void qdisc_qstats_cpu_requeues_inc(struct Qdisc *sch)
static inline void __qdisc_qstats_drop(struct Qdisc *sch, int count)
{
- sch->qstats.drops += count;
+ WRITE_ONCE(sch->qstats.drops, sch->qstats.drops + count);
}
static inline void qstats_drop_inc(struct gnet_stats_queue *qstats)
{
- qstats->drops++;
+ WRITE_ONCE(qstats->drops, qstats->drops + 1);
+}
+
+static inline void qstats_cpu_drop_inc(struct gnet_stats_queue __percpu *qstats)
+{
+ this_cpu_inc(qstats->drops);
}
static inline void qstats_cpu_overlimit_inc(struct gnet_stats_queue __percpu *qstats)
diff --git a/net/sched/act_api.c b/net/sched/act_api.c
index 332fd9695e54a1fc63bb869c28cacf5f2ed14971..551992683d9e69c247b8d9c613a69e2a897a1e79 100644
--- a/net/sched/act_api.c
+++ b/net/sched/act_api.c
@@ -1578,7 +1578,7 @@ void tcf_action_update_stats(struct tc_action *a, u64 bytes, u64 packets,
if (a->cpu_bstats) {
_bstats_update(this_cpu_ptr(a->cpu_bstats), bytes, packets);
- this_cpu_ptr(a->cpu_qstats)->drops += drops;
+ this_cpu_add(a->cpu_qstats->drops, drops);
if (hw)
_bstats_update(this_cpu_ptr(a->cpu_bstats_hw),
diff --git a/net/sched/act_bpf.c b/net/sched/act_bpf.c
index c2b5bc19e09118857d1ef3c4aed566b8225f2e9a..58a074651176730fd1bd370ba8420dfbed0d4e9c 100644
--- a/net/sched/act_bpf.c
+++ b/net/sched/act_bpf.c
@@ -76,7 +76,7 @@ TC_INDIRECT_SCOPE int tcf_bpf_act(struct sk_buff *skb,
break;
case TC_ACT_SHOT:
action = filter_res;
- qstats_drop_inc(this_cpu_ptr(prog->common.cpu_qstats));
+ qstats_cpu_drop_inc(prog->common.cpu_qstats);
break;
case TC_ACT_UNSPEC:
action = prog->tcf_action;
diff --git a/net/sched/act_ife.c b/net/sched/act_ife.c
index e1b825e14900d6f46bbfd1b7f72ab6cd554d8a73..065228026c58eb0f8ff3b3a08758e4ef0d6ea708 100644
--- a/net/sched/act_ife.c
+++ b/net/sched/act_ife.c
@@ -727,7 +727,7 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a,
tlv_data = ife_decode(skb, &metalen);
if (unlikely(!tlv_data)) {
- qstats_drop_inc(this_cpu_ptr(ife->common.cpu_qstats));
+ qstats_cpu_drop_inc(ife->common.cpu_qstats);
return TC_ACT_SHOT;
}
@@ -740,7 +740,7 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a,
curr_data = ife_tlv_meta_decode(tlv_data, ifehdr_end, &mtype,
&dlen, NULL);
if (!curr_data) {
- qstats_drop_inc(this_cpu_ptr(ife->common.cpu_qstats));
+ qstats_cpu_drop_inc(ife->common.cpu_qstats);
return TC_ACT_SHOT;
}
@@ -755,7 +755,7 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a,
}
if (WARN_ON(tlv_data != ifehdr_end)) {
- qstats_drop_inc(this_cpu_ptr(ife->common.cpu_qstats));
+ qstats_cpu_drop_inc(ife->common.cpu_qstats);
return TC_ACT_SHOT;
}
@@ -821,7 +821,7 @@ static int tcf_ife_encode(struct sk_buff *skb, const struct tc_action *a,
* so lets be conservative.. */
if ((action == TC_ACT_SHOT) || exceed_mtu) {
drop:
- qstats_drop_inc(this_cpu_ptr(ife->common.cpu_qstats));
+ qstats_cpu_drop_inc(ife->common.cpu_qstats);
return TC_ACT_SHOT;
}
diff --git a/net/sched/act_mpls.c b/net/sched/act_mpls.c
index 1abfaf9d99f1fce0fe7cafa2a9e35c80a3969ce7..4ea8b2e08c3a4dddfe1670af72a5d487a5219f5e 100644
--- a/net/sched/act_mpls.c
+++ b/net/sched/act_mpls.c
@@ -123,7 +123,7 @@ TC_INDIRECT_SCOPE int tcf_mpls_act(struct sk_buff *skb,
return p->action;
drop:
- qstats_drop_inc(this_cpu_ptr(m->common.cpu_qstats));
+ qstats_cpu_drop_inc(m->common.cpu_qstats);
return TC_ACT_SHOT;
}
diff --git a/net/sched/act_police.c b/net/sched/act_police.c
index 8060f43e4d11c0a26e1475db06b76426f50c5975..b16468a98c55e32260e8d4cb1fe3d771fca65120 100644
--- a/net/sched/act_police.c
+++ b/net/sched/act_police.c
@@ -310,7 +310,7 @@ TC_INDIRECT_SCOPE int tcf_police_act(struct sk_buff *skb,
qstats_cpu_overlimit_inc(police->common.cpu_qstats);
inc_drops:
if (ret == TC_ACT_SHOT)
- qstats_drop_inc(this_cpu_ptr(police->common.cpu_qstats));
+ qstats_cpu_drop_inc(police->common.cpu_qstats);
end:
return ret;
}
diff --git a/net/sched/act_skbedit.c b/net/sched/act_skbedit.c
index a778cdba9258c2c776ee5ba0751cca1b73c984df..bfec6b66841031cd566d0c2bdc3d120cec41e3e4 100644
--- a/net/sched/act_skbedit.c
+++ b/net/sched/act_skbedit.c
@@ -86,7 +86,7 @@ TC_INDIRECT_SCOPE int tcf_skbedit_act(struct sk_buff *skb,
return params->action;
err:
- qstats_drop_inc(this_cpu_ptr(d->common.cpu_qstats));
+ qstats_cpu_drop_inc(d->common.cpu_qstats);
return TC_ACT_SHOT;
}
diff --git a/net/sched/sch_cake.c b/net/sched/sch_cake.c
index 13c6d1869a144738c52ffc462f06338bf8245fea..c779e72f153c93ae222ec5e57a9b859259730526 100644
--- a/net/sched/sch_cake.c
+++ b/net/sched/sch_cake.c
@@ -1845,7 +1845,7 @@ static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch,
if (ack) {
WRITE_ONCE(b->ack_drops, b->ack_drops + 1);
- sch->qstats.drops++;
+ qdisc_qstats_drop(sch);
ack_pkt_len = qdisc_pkt_len(ack);
WRITE_ONCE(b->bytes, b->bytes + ack_pkt_len);
q->buffer_used += skb->truesize - ack->truesize;
diff --git a/net/sched/sch_fq_codel.c b/net/sched/sch_fq_codel.c
index 0664b2f2d6f28041e5250a44fc92311116ae0cf1..8ba722faf7e3c37cabfecd58fd4937dd3c568444 100644
--- a/net/sched/sch_fq_codel.c
+++ b/net/sched/sch_fq_codel.c
@@ -176,7 +176,7 @@ static unsigned int fq_codel_drop(struct Qdisc *sch, unsigned int max_packets,
flow->cvars.count += i;
q->backlogs[idx] -= len;
q->memory_usage -= mem;
- sch->qstats.drops += i;
+ __qdisc_qstats_drop(sch, i);
sch->qstats.backlog -= len;
sch->q.qlen -= i;
return idx;
diff --git a/net/sched/sch_gred.c b/net/sched/sch_gred.c
index 36d0cafac2063f3ba1133ad9b8fab08ce4468550..8ae65572162c188cca5ac8f030dc6f2054a7fcd0 100644
--- a/net/sched/sch_gred.c
+++ b/net/sched/sch_gred.c
@@ -389,7 +389,7 @@ static int gred_offload_dump_stats(struct Qdisc *sch)
packets += u64_stats_read(&hw_stats->stats.bstats[i].packets);
sch->qstats.qlen += hw_stats->stats.qstats[i].qlen;
sch->qstats.backlog += hw_stats->stats.qstats[i].backlog;
- sch->qstats.drops += hw_stats->stats.qstats[i].drops;
+ __qdisc_qstats_drop(sch, hw_stats->stats.qstats[i].drops);
sch->qstats.requeues += hw_stats->stats.qstats[i].requeues;
sch->qstats.overlimits += hw_stats->stats.qstats[i].overlimits;
}
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* Re: [PATCH v4 07/15] soc: qcom: mdtloader: Switch to generic PAS TZ APIs
From: Sumit Garg @ 2026-05-01 13:59 UTC (permalink / raw)
To: Mukesh Ojha
Cc: andersson, konradybcio, linux-arm-msm, devicetree, dri-devel,
freedreno, linux-media, netdev, linux-wireless, ath12k,
linux-remoteproc, robh, krzk+dt, conor+dt, robin.clark, sean,
akhilpo, lumag, abhinav.kumar, jesszhan0024, marijn.suijten,
airlied, simona, vikash.garodia, dikshita.agarwal, bod, mchehab,
elder, andrew+netdev, davem, edumazet, kuba, pabeni, jjohnson,
mathieu.poirier, trilokkumar.soni, pavan.kondeti, jorge.ramirez,
tonyh, vignesh.viswanathan, srinivas.kandagatla, amirreza.zarrabi,
jens.wiklander, op-tee, apurupa, skare, linux-kernel, Sumit Garg
In-Reply-To: <20260429134208.pqo67sh3jkvsa5ka@hu-mojha-hyd.qualcomm.com>
On Wed, Apr 29, 2026 at 07:12:08PM +0530, Mukesh Ojha wrote:
> On Mon, Apr 27, 2026 at 03:25:55PM +0530, Sumit Garg wrote:
> > From: Sumit Garg <sumit.garg@oss.qualcomm.com>
> >
> > Switch mdtloader client driver over to generic PAS TZ APIs. Generic PAS
> > TZ service allows to support multiple TZ implementation backends like QTEE
> > based SCM PAS service, OP-TEE based PAS service and any further future TZ
> > backend service.
> >
> > Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
> > ---
> > drivers/soc/qcom/mdt_loader.c | 12 ++++++------
> > include/linux/soc/qcom/mdt_loader.h | 6 +++---
> > 2 files changed, 9 insertions(+), 9 deletions(-)
> >
> > diff --git a/drivers/soc/qcom/mdt_loader.c b/drivers/soc/qcom/mdt_loader.c
> > index c004d444d698..fdde7eda538a 100644
> > --- a/drivers/soc/qcom/mdt_loader.c
> > +++ b/drivers/soc/qcom/mdt_loader.c
> > @@ -13,7 +13,7 @@
> > #include <linux/firmware.h>
> > #include <linux/kernel.h>
> > #include <linux/module.h>
> > -#include <linux/firmware/qcom/qcom_scm.h>
> > +#include <linux/firmware/qcom/qcom_pas.h>
> > #include <linux/sizes.h>
> > #include <linux/slab.h>
> > #include <linux/soc/qcom/mdt_loader.h>
> > @@ -229,7 +229,7 @@ EXPORT_SYMBOL_GPL(qcom_mdt_read_metadata);
> >
> > static int __qcom_mdt_pas_init(struct device *dev, const struct firmware *fw,
> > const char *fw_name, int pas_id, phys_addr_t mem_phys,
> > - struct qcom_scm_pas_context *ctx)
> > + struct qcom_pas_context *ctx)
> > {
> > const struct elf32_phdr *phdrs;
> > const struct elf32_phdr *phdr;
> > @@ -271,7 +271,7 @@ static int __qcom_mdt_pas_init(struct device *dev, const struct firmware *fw,
> > goto out;
> > }
> >
> > - ret = qcom_scm_pas_init_image(pas_id, metadata, metadata_len, ctx);
> > + ret = qcom_pas_init_image(pas_id, metadata, metadata_len, ctx);
> > kfree(metadata);
> > if (ret) {
> > /* Invalid firmware metadata */
> > @@ -280,7 +280,7 @@ static int __qcom_mdt_pas_init(struct device *dev, const struct firmware *fw,
> > }
> >
> > if (relocate) {
> > - ret = qcom_scm_pas_mem_setup(pas_id, mem_phys, max_addr - min_addr);
> > + ret = qcom_pas_mem_setup(pas_id, mem_phys, max_addr - min_addr);
> > if (ret) {
> > /* Unable to set up relocation */
> > dev_err(dev, "error %d setting up firmware %s\n", ret, fw_name);
> > @@ -472,7 +472,7 @@ EXPORT_SYMBOL_GPL(qcom_mdt_load);
> > * firmware segments (e.g., .bXX files). Authentication of the segments done
> > * by a separate call.
> > *
> > - * The PAS context must be initialized using qcom_scm_pas_context_init()
> > + * The PAS context must be initialized using qcom_pas_context_init()
>
> Should devm_qcom_pas_context_alloc() now
Ack.
>
> > * prior to invoking this function.
> > *
> > * @ctx: Pointer to the PAS (Peripheral Authentication Service) context
> > @@ -483,7 +483,7 @@ EXPORT_SYMBOL_GPL(qcom_mdt_load);
> > *
> > * Return: 0 on success or a negative error code on failure.
> > */
> > -int qcom_mdt_pas_load(struct qcom_scm_pas_context *ctx, const struct firmware *fw,
> > +int qcom_mdt_pas_load(struct qcom_pas_context *ctx, const struct firmware *fw,
> > const char *firmware, void *mem_region, phys_addr_t *reloc_base)
> > {
> > int ret;
> > diff --git a/include/linux/soc/qcom/mdt_loader.h b/include/linux/soc/qcom/mdt_loader.h
> > index 82372e0db0a1..142409555425 100644
> > --- a/include/linux/soc/qcom/mdt_loader.h
> > +++ b/include/linux/soc/qcom/mdt_loader.h
> > @@ -10,7 +10,7 @@
> >
> > struct device;
> > struct firmware;
> > -struct qcom_scm_pas_context;
> > +struct qcom_pas_context;
> >
> > #if IS_ENABLED(CONFIG_QCOM_MDT_LOADER)
> >
> > @@ -20,7 +20,7 @@ int qcom_mdt_load(struct device *dev, const struct firmware *fw,
> > phys_addr_t mem_phys, size_t mem_size,
> > phys_addr_t *reloc_base);
> >
> > -int qcom_mdt_pas_load(struct qcom_scm_pas_context *ctx, const struct firmware *fw,
> > +int qcom_mdt_pas_load(struct qcom_pas_context *ctx, const struct firmware *fw,
> > const char *firmware, void *mem_region, phys_addr_t *reloc_base);
> >
> > int qcom_mdt_load_no_init(struct device *dev, const struct firmware *fw,
> > @@ -45,7 +45,7 @@ static inline int qcom_mdt_load(struct device *dev, const struct firmware *fw,
> > return -ENODEV;
> > }
> >
> > -static inline int qcom_mdt_pas_load(struct qcom_scm_pas_context *ctx,
> > +static inline int qcom_mdt_pas_load(struct qcom_pas_context *ctx,
> > const struct firmware *fw, const char *firmware,
> > void *mem_region, phys_addr_t *reloc_base)
> > {
> > --
> > 2.51.0
> >
>
> With above nit
>
> Reviewed-by: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com>
Thanks.
-Sumit
^ permalink raw reply
* Re: [PATCH v4 09/15] remoteproc: qcom: Select QCOM_PAS generic service
From: Sumit Garg @ 2026-05-01 14:08 UTC (permalink / raw)
To: Mukesh Ojha
Cc: andersson, konradybcio, linux-arm-msm, devicetree, dri-devel,
freedreno, linux-media, netdev, linux-wireless, ath12k,
linux-remoteproc, robh, krzk+dt, conor+dt, robin.clark, sean,
akhilpo, lumag, abhinav.kumar, jesszhan0024, marijn.suijten,
airlied, simona, vikash.garodia, dikshita.agarwal, bod, mchehab,
elder, andrew+netdev, davem, edumazet, kuba, pabeni, jjohnson,
mathieu.poirier, trilokkumar.soni, pavan.kondeti, jorge.ramirez,
tonyh, vignesh.viswanathan, srinivas.kandagatla, amirreza.zarrabi,
jens.wiklander, op-tee, apurupa, skare, linux-kernel, Sumit Garg
In-Reply-To: <20260429135257.utgighzczrbnl6cp@hu-mojha-hyd.qualcomm.com>
On Wed, Apr 29, 2026 at 07:22:57PM +0530, Mukesh Ojha wrote:
> On Mon, Apr 27, 2026 at 03:25:57PM +0530, Sumit Garg wrote:
> > From: Sumit Garg <sumit.garg@oss.qualcomm.com>
> >
> > Select PAS generic service driver to enable support for multiple PAS
> > backends like OP-TEE in addition to SCM.
> >
> > Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
> > ---
> > drivers/remoteproc/Kconfig | 1 +
> > 1 file changed, 1 insertion(+)
> >
> > diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
> > index ee54436fea5a..da3c5d9562ea 100644
> > --- a/drivers/remoteproc/Kconfig
> > +++ b/drivers/remoteproc/Kconfig
> > @@ -230,6 +230,7 @@ config QCOM_Q6V5_PAS
> > select QCOM_Q6V5_COMMON
> > select QCOM_RPROC_COMMON
> > select QCOM_SCM
>
> Check do we really need SCM now ?
For WCNSS we don't need, I will drop there but others have SCM memory
assign needs.
> Why not the change for WCNSS, MSS ?
Ack, I will change for them too.
-Sumit
>
> > + select QCOM_PAS
> > help
> > Say y here to support the TrustZone based Peripheral Image Loader for
> > the Qualcomm remote processors. This is commonly used to control
>
>
> > --
> > 2.51.0
> >
>
> --
> -Mukesh Ojha
>
^ permalink raw reply
* Re: [PATCH net v4] ipv6: flowlabel: enforce per-netns limit for unprivileged callers
From: Willem de Bruijn @ 2026-05-01 14:09 UTC (permalink / raw)
To: Maoyi Xie, netdev
Cc: willemdebruijn.kernel, willemb, edumazet, pabeni, kuba, davem,
dsahern, kuznet, linux-kernel, stable
In-Reply-To: <20260501074130.3532402-1-maoyi.xie@ntu.edu.sg>
Maoyi Xie wrote:
> fl_size, fl_ht and ip6_fl_lock in net/ipv6/ip6_flowlabel.c are file
> scope and shared across netns. mem_check() reads fl_size to decide
> whether to deny non-CAP_NET_ADMIN callers; capable() runs against
> init_user_ns, so an unprivileged user in any non-init userns can
> push fl_size past FL_MAX_SIZE - FL_MAX_SIZE/4 and starve every
> other unprivileged userns on the host.
So previously a single unprivileged user could get 4K - 1K == 3K
entries.
Now it can only get 1K entries even after doubling FL_MAX_SIZE.
The goal of doubling that was to avoid reducing the per-user
limit.
With the expanded limit, unprivileged users collectively can fill 6K
entries. Should the check become that each individual user can only
fill half of this. Keeping the original limit:
const int unpriv_total_limit = FL_MAX_SIZE - (FL_MAX_SIZE / 4);
const int unpriv_user_limit = unpriv_total_limit / 2;
if (room <= 0 ||
((count >= FL_MAX_PER_SOCK ||
- (count > 0 && room < FL_MAX_SIZE/2) || room < FL_MAX_SIZE/4) &&
+ (count > 0 && room < FL_MAX_SIZE/2) ||
+ room < FL_MAX_SIZE/4 ||
+ atomic_read(&net->ipv6.flowlabel_count) >= unpriv_user_limit) &&
!capable(CAP_NET_ADMIN)))
Sorry for not catching this sooner.
>
> Add struct netns_ipv6::flowlabel_count, bumped and decremented next
> to fl_size in fl_intern, ip6_fl_gc and ip6_fl_purge. The new field
> is placed in the existing 4-byte hole after ipmr_seq, so struct
> netns_ipv6 stays the same size on 64-bit builds.
>
> mem_check() folds an extra FL_MAX_SIZE/8 ceiling into the existing
> non-CAP_NET_ADMIN conditional.
>
> Bump FL_MAX_SIZE from 4096 to 8192. It has been 4096 since the file
> was added; machines and connection counts have grown. The new
> per-netns ceiling is then 1024 flowlabels, half of FL_MAX_SIZE/4.
>
> CAP_NET_ADMIN against init_user_ns still bypasses both caps.
>
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Suggested-by: Willem de Bruijn <willemb@google.com>
> Cc: stable@vger.kernel.org # v5.15+
> Signed-off-by: Maoyi Xie <maoyi.xie@ntu.edu.sg>
> ---
> v4 (this submission, addressing v3 review by Willem):
> - rephrased the flowlabel_count placement note: dropped the
> flowlabel_has_excl cacheline argument; replaced with the
> simpler "fills the existing 4-byte hole after ipmr_seq" fact.
> - reordered atomic_dec(&...flowlabel_count) to sit immediately
> after atomic_dec(&fl_size) in ip6_fl_gc and ip6_fl_purge so
> the pairing is visually obvious. Both decs now happen before
> fl_free(fl) since fl_free invalidates fl->fl_net. fl_intern
> was already in this order.
> v3: addressed Willem's review on the private security@ thread;
> merged FL_MAX_SIZE doubling, dropped test data, moved
> flowlabel_count near ipmr_seq, inlined fl->fl_net in ip6_fl_gc.
> v2: per-netns counter + cap, sent to security@ as a 2-patch series.
> v1: fix-shape sketch in original disclosure.
>
> include/net/netns/ipv6.h | 1 +
> net/ipv6/ip6_flowlabel.c | 14 ++++++++++----
> 2 files changed, 11 insertions(+), 4 deletions(-)
>
> diff --git a/include/net/netns/ipv6.h b/include/net/netns/ipv6.h
> index 34bdb1308..329482373 100644
> --- a/include/net/netns/ipv6.h
> +++ b/include/net/netns/ipv6.h
> @@ -119,6 +119,7 @@ struct netns_ipv6 {
> struct fib_notifier_ops *notifier_ops;
> struct fib_notifier_ops *ip6mr_notifier_ops;
> unsigned int ipmr_seq; /* protected by rtnl_mutex */
> + atomic_t flowlabel_count;
> struct {
> struct hlist_head head;
> spinlock_t lock;
> diff --git a/net/ipv6/ip6_flowlabel.c b/net/ipv6/ip6_flowlabel.c
> index c92f98c6f..360109cad 100644
> --- a/net/ipv6/ip6_flowlabel.c
> +++ b/net/ipv6/ip6_flowlabel.c
> @@ -36,7 +36,7 @@
> /* FL hash table */
>
> #define FL_MAX_PER_SOCK 32
> -#define FL_MAX_SIZE 4096
> +#define FL_MAX_SIZE 8192
> #define FL_HASH_MASK 255
> #define FL_HASH(l) (ntohl(l)&FL_HASH_MASK)
>
> @@ -162,8 +162,9 @@ static void ip6_fl_gc(struct timer_list *unused)
> ttd = fl->expires;
> if (time_after_eq(now, ttd)) {
> *flp = fl->next;
> - fl_free(fl);
> atomic_dec(&fl_size);
> + atomic_dec(&fl->fl_net->ipv6.flowlabel_count);
> + fl_free(fl);
> continue;
> }
> if (!sched || time_before(ttd, sched))
> @@ -195,8 +196,9 @@ static void __net_exit ip6_fl_purge(struct net *net)
> if (net_eq(fl->fl_net, net) &&
> atomic_read(&fl->users) == 0) {
> *flp = fl->next;
> - fl_free(fl);
> atomic_dec(&fl_size);
> + atomic_dec(&net->ipv6.flowlabel_count);
> + fl_free(fl);
> continue;
> }
> flp = &fl->next;
> @@ -245,6 +247,7 @@ static struct ip6_flowlabel *fl_intern(struct net *net,
> fl->next = fl_ht[FL_HASH(fl->label)];
> rcu_assign_pointer(fl_ht[FL_HASH(fl->label)], fl);
> atomic_inc(&fl_size);
> + atomic_inc(&net->ipv6.flowlabel_count);
> spin_unlock_bh(&ip6_fl_lock);
> rcu_read_unlock();
> return NULL;
> @@ -464,6 +467,7 @@ fl_create(struct net *net, struct sock *sk, struct in6_flowlabel_req *freq,
>
> static int mem_check(struct sock *sk)
> {
> + struct net *net = sock_net(sk);
> int room = FL_MAX_SIZE - atomic_read(&fl_size);
> struct ipv6_fl_socklist *sfl;
> int count = 0;
> @@ -478,7 +482,9 @@ static int mem_check(struct sock *sk)
>
> if (room <= 0 ||
> ((count >= FL_MAX_PER_SOCK ||
> - (count > 0 && room < FL_MAX_SIZE/2) || room < FL_MAX_SIZE/4) &&
> + (count > 0 && room < FL_MAX_SIZE/2) ||
> + room < FL_MAX_SIZE/4 ||
> + atomic_read(&net->ipv6.flowlabel_count) >= FL_MAX_SIZE/8) &&
> !capable(CAP_NET_ADMIN)))
> return -ENOBUFS;
>
> --
> 2.34.1
>
^ permalink raw reply
* Re: [PATCH v4 10/15] drm/msm: Switch to generic PAS TZ APIs
From: Sumit Garg @ 2026-05-01 14:11 UTC (permalink / raw)
To: Mukesh Ojha
Cc: andersson, konradybcio, linux-arm-msm, devicetree, dri-devel,
freedreno, linux-media, netdev, linux-wireless, ath12k,
linux-remoteproc, robh, krzk+dt, conor+dt, robin.clark, sean,
akhilpo, lumag, abhinav.kumar, jesszhan0024, marijn.suijten,
airlied, simona, vikash.garodia, dikshita.agarwal, bod, mchehab,
elder, andrew+netdev, davem, edumazet, kuba, pabeni, jjohnson,
mathieu.poirier, trilokkumar.soni, pavan.kondeti, jorge.ramirez,
tonyh, vignesh.viswanathan, srinivas.kandagatla, amirreza.zarrabi,
jens.wiklander, op-tee, apurupa, skare, linux-kernel, Sumit Garg,
Dmitry Baryshkov
In-Reply-To: <20260429135954.nvr6nyfadsjeymyt@hu-mojha-hyd.qualcomm.com>
On Wed, Apr 29, 2026 at 07:29:54PM +0530, Mukesh Ojha wrote:
> On Mon, Apr 27, 2026 at 03:25:58PM +0530, Sumit Garg wrote:
> > From: Sumit Garg <sumit.garg@oss.qualcomm.com>
> >
> > Switch drm/msm client drivers over to generic PAS TZ APIs. Generic PAS
> > TZ service allows to support multiple TZ implementation backends like QTEE
> > based SCM PAS service, OP-TEE based PAS service and any further future TZ
> > backend service.
> >
> > Acked-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
> > Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
> > ---
> > drivers/gpu/drm/msm/Kconfig | 1 +
> > drivers/gpu/drm/msm/adreno/a5xx_gpu.c | 4 ++--
> > drivers/gpu/drm/msm/adreno/adreno_gpu.c | 11 ++++++-----
> > 3 files changed, 9 insertions(+), 7 deletions(-)
> >
> > diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gdrivers/gpu/drm/msm/Kconfigpu/drm/msm/Kconfig
> > index 250246f81ea9..09469d56513b 100644
> > --- a/drivers/gpu/drm/msm/Kconfig
> > +++ b/drivers/gpu/drm/msm/Kconfig
> > @@ -21,6 +21,7 @@ config DRM_MSM
> > select SHMEM
> > select TMPFS
> > select QCOM_SCM
>
> do we need this ?
Yeah we do..
>
> > + select QCOM_PAS
> > select QCOM_UBWC_CONFIG
> > select WANT_DEV_COREDUMP
> > select SND_SOC_HDMI_CODEC if SND_SOC
> > diff --git a/drivers/gpu/drm/msm/adreno/a5xx_gpu.c b/drivers/gpu/drm/msm/adreno/a5xx_gpu.c
> > index 79acae11154a..b556da823897 100644
> > --- a/drivers/gpu/drm/msm/adreno/a5xx_gpu.c
> > +++ b/drivers/gpu/drm/msm/adreno/a5xx_gpu.c
> > @@ -5,7 +5,7 @@
> > #include <linux/kernel.h>
> > #include <linux/types.h>
> > #include <linux/cpumask.h>
> > -#include <linux/firmware/qcom/qcom_scm.h>
> > +#include <linux/firmware/qcom/qcom_pas.h>
> > #include <linux/pm_opp.h>
> > #include <linux/nvmem-consumer.h>
> > #include <linux/slab.h>
> > @@ -653,7 +653,7 @@ static int a5xx_zap_shader_resume(struct msm_gpu *gpu)
> > if (adreno_is_a506(adreno_gpu))
> > return 0;
> >
> > - ret = qcom_scm_set_remote_state(SCM_GPU_ZAP_SHADER_RESUME, GPU_PAS_ID);
> > + ret = qcom_pas_set_remote_state(SCM_GPU_ZAP_SHADER_RESUME, GPU_PAS_ID);
> > if (ret)
> > DRM_ERROR("%s: zap-shader resume failed: %d\n",
> > gpu->name, ret);
> > diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c
> > index 66f80f2d12f9..6d68edf0578c 100644
> > --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c
> > +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c
> > @@ -8,6 +8,7 @@
> >
> > #include <linux/ascii85.h>
> > #include <linux/interconnect.h>
> > +#include <linux/firmware/qcom/qcom_pas.h>
> > #include <linux/firmware/qcom/qcom_scm.h>
>
> do we need this ?
>
..needed for qcom_scm_set_gpu_smmu_aperture() API.
> > #include <linux/kernel.h>
> > #include <linux/of_reserved_mem.h>
> > @@ -146,10 +147,10 @@ static int zap_shader_load_mdt(struct msm_gpu *gpu, const char *fwname,
> > goto out;
> >
> > /* Send the image to the secure world */
> > - ret = qcom_scm_pas_auth_and_reset(pasid);
> > + ret = qcom_pas_auth_and_reset(pasid);
> >
> > /*
> > - * If the scm call returns -EOPNOTSUPP we assume that this target
> > + * If the pas call returns -EOPNOTSUPP we assume that this target
> > * doesn't need/support the zap shader so quietly fail
> > */
> > if (ret == -EOPNOTSUPP)
> > @@ -175,9 +176,9 @@ int adreno_zap_shader_load(struct msm_gpu *gpu, u32 pasid)
> > if (!zap_available)
> > return -ENODEV;
> >
> > - /* We need SCM to be able to load the firmware */
> > - if (!qcom_scm_is_available()) {
> > - DRM_DEV_ERROR(&pdev->dev, "SCM is not available\n");
> > + /* We need PAS to be able to load the firmware */
> > + if (!qcom_pas_is_available()) {
> > + DRM_DEV_ERROR(&pdev->dev, "Qcom PAS is not available\n");
> > return -EPROBE_DEFER;
> > }
> >
> > --
> > 2.51.0
> >
>
> Reviewed-by: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com>
>
Thanks.
-Sumit
^ permalink raw reply
* Re: [PATCH net v2] psp: strip variable-length PSP header in psp_dev_rcv()
From: Daniel Zahka @ 2026-05-01 14:13 UTC (permalink / raw)
To: David Carlier, kuba
Cc: willemdebruijn.kernel, davem, edumazet, pabeni, horms, raeds,
kees, cratiu, netdev, linux-kernel, stable
In-Reply-To: <20260501130046.16008-1-devnexen@gmail.com>
On 5/1/26 9:00 AM, David Carlier wrote:
> psp_dev_rcv() unconditionally removes a fixed PSP_ENCAP_HLEN, even
> when psph->hdrlen indicates that the PSP header carries optional
> fields. A frame whose PSP header advertises a non-zero VC or any
> extension would therefore be silently mis-decapsulated: option bytes
> would spill into the inner packet head and downstream parsing would
> fail on a corrupted skb.
>
> Compute the full PSP header length from psph->hdrlen, pull the
> optional bytes into the linear region, and strip the whole header
> when decapsulating. Optional fields (VC, ...) are still ignored,
> just discarded with the rest of the header instead of leaking.
> crypt_offset and the VIRT flag are intentionally not validated here
> - callers know their device's PSP implementation and can decide.
>
> Both in-tree callers gate on hardware-validated PSP, so this is a
> correctness fix rather than a reachable corruption path under
> current configurations.
>
> Fixes: 0eddb8023cee ("psp: provide decapsulation and receive helper for drivers")
> Suggested-by: Daniel Zahka <daniel.zahka@gmail.com>
No need for the suggested tag here.
> Cc: stable@vger.kernel.org
> Signed-off-by: David Carlier <devnexen@gmail.com>
> ---
> v1 -> v2 (per Daniel Zahka):
> - strip the variable-length PSP header (psph->hdrlen) instead of
> rejecting opt-bearing frames; VC/options are ignored, not refused
> - drop the crypt_offset and PSPHDR_VERFL_VIRT checks
> - refresh kerneldoc above psp_dev_rcv()
> - retarget at net (was net-next)
>
> net/psp/psp_main.c | 41 +++++++++++++++++++++++++++++++----------
> 1 file changed, 31 insertions(+), 10 deletions(-)
>
> diff --git a/net/psp/psp_main.c b/net/psp/psp_main.c
> index 9508b6c38003..b040345d7273 100644
> --- a/net/psp/psp_main.c
> +++ b/net/psp/psp_main.c
> @@ -263,15 +263,17 @@ EXPORT_SYMBOL(psp_dev_encapsulate);
>
> /* Receive handler for PSP packets.
> *
> - * Presently it accepts only already-authenticated packets and does not
> - * support optional fields, such as virtualization cookies. The caller should
> - * ensure that skb->data is pointing to the mac header, and that skb->mac_len
> - * is set. This function does not currently adjust skb->csum (CHECKSUM_COMPLETE
> - * is not supported).
> + * Accepts only already-authenticated packets. The full PSP header is
> + * stripped according to psph->hdrlen; any optional fields it advertises
> + * (virtualization cookies, etc.) are ignored and discarded along with the
> + * rest of the header. The caller should ensure that skb->data is pointing
> + * to the mac header, and that skb->mac_len is set. This function does not
> + * currently adjust skb->csum (CHECKSUM_COMPLETE is not supported).
> */
> int psp_dev_rcv(struct sk_buff *skb, u16 dev_id, u8 generation, bool strip_icv)
> {
> int l2_hlen = 0, l3_hlen, encap;
> + u32 psp_hdr_len;
There is a style convention in the networking subsystem that
declarations are sorted longest to shortest from top to bottom. Let's
maintain that here.
nit: int psp_hlen might be more consistent with the types/names of the
other local vars.
> struct psp_skb_ext *pse;
> struct psphdr *psph;
> struct ethhdr *eth;
> @@ -312,18 +314,36 @@ int psp_dev_rcv(struct sk_buff *skb, u16 dev_id, u8 generation, bool strip_icv)
> if (unlikely(uh->dest != htons(PSP_DEFAULT_UDP_PORT)))
> return -EINVAL;
>
> - pse = skb_ext_add(skb, SKB_EXT_PSP);
> - if (!pse)
> + psph = (struct psphdr *)(skb->data + l2_hlen + l3_hlen +
> + sizeof(struct udphdr));
> +
> + /* Strip the full PSP header per psph->hdrlen; VC/options are pulled
> + * into the linear region only so they can be discarded with the
> + * rest of the header.
> + */
> + psp_hdr_len = ((u32)psph->hdrlen + 1) * 8;
I don't believe casting psph->hdrlen to u32 is necessary for correctness
here.
> +
> + if (unlikely(psp_hdr_len < sizeof(struct psphdr)))
> + return -EINVAL;
> +
> + if (psp_hdr_len > sizeof(struct psphdr) &&
> + !pskb_may_pull(skb, l2_hlen + l3_hlen +
> + sizeof(struct udphdr) + psp_hdr_len))
> return -EINVAL;
>
> psph = (struct psphdr *)(skb->data + l2_hlen + l3_hlen +
> sizeof(struct udphdr));
> +
> + pse = skb_ext_add(skb, SKB_EXT_PSP);
> + if (!pse)
> + return -EINVAL;
> +
> pse->spi = psph->spi;
> pse->dev_id = dev_id;
> pse->generation = generation;
> pse->version = FIELD_GET(PSPHDR_VERFL_VERSION, psph->verfl);
>
> - encap = PSP_ENCAP_HLEN;
> + encap = sizeof(struct udphdr) + psp_hdr_len;
> encap += strip_icv ? PSP_TRL_SIZE : 0;
>
> if (proto == htons(ETH_P_IP)) {
> @@ -340,8 +360,9 @@ int psp_dev_rcv(struct sk_buff *skb, u16 dev_id, u8 generation, bool strip_icv)
> ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) - encap);
> }
>
> - memmove(skb->data + PSP_ENCAP_HLEN, skb->data, l2_hlen + l3_hlen);
> - skb_pull(skb, PSP_ENCAP_HLEN);
> + memmove(skb->data + sizeof(struct udphdr) + psp_hdr_len,
> + skb->data, l2_hlen + l3_hlen);
> + skb_pull(skb, sizeof(struct udphdr) + psp_hdr_len);
>
> if (strip_icv)
> pskb_trim(skb, skb->len - PSP_TRL_SIZE);
Minor comments, but otherwise lgtm.
Reviewed-by: Daniel Zahka <daniel.zahka@gmail.com>
^ permalink raw reply
* Re: [PATCH net-next 1/2] net: cs89x0: remove ISA bus probing
From: Simon Horman @ 2026-05-01 14:21 UTC (permalink / raw)
To: Arnd Bergmann
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Jonathan Corbet, Andrew Lunn, Arnd Bergmann, Andrew Morton,
Shuah Khan, Mengyuan Lou, netdev, linux-doc, linux-kernel
In-Reply-To: <20260429145624.2948432-1-arnd@kernel.org>
On Wed, Apr 29, 2026 at 04:55:45PM +0200, Arnd Bergmann wrote:
> From: Arnd Bergmann <arnd@arndb.de>
>
> The cs89x0 driver is really two in one, and they are mutually exclusive:
>
> - the ISA driver was used on 486-era PCs. It likely has no remaining
> users, like the other ethernet drivers that got removed in
> linux-7.1. The DMA support in here is the last device driver use of
> the deprecated isa_bus_to_virt() interface, all other users are either
> x86 specific or or got converted to the normal dma-mapping interface.
> The driver was maintained by Andrew Morton at the time, based on
> the linux-2.2 vendor driver from Cirrus Logic.
>
> - the platform_driver instance was used on some embedded Arm boards
> around the same time, such as the EP7211 Development Kit. This
> is the same chip, but uses modern devicetree based probing and no DMA.
> This was added by Alexander Shiyan.
>
> Remove the ISA driver as a cleanup, including all of the outdated
> documentation referring to its configuration.
>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Thanks Arnd,
With the increase in both AI generated patches and review,
and the maintainer effort required to process these, the
cost of maintaining unused code has become entirely non-negligible.
So I welcome efforts to reduce that surface.
I note that there is an AI generated review of this patch available on
sashkio.dev. I believe that covers only pre-existing issues. And I
illustrates the point I've made above. I do not believe that review should
block progress of this patch.
Reviewed-by: Simon Horman <horms@kernel.org>
...
^ permalink raw reply
* Re: [PATCH net-next 2/2] ne2k: fold drivers/net/Space.c into ne.c
From: Simon Horman @ 2026-05-01 14:23 UTC (permalink / raw)
To: Arnd Bergmann
Cc: Jonathan Corbet, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Arnd Bergmann, Shuah Khan,
Andrew Morton, Borislav Petkov (AMD), linux-doc, linux-kernel,
netdev
In-Reply-To: <20260429145624.2948432-2-arnd@kernel.org>
On Wed, Apr 29, 2026 at 04:55:46PM +0200, Arnd Bergmann wrote:
> From: Arnd Bergmann <arnd@arndb.de>
>
> drivers/net/Space.c is the last remnant of the linux-2.4.x driver model
> that required each subsystem and device driver init function to be called
> from init/main.c explicitly, before the introduction of initcall levels.
>
> In linux-7.0, this was only used for a handful of ISA network drivers,
> with the ne2000 driver being the last one.
>
> Fold the code into ne.c directly, with minimal changes to preserve
> the existing command line parsing.
>
> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Thanks Arnd,
FTR, that there is an AI generated review of this patch available on
sashkio.dev. I believe that covers only pre-existing issues. And I do not
believe that review should block progress of this patch.
Reviewed-by: Simon Horman <horms@kernel.org>
...
^ permalink raw reply
* Re: [PATCH net-next] net: usb: cdc_ncm: add Apple Mac USB-C direct networking quirk
From: Simon Horman @ 2026-05-01 14:29 UTC (permalink / raw)
To: Alex Cheema
Cc: oliver, bjorn, oleavr, kuba, pabeni, davem, edumazet,
andrew+netdev, netdev, linux-usb, linux-kernel
In-Reply-To: <20260429175739.34426-1-alex@exolabs.net>
On Wed, Apr 29, 2026 at 06:57:39PM +0100, Alex Cheema wrote:
> Apple Silicon Macs expose two CDC NCM "private" data interfaces over
> USB-C with VID:PID 0x05ac:0x1905 and product string "Mac". This is the
> same protocol Apple already ships on iPhone (0x05ac:0x12a8) and iPad
> (0x05ac:0x12ab) for RemoteXPC since iOS 17 -- both data interfaces lack
> an interrupt status endpoint, so they rely on the FLAG_LINK_INTR-
> conditional bind path introduced in commit 3ec8d7572a69 ("CDC-NCM: add
> support for Apple's private interface").
>
> The id_table currently has entries for iPhone and iPad but not for the
> Mac. Without a match, cdc_ncm falls through to the generic CDC NCM
> class-match entry, which uses the FLAG_LINK_INTR-having cdc_ncm_info
> struct, so bind_common() fails on the missing status endpoint and no
> netdev appears.
>
> Add id_table entries for both interface numbers (0 and 2) of the Mac,
> bound to the existing apple_private_interface_info driver_info.
>
> Verified empirically on a Mac Studio M3 Ultra running macOS 26.5: when
> a Mac is connected via USB-C, ioreg shows VID 0x05ac, PID 0x1905,
> product string "Mac", with two NCM data interfaces at numbers 0 and 2.
> The same PID is presented by all current Apple Silicon Mac models
> (MacBook Pro/Air, Mac mini, Mac Studio across the M-series), mirroring
> Apple's single-PID-per-family pattern from iPhone/iPad.
>
> After this patch, plugging a Mac into a Linux host running the patched
> kernel produces two enx... interfaces (one per data interface),
> "ip -br link" lists them as UP, and standard userspace networking
> (DHCP, NetworkManager shared mode, etc.) works without any modprobe
> overrides or out-of-tree modules.
>
> Signed-off-by: Alex Cheema <alex@exolabs.net>
Reviewed-by: Simon Horman <horms@kernel.org>
^ permalink raw reply
* Re: [PATCH net v2] psp: strip variable-length PSP header in psp_dev_rcv()
From: David CARLIER @ 2026-05-01 14:39 UTC (permalink / raw)
To: Daniel Zahka
Cc: kuba, willemdebruijn.kernel, davem, edumazet, pabeni, horms,
raeds, kees, cratiu, netdev, linux-kernel, stable
In-Reply-To: <ba78786c-881e-4cf4-91d1-7e9d21194454@gmail.com>
On Fri, 1 May 2026 at 15:13, Daniel Zahka <daniel.zahka@gmail.com> wrote:
>
>
> On 5/1/26 9:00 AM, David Carlier wrote:
> > psp_dev_rcv() unconditionally removes a fixed PSP_ENCAP_HLEN, even
> > when psph->hdrlen indicates that the PSP header carries optional
> > fields. A frame whose PSP header advertises a non-zero VC or any
> > extension would therefore be silently mis-decapsulated: option bytes
> > would spill into the inner packet head and downstream parsing would
> > fail on a corrupted skb.
> >
> > Compute the full PSP header length from psph->hdrlen, pull the
> > optional bytes into the linear region, and strip the whole header
> > when decapsulating. Optional fields (VC, ...) are still ignored,
> > just discarded with the rest of the header instead of leaking.
> > crypt_offset and the VIRT flag are intentionally not validated here
> > - callers know their device's PSP implementation and can decide.
> >
> > Both in-tree callers gate on hardware-validated PSP, so this is a
> > correctness fix rather than a reachable corruption path under
> > current configurations.
> >
> > Fixes: 0eddb8023cee ("psp: provide decapsulation and receive helper for drivers")
> > Suggested-by: Daniel Zahka <daniel.zahka@gmail.com>
>
>
> No need for the suggested tag here.
>
>
> > Cc: stable@vger.kernel.org
> > Signed-off-by: David Carlier <devnexen@gmail.com>
> > ---
> > v1 -> v2 (per Daniel Zahka):
> > - strip the variable-length PSP header (psph->hdrlen) instead of
> > rejecting opt-bearing frames; VC/options are ignored, not refused
> > - drop the crypt_offset and PSPHDR_VERFL_VIRT checks
> > - refresh kerneldoc above psp_dev_rcv()
> > - retarget at net (was net-next)
> >
> > net/psp/psp_main.c | 41 +++++++++++++++++++++++++++++++----------
> > 1 file changed, 31 insertions(+), 10 deletions(-)
> >
> > diff --git a/net/psp/psp_main.c b/net/psp/psp_main.c
> > index 9508b6c38003..b040345d7273 100644
> > --- a/net/psp/psp_main.c
> > +++ b/net/psp/psp_main.c
> > @@ -263,15 +263,17 @@ EXPORT_SYMBOL(psp_dev_encapsulate);
> >
> > /* Receive handler for PSP packets.
> > *
> > - * Presently it accepts only already-authenticated packets and does not
> > - * support optional fields, such as virtualization cookies. The caller should
> > - * ensure that skb->data is pointing to the mac header, and that skb->mac_len
> > - * is set. This function does not currently adjust skb->csum (CHECKSUM_COMPLETE
> > - * is not supported).
> > + * Accepts only already-authenticated packets. The full PSP header is
> > + * stripped according to psph->hdrlen; any optional fields it advertises
> > + * (virtualization cookies, etc.) are ignored and discarded along with the
> > + * rest of the header. The caller should ensure that skb->data is pointing
> > + * to the mac header, and that skb->mac_len is set. This function does not
> > + * currently adjust skb->csum (CHECKSUM_COMPLETE is not supported).
> > */
> > int psp_dev_rcv(struct sk_buff *skb, u16 dev_id, u8 generation, bool strip_icv)
> > {
> > int l2_hlen = 0, l3_hlen, encap;
> > + u32 psp_hdr_len;
>
>
> There is a style convention in the networking subsystem that
> declarations are sorted longest to shortest from top to bottom. Let's
> maintain that here.
>
> nit: int psp_hlen might be more consistent with the types/names of the
> other local vars.
>
>
> > struct psp_skb_ext *pse;
> > struct psphdr *psph;
> > struct ethhdr *eth;
> > @@ -312,18 +314,36 @@ int psp_dev_rcv(struct sk_buff *skb, u16 dev_id, u8 generation, bool strip_icv)
> > if (unlikely(uh->dest != htons(PSP_DEFAULT_UDP_PORT)))
> > return -EINVAL;
> >
> > - pse = skb_ext_add(skb, SKB_EXT_PSP);
> > - if (!pse)
> > + psph = (struct psphdr *)(skb->data + l2_hlen + l3_hlen +
> > + sizeof(struct udphdr));
> > +
> > + /* Strip the full PSP header per psph->hdrlen; VC/options are pulled
> > + * into the linear region only so they can be discarded with the
> > + * rest of the header.
> > + */
> > + psp_hdr_len = ((u32)psph->hdrlen + 1) * 8;
>
>
> I don't believe casting psph->hdrlen to u32 is necessary for correctness
> here.
>
>
> > +
> > + if (unlikely(psp_hdr_len < sizeof(struct psphdr)))
> > + return -EINVAL;
> > +
> > + if (psp_hdr_len > sizeof(struct psphdr) &&
> > + !pskb_may_pull(skb, l2_hlen + l3_hlen +
> > + sizeof(struct udphdr) + psp_hdr_len))
> > return -EINVAL;
> >
> > psph = (struct psphdr *)(skb->data + l2_hlen + l3_hlen +
> > sizeof(struct udphdr));
> > +
> > + pse = skb_ext_add(skb, SKB_EXT_PSP);
> > + if (!pse)
> > + return -EINVAL;
> > +
> > pse->spi = psph->spi;
> > pse->dev_id = dev_id;
> > pse->generation = generation;
> > pse->version = FIELD_GET(PSPHDR_VERFL_VERSION, psph->verfl);
> >
> > - encap = PSP_ENCAP_HLEN;
> > + encap = sizeof(struct udphdr) + psp_hdr_len;
> > encap += strip_icv ? PSP_TRL_SIZE : 0;
> >
> > if (proto == htons(ETH_P_IP)) {
> > @@ -340,8 +360,9 @@ int psp_dev_rcv(struct sk_buff *skb, u16 dev_id, u8 generation, bool strip_icv)
> > ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) - encap);
> > }
> >
> > - memmove(skb->data + PSP_ENCAP_HLEN, skb->data, l2_hlen + l3_hlen);
> > - skb_pull(skb, PSP_ENCAP_HLEN);
> > + memmove(skb->data + sizeof(struct udphdr) + psp_hdr_len,
> > + skb->data, l2_hlen + l3_hlen);
> > + skb_pull(skb, sizeof(struct udphdr) + psp_hdr_len);
> >
> > if (strip_icv)
> > pskb_trim(skb, skb->len - PSP_TRL_SIZE);
>
>
> Minor comments, but otherwise lgtm.
>
> Reviewed-by: Daniel Zahka <daniel.zahka@gmail.com>
>
ACK, will resend tomorrow, Cheers !
^ permalink raw reply
* [PATCH 0/6] SUNRPC: Address remaining cache_check_rcu() UAF in cache content files
From: Chuck Lever @ 2026-05-01 14:51 UTC (permalink / raw)
To: Misbah Anjum N, Jeff Layton, NeilBrown, Olga Kornievskaia,
Dai Ngo, Tom Talpey, Trond Myklebust, Anna Schumaker,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Yang Erkun
Cc: linux-nfs, linux-kernel, netdev, Chuck Lever
Misbah Anjum reported a use-after-free in cache_check_rcu()
reached through e_show() while sosreport was reading
/proc/fs/nfsd/exports on ppc64le. Two fixes for that report
landed in v7.0:
48db892356d6 ("NFSD: Defer sub-object cleanup in export put callbacks")
e7fcf179b82d ("NFSD: Hold net reference for the lifetime of /proc/fs/nfs/exports fd")
The original e_show() repro is now fixed. However, the same
sosreport workload still reproduces a closely related fault on
post-v7.0 mainline (Misbah, ppc64le) and on master.20260424
(internal report, aarch64). In both cases the fault is in
cache_check_rcu() reached through c_show() rather than e_show(),
and the cache_head pointer is plain garbage:
pc : cache_check_rcu+0x40 [sunrpc]
lr : c_show+0x60 [sunrpc]
...faulting on h->flags off h = 0x0000000200000000
c_show() is the generic show callback used by
/proc/net/rpc/<cd>/content for every per-net cache_detail
(auth.unix.ip, auth.unix.gid, nfsd.fh, nfsd.export). Two
bugs combine in that path:
1. cache_unregister_net() / cache_destroy_net() free cd and
cd->hash_table synchronously when the namespace exits. The
/proc/net/rpc/.../content open path takes only a module
reference, so a fd kept open across a netns exit walks a
freed hash_table and returns garbage cache_head pointers.
This is the same hazard that e7fcf179b82d closed for the
/proc/fs/nfs/exports file alone.
2. ip_map_put() drops auth_domain_put() before kfree_rcu(), so
sub-objects can be freed before the RCU grace period -- the
same hazard that 48db892356d6 fixed for svc_export_put() and
expkey_put(). unix_gid_put() does not have this bug
structurally (its put_group_info() runs inside the call_rcu()
callback) but it uses a separate idiom from the other three
caches.
This series replaces the v1 narrow fixes with shared
infrastructure that covers all four cache_detail .put paths
and all three per-cache file types:
Patch 1 hoists nfsd_export_wq up to the sunrpc layer as
sunrpc_cache_wq, exposed through sunrpc_cache_queue_release()
and sunrpc_cache_drain() so all four put callbacks share one
workqueue and one drain primitive.
Patch 2 converts ip_map_put() to the queue_rcu_work() pattern,
moving auth_domain_put() into a deferred ip_map_release() that
runs after the RCU grace period.
Patch 3 unifies unix_gid_put() onto the same pattern for
consistency (not a bug fix on its own).
Patch 4 takes a get_net(cd->net) in content_open(), cache_open(),
and open_flush() and drops it in the matching release helpers,
so cache_destroy_net() cannot run while a sunrpc cache fd is
open.
Series has been compile-tested only.
---
Chuck Lever (6):
SUNRPC: Move cache_initialize() declaration to sunrpc-private header
SUNRPC: Provide a shared workqueue for cache release callbacks
SUNRPC: Defer ip_map sub-object cleanup past RCU grace period
SUNRPC: Use shared release pattern for the unix_gid cache
SUNRPC: Hold cd->net for the lifetime of cache files
NFSD: Convert nfsd_export_shutdown() to sunrpc_cache_destroy_net()
fs/nfsd/export.c | 45 ++--------------------
fs/nfsd/export.h | 2 -
fs/nfsd/nfsctl.c | 8 +---
include/linux/sunrpc/cache.h | 3 +-
net/sunrpc/cache.c | 90 ++++++++++++++++++++++++++++++++++++++++++--
net/sunrpc/sunrpc.h | 2 +
net/sunrpc/sunrpc_syms.c | 23 ++++++-----
net/sunrpc/svcauth_unix.c | 46 ++++++++++++----------
8 files changed, 135 insertions(+), 84 deletions(-)
---
base-commit: f3a313ecd1fdab1f5da119db355363b13af6fcac
change-id: 20260430-cache-uaf-fix-a13000f67c37
Best regards,
--
Chuck Lever
^ permalink raw reply
* [PATCH 1/6] SUNRPC: Move cache_initialize() declaration to sunrpc-private header
From: Chuck Lever @ 2026-05-01 14:51 UTC (permalink / raw)
To: Misbah Anjum N, Jeff Layton, NeilBrown, Olga Kornievskaia,
Dai Ngo, Tom Talpey, Trond Myklebust, Anna Schumaker,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Yang Erkun
Cc: linux-nfs, linux-kernel, netdev, Chuck Lever
In-Reply-To: <20260501-cache-uaf-fix-v1-0-a49928bf4817@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
cache_initialize() was introduced by commit 8eab945c5616 ("sunrpc:
make the cache cleaner workqueue deferrable", 2010) and placed in
the public include/linux/sunrpc/cache.h from the start, but it
has never been EXPORT_SYMBOL_GPL'd. The only caller, init_sunrpc()
in net/sunrpc/sunrpc_syms.c, is built into the same module that
defines the function, so external modules could not link against
the symbol even if they tried. The public declaration has been a
stale-public hygiene leak ever since.
Relocate the declaration to net/sunrpc/sunrpc.h alongside other
sunrpc-internal helpers and include that header from
net/sunrpc/cache.c so the compiler enforces prototype consistency
at the definition site. The public include/linux/sunrpc/cache.h
now reflects the actual external API surface.
No functional change.
Assisted-by: Claude:claude-opus-4-7[1m]
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
include/linux/sunrpc/cache.h | 1 -
net/sunrpc/cache.c | 1 +
net/sunrpc/sunrpc.h | 1 +
3 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h
index 2735c332ddb7..83c88dc82e69 100644
--- a/include/linux/sunrpc/cache.h
+++ b/include/linux/sunrpc/cache.h
@@ -237,7 +237,6 @@ extern int cache_check(struct cache_detail *detail,
extern void cache_flush(void);
extern void cache_purge(struct cache_detail *detail);
#define NEVER (0x7FFFFFFF)
-extern void __init cache_initialize(void);
extern int cache_register_net(struct cache_detail *cd, struct net *net);
extern void cache_unregister_net(struct cache_detail *cd, struct net *net);
diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
index 391037f15292..488a14961b19 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -39,6 +39,7 @@
#include "netns.h"
#include "netlink.h"
#include "fail.h"
+#include "sunrpc.h"
#define RPCDBG_FACILITY RPCDBG_CACHE
diff --git a/net/sunrpc/sunrpc.h b/net/sunrpc/sunrpc.h
index e3c6e3b63f0b..7fa35ee8f9a4 100644
--- a/net/sunrpc/sunrpc.h
+++ b/net/sunrpc/sunrpc.h
@@ -41,6 +41,7 @@ struct svc_rqst;
int rpc_clients_notifier_register(void);
void rpc_clients_notifier_unregister(void);
void auth_domain_cleanup(void);
+void __init cache_initialize(void);
void svc_sock_update_bufs(struct svc_serv *serv);
enum svc_auth_status svc_authenticate(struct svc_rqst *rqstp);
#endif /* _NET_SUNRPC_SUNRPC_H */
--
2.53.0
^ permalink raw reply related
* [PATCH 2/6] SUNRPC: Provide a shared workqueue for cache release callbacks
From: Chuck Lever @ 2026-05-01 14:51 UTC (permalink / raw)
To: Misbah Anjum N, Jeff Layton, NeilBrown, Olga Kornievskaia,
Dai Ngo, Tom Talpey, Trond Myklebust, Anna Schumaker,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Yang Erkun
Cc: linux-nfs, linux-kernel, netdev, Chuck Lever
In-Reply-To: <20260501-cache-uaf-fix-v1-0-a49928bf4817@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
Cache .put callbacks may need to release sub-objects whose
cleanup sleeps (path_put, auth_domain_put, put_group_info), which
precludes running the release from a call_rcu() softirq callback.
Commit 48db892356d6 ("NFSD: Defer sub-object cleanup in export
put callbacks") introduced nfsd_export_wq for that purpose, with
a dedicated workqueue chosen so that flush_workqueue() in the
per-namespace teardown path drains only NFSD export release work
rather than blocking on unrelated work queued to system_unbound_wq.
Subsequent patches in this series convert the sunrpc ip_map and
unix_gid put callbacks to the same queue_rcu_work() pattern, and
those would otherwise need their own per-cache workqueue for the
same reason. Hoist the workqueue up to the sunrpc layer so that
all four cache_detail put callbacks share a single workqueue,
managed entirely within net/sunrpc/cache.c.
Expose the workqueue through three helpers.
sunrpc_cache_queue_release() schedules a deferred release after
the next RCU grace period. sunrpc_cache_destroy_net()
encapsulates the cache_unregister_net() + drain +
cache_destroy_net() sequence that single-cache teardowns
otherwise have to open-code, putting the ordering rule in one
place. sunrpc_cache_drain() exposes the underlying
rcu_barrier() + flush_workqueue() primitive for the rare caller
that drains multiple cache_details together, such as
nfsd_export_shutdown(). Allocate the workqueue in
cache_initialize() and destroy it in a new cache_destroy()
called from cleanup_sunrpc(). Replace the local nfsd_export_wq
with the shared sunrpc helpers and drop the
nfsd_export_wq_init/shutdown helpers and their callers.
Assisted-by: Claude:claude-opus-4-7[1m]
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
fs/nfsd/export.c | 41 +++-----------------------
fs/nfsd/export.h | 2 --
fs/nfsd/nfsctl.c | 8 +----
include/linux/sunrpc/cache.h | 3 ++
net/sunrpc/cache.c | 70 +++++++++++++++++++++++++++++++++++++++++++-
net/sunrpc/sunrpc.h | 3 +-
net/sunrpc/sunrpc_syms.c | 23 +++++++++------
7 files changed, 93 insertions(+), 57 deletions(-)
diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
index 15972919e1e9..3c4340e743fa 100644
--- a/fs/nfsd/export.c
+++ b/fs/nfsd/export.c
@@ -39,8 +39,6 @@
* second map contains a reference to the entry in the first map.
*/
-static struct workqueue_struct *nfsd_export_wq;
-
#define EXPKEY_HASHBITS 8
#define EXPKEY_HASHMAX (1 << EXPKEY_HASHBITS)
#define EXPKEY_HASHMASK (EXPKEY_HASHMAX -1)
@@ -62,7 +60,7 @@ static void expkey_put(struct kref *ref)
struct svc_expkey *key = container_of(ref, struct svc_expkey, h.ref);
INIT_RCU_WORK(&key->ek_rwork, expkey_release);
- queue_rcu_work(nfsd_export_wq, &key->ek_rwork);
+ sunrpc_cache_queue_release(&key->ek_rwork);
}
static int expkey_upcall(struct cache_detail *cd, struct cache_head *h)
@@ -652,7 +650,7 @@ static void svc_export_put(struct kref *ref)
struct svc_export *exp = container_of(ref, struct svc_export, h.ref);
INIT_RCU_WORK(&exp->ex_rwork, svc_export_release);
- queue_rcu_work(nfsd_export_wq, &exp->ex_rwork);
+ sunrpc_cache_queue_release(&exp->ex_rwork);
}
/**
@@ -2193,36 +2191,6 @@ const struct seq_operations nfs_exports_op = {
.show = e_show,
};
-/**
- * nfsd_export_wq_init - allocate the export release workqueue
- *
- * Called once at module load. The workqueue runs deferred svc_export and
- * svc_expkey release work scheduled by queue_rcu_work() in the cache put
- * callbacks.
- *
- * Return values:
- * %0: workqueue allocated
- * %-ENOMEM: allocation failed
- */
-int nfsd_export_wq_init(void)
-{
- nfsd_export_wq = alloc_workqueue("nfsd_export", WQ_UNBOUND, 0);
- if (!nfsd_export_wq)
- return -ENOMEM;
- return 0;
-}
-
-/**
- * nfsd_export_wq_shutdown - drain and free the export release workqueue
- *
- * Called once at module unload. Per-namespace teardown in
- * nfsd_export_shutdown() has already drained all deferred work.
- */
-void nfsd_export_wq_shutdown(void)
-{
- destroy_workqueue(nfsd_export_wq);
-}
-
/*
* Initialize the exports module.
*/
@@ -2284,9 +2252,8 @@ nfsd_export_shutdown(struct net *net)
cache_unregister_net(nn->svc_expkey_cache, net);
cache_unregister_net(nn->svc_export_cache, net);
- /* Drain deferred export and expkey release work. */
- rcu_barrier();
- flush_workqueue(nfsd_export_wq);
+ /* One drain covers both caches' deferred release work. */
+ sunrpc_cache_drain();
cache_destroy_net(nn->svc_expkey_cache, net);
cache_destroy_net(nn->svc_export_cache, net);
svcauth_unix_purge(net);
diff --git a/fs/nfsd/export.h b/fs/nfsd/export.h
index b05399374574..8969e81de448 100644
--- a/fs/nfsd/export.h
+++ b/fs/nfsd/export.h
@@ -111,8 +111,6 @@ __be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp,
/*
* Function declarations
*/
-int nfsd_export_wq_init(void);
-void nfsd_export_wq_shutdown(void);
int nfsd_export_init(struct net *);
void nfsd_export_shutdown(struct net *);
void nfsd_export_flush(struct net *);
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index 064a2e749bc9..468aad8c3af9 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -2536,12 +2536,9 @@ static int __init init_nfsd(void)
if (retval)
goto out_free_pnfs;
nfsd_lockd_init(); /* lockd->nfsd callbacks */
- retval = nfsd_export_wq_init();
- if (retval)
- goto out_free_lockd;
retval = register_pernet_subsys(&nfsd_net_ops);
if (retval < 0)
- goto out_free_export_wq;
+ goto out_free_lockd;
retval = register_cld_notifier();
if (retval)
goto out_free_subsys;
@@ -2570,8 +2567,6 @@ static int __init init_nfsd(void)
unregister_cld_notifier();
out_free_subsys:
unregister_pernet_subsys(&nfsd_net_ops);
-out_free_export_wq:
- nfsd_export_wq_shutdown();
out_free_lockd:
nfsd_lockd_shutdown();
nfsd_drc_slab_free();
@@ -2592,7 +2587,6 @@ static void __exit exit_nfsd(void)
nfsd4_destroy_laundry_wq();
unregister_cld_notifier();
unregister_pernet_subsys(&nfsd_net_ops);
- nfsd_export_wq_shutdown();
nfsd_drc_slab_free();
nfsd_lockd_shutdown();
nfsd4_free_slabs();
diff --git a/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h
index 83c88dc82e69..84802438a5fc 100644
--- a/include/linux/sunrpc/cache.h
+++ b/include/linux/sunrpc/cache.h
@@ -237,11 +237,14 @@ extern int cache_check(struct cache_detail *detail,
extern void cache_flush(void);
extern void cache_purge(struct cache_detail *detail);
#define NEVER (0x7FFFFFFF)
+extern void sunrpc_cache_queue_release(struct rcu_work *rwork);
+extern void sunrpc_cache_drain(void);
extern int cache_register_net(struct cache_detail *cd, struct net *net);
extern void cache_unregister_net(struct cache_detail *cd, struct net *net);
extern struct cache_detail *cache_create_net(const struct cache_detail *tmpl, struct net *net);
extern void cache_destroy_net(struct cache_detail *cd, struct net *net);
+extern void sunrpc_cache_destroy_net(struct cache_detail *cd, struct net *net);
extern void sunrpc_init_cache_detail(struct cache_detail *cd);
extern void sunrpc_destroy_cache_detail(struct cache_detail *cd);
diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
index 488a14961b19..733bcd3daa46 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -1705,9 +1705,77 @@ static int create_cache_proc_entries(struct cache_detail *cd, struct net *net)
return -ENOMEM;
}
-void __init cache_initialize(void)
+static struct workqueue_struct *sunrpc_cache_wq;
+
+/**
+ * sunrpc_cache_queue_release - schedule deferred cache release work
+ * @rwork: caller-initialized rcu_work to queue
+ *
+ * Run @rwork in process context after the next RCU grace period.
+ * Use this for cache .put callbacks whose cleanup may sleep
+ * (path_put(), auth_domain_put()).
+ */
+void sunrpc_cache_queue_release(struct rcu_work *rwork)
{
+ queue_rcu_work(sunrpc_cache_wq, rwork);
+}
+EXPORT_SYMBOL_GPL(sunrpc_cache_queue_release);
+
+/**
+ * sunrpc_cache_drain - drain pending cache release work
+ *
+ * Wait for outstanding RCU callbacks to enqueue their release
+ * work, then flush that work to completion.
+ */
+void sunrpc_cache_drain(void)
+{
+ rcu_barrier();
+ flush_workqueue(sunrpc_cache_wq);
+}
+EXPORT_SYMBOL_GPL(sunrpc_cache_drain);
+
+/**
+ * sunrpc_cache_destroy_net - quiesce and tear down a per-net cache
+ * @cd: the cache_detail to release
+ * @net: the network namespace owning @cd
+ *
+ * Canonical teardown for caches whose .put callbacks use
+ * sunrpc_cache_queue_release(). Unregister @cd to stop new
+ * lookups, drain in-flight RCU callbacks and queued release
+ * work, then free @cd and its hash table. The drain ensures
+ * release workers complete while the cache_detail is still
+ * valid.
+ */
+void sunrpc_cache_destroy_net(struct cache_detail *cd, struct net *net)
+{
+ cache_unregister_net(cd, net);
+ sunrpc_cache_drain();
+ cache_destroy_net(cd, net);
+}
+EXPORT_SYMBOL_GPL(sunrpc_cache_destroy_net);
+
+/**
+ * cache_initialize - allocate sunrpc cache subsystem resources
+ */
+int __init cache_initialize(void)
+{
+ sunrpc_cache_wq = alloc_workqueue("sunrpc_cache",
+ WQ_UNBOUND | WQ_MEM_RECLAIM, 0);
+ if (!sunrpc_cache_wq)
+ return -ENOMEM;
INIT_DEFERRABLE_WORK(&cache_cleaner, do_cache_clean);
+ return 0;
+}
+
+/**
+ * cache_destroy - release sunrpc cache subsystem resources
+ *
+ * Caller must ensure no further sunrpc_cache_queue_release()
+ * calls can be scheduled before invoking this.
+ */
+void cache_destroy(void)
+{
+ destroy_workqueue(sunrpc_cache_wq);
}
int cache_register_net(struct cache_detail *cd, struct net *net)
diff --git a/net/sunrpc/sunrpc.h b/net/sunrpc/sunrpc.h
index 7fa35ee8f9a4..75ee201e4800 100644
--- a/net/sunrpc/sunrpc.h
+++ b/net/sunrpc/sunrpc.h
@@ -41,7 +41,8 @@ struct svc_rqst;
int rpc_clients_notifier_register(void);
void rpc_clients_notifier_unregister(void);
void auth_domain_cleanup(void);
-void __init cache_initialize(void);
+int __init cache_initialize(void);
+void cache_destroy(void);
void svc_sock_update_bufs(struct svc_serv *serv);
enum svc_auth_status svc_authenticate(struct svc_rqst *rqstp);
#endif /* _NET_SUNRPC_SUNRPC_H */
diff --git a/net/sunrpc/sunrpc_syms.c b/net/sunrpc/sunrpc_syms.c
index ab88ce46afb5..d75ff1e592f2 100644
--- a/net/sunrpc/sunrpc_syms.c
+++ b/net/sunrpc/sunrpc_syms.c
@@ -97,24 +97,26 @@ init_sunrpc(void)
if (err)
goto out2;
- cache_initialize();
-
- err = register_pernet_subsys(&sunrpc_net_ops);
+ err = cache_initialize();
if (err)
goto out3;
- err = register_rpc_pipefs();
+ err = register_pernet_subsys(&sunrpc_net_ops);
if (err)
goto out4;
- err = rpc_sysfs_init();
+ err = register_rpc_pipefs();
if (err)
goto out5;
- err = genl_register_family(&sunrpc_nl_family);
+ err = rpc_sysfs_init();
if (err)
goto out6;
+ err = genl_register_family(&sunrpc_nl_family);
+ if (err)
+ goto out7;
+
sunrpc_debugfs_init();
#if IS_ENABLED(CONFIG_SUNRPC_DEBUG)
rpc_register_sysctl();
@@ -123,12 +125,14 @@ init_sunrpc(void)
init_socket_xprt(); /* clnt sock transport */
return 0;
-out6:
+out7:
rpc_sysfs_exit();
-out5:
+out6:
unregister_rpc_pipefs();
-out4:
+out5:
unregister_pernet_subsys(&sunrpc_net_ops);
+out4:
+ cache_destroy();
out3:
rpcauth_remove_module();
out2:
@@ -157,6 +161,7 @@ cleanup_sunrpc(void)
rpc_unregister_sysctl();
#endif
rcu_barrier(); /* Wait for completion of call_rcu()'s */
+ cache_destroy();
}
MODULE_DESCRIPTION("Sun RPC core");
MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH 3/6] SUNRPC: Defer ip_map sub-object cleanup past RCU grace period
From: Chuck Lever @ 2026-05-01 14:51 UTC (permalink / raw)
To: Misbah Anjum N, Jeff Layton, NeilBrown, Olga Kornievskaia,
Dai Ngo, Tom Talpey, Trond Myklebust, Anna Schumaker,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Yang Erkun
Cc: linux-nfs, linux-kernel, netdev, Chuck Lever
In-Reply-To: <20260501-cache-uaf-fix-v1-0-a49928bf4817@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
ip_map_put() is already correct in that auth_domain_put() of
im->m_client reaches svcauth_unix_domain_release(), which defers
the actual kfree() of the unix_domain with call_rcu(); the ip_map
itself is freed via kfree_rcu(). Readers in c_show() under
cache_seq_start_rcu() therefore observe both objects until the
next RCU grace period.
This patch is a consistency change rather than a bug fix. The
svc_export and svc_expkey caches were converted to the
queue_rcu_work() pattern in commit 48db892356d6 ("NFSD: Defer
sub-object cleanup in export put callbacks") because path_put()
and auth_domain_put() must run in process context after the RCU
grace period. The next patch routes unix_gid through the same
mechanism. Sending ip_map through sunrpc_cache_queue_release()
unifies all four cache_detail .put callbacks on a single release
path and removes the implicit reliance on every current and
future auth_ops .domain_release implementation deferring its own
kfree() behind call_rcu().
Replace the rcu_head field with an rcu_work, move the kfree() and
auth_domain_put() into a new ip_map_release() taking a
work_struct, and have ip_map_put() invoke INIT_RCU_WORK() and
sunrpc_cache_queue_release() in place of kfree_rcu(). Switch
ip_map_cache_destroy() to sunrpc_cache_destroy_net() so
per-namespace teardown waits for outstanding release work
before freeing the cache_detail.
Assisted-by: Claude:claude-opus-4-7[1m]
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
net/sunrpc/svcauth_unix.c | 25 ++++++++++++++++---------
1 file changed, 16 insertions(+), 9 deletions(-)
diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c
index 64a2658faddb..14688813c242 100644
--- a/net/sunrpc/svcauth_unix.c
+++ b/net/sunrpc/svcauth_unix.c
@@ -103,18 +103,26 @@ struct ip_map {
char m_class[8]; /* e.g. "nfsd" */
struct in6_addr m_addr;
struct unix_domain *m_client;
- struct rcu_head m_rcu;
+ struct rcu_work m_rwork;
};
+static void ip_map_release(struct work_struct *work)
+{
+ struct ip_map *im = container_of(to_rcu_work(work),
+ struct ip_map, m_rwork);
+
+ if (test_bit(CACHE_VALID, &im->h.flags) &&
+ !test_bit(CACHE_NEGATIVE, &im->h.flags))
+ auth_domain_put(&im->m_client->h);
+ kfree(im);
+}
+
static void ip_map_put(struct kref *kref)
{
- struct cache_head *item = container_of(kref, struct cache_head, ref);
- struct ip_map *im = container_of(item, struct ip_map,h);
+ struct ip_map *im = container_of(kref, struct ip_map, h.ref);
- if (test_bit(CACHE_VALID, &item->flags) &&
- !test_bit(CACHE_NEGATIVE, &item->flags))
- auth_domain_put(&im->m_client->h);
- kfree_rcu(im, m_rcu);
+ INIT_RCU_WORK(&im->m_rwork, ip_map_release);
+ sunrpc_cache_queue_release(&im->m_rwork);
}
static inline int hash_ip6(const struct in6_addr *ip)
@@ -1569,6 +1577,5 @@ void ip_map_cache_destroy(struct net *net)
sn->ip_map_cache = NULL;
cache_purge(cd);
- cache_unregister_net(cd, net);
- cache_destroy_net(cd, net);
+ sunrpc_cache_destroy_net(cd, net);
}
--
2.53.0
^ permalink raw reply related
* [PATCH 4/6] SUNRPC: Use shared release pattern for the unix_gid cache
From: Chuck Lever @ 2026-05-01 14:51 UTC (permalink / raw)
To: Misbah Anjum N, Jeff Layton, NeilBrown, Olga Kornievskaia,
Dai Ngo, Tom Talpey, Trond Myklebust, Anna Schumaker,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Yang Erkun
Cc: linux-nfs, linux-kernel, netdev, Chuck Lever
In-Reply-To: <20260501-cache-uaf-fix-v1-0-a49928bf4817@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
unix_gid_put() is already correct in that put_group_info() runs
inside its call_rcu() callback, after the RCU grace period.
This patch is a consistency change rather than a bug fix:
the three other cache_detail .put callbacks (svc_export,
svc_expkey, ip_map) now use the queue_rcu_work() pattern via
sunrpc_cache_queue_release(), and routing unix_gid through the same
path keeps a single release mechanism for all four caches.
Replace the rcu_head field with an rcu_work, rename
unix_gid_free() to unix_gid_release() and convert it to take
a work_struct, and have unix_gid_put() invoke INIT_RCU_WORK()
and sunrpc_cache_queue_release() in place of call_rcu().
Switch unix_gid_cache_destroy() to sunrpc_cache_destroy_net()
so per-namespace teardown waits for outstanding release work
before freeing the cache_detail.
Assisted-by: Claude:claude-opus-4-7[1m]
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
net/sunrpc/svcauth_unix.c | 21 ++++++++++-----------
1 file changed, 10 insertions(+), 11 deletions(-)
diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c
index 14688813c242..762cf03574b4 100644
--- a/net/sunrpc/svcauth_unix.c
+++ b/net/sunrpc/svcauth_unix.c
@@ -420,7 +420,7 @@ struct unix_gid {
struct cache_head h;
kuid_t uid;
struct group_info *gi;
- struct rcu_head rcu;
+ struct rcu_work rwork;
};
static int unix_gid_hash(kuid_t uid)
@@ -428,23 +428,23 @@ static int unix_gid_hash(kuid_t uid)
return hash_long(from_kuid(&init_user_ns, uid), GID_HASHBITS);
}
-static void unix_gid_free(struct rcu_head *rcu)
+static void unix_gid_release(struct work_struct *work)
{
- struct unix_gid *ug = container_of(rcu, struct unix_gid, rcu);
- struct cache_head *item = &ug->h;
+ struct unix_gid *ug = container_of(to_rcu_work(work),
+ struct unix_gid, rwork);
- if (test_bit(CACHE_VALID, &item->flags) &&
- !test_bit(CACHE_NEGATIVE, &item->flags))
+ if (test_bit(CACHE_VALID, &ug->h.flags) &&
+ !test_bit(CACHE_NEGATIVE, &ug->h.flags))
put_group_info(ug->gi);
kfree(ug);
}
static void unix_gid_put(struct kref *kref)
{
- struct cache_head *item = container_of(kref, struct cache_head, ref);
- struct unix_gid *ug = container_of(item, struct unix_gid, h);
+ struct unix_gid *ug = container_of(kref, struct unix_gid, h.ref);
- call_rcu(&ug->rcu, unix_gid_free);
+ INIT_RCU_WORK(&ug->rwork, unix_gid_release);
+ sunrpc_cache_queue_release(&ug->rwork);
}
static int unix_gid_match(struct cache_head *corig, struct cache_head *cnew)
@@ -899,8 +899,7 @@ void unix_gid_cache_destroy(struct net *net)
sn->unix_gid_cache = NULL;
cache_purge(cd);
- cache_unregister_net(cd, net);
- cache_destroy_net(cd, net);
+ sunrpc_cache_destroy_net(cd, net);
}
static struct unix_gid *unix_gid_lookup(struct cache_detail *cd, kuid_t uid)
--
2.53.0
^ permalink raw reply related
* [PATCH 5/6] SUNRPC: Hold cd->net for the lifetime of cache files
From: Chuck Lever @ 2026-05-01 14:51 UTC (permalink / raw)
To: Misbah Anjum N, Jeff Layton, NeilBrown, Olga Kornievskaia,
Dai Ngo, Tom Talpey, Trond Myklebust, Anna Schumaker,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Yang Erkun
Cc: linux-nfs, linux-kernel, netdev, Chuck Lever
In-Reply-To: <20260501-cache-uaf-fix-v1-0-a49928bf4817@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
Each per-net sunrpc cache exposes three files under
/proc/net/rpc/<cachename>/: content, channel, and flush.
Their open helpers (content_open, cache_open, open_flush) take a
reference on cd->owner via try_module_get() but no reference on
cd->net. When the network namespace exits, cache_unregister_net()
followed by cache_destroy_net() free cd->hash_table and cd
synchronously without RCU deferral. Any subsequent operation on
the still-open file dereferences the freed cache_detail.
The fault produced by sosreport on aarch64 and ppc64le shows the
typical signature: cache_check_rcu() faults reading h->flags off
a garbage cache_head pointer that came from __cache_seq_start()
walking a freed cd->hash_table. Commit e7fcf179b82d ("NFSD: Hold
net reference for the lifetime of /proc/fs/nfs/exports fd") closed
this hole only for the /proc/fs/nfs/exports file, which has its own
open path; the sunrpc cache files were left exposed.
Take a get_net(cd->net) in content_open(), cache_open(), and
open_flush() once the open has otherwise succeeded, and a matching
put_net() at the tail of each release helper. Holding the net
reference for the open file lifetime prevents the namespace from
exiting while a cache fd is open, which in turn prevents
cache_destroy_net() from running and freeing cd from under the
reader.
put_net() can drop the last namespace reference, in which case
__put_net() queues net_cleanup_work on netns_wq. That work runs
ops_undo_list() on another CPU, which invokes sunrpc_exit_net()
and frees cd via cache_destroy_net(). The release helper must
not dereference cd after put_net(): cache_release(),
content_release(), and release_flush() therefore capture
cd->owner and cd->net into local variables before calling
put_net(net) and module_put(owner).
Reported-by: Misbah Anjum N <misanjum@linux.ibm.com>
Closes: https://lore.kernel.org/linux-nfs/8cf80f450085ac17164e8fa1391e9635@linux.ibm.com/
Fixes: 1b10f0b603c0 ("SUNRPC: no need get cache ref when protected by rcu")
Assisted-by: Claude:claude-opus-4-7[1m]
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
net/sunrpc/cache.c | 22 +++++++++++++++++++---
1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
index 733bcd3daa46..be7a0c8c416e 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -1037,6 +1037,7 @@ static int cache_open(struct inode *inode, struct file *filp,
if (filp->f_mode & FMODE_WRITE)
atomic_inc(&cd->writers);
filp->private_data = rp;
+ get_net(cd->net);
return 0;
}
@@ -1044,6 +1045,8 @@ static int cache_release(struct inode *inode, struct file *filp,
struct cache_detail *cd)
{
struct cache_reader *rp = filp->private_data;
+ struct module *owner;
+ struct net *net;
if (rp) {
struct cache_request *rq = NULL;
@@ -1080,7 +1083,10 @@ static int cache_release(struct inode *inode, struct file *filp,
atomic_dec(&cd->writers);
cd->last_close = seconds_since_boot();
}
- module_put(cd->owner);
+ owner = cd->owner;
+ net = cd->net;
+ put_net(net);
+ module_put(owner);
return 0;
}
@@ -1466,14 +1472,19 @@ static int content_open(struct inode *inode, struct file *file,
seq = file->private_data;
seq->private = cd;
+ get_net(cd->net);
return 0;
}
static int content_release(struct inode *inode, struct file *file,
struct cache_detail *cd)
{
+ struct module *owner = cd->owner;
+ struct net *net = cd->net;
int ret = seq_release(inode, file);
- module_put(cd->owner);
+
+ put_net(net);
+ module_put(owner);
return ret;
}
@@ -1482,13 +1493,18 @@ static int open_flush(struct inode *inode, struct file *file,
{
if (!cd || !try_module_get(cd->owner))
return -EACCES;
+ get_net(cd->net);
return nonseekable_open(inode, file);
}
static int release_flush(struct inode *inode, struct file *file,
struct cache_detail *cd)
{
- module_put(cd->owner);
+ struct module *owner = cd->owner;
+ struct net *net = cd->net;
+
+ put_net(net);
+ module_put(owner);
return 0;
}
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox