* Re: [PATCH v2] nfc: nxp-nci: i2c: restore IRQ trigger fallback
From: Carl Lee @ 2026-03-30 10:04 UTC (permalink / raw)
To: Jakub Kicinski
Cc: linux-kernel, krzk, netdev, carl.lee, peter.shen, colin.huang2
In-Reply-To: <20260316192609.3e001253@kernel.org>
On Mon, Mar 16, 2026 at 07:26:09PM -0700, Jakub Kicinski wrote:
> On Thu, 12 Mar 2026 10:51:35 +0800 Carl Lee via B4 Relay wrote:
> > From: Carl Lee <carl.lee@amd.com>
> >
> > The driver previously relied on IRQF_TRIGGER_RISING when requesting
> > the interrupt. This was removed to rely on the trigger type provided
> > by firmware.
> >
> > However, some platforms do not propagate the interrupt trigger type
> > to the IRQ descriptor, resulting in interrupts not being triggered.
> >
> > Use the trigger type provided by firmware when available and fall
> > back to the historically used rising-edge trigger otherwise.
>
> Sounds like a regression, if you can please mention which platform you
> hit the issue on, and please repost with a Fixes tag, presumably:
>
> Fixes: 57be33f85e36 ("nfc: nxp-nci: remove interrupt trigger type")
>
>
> > diff --git a/drivers/nfc/nxp-nci/i2c.c b/drivers/nfc/nxp-nci/i2c.c
> > index 6a5ce8ff91f0..7aaab92c616c 100644
> > --- a/drivers/nfc/nxp-nci/i2c.c
> > +++ b/drivers/nfc/nxp-nci/i2c.c
> > @@ -16,6 +16,7 @@
> > #include <linux/delay.h>
> > #include <linux/i2c.h>
> > #include <linux/interrupt.h>
> > +#include <linux/irq.h>
> > #include <linux/module.h>
> > #include <linux/nfc.h>
> > #include <linux/gpio/consumer.h>
> > @@ -268,6 +269,7 @@ static int nxp_nci_i2c_probe(struct i2c_client *client)
> > struct device *dev = &client->dev;
> > struct nxp_nci_i2c_phy *phy;
> > int r;
> > + unsigned long irqflags;
>
> nit: when you repost please order the variable lines longest to shortest
>
> struct device *dev = &client->dev;
> struct nxp_nci_i2c_phy *phy;
> + unsigned long irqflags;
> int r;
> --
> pw-bot: cr
>
Hi Jakub,
Thanks for the feedback and for the detailed investigation.
After further analysis, this issue is not related to the proposed change and does not appear to be reproducible on our side.
I will drop this patch for now.
Thanks!
^ permalink raw reply
* Re: [PATCH] net: lpc_eth: Fix a possible memory leak in lpc_mii_probe()
From: Vladimir Zapolskiy @ 2026-03-30 10:04 UTC (permalink / raw)
To: Ma Ke, piotr.wojtaszczyk, andrew+netdev, davem, edumazet, kuba,
pabeni, alexandre.belloni
Cc: linux-arm-kernel, netdev, linux-kernel, stable
In-Reply-To: <20260330081636.2887980-1-make24@iscas.ac.cn>
Hello Ma Ke,
On 3/30/26 11:16, Ma Ke wrote:
> lpc_mii_probe() calls of_phy_find_device() to obtain a phy_device
> pointer. of_phy_find_device() increments the refcount of the device.
> The current implementation does not decrement the refcount after using
> the pointer, which leads to a memory leak.
this is correct, there is an actual detected bug.
>
> Add phy_device_free() to balance the refcount.
But this does not sound right, you shoud use of_node_put(pldat->phy_node).
>
> Found by code review.
>
> Signed-off-by: Ma Ke <make24@iscas.ac.cn>
> Cc: stable@vger.kernel.org
> Fixes: 3503bf024b3e ("net: lpc_eth: parse phy nodes from device tree")
> ---
> drivers/net/ethernet/nxp/lpc_eth.c | 11 ++++++-----
> 1 file changed, 6 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/net/ethernet/nxp/lpc_eth.c b/drivers/net/ethernet/nxp/lpc_eth.c
> index 8b9a3e3bba30..8ce7c9bb6dd6 100644
> --- a/drivers/net/ethernet/nxp/lpc_eth.c
> +++ b/drivers/net/ethernet/nxp/lpc_eth.c
> @@ -751,7 +751,7 @@ static void lpc_handle_link_change(struct net_device *ndev)
> static int lpc_mii_probe(struct net_device *ndev)
> {
> struct netdata_local *pldat = netdev_priv(ndev);
> - struct phy_device *phydev;
> + struct phy_device *phydev, *phydev_tmp;
>
> /* Attach to the PHY */
> if (lpc_phy_interface_mode(&pldat->pdev->dev) == PHY_INTERFACE_MODE_MII)
> @@ -760,17 +760,18 @@ static int lpc_mii_probe(struct net_device *ndev)
> netdev_info(ndev, "using RMII interface\n");
>
> if (pldat->phy_node)
> - phydev = of_phy_find_device(pldat->phy_node);
> + phydev_tmp = of_phy_find_device(pldat->phy_node);
> else
> - phydev = phy_find_first(pldat->mii_bus);
> - if (!phydev) {
> + phydev_tmp = phy_find_first(pldat->mii_bus);
> + if (!phydev_tmp) {
I didn't get it, why the new phydev_tmp is needed above, please
restore the original code above.
> netdev_err(ndev, "no PHY found\n");
> return -ENODEV;
> }
>
> - phydev = phy_connect(ndev, phydev_name(phydev),
> + phydev = phy_connect(ndev, phydev_name(phydev_tmp),
> &lpc_handle_link_change,
> lpc_phy_interface_mode(&pldat->pdev->dev));
> + phy_device_free(phydev_tmp);
This is plainly wrong and has to be dropped or changed to
if (pldat->phy_node)
of_node_put(pldat->phy_node);
> if (IS_ERR(phydev)) {
> netdev_err(ndev, "Could not attach to PHY\n");
> return PTR_ERR(phydev);
Is it AI generated fix or what?.. The change looks bad, it introduces
more severe issues than it fixes.
If you think you cannot create a proper change, let me know.
--
Best wishes,
Vladimir
^ permalink raw reply
* [RFC net-next] tcp: Add TCP ROCCET congestion control module.
From: Tim Fuechsel @ 2026-03-30 10:13 UTC (permalink / raw)
To: David S. Miller, David Ahern, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Neal Cardwell, Kuniyuki Iwashima,
linux-kernel, netdev, Lukas Prause, Tim Fuechsel
TCP ROCCET is an extension of TCP CUBIC that improves its overall
performance. By its mode of function, CUBIC causes bufferbloat while
it tries to detect the available throughput of a network path. This is
particularly a problem with large buffers in mobile networks. A more
detailed description and analysis of this problem caused by TCP CUBIC
can be found in [1]. TCP ROCCET addresses this problem by adding two
additional metrics to detect congestion (queueing and bufferbloat)
on a network path. TCP ROCCET achieves better performance than CUBIC
and BBRv3, by maintaining similar throughput while reducing the latency.
In addition, TCP ROCCET does not have fairness issues when sharing a
link with TCP CUBIC and BBRv3. A paper that evaluates the performance
and function of TCP ROCCET has already been peer-reviewed and will be
presented at the WONS 2026 conference. A draft of this paper can be
found here [2].
[1] https://doi.org/10.1109/VTC2023-Fall60731.2023.10333357
[2] https://arxiv.org/abs/2510.25281
Signed-off-by: Lukas Prause <lukas.prause@ikt.uni-hannover.de>
Signed-off-by: Tim Fuechsel <t.fuechsel@gmx.de>
---
net/ipv4/Kconfig | 11 +
net/ipv4/Makefile | 1 +
net/ipv4/tcp_roccet.c | 671 ++++++++++++++++++++++++++++++++++++++++++
net/ipv4/tcp_roccet.h | 56 ++++
4 files changed, 739 insertions(+)
create mode 100644 net/ipv4/tcp_roccet.c
create mode 100644 net/ipv4/tcp_roccet.h
diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig
index 21e5164e30db..33625111c7f0 100644
--- a/net/ipv4/Kconfig
+++ b/net/ipv4/Kconfig
@@ -663,6 +663,17 @@ config TCP_CONG_CDG
delay gradients." In Networking 2011. Preprint:
http://caia.swin.edu.au/cv/dahayes/content/networking2011-cdg-preprint.pdf
+config TCP_CONG_ROCCET
+ tristate "ROCCET TCP"
+ default n
+ help
+ TCP ROCCET is a sender-side only modification of the TCP CUBIC
+ protocol stack that optimizes the performance of TCP congestion
+ control. Especially for networks with large buffers (wireless,
+ cellular networks), TCP ROCCET has improved performance by maintaining
+ similar throughput as CUBIC while reducing the latency.
+ For more information, see: https://arxiv.org/abs/2510.25281
+
config TCP_CONG_BBR
tristate "BBR TCP"
default n
diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile
index 7f9f98813986..82ed7989dcb3 100644
--- a/net/ipv4/Makefile
+++ b/net/ipv4/Makefile
@@ -45,6 +45,7 @@ obj-$(CONFIG_INET_TCP_DIAG) += tcp_diag.o
obj-$(CONFIG_INET_UDP_DIAG) += udp_diag.o
obj-$(CONFIG_INET_RAW_DIAG) += raw_diag.o
obj-$(CONFIG_TCP_CONG_BBR) += tcp_bbr.o
+obj-$(CONFIG_TCP_CONG_ROCCET) += tcp_roccet.o
obj-$(CONFIG_TCP_CONG_BIC) += tcp_bic.o
obj-$(CONFIG_TCP_CONG_CDG) += tcp_cdg.o
obj-$(CONFIG_TCP_CONG_CUBIC) += tcp_cubic.o
diff --git a/net/ipv4/tcp_roccet.c b/net/ipv4/tcp_roccet.c
new file mode 100644
index 000000000000..892faf907edb
--- /dev/null
+++ b/net/ipv4/tcp_roccet.c
@@ -0,0 +1,671 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TCP ROCCET: An RTT-Oriented CUBIC Congestion Control
+ * Extension for 5G and Beyond Networks
+ *
+ * TCP ROCCET is a new TCP congestion control
+ * algorithm suited for current cellular 5G NR beyond networks.
+ * It extends the kernel default congestion control CUBIC
+ * and improves its performance, and additionally solves an
+ * unwanted side effects of CUBIC’s implementation.
+ * ROCCET uses its own Slow Start, called LAUNCH, where loss
+ * is not considered as a congestion event.
+ * The congestion avoidance phase, called ORBITER, uses
+ * CUBIC's window growth function and adds, based on RTT
+ * and ACK rate, congestion events.
+ *
+ * A peer-reviewed paper on TCP ROCCET will be presented at the WONS 2026 conference.
+ * A draft of the paper is available here:
+ * https://arxiv.org/abs/2510.25281
+ *
+ *
+ * Further information about CUBIC:
+ * TCP CUBIC: Binary Increase Congestion control for TCP v2.3
+ * Home page:
+ * http://netsrv.csc.ncsu.edu/twiki/bin/view/Main/BIC
+ * This is from the implementation of CUBIC TCP in
+ * Sangtae Ha, Injong Rhee and Lisong Xu,
+ * "CUBIC: A New TCP-Friendly High-Speed TCP Variant"
+ * in ACM SIGOPS Operating System Review, July 2008.
+ * Available from:
+ * http://netsrv.csc.ncsu.edu/export/cubic_a_new_tcp_2008.pdf
+ *
+ * CUBIC integrates a new slow start algorithm, called HyStart.
+ * The details of HyStart are presented in
+ * Sangtae Ha and Injong Rhee,
+ * "Taming the Elephants: New TCP Slow Start", NCSU TechReport 2008.
+ * Available from:
+ * http://netsrv.csc.ncsu.edu/export/hystart_techreport_2008.pdf
+ *
+ * All testing results are available from:
+ * http://netsrv.csc.ncsu.edu/wiki/index.php/TCP_Testing
+ *
+ * Unless CUBIC is enabled and congestion window is large
+ * this behaves the same as the original Reno.
+ */
+
+#include "tcp_roccet.h"
+#include "linux/printk.h"
+#include <linux/btf.h>
+#include <linux/btf_ids.h>
+#include <linux/math64.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <net/tcp.h>
+
+/* Scale factor beta calculation (max_cwnd = snd_cwnd * beta) */
+#define BICTCP_BETA_SCALE 1024
+
+#define BICTCP_HZ 10 /* BIC HZ 2^10 = 1024 */
+
+/* Alpha value for the sRrTT multiplied by 100.
+ * Here 20 represents a value of 0.2
+ */
+#define ROCCET_ALPHA_TIMES_100 20
+
+/* The amount of seconds ROCCET stores a minRTT.
+ * Enable "calculate_min_rtt" first.
+ */
+#define ROCCET_RTT_LOOKBACK_S 10
+
+/* Parameters that are specific to the ROCCET-Algorithm */
+static int sr_rtt_upper_bound __read_mostly = 100;
+static int ack_rate_diff_ss __read_mostly = 10;
+static int ack_rate_diff_ca __read_mostly = 200;
+static bool calculate_min_rtt __read_mostly;
+static bool ignore_loss __read_mostly;
+static int roccet_min_rtt_interpolation_factor __read_mostly = 70;
+
+module_param(sr_rtt_upper_bound, int, 0644);
+MODULE_PARM_DESC(sr_rtt_upper_bound, "ROCCET's upper bound for srRTT.");
+module_param(ack_rate_diff_ss, int, 0644);
+MODULE_PARM_DESC(ack_rate_diff_ss,
+ "ROCCET's threshold to exit slow start if ACK-rate defer by given amount of segments.");
+module_param(ack_rate_diff_ca, int, 0644);
+MODULE_PARM_DESC(ack_rate_diff_ca,
+ "ROCCET's threshold for ack-rate and cum_cwnd, in percantage of the current cwnd.");
+module_param(calculate_min_rtt, bool, 0644);
+MODULE_PARM_DESC(calculate_min_rtt,
+ "Calculate min RTT if no lower RTT occurs after 10 sec.");
+module_param(ignore_loss, bool, 0644);
+MODULE_PARM_DESC(ignore_loss, "Ignore loss as a congestion event.");
+module_param(roccet_min_rtt_interpolation_factor, int, 0644);
+MODULE_PARM_DESC(roccet_min_rtt_interpolation_factor,
+ "ROCCET factor for interpolating the current RTT with the last minRTT (minRTT = (factor * currRTT + (100-factor) * minRTT) / 100)");
+
+static int fast_convergence __read_mostly = 1;
+static int beta __read_mostly = 717; /* = 717/1024 (BICTCP_BETA_SCALE) */
+static int initial_ssthresh __read_mostly;
+static int bic_scale __read_mostly = 41;
+static int tcp_friendliness __read_mostly = 1;
+
+static u32 cube_rtt_scale __read_mostly;
+static u32 beta_scale __read_mostly;
+static u64 cube_factor __read_mostly;
+
+/* Note parameters that are used for precomputing scale factors are read-only */
+module_param(fast_convergence, int, 0644);
+MODULE_PARM_DESC(fast_convergence, "turn on/off fast convergence");
+module_param(beta, int, 0644);
+MODULE_PARM_DESC(beta, "beta for multiplicative increase");
+module_param(initial_ssthresh, int, 0644);
+MODULE_PARM_DESC(initial_ssthresh, "initial value of slow start threshold");
+module_param(bic_scale, int, 0444);
+MODULE_PARM_DESC(bic_scale,
+ "scale (scaled by 1024) value for bic function (bic_scale/1024)");
+module_param(tcp_friendliness, int, 0644);
+MODULE_PARM_DESC(tcp_friendliness, "turn on/off tcp friendliness");
+
+static inline void roccettcp_reset(struct roccettcp *ca)
+{
+ memset(ca, 0, offsetof(struct roccettcp, curr_rtt));
+ ca->bw_limit.sum_cwnd = 1;
+ ca->bw_limit.sum_acked = 1;
+ ca->bw_limit.next_check = 0;
+ ca->curr_min_rtt_timed.rtt = ~0U;
+ ca->curr_min_rtt_timed.time = ~0U;
+ ca->last_rtt = 0;
+ ca->ece_received = false;
+}
+
+static inline void update_min_rtt(struct sock *sk)
+{
+ struct roccettcp *ca = inet_csk_ca(sk);
+ u32 now = jiffies_to_usecs(tcp_jiffies32);
+
+ if (now - ca->curr_min_rtt_timed.time >
+ ROCCET_RTT_LOOKBACK_S * USEC_PER_SEC &&
+ calculate_min_rtt) {
+ u32 new_min_rtt = max(ca->curr_rtt, 1);
+ u32 old_min_rtt = ca->curr_min_rtt_timed.rtt;
+
+ u32 interpolated_min_rtt =
+ (new_min_rtt * roccet_min_rtt_interpolation_factor +
+ old_min_rtt *
+ (100 - roccet_min_rtt_interpolation_factor)) /
+ 100;
+
+ ca->curr_min_rtt_timed.rtt = interpolated_min_rtt;
+ ca->curr_min_rtt_timed.time = now;
+ }
+
+ /* Check if new lower min RTT was found. If so, set it directly */
+ if (ca->curr_rtt < ca->curr_min_rtt_timed.rtt) {
+ ca->curr_min_rtt_timed.rtt = max(ca->curr_rtt, 1);
+ ca->curr_min_rtt_timed.time = now;
+ }
+}
+
+/* Return difference between last and current ack rate.
+ */
+static inline int get_ack_rate_diff(struct roccettcp *ca)
+{
+ return ca->ack_rate.last_rate - ca->ack_rate.curr_rate;
+}
+
+/* Update ack rate sampled by 100ms.
+ */
+static inline void update_ack_rate(struct sock *sk)
+{
+ struct roccettcp *ca = inet_csk_ca(sk);
+ u32 now = jiffies_to_usecs(tcp_jiffies32);
+ u32 interval = USEC_PER_MSEC * 100;
+
+ if ((u32)(now - ca->ack_rate.last_rate_time) >= interval) {
+ ca->ack_rate.last_rate_time = now;
+ ca->ack_rate.last_rate = ca->ack_rate.curr_rate;
+ ca->ack_rate.curr_rate = ca->ack_rate.cnt;
+ ca->ack_rate.cnt = 0;
+ } else {
+ ca->ack_rate.cnt += 1;
+ }
+}
+
+/* Compute srRTT.
+ */
+static inline void update_srrtt(struct sock *sk)
+{
+ struct roccettcp *ca = inet_csk_ca(sk);
+
+ if (ca->curr_min_rtt_timed.rtt == 0)
+ return;
+
+ /* Calculate the new rRTT (Scaled by 100).
+ * 100 * ((sRTT - sRTT_min) / sRTT_min)
+ */
+ u32 rrtt = (100 * (ca->curr_rtt - ca->curr_min_rtt_timed.rtt)) /
+ ca->curr_min_rtt_timed.rtt;
+
+ // (1 - alpha) * srRTT + alpha * rRTT
+ ca->curr_srrtt = ((100 - ROCCET_ALPHA_TIMES_100) * ca->curr_srrtt +
+ ROCCET_ALPHA_TIMES_100 * rrtt) / 100;
+}
+
+__bpf_kfunc static void roccettcp_init(struct sock *sk)
+{
+ struct roccettcp *ca = inet_csk_ca(sk);
+
+ roccettcp_reset(ca);
+
+ if (initial_ssthresh)
+ tcp_sk(sk)->snd_ssthresh = initial_ssthresh;
+
+ /* Initial roccet parameters */
+ ca->roccet_last_event_time_us = 0;
+ ca->curr_min_rtt = ~0U;
+ ca->ack_rate.last_rate = 0;
+ ca->ack_rate.last_rate_time = 0;
+ ca->ack_rate.curr_rate = 0;
+ ca->ack_rate.cnt = 0;
+}
+
+__bpf_kfunc static void roccettcp_cwnd_event(struct sock *sk,
+ enum tcp_ca_event event)
+{
+ if (event == CA_EVENT_TX_START) {
+ struct roccettcp *ca = inet_csk_ca(sk);
+ u32 now = tcp_jiffies32;
+ s32 delta;
+
+ delta = now - tcp_sk(sk)->lsndtime;
+
+ /* We were application limited (idle) for a while.
+ * Shift epoch_start to keep cwnd growth to cubic curve.
+ */
+ if (ca->epoch_start && delta > 0) {
+ ca->epoch_start += delta;
+ if (after(ca->epoch_start, now))
+ ca->epoch_start = now;
+ }
+ return;
+ }
+}
+
+/* calculate the cubic root of x using a table lookup followed by one
+ * Newton-Raphson iteration.
+ * Avg err ~= 0.195%
+ */
+static u32 cubic_root(u64 a)
+{
+ u32 x, b, shift;
+ /* cbrt(x) MSB values for x MSB values in [0..63].
+ * Precomputed then refined by hand - Willy Tarreau
+ *
+ * For x in [0..63],
+ * v = cbrt(x << 18) - 1
+ * cbrt(x) = (v[x] + 10) >> 6
+ */
+ static const u8 v[] = {
+ /* 0x00 */ 0, 54, 54, 54, 118, 118, 118, 118,
+ /* 0x08 */ 123, 129, 134, 138, 143, 147, 151, 156,
+ /* 0x10 */ 157, 161, 164, 168, 170, 173, 176, 179,
+ /* 0x18 */ 181, 185, 187, 190, 192, 194, 197, 199,
+ /* 0x20 */ 200, 202, 204, 206, 209, 211, 213, 215,
+ /* 0x28 */ 217, 219, 221, 222, 224, 225, 227, 229,
+ /* 0x30 */ 231, 232, 234, 236, 237, 239, 240, 242,
+ /* 0x38 */ 244, 245, 246, 248, 250, 251, 252, 254,
+ };
+
+ b = fls64(a);
+ if (b < 7) {
+ /* a in [0..63] */
+ return ((u32)v[(u32)a] + 35) >> 6;
+ }
+
+ b = ((b * 84) >> 8) - 1;
+ shift = (a >> (b * 3));
+
+ x = ((u32)(((u32)v[shift] + 10) << b)) >> 6;
+
+ /* Newton-Raphson iteration
+ * 2
+ * x = ( 2 * x + a / x ) / 3
+ * k+1 k k
+ */
+ x = (2 * x + (u32)div64_u64(a, (u64)x * (u64)(x - 1)));
+ x = ((x * 341) >> 10);
+ return x;
+}
+
+/* Compute congestion window to use.
+ */
+static inline void bictcp_update(struct roccettcp *ca, u32 cwnd, u32 acked)
+{
+ u32 delta, bic_target, max_cnt;
+ u64 offs, t;
+
+ ca->ack_cnt += acked; /* count the number of ACKed packets */
+
+ if (ca->last_cwnd == cwnd &&
+ (s32)(tcp_jiffies32 - ca->last_time) <= HZ / 32)
+ return;
+
+ /* The CUBIC function can update ca->cnt at most once per jiffy.
+ * On all cwnd reduction events, ca->epoch_start is set to 0,
+ * which will force a recalculation of ca->cnt.
+ */
+ if (ca->epoch_start && tcp_jiffies32 == ca->last_time)
+ goto tcp_friendliness;
+
+ ca->last_cwnd = cwnd;
+ ca->last_time = tcp_jiffies32;
+
+ if (ca->epoch_start == 0) {
+ ca->epoch_start = tcp_jiffies32; /* record beginning */
+ ca->ack_cnt = acked; /* start counting */
+ ca->tcp_cwnd = cwnd; /* syn with cubic */
+
+ if (ca->last_max_cwnd <= cwnd) {
+ ca->bic_K = 0;
+ ca->bic_origin_point = cwnd;
+ } else {
+ /* Compute new K based on
+ * (wmax-cwnd) * (srtt>>3 / HZ) / c * 2^(3*bictcp_HZ)
+ */
+ ca->bic_K = cubic_root(cube_factor *
+ (ca->last_max_cwnd - cwnd));
+ ca->bic_origin_point = ca->last_max_cwnd;
+ }
+ }
+
+ /* cubic function - calc */
+ /* calculate c * time^3 / rtt,
+ * while considering overflow in calculation of time^3
+ * (so time^3 is done by using 64 bit)
+ * and without the support of division of 64bit numbers
+ * (so all divisions are done by using 32 bit)
+ * also NOTE the unit of those variables
+ * time = (t - K) / 2^bictcp_HZ
+ * c = bic_scale >> 10
+ * rtt = (srtt >> 3) / HZ
+ * !!! The following code does not have overflow problems,
+ * if the cwnd < 1 million packets !!!
+ */
+
+ t = (s32)(tcp_jiffies32 - ca->epoch_start);
+ t += usecs_to_jiffies(ca->delay_min);
+
+ /* change the unit from HZ to bictcp_HZ */
+ t <<= BICTCP_HZ;
+ do_div(t, HZ);
+
+ if (t < ca->bic_K) /* t - K */
+ offs = ca->bic_K - t;
+ else
+ offs = t - ca->bic_K;
+
+ /* c/rtt * (t-K)^3 */
+ delta = (cube_rtt_scale * offs * offs * offs) >> (10 + 3 * BICTCP_HZ);
+ if (t < ca->bic_K) /* below origin*/
+ bic_target = ca->bic_origin_point - delta;
+ else /* above origin*/
+ bic_target = ca->bic_origin_point + delta;
+
+ /* cubic function - calc bictcp_cnt*/
+ if (bic_target > cwnd)
+ ca->cnt = cwnd / (bic_target - cwnd);
+ else
+ ca->cnt = 100 * cwnd; /* very small increment*/
+
+ /* The initial growth of cubic function may be too conservative
+ * when the available bandwidth is still unknown.
+ */
+ if (ca->last_max_cwnd == 0 && ca->cnt > 20)
+ ca->cnt = 20; /* increase cwnd 5% per RTT */
+
+tcp_friendliness:
+ /* TCP Friendly */
+ if (tcp_friendliness) {
+ u32 scale = beta_scale;
+
+ delta = (cwnd * scale) >> 3;
+ while (ca->ack_cnt > delta) { /* update tcp cwnd */
+ ca->ack_cnt -= delta;
+ ca->tcp_cwnd++;
+ }
+
+ if (ca->tcp_cwnd > cwnd) { /* if bic is slower than tcp */
+ delta = ca->tcp_cwnd - cwnd;
+ max_cnt = cwnd / delta;
+ if (ca->cnt > max_cnt)
+ ca->cnt = max_cnt;
+ }
+ }
+
+ /* The maximum rate of cwnd increase CUBIC allows is 1 packet per
+ * 2 packets ACKed, meaning cwnd grows at 1.5x per RTT.
+ */
+ ca->cnt = max(ca->cnt, 2U);
+}
+
+__bpf_kfunc static void roccettcp_cong_avoid(struct sock *sk, u32 ack,
+ u32 acked)
+{
+ struct tcp_sock *tp = tcp_sk(sk);
+ struct roccettcp *ca = inet_csk_ca(sk);
+
+ u32 now = jiffies_to_usecs(tcp_jiffies32);
+ u32 bw_limit_detect = 0;
+ u32 roccet_xj;
+ u32 jitter;
+
+ if (ca->last_rtt > ca->curr_rtt)
+ jitter = ca->last_rtt - ca->curr_rtt;
+ else
+ jitter = ca->curr_rtt - ca->last_rtt;
+
+ /* Update roccet parameters */
+ update_ack_rate(sk);
+ update_min_rtt(sk);
+ update_srrtt(sk);
+
+ /* ROCCET drain.
+ * Do not increase the cwnd for 100ms after a roccet congestion event
+ */
+ if (now - ca->roccet_last_event_time_us <= 100 * USEC_PER_MSEC)
+ return;
+
+ /* LAUNCH: Detect an exit point for tcp slow start
+ * in networks with large buffers of multiple BDP
+ * Like in cellular networks (5G, ...).
+ * Or exit LAUNCH if cwnd is too large for application layer
+ * data rate.
+ */
+
+ if ((tcp_in_slow_start(tp) && ca->curr_srrtt > sr_rtt_upper_bound &&
+ get_ack_rate_diff(ca) >= ack_rate_diff_ss) ||
+ (!tcp_is_cwnd_limited(sk) && tcp_in_slow_start(tp))) {
+ ca->epoch_start = 0;
+
+ /* Handle initial slow start. Here we observe the most problems */
+ if (tp->snd_ssthresh == TCP_INFINITE_SSTHRESH) {
+ tcp_sk(sk)->snd_ssthresh = tcp_snd_cwnd(tp) / 2;
+ tcp_snd_cwnd_set(tp, tcp_snd_cwnd(tp) / 2);
+ } else {
+ tcp_sk(sk)->snd_ssthresh =
+ tcp_snd_cwnd(tp) - (tcp_snd_cwnd(tp) / 3);
+ tcp_snd_cwnd_set(tp, tcp_snd_cwnd(tp) -
+ (tcp_snd_cwnd(tp) / 3));
+ }
+ ca->roccet_last_event_time_us = now;
+ return;
+ }
+
+ if (tcp_in_slow_start(tp)) {
+ acked = tcp_slow_start(tp, acked);
+ if (!acked)
+ return;
+ }
+
+ if (ca->bw_limit.next_check == 0)
+ ca->bw_limit.next_check = now + 5 * ca->curr_rtt;
+
+ ca->bw_limit.sum_cwnd += tcp_snd_cwnd(tp);
+ ca->bw_limit.sum_acked += acked;
+
+ if (ca->bw_limit.next_check < now) {
+ /* We send more data as we got acked in the last 5 RTTs */
+ if ((ca->bw_limit.sum_cwnd * 100) / ca->bw_limit.sum_acked >=
+ ack_rate_diff_ca)
+ bw_limit_detect = 1;
+
+ /* reset struct and set next end of period */
+ ca->bw_limit.sum_cwnd = 1;
+
+ /* set to 1 to avoid division by zero */
+ ca->bw_limit.sum_acked = 1;
+ ca->bw_limit.next_check = now + 5 * ca->curr_rtt;
+ }
+
+ /* Respects the jitter of the connection and add it on top of the upper bound
+ * for the srRTT
+ */
+ roccet_xj = ((jitter * 100) / ca->curr_min_rtt_timed.rtt) +
+ sr_rtt_upper_bound;
+ if (roccet_xj < sr_rtt_upper_bound)
+ roccet_xj = sr_rtt_upper_bound;
+
+ if (ca->curr_srrtt > roccet_xj && (bw_limit_detect || ca->ece_received)) {
+ if (ca->ece_received)
+ ca->ece_received = false;
+ ca->epoch_start = 0;
+ ca->roccet_last_event_time_us = now;
+ ca->cnt = 100 * tcp_snd_cwnd(tp);
+
+ /* Set Wmax if cwnd is larger than the old Wmax */
+ if (tcp_snd_cwnd(tp) > ca->last_max_cwnd)
+ ca->last_max_cwnd = tcp_snd_cwnd(tp);
+
+ tcp_snd_cwnd_set(tp, min(tp->snd_cwnd_clamp,
+ max((tcp_snd_cwnd(tp) * beta) / BICTCP_BETA_SCALE, 2U)));
+ tp->snd_ssthresh = tcp_snd_cwnd(tp);
+ return;
+ }
+
+ /* Terminates this function if cwnd is not fully utilized.
+ * In mobile networks like 5G, this termination causes the cwnd to be frozen at
+ * an excessively high value. This is because slow start or HyStart massively
+ * exceed the available bandwidth and leave the cwnd at an excessively high
+ * value. The cwnd cannot therefore be fully utilized because it is limited by
+ * the connection capacity.
+ */
+ if (!tcp_is_cwnd_limited(sk))
+ return;
+
+ bictcp_update(ca, tcp_snd_cwnd(tp), acked);
+ tcp_cong_avoid_ai(tp, max(1, ca->cnt), acked);
+}
+
+__bpf_kfunc static u32 roccettcp_recalc_ssthresh(struct sock *sk)
+{
+ const struct tcp_sock *tp = tcp_sk(sk);
+ struct roccettcp *ca = inet_csk_ca(sk);
+
+ if (ignore_loss)
+ return tcp_snd_cwnd(tp);
+
+ /* Don't exit slow start if loss occurs. */
+ if (tcp_in_slow_start(tp))
+ return tcp_snd_cwnd(tp);
+
+ ca->epoch_start = 0; /* end of epoch */
+
+ /* Wmax and fast convergence */
+ if (tcp_snd_cwnd(tp) < ca->last_max_cwnd && fast_convergence)
+ ca->last_max_cwnd =
+ (tcp_snd_cwnd(tp) * (BICTCP_BETA_SCALE + beta)) /
+ (2 * BICTCP_BETA_SCALE);
+ else
+ ca->last_max_cwnd = tcp_snd_cwnd(tp);
+
+ return max((tcp_snd_cwnd(tp) * beta) / BICTCP_BETA_SCALE, 2U);
+}
+
+__bpf_kfunc static void roccettcp_state(struct sock *sk, u8 new_state)
+{
+ struct roccettcp *ca = inet_csk_ca(sk);
+
+ if (new_state == TCP_CA_Loss)
+ roccettcp_reset(ca);
+}
+
+__bpf_kfunc static void roccettcp_acked(struct sock *sk,
+ const struct ack_sample *sample)
+{
+ struct roccettcp *ca = inet_csk_ca(sk);
+
+ /* Some calls are for duplicates without timestamps */
+ if (sample->rtt_us < 0)
+ return;
+
+ /* Discard delay samples right after fast recovery */
+ if (ca->epoch_start && (s32)(tcp_jiffies32 - ca->epoch_start) < HZ)
+ return;
+
+ u32 delay = sample->rtt_us;
+
+ if (delay == 0)
+ delay = 1;
+
+ /* first time call or link delay decreases */
+ if (ca->delay_min == 0 || ca->delay_min > delay)
+ ca->delay_min = delay;
+
+ /* Get valid sample for roccet */
+ if (sample->rtt_us > 0) {
+ ca->last_rtt = ca->curr_rtt;
+ ca->curr_rtt = sample->rtt_us;
+ }
+}
+
+__bpf_kfunc static void roccet_in_ack_event(struct sock *sk, u32 flags)
+{
+ struct roccettcp *ca = inet_csk_ca(sk);
+
+ /* Handle ECE bit.
+ * Processing of ECE events is done in roccettcp_cong_avoid()
+ */
+ if (flags & CA_ACK_ECE)
+ ca->ece_received = true;
+}
+
+static struct tcp_congestion_ops roccet_tcp __read_mostly = {
+ .init = roccettcp_init,
+ .ssthresh = roccettcp_recalc_ssthresh,
+ .cong_avoid = roccettcp_cong_avoid,
+ .set_state = roccettcp_state,
+ .undo_cwnd = tcp_reno_undo_cwnd,
+ .cwnd_event = roccettcp_cwnd_event,
+ .pkts_acked = roccettcp_acked,
+ .in_ack_event = roccet_in_ack_event,
+ .owner = THIS_MODULE,
+ .name = "roccet",
+};
+
+BTF_KFUNCS_START(tcp_roccet_check_kfunc_ids)
+BTF_ID_FLAGS(func, roccettcp_init)
+BTF_ID_FLAGS(func, roccettcp_recalc_ssthresh)
+BTF_ID_FLAGS(func, roccettcp_cong_avoid)
+BTF_ID_FLAGS(func, roccettcp_state)
+BTF_ID_FLAGS(func, roccettcp_cwnd_event)
+BTF_ID_FLAGS(func, roccettcp_acked)
+BTF_KFUNCS_END(tcp_roccet_check_kfunc_ids)
+
+static const struct btf_kfunc_id_set tcp_roccet_kfunc_set = {
+ .owner = THIS_MODULE,
+ .set = &tcp_roccet_check_kfunc_ids,
+};
+
+static int __init roccettcp_register(void)
+{
+ int ret;
+
+ BUILD_BUG_ON(sizeof(struct roccettcp) > ICSK_CA_PRIV_SIZE);
+
+ /* Precompute a bunch of the scaling factors that are used per-packet
+ * based on SRTT of 100ms
+ */
+
+ beta_scale =
+ 8 * (BICTCP_BETA_SCALE + beta) / 3 / (BICTCP_BETA_SCALE - beta);
+
+ cube_rtt_scale = (bic_scale * 10); /* 1024*c/rtt */
+
+ /* calculate the "K" for (wmax-cwnd) = c/rtt * K^3
+ * so K = cubic_root( (wmax-cwnd)*rtt/c )
+ * the unit of K is bictcp_HZ=2^10, not HZ
+ *
+ * c = bic_scale >> 10
+ * rtt = 100ms
+ *
+ * the following code has been designed and tested for
+ * cwnd < 1 million packets
+ * RTT < 100 seconds
+ * HZ < 1,000,00 (corresponding to 10 nano-second)
+ */
+
+ /* 1/c * 2^2*bictcp_HZ * srtt */
+ cube_factor = 1ull << (10 + 3 * BICTCP_HZ); /* 2^40 */
+
+ /* divide by bic_scale and by constant Srtt (100ms) */
+ do_div(cube_factor, bic_scale * 10);
+
+ ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS,
+ &tcp_roccet_kfunc_set);
+ if (ret < 0)
+ return ret;
+ return tcp_register_congestion_control(&roccet_tcp);
+}
+
+static void __exit roccettcp_unregister(void)
+{
+ tcp_unregister_congestion_control(&roccet_tcp);
+}
+
+module_init(roccettcp_register);
+module_exit(roccettcp_unregister);
+
+MODULE_AUTHOR("Lukas Prause, Tim Füchsel");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("ROCCET TCP");
+MODULE_VERSION("1.0");
diff --git a/net/ipv4/tcp_roccet.h b/net/ipv4/tcp_roccet.h
new file mode 100644
index 000000000000..a7201866d5ac
--- /dev/null
+++ b/net/ipv4/tcp_roccet.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * TCP ROCCET congestion control interface
+ */
+#ifndef __TCP_ROCCET_H
+#define __TCP_ROCCET_H 1
+
+#include <linux/math64.h>
+
+struct ack_rate {
+ u16 last_rate; /* Last ACK-rate */
+ u32 last_rate_time; /* Timestamp of the last ACK-rate */
+ u16 curr_rate; /* Current ACK-rate */
+ u16 cnt; /* Used for counting acks */
+};
+
+struct bandwidth_limit_detect {
+ u32 sum_cwnd; /* sum of cwnd during time interval */
+ u32 sum_acked; /* sum of received acks during time interval */
+ u32 next_check; /* end/upper bound of time interval */
+};
+
+struct timed_rtt {
+ u32 time; /* Time of recording */
+ u32 rtt; /* Measured RTT */
+};
+
+/* Based on the BICTCP struct with additions specific for the ROCCET-Algorithm */
+struct roccettcp {
+ u32 cnt; /* increase cwnd by 1 after ACKs */
+ u32 last_max_cwnd; /* last maximum snd_cwnd */
+ u32 last_cwnd; /* the last snd_cwnd */
+ u32 last_time; /* time when updated last_cwnd */
+ u32 bic_origin_point; /* origin point of bic function */
+ u32 bic_K; /* time to origin point from the
+ * beginning of the current epoch
+ */
+ u32 delay_min; /* min delay (usec) */
+ u32 epoch_start; /* beginning of an epoch */
+ u32 ack_cnt; /* number of acks */
+ u32 tcp_cwnd; /* estimated tcp cwnd */
+ u32 curr_rtt; /* the minimum rtt of current round */
+
+ u32 roccet_last_event_time_us; /* The last time ROCCET was triggered */
+ bool ece_received; /* Set to true if an ECE bit was received */
+ u32 curr_min_rtt; /* The current observed minRTT */
+ struct timed_rtt curr_min_rtt_timed; /* The current observed minRTT with
+ * the timestamp when it was observed
+ */
+ u32 curr_srrtt; /* The srRTT calculated based on the latest ACK */
+ struct ack_rate ack_rate; /* The last and the current ACK rate */
+ struct bandwidth_limit_detect bw_limit;
+ u32 last_rtt; /* Used for jitter calculation */
+};
+
+#endif /* __TCP_ROCCET_H */
--
2.43.0
^ permalink raw reply related
* RE: [Intel-wired-lan] [PATCH net v3] igb: remove napi_synchronize() in igb_down()
From: Holda, Patryk @ 2026-03-30 10:16 UTC (permalink / raw)
To: Fijalkowski, Maciej, Alex Dvoretsky
Cc: intel-wired-lan@lists.osuosl.org, netdev@vger.kernel.org,
Loktionov, Aleksandr, Nguyen, Anthony L, Kitszel, Przemyslaw,
kurt@linutronix.de, stable@vger.kernel.org
In-Reply-To: <abPY+aT0SWuixsmN@boxer>
> -----Original Message-----
> From: Intel-wired-lan <intel-wired-lan-bounces@osuosl.org> On Behalf Of
> Maciej Fijalkowski
> Sent: Friday, March 13, 2026 10:29 AM
> To: Alex Dvoretsky <advoretsky@gmail.com>
> Cc: intel-wired-lan@lists.osuosl.org; netdev@vger.kernel.org; Loktionov,
> Aleksandr <aleksandr.loktionov@intel.com>; Nguyen, Anthony L
> <anthony.l.nguyen@intel.com>; Kitszel, Przemyslaw
> <przemyslaw.kitszel@intel.com>; kurt@linutronix.de; stable@vger.kernel.org
> Subject: Re: [Intel-wired-lan] [PATCH net v3] igb: remove napi_synchronize()
> in igb_down()
>
> On Thu, Mar 12, 2026 at 02:52:55PM +0100, Alex Dvoretsky wrote:
> > When an AF_XDP zero-copy application terminates abruptly (e.g., kill
> > -9), the XSK buffer pool is destroyed but NAPI polling continues.
> > igb_clean_rx_irq_zc() repeatedly returns the full budget, preventing
> > napi_complete_done() from clearing NAPI_STATE_SCHED.
> >
> > igb_down() calls napi_synchronize() before napi_disable() for each
> > queue vector. napi_synchronize() spins waiting for NAPI_STATE_SCHED to
> > clear, which never happens. igb_down() blocks indefinitely, the TX
> > watchdog fires, and the TX queue remains permanently stalled.
> >
> > napi_disable() already handles this correctly: it sets NAPI_STATE_DISABLE.
> > After a full-budget poll, __napi_poll() checks napi_disable_pending().
> > If set, it forces completion and clears NAPI_STATE_SCHED, breaking the
> > loop that napi_synchronize() cannot.
> >
> > napi_synchronize() was added in commit 41f149a285da ("igb: Fix
> > possible panic caused by Rx traffic arrival while interface is down").
> > napi_disable() provides stronger guarantees: it prevents further
> > scheduling and waits for any active poll to exit.
> > Other Intel drivers (ixgbe, ice, i40e) use napi_disable() without a
> > preceding napi_synchronize() in their down paths.
> >
> > Remove redundant napi_synchronize() call and reorder napi_disable()
> > before igb_set_queue_napi() so the queue-to-NAPI mapping is only
> > cleared after polling has fully stopped.
> >
> > Fixes: 2c6196013f84 ("igb: Add AF_XDP zero-copy Rx support")
> > Cc: stable@vger.kernel.org
> > Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
> > Signed-off-by: Alex Dvoretsky <advoretsky@gmail.com>
>
> Suggested-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
> Reviewed-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
>
> > ---
> > Agreed, that looks cleaner — no reason to touch the NAPI plumbing
> > while the poll could still be running.
> >
> > v3:
> > - Reorder napi_disable() before igb_set_queue_napi() per Aleksandr
> > Loktionov's suggestion.
> >
> > v2:
> > - Replaced 3-patch series with single napi_synchronize() removal,
> > per Maciej Fijalkowski's suggestion. napi_disable() handles the
> > stuck NAPI poll via NAPI_STATE_DISABLE, making the __IGB_DOWN
> > checks in igb_clean_rx_irq_zc() and igb_tx_timeout(), and the
> > transition guards in igb_xdp_setup(), all unnecessary.
> > - Tested on Intel I210 (igb) with AF_XDP zero-copy: full E2E
> > traffic suite, graceful shutdown, and 5x kill-9 stress cycles.
> > Zero tx_timeout events.
> >
> > drivers/net/ethernet/intel/igb/igb_main.c | 3 +--
> > 1 file changed, 1 insertion(+), 2 deletions(-)
> >
> > diff --git a/drivers/net/ethernet/intel/igb/igb_main.c
> > b/drivers/net/ethernet/intel/igb/igb_main.c
> > index 7c41e32256fa..0793842cb937 100644
> > --- a/drivers/net/ethernet/intel/igb/igb_main.c
> > +++ b/drivers/net/ethernet/intel/igb/igb_main.c
> > @@ -2203,9 +2203,8 @@ void igb_down(struct igb_adapter *adapter)
> >
> > for (i = 0; i < adapter->num_q_vectors; i++) {
> > if (adapter->q_vector[i]) {
> > - napi_synchronize(&adapter->q_vector[i]->napi);
> > - igb_set_queue_napi(adapter, i, NULL);
> > napi_disable(&adapter->q_vector[i]->napi);
> > + igb_set_queue_napi(adapter, i, NULL);
> > }
> > }
> >
> > --
> > 2.51.0
> >
Tested-by: Patryk Holda <patryk.holda@intel.com>
^ permalink raw reply
* Re: [PATCH v24 06/11] cxl/hdm: Add support for getting region from committed decoder
From: Alejandro Lucero Palau @ 2026-03-30 10:26 UTC (permalink / raw)
To: Cheatham, Benjamin, alejandro.lucero-palau
Cc: linux-cxl, netdev, dave.jiang, dan.j.williams, edward.cree, davem,
kuba, pabeni, edumazet
In-Reply-To: <40f6ebb4-383c-4526-a526-c405a1c702ea@amd.com>
On 3/27/26 19:36, Cheatham, Benjamin wrote:
> On 3/23/2026 6:31 AM, alejandro.lucero-palau@amd.com wrote:
>> From: Alejandro Lucero <alucerop@amd.com>
>>
>> A Type2 device configured by the BIOS can already have its HDM
>> committed. Add a cxl_get_committed_decoder() function for cheking
>> so after memdev creation. A CXL region should have been created
>> during memdev initialization, therefore a Type2 driver can ask for
>> such a region for working with the HPA. If the HDM is not committed,
>> a Type2 driver will create the region after obtaining proper HPA
>> and DPA space.
>>
>> Signed-off-by: Alejandro Lucero <alucerop@amd.com>
>> ---
>> drivers/cxl/core/hdm.c | 39 +++++++++++++++++++++++++++++++++++++++
>> include/cxl/cxl.h | 3 +++
>> 2 files changed, 42 insertions(+)
>>
>> diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
>> index c222e98ae736..006be88efaa5 100644
>> --- a/drivers/cxl/core/hdm.c
>> +++ b/drivers/cxl/core/hdm.c
>> @@ -686,6 +686,45 @@ int cxl_dpa_alloc(struct cxl_endpoint_decoder *cxled, u64 size)
>> return devm_add_action_or_reset(&port->dev, cxl_dpa_release, cxled);
>> }
>>
>> +static int find_committed_endpoint_decoder(struct device *dev, const void *data)
>> +{
>> + struct cxl_endpoint_decoder *cxled;
>> + struct cxl_port *port;
>> +
>> + if (!is_endpoint_decoder(dev))
>> + return 0;
>> +
>> + cxled = to_cxl_endpoint_decoder(dev);
>> + port = cxled_to_port(cxled);
>> +
>> + return cxled->cxld.id == port->hdm_end;
>> +}
> I am still not convinced this is the right check. I have two reasons:
>
> 1. I would be surprised if just always taking the last endpoint decoder is what we want here.
> There's no guarantee that the last endpoint decoder is programmed to match the range, which is only
> now an issue since you're not looking at uncommitted decoders anymore. Suggestion below...
>
> 2. I'm sure it's possible, but I'm not aware of any case (outside of interleaving) that there
> would be multiple endpoint decoders above a singular port. So, it may be that the whole check is
> overkill and we can just cast the endpoint port into the endpoint decoder and be done with it.
> If the rest of the set hasn't changed from v23, interleaving is still broken. At that point,
> I'd say ditch the interleaving support for now and we can complicate all of this later.
>
> Suggestion: If you don't want to abandon interleaving or I'm wrong about the topology, checking
> if the decoder is committed by checking the HDM decoder registers directly is probably the
> correct way to do this.
>
> That gets you *a* decoder, but I'm not sure it's the right one. Getting the right one would require
> checking the resource attached to the decoder and verifying it matches the HPA range expected by
> the accelerator and/or region. I don't know if we expect the accelerator to know the HPA range
> at this point, in which case this is pretty easy. I have an inkling of what to do otherwise, but
> I'd need time to think it through.
I'm changing this code in v25 not using the current check but just if
the decoder is enabled inside the helper function for any endpoint
decoder child. This assumes there can only be one or just one supported
what I think is fine for current expectations regarding Type2 devices. A
type2 using more than one HDM will need its driver to support that.
Regarding interleaving, after your comments I decided the support was
not consistent for the case of none committed decoder, so current v24
and v25 are only covering the case of a committed decoder what is the
expectation from current known BIOSes.
Thank you.
>> +
>> +struct cxl_endpoint_decoder *cxl_get_committed_decoder(struct cxl_memdev *cxlmd,
>> + struct cxl_region **cxlr)
>> +{
>> + struct cxl_port *endpoint = cxlmd->endpoint;
>> + struct cxl_endpoint_decoder *cxled;
>> + struct device *cxled_dev;
>> +
>> + if (!endpoint)
>> + return NULL;
>> +
>> + guard(rwsem_read)(&cxl_rwsem.dpa);
>> + cxled_dev = device_find_child(&endpoint->dev, NULL,
>> + find_committed_endpoint_decoder);
>> +
>> + if (!cxled_dev)
>> + return NULL;
>> +
>> + cxled = to_cxl_endpoint_decoder(cxled_dev);
>> + *cxlr = cxled->cxld.region;
> I think the region type should be set already at this point, so I'd check it's set to
> CXL_DECODER_DEVMEM here as a sanity check.
>
>> +
>> + put_device(cxled_dev);
>> + return cxled;
>> +}
>> +EXPORT_SYMBOL_NS_GPL(cxl_get_committed_decoder, "CXL");
>> +
>> static void cxld_set_interleave(struct cxl_decoder *cxld, u32 *ctrl)
>> {
>> u16 eig;
>> diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
>> index 10a9b8fa2f6b..2a61138e2a73 100644
>> --- a/include/cxl/cxl.h
>> +++ b/include/cxl/cxl.h
>> @@ -231,4 +231,7 @@ struct cxl_dev_state *_devm_cxl_dev_state_create(struct device *dev,
>> int cxl_set_capacity(struct cxl_dev_state *cxlds, u64 capacity);
>> struct cxl_memdev *devm_cxl_add_memdev(struct cxl_dev_state *cxlds,
>> const struct cxl_memdev_attach *attach);
>> +struct cxl_region;
>> +struct cxl_endpoint_decoder *cxl_get_committed_decoder(struct cxl_memdev *cxlmd,
>> + struct cxl_region **cxlr);
>> #endif /* __CXL_CXL_H__ */
^ permalink raw reply
* [PATCH 1/2] net: hso: refactor endpoint lookup
From: Johan Hovold @ 2026-03-30 10:26 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: linux-usb, netdev, linux-kernel, Johan Hovold
In-Reply-To: <20260330102611.1671546-1-johan@kernel.org>
Use the common USB helpers for looking up bulk and interrupt endpoints
instead of a custom implementation.
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/net/usb/hso.c | 65 +++++++++++--------------------------------
1 file changed, 17 insertions(+), 48 deletions(-)
diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c
index 1825f7cf5dc0..c1aec67688ae 100644
--- a/drivers/net/usb/hso.c
+++ b/drivers/net/usb/hso.c
@@ -298,8 +298,6 @@ static int hso_mux_submit_intr_urb(struct hso_shared_int *mux_int,
struct usb_device *usb, gfp_t gfp);
static void handle_usb_error(int status, const char *function,
struct hso_device *hso_dev);
-static struct usb_endpoint_descriptor *hso_get_ep(struct usb_interface *intf,
- int type, int dir);
static int hso_get_mux_ports(struct usb_interface *intf, unsigned char *ports);
static void hso_free_interface(struct usb_interface *intf);
static int hso_start_serial_device(struct hso_device *hso_dev, gfp_t flags);
@@ -2497,16 +2495,11 @@ static struct hso_device *hso_create_net_device(struct usb_interface *interface,
hso_net->net = net;
hso_net->parent = hso_dev;
- hso_net->in_endp = hso_get_ep(interface, USB_ENDPOINT_XFER_BULK,
- USB_DIR_IN);
- if (!hso_net->in_endp) {
- dev_err(&interface->dev, "Can't find BULK IN endpoint\n");
- goto err_net;
- }
- hso_net->out_endp = hso_get_ep(interface, USB_ENDPOINT_XFER_BULK,
- USB_DIR_OUT);
- if (!hso_net->out_endp) {
- dev_err(&interface->dev, "Can't find BULK OUT endpoint\n");
+ result = usb_find_common_endpoints(interface->cur_altsetting,
+ &hso_net->in_endp, &hso_net->out_endp,
+ NULL, NULL);
+ if (result) {
+ dev_err(&interface->dev, "Can't find BULK endpoints\n");
goto err_net;
}
SET_NETDEV_DEV(net, &interface->dev);
@@ -2608,10 +2601,12 @@ static void hso_free_serial_device(struct hso_device *hso_dev)
static struct hso_device *hso_create_bulk_serial_device(
struct usb_interface *interface, int port)
{
+ struct usb_host_interface *iface_desc = interface->cur_altsetting;
struct hso_device *hso_dev;
struct hso_serial *serial;
int num_urbs;
struct hso_tiocmget *tiocmget;
+ int ret;
hso_dev = hso_create_device(interface, port);
if (!hso_dev)
@@ -2634,10 +2629,8 @@ static struct hso_device *hso_create_bulk_serial_device(
if (!serial->tiocmget->serial_state_notification)
goto exit;
tiocmget = serial->tiocmget;
- tiocmget->endp = hso_get_ep(interface,
- USB_ENDPOINT_XFER_INT,
- USB_DIR_IN);
- if (!tiocmget->endp) {
+ ret = usb_find_int_in_endpoint(iface_desc, &tiocmget->endp);
+ if (ret) {
dev_err(&interface->dev, "Failed to find INT IN ep\n");
goto exit;
}
@@ -2656,17 +2649,10 @@ static struct hso_device *hso_create_bulk_serial_device(
BULK_URB_TX_SIZE))
goto exit;
- serial->in_endp = hso_get_ep(interface, USB_ENDPOINT_XFER_BULK,
- USB_DIR_IN);
- if (!serial->in_endp) {
- dev_err(&interface->dev, "Failed to find BULK IN ep\n");
- goto exit2;
- }
-
- if (!
- (serial->out_endp =
- hso_get_ep(interface, USB_ENDPOINT_XFER_BULK, USB_DIR_OUT))) {
- dev_err(&interface->dev, "Failed to find BULK OUT ep\n");
+ ret = usb_find_common_endpoints(iface_desc, &serial->in_endp,
+ &serial->out_endp, NULL, NULL);
+ if (ret) {
+ dev_err(&interface->dev, "Failed to find BULK eps\n");
goto exit2;
}
@@ -2754,13 +2740,14 @@ static
struct hso_shared_int *hso_create_shared_int(struct usb_interface *interface)
{
struct hso_shared_int *mux = kzalloc_obj(*mux);
+ int ret;
if (!mux)
return NULL;
- mux->intr_endp = hso_get_ep(interface, USB_ENDPOINT_XFER_INT,
- USB_DIR_IN);
- if (!mux->intr_endp) {
+ ret = usb_find_int_in_endpoint(interface->cur_altsetting,
+ &mux->intr_endp);
+ if (ret) {
dev_err(&interface->dev, "Can't find INT IN endpoint\n");
goto exit;
}
@@ -3134,24 +3121,6 @@ static void hso_free_interface(struct usb_interface *interface)
/* Helper functions */
-/* Get the endpoint ! */
-static struct usb_endpoint_descriptor *hso_get_ep(struct usb_interface *intf,
- int type, int dir)
-{
- int i;
- struct usb_host_interface *iface = intf->cur_altsetting;
- struct usb_endpoint_descriptor *endp;
-
- for (i = 0; i < iface->desc.bNumEndpoints; i++) {
- endp = &iface->endpoint[i].desc;
- if (((endp->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == dir) &&
- (usb_endpoint_type(endp) == type))
- return endp;
- }
-
- return NULL;
-}
-
/* Get the byte that describes which ports are enabled */
static int hso_get_mux_ports(struct usb_interface *intf, unsigned char *ports)
{
--
2.52.0
^ permalink raw reply related
* [PATCH 0/2] net: refactor USB endpoint lookups
From: Johan Hovold @ 2026-03-30 10:26 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: linux-usb, netdev, linux-kernel, Johan Hovold
Use the common USB helpers for looking up bulk and interrupt endpoints
instead of open coding.
Johan
Johan Hovold (2):
net: hso: refactor endpoint lookup
net: ipeth: refactor endpoint lookup
drivers/net/usb/hso.c | 65 +++++++++++-----------------------------
drivers/net/usb/ipheth.c | 18 +++++------
2 files changed, 24 insertions(+), 59 deletions(-)
--
2.52.0
^ permalink raw reply
* [PATCH 2/2] net: ipeth: refactor endpoint lookup
From: Johan Hovold @ 2026-03-30 10:26 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: linux-usb, netdev, linux-kernel, Johan Hovold
In-Reply-To: <20260330102611.1671546-1-johan@kernel.org>
Use the common USB helper for looking up bulk and interrupt endpoints
instead of open coding.
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/net/usb/ipheth.c | 18 +++++++-----------
1 file changed, 7 insertions(+), 11 deletions(-)
diff --git a/drivers/net/usb/ipheth.c b/drivers/net/usb/ipheth.c
index a19789b57190..bb1364f85bd1 100644
--- a/drivers/net/usb/ipheth.c
+++ b/drivers/net/usb/ipheth.c
@@ -573,11 +573,10 @@ static int ipheth_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(intf);
+ struct usb_endpoint_descriptor *ep_in, *ep_out;
struct usb_host_interface *hintf;
- struct usb_endpoint_descriptor *endp;
struct ipheth_device *dev;
struct net_device *netdev;
- int i;
int retval;
netdev = alloc_etherdev(sizeof(struct ipheth_device));
@@ -603,19 +602,16 @@ static int ipheth_probe(struct usb_interface *intf,
goto err_endpoints;
}
- for (i = 0; i < hintf->desc.bNumEndpoints; i++) {
- endp = &hintf->endpoint[i].desc;
- if (usb_endpoint_is_bulk_in(endp))
- dev->bulk_in = endp->bEndpointAddress;
- else if (usb_endpoint_is_bulk_out(endp))
- dev->bulk_out = endp->bEndpointAddress;
- }
- if (!(dev->bulk_in && dev->bulk_out)) {
- retval = -ENODEV;
+ retval = usb_find_common_endpoints_reverse(hintf, &ep_in, &ep_out,
+ NULL, NULL);
+ if (retval) {
dev_err(&intf->dev, "Unable to find endpoints\n");
goto err_endpoints;
}
+ dev->bulk_in = ep_in->bEndpointAddress;
+ dev->bulk_out = ep_out->bEndpointAddress;
+
dev->ctrl_buf = kmalloc(IPHETH_CTRL_BUF_SIZE, GFP_KERNEL);
if (dev->ctrl_buf == NULL) {
retval = -ENOMEM;
--
2.52.0
^ permalink raw reply related
* Re: [PATCH net-next 2/3] dpll: add actual frequency monitoring callback ops
From: Vadim Fedorenko @ 2026-03-30 10:27 UTC (permalink / raw)
To: Ivan Vecera, netdev
Cc: Arkadiusz Kubalewski, Jiri Pirko, Jonathan Corbet, Shuah Khan,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Donald Hunter, Prathosh Satish, Petr Oros,
linux-doc, linux-kernel
In-Reply-To: <a341fd52-3682-473f-8116-a8323bf846ee@redhat.com>
On 30/03/2026 09:52, Ivan Vecera wrote:
> On 3/26/26 6:48 PM, Ivan Vecera wrote:
>> On 3/26/26 12:21 PM, Vadim Fedorenko wrote:
>>> On 25/03/2026 19:39, Ivan Vecera wrote:
>>>
>>>> +static int dpll_msg_add_actual_freq(struct sk_buff *msg, struct
>>>> dpll_pin *pin,
>>>> + struct dpll_pin_ref *ref,
>>>> + struct netlink_ext_ack *extack)
>>>> +{
>>>> + const struct dpll_device_ops *dev_ops = dpll_device_ops(ref-
>>>> >dpll);
>>>> + const struct dpll_pin_ops *ops = dpll_pin_ops(ref);
>>>> + struct dpll_device *dpll = ref->dpll;
>>>> + enum dpll_feature_state state;
>>>> + u64 actual_freq;
>>>> + int ret;
>>>> +
>>>> + if (!ops->actual_freq_get)
>>>> + return 0;
>>>> + if (dev_ops->freq_monitor_get) {
>>>> + ret = dev_ops->freq_monitor_get(dpll, dpll_priv(dpll),
>>>> + &state, extack);
>>>> + if (ret)
>>>> + return ret;
>>>> + if (state == DPLL_FEATURE_STATE_DISABLE)
>>>> + return 0;
>>>
>>> I think we have to signal back to user that frequency monitoring is
>>> disabled via extack.
>>
>> Hi Vadim,
>>
>> This would break pin-get operation... Do or dump pin-get operation would
>> fail with this extack message.
>>
>> Here we can check if the freq-monitoring is enabled and conditionally
>> call actual_freq_get() or measured_freq_get()
Let's make it this way, we don't want to waste even small amount of
resources for useless call.
>>
>> -or-
>>
>> Call this callback unconditionally and check for return code and if a
>> driver returns e.g. -ENODATA then skip nla_put_64bit() but return
>> success.
>>
>> WDYT?
>
> Vadim?
Sorry, was AFK this Friday/weekend
^ permalink raw reply
* Re: [PATCH v3 04/15] firmware: qcom: Add a PAS TEE service
From: Harshal Dev @ 2026-03-30 10:36 UTC (permalink / raw)
To: Sumit Garg, linux-arm-msm, devicetree, dri-devel, freedreno,
linux-media, netdev, linux-wireless, ath12k, linux-remoteproc
Cc: andersson, konradybcio, 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, mukesh.ojha, pavan.kondeti,
jorge.ramirez, tonyh, vignesh.viswanathan, srinivas.kandagatla,
amirreza.zarrabi, jens.wiklander, op-tee, apurupa, skare,
linux-kernel, Sumit Garg
In-Reply-To: <20260327131043.627120-5-sumit.garg@kernel.org>
On 3/27/2026 6:40 PM, 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 | 478 +++++++++++++++++++++++++++
> 3 files changed, 489 insertions(+)
> create mode 100644 drivers/firmware/qcom/qcom_pas_tee.c
>
[...]
> diff --git a/drivers/firmware/qcom/qcom_pas_tee.c b/drivers/firmware/qcom/qcom_pas_tee.c
> new file mode 100644
> index 000000000000..d63122c34f04
> --- /dev/null
> +++ b/drivers/firmware/qcom/qcom_pas_tee.c
> @@ -0,0 +1,478 @@
> +// 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.
Nit: Column alignment of comment descriptions.
> + */
> +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, err: %x\n",
> + pas_id, inv_arg.ret);
I can see that similar error handling pattern for tee_client_invoke_func() is
being done by other clients. But it seems that this error log doesn't really convey
whether we failed before or after entering OPTEE (or some other TEE) for invoking
the service.
Could be better if we print 'ret' when ret < 0. And print 'inv_arg.ret' when
inv_arg.ret != 0. So for example, if the param marshalling fails, or some other
issue is encountered when processing the params in a different back-end TEE
driver, we could know from the logs.
> + 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;
> +
> + if (!ctx)
> + return -EINVAL;
> +
> + mdata_shm = tee_shm_alloc_kernel_buf(data->ctx, size);
Why not move the DEFINE_FREE() above this function so we can use scoped free
here as well for mdata_shm?
struct tee_shm *mdata_shm __free(shm_free) = ...
> + 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, err: %x\n",
Nit: We can prefix %x with 0x. "err: 0x%x\n."
> + pas_id, inv_arg.ret);
> + tee_shm_free(mdata_shm);
> + return -EINVAL;
> + }
> + ctx->ptr = (void *)mdata_shm;
> +
> + return 0;
> +}
> +
> +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, err: %x\n",
> + pas_id, inv_arg.ret);
> + return -EINVAL;
I can see that originally in-case of error, qcom_scm_pas_mem_setup() bubbles
up the return value from qcom_scm_call(). Perhaps we should do the same as well,
return ret here instead of hard-coded '-EINVAL' which overrides any useful
return values returned from the back-end TEE driver.
> + }
> +
> + return 0;
> +}
> +
> +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, err: %x\n",
> + ctx->pas_id, inv_arg.ret);
> + return ERR_PTR(-EINVAL);
Same comment here, we could return ERR_PTR(ret) instead of overriding to
'-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_OR_NULL(rt_shm_va)) {
> + dev_err(dev, "rt_shm get VA failed\n");
> + return ERR_PTR(-EINVAL);
Why not just return rt_shm_va? It already encodes the error in the pointer.
> + }
> + memcpy(rt_shm_va, input_rt, input_rt_size);
> +
> + 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, err: %x\n",
> + ctx->pas_id, inv_arg.ret);
> + return ERR_PTR(-EINVAL);
Same comment.
> + }
> +
> + if (param[1].u.memref.size) {
Is it possible for param[1].u.memref.size to be 0 after a successful tee_client_invoke_func()?
> + *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 */
Can you explain this comment a bit more? Plan is to allow passing a MEM_REF here
which could be lent/shared to TEE via FFA ABI?
> + [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, err: %x\n",
> + pas_id, inv_arg.ret);
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int qcom_pas_tee_auth_and_reset(struct device *dev, u32 pas_id)
I'm guessing once all clients have migrated to PAS, plan is to drop this wrapper?
> +{
> + 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;
Nit: Why not ret = 0 initialize and always use 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, err: %x\n",
> + pas_id, inv_arg.ret);
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +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 = 0;
> +
> + 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, err: %x\n",
> + pas_id, inv_arg.ret);
> + return -EINVAL;
> + }
> +
> + return 0;
You could just return 'ret' here. :)
> +}
> +
> +static void qcom_pas_tee_metadata_release(struct device *dev,
> + struct qcom_pas_context *ctx)
> +{
> + struct tee_shm *mdata_shm = ctx->ptr;
> +
Nit: Unnecessary extra line.
> + tee_shm_free(mdata_shm);
> +}
> +
[...]
> +
> +module_tee_client_driver(optee_pas_tee_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("TEE bus based Qualcomm PAS driver");
Since this is a tee_client driver, isn't it self-explanatary that it's TEE bus based?
^ permalink raw reply
* [PATCH 1/3] nfc: nfcmrvl: refactor endpoint lookup
From: Johan Hovold @ 2026-03-30 10:36 UTC (permalink / raw)
To: netdev; +Cc: linux-kernel, Johan Hovold
In-Reply-To: <20260330103655.1672331-1-johan@kernel.org>
Use the common USB helper for looking up bulk and interrupt endpoints
instead of open coding.
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/nfc/nfcmrvl/usb.c | 20 ++++----------------
1 file changed, 4 insertions(+), 16 deletions(-)
diff --git a/drivers/nfc/nfcmrvl/usb.c b/drivers/nfc/nfcmrvl/usb.c
index ea7309453096..6c4711c869f0 100644
--- a/drivers/nfc/nfcmrvl/usb.c
+++ b/drivers/nfc/nfcmrvl/usb.c
@@ -288,9 +288,9 @@ static int nfcmrvl_probe(struct usb_interface *intf,
{
struct nfcmrvl_usb_drv_data *drv_data;
struct nfcmrvl_private *priv;
- int i;
struct usb_device *udev = interface_to_usbdev(intf);
struct nfcmrvl_platform_data config;
+ int ret;
/* No configuration for USB */
memset(&config, 0, sizeof(config));
@@ -302,21 +302,9 @@ static int nfcmrvl_probe(struct usb_interface *intf,
if (!drv_data)
return -ENOMEM;
- for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) {
- struct usb_endpoint_descriptor *ep_desc;
-
- ep_desc = &intf->cur_altsetting->endpoint[i].desc;
-
- if (!drv_data->bulk_tx_ep &&
- usb_endpoint_is_bulk_out(ep_desc)) {
- drv_data->bulk_tx_ep = ep_desc;
- } else if (!drv_data->bulk_rx_ep &&
- usb_endpoint_is_bulk_in(ep_desc)) {
- drv_data->bulk_rx_ep = ep_desc;
- }
- }
-
- if (!drv_data->bulk_tx_ep || !drv_data->bulk_rx_ep)
+ ret = usb_find_common_endpoints(intf->cur_altsetting, &drv_data->bulk_rx_ep,
+ &drv_data->bulk_tx_ep, NULL, NULL);
+ if (ret)
return -ENODEV;
drv_data->udev = udev;
--
2.52.0
^ permalink raw reply related
* [PATCH 3/3] nfc: port100: refactor endpoint lookup
From: Johan Hovold @ 2026-03-30 10:36 UTC (permalink / raw)
To: netdev; +Cc: linux-kernel, Johan Hovold
In-Reply-To: <20260330103655.1672331-1-johan@kernel.org>
Use the common USB helpers for looking up bulk and interrupt endpoints
(and determining endpoint numbers) instead of open coding.
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/nfc/port100.c | 27 ++++++---------------------
1 file changed, 6 insertions(+), 21 deletions(-)
diff --git a/drivers/nfc/port100.c b/drivers/nfc/port100.c
index 09dcc6f4c4f3..5ae61d7ebcfe 100644
--- a/drivers/nfc/port100.c
+++ b/drivers/nfc/port100.c
@@ -1489,15 +1489,11 @@ MODULE_DEVICE_TABLE(usb, port100_table);
static int port100_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
+ struct usb_endpoint_descriptor *ep_in, *ep_out;
struct port100 *dev;
int rc;
- struct usb_host_interface *iface_desc;
- struct usb_endpoint_descriptor *endpoint;
- int in_endpoint;
- int out_endpoint;
u16 fw_version;
u64 cmd_type_mask;
- int i;
dev = devm_kzalloc(&interface->dev, sizeof(struct port100), GFP_KERNEL);
if (!dev)
@@ -1508,22 +1504,11 @@ static int port100_probe(struct usb_interface *interface,
dev->interface = interface;
usb_set_intfdata(interface, dev);
- in_endpoint = out_endpoint = 0;
- iface_desc = interface->cur_altsetting;
- for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
- endpoint = &iface_desc->endpoint[i].desc;
-
- if (!in_endpoint && usb_endpoint_is_bulk_in(endpoint))
- in_endpoint = endpoint->bEndpointAddress;
-
- if (!out_endpoint && usb_endpoint_is_bulk_out(endpoint))
- out_endpoint = endpoint->bEndpointAddress;
- }
-
- if (!in_endpoint || !out_endpoint) {
+ rc = usb_find_common_endpoints(interface->cur_altsetting, &ep_in,
+ &ep_out, NULL, NULL);
+ if (rc) {
nfc_err(&interface->dev,
"Could not find bulk-in or bulk-out endpoint\n");
- rc = -ENODEV;
goto error;
}
@@ -1537,10 +1522,10 @@ static int port100_probe(struct usb_interface *interface,
}
usb_fill_bulk_urb(dev->in_urb, dev->udev,
- usb_rcvbulkpipe(dev->udev, in_endpoint),
+ usb_rcvbulkpipe(dev->udev, usb_endpoint_num(ep_in)),
NULL, 0, NULL, dev);
usb_fill_bulk_urb(dev->out_urb, dev->udev,
- usb_sndbulkpipe(dev->udev, out_endpoint),
+ usb_sndbulkpipe(dev->udev, usb_endpoint_num(ep_out)),
NULL, 0, port100_send_complete, dev);
dev->out_urb->transfer_flags = URB_ZERO_PACKET;
--
2.52.0
^ permalink raw reply related
* [PATCH 0/3] nfc: refactor USB endpoint lookups
From: Johan Hovold @ 2026-03-30 10:36 UTC (permalink / raw)
To: netdev; +Cc: linux-kernel, Johan Hovold
Use the common USB helper for looking up bulk and interrupt endpoints
instead of open coding.
Johan
Johan Hovold (3):
nfc: nfcmrvl: refactor endpoint lookup
nfc: pn533: refactor endpoint lookup
nfc: port100: refactor endpoint lookup
drivers/nfc/nfcmrvl/usb.c | 20 ++++----------------
drivers/nfc/pn533/usb.c | 34 +++++++++++-----------------------
drivers/nfc/port100.c | 27 ++++++---------------------
3 files changed, 21 insertions(+), 60 deletions(-)
--
2.52.0
^ permalink raw reply
* [PATCH 2/3] nfc: pn533: refactor endpoint lookup
From: Johan Hovold @ 2026-03-30 10:36 UTC (permalink / raw)
To: netdev; +Cc: linux-kernel, Johan Hovold
In-Reply-To: <20260330103655.1672331-1-johan@kernel.org>
Use the common USB helpers for looking up bulk and interrupt endpoints
(and determining endpoint numbers) instead of open coding.
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/nfc/pn533/usb.c | 34 +++++++++++-----------------------
1 file changed, 11 insertions(+), 23 deletions(-)
diff --git a/drivers/nfc/pn533/usb.c b/drivers/nfc/pn533/usb.c
index 0ff2f0d7caf4..efb07f944fce 100644
--- a/drivers/nfc/pn533/usb.c
+++ b/drivers/nfc/pn533/usb.c
@@ -476,14 +476,9 @@ static const struct pn533_phy_ops usb_phy_ops = {
static int pn533_usb_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
+ struct usb_endpoint_descriptor *ep_in, *ep_out;
struct pn533 *priv;
struct pn533_usb_phy *phy;
- struct usb_host_interface *iface_desc;
- struct usb_endpoint_descriptor *endpoint;
- int in_endpoint = 0;
- int out_endpoint = 0;
- int rc = -ENOMEM;
- int i;
u32 protocols;
enum pn533_protocol_type protocol_type = PN533_PROTO_REQ_ACK_RESP;
struct pn533_frame_ops *fops = NULL;
@@ -491,6 +486,7 @@ static int pn533_usb_probe(struct usb_interface *interface,
int in_buf_len = PN533_EXT_FRAME_HEADER_LEN +
PN533_STD_FRAME_MAX_PAYLOAD_LEN +
PN533_STD_FRAME_TAIL_LEN;
+ int rc;
phy = devm_kzalloc(&interface->dev, sizeof(*phy), GFP_KERNEL);
if (!phy)
@@ -503,21 +499,11 @@ static int pn533_usb_probe(struct usb_interface *interface,
phy->udev = interface_to_usbdev(interface);
phy->interface = interface;
- iface_desc = interface->cur_altsetting;
- for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
- endpoint = &iface_desc->endpoint[i].desc;
-
- if (!in_endpoint && usb_endpoint_is_bulk_in(endpoint))
- in_endpoint = endpoint->bEndpointAddress;
-
- if (!out_endpoint && usb_endpoint_is_bulk_out(endpoint))
- out_endpoint = endpoint->bEndpointAddress;
- }
-
- if (!in_endpoint || !out_endpoint) {
+ rc = usb_find_common_endpoints(interface->cur_altsetting, &ep_in,
+ &ep_out, NULL, NULL);
+ if (rc) {
nfc_err(&interface->dev,
"Could not find bulk-in or bulk-out endpoint\n");
- rc = -ENODEV;
goto error;
}
@@ -525,18 +511,20 @@ static int pn533_usb_probe(struct usb_interface *interface,
phy->out_urb = usb_alloc_urb(0, GFP_KERNEL);
phy->ack_urb = usb_alloc_urb(0, GFP_KERNEL);
- if (!phy->in_urb || !phy->out_urb || !phy->ack_urb)
+ if (!phy->in_urb || !phy->out_urb || !phy->ack_urb) {
+ rc = -ENOMEM;
goto error;
+ }
usb_fill_bulk_urb(phy->in_urb, phy->udev,
- usb_rcvbulkpipe(phy->udev, in_endpoint),
+ usb_rcvbulkpipe(phy->udev, usb_endpoint_num(ep_in)),
in_buf, in_buf_len, NULL, phy);
usb_fill_bulk_urb(phy->out_urb, phy->udev,
- usb_sndbulkpipe(phy->udev, out_endpoint),
+ usb_sndbulkpipe(phy->udev, usb_endpoint_num(ep_out)),
NULL, 0, pn533_out_complete, phy);
usb_fill_bulk_urb(phy->ack_urb, phy->udev,
- usb_sndbulkpipe(phy->udev, out_endpoint),
+ usb_sndbulkpipe(phy->udev, usb_endpoint_num(ep_out)),
NULL, 0, pn533_ack_complete, phy);
switch (id->driver_info) {
--
2.52.0
^ permalink raw reply related
* Re: [PATCH net-next v4 00/10] selftests: drivers: bash support for remote traffic generators
From: Petr Machata @ 2026-03-30 10:38 UTC (permalink / raw)
To: Jakub Kicinski
Cc: Ioana Ciornei, netdev, Andrew Lunn, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, linux-kernel, petrm,
willemb, linux-kselftest
In-Reply-To: <20260327172454.6b9bc8f3@kernel.org>
Jakub Kicinski <kuba@kernel.org> writes:
> On Fri, 27 Mar 2026 09:32:22 +0200 Ioana Ciornei wrote:
>> On Thu, Mar 26, 2026 at 12:03:42PM -0700, Jakub Kicinski wrote:
>> > On Thu, 26 Mar 2026 15:28:18 +0200 Ioana Ciornei wrote:
>> > > This patch set aims to add the necessary support so that bash written
>> > > selftests are also able to easily run with a remote traffic generator
>> > > system, either be it in another netns or one accessible through ssh.
>> > >
>> > > This patch set is a result of the discussion from v1:
>> > > https://lore.kernel.org/all/20260303084330.340b6459@kernel.org/
>> > > Even though the python infrastructure is already established, some
>> > > things are easier in bash and it would be a shame to leave behind the
>> > > bash tests that we already have.
>> >
>> > I think this introduces a bunch of regressions, eg:
>> >
>> > https://netdev-ctrl.bots.linux.dev/logs/vmksft/forwarding/results/575622/4-local-termination-sh/stdout
>> >
>> > https://netdev-ctrl.bots.linux.dev/logs/vmksft/netdevsim/results/575802/18-netcons-resume-sh/stdout
>>
>> I cannot reproduce this unfortunately. For example, local_termination.sh
>> gives me the following result with the exact patches that I submitted.
>> Any idea on what might be the difference?
>
> Hm, the system that runs this on our end is:
>
> # cat /etc/redhat-release
> Fedora release 43 (Forty Three)
>
> And it has this added on top of default install:
>
> # cat /etc/systemd/network/99-default.link
> [Match]
> OriginalName=*
>
> [Link]
> NamePolicy=keep kernel database onboard slot path
> AlternativeNamesPolicy=database onboard slot path mac
> MACAddressPolicy=none
The observed issues are consistent with TARGETS being defined, but not
an array:
$ declare -A T
$ T=([a.100]=b)
$ U=foo
$ if declare -p T &>/dev/null; then echo "${T[a.100]}"; else echo fail; fi
b
$ if declare -p U &>/dev/null; then echo "${U[a.100]}"; else echo fail; fi
bash: a.100: syntax error: invalid arithmetic operator (error token is ".100")
^ permalink raw reply
* Re: [PATCH net v3] mptcp: fix soft lockup in mptcp_recvmsg()
From: Li Xiasong @ 2026-03-30 10:43 UTC (permalink / raw)
To: Matthieu Baerts
Cc: Mat Martineau, Geliang Tang, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, netdev, mptcp,
linux-kernel, yuehaibing, zhangchangzhong, weiyongjun1
In-Reply-To: <c23acf06-8d10-4a3c-a3df-d62860ba6b31@kernel.org>
Hi Matt,
On 3/27/2026 8:13 PM, Matthieu Baerts wrote:
> Hi Li,
>
> On 27/03/2026 08:55, Li Xiasong wrote:
>> syzbot reported a soft lockup in mptcp_recvmsg() [0].
>>
>> When receiving data with MSG_PEEK | MSG_WAITALL flags, the skb is not
>> removed from the sk_receive_queue. This causes sk_wait_data() to always
>> find available data and never perform actual waiting, leading to a soft
>> lockup.
>>
>> Fix this by adding a 'last' parameter to track the last peeked skb.
>> This allows sk_wait_data() to make informed waiting decisions and prevent
>> infinite loops when MSG_PEEK is used.
>
> Thank you for the new version!
>
> (...)
>
>> diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
>> index cf1852b99963..60f6e6a189b7 100644
>> --- a/net/mptcp/protocol.c
>> +++ b/net/mptcp/protocol.c
>
> (...)
>
>> @@ -2343,7 +2347,7 @@ static int mptcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
>>
>> pr_debug("block timeout %ld\n", timeo);
>> mptcp_cleanup_rbuf(msk, copied);
>> - err = sk_wait_data(sk, &timeo, NULL);
>> + err = sk_wait_data(sk, &timeo, last);
>
> sashiko is saying [1] this:
>
>> Will this cause a soft lockup if all socket buffers in the receive queue
>> have already been peeked?
>>
>> If the queue only contains already-peeked buffers, the loop in
>> __mptcp_recvmsg_mskq() skips them using a continue statement:
>>
>> if (flags & MSG_PEEK) {
>> /* skip already peeked skbs */
>> if (total_data_len + data_len <= copied_total) {
>> total_data_len += data_len;
>> continue;
>> }
>>
>> This means last is never assigned and remains NULL.
>>
>> When last is NULL, sk_wait_data() checks if the receive queue tail is
>> not equal to NULL. Since the queue still contains the unconsumed buffers,
>> this evaluates to true and sk_wait_data() returns immediately without
>> sleeping.
>>
>> Does this result in an infinite loop here when MSG_PEEK and MSG_WAITALL
>> are used together?
>
> I *think* that's a false positive. When MSG_PEEK and MSG_WAITALL are
> used together, sk_wait_data() will be call with "last" != NULL and will
> be unblocked when a new data packet (skb->len > 0) is added to the
> queue. In other words, when walking the queue in __mptcp_recvmsg_mskq,
> it should never be full of already-peeked skb. Or did I miss something?
>
> If yes, "*last = skb" could be added before the "continue".
>
> If no, this patch can be applied in 'net' directly:
>
> Reviewed-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
>
Through code analysis and testing, there is indeed the special scenario
sashiko described:
when receiving data with MSG_PEEK | MSG_WAITALL while waiting for data
and calling shutdown(sock_fd, SHUT_WR) in another thread simultaneously,
mptcp_close_wake_up will wake up sk_wait_data, but sk->sk_state remains
FIN_WAIT2, leading to a busy loop with CPU at 100%, which can further
lead to soft lockup.
Adding '*last = skb' before 'continue' can properly solve it. I'll send
v4 with this fix later.
Thanks,
Li Xiasong
^ permalink raw reply
* [PATCH net] net/sched: taprio: fix NULL pointer dereference in class dump
From: Weiming Shi @ 2026-03-30 10:29 UTC (permalink / raw)
To: Vinicius Costa Gomes, Jamal Hadi Salim, Jiri Pirko,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, netdev, Xiang Mei, Weiming Shi, stable
When a TAPRIO child qdisc is deleted via RTM_DELQDISC, taprio_graft()
is called with new == NULL and stores NULL into q->qdiscs[cl - 1].
Subsequent RTM_GETTCLASS dump operations walk all classes via
taprio_walk() and call taprio_dump_class(), which calls taprio_leaf()
returning the NULL pointer, then dereferences it to read child->handle,
causing a kernel NULL pointer dereference.
The bug is reachable with namespace-scoped CAP_NET_ADMIN on any kernel
with CONFIG_NET_SCH_TAPRIO enabled. On systems with unprivileged user
namespaces enabled, an unprivileged local user can trigger a kernel
panic by creating a taprio qdisc inside a new network namespace,
grafting an explicit child qdisc, deleting it, and requesting a class
dump. The RTM_GETTCLASS dump itself requires no capability.
Oops: general protection fault, probably for non-canonical address 0xdffffc0000000007: 0000 [#1] SMP KASAN NOPTI
KASAN: null-ptr-deref in range [0x0000000000000038-0x000000000000003f]
RIP: 0010:taprio_dump_class (net/sched/sch_taprio.c:2475)
Call Trace:
<TASK>
tc_fill_tclass (net/sched/sch_api.c:1966)
qdisc_class_dump (net/sched/sch_api.c:2329)
taprio_walk (net/sched/sch_taprio.c:2510)
tc_dump_tclass_qdisc (net/sched/sch_api.c:2353)
tc_dump_tclass_root (net/sched/sch_api.c:2370)
tc_dump_tclass (net/sched/sch_api.c:2431)
rtnl_dumpit (net/core/rtnetlink.c:6827)
netlink_dump (net/netlink/af_netlink.c:2325)
rtnetlink_rcv_msg (net/core/rtnetlink.c:6927)
netlink_rcv_skb (net/netlink/af_netlink.c:2550)
</TASK>
Fix this by substituting &noop_qdisc when new is NULL in
taprio_graft(), following the same pattern used by multiq_graft() and
prio_graft(). This ensures q->qdiscs[] slots are never NULL, making
all consumer paths (dump, enqueue, dequeue) safe. The noop_qdisc is a
kernel-global builtin qdisc that drops all packets, which is
functionally equivalent to a NULL child for data path purposes. The
refcount increment and flag modification are guarded with
!= &noop_qdisc to avoid modifying the global singleton.
Fixes: 665338b2a7a0 ("net/sched: taprio: dump class stats for the actual q->qdiscs[]")
Cc: stable@vger.kernel.org
Reported-by: Xiang Mei <xmei5@asu.edu>
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
net/sched/sch_taprio.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c
index f721c03514f60..cecaef16c0dd1 100644
--- a/net/sched/sch_taprio.c
+++ b/net/sched/sch_taprio.c
@@ -2183,6 +2183,9 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl,
if (!dev_queue)
return -EINVAL;
+ if (!new)
+ new = &noop_qdisc;
+
if (dev->flags & IFF_UP)
dev_deactivate(dev);
@@ -2196,14 +2199,14 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl,
*old = q->qdiscs[cl - 1];
if (FULL_OFFLOAD_IS_ENABLED(q->flags)) {
WARN_ON_ONCE(dev_graft_qdisc(dev_queue, new) != *old);
- if (new)
+ if (new != &noop_qdisc)
qdisc_refcount_inc(new);
if (*old)
qdisc_put(*old);
}
q->qdiscs[cl - 1] = new;
- if (new)
+ if (new != &noop_qdisc)
new->flags |= TCQ_F_ONETXQUEUE | TCQ_F_NOPARENT;
if (dev->flags & IFF_UP)
--
2.43.0
^ permalink raw reply related
* Re: [PATCH net v3 04/11] list: Move on_list_rcu() to list.h and add on_list() also
From: David Howells @ 2026-03-30 10:49 UTC (permalink / raw)
To: Linus Torvalds
Cc: dhowells, Jakub Kicinski, netdev, Marc Dionne, David S. Miller,
Eric Dumazet, Paolo Abeni, linux-afs, linux-kernel,
Mathieu Desnoyers, John Johansen, Minas Harutyunyan, Simon Horman,
apparmor, linux-usb, stable
In-Reply-To: <CAHk-=wiJ6gEELLviexdmSHnyjVoG7MFo8Qwhd1zxs_tCnL-=gQ@mail.gmail.com>
Linus Torvalds <torvalds@linux-foundation.org> wrote:
> ... and I do *not* see a huge advantage to a helper function that just wraps
> "list_empty()" with another name that is actually *less* descriptive.
I don't like list_empty() as the name of the function used to find out whether
an entry is on a list. Yes, technically, all it's doing is seeing if the
list_head is 'empty', but, linguistically, it looks wrong: the question you're
asking is not if the list is empty (you're not looking at the list head), but
if the entry is on a list.
So if I see in the code:
if (list_empty(p))
what is the test actually asking?
Note that various other list types in the kernel have separate "is the list
empty" and "is the entry on a list" primitives, though, granted, usually
because they require separate functions programmatically.
Anyway, I'll find a different way to do this, not involving checking the prev
pointer. What I don't want to do is hard code "prev == LIST_POISON2" into my
stuff. Anything like that really needs to be in list.h.
David
^ permalink raw reply
* [PATCH net-next v2 0/3] dpll: add frequency monitoring feature
From: Ivan Vecera @ 2026-03-30 10:55 UTC (permalink / raw)
To: netdev
Cc: Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Jonathan Corbet, Shuah Khan, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Donald Hunter,
Prathosh Satish, Petr Oros, linux-doc, linux-kernel
This series adds support for monitoring the measured input frequency
of DPLL input pins via the DPLL netlink interface.
Some DPLL devices can measure the actual frequency being received on
input pins. The approach mirrors the existing phase-offset-monitor
feature: a device-level attribute (DPLL_A_FREQUENCY_MONITOR) enables
or disables monitoring, and a per-pin attribute
(DPLL_A_PIN_MEASURED_FREQUENCY) exposes the measured frequency in Hz
when monitoring is enabled.
Patch 1 adds the new attributes to the DPLL netlink spec (dpll.yaml),
regenerates the auto-generated UAPI header and netlink policy, and
updates Documentation/driver-api/dpll.rst.
Patch 2 adds the callback operations (freq_monitor_get/set for
devices, measured_freq_get for pins) and the corresponding netlink
GET/SET handlers in the DPLL core. The core only invokes
measured_freq_get when the frequency monitor is enabled on the parent
device.
Patch 3 implements the feature in the ZL3073x driver by extracting
a common measurement latch helper from the existing FFO update path,
adding a frequency measurement function, and wiring up the new
callbacks.
Changes v1 -> v2:
- Renamed actual-frequency to measured-frequency (Vadim)
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Ivan Vecera (3):
dpll: add frequency monitoring to netlink spec
dpll: add frequency monitoring callback ops
dpll: zl3073x: implement frequency monitoring
Documentation/driver-api/dpll.rst | 18 ++++++
Documentation/netlink/specs/dpll.yaml | 17 +++++
drivers/dpll/dpll_netlink.c | 90 +++++++++++++++++++++++++++
drivers/dpll/dpll_nl.c | 5 +-
drivers/dpll/zl3073x/core.c | 88 ++++++++++++++++++++++----
drivers/dpll/zl3073x/dpll.c | 88 +++++++++++++++++++++++++-
drivers/dpll/zl3073x/dpll.h | 2 +
drivers/dpll/zl3073x/ref.h | 14 +++++
include/linux/dpll.h | 10 +++
include/uapi/linux/dpll.h | 2 +
10 files changed, 318 insertions(+), 16 deletions(-)
--
2.52.0
^ permalink raw reply
* [PATCH net-next v2 1/3] dpll: add frequency monitoring to netlink spec
From: Ivan Vecera @ 2026-03-30 10:55 UTC (permalink / raw)
To: netdev
Cc: Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Jonathan Corbet, Shuah Khan, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Donald Hunter,
Prathosh Satish, Petr Oros, linux-doc, linux-kernel
In-Reply-To: <20260330105505.715099-1-ivecera@redhat.com>
Add DPLL_A_FREQUENCY_MONITOR device attribute to allow control over
the frequency monitor feature. The attribute uses the existing
dpll_feature_state enum (enable/disable) and is present in both
device-get reply and device-set request.
Add DPLL_A_PIN_MEASURED_FREQUENCY pin attribute to expose the measured
input frequency in Hz. The attribute is present in the pin-get reply.
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
Documentation/driver-api/dpll.rst | 18 ++++++++++++++++++
Documentation/netlink/specs/dpll.yaml | 17 +++++++++++++++++
drivers/dpll/dpll_nl.c | 5 +++--
include/uapi/linux/dpll.h | 2 ++
4 files changed, 40 insertions(+), 2 deletions(-)
diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst
index 83118c728ed90..f38e7012e8c0a 100644
--- a/Documentation/driver-api/dpll.rst
+++ b/Documentation/driver-api/dpll.rst
@@ -250,6 +250,22 @@ in the ``DPLL_A_PIN_PHASE_OFFSET`` attribute.
``DPLL_A_PHASE_OFFSET_MONITOR`` attr state of a feature
=============================== ========================
+Frequency monitor
+=================
+
+Some DPLL devices may offer the capability to measure the actual
+frequency of all available input pins. The attribute and current feature state
+shall be included in the response message of the ``DPLL_CMD_DEVICE_GET``
+command for supported DPLL devices. In such cases, users can also control
+the feature using the ``DPLL_CMD_DEVICE_SET`` command by setting the
+``enum dpll_feature_state`` values for the attribute.
+Once enabled the measured input frequency for each input pin shall be
+returned in the ``DPLL_A_PIN_MEASURED_FREQUENCY`` attribute.
+
+ =============================== ========================
+ ``DPLL_A_FREQUENCY_MONITOR`` attr state of a feature
+ =============================== ========================
+
Embedded SYNC
=============
@@ -411,6 +427,8 @@ according to attribute purpose.
``DPLL_A_PIN_STATE`` attr state of pin on the parent
pin
``DPLL_A_PIN_CAPABILITIES`` attr bitmask of pin capabilities
+ ``DPLL_A_PIN_MEASURED_FREQUENCY`` attr measured frequency of
+ an input pin in Hz
==================================== ==================================
==================================== =================================
diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml
index 3dd48a32f7837..80292b0f2b488 100644
--- a/Documentation/netlink/specs/dpll.yaml
+++ b/Documentation/netlink/specs/dpll.yaml
@@ -319,6 +319,13 @@ attribute-sets:
name: phase-offset-avg-factor
type: u32
doc: Averaging factor applied to calculation of reported phase offset.
+ -
+ name: frequency-monitor
+ type: u32
+ enum: feature-state
+ doc: Receive or request state of frequency monitor feature.
+ If enabled, dpll device shall measure all currently available
+ inputs for their actual input frequency.
-
name: pin
enum-name: dpll_a_pin
@@ -456,6 +463,13 @@ attribute-sets:
Value is in PPT (parts per trillion, 10^-12).
Note: This attribute provides higher resolution than the standard
fractional-frequency-offset (which is in PPM).
+ -
+ name: measured-frequency
+ type: u64
+ doc: |
+ The measured frequency of the input pin in Hz.
+ This is the actual frequency being received on the pin,
+ as measured by the dpll device hardware.
-
name: pin-parent-device
@@ -544,6 +558,7 @@ operations:
- type
- phase-offset-monitor
- phase-offset-avg-factor
+ - frequency-monitor
dump:
reply: *dev-attrs
@@ -563,6 +578,7 @@ operations:
- mode
- phase-offset-monitor
- phase-offset-avg-factor
+ - frequency-monitor
-
name: device-create-ntf
doc: Notification about device appearing
@@ -643,6 +659,7 @@ operations:
- esync-frequency-supported
- esync-pulse
- reference-sync
+ - measured-frequency
dump:
request:
diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c
index a2b22d4921142..1e652340a5d73 100644
--- a/drivers/dpll/dpll_nl.c
+++ b/drivers/dpll/dpll_nl.c
@@ -43,11 +43,12 @@ static const struct nla_policy dpll_device_get_nl_policy[DPLL_A_ID + 1] = {
};
/* DPLL_CMD_DEVICE_SET - do */
-static const struct nla_policy dpll_device_set_nl_policy[DPLL_A_PHASE_OFFSET_AVG_FACTOR + 1] = {
+static const struct nla_policy dpll_device_set_nl_policy[DPLL_A_FREQUENCY_MONITOR + 1] = {
[DPLL_A_ID] = { .type = NLA_U32, },
[DPLL_A_MODE] = NLA_POLICY_RANGE(NLA_U32, 1, 2),
[DPLL_A_PHASE_OFFSET_MONITOR] = NLA_POLICY_MAX(NLA_U32, 1),
[DPLL_A_PHASE_OFFSET_AVG_FACTOR] = { .type = NLA_U32, },
+ [DPLL_A_FREQUENCY_MONITOR] = NLA_POLICY_MAX(NLA_U32, 1),
};
/* DPLL_CMD_PIN_ID_GET - do */
@@ -115,7 +116,7 @@ static const struct genl_split_ops dpll_nl_ops[] = {
.doit = dpll_nl_device_set_doit,
.post_doit = dpll_post_doit,
.policy = dpll_device_set_nl_policy,
- .maxattr = DPLL_A_PHASE_OFFSET_AVG_FACTOR,
+ .maxattr = DPLL_A_FREQUENCY_MONITOR,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h
index de0005f28e5c5..2e75d547716ea 100644
--- a/include/uapi/linux/dpll.h
+++ b/include/uapi/linux/dpll.h
@@ -218,6 +218,7 @@ enum dpll_a {
DPLL_A_CLOCK_QUALITY_LEVEL,
DPLL_A_PHASE_OFFSET_MONITOR,
DPLL_A_PHASE_OFFSET_AVG_FACTOR,
+ DPLL_A_FREQUENCY_MONITOR,
__DPLL_A_MAX,
DPLL_A_MAX = (__DPLL_A_MAX - 1)
@@ -254,6 +255,7 @@ enum dpll_a_pin {
DPLL_A_PIN_REFERENCE_SYNC,
DPLL_A_PIN_PHASE_ADJUST_GRAN,
DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT,
+ DPLL_A_PIN_MEASURED_FREQUENCY,
__DPLL_A_PIN_MAX,
DPLL_A_PIN_MAX = (__DPLL_A_PIN_MAX - 1)
--
2.52.0
^ permalink raw reply related
* [PATCH net-next v2 2/3] dpll: add frequency monitoring callback ops
From: Ivan Vecera @ 2026-03-30 10:55 UTC (permalink / raw)
To: netdev
Cc: Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Jonathan Corbet, Shuah Khan, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Donald Hunter,
Prathosh Satish, Petr Oros, linux-doc, linux-kernel
In-Reply-To: <20260330105505.715099-1-ivecera@redhat.com>
Add new callback operations for a dpll device:
- freq_monitor_get(..) - to obtain current state of frequency monitor
feature from dpll device,
- freq_monitor_set(..) - to allow feature configuration.
Add new callback operation for a dpll pin:
- measured_freq_get(..) - to obtain the measured frequency in Hz.
Obtain the feature state value using the get callback and provide it to
the user if the device driver implements callbacks. The measured_freq_get
pin callback is only invoked when the frequency monitor is enabled.
Execute the set callback upon user requests.
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/dpll_netlink.c | 90 +++++++++++++++++++++++++++++++++++++
include/linux/dpll.h | 10 +++++
2 files changed, 100 insertions(+)
diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
index 83cbd64abf5a4..abddcd4de28b6 100644
--- a/drivers/dpll/dpll_netlink.c
+++ b/drivers/dpll/dpll_netlink.c
@@ -175,6 +175,26 @@ dpll_msg_add_phase_offset_monitor(struct sk_buff *msg, struct dpll_device *dpll,
return 0;
}
+static int
+dpll_msg_add_freq_monitor(struct sk_buff *msg, struct dpll_device *dpll,
+ struct netlink_ext_ack *extack)
+{
+ const struct dpll_device_ops *ops = dpll_device_ops(dpll);
+ enum dpll_feature_state state;
+ int ret;
+
+ if (ops->freq_monitor_set && ops->freq_monitor_get) {
+ ret = ops->freq_monitor_get(dpll, dpll_priv(dpll),
+ &state, extack);
+ if (ret)
+ return ret;
+ if (nla_put_u32(msg, DPLL_A_FREQUENCY_MONITOR, state))
+ return -EMSGSIZE;
+ }
+
+ return 0;
+}
+
static int
dpll_msg_add_phase_offset_avg_factor(struct sk_buff *msg,
struct dpll_device *dpll,
@@ -400,6 +420,38 @@ static int dpll_msg_add_ffo(struct sk_buff *msg, struct dpll_pin *pin,
ffo);
}
+static int dpll_msg_add_measured_freq(struct sk_buff *msg, struct dpll_pin *pin,
+ struct dpll_pin_ref *ref,
+ struct netlink_ext_ack *extack)
+{
+ const struct dpll_device_ops *dev_ops = dpll_device_ops(ref->dpll);
+ const struct dpll_pin_ops *ops = dpll_pin_ops(ref);
+ struct dpll_device *dpll = ref->dpll;
+ enum dpll_feature_state state;
+ u64 measured_freq;
+ int ret;
+
+ if (!ops->measured_freq_get)
+ return 0;
+ if (dev_ops->freq_monitor_get) {
+ ret = dev_ops->freq_monitor_get(dpll, dpll_priv(dpll),
+ &state, extack);
+ if (ret)
+ return ret;
+ if (state == DPLL_FEATURE_STATE_DISABLE)
+ return 0;
+ }
+ ret = ops->measured_freq_get(pin, dpll_pin_on_dpll_priv(dpll, pin),
+ dpll, dpll_priv(dpll), &measured_freq, extack);
+ if (ret)
+ return ret;
+ if (nla_put_64bit(msg, DPLL_A_PIN_MEASURED_FREQUENCY,
+ sizeof(measured_freq), &measured_freq, DPLL_A_PIN_PAD))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
static int
dpll_msg_add_pin_freq(struct sk_buff *msg, struct dpll_pin *pin,
struct dpll_pin_ref *ref, struct netlink_ext_ack *extack)
@@ -670,6 +722,9 @@ dpll_cmd_pin_get_one(struct sk_buff *msg, struct dpll_pin *pin,
if (ret)
return ret;
ret = dpll_msg_add_ffo(msg, pin, ref, extack);
+ if (ret)
+ return ret;
+ ret = dpll_msg_add_measured_freq(msg, pin, ref, extack);
if (ret)
return ret;
ret = dpll_msg_add_pin_esync(msg, pin, ref, extack);
@@ -722,6 +777,9 @@ dpll_device_get_one(struct dpll_device *dpll, struct sk_buff *msg,
if (ret)
return ret;
ret = dpll_msg_add_phase_offset_avg_factor(msg, dpll, extack);
+ if (ret)
+ return ret;
+ ret = dpll_msg_add_freq_monitor(msg, dpll, extack);
if (ret)
return ret;
@@ -948,6 +1006,32 @@ dpll_phase_offset_avg_factor_set(struct dpll_device *dpll, struct nlattr *a,
extack);
}
+static int
+dpll_freq_monitor_set(struct dpll_device *dpll, struct nlattr *a,
+ struct netlink_ext_ack *extack)
+{
+ const struct dpll_device_ops *ops = dpll_device_ops(dpll);
+ enum dpll_feature_state state = nla_get_u32(a), old_state;
+ int ret;
+
+ if (!(ops->freq_monitor_set && ops->freq_monitor_get)) {
+ NL_SET_ERR_MSG_ATTR(extack, a,
+ "dpll device not capable of frequency monitor");
+ return -EOPNOTSUPP;
+ }
+ ret = ops->freq_monitor_get(dpll, dpll_priv(dpll), &old_state,
+ extack);
+ if (ret) {
+ NL_SET_ERR_MSG(extack,
+ "unable to get current state of frequency monitor");
+ return ret;
+ }
+ if (state == old_state)
+ return 0;
+
+ return ops->freq_monitor_set(dpll, dpll_priv(dpll), state, extack);
+}
+
static int
dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a,
struct netlink_ext_ack *extack)
@@ -1878,6 +1962,12 @@ dpll_set_from_nlattr(struct dpll_device *dpll, struct genl_info *info)
if (ret)
return ret;
break;
+ case DPLL_A_FREQUENCY_MONITOR:
+ ret = dpll_freq_monitor_set(dpll, a,
+ info->extack);
+ if (ret)
+ return ret;
+ break;
}
}
diff --git a/include/linux/dpll.h b/include/linux/dpll.h
index 2ce295b46b8cd..b7277a8b484d2 100644
--- a/include/linux/dpll.h
+++ b/include/linux/dpll.h
@@ -52,6 +52,12 @@ struct dpll_device_ops {
int (*phase_offset_avg_factor_get)(const struct dpll_device *dpll,
void *dpll_priv, u32 *factor,
struct netlink_ext_ack *extack);
+ int (*freq_monitor_set)(const struct dpll_device *dpll, void *dpll_priv,
+ enum dpll_feature_state state,
+ struct netlink_ext_ack *extack);
+ int (*freq_monitor_get)(const struct dpll_device *dpll, void *dpll_priv,
+ enum dpll_feature_state *state,
+ struct netlink_ext_ack *extack);
};
struct dpll_pin_ops {
@@ -110,6 +116,10 @@ struct dpll_pin_ops {
int (*ffo_get)(const struct dpll_pin *pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
s64 *ffo, struct netlink_ext_ack *extack);
+ int (*measured_freq_get)(const struct dpll_pin *pin, void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, u64 *measured_freq,
+ struct netlink_ext_ack *extack);
int (*esync_set)(const struct dpll_pin *pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
u64 freq, struct netlink_ext_ack *extack);
--
2.52.0
^ permalink raw reply related
* [PATCH net-next v2 3/3] dpll: zl3073x: implement frequency monitoring
From: Ivan Vecera @ 2026-03-30 10:55 UTC (permalink / raw)
To: netdev
Cc: Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Jonathan Corbet, Shuah Khan, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Donald Hunter,
Prathosh Satish, Petr Oros, linux-doc, linux-kernel
In-Reply-To: <20260330105505.715099-1-ivecera@redhat.com>
Extract common measurement latch logic from zl3073x_ref_ffo_update()
into a new zl3073x_ref_freq_meas_latch() helper and add
zl3073x_ref_freq_meas_update() that uses it to latch and read absolute
input reference frequencies in Hz.
Add meas_freq field to struct zl3073x_ref and the corresponding
zl3073x_ref_meas_freq_get() accessor. The measured frequencies are
updated periodically alongside the existing FFO measurements.
Add freq_monitor boolean to struct zl3073x_dpll and implement the
freq_monitor_set/get device callbacks to enable/disable frequency
monitoring via the DPLL netlink interface.
Implement measured_freq_get pin callback for input pins that returns the
measured input frequency in Hz.
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/zl3073x/core.c | 88 +++++++++++++++++++++++++++++++------
drivers/dpll/zl3073x/dpll.c | 88 ++++++++++++++++++++++++++++++++++++-
drivers/dpll/zl3073x/dpll.h | 2 +
drivers/dpll/zl3073x/ref.h | 14 ++++++
4 files changed, 178 insertions(+), 14 deletions(-)
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 6363002d48d46..320c199637efa 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -632,22 +632,21 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel)
}
/**
- * zl3073x_ref_ffo_update - update reference fractional frequency offsets
+ * zl3073x_ref_freq_meas_latch - latch reference frequency measurements
* @zldev: pointer to zl3073x_dev structure
+ * @type: measurement type (ZL_REF_FREQ_MEAS_CTRL_*)
*
- * The function asks device to update fractional frequency offsets latch
- * registers the latest measured values, reads and stores them into
+ * The function waits for the previous measurement to finish, selects all
+ * references and requests a new measurement of the given type.
*
* Return: 0 on success, <0 on error
*/
static int
-zl3073x_ref_ffo_update(struct zl3073x_dev *zldev)
+zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type)
{
- int i, rc;
+ int rc;
- /* Per datasheet we have to wait for 'ref_freq_meas_ctrl' to be zero
- * to ensure that the measured data are coherent.
- */
+ /* Wait for previous measurement to finish */
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
ZL_REF_FREQ_MEAS_CTRL);
if (rc)
@@ -663,15 +662,64 @@ zl3073x_ref_ffo_update(struct zl3073x_dev *zldev)
if (rc)
return rc;
- /* Request frequency offset measurement */
- rc = zl3073x_write_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
- ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF);
+ /* Request measurement */
+ rc = zl3073x_write_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, type);
if (rc)
return rc;
/* Wait for finish */
- rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
- ZL_REF_FREQ_MEAS_CTRL);
+ return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
+ ZL_REF_FREQ_MEAS_CTRL);
+}
+
+/**
+ * zl3073x_ref_freq_meas_update - update measured input reference frequencies
+ * @zldev: pointer to zl3073x_dev structure
+ *
+ * The function asks device to latch measured input reference frequencies
+ * and stores the results in the ref state.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_ref_freq_meas_update(struct zl3073x_dev *zldev)
+{
+ int i, rc;
+
+ rc = zl3073x_ref_freq_meas_latch(zldev, ZL_REF_FREQ_MEAS_CTRL_REF_FREQ);
+ if (rc)
+ return rc;
+
+ /* Read measured frequencies in Hz (unsigned 32-bit, LSB = 1 Hz) */
+ for (i = 0; i < ZL3073X_NUM_REFS; i++) {
+ u32 value;
+
+ rc = zl3073x_read_u32(zldev, ZL_REG_REF_FREQ(i), &value);
+ if (rc)
+ return rc;
+
+ zldev->ref[i].meas_freq = value;
+ }
+
+ return 0;
+}
+
+/**
+ * zl3073x_ref_ffo_update - update reference fractional frequency offsets
+ * @zldev: pointer to zl3073x_dev structure
+ *
+ * The function asks device to update fractional frequency offsets latch
+ * registers the latest measured values, reads and stores them into
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_ref_ffo_update(struct zl3073x_dev *zldev)
+{
+ int i, rc;
+
+ rc = zl3073x_ref_freq_meas_latch(zldev,
+ ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF);
if (rc)
return rc;
@@ -714,6 +762,20 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
dev_warn(zldev->dev, "Failed to update phase offsets: %pe\n",
ERR_PTR(rc));
+ /* Update measured input reference frequencies if any DPLL has
+ * frequency monitoring enabled.
+ */
+ list_for_each_entry(zldpll, &zldev->dplls, list) {
+ if (zldpll->freq_monitor) {
+ rc = zl3073x_ref_freq_meas_update(zldev);
+ if (rc)
+ dev_warn(zldev->dev,
+ "Failed to update measured frequencies: %pe\n",
+ ERR_PTR(rc));
+ break;
+ }
+ }
+
/* Update references' fractional frequency offsets */
rc = zl3073x_ref_ffo_update(zldev);
if (rc)
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index a29f606318f6d..c44bfecf2c265 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -39,6 +39,7 @@
* @pin_state: last saved pin state
* @phase_offset: last saved pin phase offset
* @freq_offset: last saved fractional frequency offset
+ * @measured_freq: last saved measured frequency
*/
struct zl3073x_dpll_pin {
struct list_head list;
@@ -54,6 +55,7 @@ struct zl3073x_dpll_pin {
enum dpll_pin_state pin_state;
s64 phase_offset;
s64 freq_offset;
+ u32 measured_freq;
};
/*
@@ -202,6 +204,20 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
return 0;
}
+static int
+zl3073x_dpll_input_pin_measured_freq_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, u64 *measured_freq,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll_pin *pin = pin_priv;
+
+ *measured_freq = pin->measured_freq;
+
+ return 0;
+}
+
static int
zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin,
void *pin_priv,
@@ -1116,6 +1132,35 @@ zl3073x_dpll_phase_offset_monitor_set(const struct dpll_device *dpll,
return 0;
}
+static int
+zl3073x_dpll_freq_monitor_get(const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_feature_state *state,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+
+ if (zldpll->freq_monitor)
+ *state = DPLL_FEATURE_STATE_ENABLE;
+ else
+ *state = DPLL_FEATURE_STATE_DISABLE;
+
+ return 0;
+}
+
+static int
+zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_feature_state state,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+
+ zldpll->freq_monitor = (state == DPLL_FEATURE_STATE_ENABLE);
+
+ return 0;
+}
+
static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
.direction_get = zl3073x_dpll_pin_direction_get,
.esync_get = zl3073x_dpll_input_pin_esync_get,
@@ -1123,6 +1168,7 @@ static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
.ffo_get = zl3073x_dpll_input_pin_ffo_get,
.frequency_get = zl3073x_dpll_input_pin_frequency_get,
.frequency_set = zl3073x_dpll_input_pin_frequency_set,
+ .measured_freq_get = zl3073x_dpll_input_pin_measured_freq_get,
.phase_offset_get = zl3073x_dpll_input_pin_phase_offset_get,
.phase_adjust_get = zl3073x_dpll_input_pin_phase_adjust_get,
.phase_adjust_set = zl3073x_dpll_input_pin_phase_adjust_set,
@@ -1151,6 +1197,8 @@ static const struct dpll_device_ops zl3073x_dpll_device_ops = {
.phase_offset_avg_factor_set = zl3073x_dpll_phase_offset_avg_factor_set,
.phase_offset_monitor_get = zl3073x_dpll_phase_offset_monitor_get,
.phase_offset_monitor_set = zl3073x_dpll_phase_offset_monitor_set,
+ .freq_monitor_get = zl3073x_dpll_freq_monitor_get,
+ .freq_monitor_set = zl3073x_dpll_freq_monitor_set,
.supported_modes_get = zl3073x_dpll_supported_modes_get,
};
@@ -1593,6 +1641,39 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin)
return false;
}
+/**
+ * zl3073x_dpll_pin_measured_freq_check - check for pin measured frequency change
+ * @pin: pin to check
+ *
+ * Check for the given pin's measured frequency change.
+ *
+ * Return: true on measured frequency change, false otherwise
+ */
+static bool
+zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin)
+{
+ struct zl3073x_dpll *zldpll = pin->dpll;
+ struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_ref *ref;
+ u8 ref_id;
+
+ if (!zldpll->freq_monitor)
+ return false;
+
+ ref_id = zl3073x_input_pin_ref_get(pin->id);
+ ref = zl3073x_ref_state_get(zldev, ref_id);
+
+ if (pin->measured_freq != ref->meas_freq) {
+ dev_dbg(zldev->dev, "%s measured freq changed: %u -> %u\n",
+ pin->label, pin->measured_freq, ref->meas_freq);
+ pin->measured_freq = ref->meas_freq;
+
+ return true;
+ }
+
+ return false;
+}
+
/**
* zl3073x_dpll_changes_check - check for changes and send notifications
* @zldpll: pointer to zl3073x_dpll structure
@@ -1677,13 +1758,18 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
pin_changed = true;
}
- /* Check for phase offset and ffo change once per second */
+ /* Check for phase offset, ffo, and measured freq change
+ * once per second.
+ */
if (zldpll->check_count % 2 == 0) {
if (zl3073x_dpll_pin_phase_offset_check(pin))
pin_changed = true;
if (zl3073x_dpll_pin_ffo_check(pin))
pin_changed = true;
+
+ if (zl3073x_dpll_pin_measured_freq_check(pin))
+ pin_changed = true;
}
if (pin_changed)
diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
index 115ee4f67e7ab..434c32a7db123 100644
--- a/drivers/dpll/zl3073x/dpll.h
+++ b/drivers/dpll/zl3073x/dpll.h
@@ -15,6 +15,7 @@
* @id: DPLL index
* @check_count: periodic check counter
* @phase_monitor: is phase offset monitor enabled
+ * @freq_monitor: is frequency monitor enabled
* @ops: DPLL device operations for this instance
* @dpll_dev: pointer to registered DPLL device
* @tracker: tracking object for the acquired reference
@@ -28,6 +29,7 @@ struct zl3073x_dpll {
u8 id;
u8 check_count;
bool phase_monitor;
+ bool freq_monitor;
struct dpll_device_ops ops;
struct dpll_device *dpll_dev;
dpll_tracker tracker;
diff --git a/drivers/dpll/zl3073x/ref.h b/drivers/dpll/zl3073x/ref.h
index 06d8d4d97ea26..be16be20dbc7e 100644
--- a/drivers/dpll/zl3073x/ref.h
+++ b/drivers/dpll/zl3073x/ref.h
@@ -23,6 +23,7 @@ struct zl3073x_dev;
* @sync_ctrl: reference sync control
* @config: reference config
* @ffo: current fractional frequency offset
+ * @meas_freq: measured input frequency in Hz
* @mon_status: reference monitor status
*/
struct zl3073x_ref {
@@ -40,6 +41,7 @@ struct zl3073x_ref {
);
struct_group(stat, /* Status */
s64 ffo;
+ u32 meas_freq;
u8 mon_status;
);
};
@@ -68,6 +70,18 @@ zl3073x_ref_ffo_get(const struct zl3073x_ref *ref)
return ref->ffo;
}
+/**
+ * zl3073x_ref_meas_freq_get - get measured input frequency
+ * @ref: pointer to ref state
+ *
+ * Return: measured input frequency in Hz
+ */
+static inline u32
+zl3073x_ref_meas_freq_get(const struct zl3073x_ref *ref)
+{
+ return ref->meas_freq;
+}
+
/**
* zl3073x_ref_freq_get - get given input reference frequency
* @ref: pointer to ref state
--
2.52.0
^ permalink raw reply related
* Re: [PATCH 0/3] net: pse-pd: support module-based PSE controller drivers
From: Kory Maincent @ 2026-03-30 11:09 UTC (permalink / raw)
To: Carlo Szelinsky
Cc: Oleksij Rempel, Andrew Lunn, Heiner Kallweit, Russell King,
Jakub Kicinski, David S . Miller, Eric Dumazet, Paolo Abeni,
Simon Horman, netdev, linux-kernel
In-Reply-To: <20260329161014.2908509-1-github@szelinsky.de>
On Sun, 29 Mar 2026 18:10:11 +0200
Carlo Szelinsky <github@szelinsky.de> wrote:
> When a PSE controller driver is built as a module, it may not be probed
> yet when PHYs are registered on the MDIO bus. This causes
> of_pse_control_get() to return -EPROBE_DEFER, destroying the PHY device.
> Later, regulator_late_cleanup disables the unclaimed PSE regulators,
> permanently killing PoE.
That's a good idea. I still think that one day I will remove registering
a regulator for every PIs as it only brings pain and fixups, but well until
then lets continue dealing with it.
You forgot net-next in the patch subject.
> This series fixes the issue in three steps:
>
> 1. Treat -EPROBE_DEFER as non-fatal during PHY registration, allowing
> the PHY to register with psec=NULL.
>
> 2. Add an admin_state_synced flag to pse_pi so that pse_pi_is_enabled()
> reports unclaimed PIs as disabled, preventing regulator_late_cleanup
> from shutting them down. The existing dual-path behavior (software-
> tracked vs. hardware-queried state) is preserved for claimed PIs.
>
> 3. Add pse_control_try_resolve() for lazy PSE control resolution on
> first ethtool access, serialized by RTNL.
>
> This is tested on my setup, but I am not fully sure if this is the right
> approach to solve this problem. I would love to get feedback from the
> maintainers on whether the overall design direction makes sense, or if
> there is a better way to handle the deferred PSE control acquisition.
You approach seems good to me.
--
Köry Maincent, Bootlin
Embedded Linux and kernel engineering
https://bootlin.com
^ permalink raw reply
* Re: [PATCH net-next v4 00/10] selftests: drivers: bash support for remote traffic generators
From: Ioana Ciornei @ 2026-03-30 11:11 UTC (permalink / raw)
To: Petr Machata
Cc: Jakub Kicinski, netdev, Andrew Lunn, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, linux-kernel, willemb,
linux-kselftest
In-Reply-To: <87a4vp61m4.fsf@nvidia.com>
On Mon, Mar 30, 2026 at 12:38:54PM +0200, Petr Machata wrote:
>
> Jakub Kicinski <kuba@kernel.org> writes:
>
> > On Fri, 27 Mar 2026 09:32:22 +0200 Ioana Ciornei wrote:
> >> On Thu, Mar 26, 2026 at 12:03:42PM -0700, Jakub Kicinski wrote:
> >> > On Thu, 26 Mar 2026 15:28:18 +0200 Ioana Ciornei wrote:
> >> > > This patch set aims to add the necessary support so that bash written
> >> > > selftests are also able to easily run with a remote traffic generator
> >> > > system, either be it in another netns or one accessible through ssh.
> >> > >
> >> > > This patch set is a result of the discussion from v1:
> >> > > https://lore.kernel.org/all/20260303084330.340b6459@kernel.org/
> >> > > Even though the python infrastructure is already established, some
> >> > > things are easier in bash and it would be a shame to leave behind the
> >> > > bash tests that we already have.
> >> >
> >> > I think this introduces a bunch of regressions, eg:
> >> >
> >> > https://netdev-ctrl.bots.linux.dev/logs/vmksft/forwarding/results/575622/4-local-termination-sh/stdout
> >> >
> >> > https://netdev-ctrl.bots.linux.dev/logs/vmksft/netdevsim/results/575802/18-netcons-resume-sh/stdout
> >>
> >> I cannot reproduce this unfortunately. For example, local_termination.sh
> >> gives me the following result with the exact patches that I submitted.
> >> Any idea on what might be the difference?
> >
> > Hm, the system that runs this on our end is:
> >
> > # cat /etc/redhat-release
> > Fedora release 43 (Forty Three)
> >
> > And it has this added on top of default install:
> >
> > # cat /etc/systemd/network/99-default.link
> > [Match]
> > OriginalName=*
> >
> > [Link]
> > NamePolicy=keep kernel database onboard slot path
> > AlternativeNamesPolicy=database onboard slot path mac
> > MACAddressPolicy=none
>
> The observed issues are consistent with TARGETS being defined, but not
> an array:
>
> $ declare -A T
> $ T=([a.100]=b)
> $ U=foo
> $ if declare -p T &>/dev/null; then echo "${T[a.100]}"; else echo fail; fi
> b
> $ if declare -p U &>/dev/null; then echo "${U[a.100]}"; else echo fail; fi
> bash: a.100: syntax error: invalid arithmetic operator (error token is ".100")
Totally agree, that's what it looks like.
On the other hand, I don't see any other use of the TARGETS variable in
tools/testing/selftests. The only way I can trigger that kind of error
is by setting and exporting TARGETS before running the test.
root@localhost:~/ksft-net-drv# export TARGETS=test
root@localhost:~/ksft-net-drv# ./run_kselftest.sh -t drivers/net/netconsole:netcons_resume.sh
[ 1895.134633] kselftest: Running tests in drivers/net/netconsole
TAP version 13
1..1
# timeout set to 45
# selftests: drivers/net/netconsole: netcons_resume.sh
# Running with bind mode: ifname
# /root/ksft-net-drv/drivers/net/lib/sh/../../../../net/lib.sh: line 707: eni497np1: unbound variable
not ok 1 selftests: drivers/net/netconsole: netcons_resume.sh # exit=1
So this means that TARGETS is used by the netdev testing
infrastructure, I presume.
Anyhow, I can change the variable name to avoid this. Something like
NETIF_TARGETS?
^ permalink raw reply
* Re: [PATCH net-next v4 00/10] selftests: drivers: bash support for remote traffic generators
From: Petr Machata @ 2026-03-30 11:10 UTC (permalink / raw)
To: Petr Machata
Cc: Jakub Kicinski, Ioana Ciornei, netdev, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
linux-kernel, willemb, linux-kselftest
In-Reply-To: <87a4vp61m4.fsf@nvidia.com>
Petr Machata <petrm@nvidia.com> writes:
> Jakub Kicinski <kuba@kernel.org> writes:
>
>> On Fri, 27 Mar 2026 09:32:22 +0200 Ioana Ciornei wrote:
>>> On Thu, Mar 26, 2026 at 12:03:42PM -0700, Jakub Kicinski wrote:
>>> > On Thu, 26 Mar 2026 15:28:18 +0200 Ioana Ciornei wrote:
>>> > > This patch set aims to add the necessary support so that bash written
>>> > > selftests are also able to easily run with a remote traffic generator
>>> > > system, either be it in another netns or one accessible through ssh.
>>> > >
>>> > > This patch set is a result of the discussion from v1:
>>> > > https://lore.kernel.org/all/20260303084330.340b6459@kernel.org/
>>> > > Even though the python infrastructure is already established, some
>>> > > things are easier in bash and it would be a shame to leave behind the
>>> > > bash tests that we already have.
>>> >
>>> > I think this introduces a bunch of regressions, eg:
>>> >
>>> > https://netdev-ctrl.bots.linux.dev/logs/vmksft/forwarding/results/575622/4-local-termination-sh/stdout
>>> >
>>> > https://netdev-ctrl.bots.linux.dev/logs/vmksft/netdevsim/results/575802/18-netcons-resume-sh/stdout
>>>
>>> I cannot reproduce this unfortunately. For example, local_termination.sh
>>> gives me the following result with the exact patches that I submitted.
>>> Any idea on what might be the difference?
>>
>> Hm, the system that runs this on our end is:
>>
>> # cat /etc/redhat-release
>> Fedora release 43 (Forty Three)
>>
>> And it has this added on top of default install:
>>
>> # cat /etc/systemd/network/99-default.link
>> [Match]
>> OriginalName=*
>>
>> [Link]
>> NamePolicy=keep kernel database onboard slot path
>> AlternativeNamesPolicy=database onboard slot path mac
>> MACAddressPolicy=none
>
> The observed issues are consistent with TARGETS being defined, but not
> an array:
>
> $ declare -A T
> $ T=([a.100]=b)
> $ U=foo
> $ if declare -p T &>/dev/null; then echo "${T[a.100]}"; else echo fail; fi
> b
> $ if declare -p U &>/dev/null; then echo "${U[a.100]}"; else echo fail; fi
> bash: a.100: syntax error: invalid arithmetic operator (error token is ".100")
I'm wondering if there is a shell export of the variable for make to use
for build process, and then it gets inherited by tests launched from
make as well.
Whatever the cause, the test will have to be more careful in how it uses
the variable.
^ permalink raw reply
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