* [PATCH net-next v6 2/5] dpll: add DPLL_PIN_TYPE_INT_NCO pin type
From: Ivan Vecera @ 2026-06-30 12:55 UTC (permalink / raw)
To: netdev
Cc: Jiri Pirko, Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
Eric Dumazet, Jakub Kicinski, Jiri Pirko, Michal Schmidt,
Paolo Abeni, Pasi Vaananen, Petr Oros, Prathosh Satish,
Simon Horman, Vadim Fedorenko, linux-kernel
In-Reply-To: <20260630125536.720717-1-ivecera@redhat.com>
Add DPLL_PIN_TYPE_INT_NCO pin type for virtual pins representing
the NCO mode of a DPLL. When connected as a DPLL input, the DPLL
enters NCO mode where the output frequency is adjusted by the host
via the PTP clock interface.
Update the fractional-frequency-offset and fractional-frequency-
offset-ppt attribute documentation to note that for INT_NCO pins
these attributes represent the DPLL's current output frequency
offset from its nominal frequency.
Reviewed-by: Jiri Pirko <jiri@nvidia.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
Changes:
v2:
- Clarify int-nco pin type documentation to describe frequency
control via the PTP clock interface instead of generic "controlled
by the host".
- Tighten FFO attribute documentation for INT_NCO pins to describe
the DPLL's output frequency offset from nominal frequency.
- Mention both fractional-frequency-offset (PPM) and
fractional-frequency-offset-ppt attributes in the commit message.
---
Documentation/netlink/specs/dpll.yaml | 13 +++++++++++++
drivers/dpll/dpll_nl.c | 2 +-
include/uapi/linux/dpll.h | 4 ++++
3 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml
index 526a5b2df2bd2..cdc8c7b456df8 100644
--- a/Documentation/netlink/specs/dpll.yaml
+++ b/Documentation/netlink/specs/dpll.yaml
@@ -165,6 +165,13 @@ definitions:
-
name: gnss
doc: GNSS recovered clock
+ -
+ name: int-nco
+ doc: |
+ Device internal numerically controlled oscillator.
+ When connected as a DPLL input, the DPLL enters NCO mode
+ where the output frequency is adjusted by the host via
+ the PTP clock interface.
render-max: true
-
type: enum
@@ -462,6 +469,9 @@ attribute-sets:
offset on the media associated with the pin. Inside
the pin-parent-device nest it represents the frequency
offset between the pin and its parent DPLL device.
+ For pins of type PIN_TYPE_INT_NCO this represents
+ the DPLL's current output frequency offset from its
+ nominal frequency.
Value is in PPM (parts per million).
This is a lower-precision version of
fractional-frequency-offset-ppt.
@@ -508,6 +518,9 @@ attribute-sets:
offset on the media associated with the pin. Inside
the pin-parent-device nest it represents the frequency
offset between the pin and its parent DPLL device.
+ For pins of type PIN_TYPE_INT_NCO this represents
+ the DPLL's current output frequency offset from its
+ nominal frequency.
Value is in PPT (parts per trillion, 10^-12).
This is a higher-precision version of
fractional-frequency-offset.
diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c
index ed3bbe9841ea7..b1ba490e72b05 100644
--- a/drivers/dpll/dpll_nl.c
+++ b/drivers/dpll/dpll_nl.c
@@ -61,7 +61,7 @@ static const struct nla_policy dpll_pin_id_get_nl_policy[DPLL_A_PIN_TYPE + 1] =
[DPLL_A_PIN_BOARD_LABEL] = { .type = NLA_NUL_STRING, },
[DPLL_A_PIN_PANEL_LABEL] = { .type = NLA_NUL_STRING, },
[DPLL_A_PIN_PACKAGE_LABEL] = { .type = NLA_NUL_STRING, },
- [DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 5),
+ [DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 6),
};
/* DPLL_CMD_PIN_GET - do */
diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h
index 5d7ca6a413cdd..85b898b1db5ee 100644
--- a/include/uapi/linux/dpll.h
+++ b/include/uapi/linux/dpll.h
@@ -129,6 +129,9 @@ enum dpll_type {
* @DPLL_PIN_TYPE_SYNCE_ETH_PORT: ethernet port PHY's recovered clock
* @DPLL_PIN_TYPE_INT_OSCILLATOR: device internal oscillator
* @DPLL_PIN_TYPE_GNSS: GNSS recovered clock
+ * @DPLL_PIN_TYPE_INT_NCO: Device internal numerically controlled oscillator.
+ * When connected as a DPLL input, the DPLL enters NCO mode where the output
+ * frequency is adjusted by the host via the PTP clock interface.
*/
enum dpll_pin_type {
DPLL_PIN_TYPE_MUX = 1,
@@ -136,6 +139,7 @@ enum dpll_pin_type {
DPLL_PIN_TYPE_SYNCE_ETH_PORT,
DPLL_PIN_TYPE_INT_OSCILLATOR,
DPLL_PIN_TYPE_GNSS,
+ DPLL_PIN_TYPE_INT_NCO,
/* private: */
__DPLL_PIN_TYPE_MAX,
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v6 3/5] dpll: zl3073x: use per-operation poll timeouts
From: Ivan Vecera @ 2026-06-30 12:55 UTC (permalink / raw)
To: netdev
Cc: Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
Eric Dumazet, Jakub Kicinski, Jiri Pirko, Michal Schmidt,
Paolo Abeni, Pasi Vaananen, Petr Oros, Prathosh Satish,
Simon Horman, Vadim Fedorenko, linux-kernel
In-Reply-To: <20260630125536.720717-1-ivecera@redhat.com>
Replace the single 2s timeout in zl3073x_poll_zero_u8() with a
per-caller timeout parameter. Different HW operations have different
expected completion times so using per-operation timeouts improves
error detection. The timeout values are based on proprietary source
code provided by Microchip and own measurement.
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/zl3073x/chan.c | 6 ++++--
drivers/dpll/zl3073x/core.c | 29 +++++++++++++++++------------
drivers/dpll/zl3073x/core.h | 10 +++++++++-
3 files changed, 30 insertions(+), 15 deletions(-)
diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c
index 2fe3c3da84bb5..677a920c16254 100644
--- a/drivers/dpll/zl3073x/chan.c
+++ b/drivers/dpll/zl3073x/chan.c
@@ -33,7 +33,8 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
/* Read df_offset vs tracked reference */
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
- ZL_DPLL_DF_READ_SEM);
+ ZL_DPLL_DF_READ_SEM,
+ ZL_POLL_DF_READ_TIMEOUT_US);
if (rc)
return rc;
@@ -43,7 +44,8 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
return rc;
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
- ZL_DPLL_DF_READ_SEM);
+ ZL_DPLL_DF_READ_SEM,
+ ZL_POLL_DF_READ_TIMEOUT_US);
if (rc)
return rc;
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 8e6416a4741de..0b2050aa2ed92 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -311,17 +311,17 @@ int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val)
* @zldev: zl3073x device pointer
* @reg: register to poll (has to be 8bit register)
* @mask: bit mask for polling
+ * @timeout_us: timeout in microseconds
*
* Waits for bits specified by @mask in register @reg value to be cleared
* by the device.
*
* Returns: 0 on success, <0 on error
*/
-int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask)
+int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,
+ u8 mask, unsigned int timeout_us)
{
- /* Register polling sleep & timeout */
-#define ZL_POLL_SLEEP_US 10
-#define ZL_POLL_TIMEOUT_US 2000000
+#define ZL_POLL_SLEEP_US 10
unsigned int val;
/* Check the register is 8bit */
@@ -335,7 +335,7 @@ int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask)
reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;
return regmap_read_poll_timeout(zldev->regmap, reg, val, !(val & mask),
- ZL_POLL_SLEEP_US, ZL_POLL_TIMEOUT_US);
+ ZL_POLL_SLEEP_US, timeout_us);
}
int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
@@ -354,7 +354,8 @@ int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
return rc;
/* Wait for the operation to actually finish */
- return zl3073x_poll_zero_u8(zldev, op_reg, op_val);
+ return zl3073x_poll_zero_u8(zldev, op_reg, op_val,
+ ZL_POLL_MB_TIMEOUT_US);
}
/**
@@ -377,8 +378,8 @@ zl3073x_do_hwreg_op(struct zl3073x_dev *zldev, u8 op)
return rc;
/* Poll for completion - pending bit cleared */
- return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP,
- ZL_HWREG_OP_PENDING);
+ return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP, ZL_HWREG_OP_PENDING,
+ ZL_POLL_HWREG_TIMEOUT_US);
}
/**
@@ -609,7 +610,8 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel)
* to be zero to ensure that the measured data are coherent.
*/
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
- ZL_REF_PHASE_ERR_READ_RQST_RD);
+ ZL_REF_PHASE_ERR_READ_RQST_RD,
+ ZL_POLL_PHASE_ERR_TIMEOUT_US);
if (rc)
return rc;
@@ -628,7 +630,8 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel)
/* Wait for finish */
return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
- ZL_REF_PHASE_ERR_READ_RQST_RD);
+ ZL_REF_PHASE_ERR_READ_RQST_RD,
+ ZL_POLL_PHASE_ERR_TIMEOUT_US);
}
/**
@@ -648,7 +651,8 @@ zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type)
/* Wait for previous measurement to finish */
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
- ZL_REF_FREQ_MEAS_CTRL);
+ ZL_REF_FREQ_MEAS_CTRL,
+ ZL_POLL_FREQ_MEAS_TIMEOUT_US);
if (rc)
return rc;
@@ -669,7 +673,8 @@ zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type)
/* Wait for finish */
return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
- ZL_REF_FREQ_MEAS_CTRL);
+ ZL_REF_FREQ_MEAS_CTRL,
+ ZL_POLL_FREQ_MEAS_TIMEOUT_US);
}
/**
diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h
index addba378b0df4..6b55a05a222ed 100644
--- a/drivers/dpll/zl3073x/core.h
+++ b/drivers/dpll/zl3073x/core.h
@@ -7,6 +7,7 @@
#include <linux/kthread.h>
#include <linux/list.h>
#include <linux/mutex.h>
+#include <linux/time64.h>
#include <linux/types.h>
#include "chan.h"
@@ -19,6 +20,12 @@ struct device;
struct regmap;
struct zl3073x_dpll;
+/* Per-operation poll timeouts */
+#define ZL_POLL_DF_READ_TIMEOUT_US (25 * USEC_PER_MSEC)
+#define ZL_POLL_FREQ_MEAS_TIMEOUT_US (50 * USEC_PER_MSEC)
+#define ZL_POLL_HWREG_TIMEOUT_US (50 * USEC_PER_MSEC)
+#define ZL_POLL_MB_TIMEOUT_US (30 * USEC_PER_MSEC)
+#define ZL_POLL_PHASE_ERR_TIMEOUT_US (50 * USEC_PER_MSEC)
enum zl3073x_flags {
ZL3073X_FLAG_REF_PHASE_COMP_32_BIT,
@@ -127,7 +134,8 @@ struct zl3073x_hwreg_seq_item {
int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
unsigned int mask_reg, u16 mask_val);
-int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask);
+int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,
+ u8 mask, unsigned int timeout_us);
int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val);
int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val);
int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val);
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v6 4/5] dpll: zl3073x: add per-DPLL serialization lock
From: Ivan Vecera @ 2026-06-30 12:55 UTC (permalink / raw)
To: netdev
Cc: Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
Eric Dumazet, Jakub Kicinski, Jiri Pirko, Michal Schmidt,
Paolo Abeni, Pasi Vaananen, Petr Oros, Prathosh Satish,
Simon Horman, Vadim Fedorenko, linux-kernel
In-Reply-To: <20260630125536.720717-1-ivecera@redhat.com>
Add a per-DPLL mutex that serializes all operations on a given DPLL
channel across DPLL netlink callbacks, the periodic kthread worker,
and (in subsequent patches) PTP clock callbacks.
All DPLL pin and device callbacks that access mutable state take the
lock as the first operation. The periodic worker holds it for the
entire check cycle of each channel, deferring change notifications
until after the lock is released to avoid ABBA deadlock with
dpll_lock. This establishes the lock ordering:
dpll_lock (subsystem, outer) -> zldpll->lock (driver, inner).
Move zl3073x_chan_state_update() from the per-device
zl3073x_dev_chan_states_update() loop into the per-DPLL
zl3073x_dpll_changes_check() so it runs under zldpll->lock.
This serializes df_offset writes with all readers and
eliminates the need for separate df_offset synchronization.
Change pin->freq_offset from atomic64_t to plain s64 since all
readers and writers are now serialized by zldpll->lock, making
atomic access unnecessary.
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/zl3073x/core.c | 19 +---
drivers/dpll/zl3073x/core.h | 2 +-
drivers/dpll/zl3073x/dpll.c | 189 +++++++++++++++++++++++++++---------
drivers/dpll/zl3073x/dpll.h | 2 +
4 files changed, 149 insertions(+), 63 deletions(-)
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 0b2050aa2ed92..7f5afaaae6342 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -567,19 +567,7 @@ zl3073x_dev_ref_states_update(struct zl3073x_dev *zldev)
}
}
-static void
-zl3073x_dev_chan_states_update(struct zl3073x_dev *zldev)
-{
- int i, rc;
- for (i = 0; i < zldev->info->num_channels; i++) {
- rc = zl3073x_chan_state_update(zldev, i);
- if (rc)
- dev_warn(zldev->dev,
- "Failed to get DPLL%u state: %pe\n", i,
- ERR_PTR(rc));
- }
-}
/**
* zl3073x_ref_phase_offsets_update - update reference phase offsets
@@ -720,9 +708,6 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
/* Update input references' states */
zl3073x_dev_ref_states_update(zldev);
- /* Update DPLL channels' states */
- zl3073x_dev_chan_states_update(zldev);
-
/* Update DPLL-to-connected-ref phase offsets registers */
rc = zl3073x_ref_phase_offsets_update(zldev, -1);
if (rc)
@@ -732,7 +717,7 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
/* Update measured input reference frequencies if frequency
* monitoring is enabled.
*/
- if (zldev->freq_monitor) {
+ if (READ_ONCE(zldev->freq_monitor)) {
rc = zl3073x_ref_freq_meas_update(zldev);
if (rc)
dev_warn(zldev->dev,
@@ -768,7 +753,7 @@ int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor)
return rc;
/* Save the new factor */
- zldev->phase_avg_factor = factor;
+ WRITE_ONCE(zldev->phase_avg_factor, factor);
return 0;
}
diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h
index 6b55a05a222ed..78dc208f3eea2 100644
--- a/drivers/dpll/zl3073x/core.h
+++ b/drivers/dpll/zl3073x/core.h
@@ -101,7 +101,7 @@ void zl3073x_dev_stop(struct zl3073x_dev *zldev);
static inline u8 zl3073x_dev_phase_avg_factor_get(struct zl3073x_dev *zldev)
{
- return zldev->phase_avg_factor;
+ return READ_ONCE(zldev->phase_avg_factor);
}
int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor);
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 5e58ded5734d7..87e8b7c865485 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -1,6 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only
-#include <linux/atomic.h>
#include <linux/bits.h>
#include <linux/bitfield.h>
#include <linux/bug.h>
@@ -58,7 +57,7 @@ struct zl3073x_dpll_pin {
s32 phase_gran;
enum dpll_pin_operstate operstate;
s64 phase_offset;
- atomic64_t freq_offset;
+ s64 freq_offset;
u32 measured_freq;
};
@@ -134,6 +133,8 @@ zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin,
const struct zl3073x_ref *ref;
u8 ref_id;
+ guard(mutex)(&zldpll->lock);
+
ref_id = zl3073x_input_pin_ref_get(pin->id);
ref = zl3073x_ref_state_get(zldev, ref_id);
@@ -170,6 +171,8 @@ zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin,
struct zl3073x_ref ref;
u8 ref_id, sync_mode;
+ guard(mutex)(&zldpll->lock);
+
ref_id = zl3073x_input_pin_ref_get(pin->id);
ref = *zl3073x_ref_state_get(zldev, ref_id);
@@ -205,6 +208,8 @@ zl3073x_dpll_input_pin_ref_sync_get(const struct dpll_pin *dpll_pin,
const struct zl3073x_ref *ref;
u8 ref_id, mode, pair;
+ guard(mutex)(&zldpll->lock);
+
ref_id = zl3073x_input_pin_ref_get(pin->id);
ref = zl3073x_ref_state_get(zldev, ref_id);
mode = zl3073x_ref_sync_mode_get(ref);
@@ -236,6 +241,8 @@ zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin,
struct zl3073x_ref ref;
int rc;
+ guard(mutex)(&zldpll->lock);
+
ref_id = zl3073x_input_pin_ref_get(pin->id);
sync_ref_id = zl3073x_input_pin_ref_get(sync_pin->id);
ref = *zl3073x_ref_state_get(zldev, ref_id);
@@ -299,12 +306,15 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
struct dpll_ffo_param *ffo,
struct netlink_ext_ack *extack)
{
+ struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dpll_pin *pin = pin_priv;
+ guard(mutex)(&zldpll->lock);
+
if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
return -ENODATA;
- ffo->ffo = atomic64_read(&pin->freq_offset);
+ ffo->ffo = pin->freq_offset;
return 0;
}
@@ -316,8 +326,11 @@ zl3073x_dpll_input_pin_measured_freq_get(const struct dpll_pin *dpll_pin,
void *dpll_priv, u64 *measured_freq,
struct netlink_ext_ack *extack)
{
+ struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dpll_pin *pin = pin_priv;
+ guard(mutex)(&zldpll->lock);
+
*measured_freq = pin->measured_freq;
*measured_freq *= DPLL_PIN_MEASURED_FREQUENCY_DIVIDER;
@@ -335,6 +348,8 @@ zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin,
struct zl3073x_dpll_pin *pin = pin_priv;
u8 ref_id;
+ guard(mutex)(&zldpll->lock);
+
ref_id = zl3073x_input_pin_ref_get(pin->id);
*frequency = zl3073x_dev_ref_freq_get(zldpll->dev, ref_id);
@@ -354,6 +369,8 @@ zl3073x_dpll_input_pin_frequency_set(const struct dpll_pin *dpll_pin,
struct zl3073x_ref ref;
u8 ref_id;
+ guard(mutex)(&zldpll->lock);
+
/* Get reference state */
ref_id = zl3073x_input_pin_ref_get(pin->id);
ref = *zl3073x_ref_state_get(zldev, ref_id);
@@ -402,6 +419,8 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
u8 conn_id, ref_id;
s64 ref_phase;
+ guard(mutex)(&zldpll->lock);
+
/* Get currently connected reference */
conn_id = zl3073x_dpll_connected_ref_get(zldpll);
@@ -459,6 +478,8 @@ zl3073x_dpll_input_pin_phase_adjust_get(const struct dpll_pin *dpll_pin,
s64 phase_comp;
u8 ref_id;
+ guard(mutex)(&zldpll->lock);
+
/* Read reference configuration */
ref_id = zl3073x_input_pin_ref_get(pin->id);
ref = zl3073x_ref_state_get(zldev, ref_id);
@@ -491,6 +512,8 @@ zl3073x_dpll_input_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
struct zl3073x_ref ref;
u8 ref_id;
+ guard(mutex)(&zldpll->lock);
+
/* Read reference configuration */
ref_id = zl3073x_input_pin_ref_get(pin->id);
ref = *zl3073x_ref_state_get(zldev, ref_id);
@@ -524,6 +547,8 @@ zl3073x_dpll_ref_operstate_get(struct zl3073x_dpll_pin *pin,
const struct zl3073x_ref *ref;
u8 ref_id;
+ lockdep_assert_held(&zldpll->lock);
+
ref_id = zl3073x_input_pin_ref_get(pin->id);
/* Check if this pin is the currently locked reference */
@@ -557,6 +582,8 @@ zl3073x_dpll_input_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
const struct zl3073x_chan *chan;
u8 mode, ref;
+ guard(mutex)(&zldpll->lock);
+
chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
ref = zl3073x_input_pin_ref_get(pin->id);
mode = zl3073x_chan_mode_get(chan);
@@ -590,8 +617,11 @@ zl3073x_dpll_input_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin,
enum dpll_pin_operstate *operstate,
struct netlink_ext_ack *extack)
{
+ struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dpll_pin *pin = pin_priv;
+ guard(mutex)(&zldpll->lock);
+
return zl3073x_dpll_ref_operstate_get(pin, operstate);
}
@@ -607,7 +637,9 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
struct zl3073x_dpll_pin *pin = pin_priv;
struct zl3073x_chan chan;
u8 mode, ref;
- int rc;
+ int rc = 0;
+
+ mutex_lock(&zldpll->lock);
chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
ref = zl3073x_input_pin_ref_get(pin->id);
@@ -649,13 +681,13 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
case ZL_DPLL_MODE_REFSEL_MODE_AUTO:
if (state == DPLL_PIN_STATE_SELECTABLE) {
if (zl3073x_chan_ref_is_selectable(&chan, ref))
- return 0; /* Pin is already selectable */
+ goto unlock; /* Pin is already selectable */
/* Restore pin priority in HW */
zl3073x_chan_ref_prio_set(&chan, ref, pin->prio);
} else if (state == DPLL_PIN_STATE_DISCONNECTED) {
if (!zl3073x_chan_ref_is_selectable(&chan, ref))
- return 0; /* Pin is already disconnected */
+ goto unlock; /* Pin is already disconnected */
/* Set pin priority to none in HW */
zl3073x_chan_ref_prio_set(&chan, ref,
@@ -668,18 +700,20 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
/* In other modes we cannot change input reference */
NL_SET_ERR_MSG(extack,
"Pin state cannot be changed in current mode");
- return -EOPNOTSUPP;
+ rc = -EOPNOTSUPP;
+ goto unlock;
}
/* Commit DPLL channel changes */
rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan);
- if (rc)
- return rc;
+ goto unlock;
- return 0;
invalid_state:
NL_SET_ERR_MSG_MOD(extack, "Invalid pin state for this device mode");
- return -EINVAL;
+ rc = -EINVAL;
+unlock:
+ mutex_unlock(&zldpll->lock);
+ return rc;
}
static int
@@ -687,8 +721,11 @@ zl3073x_dpll_input_pin_prio_get(const struct dpll_pin *dpll_pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
u32 *prio, struct netlink_ext_ack *extack)
{
+ struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dpll_pin *pin = pin_priv;
+ guard(mutex)(&zldpll->lock);
+
*prio = pin->prio;
return 0;
@@ -705,6 +742,8 @@ zl3073x_dpll_input_pin_prio_set(const struct dpll_pin *dpll_pin, void *pin_priv,
u8 ref;
int rc;
+ guard(mutex)(&zldpll->lock);
+
if (prio > ZL_DPLL_REF_PRIO_MAX)
return -EINVAL;
@@ -740,6 +779,8 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin,
u32 synth_freq, out_freq;
u8 out_id;
+ guard(mutex)(&zldpll->lock);
+
out_id = zl3073x_output_pin_out_get(pin->id);
out = zl3073x_out_state_get(zldev, out_id);
@@ -797,6 +838,8 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
u32 synth_freq;
u8 out_id;
+ guard(mutex)(&zldpll->lock);
+
out_id = zl3073x_output_pin_out_get(pin->id);
out = *zl3073x_out_state_get(zldev, out_id);
@@ -817,7 +860,7 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
/* If esync is being disabled just write mailbox and finish */
if (!freq)
- goto write_mailbox;
+ return zl3073x_out_state_set(zldev, out_id, &out);
/* Get attached synth frequency */
synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(&out));
@@ -834,7 +877,6 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
*/
out.esync_n_width = out.div / 2;
-write_mailbox:
/* Commit output configuration */
return zl3073x_out_state_set(zldev, out_id, &out);
}
@@ -849,6 +891,8 @@ zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin,
struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dpll_pin *pin = pin_priv;
+ guard(mutex)(&zldpll->lock);
+
*frequency = zl3073x_dev_output_pin_freq_get(zldpll->dev, pin->id);
return 0;
@@ -869,6 +913,8 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
struct zl3073x_out out;
u8 out_id;
+ guard(mutex)(&zldpll->lock);
+
out_id = zl3073x_output_pin_out_get(pin->id);
out = *zl3073x_out_state_get(zldev, out_id);
@@ -942,6 +988,8 @@ zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin,
const struct zl3073x_out *out;
u8 out_id;
+ guard(mutex)(&zldpll->lock);
+
out_id = zl3073x_output_pin_out_get(pin->id);
out = zl3073x_out_state_get(zldev, out_id);
@@ -965,6 +1013,8 @@ zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
struct zl3073x_out out;
u8 out_id;
+ guard(mutex)(&zldpll->lock);
+
out_id = zl3073x_output_pin_out_get(pin->id);
out = *zl3073x_out_state_get(zldev, out_id);
@@ -998,6 +1048,8 @@ zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv,
u16 val;
int rc;
+ guard(mutex)(&zldpll->lock);
+
rc = zl3073x_read_u16(zldev, ZL_REG_DIE_TEMP_STATUS, &val);
if (rc)
return rc;
@@ -1009,14 +1061,13 @@ zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv,
}
static int
-zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
- enum dpll_lock_status *status,
- enum dpll_lock_status_error *status_error,
- struct netlink_ext_ack *extack)
+__zl3073x_dpll_lock_status_get(struct zl3073x_dpll *zldpll,
+ enum dpll_lock_status *status)
{
- struct zl3073x_dpll *zldpll = dpll_priv;
const struct zl3073x_chan *chan;
+ lockdep_assert_held(&zldpll->lock);
+
chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
switch (zl3073x_chan_mode_get(chan)) {
@@ -1052,6 +1103,19 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
return 0;
}
+static int
+zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
+ enum dpll_lock_status *status,
+ enum dpll_lock_status_error *status_error,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+
+ guard(mutex)(&zldpll->lock);
+
+ return __zl3073x_dpll_lock_status_get(zldpll, status);
+}
+
static int
zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll,
void *dpll_priv, unsigned long *modes,
@@ -1060,6 +1124,8 @@ zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll,
struct zl3073x_dpll *zldpll = dpll_priv;
const struct zl3073x_chan *chan;
+ guard(mutex)(&zldpll->lock);
+
chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
/* We support switching between automatic and manual mode, except in
@@ -1082,6 +1148,8 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
struct zl3073x_dpll *zldpll = dpll_priv;
const struct zl3073x_chan *chan;
+ guard(mutex)(&zldpll->lock);
+
chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
switch (zl3073x_chan_mode_get(chan)) {
@@ -1138,8 +1206,8 @@ zl3073x_dpll_phase_offset_avg_factor_set(const struct dpll_device *dpll,
return rc;
}
- /* The averaging factor is common for all DPLL channels so after change
- * we have to send a notification for other DPLL devices.
+ /* The averaging factor is common for all DPLL channels so after
+ * change we have to send a notification for other DPLL devices.
*/
list_for_each_entry(item, &zldpll->dev->dplls, list) {
struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev);
@@ -1160,6 +1228,8 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
u8 hw_mode, ref;
int rc;
+ guard(mutex)(&zldpll->lock);
+
chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
ref = zl3073x_chan_refsel_ref_get(&chan);
@@ -1221,6 +1291,8 @@ zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll,
{
struct zl3073x_dpll *zldpll = dpll_priv;
+ guard(mutex)(&zldpll->lock);
+
if (zldpll->phase_monitor)
*state = DPLL_FEATURE_STATE_ENABLE;
else
@@ -1237,6 +1309,8 @@ zl3073x_dpll_phase_offset_monitor_set(const struct dpll_device *dpll,
{
struct zl3073x_dpll *zldpll = dpll_priv;
+ guard(mutex)(&zldpll->lock);
+
zldpll->phase_monitor = (state == DPLL_FEATURE_STATE_ENABLE);
return 0;
@@ -1250,7 +1324,7 @@ zl3073x_dpll_freq_monitor_get(const struct dpll_device *dpll,
{
struct zl3073x_dpll *zldpll = dpll_priv;
- if (zldpll->dev->freq_monitor)
+ if (READ_ONCE(zldpll->dev->freq_monitor))
*state = DPLL_FEATURE_STATE_ENABLE;
else
*state = DPLL_FEATURE_STATE_DISABLE;
@@ -1265,13 +1339,14 @@ zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll,
struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *item, *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->dev;
- zldpll->dev->freq_monitor = (state == DPLL_FEATURE_STATE_ENABLE);
+ WRITE_ONCE(zldev->freq_monitor, state == DPLL_FEATURE_STATE_ENABLE);
/* The frequency monitoring is common for all DPLL channels so after
* change we have to send a notification for other DPLL devices.
*/
- list_for_each_entry(item, &zldpll->dev->dplls, list) {
+ list_for_each_entry(item, &zldev->dplls, list) {
struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev);
if (item != zldpll && dpll_dev)
@@ -1697,6 +1772,8 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin)
u8 ref_id;
int rc;
+ lockdep_assert_held(&zldpll->lock);
+
/* No phase offset if the ref monitor reports signal errors */
ref_id = zl3073x_input_pin_ref_get(pin->id);
if (!zl3073x_dev_ref_is_status_ok(zldev, ref_id))
@@ -1753,6 +1830,8 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin)
const struct zl3073x_chan *chan;
s64 ffo;
+ lockdep_assert_held(&zldpll->lock);
+
if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
return false;
@@ -1760,9 +1839,10 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin)
ffo = mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan),
244140625, 36);
- if (atomic64_xchg(&pin->freq_offset, ffo) != ffo) {
+ if (pin->freq_offset != ffo) {
dev_dbg(zldev->dev, "%s freq offset changed to: %lld\n",
pin->label, ffo);
+ pin->freq_offset = ffo;
return true;
}
@@ -1787,7 +1867,9 @@ zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin)
u8 ref_id;
u32 freq;
- if (!zldpll->dev->freq_monitor)
+ lockdep_assert_held(&zldpll->lock);
+
+ if (!READ_ONCE(zldpll->dev->freq_monitor))
return false;
ref_id = zl3073x_input_pin_ref_get(pin->id);
@@ -1817,27 +1899,37 @@ zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin)
void
zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
{
+ DECLARE_BITMAP(changed_pins, ZL3073X_NUM_INPUT_PINS);
struct zl3073x_dev *zldev = zldpll->dev;
enum dpll_lock_status lock_status;
struct device *dev = zldev->dev;
struct zl3073x_dpll_pin *pin;
+ bool dev_changed = false;
int rc;
+ bitmap_zero(changed_pins, ZL3073X_NUM_INPUT_PINS);
+
+ mutex_lock(&zldpll->lock);
+
zldpll->check_count++;
- /* Get current lock status for the DPLL */
- rc = zl3073x_dpll_lock_status_get(zldpll->dpll_dev, zldpll,
- &lock_status, NULL, NULL);
+ rc = zl3073x_chan_state_update(zldev, zldpll->id);
+ if (rc) {
+ dev_err(dev, "Failed to get DPLL%u state: %pe\n",
+ zldpll->id, ERR_PTR(rc));
+ goto unlock;
+ }
+
+ rc = __zl3073x_dpll_lock_status_get(zldpll, &lock_status);
if (rc) {
dev_err(dev, "Failed to get DPLL%u lock status: %pe\n",
zldpll->id, ERR_PTR(rc));
- return;
+ goto unlock;
}
- /* If lock status was changed then notify DPLL core */
if (zldpll->lock_status != lock_status) {
zldpll->lock_status = lock_status;
- dpll_device_change_ntf(zldpll->dpll_dev);
+ dev_changed = true;
}
/* Update phase offset latch registers for this DPLL if the phase
@@ -1849,17 +1941,13 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
dev_err(zldev->dev,
"Failed to update phase offsets: %pe\n",
ERR_PTR(rc));
- return;
+ goto unlock;
}
}
list_for_each_entry(pin, &zldpll->pins, list) {
enum dpll_pin_operstate operstate;
- bool pin_changed = false;
- /* Output pins change checks are not necessary because output
- * states are constant.
- */
if (!zl3073x_dpll_is_input_pin(pin))
continue;
@@ -1868,31 +1956,40 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
dev_err(dev,
"Failed to get %s on DPLL%u oper state: %pe\n",
pin->label, zldpll->id, ERR_PTR(rc));
- return;
+ goto unlock;
}
if (operstate != pin->operstate) {
dev_dbg(dev, "%s oper state changed: %u->%u\n",
pin->label, pin->operstate, operstate);
pin->operstate = operstate;
- pin_changed = true;
+ set_bit(pin->id, changed_pins);
}
- /* 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;
+ set_bit(pin->id, changed_pins);
if (zl3073x_dpll_pin_ffo_check(pin))
- pin_changed = true;
+ set_bit(pin->id, changed_pins);
if (zl3073x_dpll_pin_measured_freq_check(pin))
- pin_changed = true;
+ set_bit(pin->id, changed_pins);
}
+ }
- if (pin_changed)
+unlock:
+ mutex_unlock(&zldpll->lock);
+
+ /* Send notifications outside the lock to avoid ABBA deadlock
+ * with dpll_lock taken by notification functions.
+ */
+ if (dev_changed)
+ dpll_device_change_ntf(zldpll->dpll_dev);
+
+ list_for_each_entry(pin, &zldpll->pins, list) {
+ if (zl3073x_dpll_is_input_pin(pin) &&
+ test_bit(pin->id, changed_pins))
dpll_pin_change_ntf(pin->dpll_pin);
}
}
@@ -1949,6 +2046,7 @@ zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)
zldpll->dev = zldev;
zldpll->id = ch;
+ mutex_init(&zldpll->lock);
INIT_LIST_HEAD(&zldpll->pins);
return zldpll;
@@ -1965,6 +2063,7 @@ zl3073x_dpll_free(struct zl3073x_dpll *zldpll)
{
WARN(zldpll->dpll_dev, "DPLL device is still registered\n");
+ mutex_destroy(&zldpll->lock);
kfree(zldpll);
}
diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
index 21adcc18e45e1..9f57c944a0077 100644
--- a/drivers/dpll/zl3073x/dpll.h
+++ b/drivers/dpll/zl3073x/dpll.h
@@ -18,6 +18,7 @@
* @ops: DPLL device operations for this instance
* @dpll_dev: pointer to registered DPLL device
* @tracker: tracking object for the acquired reference
+ * @lock: per-DPLL mutex serializing all operations
* @lock_status: last saved DPLL lock status
* @pins: list of pins
*/
@@ -30,6 +31,7 @@ struct zl3073x_dpll {
struct dpll_device_ops ops;
struct dpll_device *dpll_dev;
dpll_tracker tracker;
+ struct mutex lock;
enum dpll_lock_status lock_status;
struct list_head pins;
};
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v6 5/5] dpll: zl3073x: add NCO virtual input pin
From: Ivan Vecera @ 2026-06-30 12:55 UTC (permalink / raw)
To: netdev
Cc: Petr Oros, Chris du Quesnay, Arkadiusz Kubalewski,
David S. Miller, Donald Hunter, Eric Dumazet, Jakub Kicinski,
Jiri Pirko, Michal Schmidt, Paolo Abeni, Pasi Vaananen,
Prathosh Satish, Simon Horman, Vadim Fedorenko, linux-kernel
In-Reply-To: <20260630125536.720717-1-ivecera@redhat.com>
Add a virtual NCO (Numerically Controlled Oscillator) input pin that
lets userspace switch a DPLL channel into NCO mode. A single NCO pin
is shared across all DPLL channels - each channel has its own
independent connection state. The NCO pin is registered with the new
DPLL_PIN_TYPE_INT_NCO type and reports DPLL_PIN_STATE_CONNECTED /
DPLL_PIN_OPERSTATE_ACTIVE when the channel is in NCO mode.
At NCO pin registration the following bits are configured in
dpll_ctrl_x:
- nco_auto_read: auto-capture tracking offset on NCO entry
- tod_step_reset: apply negated ToD step accumulator on NCO exit
- tie_clear: PPS DPLLs set 1 to re-align outputs on NCO exit,
EEC DPLLs keep 0 to prevent an unwanted TIE write
Before switching to NCO mode, dpll_df_read_x is configured with
ref_ofst=0 and cmd=ACC_I so that nco_auto_read captures the
accumulated I-part offset relative to the master clock. Without
this, the captured df_offset would be near zero (offset relative
to the input reference after lock).
On NCO entry the df_offset captured by nco_auto_read is read from
the register. Per the datasheet, nco_auto_read only captures a valid
offset when entering NCO from reflock, auto or holdover mode; from
freerun the captured value is not meaningful and df_offset is marked as
ZL_DPLL_DF_OFFSET_UNKNOWN. The same sentinel is set in
chan_state_update() when the channel is not locked, and both FFO
consumers (NCO pin and input pin) guard against it.
Disconnecting the NCO pin switches to freerun rather than holdover
because holdover averaging is not updated during NCO mode.
When connecting the NCO pin displaces a previously connected input
pin (reflock mode), a change notification is sent for that input pin.
Input reference pins are now always registered regardless of the
initial DPLL mode. Previously they were skipped when the DPLL was
in NCO mode, but the NCO pin provides the proper mechanism for
mode transitions.
Reviewed-by: Petr Oros <poros@redhat.com>
Tested-by: Chris du Quesnay <Chris.duQuesnay@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
Changes:
v6:
- Increase nco_auto_read delays to 25 ms (worst-case internal
register update time reported by Chris du Quesnay, Microchip).
- Add 25 ms delay between df_read configuration and NCO mode
switch.
v5:
- Configure dpll_df_read register before NCO mode switch:
ref_ofst=0 to read offset relative to master clock,
cmd=ACC_I for accumulated I-part. Without this, nco_auto_read
captures incorrect df_offset (reported by Chris du Quesnay).
v4:
- Drop multiop_lock from chan_state_update() and nco_mode_set(),
df_offset access is now serialized by the per-DPLL
zldpll->lock introduced in the new lock patch.
- Add zldpll->lock guard to all NCO pin callbacks for
consistency with the lock patch.
- Use mutex_lock/unlock in nco_pin_register,
nco_pin_state_on_dpll_set, and input_pin_state_on_dpll_set
instead of guard()/scoped_guard() to avoid mixing cleanup
helpers with goto-based error handling.
- Filter NCO pin in the deferred notification loop to match the
monitoring loop filter.
- Introduce ZL_DPLL_DF_OFFSET_UNKNOWN (S64_MIN) sentinel for
df_offset: set on read failure, when entering NCO from a
freerun mode, and when
chan_state_update() finds the channel not locked. Guard both
NCO pin and input pin FFO consumers against the sentinel.
- Send __dpll_pin_change_ntf() for the displaced input pin when
connecting the NCO pin from reflock mode.
- Read df_offset from register at probe when firmware left the
channel in NCO mode.
- Add comment clarifying that nco_auto_read completes before the
mode switch (specified by the datasheet and verified by
HW testing).
- Unify df_offset sign convention comments with datasheet
reference (f_offset = f_nom * (-df_offset) / 2^48).
v3:
- Fixed Signed-off-by position
v2:
- Configure nco_auto_read, tod_step_reset and tie_clear once at
NCO pin registration since these are persistent R/W bits.
In v1 nco_auto_read was set at registration, while tod_step_reset
and tie_clear were set on each NCO exit path.
- Add zl3073x_chan_nco_mode_set() helper that writes mode_refsel
directly and reads df_offset from the register without the
DF_READ semaphore protocol. A 25 ms delay is needed for the
device to update internal registers before nco_auto_read
populates the df_offset register (determined by HW testing).
In v1 the full DF_READ semaphore protocol with
zl3073x_chan_state_update() was used.
- Zero df_offset on read failure instead of keeping stale value.
- Serialize zl3073x_chan_state_update() and
zl3073x_chan_nco_mode_set() with multiop_lock to prevent
concurrent df_offset access from the periodic worker.
- Gate df_offset read in zl3073x_chan_state_update() on LOCK state
instead of skipping NCO channels in chan_states_update(). This
keeps mon_status and refsel_status fresh in all modes.
- Send __dpll_pin_change_ntf() for the NCO pin when leaving NCO
mode via mode_set() or input pin connect, since the periodic
worker skips the NCO pin.
- Add comments explaining the inverted sign convention of the
dpll_df_offset register.
- Document why NCO disconnect selects freerun over holdover, the
shared NCO pin design, and the input pin registration change.
---
drivers/dpll/zl3073x/chan.c | 122 +++++++++++++-
drivers/dpll/zl3073x/chan.h | 48 ++++++
drivers/dpll/zl3073x/dpll.c | 308 ++++++++++++++++++++++++++++++++----
drivers/dpll/zl3073x/dpll.h | 2 +
drivers/dpll/zl3073x/regs.h | 11 ++
5 files changed, 462 insertions(+), 29 deletions(-)
diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c
index 677a920c16254..4ec2cf53dad46 100644
--- a/drivers/dpll/zl3073x/chan.c
+++ b/drivers/dpll/zl3073x/chan.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/cleanup.h>
+#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/string.h>
#include <linux/types.h>
@@ -31,7 +32,15 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
if (rc)
return rc;
- /* Read df_offset vs tracked reference */
+ /* Read df_offset only when locked to a reference. In NCO mode
+ * df_offset was captured at entry by nco_mode_set() - preserve it.
+ */
+ if (!zl3073x_chan_is_locked(chan)) {
+ if (!zl3073x_chan_mode_is_nco(chan))
+ chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN;
+ return 0;
+ }
+
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
ZL_DPLL_DF_READ_SEM,
ZL_POLL_DF_READ_TIMEOUT_US);
@@ -58,6 +67,96 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
return 0;
}
+/**
+ * zl3073x_chan_nco_mode_set - switch DPLL channel to NCO mode
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: DPLL channel index
+ *
+ * Switches the channel to NCO mode and reads the df_offset
+ * auto-captured by nco_auto_read directly from the register.
+ * No DF_READ handshake is needed as nco_auto_read populates
+ * the register before the mode switch completes.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index)
+{
+ struct zl3073x_chan *chan = &zldev->chan[index];
+ u8 prev_mode, df_read;
+ u64 val;
+ int rc;
+
+ prev_mode = zl3073x_chan_mode_get(chan);
+
+ /* nco_auto_read captures the tracking offset at NCO entry only
+ * from reflock, auto or holdover mode. From freerun the captured
+ * value is not meaningful.
+ */
+ if (prev_mode == ZL_DPLL_MODE_REFSEL_MODE_FREERUN) {
+ zl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO);
+
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
+ chan->mode_refsel);
+ if (rc) {
+ zl3073x_chan_mode_set(chan, prev_mode);
+ return rc;
+ }
+
+ chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN;
+ return 0;
+ }
+
+ /* Configure df_read for nco_auto_read:
+ * ref_ofst=0 - reads offset relative to master clock (not input ref)
+ * cmd=CMD_ACC_I - accumulated I-part covering both locked and
+ * holdover entry.
+ *
+ * No semaphore is set - this only configures what the df_offset
+ * value represents after the mode switch; nco_auto_read performs
+ * the actual read automatically.
+ */
+ df_read = FIELD_PREP(ZL_DPLL_DF_READ_REF_OFST, 0) |
+ FIELD_PREP(ZL_DPLL_DF_READ_CMD, ZL_DPLL_DF_READ_CMD_ACC_I);
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index), df_read);
+ if (rc)
+ return rc;
+
+ /* Wait for df_read configuration to take effect before
+ * triggering nco_auto_read via mode switch. The worst-case
+ * internal register update time is 25 ms.
+ */
+ fsleep(25000);
+
+ zl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO);
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
+ chan->mode_refsel);
+ if (rc) {
+ zl3073x_chan_mode_set(chan, prev_mode);
+ return rc;
+ }
+
+ /* Wait for nco_auto_read to populate df_offset. The worst-case
+ * internal register update time is 25 ms.
+ */
+ fsleep(25000);
+
+ /* Read df_offset captured by nco_auto_read during mode switch.
+ * No DF_READ semaphore handshake needed. Mode switch already
+ * succeeded, so don't propagate a read failure back to userspace.
+ */
+ rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val);
+ if (rc) {
+ dev_warn(zldev->dev,
+ "Failed to read DPLL%u df_offset: %pe\n",
+ index, ERR_PTR(rc));
+ chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN;
+ } else {
+ chan->df_offset = sign_extend64(val, 47);
+ }
+
+ return 0;
+}
+
/**
* zl3073x_chan_state_fetch - fetch DPLL channel state from hardware
* @zldev: pointer to zl3073x_dev structure
@@ -73,6 +172,10 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)
struct zl3073x_chan *chan = &zldev->chan[index];
int rc, i;
+ rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_CTRL(index), &chan->ctrl);
+ if (rc)
+ return rc;
+
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
&chan->mode_refsel);
if (rc)
@@ -85,6 +188,13 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)
if (rc)
return rc;
+ /* If firmware left the channel in NCO mode, mark df_offset as
+ * unknown - we cannot know whether the preconditions for a valid
+ * nco_auto_read capture were met.
+ */
+ if (zl3073x_chan_mode_is_nco(chan))
+ chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN;
+
dev_dbg(zldev->dev,
"DPLL%u lock_state: %u, ho: %u, sel_state: %u, sel_ref: %u\n",
index, zl3073x_chan_lock_state_get(chan),
@@ -147,7 +257,15 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
if (!memcmp(&dchan->cfg, &chan->cfg, sizeof(chan->cfg)))
return 0;
- /* Direct register write for mode_refsel */
+ /* Direct register writes for ctrl and mode_refsel */
+ if (dchan->ctrl != chan->ctrl) {
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_CTRL(index),
+ chan->ctrl);
+ if (rc)
+ return rc;
+ dchan->ctrl = chan->ctrl;
+ }
+
if (dchan->mode_refsel != chan->mode_refsel) {
rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
chan->mode_refsel);
diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h
index 4353809c69122..dc9c6d95bdee7 100644
--- a/drivers/dpll/zl3073x/chan.h
+++ b/drivers/dpll/zl3073x/chan.h
@@ -13,6 +13,7 @@ struct zl3073x_dev;
/**
* struct zl3073x_chan - DPLL channel state
+ * @ctrl: DPLL control register value
* @mode_refsel: mode and reference selection register value
* @ref_prio: reference priority registers (4 bits per ref, P/N packed)
* @mon_status: monitor status register value
@@ -21,6 +22,7 @@ struct zl3073x_dev;
*/
struct zl3073x_chan {
struct_group(cfg,
+ u8 ctrl;
u8 mode_refsel;
u8 ref_prio[ZL3073X_NUM_REFS / 2];
);
@@ -38,6 +40,7 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
const struct zl3073x_chan *chan);
int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index);
+int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index);
/**
* zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference
@@ -152,6 +155,51 @@ static inline u8 zl3073x_chan_lock_state_get(const struct zl3073x_chan *chan)
return FIELD_GET(ZL_DPLL_MON_STATUS_STATE, chan->mon_status);
}
+/**
+ * zl3073x_chan_is_locked - check if channel is locked to a reference
+ * @chan: pointer to channel state
+ *
+ * Return: true if channel is locked, false otherwise
+ */
+static inline bool zl3073x_chan_is_locked(const struct zl3073x_chan *chan)
+{
+ u8 lock_state = zl3073x_chan_lock_state_get(chan);
+ return lock_state == ZL_DPLL_MON_STATUS_STATE_LOCK;
+}
+
+/**
+ * zl3073x_chan_mode_is_auto - check if channel is in automatic mode
+ * @chan: pointer to channel state
+ *
+ * Return: true if channel is in automatic mode, false otherwise
+ */
+static inline bool zl3073x_chan_mode_is_auto(const struct zl3073x_chan *chan)
+{
+ return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_AUTO;
+}
+
+/**
+ * zl3073x_chan_mode_is_nco - check if channel is in NCO mode
+ * @chan: pointer to channel state
+ *
+ * Return: true if channel is in NCO mode, false otherwise
+ */
+static inline bool zl3073x_chan_mode_is_nco(const struct zl3073x_chan *chan)
+{
+ return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO;
+}
+
+/**
+ * zl3073x_chan_mode_is_reflock - check if channel is in reflock mode
+ * @chan: pointer to channel state
+ *
+ * Return: true if channel is in reflock mode, false otherwise
+ */
+static inline bool zl3073x_chan_mode_is_reflock(const struct zl3073x_chan *chan)
+{
+ return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_REFLOCK;
+}
+
/**
* zl3073x_chan_is_ho_ready - check if holdover is ready
* @chan: pointer to channel state
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 87e8b7c865485..d91f52b58eae3 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -80,6 +80,18 @@ zl3073x_dpll_is_input_pin(struct zl3073x_dpll_pin *pin)
return pin->dir == DPLL_PIN_DIRECTION_INPUT;
}
+/**
+ * zl3073x_dpll_is_nco_pin - check if the pin is a virtual NCO pin
+ * @pin: pin to check
+ *
+ * Return: true if pin is a virtual NCO pin, false otherwise.
+ */
+static bool
+zl3073x_dpll_is_nco_pin(struct zl3073x_dpll_pin *pin)
+{
+ return pin->id == ZL3073X_NCO_PIN_ID;
+}
+
/**
* zl3073x_dpll_is_p_pin - check if the pin is P-pin
* @pin: pin to check
@@ -119,6 +131,19 @@ zl3073x_dpll_pin_get_by_ref(struct zl3073x_dpll *zldpll, u8 ref_id)
return NULL;
}
+static struct zl3073x_dpll_pin *
+zl3073x_dpll_nco_pin_get(struct zl3073x_dpll *zldpll)
+{
+ struct zl3073x_dpll_pin *pin;
+
+ list_for_each_entry(pin, &zldpll->pins, list) {
+ if (zl3073x_dpll_is_nco_pin(pin))
+ return pin;
+ }
+
+ return NULL;
+}
+
static int
zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin,
void *pin_priv,
@@ -635,6 +660,7 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
{
struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dpll_pin *pin = pin_priv;
+ struct zl3073x_dpll_pin *nco_pin = NULL;
struct zl3073x_chan chan;
u8 mode, ref;
int rc = 0;
@@ -666,6 +692,10 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
goto invalid_state;
}
break;
+ case ZL_DPLL_MODE_REFSEL_MODE_NCO:
+ if (state == DPLL_PIN_STATE_CONNECTED)
+ nco_pin = zl3073x_dpll_nco_pin_get(zldpll);
+ fallthrough;
case ZL_DPLL_MODE_REFSEL_MODE_FREERUN:
case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER:
if (state == DPLL_PIN_STATE_CONNECTED) {
@@ -713,6 +743,13 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
rc = -EINVAL;
unlock:
mutex_unlock(&zldpll->lock);
+
+ /* If leaving NCO mode, notify userspace about the NCO pin
+ * state change - the periodic worker skips the NCO pin.
+ */
+ if (!rc && nco_pin)
+ __dpll_pin_change_ntf(nco_pin->dpll_pin);
+
return rc;
}
@@ -1039,6 +1076,144 @@ zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
return 0;
}
+static int
+zl3073x_dpll_nco_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_operstate *operstate,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ const struct zl3073x_chan *chan;
+
+ guard(mutex)(&zldpll->lock);
+
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ if (zl3073x_chan_mode_is_nco(chan))
+ *operstate = DPLL_PIN_OPERSTATE_ACTIVE;
+ else
+ *operstate = DPLL_PIN_OPERSTATE_STANDBY;
+
+ return 0;
+}
+
+static int
+zl3073x_dpll_nco_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state *state,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ const struct zl3073x_chan *chan;
+
+ guard(mutex)(&zldpll->lock);
+
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ if (zl3073x_chan_mode_is_nco(chan))
+ *state = DPLL_PIN_STATE_CONNECTED;
+ else
+ *state = DPLL_PIN_STATE_DISCONNECTED;
+
+ return 0;
+}
+
+static int
+zl3073x_dpll_nco_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state state,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll_pin *ref_pin = NULL;
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_chan chan;
+ u8 ref;
+ int rc;
+
+ mutex_lock(&zldpll->lock);
+
+ chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+
+ switch (state) {
+ case DPLL_PIN_STATE_CONNECTED:
+ if (zl3073x_chan_mode_is_nco(&chan)) {
+ mutex_unlock(&zldpll->lock);
+ return 0;
+ }
+ if (zl3073x_chan_mode_is_auto(&chan)) {
+ NL_SET_ERR_MSG(extack,
+ "NCO pin cannot be connected in automatic mode");
+ mutex_unlock(&zldpll->lock);
+ return -EINVAL;
+ }
+ if (zl3073x_chan_mode_is_reflock(&chan)) {
+ /* Get currently connected pin */
+ ref = zl3073x_chan_ref_get(&chan);
+ ref_pin = zl3073x_dpll_pin_get_by_ref(zldpll, ref);
+ }
+ rc = zl3073x_chan_nco_mode_set(zldpll->dev, zldpll->id);
+ break;
+ case DPLL_PIN_STATE_DISCONNECTED:
+ if (!zl3073x_chan_mode_is_nco(&chan)) {
+ mutex_unlock(&zldpll->lock);
+ return 0;
+ }
+ /* Switch to freerun - holdover averaging was not
+ * updated during NCO mode.
+ */
+ zl3073x_chan_mode_set(&chan,
+ ZL_DPLL_MODE_REFSEL_MODE_FREERUN);
+ rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan);
+ break;
+ default:
+ NL_SET_ERR_MSG(extack, "invalid pin state for NCO pin");
+ mutex_unlock(&zldpll->lock);
+ return -EINVAL;
+ }
+
+ mutex_unlock(&zldpll->lock);
+
+ if (!rc && ref_pin)
+ __dpll_pin_change_ntf(ref_pin->dpll_pin);
+
+ return rc;
+}
+
+static int
+zl3073x_dpll_nco_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
+ const struct dpll_device *dpll, void *dpll_priv,
+ struct dpll_ffo_param *ffo,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ const struct zl3073x_chan *chan;
+ s64 df_offset;
+
+ guard(mutex)(&zldpll->lock);
+
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ if (!zl3073x_chan_mode_is_nco(chan))
+ return -ENODATA;
+
+ /* Do not report FFO if a failure occurred during switching to NCO. */
+ df_offset = zl3073x_chan_df_offset_get(chan);
+ if (df_offset == ZL_DPLL_DF_OFFSET_UNKNOWN)
+ return -ENODATA;
+
+ /* dpll_df_offset register has inverted sign per datasheet:
+ * f_offset = f_nom * (-df_offset) / 2^48
+ * NCO pin reports DPLL output offset from nominal, so negate.
+ * Convert to PPT: ppt = -df * 5^12 / 2^36
+ */
+ ffo->ffo = -mul_s64_u64_shr(df_offset, 244140625, 36);
+
+ return 0;
+}
+
static int
zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv,
s32 *temp, struct netlink_ext_ack *extack)
@@ -1121,21 +1296,7 @@ zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll,
void *dpll_priv, unsigned long *modes,
struct netlink_ext_ack *extack)
{
- struct zl3073x_dpll *zldpll = dpll_priv;
- const struct zl3073x_chan *chan;
-
- guard(mutex)(&zldpll->lock);
-
- chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
-
- /* We support switching between automatic and manual mode, except in
- * a case where the DPLL channel is configured to run in NCO mode.
- * In this case, report only the manual mode to which the NCO is mapped
- * as the only supported one.
- */
- if (zl3073x_chan_mode_get(chan) != ZL_DPLL_MODE_REFSEL_MODE_NCO)
- __set_bit(DPLL_MODE_AUTOMATIC, modes);
-
+ __set_bit(DPLL_MODE_AUTOMATIC, modes);
__set_bit(DPLL_MODE_MANUAL, modes);
return 0;
@@ -1224,11 +1385,12 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
enum dpll_mode mode, struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dpll_pin *nco_pin = NULL;
struct zl3073x_chan chan;
u8 hw_mode, ref;
int rc;
- guard(mutex)(&zldpll->lock);
+ mutex_lock(&zldpll->lock);
chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
ref = zl3073x_chan_refsel_ref_get(&chan);
@@ -1249,6 +1411,9 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
else
hw_mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER;
} else {
+ if (zl3073x_chan_mode_is_nco(&chan))
+ nco_pin = zl3073x_dpll_nco_pin_get(zldpll);
+
/* We are switching from manual to automatic mode:
* - if there is a valid reference selected then ensure that
* it is selectable after switch to automatic mode
@@ -1277,9 +1442,18 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
if (rc) {
NL_SET_ERR_MSG_MOD(extack,
"failed to set reference selection mode");
+ mutex_unlock(&zldpll->lock);
return rc;
}
+ mutex_unlock(&zldpll->lock);
+
+ /* If leaving NCO mode, notify userspace about the NCO pin
+ * state change - the periodic worker skips the NCO pin.
+ */
+ if (nco_pin)
+ __dpll_pin_change_ntf(nco_pin->dpll_pin);
+
return 0;
}
@@ -1388,6 +1562,15 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
.state_on_dpll_get = zl3073x_dpll_output_pin_state_on_dpll_get,
};
+static const struct dpll_pin_ops zl3073x_dpll_nco_pin_ops = {
+ .supported_ffo = BIT(DPLL_FFO_PIN_DEVICE),
+ .direction_get = zl3073x_dpll_pin_direction_get,
+ .ffo_get = zl3073x_dpll_nco_pin_ffo_get,
+ .operstate_on_dpll_get = zl3073x_dpll_nco_pin_operstate_on_dpll_get,
+ .state_on_dpll_get = zl3073x_dpll_nco_pin_state_on_dpll_get,
+ .state_on_dpll_set = zl3073x_dpll_nco_pin_state_on_dpll_set,
+};
+
static const struct dpll_device_ops zl3073x_dpll_device_ops = {
.lock_status_get = zl3073x_dpll_lock_status_get,
.mode_get = zl3073x_dpll_mode_get,
@@ -1535,7 +1718,9 @@ zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin)
WARN(!pin->dpll_pin, "DPLL pin is not registered\n");
- if (zl3073x_dpll_is_input_pin(pin))
+ if (zl3073x_dpll_is_nco_pin(pin))
+ ops = &zl3073x_dpll_nco_pin_ops;
+ else if (zl3073x_dpll_is_input_pin(pin))
ops = &zl3073x_dpll_input_pin_ops;
else
ops = &zl3073x_dpll_output_pin_ops;
@@ -1588,20 +1773,13 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll,
enum dpll_pin_direction dir, u8 index)
{
struct zl3073x_dev *zldev = zldpll->dev;
- const struct zl3073x_chan *chan;
bool is_diff, is_enabled;
const char *name;
- chan = zl3073x_chan_state_get(zldev, zldpll->id);
-
if (dir == DPLL_PIN_DIRECTION_INPUT) {
u8 ref_id = zl3073x_input_pin_ref_get(index);
const struct zl3073x_ref *ref;
- /* Skip the pin if the DPLL is running in NCO mode */
- if (zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO)
- return false;
-
name = "REF";
ref = zl3073x_ref_state_get(zldev, ref_id);
is_diff = zl3073x_ref_is_diff(ref);
@@ -1642,6 +1820,66 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll,
return true;
}
+static const struct dpll_pin_properties zl3073x_dpll_nco_pin_props = {
+ .type = DPLL_PIN_TYPE_INT_NCO,
+ .package_label = "NCO",
+ .capabilities = DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE,
+};
+
+static int
+zl3073x_dpll_nco_pin_register(struct zl3073x_dpll *zldpll)
+{
+ struct zl3073x_dpll_pin *pin;
+ struct zl3073x_chan chan;
+ int rc;
+
+ /* Ensure that ctrl bits are configured for NCO operation:
+ * - nco_auto_read: auto-capture tracking offset on NCO entry
+ * - tod_step_reset: apply negated ToD step on NCO exit
+ * - tie_clear: PPS DPLLs re-align outputs on NCO exit
+ */
+ mutex_lock(&zldpll->lock);
+ chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ FIELD_MODIFY(ZL_DPLL_CTRL_NCO_AUTO_READ, &chan.ctrl, 1);
+ FIELD_MODIFY(ZL_DPLL_CTRL_TOD_STEP_RST, &chan.ctrl, 1);
+ FIELD_MODIFY(ZL_DPLL_CTRL_TIE_CLEAR, &chan.ctrl,
+ zldpll->type == DPLL_TYPE_PPS ? 1 : 0);
+ rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan);
+ mutex_unlock(&zldpll->lock);
+ if (rc)
+ return rc;
+
+ pin = zl3073x_dpll_pin_alloc(zldpll, DPLL_PIN_DIRECTION_INPUT,
+ ZL3073X_NCO_PIN_ID);
+ if (IS_ERR(pin))
+ return PTR_ERR(pin);
+
+ pin->dpll_pin = dpll_pin_get(zldpll->dev->clock_id, ZL3073X_NCO_PIN_ID,
+ THIS_MODULE, &zl3073x_dpll_nco_pin_props,
+ &pin->tracker);
+ if (IS_ERR(pin->dpll_pin)) {
+ rc = PTR_ERR(pin->dpll_pin);
+ goto err_pin_get;
+ }
+
+ rc = dpll_pin_register(zldpll->dpll_dev, pin->dpll_pin,
+ &zl3073x_dpll_nco_pin_ops, pin);
+ if (rc)
+ goto err_register;
+
+ list_add(&pin->list, &zldpll->pins);
+
+ return 0;
+
+err_register:
+ dpll_pin_put(pin->dpll_pin, &pin->tracker);
+err_pin_get:
+ pin->dpll_pin = NULL;
+ kfree(pin);
+
+ return rc;
+}
+
/**
* zl3073x_dpll_pins_register - register all registerable DPLL pins
* @zldpll: pointer to zl3073x_dpll structure
@@ -1689,6 +1927,11 @@ zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll)
list_add(&pin->list, &zldpll->pins);
}
+ /* Register NCO virtual input pin */
+ rc = zl3073x_dpll_nco_pin_register(zldpll);
+ if (rc)
+ goto error;
+
return 0;
error:
@@ -1724,8 +1967,8 @@ zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll)
return rc;
}
- rc = dpll_device_register(zldpll->dpll_dev,
- zl3073x_prop_dpll_type_get(zldev, zldpll->id),
+ zldpll->type = zl3073x_prop_dpll_type_get(zldev, zldpll->id);
+ rc = dpll_device_register(zldpll->dpll_dev, zldpll->type,
&zldpll->ops, zldpll);
if (rc) {
dpll_device_put(zldpll->dpll_dev, &zldpll->tracker);
@@ -1836,6 +2079,14 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin)
return false;
chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ if (zl3073x_chan_df_offset_get(chan) == ZL_DPLL_DF_OFFSET_UNKNOWN)
+ return false;
+
+ /* dpll_df_offset register has inverted sign per datasheet:
+ * f_offset = f_nom * (-df_offset) / 2^48
+ * Input pin FFO is pin-vs-DPLL (opposite of DPLL-vs-reference),
+ * so the two inversions cancel out: ppt = df * 5^12 / 2^36
+ */
ffo = mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan),
244140625, 36);
@@ -1948,7 +2199,9 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
list_for_each_entry(pin, &zldpll->pins, list) {
enum dpll_pin_operstate operstate;
- if (!zl3073x_dpll_is_input_pin(pin))
+ /* Only physical input pins need monitoring */
+ if (!zl3073x_dpll_is_input_pin(pin) ||
+ zl3073x_dpll_is_nco_pin(pin))
continue;
rc = zl3073x_dpll_ref_operstate_get(pin, &operstate);
@@ -1989,6 +2242,7 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
list_for_each_entry(pin, &zldpll->pins, list) {
if (zl3073x_dpll_is_input_pin(pin) &&
+ !zl3073x_dpll_is_nco_pin(pin) &&
test_bit(pin->id, changed_pins))
dpll_pin_change_ntf(pin->dpll_pin);
}
diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
index 9f57c944a0077..faebc402ba1b7 100644
--- a/drivers/dpll/zl3073x/dpll.h
+++ b/drivers/dpll/zl3073x/dpll.h
@@ -19,6 +19,7 @@
* @dpll_dev: pointer to registered DPLL device
* @tracker: tracking object for the acquired reference
* @lock: per-DPLL mutex serializing all operations
+ * @type: DPLL type (PPS or EEC)
* @lock_status: last saved DPLL lock status
* @pins: list of pins
*/
@@ -32,6 +33,7 @@ struct zl3073x_dpll {
struct dpll_device *dpll_dev;
dpll_tracker tracker;
struct mutex lock;
+ enum dpll_type type;
enum dpll_lock_status lock_status;
struct list_head pins;
};
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index 9578f00095282..b70ead7d4495b 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -5,6 +5,7 @@
#include <linux/bitfield.h>
#include <linux/bits.h>
+#include <linux/limits.h>
/*
* Hardware limits for ZL3073x chip family
@@ -17,6 +18,7 @@
#define ZL3073X_NUM_OUTPUT_PINS (ZL3073X_NUM_OUTS * 2)
#define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \
ZL3073X_NUM_OUTPUT_PINS)
+#define ZL3073X_NCO_PIN_ID ZL3073X_NUM_PINS
/*
* Register address structure:
@@ -164,10 +166,18 @@
#define ZL_DPLL_MODE_REFSEL_MODE_NCO 4
#define ZL_DPLL_MODE_REFSEL_REF GENMASK(7, 4)
+#define ZL_REG_DPLL_CTRL(_idx) \
+ ZL_REG_IDX(_idx, 5, 0x05, 1, ZL3073X_MAX_CHANNELS, 4)
+#define ZL_DPLL_CTRL_TIE_CLEAR BIT(0)
+#define ZL_DPLL_CTRL_TOD_STEP_RST BIT(2)
+#define ZL_DPLL_CTRL_NCO_AUTO_READ BIT(7)
+
#define ZL_REG_DPLL_DF_READ(_idx) \
ZL_REG_IDX(_idx, 5, 0x28, 1, ZL3073X_MAX_CHANNELS, 1)
#define ZL_DPLL_DF_READ_SEM BIT(4)
#define ZL_DPLL_DF_READ_REF_OFST BIT(3)
+#define ZL_DPLL_DF_READ_CMD GENMASK(2, 0)
+#define ZL_DPLL_DF_READ_CMD_ACC_I 4
#define ZL_REG_DPLL_MEAS_CTRL ZL_REG(5, 0x50, 1)
#define ZL_DPLL_MEAS_CTRL_EN BIT(0)
@@ -190,6 +200,7 @@
#define ZL_REG_DPLL_DF_OFFSET_4 ZL_REG(7, 0x00, 6)
#define ZL_REG_DPLL_DF_OFFSET(_idx) \
((_idx) < 4 ? ZL_REG_DPLL_DF_OFFSET_03(_idx) : ZL_REG_DPLL_DF_OFFSET_4)
+#define ZL_DPLL_DF_OFFSET_UNKNOWN S64_MIN
/***********************************
* Register Page 9, Synth and Output
--
2.53.0
^ permalink raw reply related
* Re: [TEST] intel: low timeout
From: Pielech, Adrian @ 2026-06-30 12:56 UTC (permalink / raw)
To: Jakub Kicinski
Cc: Kitszel, Przemyslaw, netdev@vger.kernel.org, intel-wired-lan,
leszek.pepiak
In-Reply-To: <20260627095400.3c3d9f80@kernel.org>
On 6/27/2026 6:54 PM, Jakub Kicinski wrote:
> Hi!
>
> Some of the tests need more than 5min, could you increase the timeout
> in the runner to 10 or 15min? Looks like it's hard-killing tests right
> now after 2min:
>
> https://netdev-ci-results.intel.com/ice-results/net-next-hw-2026-06-27--16-00/ice-E810-XXV4/xdp.py/stdout
>
> which leaks config across tests:
>
> https://netdev-ci-results.intel.com/ice-results/net-next-hw-2026-06-27--16-00/ice-E810-XXV4/irq.py/stdout
>
> BTW the JSON reports the timed out tests as pass.
Hi Jakub,
I've increased timeout to 10 minutes per test run. It seems to help with
XDP tests score.
I'll later take a look on default behavior of runner in case of timeouts.
^ permalink raw reply
* Re: [PATCH net] ppp: fix use-after-free reads in the stats ioctls.
From: Paolo Abeni @ 2026-06-30 13:06 UTC (permalink / raw)
To: Norbert Szetei, netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
linux-ppp, linux-kernel, Qingfang Deng
In-Reply-To: <CF6F0CC7-C448-406B-8E24-2025AD585D18@doyensec.com>
Adding Qingfang.
On 6/28/26 2:44 PM, Norbert Szetei wrote:
> ppp_get_stats() (SIOCGPPPSTATS) and the SIOCGPPPCSTATS handler, both
> reached from ppp_net_siocdevprivate(), dereference state that other
> ioctls free under the ppp lock, without taking it:
>
> - ppp_get_stats() reads ppp->vj; PPPIOCSMAXCID frees it with
> slhc_free() under ppp_lock().
> - SIOCGPPPCSTATS calls ->comp_stat()/->decomp_stat() on
> ppp->xc_state / ppp->rc_state; PPPIOCSCOMPRESS and ppp_ccp_closed()
> free those.
>
> A concurrent stats ioctl can then read freed memory (slab-use-after-
> free), and the freed contents are copied back to userspace. This is
> reachable by a local user who has CAP_NET_ADMIN privileges and
> read/write access to /dev/ppp.
>
> Take the lock the freeing path holds around each access: the receive
> lock in ppp_get_stats() (PPPIOCSMAXCID frees ppp->vj under ppp_lock(),
> which includes it) and ppp_lock() around the SIOCGPPPCSTATS callbacks.
>
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Assisted-by: Claude:claude-opus-4-8
> Signed-off-by: Norbert Szetei <norbert@doyensec.com>
> ---
> drivers/net/ppp/ppp_generic.c | 14 ++++++++++++--
> 1 file changed, 12 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c
> index 57c68efa5ff8..847c5e1793c8 100644
> --- a/drivers/net/ppp/ppp_generic.c
> +++ b/drivers/net/ppp/ppp_generic.c
> @@ -1505,10 +1505,13 @@ ppp_net_siocdevprivate(struct net_device *dev, struct ifreq *ifr,
>
> case SIOCGPPPCSTATS:
> memset(&cstats, 0, sizeof(cstats));
> + /* protect against PPPIOCSCOMPRESS/ppp_ccp_closed() freeing the state */
> + ppp_lock(ppp);
> if (ppp->xc_state)
> ppp->xcomp->comp_stat(ppp->xc_state, &cstats.c);
> if (ppp->rc_state)
> ppp->rcomp->decomp_stat(ppp->rc_state, &cstats.d);
> + ppp_unlock(ppp);
It looks like that this fix addresses the reported races, but I don't
like stats blocking the TX and RX path. Perhaps you should consider
switching to proper RCU for the relevant structs, and likely 2 separate
patches, one for xc_state/rc_state and the other for vj.
/P
^ permalink raw reply
* [PATCH v3] bpf: Fix smp_processor_id() call trace for preemptible kernels
From: Edward Adam Davis @ 2026-06-30 13:09 UTC (permalink / raw)
To: eadavis
Cc: jiayuan.chen, sashiko-bot, sashiko-reviews, andrii, ast, bpf,
daniel, eddyz87, emil, jolsa, linux-kernel, martin.lau, memxor,
netdev, song, syzkaller-bugs, yonghong.song
In-Reply-To: <tencent_B6259ABAF5BB63335B9E3C1BB74B071BD608@qq.com>
bpf_mem_cache_free_rcu() maybe called in preemptible context, this
will trigger the below warning message:
BUG: using smp_processor_id() in preemptible [00000000] code: syz.0.17/5820
caller is bpf_mem_cache_free_rcu+0x48/0xc0 kernel/bpf/memalloc.c:954
Call Trace:
check_preemption_disabled+0xd3/0xe0 lib/smp_processor_id.c:47
bpf_mem_cache_free_rcu+0x48/0xc0 kernel/bpf/memalloc.c:954
rhtab_delete_elem+0x185a/0x1b30 kernel/bpf/hashtab.c:2969
__rhtab_map_lookup_and_delete_batch+0x935/0xcb0 kernel/bpf/hashtab.c:3349
bpf_map_do_batch+0x445/0x630 kernel/bpf/syscall.c:-1
__sys_bpf+0x906/0xd90 kernel/bpf/syscall.c:-1
this_cpu_ptr() access needs to be guarded against preemption.
Fixes: 5af6807bdb10 ("bpf: Introduce bpf_mem_free_rcu() similar to kfree_rcu().")
Reported-by: syzbot+fd7e415d891073b83e1f@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=fd7e415d891073b83e1f
Signed-off-by: Edward Adam Davis <eadavis@qq.com>
---
v1 -> v2: using guard against preemption
v2 -> v3: replace get/put_cpu() to bpf_disable/enable_instrumentation()
kernel/bpf/hashtab.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c
index 9f394e1aa2e8..d3f2f8a379e8 100644
--- a/kernel/bpf/hashtab.c
+++ b/kernel/bpf/hashtab.c
@@ -3299,6 +3299,7 @@ static int __rhtab_map_lookup_and_delete_batch(struct bpf_map *map,
dst_val = values;
total = 0;
+ bpf_disable_instrumentation();
rcu_read_lock();
/*
@@ -3313,6 +3314,7 @@ static int __rhtab_map_lookup_and_delete_batch(struct bpf_map *map,
elem = rhtab_lookup_elem(map, cursor);
if (!elem) {
rcu_read_unlock();
+ bpf_enable_instrumentation();
ret = -EAGAIN;
goto free;
}
@@ -3350,6 +3352,7 @@ static int __rhtab_map_lookup_and_delete_batch(struct bpf_map *map,
}
rcu_read_unlock();
+ bpf_enable_instrumentation();
if (total == 0) {
ret = -ENOENT;
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v2 0/3] Introduce HSR/PRP HW offload support for PRU-ICSSM Ethernet driver
From: Parvathi Pudi @ 2026-06-30 12:46 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, danishanwar,
parvathi, rogerq, pmohan, afd, basharath, arnd
Cc: linux-kernel, netdev, linux-arm-kernel, pratheesh, j-rameshbabu,
vigneshr, praneeth, srk, rogerq, m-malladi, krishna, mohan
Hi,
This series introduces HSR and PRP protocol HW offload support for ICSSM-Prueth driver.
HW offload support for HSR/PRP is implemented using dedicated HSR/PRP firmware running
on 2 PRU cores(PRU-ICSS) as a "DAN" available in AM57xx, AM437x and AM335x.
The following features are offloaded to HW in case of HSR and PRP:
1. L2 forwarding of a HSR frame via traditional store and forward or via cut-through (only for HSR)
2. Transmit frame duplication is offloaded to HW
3. Tag removal on the receive is offloaded to HW
4. Redundant duplicate packet discard on the receive is also offloaded to HW
In HW offload mode, redundant tag insertion in the transmit path will be still done by HSR driver
and firmware updates the LAN information available in the tag on the fly when PRU is transmitting
frame in that respective LAN.
HSR Test Setup:
--------------
___________ ______________ ___________
| | Link AB | | Link BC | |
__| AM57* |_________|AM57/AM43/AM33|_________| AM57* |___
| | Station A | | Station B | | Station C | |
| |___________| |______________| |___________| |
| |
|_________________________________________________________________|
Link CA
Steps to switch to HSR forward offload mode:
-------------------------------------------------
Example assuming eth1, eth2 ports of ICSSM on AM57x, AM437x and AM335x EVM's
1) Bring down both slave interfaces
ip link set eth1 down
ip link set eth2 down
2) Set matching MAC addresses on both slave interfaces
ip link set eth1 address <mac-addr>
ip link set eth2 address <mac-addr>
3) Enable HSR offload for both interfaces
ethtool -K eth1 hsr-fwd-offload on
ethtool -K eth1 hsr-dup-offload on
ethtool -K eth1 hsr-tag-rm-offload on
ethtool -K eth2 hsr-fwd-offload on
ethtool -K eth2 hsr-dup-offload on
ethtool -K eth2 hsr-tag-rm-offload on
4) Create HSR interface and add slave interfaces to it
ip link add name hsr0 type hsr slave1 eth1 slave2 eth2 \
supervision 45 version 1
5) Add IP address to the HSR interface
ip addr add <IP_ADDR>/24 dev hsr0
6) Bring up the HSR interface
ip link set hsr0 up
7) Bring up the both slave ports
ip link set eth1 up
ip link set eth2 up
Switching back to default mode:
--------------------------------
1) Bring down both slave interfaces
ip link set eth1 down
ip link set eth2 down
2) Delete HSR interface
ip link delete hsr0
3) Disable HSR port-to-port offloading mode, packet duplication
ethtool -K eth1 hsr-fwd-offload off
ethtool -K eth1 hsr-dup-offload off
ethtool -K eth1 hsr-tag-rm-offload off
ethtool -K eth2 hsr-fwd-offload off
ethtool -K eth2 hsr-dup-offload off
ethtool -K eth2 hsr-tag-rm-offload off
Testing the port-to-port frame forward offload feature:
-------------------------------------------------------
1) Connect the LAN cables as shown in the test setup.
2) Configure Station A and Station C in HSR non-offload mode.
3) Configure Station B is HSR offload mode.
4) Since HSR is a redundancy protocol, disconnect cable "Link CA",
to ensure frames from Station A reach Station C only through
Station B.
5) Run iperf3 Server on Station C and client on station A.
7) Check the CPU usage on Station B.
CPU usage report on Station B using mpstat when running UDP iperf3:
-------------------------------------------------------------------
AM57xx
------
1) Non-Offload case
-------------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 0.00 0.00 0.00 0.00 0.00 10.41 0.00 0.00 89.59
0 0.00 0.00 0.00 0.00 0.00 20.88 0.00 0.00 79.12
1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
2) Offload case
---------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 0.00 0.00 0.10 0.00 0.00 0.73 0.00 0.00 99.17
0 0.00 0.00 0.20 0.00 0.00 1.46 0.00 0.00 98.34
1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
AM437x
------
1) Non-Offload case
-------------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 0.30 0.00 0.80 0.00 0.00 35.19 0.00 0.00 63.72
0 0.30 0.00 0.80 0.00 0.00 35.19 0.00 0.00 63.72
2) Offload case
---------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 0.10 0.00 0.31 0.10 0.00 1.74 0.00 0.00 97.75
0 0.10 0.00 0.31 0.10 0.00 1.74 0.00 0.00 97.75
AM335x
------
1) Non Offload case
-------------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 0.30 0.00 1.10 0.00 0.00 90.32 0.00 0.00 8.28
0 0.30 0.00 1.10 0.00 0.00 90.32 0.00 0.00 8.28
2) Offload case
---------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 0.43 0.00 3.61 0.00 0.00 13.28 0.00 0.00 82.68
0 0.43 0.00 3.61 0.00 0.00 13.28 0.00 0.00 82.68
PRP Test Setup:
---------------
_________________ LAN-A __________________
| |eth1-----------eth1| |
| AM57/AM437/AM335| | AM57/AM437/AM335 |
| station A |eth2-----------eth2| station B |
|_________________| LAN-B |__________________|
Steps to switch to PRP offload mode:
------------------------------------
Example assuming eth1, eth2 ports of ICSSM on AM57x, AM437x and AM335x EVM's
1) Bring down both slave interfaces
ip link set eth1 down
ip link set eth2 down
2) Set matching MAC addresses on both slave interfaces
ip link set eth1 address <mac-addr>
ip link set eth2 address <mac-addr>
3) Enable PRP offload for both interfaces
ethtool -K eth1 hsr-dup-offload on
ethtool -K eth1 hsr-tag-rm-offload on
ethtool -K eth2 hsr-dup-offload on
ethtool -K eth2 hsr-tag-rm-offload on
4) Create PRP interface and add slave interfaces to it
ip link add name prp0 type hsr slave1 eth1 slave2 eth2 \
supervision 45 proto 1
5) Add IP address to the PRP interface
ip addr add <IP_ADDR>/24 dev prp0
6) Bring up the PRP interface
ip link set prp0 up
7) Bring up the both slave ports
ip link set eth1 up
ip link set eth2 up
Switching back to default mode:
--------------------------------
1) Bring down both slave interfaces
ip link set eth1 down
ip link set eth2 down
2) Delete PRP interface
ip link delete prp0
3) Disable PRP offloading mode
ethtool -K eth1 hsr-dup-offload off
ethtool -K eth1 hsr-tag-rm-offload off
ethtool -K eth2 hsr-dup-offload off
ethtool -K eth2 hsr-tag-rm-offload off
Testing the PRP offload feature:
--------------------------------
1) Connect eth1 of Station A to eth1 of Station B (LAN-A).
Connect eth2 of Station A to eth2 of Station B (LAN-B).
2) Configure Station A in PRP non-offload mode.
3) Configure Station B in PRP offload mode.
4) Run iperf3 Server on Station B and client on Station A.
5) Check the CPU usage on Station B.
6) Disconnect LAN-B cable to verify Station A frames still reach
Station B over LAN-A with no traffic interruption.
7) Reconnect LAN-B and disconnect LAN-A, verify the same.
CPU usage report on Station B using mpstat when running UDP iperf3:
-------------------------------------------------------------------
AM57x
-----
1) Non Offload case
-------------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 2.04 0.00 18.85 0.00 0.00 27.83 0.00 0.00 51.27
0 1.80 0.00 21.56 0.00 0.00 54.89 0.00 0.00 21.76
1 2.29 0.00 16.14 0.00 0.00 0.80 0.00 0.00 80.78
2) Offload case
---------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 2.79 0.00 18.36 0.00 0.00 18.16 0.00 0.00 60.68
0 3.89 0.00 22.16 0.00 0.00 36.13 0.00 0.00 37.82
1 1.69 0.00 14.56 0.00 0.00 0.20 0.00 0.00 83.55
AM437x
------
1) Non Offload case
-------------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 5.68 0.00 43.27 0.00 0.00 43.57 0.00 0.00 7.48
0 5.68 0.00 43.27 0.00 0.00 43.57 0.00 0.00 7.48
2) Offload case
---------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 6.39 0.00 42.86 0.00 0.00 32.57 0.00 0.00 18.18
0 6.39 0.00 42.86 0.00 0.00 32.57 0.00 0.00 18.18
AM335x
------
1) Non Offload case
-------------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 2.29 0.00 14.04 0.00 0.00 75.50 0.00 0.00 8.17
0 2.29 0.00 14.04 0.00 0.00 75.50 0.00 0.00 8.17
2) Offload case
---------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 5.70 0.00 48.50 0.00 0.00 29.00 0.00 0.00 16.80
0 5.70 0.00 48.50 0.00 0.00 29.00 0.00 0.00 16.80
Note:
hsr-tag-rm-offload and hsr-dup-offload are tightly coupled in the firmware implementation.
They both need to be enabled / disabled together and hsr-tag-ins-offload is unsupported.
This is the v2 of the patch series [v1]. This version of the patchset addresses the
comments made on [v1] of the series.
Changes from v1 to v2 :
*) Fixed all applicable issues, except for the false positives flagged by the AI review
on sashiko.dev.
*) Addressed Jakub Kicinski comments on patch 3 of the series.
*) Rebased the series on latest net-next.
[v1] https://lore.kernel.org/all/20260611123636.376577-1-parvathi@couthit.com/
Thanks and Regards,
Parvathi.
Roger Quadros (3):
net: ti: icssm-prueth: Add HSR and PRP HW offload mode support for
AM57xx, AM437x and AM335x
net: ti: icssm-prueth: Add priority based RX IRQ handlers
net: ti: icssm-prueth: Support duplicate HW offload feature for HSR
and PRP
drivers/net/ethernet/ti/Makefile | 2 +-
.../ethernet/ti/icssm/icssm_lre_firmware.h | 141 ++++
drivers/net/ethernet/ti/icssm/icssm_prueth.c | 728 ++++++++++++++++--
drivers/net/ethernet/ti/icssm/icssm_prueth.h | 73 +-
.../ethernet/ti/icssm/icssm_prueth_common.c | 283 +++++++
.../net/ethernet/ti/icssm/icssm_prueth_lre.c | 224 ++++++
.../net/ethernet/ti/icssm/icssm_prueth_lre.h | 19 +
.../ethernet/ti/icssm/icssm_prueth_switch.c | 310 +++++++-
.../ethernet/ti/icssm/icssm_prueth_switch.h | 1 +
drivers/net/ethernet/ti/icssm/icssm_switch.h | 35 +-
10 files changed, 1749 insertions(+), 67 deletions(-)
create mode 100644 drivers/net/ethernet/ti/icssm/icssm_lre_firmware.h
create mode 100644 drivers/net/ethernet/ti/icssm/icssm_prueth_common.c
create mode 100644 drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c
create mode 100644 drivers/net/ethernet/ti/icssm/icssm_prueth_lre.h
--
2.43.0
^ permalink raw reply
* Re: [PATCH v2 02/19] driver core: platform: provide platform_device_set_of_node()
From: Bartosz Golaszewski @ 2026-06-30 13:22 UTC (permalink / raw)
To: Manuel Ebner
Cc: brgl, linux-kernel, netdev, linux-arm-msm, linux-sound,
driver-core, devicetree, linuxppc-dev, linux-i2c, iommu, linux-pm,
imx, linux-arm-kernel, intel-xe, dri-devel, linux-usb, linux-mips,
platform-driver-x86, Bartosz Golaszewski, Lee Jones,
Thierry Reding, Sebastian Hesselbarth, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Srinivas Kandagatla, Greg Kroah-Hartman, Vinod Koul,
Rafael J. Wysocki, Danilo Krummrich, Rob Herring, Saravana Kannan,
Madhavan Srinivasan, Michael Ellerman, Nicholas Piggin,
Christophe Leroy (CS GROUP), Andi Shyti, Andy Shevchenko,
Joerg Roedel, Will Deacon, Robin Murphy, Doug Berger,
Florian Fainelli, Broadcom internal kernel review list,
Ulf Hansson, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Matthew Brost, Thomas Hellström, Rodrigo Vivi,
David Airlie, Simona Vetter, Peter Chen, Paul Cercueil, Bin Liu,
Philipp Zabel, Maximilian Luz, Hans de Goede, Ilpo Järvinen,
Krzysztof Kozlowski, Benjamin Herrenschmidt
In-Reply-To: <263f58b418a27a2339fc2478f93234e0208b0ad9.camel@mailbox.org>
On Tue, 30 Jun 2026 13:37:54 +0200, Manuel Ebner <manuelebner@mailbox.org> said:
> On Mon, 2026-06-29 at 11:12 +0200, Bartosz Golaszewski wrote:
>> [...]
>>
>> +/**
>> + * platform_device_set_of_node - assign an OF node to device
>> + * @pdev: platform device to add the node for
>> + * @np: new device node
>> + *
>> + * Assign an OF node to this platform device. Internally keep track of the
>> + * reference count. Devices created with platform_device_alloc() must use this
>> + * function instead of assigning the node manually.
>
> Doesn't it make sense to add a remark to the kernel doc of platform_device_alloc()?
>
> Thanks
> Manuel
>
>> [...]
>
Sure, will do in the next iteration.
Bart
^ permalink raw reply
* Re: [PATCH net-next v11 1/7] dt-bindings: phy: document the serdes PHY on sa8255p
From: Krzysztof Kozlowski @ 2026-06-30 13:24 UTC (permalink / raw)
To: Vinod Koul, Geert Uytterhoeven
Cc: Bartosz Golaszewski, Bjorn Andersson, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Maxime Coquelin,
Alexandre Torgue, Giuseppe Cavallaro, Chen-Yu Tsai,
Jernej Skrabec, Neil Armstrong, Kevin Hilman, Jerome Brunet,
Shawn Guo, Fabio Estevam, Jan Petrous, s32, Mohd Ayaan Anwar,
Romain Gantois, Magnus Damm, Maxime Ripard, Christophe Roullier,
Radu Rendec, linux-arm-msm, devicetree, linux-kernel, netdev,
linux-stm32, linux-arm-kernel, Drew Fustini, linux-sunxi,
linux-amlogic, linux-mips, imx, linux-renesas-soc, linux-rockchip,
sophgo, linux-riscv, Bartosz Golaszewski, Bartosz Golaszewski
In-Reply-To: <akOZFIowVvprnAMf@vaman>
On 30/06/2026 12:23, Vinod Koul wrote:
> On 29-06-26, 16:51, Geert Uytterhoeven wrote:
>>> Russell King asked me to put the PHY logic for SCMI pm domains into the PHY
>>> driver instead of the MAC driver where it was previously. Instead of cramming
>>> both HLOS and firmware handling into the same driver, I figured it makes more
>>> sense to have a dedicated, cleaner driver as the two share very little code (if
>>> any).
>>
>> I think you are mixing up DT bindings and driver implementation?
>
> Should the bindings change if we have different driver and firmware
> implementations? Isn't binding supposed to be agnostic of
> implementations..?
I did not follow earlier discussions, so I do not know Russell
arguments, but in general it's true that driver choices should not
influence binding decisions. IOW, you need to figure out which real
device is part of power domain and add the power-domains to that device
node (that device).
Best regards,
Krzysztof
^ permalink raw reply
* [PATCH 0/3] arm64: dts/net: stmmac: Add Agilex5 SoCDK TSN Config2 board support
From: muhammad.nazim.amirul.nazle.asmade @ 2026-06-30 13:31 UTC (permalink / raw)
To: dinguyen, maxime.chevallier
Cc: rmk+kernel, krzk+dt, conor+dt, robh, davem, edumazet, kuba,
pabeni, andrew+netdev, devicetree, linux-arm-kernel, netdev,
linux-kernel
From: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
The Intel SoCFPGA Agilex5 SoCDK TSN Config2 board uses a dual-port
Ethernet setup where gmac1 (TSN port) operates with different MAC-side
and PHY-side interface modes: GMII internally in the MAC, and RGMII
towards the PHY.
Russell King's commit de696c63c1dc ("net: stmmac: socfpga: convert to
use phy_interface") replaced mac_interface with phy_interface in
socfpga_get_plat_phymode(), correctly noting that no upstream DTS files
set the "mac-mode" property at the time. However, the Agilex5 SoCDK
TSN Config2 board requires mac-mode and phy-mode to differ, causing
ping failures when the MAC is configured with RGMII instead of GMII.
This series fixes the issue in three steps:
Patch 1 (dt-bindings): Add the compatible string for the new board
variant to Documentation/devicetree/bindings/arm/altera.yaml.
Patch 2 (dts): Add the device tree source for the Agilex5 SoCDK
TSN Config2 board, enabling gmac1 with mac-mode = "gmii" alongside
the standard gmac2 port.
Patch 3 (driver): Restore mac_interface support in dwmac-socfpga by
reading the optional "mac-mode" DT property. When absent, it falls
back to phy_interface, preserving existing behaviour for all other
boards.
Note: Patches 1 and 2 target Dinh Nguyen's SoCFPGA tree
(git://git.kernel.org/pub/scm/linux/kernel/git/dinguyen/linux.git).
Patch 3 targets net-next.
Nazim Amirul (3):
dt-bindings: arm: altera: Add Agilex5 SoCDK TSN Config2 board
arm64: dts: socfpga: agilex5: Add SoCDK TSN Config2 board
net: stmmac: dwmac-socfpga: Add mac-mode DT property support
Documentation/devicetree/bindings/arm/altera.yaml | 1 +
arch/arm64/boot/dts/intel/Makefile | 9 +-
arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_tsn_cfg2.dts | 133 ++++++++++++++++++
drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c | 23 ++-
4 files changed, 162 insertions(+), 4 deletions(-)
create mode 100644 arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_tsn_cfg2.dts
--
2.43.7
^ permalink raw reply
* [PATCH 1/3] dt-bindings: arm: altera: Add Agilex5 SoCDK TSN Config2 board board
From: muhammad.nazim.amirul.nazle.asmade @ 2026-06-30 13:31 UTC (permalink / raw)
To: dinguyen, maxime.chevallier
Cc: rmk+kernel, krzk+dt, conor+dt, robh, davem, edumazet, kuba,
pabeni, andrew+netdev, devicetree, linux-arm-kernel, netdev,
linux-kernel
In-Reply-To: <20260630133108.27244-1-muhammad.nazim.amirul.nazle.asmade@altera.com>
From: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
Add compatible string for the Intel SoCFPGA Agilex5 SoCDK TSN Config2
board variant, which uses a dual-port TSN configuration where gmac1
operates with different MAC-side (GMII) and PHY-side (RGMII) interface
modes.
Signed-off-by: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
---
Documentation/devicetree/bindings/arm/altera.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/arm/altera.yaml b/Documentation/devicetree/bindings/arm/altera.yaml
index 3030cf46fe74..e431469a7175 100644
--- a/Documentation/devicetree/bindings/arm/altera.yaml
+++ b/Documentation/devicetree/bindings/arm/altera.yaml
@@ -114,6 +114,7 @@ properties:
- intel,socfpga-agilex5-socdk-debug
- intel,socfpga-agilex5-socdk-modular
- intel,socfpga-agilex5-socdk-nand
+ - intel,socfpga-agilex5-socdk-tsn-cfg2
- const: intel,socfpga-agilex5
- description: Agilex72 boards
--
2.43.7
^ permalink raw reply related
* [PATCH 3/3] net: stmmac: dwmac-socfpga: Add mac-mode DT property support
From: muhammad.nazim.amirul.nazle.asmade @ 2026-06-30 13:31 UTC (permalink / raw)
To: dinguyen, maxime.chevallier
Cc: rmk+kernel, krzk+dt, conor+dt, robh, davem, edumazet, kuba,
pabeni, andrew+netdev, devicetree, linux-arm-kernel, netdev,
linux-kernel
In-Reply-To: <20260630133108.27244-1-muhammad.nazim.amirul.nazle.asmade@altera.com>
From: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
Russell King's commit de696c63c1dc ("net: stmmac: socfpga: convert to
use phy_interface") replaced mac_interface with phy_interface in
socfpga_get_plat_phymode(), noting that no upstream DTS files set the
"mac-mode" property, making the two values identical.
The Agilex5 SoCDK TSN Config2 board is an exception: its gmac1 TSN
port uses GMII internally in the MAC while the PHY-side interface is
RGMII, so mac-mode and phy-mode differ. Without restoring mac_interface
support, the MAC is configured with RGMII instead of GMII, causing
connectivity failures on this board.
Add socfpga_of_get_mac_mode() to read the optional "mac-mode" DT
property and store it in a new mac_interface field. When the property
is absent, mac_interface falls back to phy_interface, preserving
the existing behaviour for all other boards.
Fixes: de696c63c1dc ("net: stmmac: socfpga: convert to use phy_interface")
Signed-off-by: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
---
.../ethernet/stmicro/stmmac/dwmac-socfpga.c | 23 ++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
index 1d7f0a57d288..6a6837c4a414 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
@@ -69,12 +69,30 @@ struct socfpga_dwmac {
void __iomem *tse_pcs_base;
void __iomem *sgmii_adapter_base;
bool f2h_ptp_ref_clk;
+ phy_interface_t mac_interface;
const struct socfpga_dwmac_ops *ops;
};
+static int socfpga_of_get_mac_mode(struct device_node *np)
+{
+ const char *pm;
+ int err, i;
+
+ err = of_property_read_string(np, "mac-mode", &pm);
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < PHY_INTERFACE_MODE_MAX; i++) {
+ if (!strcasecmp(pm, phy_modes(i)))
+ return i;
+ }
+
+ return -ENODEV;
+}
+
static phy_interface_t socfpga_get_plat_phymode(struct socfpga_dwmac *dwmac)
{
- return dwmac->plat_dat->phy_interface;
+ return dwmac->mac_interface;
}
static void socfpga_sgmii_config(struct socfpga_dwmac *dwmac, bool enable)
@@ -650,6 +668,9 @@ static int socfpga_dwmac_probe(struct platform_device *pdev)
plat_dat->pcs_exit = socfpga_dwmac_pcs_exit;
plat_dat->select_pcs = socfpga_dwmac_select_pcs;
+ ret = socfpga_of_get_mac_mode(pdev->dev.of_node);
+ dwmac->mac_interface = ret < 0 ? plat_dat->phy_interface : ret;
+
ops->setup_plat_dat(dwmac);
return devm_stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res);
--
2.43.7
^ permalink raw reply related
* [PATCH 2/3] arm64: dts: socfpga: agilex5: Add SoCDK TSN Config2 board
From: muhammad.nazim.amirul.nazle.asmade @ 2026-06-30 13:31 UTC (permalink / raw)
To: dinguyen, maxime.chevallier
Cc: rmk+kernel, krzk+dt, conor+dt, robh, davem, edumazet, kuba,
pabeni, andrew+netdev, devicetree, linux-arm-kernel, netdev,
linux-kernel
In-Reply-To: <20260630133108.27244-1-muhammad.nazim.amirul.nazle.asmade@altera.com>
From: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
Add device tree for the Intel SoCFPGA Agilex5 SoCDK TSN Config2 board
variant. This configuration enables gmac1 as a TSN port alongside
the standard gmac2 Ethernet port.
The TSN port (gmac1) uses GMII internally in the MAC but connects to an
RGMII PHY. The mac-mode property is set to "gmii" to reflect the
MAC-side interface, while phy-mode is set to "rgmii" for the PHY-side
interface.
Signed-off-by: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
---
arch/arm64/boot/dts/intel/Makefile | 3 +-
.../intel/socfpga_agilex5_socdk_tsn_cfg2.dts | 133 ++++++++++++++++++
2 files changed, 135 insertions(+), 1 deletion(-)
create mode 100644 arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_tsn_cfg2.dts
diff --git a/arch/arm64/boot/dts/intel/Makefile b/arch/arm64/boot/dts/intel/Makefile
index 270c70fdf084..fc7ba2c6384b 100644
--- a/arch/arm64/boot/dts/intel/Makefile
+++ b/arch/arm64/boot/dts/intel/Makefile
@@ -4,10 +4,11 @@ dtb-$(CONFIG_ARCH_INTEL_SOCFPGA) += socfpga_agilex_n6000.dtb \
socfpga_agilex_socdk_emmc.dtb \
socfpga_agilex_socdk_nand.dtb \
socfpga_agilex3_socdk.dtb \
- socfpga_agilex5_socdk.dtb \
+ socfpga_agilex5_socdk.dtb \
socfpga_agilex5_socdk_013b.dtb \
socfpga_agilex5_socdk_modular.dtb \
socfpga_agilex5_socdk_nand.dtb \
+ socfpga_agilex5_socdk_tsn_cfg2.dtb \
socfpga_agilex72_socdk.dtb \
socfpga_agilex7m_socdk.dtb \
socfpga_n5x_socdk.dtb
diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_tsn_cfg2.dts b/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_tsn_cfg2.dts
new file mode 100644
index 000000000000..f84f41a647ae
--- /dev/null
+++ b/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_tsn_cfg2.dts
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026, Altera Corporation
+ */
+#include "socfpga_agilex5.dtsi"
+
+/ {
+ model = "SoCFPGA Agilex5 SoCDK TSN Config2";
+ compatible = "intel,socfpga-agilex5-socdk-tsn-cfg2", "intel,socfpga-agilex5";
+
+ aliases {
+ serial0 = &uart0;
+ ethernet1 = &gmac1;
+ ethernet2 = &gmac2;
+ };
+
+ chosen {
+ stdout-path = "serial0:115200n8";
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ led-0 {
+ label = "hps_led0";
+ gpios = <&porta 11 GPIO_ACTIVE_HIGH>;
+ };
+
+ };
+
+ memory@80000000 {
+ device_type = "memory";
+ /* We expect the bootloader to fill in the reg */
+ reg = <0x0 0x80000000 0x0 0x0>;
+ };
+};
+
+&gpio0 {
+ status = "okay";
+};
+
+&gpio1 {
+ status = "okay";
+};
+
+/*
+ * gmac1 is the TSN port. The MAC operates in GMII mode internally
+ * while the PHY-side interface is RGMII, so mac-mode and phy-mode differ.
+ */
+&gmac1 {
+ status = "okay";
+ phy-mode = "rgmii"; /* TX/RX clock delays provided by Agilex5 I/O hardware */
+ mac-mode = "gmii";
+ phy-handle = <&emac1_phy0>;
+ max-frame-size = <9000>;
+
+ mdio0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "snps,dwmac-mdio";
+
+ emac1_phy0: ethernet-phy@0 {
+ reg = <0>;
+ };
+ };
+};
+
+&gmac2 {
+ status = "okay";
+ phy-mode = "rgmii"; /* TX/RX clock delays provided by Agilex5 I/O hardware */
+ phy-handle = <&emac2_phy0>;
+ max-frame-size = <9000>;
+
+ mdio0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "snps,dwmac-mdio";
+
+ emac2_phy0: ethernet-phy@0 {
+ reg = <0>;
+ };
+ };
+};
+
+&osc1 {
+ clock-frequency = <25000000>;
+};
+
+&qspi {
+ status = "okay";
+ flash@0 {
+ compatible = "micron,mt25qu02g", "jedec,spi-nor";
+ reg = <0>;
+ spi-max-frequency = <100000000>;
+ m25p,fast-read;
+ cdns,read-delay = <2>;
+ cdns,tshsl-ns = <50>;
+ cdns,tsd2d-ns = <50>;
+ cdns,tchsh-ns = <4>;
+ cdns,tslch-ns = <4>;
+ spi-tx-bus-width = <4>;
+ spi-rx-bus-width = <4>;
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ qspi_boot: partition@0 {
+ label = "u-boot";
+ reg = <0x0 0x04200000>;
+ };
+
+ root: partition@4200000 {
+ label = "root";
+ reg = <0x04200000 0x0be00000>;
+ };
+ };
+ };
+};
+
+&uart0 {
+ status = "okay";
+};
+
+&usb0 {
+ status = "okay";
+ disable-over-current;
+};
+
+&watchdog0 {
+ status = "okay";
+};
--
2.43.7
^ permalink raw reply related
* Re: [PATCH] net: ipv4: fix TOCTOU race in __ip_do_redirect
From: Eric Dumazet @ 2026-06-30 13:31 UTC (permalink / raw)
To: Lei Huang
Cc: dsahern, idosch, davem, kuba, pabeni, horms, netdev, linux-kernel,
Lei Huang
In-Reply-To: <20260630122333.935406-1-huanglei814@163.com>
On Tue, Jun 30, 2026 at 5:24 AM Lei Huang <huanglei814@163.com> wrote:
>
> From: Lei Huang <huanglei@kylinos.cn>
>
> fib_lookup() internally acquires and releases rcu_read_lock and always uses
> FIB_LOOKUP_NOREF (no refcount on fib_info). After it returns, res (a local
> struct fib_result on the stack) has its nhc field pointing into the
> fib_info internal nexthop array, but RCU protection is already dropped.
> A concurrent route deletion can free the fib_info via kfree_rcu, making
> res.nhc a stale pointer. Subsequent FIB_RES_NHC(res) reads this stale value
> and update_or_create_fnhe() dereferences it, causing UAF.
>
> Fix by wrap the entire fib_lookup + FIB_RES_NHC + update_or_create_fnhe
> region in an explicit rcu_read_lock/unlock to keep the fib_info alive
> throughout the critical section.
>
> Signed-off-by: Lei Huang <huanglei@kylinos.cn>
You forgot to include a Fixes: tag.
Please read Documentation/process/maintainer-netdev.rst
Anyway, this patch isn't needed; all callers of this helper already
use rcu_read_lock().
I am guessing all of them are called from ip_protocol_deliver_rcu()
If you think about this, LOCKDEP would have fired a warning years ago
at line 769:
in_dev = __in_dev_get_rcu(dev);
pw-bot: cr
^ permalink raw reply
* Re: [PATCH v3 4/4] vhost/vsock: add VHOST_RESET_OWNER ioctl
From: Stefano Garzarella @ 2026-06-30 13:40 UTC (permalink / raw)
To: Andrey Drobyshev
Cc: linux-kernel, kvm, virtualization, netdev, mst, stefanha,
dongli.zhang, maciej.szmigiero, bchaney, mark.kanda, ptikhomirov,
den
In-Reply-To: <20260625155416.480669-5-andrey.drobyshev@virtuozzo.com>
On Thu, Jun 25, 2026 at 06:54:16PM +0300, Andrey Drobyshev wrote:
>From: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
>
>This ioctl is needed for QEMU's CPR (checkpoint-restore) migration of
>the guest with vhost-vsock device. For this to work, we need to reset
>the device ownership on the source side by calling RESET_OWNER, and then
>claim it on the dest side by calling SET_OWNER. We expect not to lose any
>AF_VSOCK connection while this happens.
>
>RESET_OWNER keeps the guest CID hashed, so that connections survive. That
>leaves the device reachable by a lockless send/cancel path while the worker
>is being torn down: a concurrent vhost_transport_send_pkt() or
>vhost_transport_cancel_pkt() can call vhost_vq_work_queue() as
>vhost_workers_free() frees the worker. That might cause a use-after-free
>of vq->worker. In addition, any work queued onto the dying worker leaves
>VHOST_WORK_QUEUED stuck, stalling send_pkt_queue after resume.
>
>Fence the send/cancel paths around the teardown: send_pkt()/cancel_pkt()
>only kick the worker while the backend is alive. And reset_owner() calls
>synchronize_rcu() after drop_backends() so in-flight send/cancel finish
>before the worker is freed.
>
>Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
>Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
>---
> drivers/vhost/vsock.c | 51 +++++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 49 insertions(+), 2 deletions(-)
>
>diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
>index 81d4f7209719..f0a0aa7d3200 100644
>--- a/drivers/vhost/vsock.c
>+++ b/drivers/vhost/vsock.c
>@@ -318,7 +318,14 @@ vhost_transport_send_pkt(struct sk_buff *skb, struct net *net)
> atomic_inc(&vsock->queued_replies);
>
> virtio_vsock_skb_queue_tail(&vsock->send_pkt_queue, skb);
>- vhost_vq_work_queue(&vsock->vqs[VSOCK_VQ_RX], &vsock->send_pkt_work);
>+
>+ /* Skip the kick once the backend is gone (stop/RESET_OWNER); the skb
>+ * stays queued and vhost_vsock_start() drains it. Pairs with the
>+ * synchronize_rcu() in vhost_vsock_reset_owner().
>+ */
Please explain better (as done by commit bb26ed5f3a8b ("vhost/vsock:
Refuse the connection immediately when guest isn't ready") in the
comment removed by this seris) why we can use vhost_vq_get_backend()
without vq->mutex held.
>+ if (data_race(vhost_vq_get_backend(&vsock->vqs[VSOCK_VQ_RX])))
>+ vhost_vq_work_queue(&vsock->vqs[VSOCK_VQ_RX],
>+ &vsock->send_pkt_work);
BTW I'm now confused about what we are preventing here. A better
explanation should be added both in the commit and in the comment,
because it's hard to understand what we're preventing.
That said, if there is a problem, perhaps it should be fixed in vhost.c,
because it seems more like a generic issue.
vhost_vq_work_queue() has `worker = rcu_dereference(vq->worker);` so
should already prevent UAF, no?
Or maybe vhost_workers_free() is missing a synchronize_rcu()?
>
> rcu_read_unlock();
> return len;
>@@ -346,7 +353,15 @@ vhost_transport_cancel_pkt(struct vsock_sock *vsk)
> int new_cnt;
>
> new_cnt = atomic_sub_return(cnt, &vsock->queued_replies);
>- if (new_cnt + cnt >= tx_vq->num && new_cnt < tx_vq->num)
>+
>+ /* Skip the kick once the backend is gone (stop/RESET_OWNER):
>+ * vhost_poll_queue() would touch the worker which is being freed
>+ * by teardown, e.g. on RESET_OWNER. Pairs with the
>+ * synchronize_rcu() in vhost_vsock_reset_owner(). The TX VQ is
Ditto about the comment.
>+ * re-kicked by vhost_vsock_start().
>+ */
>+ if (data_race(vhost_vq_get_backend(tx_vq)) &&
>+ new_cnt + cnt >= tx_vq->num && new_cnt < tx_vq->num)
> vhost_poll_queue(&tx_vq->poll);
> }
>
>@@ -903,6 +918,36 @@ static int vhost_vsock_set_features(struct vhost_vsock *vsock, u64 features)
> return -EFAULT;
> }
>
>+static int vhost_vsock_reset_owner(struct vhost_vsock *vsock)
Why returning int?
We are defining err as long here, also the caller vhost_vsock_dev_ioctl()
returns long, so it is not clear to me why here we are not just
returning long.
>+{
>+ struct vhost_iotlb *umem;
>+ long err;
>+
>+ mutex_lock(&vsock->dev.mutex);
>+ err = vhost_dev_check_owner(&vsock->dev);
>+ if (err)
>+ goto done;
>+ umem = vhost_dev_reset_owner_prepare();
>+ if (!umem) {
>+ err = -ENOMEM;
>+ goto done;
>+ }
>+ vhost_vsock_drop_backends(vsock);
>+
>+ /* Let in-flight send_pkt() callers stop touching the worker before the
>+ * flush + free below. Pairs with the backend check in
>+ * vhost_transport_send_pkt().
This is also paired with vhost_transport_cancel_pkt(), so please update
this comment.
>+ */
>+ synchronize_rcu();
>+
>+ vhost_vsock_flush(vsock);
>+ vhost_dev_stop(&vsock->dev);
>+ vhost_dev_reset_owner(&vsock->dev, umem);
>+done:
>+ mutex_unlock(&vsock->dev.mutex);
>+ return err;
>+}
>+
> static long vhost_vsock_dev_ioctl(struct file *f, unsigned int ioctl,
> unsigned long arg)
> {
>@@ -946,6 +991,8 @@ static long vhost_vsock_dev_ioctl(struct file *f, unsigned int ioctl,
> return -EOPNOTSUPP;
> vhost_set_backend_features(&vsock->dev, features);
> return 0;
>+ case VHOST_RESET_OWNER:
>+ return vhost_vsock_reset_owner(vsock);
> default:
> mutex_lock(&vsock->dev.mutex);
> r = vhost_dev_ioctl(&vsock->dev, ioctl, argp);
>--
>2.47.1
>
^ permalink raw reply
* Re: [PATCH net-next v11 1/7] dt-bindings: phy: document the serdes PHY on sa8255p
From: Bartosz Golaszewski @ 2026-06-30 13:44 UTC (permalink / raw)
To: Vinod Koul
Cc: Geert Uytterhoeven, Bartosz Golaszewski, Bjorn Andersson,
Konrad Dybcio, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Maxime Coquelin, Alexandre Torgue,
Giuseppe Cavallaro, Chen-Yu Tsai, Jernej Skrabec, Neil Armstrong,
Kevin Hilman, Jerome Brunet, Shawn Guo, Fabio Estevam,
Jan Petrous, s32, Mohd Ayaan Anwar, Romain Gantois, Magnus Damm,
Maxime Ripard, Christophe Roullier, Radu Rendec, linux-arm-msm,
devicetree, linux-kernel, netdev, linux-stm32, linux-arm-kernel,
Drew Fustini, linux-sunxi, linux-amlogic, linux-mips, imx,
linux-renesas-soc, linux-rockchip, sophgo, linux-riscv,
Bartosz Golaszewski, Bartosz Golaszewski
In-Reply-To: <akOZFIowVvprnAMf@vaman>
On Tue, 30 Jun 2026 12:23:16 +0200, Vinod Koul <vkoul@kernel.org> said:
> On 29-06-26, 16:51, Geert Uytterhoeven wrote:
>> > Russell King asked me to put the PHY logic for SCMI pm domains into the PHY
>> > driver instead of the MAC driver where it was previously. Instead of cramming
>> > both HLOS and firmware handling into the same driver, I figured it makes more
>> > sense to have a dedicated, cleaner driver as the two share very little code (if
>> > any).
>>
>> I think you are mixing up DT bindings and driver implementation?
>
> Should the bindings change if we have different driver and firmware
> implementations? Isn't binding supposed to be agnostic of
> implementations..?
>
The way sa8255p implements SCMI is with SMC exclusively but - since even base
support is not yet upstream - maybe it would be possible to expose SCMI clocks
like some platforms do and reuse the same binding.
Would it make sense?
Bart
^ permalink raw reply
* [PATCH net 5/6] batman-adv: bla: reacquire gw address after skb realloc
From: Simon Wunderlich @ 2026-06-30 13:44 UTC (permalink / raw)
To: netdev
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, b.a.t.m.a.n, Sven Eckelmann, stable,
Simon Wunderlich
In-Reply-To: <20260630134430.85786-1-sw@simonwunderlich.de>
From: Sven Eckelmann <sven@narfation.org>
The pskb_may_pull() called by batadv_bla_is_backbone_gw() could reallocate
the buffer behind the skb. Variables which were pointing to the old buffer
need to be reassigned to avoid an use-after-free.
Cc: stable@vger.kernel.org
Fixes: 9e794b6bf4a2 ("batman-adv: drop unicast packets from other backbone gw")
Signed-off-by: Sven Eckelmann <sven@narfation.org>
Signed-off-by: Simon Wunderlich <sw@simonwunderlich.de>
---
net/batman-adv/routing.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
index 2cc2307a41702..bbd40fe3a8e59 100644
--- a/net/batman-adv/routing.c
+++ b/net/batman-adv/routing.c
@@ -1029,6 +1029,7 @@ int batadv_recv_unicast_packet(struct sk_buff *skb,
hdr_size);
batadv_orig_node_put(orig_node_gw);
if (is_gw) {
+ orig_addr_gw = eth_hdr(skb)->h_source;
batadv_dbg(BATADV_DBG_BLA, bat_priv,
"%s(): Dropped unicast pkt received from another backbone gw %pM.\n",
__func__, orig_addr_gw);
--
2.47.3
^ permalink raw reply related
* [PATCH net 6/6] batman-adv: dat: ensure accessible eth_hdr proto field
From: Simon Wunderlich @ 2026-06-30 13:44 UTC (permalink / raw)
To: netdev
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, b.a.t.m.a.n, Sven Eckelmann, stable, Sashiko,
Simon Wunderlich
In-Reply-To: <20260630134430.85786-1-sw@simonwunderlich.de>
From: Sven Eckelmann <sven@narfation.org>
When batadv_get_vid() accesses the proto field of the ethernet header, it
is not checking if the data itself is accessible. The caller is responsible
for it. But in contrast to other call sites, batadv_dat_get_vid() and its
caller didn't make sure this is true. This could have caused an
out-of-bounds access.
Cc: stable@vger.kernel.org
Reported-by: Sashiko <sashiko-bot@kernel.org>
Fixes: be1db4f6615b ("batman-adv: make the Distributed ARP Table vlan aware")
Signed-off-by: Sven Eckelmann <sven@narfation.org>
Signed-off-by: Simon Wunderlich <sw@simonwunderlich.de>
---
net/batman-adv/distributed-arp-table.c | 23 +++++++++++++++++++++++
net/batman-adv/main.c | 3 +++
2 files changed, 26 insertions(+)
diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c
index ead02c9e08484..c40c9e02391be 100644
--- a/net/batman-adv/distributed-arp-table.c
+++ b/net/batman-adv/distributed-arp-table.c
@@ -1066,6 +1066,9 @@ static u16 batadv_arp_get_type(struct batadv_priv *bat_priv,
* @skb: the buffer containing the packet to extract the VID from
* @hdr_size: the size of the batman-adv header encapsulating the packet
*
+ * The caller must ensure that at least @hdr_size + ETH_HLEN bytes are
+ * accessible after skb->data.
+ *
* Return: If the packet embedded in the skb is vlan tagged this function
* returns the VID with the BATADV_VLAN_HAS_TAG flag. Otherwise BATADV_NO_FLAGS
* is returned.
@@ -1148,6 +1151,10 @@ bool batadv_dat_snoop_outgoing_arp_request(struct batadv_priv *bat_priv,
if (!READ_ONCE(bat_priv->distributed_arp_table))
goto out;
+ /* first, find out the vid. */
+ if (!pskb_may_pull(skb, hdr_size + ETH_HLEN))
+ goto out;
+
vid = batadv_dat_get_vid(skb, &hdr_size);
type = batadv_arp_get_type(bat_priv, skb, hdr_size);
@@ -1243,6 +1250,10 @@ bool batadv_dat_snoop_incoming_arp_request(struct batadv_priv *bat_priv,
if (!READ_ONCE(bat_priv->distributed_arp_table))
goto out;
+ /* first, find out the vid. */
+ if (!pskb_may_pull(skb, hdr_size + ETH_HLEN))
+ goto out;
+
vid = batadv_dat_get_vid(skb, &hdr_size);
type = batadv_arp_get_type(bat_priv, skb, hdr_size);
@@ -1305,6 +1316,10 @@ void batadv_dat_snoop_outgoing_arp_reply(struct batadv_priv *bat_priv,
if (!READ_ONCE(bat_priv->distributed_arp_table))
return;
+ /* first, find out the vid. */
+ if (!pskb_may_pull(skb, hdr_size + ETH_HLEN))
+ return;
+
vid = batadv_dat_get_vid(skb, &hdr_size);
type = batadv_arp_get_type(bat_priv, skb, hdr_size);
@@ -1353,6 +1368,10 @@ bool batadv_dat_snoop_incoming_arp_reply(struct batadv_priv *bat_priv,
if (!READ_ONCE(bat_priv->distributed_arp_table))
goto out;
+ /* first, find out the vid. */
+ if (!pskb_may_pull(skb, hdr_size + ETH_HLEN))
+ goto out;
+
vid = batadv_dat_get_vid(skb, &hdr_size);
type = batadv_arp_get_type(bat_priv, skb, hdr_size);
@@ -1807,6 +1826,10 @@ bool batadv_dat_drop_broadcast_packet(struct batadv_priv *bat_priv,
if (batadv_forw_packet_is_rebroadcast(forw_packet))
goto out;
+ /* first, find out the vid. */
+ if (!pskb_may_pull(forw_packet->skb, hdr_size + ETH_HLEN))
+ goto out;
+
vid = batadv_dat_get_vid(forw_packet->skb, &hdr_size);
type = batadv_arp_get_type(bat_priv, forw_packet->skb, hdr_size);
diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c
index 3c4572284b532..4d3807a645b78 100644
--- a/net/batman-adv/main.c
+++ b/net/batman-adv/main.c
@@ -580,6 +580,9 @@ void batadv_recv_handler_unregister(u8 packet_type)
* @skb: the buffer containing the packet
* @header_len: length of the batman header preceding the ethernet header
*
+ * The caller must ensure that at least @header_len + ETH_HLEN bytes are
+ * accessible after skb->data.
+ *
* Return: VID with the BATADV_VLAN_HAS_TAG flag when the packet embedded in the
* skb is vlan tagged. Otherwise BATADV_NO_FLAGS.
*/
--
2.47.3
^ permalink raw reply related
* Re: [PATCH v3] net/sched: act_nat: only rewrite IPv4 packets
From: Paolo Abeni @ 2026-06-30 13:44 UTC (permalink / raw)
To: Samuel Moelius, Jamal Hadi Salim
Cc: Jiri Pirko, David S. Miller, Eric Dumazet, Jakub Kicinski,
Simon Horman, Herbert Xu, open list:TC subsystem, open list
In-Reply-To: <20260628132029.2209612.f2aaf6467355.act-nat-non-ipv4@trailofbits.com>
On 6/28/26 3:20 PM, Samuel Moelius wrote:
> act_nat can process packets whose protocol is not IPv4 and then
> interpret the payload as an IPv4 header. Non-IPv4 packets may be
> modified based on unrelated bytes at the network header offset.
>
> The action is documented as IPv4 NAT and should leave other protocols
> alone.
>
> Check skb->protocol before parsing and rewriting the IPv4 header. This
> keeps accepting hardware-accelerated VLAN IPv4 packets whose network
> header already points at the IPv4 header, while still rejecting inline
> VLAN packets because act_nat does not adjust the network header offset
> before using ip_hdr(skb).
>
> Fixes: b4219952356b ("[PKT_SCHED]: Add stateless NAT")
> Assisted-by: Codex:gpt-5.5-cyber-preview
> Signed-off-by: Samuel Moelius <sam.moelius@trailofbits.com>
> ---
> Changes in v3:
> - Check skb->protocol
> Changes in v2:
> - Check skb_protocol(skb, false)
>
> net/sched/act_nat.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/net/sched/act_nat.c b/net/sched/act_nat.c
> index abb332dee836..1bf4a5853617 100644
> --- a/net/sched/act_nat.c
> +++ b/net/sched/act_nat.c
> @@ -142,6 +142,9 @@ TC_INDIRECT_SCOPE int tcf_nat_act(struct sk_buff *skb,
> egress = parms->flags & TCA_NAT_FLAG_EGRESS;
>
> noff = skb_network_offset(skb);
> + if (skb->protocol != htons(ETH_P_IP))
AFAICS, in the xmit path, for actions applied at the H/W NIC level,
without vlan H/W offload, the above test check will fail, even if the
current code would work just fine (skb->network_header points to the
actual IPv4 header).
My take is implementing a robust and safe check here would be overkill.
Users should attach the action to the correct filter.
/P
> + goto out;
> +
> if (!pskb_may_pull(skb, sizeof(*iph) + noff))
> goto drop;
>
^ permalink raw reply
* Re: [PATCH net-next v4] net: usb: rtl8150: handle link status read failures
From: Andrew Lunn @ 2026-06-30 13:48 UTC (permalink / raw)
To: Yousef Alhouseen
Cc: Petko Manolov, Andrew Lunn, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, linux-usb, netdev, linux-kernel,
syzbot+9db6c624635564ad813c
In-Reply-To: <20260630101216.10365-1-alhouseenyousef@gmail.com>
On Tue, Jun 30, 2026 at 12:12:16PM +0200, Yousef Alhouseen wrote:
> set_carrier() ignores the result of the USB control transfer and tests
> the stack variable supplied as its receive buffer. If the device rejects
> or aborts the request, that variable remains uninitialized and the driver
> chooses an arbitrary carrier state.
>
> Leave the existing carrier state unchanged when the link status cannot be
> read. A transient USB error should not be treated as link loss.
>
> Reported-by: syzbot+9db6c624635564ad813c@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=9db6c624635564ad813c
> Suggested-by: Petko Manolov <petkan@nucleusys.com>
> Signed-off-by: Yousef Alhouseen <alhouseenyousef@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Andrew
^ permalink raw reply
* [PATCH net 1/6] batman-adv: retrieve ethhdr after potential skb realloc on RX
From: Simon Wunderlich @ 2026-06-30 13:44 UTC (permalink / raw)
To: netdev
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, b.a.t.m.a.n, Sven Eckelmann, stable, Sashiko,
Simon Wunderlich
In-Reply-To: <20260630134430.85786-1-sw@simonwunderlich.de>
From: Sven Eckelmann <sven@narfation.org>
pskb_may_pull() in batadv_interface_rx() could reallocate the buffer behind
the skb. Variables which were pointing to the old buffer need to be
reassigned to avoid an use-after-free.
This was done correctly for the VLAN header but missed for the ethernet
header which is later used for the TT and AP isolation handling.
Cc: stable@vger.kernel.org
Reported-by: Sashiko <sashiko-bot@kernel.org>
Fixes: c6c8fea29769 ("net: Add batman-adv meshing protocol")
Fixes: c78296665c3d ("batman-adv: Check skb size before using encapsulated ETH+VLAN header")
Signed-off-by: Sven Eckelmann <sven@narfation.org>
Signed-off-by: Simon Wunderlich <sw@simonwunderlich.de>
---
net/batman-adv/mesh-interface.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c
index 44026810b99ce..511f70e0706a7 100644
--- a/net/batman-adv/mesh-interface.c
+++ b/net/batman-adv/mesh-interface.c
@@ -434,6 +434,7 @@ void batadv_interface_rx(struct net_device *mesh_iface,
if (!pskb_may_pull(skb, VLAN_ETH_HLEN))
goto dropped;
+ ethhdr = eth_hdr(skb);
vhdr = skb_vlan_eth_hdr(skb);
/* drop batman-in-batman packets to prevent loops */
--
2.47.3
^ permalink raw reply related
* [PATCH net 0/6] pull request: batman-adv 2026-06-30
From: Simon Wunderlich @ 2026-06-30 13:44 UTC (permalink / raw)
To: netdev
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, b.a.t.m.a.n, Simon Wunderlich
Dear net maintainers,
here are a couple of bugfixes for batman-adv which we would like to have integrated into net.
Please pull or let me know of any problem!
Thank you,
Simon
The following changes since commit 805185b7c7a1069e407b6f7b3bc98e44d415f484:
Merge tag 'net-7.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net (2026-06-25 12:25:36 -0700)
are available in the Git repository at:
https://git.open-mesh.org/batadv.git tags/batadv-net-pullrequest-20260630
for you to fetch changes up to 26560c4a03dc4d607331600c187f59ab2df5f341:
batman-adv: dat: ensure accessible eth_hdr proto field (2026-06-28 11:49:04 +0200)
----------------------------------------------------------------
Here are some batman-adv bugfix, all by Sven Eckelmann:
- fix pointers after potential skb reallocs (5 patches)
- dat: ensure accessible eth_hdr proto field
----------------------------------------------------------------
Sven Eckelmann (6):
batman-adv: retrieve ethhdr after potential skb realloc on RX
batman-adv: access unicast_ttvn skb->data only after skb realloc
batman-adv: gw: acquire ethernet header only after skb realloc
batman-adv: dat: acquire ARP hw source only after skb realloc
batman-adv: bla: reacquire gw address after skb realloc
batman-adv: dat: ensure accessible eth_hdr proto field
net/batman-adv/distributed-arp-table.c | 28 +++++++++++++++++++++++++++-
net/batman-adv/gateway_client.c | 3 ++-
net/batman-adv/main.c | 3 +++
net/batman-adv/mesh-interface.c | 1 +
net/batman-adv/routing.c | 3 ++-
5 files changed, 35 insertions(+), 3 deletions(-)
^ permalink raw reply
* [PATCH net 3/6] batman-adv: gw: acquire ethernet header only after skb realloc
From: Simon Wunderlich @ 2026-06-30 13:44 UTC (permalink / raw)
To: netdev
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, b.a.t.m.a.n, Sven Eckelmann, stable,
Simon Wunderlich
In-Reply-To: <20260630134430.85786-1-sw@simonwunderlich.de>
From: Sven Eckelmann <sven@narfation.org>
The pskb_may_pull() called by batadv_get_vid() could reallocate the buffer
behind the skb. Variables which were pointing to the old buffer need to be
reassigned to avoid an use-after-free.
Cc: stable@vger.kernel.org
Fixes: 6c413b1c22a2 ("batman-adv: send every DHCP packet as bat-unicast")
Signed-off-by: Sven Eckelmann <sven@narfation.org>
Signed-off-by: Simon Wunderlich <sw@simonwunderlich.de>
---
net/batman-adv/gateway_client.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/net/batman-adv/gateway_client.c b/net/batman-adv/gateway_client.c
index 305488a74a256..a5ac82eabd250 100644
--- a/net/batman-adv/gateway_client.c
+++ b/net/batman-adv/gateway_client.c
@@ -684,12 +684,13 @@ bool batadv_gw_out_of_range(struct batadv_priv *bat_priv,
struct batadv_gw_node *gw_node = NULL;
struct batadv_gw_node *curr_gw = NULL;
struct batadv_neigh_ifinfo *curr_ifinfo, *old_ifinfo;
- struct ethhdr *ethhdr = (struct ethhdr *)skb->data;
+ struct ethhdr *ethhdr;
bool out_of_range = false;
u8 curr_tq_avg;
unsigned short vid;
vid = batadv_get_vid(skb, 0);
+ ethhdr = (struct ethhdr *)skb->data;
if (is_multicast_ether_addr(ethhdr->h_dest))
goto out;
--
2.47.3
^ permalink raw reply related
* [PATCH net 2/6] batman-adv: access unicast_ttvn skb->data only after skb realloc
From: Simon Wunderlich @ 2026-06-30 13:44 UTC (permalink / raw)
To: netdev
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, b.a.t.m.a.n, Sven Eckelmann, stable, Sashiko,
Simon Wunderlich
In-Reply-To: <20260630134430.85786-1-sw@simonwunderlich.de>
From: Sven Eckelmann <sven@narfation.org>
The pskb_may_pull() called by batadv_get_vid() could reallocate the buffer
behind the skb. Variables which were pointing to the old buffer need to be
reassigned to avoid an use-after-free.
This was done correctly for the ethernet header but missed for the
unicast_packet pointer.
Cc: stable@vger.kernel.org
Reported-by: Sashiko <sashiko-bot@kernel.org>
Fixes: c018ad3de61a ("batman-adv: add the VLAN ID attribute to the TT entry")
Signed-off-by: Sven Eckelmann <sven@narfation.org>
Signed-off-by: Simon Wunderlich <sw@simonwunderlich.de>
---
net/batman-adv/routing.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
index c05fcc9241add..2cc2307a41702 100644
--- a/net/batman-adv/routing.c
+++ b/net/batman-adv/routing.c
@@ -855,8 +855,8 @@ static bool batadv_check_unicast_ttvn(struct batadv_priv *bat_priv,
if (skb_cow(skb, sizeof(*unicast_packet)) < 0)
return false;
- unicast_packet = (struct batadv_unicast_packet *)skb->data;
vid = batadv_get_vid(skb, hdr_len);
+ unicast_packet = (struct batadv_unicast_packet *)skb->data;
ethhdr = (struct ethhdr *)(skb->data + hdr_len);
/* do not reroute multicast frames in a unicast header */
--
2.47.3
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox