* [PATCH net-next v4 2/2] dpll: zl3073x: report FFO as DPLL vs input reference offset
From: Ivan Vecera @ 2026-05-11 15:58 UTC (permalink / raw)
To: netdev
Cc: Petr Oros, Andrew Lunn, Arkadiusz Kubalewski, David S. Miller,
Donald Hunter, Eric Dumazet, Jakub Kicinski, Jiri Pirko,
Jonathan Corbet, Leon Romanovsky, Mark Bloch, Michal Schmidt,
Paolo Abeni, Pasi Vaananen, Prathosh Satish, Saeed Mahameed,
Shuah Khan, Simon Horman, Tariq Toukan, Vadim Fedorenko,
linux-doc, linux-kernel, linux-rdma
In-Reply-To: <20260511155816.99936-1-ivecera@redhat.com>
Replace the per-reference frequency offset measurement (which was
redundant with measured-frequency) with a direct read of the DPLL's
delta frequency offset vs its tracked input reference.
The new implementation uses the dpll_df_offset_x register with
ref_ofst=1 via the dpll_df_read_x semaphore mechanism. This
provides 2^-48 resolution (~3.5 fE) and reports the actual
frequency difference between the DPLL and its active input.
Switch supported_ffo from DPLL_FFO_PORT_RXTX_RATE to
DPLL_FFO_PIN_DEVICE so FFO is reported only in the per-parent
context for the active input pin.
Use atomic64_t for freq_offset to prevent torn reads on 32-bit
architectures between the periodic worker and netlink callbacks.
Rewrite ffo_check to compare the cached df_offset converted to PPT
instead of using the old per-reference measurement. Remove the
ref_ffo_update periodic measurement and the ref ffo field since
they are no longer needed.
Changes v3 -> v4:
- Switch to DPLL_FFO_PIN_DEVICE, remove dpll=NULL guard
- Use atomic64_t for freq_offset (torn read on 32-bit)
Reviewed-by: Petr Oros <poros@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/zl3073x/chan.c | 31 +++++++++++++++++++++++--
drivers/dpll/zl3073x/chan.h | 14 ++++++++++++
drivers/dpll/zl3073x/core.c | 45 -------------------------------------
drivers/dpll/zl3073x/dpll.c | 38 ++++++++++++++-----------------
drivers/dpll/zl3073x/ref.h | 14 ------------
drivers/dpll/zl3073x/regs.h | 15 +++++++++++++
6 files changed, 75 insertions(+), 82 deletions(-)
diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c
index 2f48ca2391494..2fe3c3da84bb5 100644
--- a/drivers/dpll/zl3073x/chan.c
+++ b/drivers/dpll/zl3073x/chan.c
@@ -18,6 +18,7 @@
int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
{
struct zl3073x_chan *chan = &zldev->chan[index];
+ u64 val;
int rc;
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(index),
@@ -25,8 +26,34 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
if (rc)
return rc;
- return zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),
- &chan->refsel_status);
+ rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),
+ &chan->refsel_status);
+ if (rc)
+ return rc;
+
+ /* Read df_offset vs tracked reference */
+ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
+ ZL_DPLL_DF_READ_SEM);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index),
+ ZL_DPLL_DF_READ_SEM | ZL_DPLL_DF_READ_REF_OFST);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
+ ZL_DPLL_DF_READ_SEM);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val);
+ if (rc)
+ return rc;
+
+ chan->df_offset = sign_extend64(val, 47);
+
+ return 0;
}
/**
diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h
index 481da2133202b..4353809c69122 100644
--- a/drivers/dpll/zl3073x/chan.h
+++ b/drivers/dpll/zl3073x/chan.h
@@ -17,6 +17,7 @@ struct zl3073x_dev;
* @ref_prio: reference priority registers (4 bits per ref, P/N packed)
* @mon_status: monitor status register value
* @refsel_status: reference selection status register value
+ * @df_offset: frequency offset vs tracked reference in 2^-48 steps
*/
struct zl3073x_chan {
struct_group(cfg,
@@ -26,6 +27,7 @@ struct zl3073x_chan {
struct_group(stat,
u8 mon_status;
u8 refsel_status;
+ s64 df_offset;
);
};
@@ -37,6 +39,18 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index);
+/**
+ * zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference
+ * @chan: pointer to channel state
+ *
+ * Return: frequency offset in 2^-48 steps
+ */
+static inline s64
+zl3073x_chan_df_offset_get(const struct zl3073x_chan *chan)
+{
+ return chan->df_offset;
+}
+
/**
* zl3073x_chan_mode_get - get DPLL channel operating mode
* @chan: pointer to channel state
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 5f1e70f3e40a0..b3345060490db 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -704,44 +704,6 @@ zl3073x_ref_freq_meas_update(struct zl3073x_dev *zldev)
return 0;
}
-/**
- * zl3073x_ref_ffo_update - update reference fractional frequency offsets
- * @zldev: pointer to zl3073x_dev structure
- *
- * The function asks device to latch the latest measured fractional
- * frequency offset values, reads and stores them into the ref state.
- *
- * Return: 0 on success, <0 on error
- */
-static int
-zl3073x_ref_ffo_update(struct zl3073x_dev *zldev)
-{
- int i, rc;
-
- rc = zl3073x_ref_freq_meas_latch(zldev,
- ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF);
- if (rc)
- return rc;
-
- /* Read DPLL-to-REFx frequency offset measurements */
- for (i = 0; i < ZL3073X_NUM_REFS; i++) {
- s32 value;
-
- /* Read value stored in units of 2^-32 signed */
- rc = zl3073x_read_u32(zldev, ZL_REG_REF_FREQ(i), &value);
- if (rc)
- return rc;
-
- /* Convert to ppt
- * ffo = (10^12 * value) / 2^32
- * ffo = ( 5^12 * value) / 2^20
- */
- zldev->ref[i].ffo = mul_s64_u64_shr(value, 244140625, 20);
- }
-
- return 0;
-}
-
static void
zl3073x_dev_periodic_work(struct kthread_work *work)
{
@@ -776,13 +738,6 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
}
}
- /* Update references' fractional frequency offsets */
- rc = zl3073x_ref_ffo_update(zldev);
- if (rc)
- dev_warn(zldev->dev,
- "Failed to update fractional frequency offsets: %pe\n",
- ERR_PTR(rc));
-
list_for_each_entry(zldpll, &zldev->dplls, list)
zl3073x_dpll_changes_check(zldpll);
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 05e63661bf074..cff85cdb9d0e5 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/atomic.h>
#include <linux/bits.h>
#include <linux/bitfield.h>
#include <linux/bug.h>
@@ -57,7 +58,7 @@ struct zl3073x_dpll_pin {
s32 phase_gran;
enum dpll_pin_operstate operstate;
s64 phase_offset;
- s64 freq_offset;
+ atomic64_t freq_offset;
u32 measured_freq;
};
@@ -300,7 +301,10 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
{
struct zl3073x_dpll_pin *pin = pin_priv;
- ffo->ffo = pin->freq_offset;
+ if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
+ return -ENODATA;
+
+ ffo->ffo = atomic64_read(&pin->freq_offset);
return 0;
}
@@ -1275,7 +1279,7 @@ zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll,
}
static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
- .supported_ffo = BIT(DPLL_FFO_PORT_RXTX_RATE),
+ .supported_ffo = BIT(DPLL_FFO_PIN_DEVICE),
.direction_get = zl3073x_dpll_pin_direction_get,
.esync_get = zl3073x_dpll_input_pin_esync_get,
.esync_set = zl3073x_dpll_input_pin_esync_set,
@@ -1731,37 +1735,29 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin)
}
/**
- * zl3073x_dpll_pin_ffo_check - check for pin fractional frequency offset change
+ * zl3073x_dpll_pin_ffo_check - check for FFO change on active pin
* @pin: pin to check
*
- * Check for the given pin's fractional frequency change.
- *
- * Return: true on fractional frequency offset change, false otherwise
+ * Return: true on change, false otherwise
*/
static bool
zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin)
{
struct zl3073x_dpll *zldpll = pin->dpll;
struct zl3073x_dev *zldev = zldpll->dev;
- const struct zl3073x_ref *ref;
- u8 ref_id;
+ const struct zl3073x_chan *chan;
s64 ffo;
- /* Get reference monitor status */
- ref_id = zl3073x_input_pin_ref_get(pin->id);
- ref = zl3073x_ref_state_get(zldev, ref_id);
-
- /* Do not report ffo changes if the reference monitor report errors */
- if (!zl3073x_ref_is_status_ok(ref))
+ if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
return false;
- /* Compare with previous value */
- ffo = zl3073x_ref_ffo_get(ref);
- if (pin->freq_offset != ffo) {
- dev_dbg(zldev->dev, "%s freq offset changed: %lld -> %lld\n",
- pin->label, pin->freq_offset, ffo);
- pin->freq_offset = ffo;
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ ffo = mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan),
+ 244140625, 36);
+ if (atomic64_xchg(&pin->freq_offset, ffo) != ffo) {
+ dev_dbg(zldev->dev, "%s freq offset changed to: %lld\n",
+ pin->label, ffo);
return true;
}
diff --git a/drivers/dpll/zl3073x/ref.h b/drivers/dpll/zl3073x/ref.h
index 55e80e4f08734..e140ca3ea17dc 100644
--- a/drivers/dpll/zl3073x/ref.h
+++ b/drivers/dpll/zl3073x/ref.h
@@ -22,7 +22,6 @@ struct zl3073x_dev;
* @freq_ratio_n: FEC mode divisor
* @sync_ctrl: reference sync control
* @config: reference config
- * @ffo: current fractional frequency offset
* @meas_freq: measured input frequency in Hz
* @mon_status: reference monitor status
*/
@@ -40,7 +39,6 @@ struct zl3073x_ref {
u8 config;
);
struct_group(stat, /* Status */
- s64 ffo;
u32 meas_freq;
u8 mon_status;
);
@@ -58,18 +56,6 @@ int zl3073x_ref_state_update(struct zl3073x_dev *zldev, u8 index);
int zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult);
-/**
- * zl3073x_ref_ffo_get - get current fractional frequency offset
- * @ref: pointer to ref state
- *
- * Return: the latest measured fractional frequency offset
- */
-static inline s64
-zl3073x_ref_ffo_get(const struct zl3073x_ref *ref)
-{
- return ref->ffo;
-}
-
/**
* zl3073x_ref_meas_freq_get - get measured input frequency
* @ref: pointer to ref state
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index 8015808bdf548..9578f00095282 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -164,6 +164,11 @@
#define ZL_DPLL_MODE_REFSEL_MODE_NCO 4
#define ZL_DPLL_MODE_REFSEL_REF GENMASK(7, 4)
+#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_REG_DPLL_MEAS_CTRL ZL_REG(5, 0x50, 1)
#define ZL_DPLL_MEAS_CTRL_EN BIT(0)
#define ZL_DPLL_MEAS_CTRL_AVG_FACTOR GENMASK(7, 4)
@@ -176,6 +181,16 @@
#define ZL_REG_DPLL_PHASE_ERR_DATA(_idx) \
ZL_REG_IDX(_idx, 5, 0x55, 6, ZL3073X_MAX_CHANNELS, 6)
+/*******************************
+ * Register Pages 6-7, DPLL Data
+ *******************************/
+
+#define ZL_REG_DPLL_DF_OFFSET_03(_idx) \
+ ZL_REG_IDX(_idx, 6, 0x00, 6, 4, 0x20)
+#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)
+
/***********************************
* Register Page 9, Synth and Output
***********************************/
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v4 1/2] dpll: add fractional frequency offset to pin-parent-device
From: Ivan Vecera @ 2026-05-11 15:58 UTC (permalink / raw)
To: netdev
Cc: Jiri Pirko, Andrew Lunn, Arkadiusz Kubalewski, David S. Miller,
Donald Hunter, Eric Dumazet, Jakub Kicinski, Jiri Pirko,
Jonathan Corbet, Leon Romanovsky, Mark Bloch, Michal Schmidt,
Paolo Abeni, Pasi Vaananen, Petr Oros, Prathosh Satish,
Saeed Mahameed, Shuah Khan, Simon Horman, Tariq Toukan,
Vadim Fedorenko, linux-doc, linux-kernel, linux-rdma
In-Reply-To: <20260511155816.99936-1-ivecera@redhat.com>
Add both fractional-frequency-offset (PPM) and
fractional-frequency-offset-ppt (PPT) attributes to the
pin-parent-device nested attribute set, alongside the existing
top-level pin attributes. Both carry the same measurement at
different precisions.
Introduce enum dpll_ffo_type and struct dpll_ffo_param to
distinguish FFO contexts: DPLL_FFO_PORT_RXTX_RATE for the RX vs
TX symbol rate offset reported at the top level, and
DPLL_FFO_PIN_DEVICE for the pin vs parent DPLL offset reported
in the pin-parent-device nest.
Add a supported_ffo bitmask to struct dpll_pin_ops so drivers
declare which FFO types they support. The core only calls ffo_get
for types the driver has opted into, eliminating the need for
per-driver NULL pointer guards. Validate at pin registration time
that supported_ffo is not set without an ffo_get callback.
Update mlx5 (DPLL_FFO_PORT_RXTX_RATE) and zl3073x
(DPLL_FFO_PORT_RXTX_RATE) drivers to use the new API.
Add documentation for both FFO types to dpll.rst.
Changes v3 -> v4:
- Replace dpll=NULL overloading with enum dpll_ffo_type and
struct dpll_ffo_param (Jakub Kicinski)
- Add supported_ffo opt-in bitmask in dpll_pin_ops for fail-close
driver validation (Jakub Kicinski)
- Add WARN_ON in dpll_pin_register for supported_ffo without
ffo_get callback
Reviewed-by: Jiri Pirko <jiri@nvidia.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
Documentation/driver-api/dpll.rst | 20 +++++++++++++
Documentation/netlink/specs/dpll.yaml | 28 ++++++++++-------
drivers/dpll/dpll_core.c | 3 +-
drivers/dpll/dpll_netlink.c | 30 ++++++++++---------
drivers/dpll/dpll_nl.c | 2 ++
drivers/dpll/zl3073x/dpll.c | 6 ++--
.../net/ethernet/mellanox/mlx5/core/dpll.c | 6 ++--
include/linux/dpll.h | 16 +++++++++-
8 files changed, 81 insertions(+), 30 deletions(-)
diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst
index 37eaef785e304..090cd4d017c3a 100644
--- a/Documentation/driver-api/dpll.rst
+++ b/Documentation/driver-api/dpll.rst
@@ -258,6 +258,26 @@ in the ``DPLL_A_PIN_PHASE_OFFSET`` attribute.
``DPLL_A_PHASE_OFFSET_MONITOR`` attr state of a feature
=============================== ========================
+Fractional frequency offset
+===========================
+
+The fractional frequency offset (FFO) is reported through two attributes
+that carry the same measurement at different precisions:
+
+- ``DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET`` in PPM (parts per million)
+- ``DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT`` in PPT (parts per trillion)
+
+Both attributes appear at the top level of a pin and inside each
+``pin-parent-device`` nest. Two FFO types are defined:
+
+- ``DPLL_FFO_PORT_RXTX_RATE`` - RX vs TX symbol rate offset (top-level)
+- ``DPLL_FFO_PIN_DEVICE`` - pin vs parent DPLL offset (per-parent)
+
+The driver declares which types it supports via the ``supported_ffo``
+bitmask in ``struct dpll_pin_ops``. The core only calls the ``ffo_get``
+callback for types the driver has opted into. The requested type is
+passed to the driver in the ``struct dpll_ffo_param``.
+
Frequency monitor
=================
diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml
index c45de70a47ce6..91a172617b3a9 100644
--- a/Documentation/netlink/specs/dpll.yaml
+++ b/Documentation/netlink/specs/dpll.yaml
@@ -448,12 +448,14 @@ attribute-sets:
name: fractional-frequency-offset
type: sint
doc: |
- The FFO (Fractional Frequency Offset) between the RX and TX
- symbol rate on the media associated with the pin:
- (rx_frequency-tx_frequency)/rx_frequency
+ The FFO (Fractional Frequency Offset) of the pin.
+ At top level this represents the RX vs TX symbol rate
+ 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.
Value is in PPM (parts per million).
- This may be implemented for example for pin of type
- PIN_TYPE_SYNCE_ETH_PORT.
+ This is a lower-precision version of
+ fractional-frequency-offset-ppt.
-
name: esync-frequency
type: u64
@@ -492,12 +494,14 @@ attribute-sets:
name: fractional-frequency-offset-ppt
type: sint
doc: |
- The FFO (Fractional Frequency Offset) of the pin with respect to
- the nominal frequency.
- Value = (frequency_measured - frequency_nominal) / frequency_nominal
+ The FFO (Fractional Frequency Offset) of the pin.
+ At top level this represents the RX vs TX symbol rate
+ 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.
Value is in PPT (parts per trillion, 10^-12).
- Note: This attribute provides higher resolution than the standard
- fractional-frequency-offset (which is in PPM).
+ This is a higher-precision version of
+ fractional-frequency-offset.
-
name: measured-frequency
type: u64
@@ -534,6 +538,10 @@ attribute-sets:
name: operstate
-
name: phase-offset
+ -
+ name: fractional-frequency-offset
+ -
+ name: fractional-frequency-offset-ppt
-
name: pin-parent-pin
subset-of: pin
diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c
index cbb635db43210..20a54728549cc 100644
--- a/drivers/dpll/dpll_core.c
+++ b/drivers/dpll/dpll_core.c
@@ -879,7 +879,8 @@ dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin,
WARN_ON(!ops->direction_get) ||
WARN_ON(ops->measured_freq_get &&
(!dpll_device_ops(dpll)->freq_monitor_get ||
- !dpll_device_ops(dpll)->freq_monitor_set)))
+ !dpll_device_ops(dpll)->freq_monitor_set)) ||
+ WARN_ON(ops->supported_ffo && !ops->ffo_get))
return -EINVAL;
mutex_lock(&dpll_lock);
diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
index 05cf946b4be5e..00e8696cb267b 100644
--- a/drivers/dpll/dpll_netlink.c
+++ b/drivers/dpll/dpll_netlink.c
@@ -417,31 +417,28 @@ dpll_msg_add_phase_offset(struct sk_buff *msg, struct dpll_pin *pin,
static int dpll_msg_add_ffo(struct sk_buff *msg, struct dpll_pin *pin,
struct dpll_pin_ref *ref,
+ enum dpll_ffo_type type,
struct netlink_ext_ack *extack)
{
const struct dpll_pin_ops *ops = dpll_pin_ops(ref);
- struct dpll_device *dpll = ref->dpll;
- s64 ffo;
+ struct dpll_ffo_param ffo = { .type = type };
int ret;
- if (!ops->ffo_get)
+ if (!ops->ffo_get || !(ops->supported_ffo & BIT(type)))
return 0;
- ret = ops->ffo_get(pin, dpll_pin_on_dpll_priv(dpll, pin),
- dpll, dpll_priv(dpll), &ffo, extack);
+ ret = ops->ffo_get(pin, dpll_pin_on_dpll_priv(ref->dpll, pin),
+ ref->dpll, dpll_priv(ref->dpll), &ffo, extack);
if (ret) {
if (ret == -ENODATA)
return 0;
return ret;
}
- /* Put the FFO value in PPM to preserve compatibility with older
- * programs.
- */
- ret = nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET,
- div_s64(ffo, 1000000));
- if (ret)
+ if (nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET,
+ div_s64(ffo.ffo, 1000000)))
return -EMSGSIZE;
- return nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT,
- ffo);
+ return nla_put_sint(msg,
+ DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT,
+ ffo.ffo);
}
static int dpll_msg_add_measured_freq(struct sk_buff *msg, struct dpll_pin *pin,
@@ -686,6 +683,10 @@ dpll_msg_add_pin_dplls(struct sk_buff *msg, struct dpll_pin *pin,
if (ret)
goto nest_cancel;
ret = dpll_msg_add_phase_offset(msg, pin, ref, extack);
+ if (ret)
+ goto nest_cancel;
+ ret = dpll_msg_add_ffo(msg, pin, ref,
+ DPLL_FFO_PIN_DEVICE, extack);
if (ret)
goto nest_cancel;
nla_nest_end(msg, attr);
@@ -748,7 +749,8 @@ dpll_cmd_pin_get_one(struct sk_buff *msg, struct dpll_pin *pin,
ret = dpll_msg_add_pin_phase_adjust(msg, pin, ref, extack);
if (ret)
return ret;
- ret = dpll_msg_add_ffo(msg, pin, ref, extack);
+ ret = dpll_msg_add_ffo(msg, pin, ref,
+ DPLL_FFO_PORT_RXTX_RATE, extack);
if (ret)
return ret;
ret = dpll_msg_add_measured_freq(msg, pin, ref, extack);
diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c
index 58235845fa3d5..b1d9182c7802f 100644
--- a/drivers/dpll/dpll_nl.c
+++ b/drivers/dpll/dpll_nl.c
@@ -19,6 +19,8 @@ const struct nla_policy dpll_pin_parent_device_nl_policy[DPLL_A_PIN_OPERSTATE +
[DPLL_A_PIN_STATE] = NLA_POLICY_RANGE(NLA_U32, 1, 3),
[DPLL_A_PIN_OPERSTATE] = NLA_POLICY_RANGE(NLA_U32, 1, 4),
[DPLL_A_PIN_PHASE_OFFSET] = { .type = NLA_S64, },
+ [DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET] = { .type = NLA_SINT, },
+ [DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT] = { .type = NLA_SINT, },
};
const struct nla_policy dpll_pin_parent_pin_nl_policy[DPLL_A_PIN_STATE + 1] = {
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 6fd718696de0d..05e63661bf074 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -295,11 +295,12 @@ zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin,
static int
zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
- s64 *ffo, struct netlink_ext_ack *extack)
+ struct dpll_ffo_param *ffo,
+ struct netlink_ext_ack *extack)
{
struct zl3073x_dpll_pin *pin = pin_priv;
- *ffo = pin->freq_offset;
+ ffo->ffo = pin->freq_offset;
return 0;
}
@@ -1274,6 +1275,7 @@ zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll,
}
static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
+ .supported_ffo = BIT(DPLL_FFO_PORT_RXTX_RATE),
.direction_get = zl3073x_dpll_pin_direction_get,
.esync_get = zl3073x_dpll_input_pin_esync_get,
.esync_set = zl3073x_dpll_input_pin_esync_set,
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/dpll.c b/drivers/net/ethernet/mellanox/mlx5/core/dpll.c
index bce72e8d1bc31..7c69d9029bfa4 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/dpll.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/dpll.c
@@ -300,7 +300,8 @@ static int mlx5_dpll_state_on_dpll_set(const struct dpll_pin *pin,
static int mlx5_dpll_ffo_get(const struct dpll_pin *pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
- s64 *ffo, struct netlink_ext_ack *extack)
+ struct dpll_ffo_param *ffo,
+ struct netlink_ext_ack *extack)
{
struct mlx5_dpll_synce_status synce_status;
struct mlx5_dpll *mdpll = pin_priv;
@@ -309,10 +310,11 @@ static int mlx5_dpll_ffo_get(const struct dpll_pin *pin, void *pin_priv,
err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status);
if (err)
return err;
- return mlx5_dpll_pin_ffo_get(&synce_status, ffo);
+ return mlx5_dpll_pin_ffo_get(&synce_status, &ffo->ffo);
}
static const struct dpll_pin_ops mlx5_dpll_pins_ops = {
+ .supported_ffo = BIT(DPLL_FFO_PORT_RXTX_RATE),
.direction_get = mlx5_dpll_pin_direction_get,
.state_on_dpll_get = mlx5_dpll_state_on_dpll_get,
.state_on_dpll_set = mlx5_dpll_state_on_dpll_set,
diff --git a/include/linux/dpll.h b/include/linux/dpll.h
index b6f16c884b99e..945dfde9dc54d 100644
--- a/include/linux/dpll.h
+++ b/include/linux/dpll.h
@@ -60,7 +60,20 @@ struct dpll_device_ops {
struct netlink_ext_ack *extack);
};
+enum dpll_ffo_type {
+ DPLL_FFO_PORT_RXTX_RATE,
+ DPLL_FFO_PIN_DEVICE,
+
+ __DPLL_FFO_TYPE_MAX,
+};
+
+struct dpll_ffo_param {
+ enum dpll_ffo_type type;
+ s64 ffo;
+};
+
struct dpll_pin_ops {
+ unsigned long supported_ffo;
int (*frequency_set)(const struct dpll_pin *pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
const u64 frequency,
@@ -121,7 +134,8 @@ struct dpll_pin_ops {
struct netlink_ext_ack *extack);
int (*ffo_get)(const struct dpll_pin *pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
- s64 *ffo, struct netlink_ext_ack *extack);
+ struct dpll_ffo_param *ffo,
+ struct netlink_ext_ack *extack);
int (*measured_freq_get)(const struct dpll_pin *pin, void *pin_priv,
const struct dpll_device *dpll,
void *dpll_priv, u64 *measured_freq,
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v4 0/2] dpll: rework fractional frequency offset reporting
From: Ivan Vecera @ 2026-05-11 15:58 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
Eric Dumazet, Jakub Kicinski, Jiri Pirko, Jonathan Corbet,
Leon Romanovsky, Mark Bloch, Michal Schmidt, Paolo Abeni,
Pasi Vaananen, Petr Oros, Prathosh Satish, Saeed Mahameed,
Shuah Khan, Simon Horman, Tariq Toukan, Vadim Fedorenko,
linux-doc, linux-kernel, linux-rdma
Rework how the fractional frequency offset (FFO) is reported in
the DPLL subsystem.
Both fractional-frequency-offset (PPM) and
fractional-frequency-offset-ppt (PPT) attributes are now present at
the top level of a pin and inside each pin-parent-device nest. They
carry the same measurement at different precisions.
Introduce enum dpll_ffo_type and struct dpll_ffo_param to distinguish
FFO contexts: DPLL_FFO_PORT_RXTX_RATE for the RX vs TX symbol rate
offset at the top level, and DPLL_FFO_PIN_DEVICE for the pin vs
parent DPLL offset in the nest. Drivers declare which types they
support via the supported_ffo bitmask in dpll_pin_ops; the core only
calls ffo_get for opted-in types.
Patch 1 adds the type-safe FFO API, updates the YAML spec, netlink
handling, and documentation, and converts mlx5 and zl3073x drivers.
Patch 2 implements the nested FFO for zl3073x using the
dpll_df_offset_x register with ref_ofst=1, providing 2^-48
resolution. The old per-reference frequency measurement is removed
as it was redundant with measured-frequency.
Changes v3 -> v4:
- Replace dpll=NULL overloading with enum dpll_ffo_type and
struct dpll_ffo_param (Jakub Kicinski)
- Add supported_ffo opt-in bitmask in dpll_pin_ops for fail-close
driver validation (Jakub Kicinski)
- Add WARN_ON in dpll_pin_register for supported_ffo without
ffo_get callback
- Use atomic64_t for freq_offset to prevent torn reads on 32-bit
Changes v2 -> v3:
- Keep both FFO attributes (PPM and PPT) at both levels instead of
moving PPT under pin-parent-device only (Jiri Pirko)
- Unify attribute documentation to describe semantics at each level
Changes v1 -> v2:
- Minor commit message fixes
Ivan Vecera (2):
dpll: add fractional frequency offset to pin-parent-device
dpll: zl3073x: report FFO as DPLL vs input reference offset
Documentation/driver-api/dpll.rst | 20 +++++++++
Documentation/netlink/specs/dpll.yaml | 28 +++++++-----
drivers/dpll/dpll_core.c | 3 +-
drivers/dpll/dpll_netlink.c | 30 +++++++------
drivers/dpll/dpll_nl.c | 2 +
drivers/dpll/zl3073x/chan.c | 31 ++++++++++++-
drivers/dpll/zl3073x/chan.h | 14 ++++++
drivers/dpll/zl3073x/core.c | 45 -------------------
drivers/dpll/zl3073x/dpll.c | 40 ++++++++---------
drivers/dpll/zl3073x/ref.h | 14 ------
drivers/dpll/zl3073x/regs.h | 15 +++++++
.../net/ethernet/mellanox/mlx5/core/dpll.c | 6 ++-
include/linux/dpll.h | 16 ++++++-
13 files changed, 154 insertions(+), 110 deletions(-)
--
2.53.0
^ permalink raw reply
* Re: [PATCH] killswitch: add per-function short-circuit mitigation primitive
From: Sasha Levin @ 2026-05-11 15:55 UTC (permalink / raw)
To: Michal Hocko
Cc: Breno Leitao, Andrew Morton, corbet, skhan, linux-doc,
linux-kernel, linux-kselftest, gregkh
In-Reply-To: <agHm9Vj7bPPCRS1g@tiehlicka>
On Mon, May 11, 2026 at 04:25:57PM +0200, Michal Hocko wrote:
>On Mon 11-05-26 09:56:30, Sasha Levin wrote:
>> On Mon, May 11, 2026 at 03:49:24PM +0200, Michal Hocko wrote:
>> > On Mon 11-05-26 09:39:32, Sasha Levin wrote:
>> > > On Mon, May 11, 2026 at 03:07:51PM +0200, Michal Hocko wrote:
>> > > > On Mon 11-05-26 04:41:38, Breno Leitao wrote:
>> > > > > On Fri, May 08, 2026 at 05:47:04PM -0400, Sasha Levin wrote:
>> > > > > > On Fri, May 08, 2026 at 01:56:30PM -0700, Andrew Morton wrote:
>> > > > > > > On Thu, 7 May 2026 03:05:45 -0400 Sasha Levin <sashal@kernel.org> wrote:
>> > > > > > >
>> > > > > > > > When a (security) issue goes public, fleets stay exposed until a patched kernel
>> > > > > > > > is built, distributed, and rebooted into.
>> > > > > > > >
>> > > > > > > > For many such issues the simplest mitigation is to stop calling the buggy
>> > > > > > > > function. Killswitch provides that. An admin writes:
>> > > > > > > >
>> > > > > > > > echo "engage af_alg_sendmsg -1" \
>> > > > > > > > > /sys/kernel/security/killswitch/control
>> > > > > > >
>> > > > > > > It certainly sounds useful, but what would I know. How do we hunt down
>> > > > > > > suitable operations people (aka "target audience") to find out how
>> > > > > > > useful this is to them?
>> > > > > >
>> > > > > > I'm not entierly sure here... If folks have suggestions on folks to loop in,
>> > > > > > that'll be great!
>> > > > >
>> > > > > I work with these issues at Meta, and this approach would address a real
>> > > > > need we have.
>> > >
>> > > Thanks for the feedback!
>> > >
>> > > > > While livepatch could theoretically solve this problem, it's less suited
>> > > > > for rapid mitigation for a couple of reasons:
>> > > > >
>> > > > > 1) Livepatch rollout is inherently slower due to the blast radius if a
>> > > > > bug exists in the livepatch mechanism itself.
>> > > > >
>> > > > > 2) It's common to run hundreds of different kernel versions across a
>> > > > > fleet. Since livepatch is kernel-specific, a single CVE suddenly
>> > > > > requires building and deploying hundreds of individual livepatches—
>> > > > > far less practical than a simple sysfs write.
>> > > >
>> > > > LP is certainly a more laborous solution. I guess this is quite clear.
>> > > > It is also much safer option as it deals with all implementation details
>> > > > like consistency. All that is not done for fun. I am really wondering
>> > > > how admins are expected to a) know which kernel functions are ok/safe to
>> > > > disable and b) when it is safe to do so without introducing unsafe
>> > > > kernel state or introduce an outright bug that way.
>> > >
>> > > In a similar way to how they would know if a given livepatch is safe to apply -
>> > > ideally it would be communicated by the vendor/distro/kernel team.
>> >
>> > You have missed my point. KLP takes an extra steps to make sure patching
>> > a particular function is safe to modify or to put the change into the
>> > effect.
>>
>> Safety checks like making sure the patched function is on the stack, or did you
>> mean something else?
>
>Yes, exactly what LP infrastructure already provides.
But do we actually need it here?
For threads already inside the body when killswitch is engaged: they just keep
executing the body to completion.
For threads at entry after the kprobe arms: pre-handler fires, they exit with
the override retval, never enter the body.
For threads arriving while the kprobe is being armed: kprobes-on-ftrace uses
ftrace's text_poke for atomic arm, so there's no "half-armed" window.
>> > > "On Debian XX.YY, use the following command to mitigate CVE-AAAA-BBBB:
>> > >
>> > > echo "engage woops -1" > /sys/kernel/security/killswitch/control"
>> > >
>> > > > Thiking about this I can see how waiting for an official LP can be time
>> > > > consuming and sometimes creating those is far from trivial. But would it
>> > > > make sense to have automated LP creation tooling available that would
>> > > > allow to return early from a function and relly on the existing
>> > > > infrastructure to do the right thing?
>> > >
>> > > This would definitely help (and in light of how the last couple of weeks played
>> > > out, the case for livepatches definitely increased), but not all
>> > > vendors/distros provide livepatches.
>> >
>> > The point I've tried to make is that you (as an admin) shouldn't depend
>> > on your vendor to provide you with an official LP just to disable a
>> > certain function(ality). That is/should be a trivial case where the LP
>> > should be ideally generated automagically if you have a tooling
>> > available. I might be wrong and overlook some complexity here.
>>
>> Module signing is what stops that approach for me.
>
>OK, so the actual constrain here is that you cannot load your own
>modules. That was not really clear from your description. I assume you
>cannot enroll your own key and sign?
It's one of them, yes. I don't want to build my own kernel (and assume that
most distro users don't build their own kernel), so I can't enroll my own keys.
Outside of that, you would still need to have a patch-per-version (or at least,
per group of versions where the patch applies without conflicts).
--
Thanks,
Sasha
^ permalink raw reply
* [PATCH v6 4/4] Documentation: document panic_on_unrecoverable_memory_failure sysctl
From: Breno Leitao @ 2026-05-11 15:38 UTC (permalink / raw)
To: Miaohe Lin, Naoya Horiguchi, Andrew Morton, Jonathan Corbet,
Shuah Khan, David Hildenbrand, Lorenzo Stoakes, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Liam R. Howlett
Cc: linux-mm, linux-kernel, linux-doc, linux-kselftest, Breno Leitao,
linux-trace-kernel, kernel-team
In-Reply-To: <20260511-ecc_panic-v6-0-183012ba7d4b@debian.org>
Add documentation for the new vm.panic_on_unrecoverable_memory_failure
sysctl, describing which failures trigger a panic (kernel-owned pages
the handler cannot recover) and which are intentionally left out
(transient allocator races and unclassified pages).
Signed-off-by: Breno Leitao <leitao@debian.org>
---
Documentation/admin-guide/sysctl/vm.rst | 70 +++++++++++++++++++++++++++++++++
1 file changed, 70 insertions(+)
diff --git a/Documentation/admin-guide/sysctl/vm.rst b/Documentation/admin-guide/sysctl/vm.rst
index 97e12359775c9..802c51ba8c43b 100644
--- a/Documentation/admin-guide/sysctl/vm.rst
+++ b/Documentation/admin-guide/sysctl/vm.rst
@@ -67,6 +67,7 @@ Currently, these files are in /proc/sys/vm:
- page-cluster
- page_lock_unfairness
- panic_on_oom
+- panic_on_unrecoverable_memory_failure
- percpu_pagelist_high_fraction
- stat_interval
- stat_refresh
@@ -925,6 +926,75 @@ panic_on_oom=2+kdump gives you very strong tool to investigate
why oom happens. You can get snapshot.
+panic_on_unrecoverable_memory_failure
+======================================
+
+When a hardware memory error (e.g. multi-bit ECC) hits a kernel page
+that cannot be recovered by the memory failure handler, the default
+behaviour is to ignore the error and continue operation. This is
+dangerous because the corrupted data remains accessible to the kernel,
+risking silent data corruption or a delayed crash when the poisoned
+memory is next accessed.
+
+When enabled, this sysctl triggers a panic on kernel-owned pages that
+the memory failure handler cannot recover: reserved pages
+(``PageReserved``) and stable kernel pages that hwpoison cannot handle
+(slab, vmalloc, page tables, kernel stacks, and similar non-LRU,
+non-buddy pages).
+
+Other failure paths are intentionally left out because they can be
+reached by transient races with the page allocator (an in-flight
+buddy allocation has refcount 0 and is no longer on the buddy free
+list, briefly), and panicking on them would risk killing the box for
+a page that was actually destined for userspace where the standard
+SIGBUS recovery path applies. Pages whose state could not be
+classified at all are also not covered, since an unknown state is
+not a sound basis for a panic decision.
+
+For many environments it is preferable to panic immediately with a clean
+crash dump that captures the original error context, rather than to
+continue and face a random crash later whose cause is difficult to
+diagnose.
+
+Use cases
+---------
+
+This option is most useful in environments where unattributed crashes
+are expensive to debug or where data integrity must take precedence
+over availability:
+
+* Large fleets, where multi-bit ECC errors on kernel pages are observed
+ regularly and post-mortem analysis of an unrelated downstream crash
+ (often seconds to minutes after the original error) consumes
+ significant engineering effort.
+
+* Systems configured with kdump, where panicking at the moment of the
+ hardware error produces a vmcore that still contains the faulting
+ address, the affected page state, and the originating MCE/GHES
+ record — context that is typically lost by the time a delayed crash
+ occurs.
+
+* High-availability clusters that rely on fast, deterministic node
+ failure for failover, and prefer an immediate panic over silent data
+ corruption propagating to replicas or persistent storage.
+
+* Kernel and platform developers reproducing hwpoison issues with
+ tools such as ``mce-inject`` or error-injection debugfs interfaces,
+ where panicking on the unrecoverable path makes regressions
+ immediately visible instead of surfacing as later, unrelated
+ failures.
+
+= =====================================================================
+0 Try to continue operation (default).
+1 Panic immediately. If the ``panic`` sysctl is also non-zero then the
+ machine will be rebooted.
+= =====================================================================
+
+Example::
+
+ echo 1 > /proc/sys/vm/panic_on_unrecoverable_memory_failure
+
+
percpu_pagelist_high_fraction
=============================
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v6 3/4] mm/memory-failure: add panic option for unrecoverable pages
From: Breno Leitao @ 2026-05-11 15:38 UTC (permalink / raw)
To: Miaohe Lin, Naoya Horiguchi, Andrew Morton, Jonathan Corbet,
Shuah Khan, David Hildenbrand, Lorenzo Stoakes, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Liam R. Howlett
Cc: linux-mm, linux-kernel, linux-doc, linux-kselftest, Breno Leitao,
linux-trace-kernel, kernel-team
In-Reply-To: <20260511-ecc_panic-v6-0-183012ba7d4b@debian.org>
Add a sysctl panic_on_unrecoverable_memory_failure that triggers a
kernel panic when memory_failure() encounters pages that cannot be
recovered. This provides a clean crash with useful debug information
rather than allowing silent data corruption or a delayed crash at an
unrelated code path.
Panic eligibility is intentionally narrow: only MF_MSG_KERNEL with
result == MF_IGNORED panics. That covers reserved pages (PageReserved)
and the kernel page types that the prior patch promotes from
MF_MSG_GET_HWPOISON via MF_GET_PAGE_UNHANDLABLE — slab, vmalloc, page
tables, kernel stacks, and similar non-LRU/non-buddy kernel-owned pages.
All other action types are excluded:
- MF_MSG_GET_HWPOISON and MF_MSG_KERNEL_HIGH_ORDER can be reached by
transient refcount races with the page allocator (an in-flight buddy
allocation has refcount 0 and is no longer on the buddy free list,
briefly), and panicking on them would risk killing the box for what
is actually a recoverable userspace page.
- MF_MSG_UNKNOWN means identify_page_state() could not classify the
page; that is precisely the wrong basis for a panic decision.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
mm/memory-failure.c | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index 4210173060aac..e4a9ceacaf36b 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -74,6 +74,8 @@ static int sysctl_memory_failure_recovery __read_mostly = 1;
static int sysctl_enable_soft_offline __read_mostly = 1;
+static int sysctl_panic_on_unrecoverable_mf __read_mostly;
+
atomic_long_t num_poisoned_pages __read_mostly = ATOMIC_LONG_INIT(0);
static bool hw_memory_failure __read_mostly = false;
@@ -155,6 +157,15 @@ static const struct ctl_table memory_failure_table[] = {
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_ONE,
+ },
+ {
+ .procname = "panic_on_unrecoverable_memory_failure",
+ .data = &sysctl_panic_on_unrecoverable_mf,
+ .maxlen = sizeof(sysctl_panic_on_unrecoverable_mf),
+ .mode = 0644,
+ .proc_handler = proc_dointvec_minmax,
+ .extra1 = SYSCTL_ZERO,
+ .extra2 = SYSCTL_ONE,
}
};
@@ -1281,6 +1292,18 @@ static void update_per_node_mf_stats(unsigned long pfn,
++mf_stats->total;
}
+static bool panic_on_unrecoverable_mf(enum mf_action_page_type type,
+ enum mf_result result)
+{
+ if (!sysctl_panic_on_unrecoverable_mf || result != MF_IGNORED)
+ return false;
+
+ if (type == MF_MSG_KERNEL)
+ return true;
+
+ return false;
+}
+
/*
* "Dirty/Clean" indication is not 100% accurate due to the possibility of
* setting PG_dirty outside page lock. See also comment above set_page_dirty().
@@ -1298,6 +1321,9 @@ static int action_result(unsigned long pfn, enum mf_action_page_type type,
pr_err("%#lx: recovery action for %s: %s\n",
pfn, action_page_types[type], action_name[result]);
+ if (panic_on_unrecoverable_mf(type, result))
+ panic("Memory failure: %#lx: unrecoverable page", pfn);
+
return (result == MF_RECOVERED || result == MF_DELAYED) ? 0 : -EBUSY;
}
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v6 2/4] mm/memory-failure: classify get_any_page() failures by reason
From: Breno Leitao @ 2026-05-11 15:38 UTC (permalink / raw)
To: Miaohe Lin, Naoya Horiguchi, Andrew Morton, Jonathan Corbet,
Shuah Khan, David Hildenbrand, Lorenzo Stoakes, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Liam R. Howlett
Cc: linux-mm, linux-kernel, linux-doc, linux-kselftest, Breno Leitao,
linux-trace-kernel, kernel-team, Lance Yang
In-Reply-To: <20260511-ecc_panic-v6-0-183012ba7d4b@debian.org>
When get_any_page() fails to grab a page reference, the *reason* it
failed is known at the call site but is not surfaced to callers: the
HWPoisonHandlable() rejection path (a stable kernel page hwpoison cannot
handle — slab, vmalloc, page tables, kernel stacks, ...) and the
page_count() / put_page race paths (a transient page-allocator lifecycle
race) all collapse to a single negative errno by the time
memory_failure() sees them. memory_failure() can only observe the
conflated result and reports both as MF_MSG_GET_HWPOISON.
Surface the diagnosis explicitly. Add an mf_get_page_status enum,
plumbed out through get_any_page() and get_hwpoison_page() (NULL is
accepted by callers that do not care — unpoison_memory() and
soft_offline_page() pass NULL). get_any_page() sets the status at the
moment it gives up:
MF_GET_PAGE_UNHANDLABLE — HWPoisonHandlable() rejected the page
after retries.
MF_GET_PAGE_RACE — exhausted retries on a refcount /
lifecycle race with the allocator.
memory_failure() then promotes the unhandlable case to MF_MSG_KERNEL
alongside the existing PageReserved branch, and leaves the
transient-race case as MF_MSG_GET_HWPOISON. This forms the foundation
a later patch will rely on to decide whether an unrecoverable failure
should panic.
Drop the "reserved" qualifier from action_page_types[MF_MSG_KERNEL]
and the matching tracepoint string in MF_PAGE_TYPE: the enum value
now covers both PageReserved pages and unhandlable kernel pages
(slab, vmalloc, page tables, kernel stacks, ...), so "kernel page"
is the accurate label for both populations.
Suggested-by: Lance Yang <lance.yang@linux.dev>
Signed-off-by: Breno Leitao <leitao@debian.org>
---
include/trace/events/memory-failure.h | 2 +-
mm/memory-failure.c | 46 +++++++++++++++++++++++++++++------
2 files changed, 39 insertions(+), 9 deletions(-)
diff --git a/include/trace/events/memory-failure.h b/include/trace/events/memory-failure.h
index aa57cc8f896be..8a860e6fcb4e9 100644
--- a/include/trace/events/memory-failure.h
+++ b/include/trace/events/memory-failure.h
@@ -24,7 +24,7 @@
EMe ( MF_RECOVERED, "Recovered" )
#define MF_PAGE_TYPE \
- EM ( MF_MSG_KERNEL, "reserved kernel page" ) \
+ EM ( MF_MSG_KERNEL, "kernel page" ) \
EM ( MF_MSG_KERNEL_HIGH_ORDER, "high-order kernel page" ) \
EM ( MF_MSG_HUGE, "huge page" ) \
EM ( MF_MSG_FREE_HUGE, "free huge page" ) \
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index f112fb27a8ff6..4210173060aac 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -878,7 +878,7 @@ static const char *action_name[] = {
};
static const char * const action_page_types[] = {
- [MF_MSG_KERNEL] = "reserved kernel page",
+ [MF_MSG_KERNEL] = "kernel page",
[MF_MSG_KERNEL_HIGH_ORDER] = "high-order kernel page",
[MF_MSG_HUGE] = "huge page",
[MF_MSG_FREE_HUGE] = "free huge page",
@@ -1389,11 +1389,29 @@ static int __get_hwpoison_page(struct page *page, unsigned long flags)
#define GET_PAGE_MAX_RETRY_NUM 3
-static int get_any_page(struct page *p, unsigned long flags)
+enum mf_get_page_status {
+ MF_GET_PAGE_OK = 0,
+ MF_GET_PAGE_RACE,
+ MF_GET_PAGE_UNHANDLABLE,
+};
+
+static void set_mf_get_page_status(enum mf_get_page_status *gp_status,
+ enum mf_get_page_status value)
+{
+ if (!gp_status)
+ return;
+
+ *gp_status = value;
+}
+
+static int get_any_page(struct page *p, unsigned long flags,
+ enum mf_get_page_status *gp_status)
{
int ret = 0, pass = 0;
bool count_increased = false;
+ set_mf_get_page_status(gp_status, MF_GET_PAGE_OK);
+
if (flags & MF_COUNT_INCREASED)
count_increased = true;
@@ -1406,11 +1424,13 @@ static int get_any_page(struct page *p, unsigned long flags)
if (pass++ < GET_PAGE_MAX_RETRY_NUM)
goto try_again;
ret = -EBUSY;
+ set_mf_get_page_status(gp_status, MF_GET_PAGE_RACE);
} else if (!PageHuge(p) && !is_free_buddy_page(p)) {
/* We raced with put_page, retry. */
if (pass++ < GET_PAGE_MAX_RETRY_NUM)
goto try_again;
ret = -EIO;
+ set_mf_get_page_status(gp_status, MF_GET_PAGE_RACE);
}
goto out;
} else if (ret == -EBUSY) {
@@ -1423,6 +1443,7 @@ static int get_any_page(struct page *p, unsigned long flags)
goto try_again;
}
ret = -EIO;
+ set_mf_get_page_status(gp_status, MF_GET_PAGE_UNHANDLABLE);
goto out;
}
}
@@ -1442,6 +1463,7 @@ static int get_any_page(struct page *p, unsigned long flags)
}
put_page(p);
ret = -EIO;
+ set_mf_get_page_status(gp_status, MF_GET_PAGE_UNHANDLABLE);
}
out:
if (ret == -EIO)
@@ -1480,6 +1502,7 @@ static int __get_unpoison_page(struct page *page)
* get_hwpoison_page() - Get refcount for memory error handling
* @p: Raw error page (hit by memory error)
* @flags: Flags controlling behavior of error handling
+ * @gp_status: Optional output for the reason get_any_page() failed
*
* get_hwpoison_page() takes a page refcount of an error page to handle memory
* error on it, after checking that the error page is in a well-defined state
@@ -1503,7 +1526,8 @@ static int __get_unpoison_page(struct page *page)
* operations like allocation and free,
* -EHWPOISON when the page is hwpoisoned and taken off from buddy.
*/
-static int get_hwpoison_page(struct page *p, unsigned long flags)
+static int get_hwpoison_page(struct page *p, unsigned long flags,
+ enum mf_get_page_status *gp_status)
{
int ret;
@@ -1511,7 +1535,7 @@ static int get_hwpoison_page(struct page *p, unsigned long flags)
if (flags & MF_UNPOISON)
ret = __get_unpoison_page(p);
else
- ret = get_any_page(p, flags);
+ ret = get_any_page(p, flags, gp_status);
zone_pcp_enable(page_zone(p));
return ret;
@@ -2349,6 +2373,7 @@ int memory_failure(unsigned long pfn, int flags)
bool retry = true;
int hugetlb = 0;
bool is_reserved;
+ enum mf_get_page_status gp_status = MF_GET_PAGE_OK;
if (!sysctl_memory_failure_recovery)
panic("Memory failure on page %lx", pfn);
@@ -2424,7 +2449,7 @@ int memory_failure(unsigned long pfn, int flags)
*/
is_reserved = PageReserved(p);
- res = get_hwpoison_page(p, flags);
+ res = get_hwpoison_page(p, flags, &gp_status);
if (!res) {
if (is_free_buddy_page(p)) {
if (take_page_off_buddy(p)) {
@@ -2445,7 +2470,12 @@ int memory_failure(unsigned long pfn, int flags)
}
goto unlock_mutex;
} else if (res < 0) {
- if (is_reserved)
+ /*
+ * Promote a stable unhandlable kernel page diagnosed by
+ * get_hwpoison_page() to MF_MSG_KERNEL alongside reserved
+ * pages; transient lifecycle races stay as MF_MSG_GET_HWPOISON.
+ */
+ if (is_reserved || gp_status == MF_GET_PAGE_UNHANDLABLE)
res = action_result(pfn, MF_MSG_KERNEL, MF_IGNORED);
else
res = action_result(pfn, MF_MSG_GET_HWPOISON,
@@ -2750,7 +2780,7 @@ int unpoison_memory(unsigned long pfn)
goto unlock_mutex;
}
- ghp = get_hwpoison_page(p, MF_UNPOISON);
+ ghp = get_hwpoison_page(p, MF_UNPOISON, NULL);
if (!ghp) {
if (folio_test_hugetlb(folio)) {
huge = true;
@@ -2957,7 +2987,7 @@ int soft_offline_page(unsigned long pfn, int flags)
retry:
get_online_mems();
- ret = get_hwpoison_page(page, flags | MF_SOFT_OFFLINE);
+ ret = get_hwpoison_page(page, flags | MF_SOFT_OFFLINE, NULL);
put_online_mems();
if (hwpoison_filter(page)) {
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v6 1/4] mm/memory-failure: report MF_MSG_KERNEL for reserved pages
From: Breno Leitao @ 2026-05-11 15:38 UTC (permalink / raw)
To: Miaohe Lin, Naoya Horiguchi, Andrew Morton, Jonathan Corbet,
Shuah Khan, David Hildenbrand, Lorenzo Stoakes, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Liam R. Howlett
Cc: linux-mm, linux-kernel, linux-doc, linux-kselftest, Breno Leitao,
linux-trace-kernel, kernel-team, Lance Yang
In-Reply-To: <20260511-ecc_panic-v6-0-183012ba7d4b@debian.org>
When get_hwpoison_page() returns a negative value, distinguish
reserved pages from other failure cases by reporting MF_MSG_KERNEL
instead of MF_MSG_GET_HWPOISON. Reserved pages belong to the kernel
and should be classified accordingly for proper handling.
Sample PG_reserved before the get_hwpoison_page() call. In the
MF_COUNT_INCREASED path get_any_page() can drop the caller's
reference before returning -EIO, after which the underlying page may
have been freed and reallocated with page->flags reset; reading
PageReserved(p) at that point would observe stale or unrelated state.
The pre-call snapshot reflects what the page actually was at the
time of the failure event.
Acked-by: Miaohe Lin <linmiaohe@huawei.com>
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Signed-off-by: Breno Leitao <leitao@debian.org>
---
mm/memory-failure.c | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index 866c4428ac7ef..f112fb27a8ff6 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -2348,6 +2348,7 @@ int memory_failure(unsigned long pfn, int flags)
unsigned long page_flags;
bool retry = true;
int hugetlb = 0;
+ bool is_reserved;
if (!sysctl_memory_failure_recovery)
panic("Memory failure on page %lx", pfn);
@@ -2411,6 +2412,18 @@ int memory_failure(unsigned long pfn, int flags)
* In fact it's dangerous to directly bump up page count from 0,
* that may make page_ref_freeze()/page_ref_unfreeze() mismatch.
*/
+ /*
+ * Pages with PG_reserved set are not currently managed by the
+ * page allocator (memblock-reserved memory, driver reservations,
+ * etc.), so classify them as kernel-owned for reporting.
+ *
+ * Sample the flag before get_hwpoison_page(): in the
+ * MF_COUNT_INCREASED path, get_any_page() can drop the caller's
+ * reference before returning -EIO, after which page->flags may
+ * have been reset by the allocator.
+ */
+ is_reserved = PageReserved(p);
+
res = get_hwpoison_page(p, flags);
if (!res) {
if (is_free_buddy_page(p)) {
@@ -2432,7 +2445,11 @@ int memory_failure(unsigned long pfn, int flags)
}
goto unlock_mutex;
} else if (res < 0) {
- res = action_result(pfn, MF_MSG_GET_HWPOISON, MF_IGNORED);
+ if (is_reserved)
+ res = action_result(pfn, MF_MSG_KERNEL, MF_IGNORED);
+ else
+ res = action_result(pfn, MF_MSG_GET_HWPOISON,
+ MF_IGNORED);
goto unlock_mutex;
}
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v6 0/4] mm/memory-failure: add panic option for unrecoverable pages
From: Breno Leitao @ 2026-05-11 15:38 UTC (permalink / raw)
To: Miaohe Lin, Naoya Horiguchi, Andrew Morton, Jonathan Corbet,
Shuah Khan, David Hildenbrand, Lorenzo Stoakes, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Liam R. Howlett
Cc: linux-mm, linux-kernel, linux-doc, linux-kselftest, Breno Leitao,
linux-trace-kernel, kernel-team, Lance Yang
Changes from previouis version
* Replaced the late, action-time recheck for MF_MSG_KERNEL_HIGH_ORDER
with early failure classification inside get_any_page() (new patch
2/4).
* David Hildenbrand pointed out that the recheck inspected refcount and
folio mapping without holding a folio reference, which is unsafe
(concurrent split can trigger VM_WARN_ON_FOLIO).
* Lance Yang suggested moving the disambiguation to the call site that
still knows *why* the page reference could not be taken, which is what
this version does via a new enum mf_get_page_status (MF_GET_PAGE_OK
/ RACE / UNHANDLABLE) plumbed out through get_hwpoison_page().
Signed-off-by: Breno Leitao <leitao@debian.org>
---
Changes in v6:
- Dropped the selftest given the value was not clear
- Get the status of the failure from get_any_page()
- Small nits from different people/AIs.
- Link to v5: https://patch.msgid.link/20260424-ecc_panic-v5-0-a35f4b50425c@debian.org
Changes in v5:
- Add vm.panic_on_unrecoverable_memory_failure sysctl to panic on
unrecoverable kernel page hwpoison events (reserved pages, refcount-0
non-buddy pages, unknown state), with a recheck to avoid racing with
concurrent buddy allocations. (Miaohe)
- Distinguish reserved pages as MF_MSG_KERNEL in memory_failure(),
document the new sysctl in Documentation/admin-guide/sysctl/vm.rst,
and add a selftest verifying SIGBUS recovery on userspace pages still
works when the sysctl is enabled. (Miaohe)
- Added a selftest
- Link to v4:
https://patch.msgid.link/20260415-ecc_panic-v4-0-2d0277f8f601@debian.org
Changes in v4:
- Drop CONFIG_BOOTPARAM_MEMORY_FAILURE_PANIC kernel configuration option.
- Split the reserved page classification (MF_MSG_KERNEL) into its own
patch, separate from the panic mechanism.
- Document why the buddy allocator TOCTOU race (between
get_hwpoison_page() and is_free_buddy_page()) cannot cause false
positives: PG_hwpoison is set beforehand and check_new_page() in the
page allocator rejects hwpoisoned pages.
- Document the narrow LRU isolation race window for MF_MSG_UNKNOWN and
its mitigation via identify_page_state()'s two-pass design.
- Explicitly document why MF_MSG_GET_HWPOISON is excluded from the
panic conditions (shared path with transient races and non-reserved
kernel memory).
- Link to v3: https://patch.msgid.link/20260413-ecc_panic-v3-0-1dcbb2f12bc4@debian.org
Changes in v3:
- Rename is_unrecoverable_memory_failure() to panic_on_unrecoverable_mf()
as suggested by maintainer.
- Add CONFIG_BOOTPARAM_MEMORY_FAILURE_PANIC kernel configuration option,
similar to CONFIG_BOOTPARAM_HARDLOCKUP_PANIC.
- Add documentation for the sysctl and CONFIG option.
- Add code comments documenting the panic condition design rationale and
how the retry mechanism mitigates false positives from buddy allocator
races.
- Link to v2: https://patch.msgid.link/20260331-ecc_panic-v2-0-9e40d0f64f7a@debian.org
Changes in v2:
- Panic on MF_MSG_KERNEL, MF_MSG_KERNEL_HIGH_ORDER and MF_MSG_UNKNOWN
instead of MF_MSG_GET_HWPOISON.
- Report MF_MSG_KERNEL for reserved pages when get_hwpoison_page() fails
instead of MF_MSG_GET_HWPOISON.
- Link to v1: https://patch.msgid.link/20260323-ecc_panic-v1-0-72a1921726c5@debian.org
To: Miaohe Lin <linmiaohe@huawei.com>
To: Naoya Horiguchi <nao.horiguchi@gmail.com>
To: Andrew Morton <akpm@linux-foundation.org>
To: Steven Rostedt <rostedt@goodmis.org>
To: Masami Hiramatsu <mhiramat@kernel.org>
To: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
To: Jonathan Corbet <corbet@lwn.net>
To: Shuah Khan <skhan@linuxfoundation.org>
Cc: linux-mm@kvack.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-trace-kernel@vger.kernel.org
Cc: linux-doc@vger.kernel.org
---
Breno Leitao (4):
mm/memory-failure: report MF_MSG_KERNEL for reserved pages
mm/memory-failure: classify get_any_page() failures by reason
mm/memory-failure: add panic option for unrecoverable pages
Documentation: document panic_on_unrecoverable_memory_failure sysctl
Documentation/admin-guide/sysctl/vm.rst | 70 ++++++++++++++++++++++++++
include/trace/events/memory-failure.h | 2 +-
mm/memory-failure.c | 89 ++++++++++++++++++++++++++++++---
3 files changed, 152 insertions(+), 9 deletions(-)
---
base-commit: e98d21c170b01ddef366f023bbfcf6b31509fa83
change-id: 20260323-ecc_panic-4e473b83087c
Best regards,
--
Breno Leitao <leitao@debian.org>
^ permalink raw reply
* Re: [PATCH RFC v4 10/10] docs: iio: add documentation for ad9910 driver
From: David Lechner @ 2026-05-11 15:23 UTC (permalink / raw)
To: Rodrigo Alencar, rodrigo.alencar, linux-iio, devicetree,
linux-kernel, linux-doc, linux-hardening
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan, Kees Cook,
Gustavo A. R. Silva
In-Reply-To: <uphcx5zr4lmukuom75g66hp4agurty7yq6mo6ri6otrsscqfek@tn3u5jjszaoy>
On 5/11/26 10:02 AM, Rodrigo Alencar wrote:
> On 26/05/11 09:46AM, David Lechner wrote:
>> On 5/10/26 4:30 AM, Rodrigo Alencar wrote:
>>> On 26/05/09 06:42PM, David Lechner wrote:
>>>> On 5/8/26 12:00 PM, Rodrigo Alencar via B4 Relay wrote:
>>>>> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
>>>>>
>>>>> Add documentation for the AD9910 DDS IIO driver, which describes channels,
>>>>> DDS modes, attributes and ABI usage examples.
>>>
>>> ...
>>>
>>>>> + must be a power of 2.
>>>>> +
>>>>> + * - ``frequency_offset``
>>>>> + - Hz
>>>>> + - Base FTW to which scaled parallel data is added. Range :math:`[0, f_{SYSCLK}/2)`.
>>>>> +
>>>>> + * - ``phase_offset``
>>>>> + - rad
>>>>> + - Base phase for polar modulation. Lower 8 bits of POW register.
>>>>> + Range :math:`[0, 2\pi/256)`.
>>>>> +
>>>>> + * - ``scale_offset``
>>>>> + - fractional
>>>>> + - Base amplitude for polar modulation. Lower 6 bits of ASF register.
>>>>> + Range :math:`[0, 1/256)`.
>>>>> +
>>>>
>>>> I guess there was some discussion on these attributes. I see some of these in the
>>>> ad9832 driver in staging, but I'm guessing they are new ABI. It isn't clear to
>>>> me from the documentation here what they actually do though. I guess they are
>>>> just basic transformations on the input signal?
>>>
>>> Not sure how the ABI is not clear:
>>>
>>> For a channel that allows amplitude control through buffers, this
>>> represents the value for a base amplitude scale. The actual output
>>> amplitude scale is a result with the sum of this value.
>>>
>>> So yes, it is a basic transformation.
>>
>> I didn't have time to read the ABI docs yet. For scale_offset though,
>> how is that different from the existing offset attribute?
>
> I suppose that existing offset ABI is applied to (raw * scale), mostly for
> voltage channels, here the scale_offset is an offset to the scale itself.
Ah, so a very general case would be (raw * (scale + scale_offset)) + offset
when the scale can change as a function of time and comes from an external
source.
>
>>>
>>>>
>>>> And a practical note, they should be "frequencyscale". I don't like that it is
>>>> harder to read, but it is easier for a machine to parse.
>>>
>>> Parsers like the ones in libiio is not having problems with that.
>>>
>>>>> +Usage examples
>>>>> +^^^^^^^^^^^^^^
>>>>> +
>>>>> +Set parallel port frequency modulation with a scale of 16 and a 50 MHz
>>>>> +offset:
>>>>> +
>>>>> +.. code-block:: bash
>>>>> +
>>>>> + echo 16 > /sys/bus/iio/devices/iio:device0/out_altvoltage113_frequency_scale
>>>>> + echo 50000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage113_frequency_offset
>>>>> +
>>>>> +Digital ramp generator (DRG)
>>>>> +----------------------------
>>>>> +
>>>>> +The DRG produces linear frequency, phase or amplitude sweeps using dedicated
>>>>> +hardware. It is controlled through three channels: a parent control channel
>>>>> +(``digital_ramp_generator``) and two child ramp channels
>>>>> +(``digital_ramp_up``, ``digital_ramp_down``). DRG destination is set when
>>>>> +ramp attributes are written, i.e. writing to ``frequency`` or ``frequency_roc``
>>>>> +sets the destination to frequency.
>>>>
>>>> Would it be better to say that the destination is set when the the
>>>> value is non-zero? Otherwise, how would one change the destination
>>>> once set?
>>>
>>> Destination is only one, so you just need to write phase or phase_roc, if you want
>>> to target phase then. Does that not sound intuitive?
>>
>> I was thinking about if you needed to change the configuration.
>> If you set it to phase, then want to change it to frequency, how
>> could you do that if 0 is a valid value for phase?
>>
>> Also how could you know which is selected by reading back the
>> values if 0 is a valid value?
>
> This is where Jonathan raised some concerns, so it is a good oportunity for you
> to provide your inputs! Right now, I am returning -EBUSY on read of an attribute
> where its destination is not selected. As pointed out, the destination selection
> is happening when writting to the attribute. In the previous patch, Jonathan
> suggested frequency_active, phase_active and scale_active to track mode priority,
> and It could be leveraged here for DRG destination selection. I havent gone for
> that because I was not willing to add that to all the channels given that it is
> mostly used for debugging, so I added frequency_source, phase_source and
> amplitude_source to debugfs instead.
The "last write wins" with the others changing to EBUSY makes more sense to
me now. If the docs said that, I missed it. Otherwise, that would be a helpful
thing to add to the docs here.
>
> Destination selection for RAM mode is firmware based at this point.
Seems reasonable.
> Destination selection for Parallel mode is still not clear... could use
> those *_active attributes or separate channels.
Since there are _offset attributes proposed for parallel input already,
could we just make it the same where you have to write one of those
attributes?
>
>>>
>>> Zero is a valid value to be written.
>>>
>>>>
>
^ permalink raw reply
* Re: [PATCH] Documentation: KVM: Document guest-visible compatibility expectations
From: Paolo Bonzini @ 2026-05-11 15:14 UTC (permalink / raw)
To: David Woodhouse, Jonathan Corbet, Shuah Khan, kvm, linux-doc,
linux-kernel, Sean Christopherson, Jim Mattson
Cc: Oliver Upton, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Catalin Marinas, Will Deacon, Raghavendra Rao Ananta, Eric Auger,
Kees Cook, Arnd Bergmann, Nathan Chancellor, linux-arm-kernel,
kvmarm, linux-kselftest
In-Reply-To: <6856b269d2af706eae397e0cf9c1231f89d9a932.camel@infradead.org>
On 5/11/26 10:57, David Woodhouse wrote:
> From: David Woodhouse <dwmw@amazon.co.uk>
>
> Document the expectation that KVM maintains guest-visible compatibility
> across host kernel upgrades and rollbacks. Specifically:
>
> - State saved/restored via KVM ioctls must be sufficient for live
> migration (and live update) between kernel versions.
>
> - Where a new kernel introduces a guest-visible change, it provides a
> mechanism for userspace to select the previous behaviour.
>
> - This allows both forward migration (upgrade) and backward migration
> (rollback) of guests.
>
> These expectations have been implicitly required on x86 but were not
> explicitly documented. Harmonise the expectations across all of KVM.
One big part of achieving this on x86 is the handling of CPUID. Despite
all the mess that KVM_SET_CPUID2 is (and sometimes the underlying
architecture too, as Jim Mattson would certainly agree), KVM is
generally able to provide a consistent view of its configuration to the
guest. This doesn't quite extend to compatibility across vendors, but
it does work across processor generations from either Intel or AMD.
I understand that Arm traditionally had much more trouble than x86 with
vendor-specified behavior that goes beyond the set of architectural
features, so we may need to tune the expectations. However, I agree
with David that this is needed at least as long as the host CPU does not
change.
Thanks,
Paolo
> Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
> ---
> Documentation/virt/kvm/api.rst | 14 ++++++++++++++
> Documentation/virt/kvm/review-checklist.rst | 20 ++++++++++++++------
> 2 files changed, 28 insertions(+), 6 deletions(-)
>
> diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
> index 269970221797..864f3daa7acb 100644
> --- a/Documentation/virt/kvm/api.rst
> +++ b/Documentation/virt/kvm/api.rst
> @@ -97,6 +97,20 @@ Instead, kvm defines extension identifiers and a facility to query
> whether a particular extension identifier is available. If it is, a
> set of ioctls is available for application use.
>
> +KVM will ensure that the state that can be saved and restored via the
> +KVM ioctls is sufficient to allow migration of a running guest between
> +host kernels while maintaining full compatibility of the guest-visible
> +device model. This includes migration to newer kernels (upgrade) and
> +to older kernels (rollback), provided that the older kernel supports
> +the set of features exposed to the guest. Where a new kernel version
> +introduces a guest-visible change, it will provide a mechanism (such
> +as a capability or a device attribute) that allows userspace to select
> +the previous behaviour. This serves two purposes: guests migrated
> +from an older kernel can continue to run with their original
> +observable environment, and new guests launched on the newer kernel
> +can be configured to match the feature set of the older kernel, so
> +that they remain migratable to the older kernel in case of rollback.
> +
>
> 4. API description
> ==================
> diff --git a/Documentation/virt/kvm/review-checklist.rst b/Documentation/virt/kvm/review-checklist.rst
> index 053f00c50d66..f0fbe1577a90 100644
> --- a/Documentation/virt/kvm/review-checklist.rst
> +++ b/Documentation/virt/kvm/review-checklist.rst
> @@ -18,22 +18,30 @@ Review checklist for kvm patches
> 5. New features must default to off (userspace should explicitly request them).
> Performance improvements can and should default to on.
>
> -6. New cpu features should be exposed via KVM_GET_SUPPORTED_CPUID2,
> +6. Guest-visible changes must not break migration compatibility. A guest
> + migrated from an older kernel must be able to run with its original
> + observable environment, and a guest launched on a newer kernel must be
> + configurable to match the older kernel's feature set for rollback.
> + Where a change alters guest-visible behaviour, provide a mechanism
> + (capability, device attribute, etc.) for userspace to select the
> + previous behaviour.
> +
> +7. New cpu features should be exposed via KVM_GET_SUPPORTED_CPUID2,
> or its equivalent for non-x86 architectures
>
> -7. The feature should be testable (see below).
> +8. The feature should be testable (see below).
>
> -8. Changes should be vendor neutral when possible. Changes to common code
> +9. Changes should be vendor neutral when possible. Changes to common code
> are better than duplicating changes to vendor code.
>
> -9. Similarly, prefer changes to arch independent code than to arch dependent
> +10. Similarly, prefer changes to arch independent code than to arch dependent
> code.
>
> -10. User/kernel interfaces and guest/host interfaces must be 64-bit clean
> +11. User/kernel interfaces and guest/host interfaces must be 64-bit clean
> (all variables and sizes naturally aligned on 64-bit; use specific types
> only - u64 rather than ulong).
>
> -11. New guest visible features must either be documented in a hardware manual
> +12. New guest visible features must either be documented in a hardware manual
> or be accompanied by documentation.
>
> Testing of KVM code
^ permalink raw reply
* Re: [PATCH RFC v4 10/10] docs: iio: add documentation for ad9910 driver
From: Rodrigo Alencar @ 2026-05-11 15:02 UTC (permalink / raw)
To: David Lechner, Rodrigo Alencar, rodrigo.alencar, linux-iio,
devicetree, linux-kernel, linux-doc, linux-hardening
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan, Kees Cook,
Gustavo A. R. Silva
In-Reply-To: <18c2eab9-c0c7-4e93-b4e8-73b18531e784@baylibre.com>
On 26/05/11 09:46AM, David Lechner wrote:
> On 5/10/26 4:30 AM, Rodrigo Alencar wrote:
> > On 26/05/09 06:42PM, David Lechner wrote:
> >> On 5/8/26 12:00 PM, Rodrigo Alencar via B4 Relay wrote:
> >>> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> >>>
> >>> Add documentation for the AD9910 DDS IIO driver, which describes channels,
> >>> DDS modes, attributes and ABI usage examples.
> >
> > ...
> >
> >>> + must be a power of 2.
> >>> +
> >>> + * - ``frequency_offset``
> >>> + - Hz
> >>> + - Base FTW to which scaled parallel data is added. Range :math:`[0, f_{SYSCLK}/2)`.
> >>> +
> >>> + * - ``phase_offset``
> >>> + - rad
> >>> + - Base phase for polar modulation. Lower 8 bits of POW register.
> >>> + Range :math:`[0, 2\pi/256)`.
> >>> +
> >>> + * - ``scale_offset``
> >>> + - fractional
> >>> + - Base amplitude for polar modulation. Lower 6 bits of ASF register.
> >>> + Range :math:`[0, 1/256)`.
> >>> +
> >>
> >> I guess there was some discussion on these attributes. I see some of these in the
> >> ad9832 driver in staging, but I'm guessing they are new ABI. It isn't clear to
> >> me from the documentation here what they actually do though. I guess they are
> >> just basic transformations on the input signal?
> >
> > Not sure how the ABI is not clear:
> >
> > For a channel that allows amplitude control through buffers, this
> > represents the value for a base amplitude scale. The actual output
> > amplitude scale is a result with the sum of this value.
> >
> > So yes, it is a basic transformation.
>
> I didn't have time to read the ABI docs yet. For scale_offset though,
> how is that different from the existing offset attribute?
I suppose that existing offset ABI is applied to (raw * scale), mostly for
voltage channels, here the scale_offset is an offset to the scale itself.
> >
> >>
> >> And a practical note, they should be "frequencyscale". I don't like that it is
> >> harder to read, but it is easier for a machine to parse.
> >
> > Parsers like the ones in libiio is not having problems with that.
> >
> >>> +Usage examples
> >>> +^^^^^^^^^^^^^^
> >>> +
> >>> +Set parallel port frequency modulation with a scale of 16 and a 50 MHz
> >>> +offset:
> >>> +
> >>> +.. code-block:: bash
> >>> +
> >>> + echo 16 > /sys/bus/iio/devices/iio:device0/out_altvoltage113_frequency_scale
> >>> + echo 50000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage113_frequency_offset
> >>> +
> >>> +Digital ramp generator (DRG)
> >>> +----------------------------
> >>> +
> >>> +The DRG produces linear frequency, phase or amplitude sweeps using dedicated
> >>> +hardware. It is controlled through three channels: a parent control channel
> >>> +(``digital_ramp_generator``) and two child ramp channels
> >>> +(``digital_ramp_up``, ``digital_ramp_down``). DRG destination is set when
> >>> +ramp attributes are written, i.e. writing to ``frequency`` or ``frequency_roc``
> >>> +sets the destination to frequency.
> >>
> >> Would it be better to say that the destination is set when the the
> >> value is non-zero? Otherwise, how would one change the destination
> >> once set?
> >
> > Destination is only one, so you just need to write phase or phase_roc, if you want
> > to target phase then. Does that not sound intuitive?
>
> I was thinking about if you needed to change the configuration.
> If you set it to phase, then want to change it to frequency, how
> could you do that if 0 is a valid value for phase?
>
> Also how could you know which is selected by reading back the
> values if 0 is a valid value?
This is where Jonathan raised some concerns, so it is a good oportunity for you
to provide your inputs! Right now, I am returning -EBUSY on read of an attribute
where its destination is not selected. As pointed out, the destination selection
is happening when writting to the attribute. In the previous patch, Jonathan
suggested frequency_active, phase_active and scale_active to track mode priority,
and It could be leveraged here for DRG destination selection. I havent gone for
that because I was not willing to add that to all the channels given that it is
mostly used for debugging, so I added frequency_source, phase_source and
amplitude_source to debugfs instead.
Destination selection for RAM mode is firmware based at this point.
Destination selection for Parallel mode is still not clear... could use
those *_active attributes or separate channels.
> >
> > Zero is a valid value to be written.
> >
> >>
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply
* Re: [RFC PATCH 4/5] mm: swap: fall back to order-0 after large swapin races
From: Kairui Song @ 2026-05-11 14:59 UTC (permalink / raw)
To: fujunjie, David Hildenbrand (Arm)
Cc: Andrew Morton, Chris Li, Johannes Weiner, Nhat Pham, Yosry Ahmed,
linux-mm, linux-kernel, linux-doc, Jonathan Corbet, Ryan Roberts,
Barry Song, Baolin Wang, Chengming Zhou, Baoquan He,
Lorenzo Stoakes
In-Reply-To: <24edd9d6-99f2-4d3d-83eb-69b406f4a9a0@kernel.org>
On Mon, May 11, 2026 at 9:14 PM David Hildenbrand (Arm)
<david@kernel.org> wrote:
>
> On 5/8/26 22:20, fujunjie wrote:
> > swapin_folio() documents that a large folio insertion race returns NULL
> > so the caller can fall back to order-0 swapin. do_swap_page() currently
> > turns that NULL into VM_FAULT_OOM if the PTE is unchanged, which is
> > harsher than necessary and gets in the way of rejecting large folio
> > ranges for backend reasons.
> >
> > Move the synchronous swapin sequence into a helper and retry with an
> > order-0 folio when a large folio cannot be inserted into the swap cache.
> > Count the event as an mTHP swapin fallback before dropping the failed
> > large allocation.
> >
> > Signed-off-by: fujunjie <fujunjie1@qq.com>
> > ---
> > mm/memory.c | 50 +++++++++++++++++++++++++++++++++++++++-----------
> > 1 file changed, 39 insertions(+), 11 deletions(-)
> >
> > diff --git a/mm/memory.c b/mm/memory.c
> > index ea6568571131..84e3b77b8293 100644
> > --- a/mm/memory.c
> > +++ b/mm/memory.c
> > @@ -4757,6 +4757,44 @@ static struct folio *alloc_swap_folio(struct vm_fault *vmf)
> > }
> > #endif /* CONFIG_TRANSPARENT_HUGEPAGE */
> >
> > +static struct folio *swapin_synchronous_folio(swp_entry_t entry,
> > + struct vm_fault *vmf)
> > +{
> > + struct folio *swapcache, *folio;
> > + bool large;
> > + int order;
> > +
> > + folio = alloc_swap_folio(vmf);
> > + if (!folio)
> > + return NULL;
> > +
> > + large = folio_test_large(folio);
> > + order = folio_order(folio);
> > +
> > + /*
> > + * folio is charged, so swapin can only fail due to raced swapin and
> > + * return NULL.
> > + */
> > + swapcache = swapin_folio(entry, folio);
> > + if (swapcache == folio)
> > + return folio;
> > +
> > + if (!swapcache && large)
> > + count_mthp_stat(order, MTHP_STAT_SWPIN_FALLBACK);
> > + folio_put(folio);
> > + if (swapcache || !large)
> > + return swapcache;
> > +
> > + folio = __alloc_swap_folio(vmf);
> > + if (!folio)
> > + return NULL;
> > +
> > + swapcache = swapin_folio(entry, folio);
> > + if (swapcache != folio)
> > + folio_put(folio);
> > + return swapcache;
> > +}
> > +
> > /* Sanity check that a folio is fully exclusive */
> > static void check_swap_exclusive(struct folio *folio, swp_entry_t entry,
> > unsigned int nr_pages)
> > @@ -4860,17 +4898,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
> > swap_update_readahead(folio, vma, vmf->address);
> > if (!folio) {
> > if (data_race(si->flags & SWP_SYNCHRONOUS_IO)) {
> > - folio = alloc_swap_folio(vmf);
> > - if (folio) {
> > - /*
> > - * folio is charged, so swapin can only fail due
> > - * to raced swapin and return NULL.
> > - */
> > - swapcache = swapin_folio(entry, folio);
> > - if (swapcache != folio)
> > - folio_put(folio);
> > - folio = swapcache;
> > - }
> > + folio = swapin_synchronous_folio(entry, vmf);
> > } else {
> > folio = swapin_readahead(entry, GFP_HIGHUSER_MOVABLE, vmf);
> > }
>
> There are some upcoming changes with:
>
> https://lore.kernel.org/r/20260421-swap-table-p4-v3-5-2f23759a76bc@tencent.com
>
>
> All the of that logic you have in swapin_synchronous_folio() should ideally not
> go into memory.c, but into some swap specific code.
>
> But
>
> https://lore.kernel.org/r/20260421-swap-table-p4-v3-0-2f23759a76bc@tencent.com
Thanks for mentioning this!
I think Junjie's change fits better after that change indeed. And I
checked the code, it should fits easily too.
It's already strange enough that THP swapin is bundled with
synchronous swapin, we better not make it more divergent here, and add
more bits into memory.c.
And this commit will limit it to anon, no shmem, which is another
strange detail. Or we'll have to repeat everything and copy these code
to shmem.c...
Once all swap-ins uses basically the same path as in that series, all
swap-ins will be able to have similar THP and zswap THP support too.
^ permalink raw reply
* Re: [PATCH v2] killswitch: add per-function short-circuit mitigation primitive
From: Breno Leitao @ 2026-05-11 14:59 UTC (permalink / raw)
To: Sasha Levin
Cc: corbet, akpm, skhan, linux-doc, linux-kernel, linux-kselftest,
gregkh
In-Reply-To: <agHcc4s-xj83dzty@laps>
On Mon, May 11, 2026 at 09:41:07AM -0400, Sasha Levin wrote:
> On Mon, May 11, 2026 at 06:14:27AM -0700, Breno Leitao wrote:
> > helo Sasha,
> >
> > First of all, Thanks for this feature, this is useful to me, and I am
> > interested in it. Feel free to copy me and I can test the next revisions
> >
> > On Fri, May 08, 2026 at 03:57:48PM -0400, Sasha Levin wrote:
> >
> > > +config KILLSWITCH
> > > + bool "Killswitch: short-circuit a kernel function as a CVE mitigation"
> > > + depends on SECURITYFS
> > > + depends on KPROBES && HAVE_KPROBES_ON_FTRACE
> > > + depends on HAVE_FUNCTION_ERROR_INJECTION
> > > + select FUNCTION_ERROR_INJECTION
> > > + help
> > > + Provide an admin-facing mechanism to make a chosen kernel function
> > > + return a fixed value without executing its body, as a temporary
> > > + mitigation for a security bug before a real fix is available.
> > > +
> > > + Operators write "engage <symbol> <retval> [reason]" to
> >
> > Should [reason] be shown at "engaged" ? I was expecting it, and in fact find it
> > very useful, but I don't see it.
> >
> > # echo "engage __x64_sys_getuid 12 CVE-2026-99999-INCIDENT-4242" > /sys/kernel/security/killswitch/control
> > # cat /sys/kernel/security/killswitch/engaged
> > __x64_sys_getuid retval=12 hits=12
>
> This was a woopsie on my end: originally I planned to have a reason field, but
> then decided to drop it to keep the patch simple. However, I forgot to fix up
> the kconfig help text :(
>
> If you think it'll be useful, I'm happy to add it back.
I see the value in having a reason field, but at the same time this is
just a small detail in the grand scheme here. That said, starting with
a simpler implementation and adding it later—once the feature sees
production use seems like a reasonable approach.
Thanks,
--breno
^ permalink raw reply
* Re: [PATCH v7 00/20] ARM64 PMU Partitioning
From: James Clark @ 2026-05-11 14:57 UTC (permalink / raw)
To: Colton Lewis
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, linux-doc,
linux-kernel, linux-arm-kernel, kvmarm, linux-perf-users,
linux-kselftest
In-Reply-To: <20260504211813.1804997-1-coltonlewis@google.com>
On 04/05/2026 10:17 pm, Colton Lewis wrote:
> This series creates a new PMU scheme on ARM, a partitioned PMU that
> allows reserving a subset of counters for more direct guest access,
> significantly reducing overhead. More details, including performance
> benchmarks, can be read in the v1 cover letter linked below.
>
> An overview of what this series accomplishes was presented at KVM
> Forum 2025. Slides [1] and video [2] are linked below.
>
> After a few false starts, meeting with Will Deacon and Mark Rutland to
> discuss implementation ideas, and a few more false starts, I finally
> have an implementation of dynamic counter reservation that works
> without disrupting host perf too much. Now the host only loses access
> to the guest counters when a vCPU resides on the CPU.
>
> The key was creating perf_pmu_resched_update, which behaves exactly
> like perf_pmu_resched except it takes a callback to call in between
> when the perf events are scheduled out and when they are scheduled
> back in. That allows us to update the PMU's available counters when we
> know they are not currently in use without needing to expose private
> perf core functions and triple check they are not being called in a
> way that violates existing assumptions.
>
> Because this introduces a possibility of perf reschedule during vCPU
> load, I've optimized to only do that operation if there are host
> events occupying the intended guest counters at the time of the load.
>
> The kernel command line parameter for the driver still exists, but now
> only defines an upper limit of counters the guest might use rather
> than taking those counters from the host permanently.
>
> v7:
>
> * Implement dynamic counter reservation as described above. One side
> effect is the PMUv3 driver now needs much fewer changes to enforce
> the boundary.
>
> * Move register accesses out of fast path for non-FGT hardware. The
> performance impact was negligible and this moves bloat out of the
> fast path and allows a more reliable design with more code sharing.
>
> * Make PMCCNTR a special case in the context swap again because trying
> to access it with PMXEVCNTR is undefined.
>
> * Fix a bug where kvm_pmu_guest_counter_mask was using & instead of |.
>
> * Re-expose the dedicated instruction counter to the host since it was
> decided the guest will not own it.
>
> * Change the global armv8pmu_reserved_host_counters to
> armv8pmu_is_partitoned because it was only used in boolean checks.
>
> * Fix typo in vcpu attribute commit so the spelling of the flag in the
> commit message matches the code.
>
> * Rebase to v7.0-rc7
>
> v6:
> https://lore.kernel.org/kvmarm/20260209221414.2169465-1-coltonlewis@google.com/
>
> v5:
> https://lore.kernel.org/kvmarm/20251209205121.1871534-1-coltonlewis@google.com/
>
> v4:
> https://lore.kernel.org/kvmarm/20250714225917.1396543-1-coltonlewis@google.com/
>
> v3:
> https://lore.kernel.org/kvm/20250626200459.1153955-1-coltonlewis@google.com/
>
> v2:
> https://lore.kernel.org/kvm/20250620221326.1261128-1-coltonlewis@google.com/
>
> v1:
> https://lore.kernel.org/kvm/20250602192702.2125115-1-coltonlewis@google.com/
>
> [1] https://gitlab.com/qemu-project/kvm-forum/-/raw/main/_attachments/2025/Optimizing__itvHkhc.pdf
> [2] https://www.youtube.com/watch?v=YRzZ8jMIA6M&list=PLW3ep1uCIRfxwmllXTOA2txfDWN6vUOHp&index=9
>
> Colton Lewis (19):
> arm64: cpufeature: Add cpucap for HPMN0
> KVM: arm64: Reorganize PMU functions
> perf: arm_pmuv3: Generalize counter bitmasks
> perf: arm_pmuv3: Check cntr_mask before using pmccntr
> perf: arm_pmuv3: Add method to partition the PMU
> KVM: arm64: Set up FGT for Partitioned PMU
> KVM: arm64: Add Partitioned PMU register trap handlers
> KVM: arm64: Set up MDCR_EL2 to handle a Partitioned PMU
> KVM: arm64: Context swap Partitioned PMU guest registers
> KVM: arm64: Enforce PMU event filter at vcpu_load()
> perf: Add perf_pmu_resched_update()
> KVM: arm64: Apply dynamic guest counter reservations
> KVM: arm64: Implement lazy PMU context swaps
> perf: arm_pmuv3: Handle IRQs for Partitioned PMU guest counters
> KVM: arm64: Detect overflows for the Partitioned PMU
> KVM: arm64: Add vCPU device attr to partition the PMU
> KVM: selftests: Add find_bit to KVM library
> KVM: arm64: selftests: Add test case for Partitioned PMU
> KVM: arm64: selftests: Relax testing for exceptions when partitioned
>
> Marc Zyngier (1):
> KVM: arm64: Reorganize PMU includes
>
> arch/arm/include/asm/arm_pmuv3.h | 18 +
> arch/arm64/include/asm/arm_pmuv3.h | 12 +-
> arch/arm64/include/asm/kvm_host.h | 17 +-
> arch/arm64/include/asm/kvm_types.h | 6 +-
> arch/arm64/include/uapi/asm/kvm.h | 2 +
> arch/arm64/kernel/cpufeature.c | 8 +
> arch/arm64/kvm/Makefile | 2 +-
> arch/arm64/kvm/arm.c | 2 +
> arch/arm64/kvm/config.c | 41 +-
> arch/arm64/kvm/debug.c | 31 +-
> arch/arm64/kvm/pmu-direct.c | 494 ++++++++++++
> arch/arm64/kvm/pmu-emul.c | 674 +----------------
> arch/arm64/kvm/pmu.c | 701 ++++++++++++++++++
> arch/arm64/kvm/sys_regs.c | 250 ++++++-
> arch/arm64/tools/cpucaps | 1 +
> arch/arm64/tools/sysreg | 6 +-
> drivers/perf/arm_pmuv3.c | 111 ++-
> include/kvm/arm_pmu.h | 110 +++
> include/linux/perf/arm_pmu.h | 3 +
> include/linux/perf/arm_pmuv3.h | 14 +-
> include/linux/perf_event.h | 3 +
> kernel/events/core.c | 28 +-
> tools/testing/selftests/kvm/Makefile.kvm | 1 +
> .../selftests/kvm/arm64/vpmu_counter_access.c | 112 ++-
> tools/testing/selftests/kvm/lib/find_bit.c | 1 +
> 25 files changed, 1861 insertions(+), 787 deletions(-)
> create mode 100644 arch/arm64/kvm/pmu-direct.c
> create mode 100644 tools/testing/selftests/kvm/lib/find_bit.c
>
>
> base-commit: 591cd656a1bf5ea94a222af5ef2ee76df029c1d2
> --
> 2.54.0.545.g6539524ca2-goog
I tested it a bit and ran the kselftests and it all seems to be working
ok. Some of the critical sashiko comments look like they are worth
looking into though:
https://sashiko.dev/#/patchset/20260504211813.1804997-1-coltonlewis%40google.com
For example writing to PMCR_EL0.P from EL2 resets the host's counters,
even if it's KVM doing it after trapping a write from the guest.
^ permalink raw reply
* Re: [PATCH v7 06/20] perf: arm_pmuv3: Add method to partition the PMU
From: James Clark @ 2026-05-11 14:51 UTC (permalink / raw)
To: Colton Lewis
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, linux-doc,
linux-kernel, linux-arm-kernel, kvmarm, linux-perf-users,
linux-kselftest, kvm
In-Reply-To: <20260504211813.1804997-7-coltonlewis@google.com>
On 04/05/2026 10:17 pm, Colton Lewis wrote:
> For PMUv3, the register field MDCR_EL2.HPMN partitiones the PMU
> counters into two ranges where counters 0..HPMN-1 are accessible by
> EL1 and, if allowed, EL0 while counters HPMN..N are only accessible by
> EL2.
>
> Create a module parameter reserved_host_counters to reserve a number
> of counters for the host. Counters not reserved for the host may be
> used by a guest VM when the PMU is partitioned.
>
> Add the function armv8pmu_partition() to check the validity of the
> reservation and record a partition has happened and the maximum
> allowable value for HPMN.
>
> Due to the difficulty this feature would create for the driver running
> in nVHE mode, partitioning is only allowed in VHE mode. In order to
> support a partitioning on nVHE we'd need to explicitly disable guest
> counters on every exit and reset HPMN to place all counters in the
> first range.
>
> Signed-off-by: Colton Lewis <coltonlewis@google.com>
> ---
> arch/arm/include/asm/arm_pmuv3.h | 4 ++
> arch/arm64/include/asm/arm_pmuv3.h | 5 ++
> arch/arm64/kvm/Makefile | 2 +-
> arch/arm64/kvm/pmu-direct.c | 22 +++++++++
> drivers/perf/arm_pmuv3.c | 77 ++++++++++++++++++++++++++++--
> include/kvm/arm_pmu.h | 8 ++++
> include/linux/perf/arm_pmu.h | 2 +
> 7 files changed, 115 insertions(+), 5 deletions(-)
> create mode 100644 arch/arm64/kvm/pmu-direct.c
>
> diff --git a/arch/arm/include/asm/arm_pmuv3.h b/arch/arm/include/asm/arm_pmuv3.h
> index 2ec0e5e83fc98..154503f054886 100644
> --- a/arch/arm/include/asm/arm_pmuv3.h
> +++ b/arch/arm/include/asm/arm_pmuv3.h
> @@ -221,6 +221,10 @@ static inline bool kvm_pmu_counter_deferred(struct perf_event_attr *attr)
> return false;
> }
>
> +static inline bool has_host_pmu_partition_support(void)
> +{
> + return false;
> +}
> static inline bool kvm_set_pmuserenr(u64 val)
> {
> return false;
> diff --git a/arch/arm64/include/asm/arm_pmuv3.h b/arch/arm64/include/asm/arm_pmuv3.h
> index cf2b2212e00a2..27c4d6d47da31 100644
> --- a/arch/arm64/include/asm/arm_pmuv3.h
> +++ b/arch/arm64/include/asm/arm_pmuv3.h
> @@ -171,6 +171,11 @@ static inline bool pmuv3_implemented(int pmuver)
> pmuver == ID_AA64DFR0_EL1_PMUVer_NI);
> }
>
> +static inline bool is_pmuv3p1(int pmuver)
> +{
> + return pmuver >= ID_AA64DFR0_EL1_PMUVer_V3P1;
> +}
> +
> static inline bool is_pmuv3p4(int pmuver)
> {
> return pmuver >= ID_AA64DFR0_EL1_PMUVer_V3P4;
> diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile
> index 3ebc0570345cc..baf0f296c0e53 100644
> --- a/arch/arm64/kvm/Makefile
> +++ b/arch/arm64/kvm/Makefile
> @@ -26,7 +26,7 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \
> vgic/vgic-its.o vgic/vgic-debug.o vgic/vgic-v3-nested.o \
> vgic/vgic-v5.o
>
> -kvm-$(CONFIG_HW_PERF_EVENTS) += pmu-emul.o pmu.o
> +kvm-$(CONFIG_HW_PERF_EVENTS) += pmu-emul.o pmu-direct.o pmu.o
> kvm-$(CONFIG_ARM64_PTR_AUTH) += pauth.o
> kvm-$(CONFIG_PTDUMP_STAGE2_DEBUGFS) += ptdump.o
>
> diff --git a/arch/arm64/kvm/pmu-direct.c b/arch/arm64/kvm/pmu-direct.c
> new file mode 100644
> index 0000000000000..74e40e4915416
> --- /dev/null
> +++ b/arch/arm64/kvm/pmu-direct.c
> @@ -0,0 +1,22 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Google LLC
> + * Author: Colton Lewis <coltonlewis@google.com>
> + */
> +
> +#include <linux/kvm_host.h>
> +
> +#include <asm/arm_pmuv3.h>
> +
> +/**
> + * has_host_pmu_partition_support() - Determine if partitioning is possible
> + *
> + * Partitioning is only supported in VHE mode with PMUv3
> + *
> + * Return: True if partitioning is possible, false otherwise
> + */
> +bool has_host_pmu_partition_support(void)
> +{
> + return has_vhe() &&
> + system_supports_pmuv3();
> +}
> diff --git a/drivers/perf/arm_pmuv3.c b/drivers/perf/arm_pmuv3.c
> index 7ff3139dda893..6e447227d801f 100644
> --- a/drivers/perf/arm_pmuv3.c
> +++ b/drivers/perf/arm_pmuv3.c
> @@ -42,6 +42,13 @@
> #define ARMV8_THUNDER_PERFCTR_L1I_CACHE_PREF_ACCESS 0xEC
> #define ARMV8_THUNDER_PERFCTR_L1I_CACHE_PREF_MISS 0xED
>
> +static int reserved_host_counters __read_mostly = -1;
> +bool armv8pmu_is_partitioned;
> +
> +module_param(reserved_host_counters, int, 0);
> +MODULE_PARM_DESC(reserved_host_counters,
> + "PMU Partition: -1 = No partition; +N = Reserve N counters for the host");
> +
> /*
> * ARMv8 Architectural defined events, not all of these may
> * be supported on any given implementation. Unsupported events will
> @@ -532,6 +539,11 @@ static void armv8pmu_pmcr_write(u64 val)
> write_pmcr(val);
> }
>
> +static u64 armv8pmu_pmcr_n_read(void)
> +{
> + return FIELD_GET(ARMV8_PMU_PMCR_N, armv8pmu_pmcr_read());
> +}
> +
> static int armv8pmu_has_overflowed(u64 pmovsr)
> {
> return !!(pmovsr & ARMV8_PMU_CNT_MASK_ALL);
> @@ -1312,6 +1324,54 @@ struct armv8pmu_probe_info {
> bool present;
> };
>
> +/**
> + * armv8pmu_reservation_is_valid() - Determine if reservation is allowed
> + * @host_counters: Number of host counters to reserve
> + *
> + * Determine if the number of host counters in the argument is an
> + * allowed reservation, 0 to NR_COUNTERS inclusive.
> + *
> + * Return: True if reservation allowed, false otherwise
> + */
> +static bool armv8pmu_reservation_is_valid(int host_counters)
> +{
> + return host_counters >= 0 &&
> + host_counters <= armv8pmu_pmcr_n_read();
> +}
> +
> +/**
> + * armv8pmu_partition() - Partition the PMU
> + * @pmu: Pointer to pmu being partitioned
> + * @host_counters: Number of host counters to reserve
> + *
> + * Partition the given PMU by taking a number of host counters to
> + * reserve and, if it is a valid reservation, recording the
> + * corresponding HPMN value in the max_guest_counters field of the PMU and
> + * clearing the guest-reserved counters from the counter mask.
> + *
> + * Return: 0 on success, -ERROR otherwise
> + */
> +static int armv8pmu_partition(struct arm_pmu *pmu, int host_counters)
> +{
> + u8 nr_counters;
> + u8 hpmn;
> +
> + if (!armv8pmu_reservation_is_valid(host_counters)) {
> + pr_err("PMU partition reservation of %d host counters is not valid", host_counters);
> + return -EINVAL;
> + }
> +
> + nr_counters = armv8pmu_pmcr_n_read();
> + hpmn = nr_counters - host_counters;
> +
> + pmu->max_guest_counters = hpmn;
> + armv8pmu_is_partitioned = true;
> +
> + pr_info("Partitioned PMU with %d host counters -> %u guest counters", host_counters, hpmn);
> +
> + return 0;
> +}
> +
> static void __armv8pmu_probe_pmu(void *info)
> {
> struct armv8pmu_probe_info *probe = info;
> @@ -1326,17 +1386,26 @@ static void __armv8pmu_probe_pmu(void *info)
>
> cpu_pmu->pmuver = pmuver;
> probe->present = true;
> + cpu_pmu->max_guest_counters = -1;
>
> /* Read the nb of CNTx counters supported from PMNC */
> - bitmap_set(cpu_pmu->cntr_mask,
> - 0, FIELD_GET(ARMV8_PMU_PMCR_N, armv8pmu_pmcr_read()));
> + bitmap_set(cpu_pmu->hw_cntr_mask, 0, armv8pmu_pmcr_n_read());
>
> /* Add the CPU cycles counter */
> - set_bit(ARMV8_PMU_CYCLE_IDX, cpu_pmu->cntr_mask);
> + set_bit(ARMV8_PMU_CYCLE_IDX, cpu_pmu->hw_cntr_mask);
>
> /* Add the CPU instructions counter */
> if (pmuv3_has_icntr())
> - set_bit(ARMV8_PMU_INSTR_IDX, cpu_pmu->cntr_mask);
> + set_bit(ARMV8_PMU_INSTR_IDX, cpu_pmu->hw_cntr_mask);
> +
> + bitmap_copy(cpu_pmu->cntr_mask, cpu_pmu->hw_cntr_mask, ARMPMU_MAX_HWEVENTS);
> +
> + if (reserved_host_counters >= 0) {
> + if (has_host_pmu_partition_support())
> + armv8pmu_partition(cpu_pmu, reserved_host_counters);
> + else
> + pr_err("PMU partition is not supported");
> + }
>
> pmceid[0] = pmceid_raw[0] = read_pmceid0();
> pmceid[1] = pmceid_raw[1] = read_pmceid1();
> diff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h
> index 24a471cf59d56..95f404cdcb2df 100644
> --- a/include/kvm/arm_pmu.h
> +++ b/include/kvm/arm_pmu.h
> @@ -47,7 +47,10 @@ struct arm_pmu_entry {
> struct arm_pmu *arm_pmu;
> };
>
> +extern bool armv8pmu_is_partitioned;
> +
> bool kvm_supports_guest_pmuv3(void);
> +bool has_host_pmu_partition_support(void);
> #define kvm_arm_pmu_irq_initialized(v) ((v)->arch.pmu.irq_num >= VGIC_NR_SGIS)
> u64 kvm_pmu_get_counter_value(struct kvm_vcpu *vcpu, u64 select_idx);
> void kvm_pmu_set_counter_value(struct kvm_vcpu *vcpu, u64 select_idx, u64 val);
> @@ -117,6 +120,11 @@ static inline bool kvm_supports_guest_pmuv3(void)
> return false;
> }
>
> +static inline bool has_host_pmu_partition_support(void)
> +{
> + return false;
> +}
> +
> #define kvm_arm_pmu_irq_initialized(v) (false)
> static inline u64 kvm_pmu_get_counter_value(struct kvm_vcpu *vcpu,
> u64 select_idx)
> diff --git a/include/linux/perf/arm_pmu.h b/include/linux/perf/arm_pmu.h
> index 52b37f7bdbf9e..f7b000bb3eca8 100644
> --- a/include/linux/perf/arm_pmu.h
> +++ b/include/linux/perf/arm_pmu.h
> @@ -109,6 +109,7 @@ struct arm_pmu {
> */
> int (*map_pmuv3_event)(unsigned int eventsel);
> DECLARE_BITMAP(cntr_mask, ARMPMU_MAX_HWEVENTS);
> + DECLARE_BITMAP(hw_cntr_mask, ARMPMU_MAX_HWEVENTS);
I think this needs a comment or a clearer name. Both cntr_mask and
hw_cntr_mask are used in KVM and the PMU driver and it's not immediately
obvious what the difference is.
^ permalink raw reply
* Re: [PATCH v7 10/20] KVM: arm64: Context swap Partitioned PMU guest registers
From: James Clark @ 2026-05-11 14:49 UTC (permalink / raw)
To: Colton Lewis
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, linux-doc,
linux-kernel, linux-arm-kernel, kvmarm, linux-perf-users,
linux-kselftest, kvm
In-Reply-To: <20260504211813.1804997-11-coltonlewis@google.com>
On 04/05/2026 10:18 pm, Colton Lewis wrote:
> Save and restore newly untrapped registers that can be directly
> accessed by the guest when the PMU is partitioned.
>
> * PMEVCNTRn_EL0
> * PMCCNTR_EL0
> * PMSELR_EL0
> * PMCR_EL0
> * PMCNTEN_EL0
> * PMINTEN_EL1
>
> If we know we are not partitioned (that is, using the emulated vPMU),
> then return immediately. A later patch will make this lazy so the
> context swaps don't happen unless the guest has accessed the PMU.
>
> PMEVTYPER is handled in a following patch since we must apply the KVM
> event filter before writing values to hardware.
>
> PMOVS guest counters are cleared to avoid the possibility of
> generating spurious interrupts when PMINTEN is written. This is fine
> because the virtual register for PMOVS is always the canonical value.
>
> Signed-off-by: Colton Lewis <coltonlewis@google.com>
> ---
> arch/arm/include/asm/arm_pmuv3.h | 4 +
> arch/arm64/kvm/arm.c | 2 +
> arch/arm64/kvm/pmu-direct.c | 169 +++++++++++++++++++++++++++++++
> include/kvm/arm_pmu.h | 16 +++
> 4 files changed, 191 insertions(+)
>
> diff --git a/arch/arm/include/asm/arm_pmuv3.h b/arch/arm/include/asm/arm_pmuv3.h
> index 42d62aa48d0a6..eebc89bdab7a1 100644
> --- a/arch/arm/include/asm/arm_pmuv3.h
> +++ b/arch/arm/include/asm/arm_pmuv3.h
> @@ -235,6 +235,10 @@ static inline bool kvm_pmu_is_partitioned(struct arm_pmu *pmu)
> {
> return false;
> }
> +static inline u64 kvm_pmu_host_counter_mask(struct arm_pmu *pmu)
> +{
> + return ~0;
> +}
>
> /* PMU Version in DFR Register */
> #define ARMV8_PMU_DFR_VER_NI 0
> diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> index 410ffd41fd73a..a942f2bc13fc4 100644
> --- a/arch/arm64/kvm/arm.c
> +++ b/arch/arm64/kvm/arm.c
> @@ -680,6 +680,7 @@ void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
> kvm_vcpu_load_vhe(vcpu);
> kvm_arch_vcpu_load_fp(vcpu);
> kvm_vcpu_pmu_restore_guest(vcpu);
> + kvm_pmu_load(vcpu);
> if (kvm_arm_is_pvtime_enabled(&vcpu->arch))
> kvm_make_request(KVM_REQ_RECORD_STEAL, vcpu);
>
> @@ -721,6 +722,7 @@ void kvm_arch_vcpu_put(struct kvm_vcpu *vcpu)
> kvm_timer_vcpu_put(vcpu);
> kvm_vgic_put(vcpu);
> kvm_vcpu_pmu_restore_host(vcpu);
> + kvm_pmu_put(vcpu);
> if (vcpu_has_nv(vcpu))
> kvm_vcpu_put_hw_mmu(vcpu);
> kvm_arm_vmid_clear_active();
> diff --git a/arch/arm64/kvm/pmu-direct.c b/arch/arm64/kvm/pmu-direct.c
> index 63ac72910e4b5..360d022d918d5 100644
> --- a/arch/arm64/kvm/pmu-direct.c
> +++ b/arch/arm64/kvm/pmu-direct.c
> @@ -9,6 +9,7 @@
> #include <linux/perf/arm_pmuv3.h>
>
> #include <asm/arm_pmuv3.h>
> +#include <asm/kvm_emulate.h>
>
> /**
> * has_host_pmu_partition_support() - Determine if partitioning is possible
> @@ -98,3 +99,171 @@ u8 kvm_pmu_hpmn(struct kvm_vcpu *vcpu)
>
> return *host_data_ptr(nr_event_counters);
> }
> +
> +/**
> + * kvm_pmu_host_counter_mask() - Compute bitmask of host-reserved counters
> + * @pmu: Pointer to arm_pmu struct
> + *
> + * Compute the bitmask that selects the host-reserved counters in the
> + * {PMCNTEN,PMINTEN,PMOVS}{SET,CLR} registers. These are the counters
> + * in HPMN..N
> + *
> + * Return: Bitmask
> + */
> +u64 kvm_pmu_host_counter_mask(struct arm_pmu *pmu)
> +{
> + u8 nr_counters = *host_data_ptr(nr_event_counters);
> +
> + if (kvm_pmu_is_partitioned(pmu))
> + return GENMASK(nr_counters - 1, pmu->max_guest_counters);
> +
> + return ARMV8_PMU_CNT_MASK_ALL;
> +}
> +
> +/**
> + * kvm_pmu_guest_counter_mask() - Compute bitmask of guest-reserved counters
> + * @pmu: Pointer to arm_pmu struct
> + *
> + * Compute the bitmask that selects the guest-reserved counters in the
> + * {PMCNTEN,PMINTEN,PMOVS}{SET,CLR} registers. These are the counters
> + * in 0..HPMN and the cycle and instruction counters.
> + *
> + * Return: Bitmask
> + */
> +u64 kvm_pmu_guest_counter_mask(struct arm_pmu *pmu)
> +{
> + if (kvm_pmu_is_partitioned(pmu))
> + return ARMV8_PMU_CNT_MASK_C | GENMASK(pmu->max_guest_counters - 1, 0);
> +
> + return 0;
> +}
Minor nit: slightly inconsistent use of types. Returns a u64 but doesn't
use GENMASK_ULL and is also usually saved into a long when it's called.
^ permalink raw reply
* Re: [PATCH v7 13/20] KVM: arm64: Apply dynamic guest counter reservations
From: James Clark @ 2026-05-11 14:47 UTC (permalink / raw)
To: Colton Lewis
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, linux-doc,
linux-kernel, linux-arm-kernel, kvmarm, linux-perf-users,
linux-kselftest, kvm
In-Reply-To: <20260504211813.1804997-14-coltonlewis@google.com>
On 04/05/2026 10:18 pm, Colton Lewis wrote:
> Apply dynamic guest counter reservations by checking if the requested
> guest mask collides with any events the host has scheduled and calling
> pmu_perf_resched_update() with a hook that updates the mask of
> available counters in between schedule out and schedule in.
>
> Signed-off-by: Colton Lewis <coltonlewis@google.com>
> ---
> arch/arm64/kvm/pmu-direct.c | 69 ++++++++++++++++++++++++++++++++++++
> include/linux/perf/arm_pmu.h | 1 +
> 2 files changed, 70 insertions(+)
>
> diff --git a/arch/arm64/kvm/pmu-direct.c b/arch/arm64/kvm/pmu-direct.c
> index 2252d3b905db9..14cc419dbafad 100644
> --- a/arch/arm64/kvm/pmu-direct.c
> +++ b/arch/arm64/kvm/pmu-direct.c
> @@ -100,6 +100,73 @@ u8 kvm_pmu_hpmn(struct kvm_vcpu *vcpu)
> return *host_data_ptr(nr_event_counters);
> }
>
> +/* Callback to update counter mask between perf scheduling */
> +static void kvm_pmu_update_mask(struct pmu *pmu, void *data)
> +{
> + struct arm_pmu *arm_pmu = to_arm_pmu(pmu);
> + unsigned long *new_mask = data;
> +
> + bitmap_copy(arm_pmu->cntr_mask, new_mask, ARMPMU_MAX_HWEVENTS);
> +}
> +
> +/**
> + * kvm_pmu_set_guest_counters() - Handle dynamic counter reservations
> + * @cpu_pmu: struct arm_pmu to potentially modify
> + * @guest_mask: new guest mask for the pmu
> + *
> + * Check if guest counters will interfere with current host events and
> + * call into perf_pmu_resched_update if a reschedule is required.
> + */
> +static void kvm_pmu_set_guest_counters(struct arm_pmu *cpu_pmu, u64 guest_mask)
> +{
> + struct pmu_hw_events *cpuc = this_cpu_ptr(cpu_pmu->hw_events);
> + DECLARE_BITMAP(guest_bitmap, ARMPMU_MAX_HWEVENTS);
> + DECLARE_BITMAP(new_mask, ARMPMU_MAX_HWEVENTS);
> + bool need_resched = false;
> +
> + bitmap_from_arr64(guest_bitmap, &guest_mask, ARMPMU_MAX_HWEVENTS);
> + bitmap_copy(new_mask, cpu_pmu->hw_cntr_mask, ARMPMU_MAX_HWEVENTS);
> +
> + if (guest_mask) {
> + /* Subtract guest counters from available host mask */
> + bitmap_andnot(new_mask, new_mask, guest_bitmap, ARMPMU_MAX_HWEVENTS);
> +
> + /* Did we collide with an active host event? */
> + if (bitmap_intersects(cpuc->used_mask, guest_bitmap, ARMPMU_MAX_HWEVENTS)) {
> + int idx;
> +
> + need_resched = true;
> + cpuc->host_squeezed = true;
> +
> + /* Look for pinned events that are about to be preempted */
> + for_each_set_bit(idx, guest_bitmap, ARMPMU_MAX_HWEVENTS) {
> + if (test_bit(idx, cpuc->used_mask) && cpuc->events[idx] &&
> + cpuc->events[idx]->attr.pinned) {
> + pr_warn_ratelimited("perf: Pinned host event squeezed out by KVM guest PMU partition\n");
Hi Colton,
I get "perf: Pinned host event squeezed out by KVM guest PMU partition"
even with arm_pmuv3.reserved_host_counters=3 for example. I would have
expected any non zero value to stop the warning.
I think armv8pmu_get_single_idx() needs to be changed to allocate from
the high end host counters first. A more complicated option would be
checking to see if there are any non-pinned counters in the host
reserved half when a new pinned counter is opened, then swapping the
places of the new pinned and existing non-pinned counters so pinned
always prefer being put into the host half. But it's probably not worth
doing that.
James
> + break;
> + }
> + }
> + }
> + } else {
> + /*
> + * Restoring to hw_cntr_mask.
> + * Only resched if we previously squeezed an event.
> + */
> + if (cpuc->host_squeezed) {
> + need_resched = true;
> + cpuc->host_squeezed = false;
> + }
> + }
> +
> + if (need_resched) {
> + /* Collision: run full perf reschedule */
> + perf_pmu_resched_update(&cpu_pmu->pmu, kvm_pmu_update_mask, new_mask);
> + } else {
> + /* Host was never using guest counters anyway */
> + bitmap_copy(cpu_pmu->cntr_mask, new_mask, ARMPMU_MAX_HWEVENTS);
> + }
> +}
> +
> /**
> * kvm_pmu_host_counter_mask() - Compute bitmask of host-reserved counters
> * @pmu: Pointer to arm_pmu struct
> @@ -218,6 +285,7 @@ void kvm_pmu_load(struct kvm_vcpu *vcpu)
>
> pmu = vcpu->kvm->arch.arm_pmu;
> guest_counters = kvm_pmu_guest_counter_mask(pmu);
> + kvm_pmu_set_guest_counters(pmu, guest_counters);
> kvm_pmu_apply_event_filter(vcpu);
>
> for_each_set_bit(i, &guest_counters, ARMPMU_MAX_HWEVENTS) {
> @@ -319,5 +387,6 @@ void kvm_pmu_put(struct kvm_vcpu *vcpu)
> val = read_sysreg(pmintenset_el1);
> __vcpu_assign_sys_reg(vcpu, PMINTENSET_EL1, val & mask);
>
> + kvm_pmu_set_guest_counters(pmu, 0);
> preempt_enable();
> }
> diff --git a/include/linux/perf/arm_pmu.h b/include/linux/perf/arm_pmu.h
> index f7b000bb3eca8..63f88fec5e80f 100644
> --- a/include/linux/perf/arm_pmu.h
> +++ b/include/linux/perf/arm_pmu.h
> @@ -75,6 +75,7 @@ struct pmu_hw_events {
>
> /* Active events requesting branch records */
> unsigned int branch_users;
> + bool host_squeezed;
> };
>
> enum armpmu_attr_groups {
^ permalink raw reply
* Re: [PATCH RFC v4 10/10] docs: iio: add documentation for ad9910 driver
From: David Lechner @ 2026-05-11 14:46 UTC (permalink / raw)
To: Rodrigo Alencar, rodrigo.alencar, linux-iio, devicetree,
linux-kernel, linux-doc, linux-hardening
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan, Kees Cook,
Gustavo A. R. Silva
In-Reply-To: <zvulxrrvg4sf7m5pjfpfucg7yssgallfu6zi6mcyblu2qy24hn@wdzs7h77vkoz>
On 5/10/26 4:30 AM, Rodrigo Alencar wrote:
> On 26/05/09 06:42PM, David Lechner wrote:
>> On 5/8/26 12:00 PM, Rodrigo Alencar via B4 Relay wrote:
>>> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
>>>
>>> Add documentation for the AD9910 DDS IIO driver, which describes channels,
>>> DDS modes, attributes and ABI usage examples.
>
> ...
>
>>> + must be a power of 2.
>>> +
>>> + * - ``frequency_offset``
>>> + - Hz
>>> + - Base FTW to which scaled parallel data is added. Range :math:`[0, f_{SYSCLK}/2)`.
>>> +
>>> + * - ``phase_offset``
>>> + - rad
>>> + - Base phase for polar modulation. Lower 8 bits of POW register.
>>> + Range :math:`[0, 2\pi/256)`.
>>> +
>>> + * - ``scale_offset``
>>> + - fractional
>>> + - Base amplitude for polar modulation. Lower 6 bits of ASF register.
>>> + Range :math:`[0, 1/256)`.
>>> +
>>
>> I guess there was some discussion on these attributes. I see some of these in the
>> ad9832 driver in staging, but I'm guessing they are new ABI. It isn't clear to
>> me from the documentation here what they actually do though. I guess they are
>> just basic transformations on the input signal?
>
> Not sure how the ABI is not clear:
>
> For a channel that allows amplitude control through buffers, this
> represents the value for a base amplitude scale. The actual output
> amplitude scale is a result with the sum of this value.
>
> So yes, it is a basic transformation.
I didn't have time to read the ABI docs yet. For scale_offset though,
how is that different from the existing offset attribute?
>
>>
>> And a practical note, they should be "frequencyscale". I don't like that it is
>> harder to read, but it is easier for a machine to parse.
>
> Parsers like the ones in libiio is not having problems with that.
>
>>> +Usage examples
>>> +^^^^^^^^^^^^^^
>>> +
>>> +Set parallel port frequency modulation with a scale of 16 and a 50 MHz
>>> +offset:
>>> +
>>> +.. code-block:: bash
>>> +
>>> + echo 16 > /sys/bus/iio/devices/iio:device0/out_altvoltage113_frequency_scale
>>> + echo 50000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage113_frequency_offset
>>> +
>>> +Digital ramp generator (DRG)
>>> +----------------------------
>>> +
>>> +The DRG produces linear frequency, phase or amplitude sweeps using dedicated
>>> +hardware. It is controlled through three channels: a parent control channel
>>> +(``digital_ramp_generator``) and two child ramp channels
>>> +(``digital_ramp_up``, ``digital_ramp_down``). DRG destination is set when
>>> +ramp attributes are written, i.e. writing to ``frequency`` or ``frequency_roc``
>>> +sets the destination to frequency.
>>
>> Would it be better to say that the destination is set when the the
>> value is non-zero? Otherwise, how would one change the destination
>> once set?
>
> Destination is only one, so you just need to write phase or phase_roc, if you want
> to target phase then. Does that not sound intuitive?
I was thinking about if you needed to change the configuration.
If you set it to phase, then want to change it to frequency, how
could you do that if 0 is a valid value for phase?
Also how could you know which is selected by reading back the
values if 0 is a valid value?
>
> Zero is a valid value to be written.
>
>>
^ permalink raw reply
* Re: [PATCH v5 2/4] mm/memory-failure: add panic option for unrecoverable pages
From: Breno Leitao @ 2026-05-11 14:44 UTC (permalink / raw)
To: Lance Yang
Cc: david, linmiaohe, nao.horiguchi, akpm, corbet, skhan, ljs,
Liam.Howlett, vbabka, rppt, surenb, mhocko, shuah, linux-mm,
linux-kernel, linux-doc, linux-kselftest, kernel-team
In-Reply-To: <20260510144220.92522-1-lance.yang@linux.dev>
On Sun, May 10, 2026 at 10:42:20PM +0800, Lance Yang wrote:
>
> On Wed, May 06, 2026 at 09:18:12AM -0700, Breno Leitao wrote:
> >On Tue, Apr 28, 2026 at 11:07:21AM +0800, Lance Yang wrote:
> >>
> >> On Mon, Apr 27, 2026 at 05:49:28PM +0200, David Hildenbrand (Arm) wrote:
> >> >> + switch (type) {
> >> >> + case MF_MSG_KERNEL:
> >> >> + case MF_MSG_UNKNOWN:
> >> >> + return true;
> >> >> + case MF_MSG_KERNEL_HIGH_ORDER:
> >> >> + /*
> >> >> + * Rule out a concurrent buddy allocation: give the
> >> >> + * allocator a moment to finish prep_new_page() and
> >> >> + * re-check. A genuine high-order kernel tail page stays
> >> >> + * unowned; an in-flight allocation will have bumped the
> >> >> + * refcount, attached a mapping, or placed the page on
> >> >> + * an LRU by now.
> >> >> + */
> >> >> + p = pfn_to_online_page(pfn);
> >> >> + if (!p)
> >> >> + return true;
> >> >> + /*
> >> >> + * Yield so a concurrent allocator on another CPU can
> >> >> + * finish prep_new_page() and have its writes become
> >> >> + * visible before we resample the page state.
> >> >> + */
> >> >> + cpu_relax();
> >> >> + return page_count(p) == 0 &&
> >> >> + !PageLRU(p) &&
> >> >> + !page_mapped(p) &&
> >> >> + !page_folio(p)->mapping &&
> >> >> + !is_free_buddy_page(p);
> >> >
> >> >I don't get what you are doing here. The right way to check for a tail page is
> >> >not by checking the refcount.
> >> >
> >> >Further, you are not holding a folio reference? If so, calling
> >> >page_mapped/folio_mapped is shaky. On concurrent folio split you can trigger a
> >> >VM_WARN_ON_FOLIO().
> >> >
> >> >
> >> >Maybe folio_snapshot() is what you are looking for, if you are in fact not
> >> >holding a reference?
> >>
> >> Right! Maybe we should not try to make this decision in
> >> panic_on_unrecoverable_mf().
> >>
> >> By the time we get here, we only know the final MF_MSG_* type. The
> >> real reason why get_hwpoison_page() failed is already lost.
> >>
> >> Wonder if it would be better to split that earlier, around
> >> __get_unpoison_page()/get_any_page(). That code still knows why
> >> grabbing the page failed, either an unsupported kernel page or
> >> just a temporary race we cannot really trust :)
> >>
> >> Then the later panic logic can be simple: panic for the stable
> >> unsupported kernel page case, and not for the temporary race case.
> >>
> >> That would also avoid trying to guess MF_MSG_KERNEL_HIGH_ORDER here:)
> >
> >This is a very good feedback, and definitely what I wanted to do, but,
> >failed. Once we have the reason, we don't need this dance to guess the
> >reason.
> >
> >I've hacked a patch based on this approach. How does it sound?
>
> Yes. This direction makes sense to me, not an expert though :D
>
> I played with something similar (untested) on top of patch #01:
Thanks!
I'll prepare a new series addressing all the feedback from both
reviewers and AI analysis. I will resend soon and we can catch up
on the next revision,
Thanks for the review,
--breno
^ permalink raw reply
* Re: [PATCH] killswitch: add per-function short-circuit mitigation primitive
From: Michal Hocko @ 2026-05-11 14:25 UTC (permalink / raw)
To: Sasha Levin
Cc: Breno Leitao, Andrew Morton, corbet, skhan, linux-doc,
linux-kernel, linux-kselftest, gregkh
In-Reply-To: <agHgDgwu8H9Opzpl@laps>
On Mon 11-05-26 09:56:30, Sasha Levin wrote:
> On Mon, May 11, 2026 at 03:49:24PM +0200, Michal Hocko wrote:
> > On Mon 11-05-26 09:39:32, Sasha Levin wrote:
> > > On Mon, May 11, 2026 at 03:07:51PM +0200, Michal Hocko wrote:
> > > > On Mon 11-05-26 04:41:38, Breno Leitao wrote:
> > > > > On Fri, May 08, 2026 at 05:47:04PM -0400, Sasha Levin wrote:
> > > > > > On Fri, May 08, 2026 at 01:56:30PM -0700, Andrew Morton wrote:
> > > > > > > On Thu, 7 May 2026 03:05:45 -0400 Sasha Levin <sashal@kernel.org> wrote:
> > > > > > >
> > > > > > > > When a (security) issue goes public, fleets stay exposed until a patched kernel
> > > > > > > > is built, distributed, and rebooted into.
> > > > > > > >
> > > > > > > > For many such issues the simplest mitigation is to stop calling the buggy
> > > > > > > > function. Killswitch provides that. An admin writes:
> > > > > > > >
> > > > > > > > echo "engage af_alg_sendmsg -1" \
> > > > > > > > > /sys/kernel/security/killswitch/control
> > > > > > >
> > > > > > > It certainly sounds useful, but what would I know. How do we hunt down
> > > > > > > suitable operations people (aka "target audience") to find out how
> > > > > > > useful this is to them?
> > > > > >
> > > > > > I'm not entierly sure here... If folks have suggestions on folks to loop in,
> > > > > > that'll be great!
> > > > >
> > > > > I work with these issues at Meta, and this approach would address a real
> > > > > need we have.
> > >
> > > Thanks for the feedback!
> > >
> > > > > While livepatch could theoretically solve this problem, it's less suited
> > > > > for rapid mitigation for a couple of reasons:
> > > > >
> > > > > 1) Livepatch rollout is inherently slower due to the blast radius if a
> > > > > bug exists in the livepatch mechanism itself.
> > > > >
> > > > > 2) It's common to run hundreds of different kernel versions across a
> > > > > fleet. Since livepatch is kernel-specific, a single CVE suddenly
> > > > > requires building and deploying hundreds of individual livepatches—
> > > > > far less practical than a simple sysfs write.
> > > >
> > > > LP is certainly a more laborous solution. I guess this is quite clear.
> > > > It is also much safer option as it deals with all implementation details
> > > > like consistency. All that is not done for fun. I am really wondering
> > > > how admins are expected to a) know which kernel functions are ok/safe to
> > > > disable and b) when it is safe to do so without introducing unsafe
> > > > kernel state or introduce an outright bug that way.
> > >
> > > In a similar way to how they would know if a given livepatch is safe to apply -
> > > ideally it would be communicated by the vendor/distro/kernel team.
> >
> > You have missed my point. KLP takes an extra steps to make sure patching
> > a particular function is safe to modify or to put the change into the
> > effect.
>
> Safety checks like making sure the patched function is on the stack, or did you
> mean something else?
Yes, exactly what LP infrastructure already provides.
> > > "On Debian XX.YY, use the following command to mitigate CVE-AAAA-BBBB:
> > >
> > > echo "engage woops -1" > /sys/kernel/security/killswitch/control"
> > >
> > > > Thiking about this I can see how waiting for an official LP can be time
> > > > consuming and sometimes creating those is far from trivial. But would it
> > > > make sense to have automated LP creation tooling available that would
> > > > allow to return early from a function and relly on the existing
> > > > infrastructure to do the right thing?
> > >
> > > This would definitely help (and in light of how the last couple of weeks played
> > > out, the case for livepatches definitely increased), but not all
> > > vendors/distros provide livepatches.
> >
> > The point I've tried to make is that you (as an admin) shouldn't depend
> > on your vendor to provide you with an official LP just to disable a
> > certain function(ality). That is/should be a trivial case where the LP
> > should be ideally generated automagically if you have a tooling
> > available. I might be wrong and overlook some complexity here.
>
> Module signing is what stops that approach for me.
OK, so the actual constrain here is that you cannot load your own
modules. That was not really clear from your description. I assume you
cannot enroll your own key and sign?
--
Michal Hocko
SUSE Labs
^ permalink raw reply
* Re: [PATCH v10 15/30] KVM: arm64: Support SME control registers
From: Mark Brown @ 2026-05-11 14:17 UTC (permalink / raw)
To: Mark Rutland
Cc: Marc Zyngier, Joey Gouly, Catalin Marinas, Suzuki K Poulose,
Will Deacon, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Oliver Upton, Dave Martin, Fuad Tabba, Ben Horgan,
linux-arm-kernel, kvmarm, linux-kernel, kvm, linux-doc,
linux-kselftest, Peter Maydell, Eric Auger
In-Reply-To: <af4bWxiOogfPz_dp@J2N7QTR9R3>
[-- Attachment #1: Type: text/plain, Size: 3635 bytes --]
On Fri, May 08, 2026 at 06:20:27PM +0100, Mark Rutland wrote:
> > +static bool access_smcr_el2(struct kvm_vcpu *vcpu,
> > + struct sys_reg_params *p,
> > + const struct sys_reg_desc *r)
> > +{
> > + vq = SYS_FIELD_GET(SMCR_ELx, LEN, smcr) + 1;
> > + vq = min(vq, vcpu_sme_max_vq(vcpu));
> > + smcr &= ~SMCR_ELx_LEN_MASK;
> > + smcr |= SYS_FIELD_PREP(SMCR_ELx, LEN, vq - 1);
> I'm not sure this sanitization is correct or necessary, and the same
> concern applies to ZCR_ELx.LEN.
> AFAICT, none of the values for the SMCR_ELx.LEN and ZCR_ELx.LEN fields
> are reserved or unallocated. Thus all the bits of those fields should be
> stateful, and a read should observe the last value written, regardless
> of the effective value of the field.
...
> Either what we're doing is wrong, or the architcture requires a
> clarification to say that values corresponding to unimplmented vector
> lengths are reserved.
> If those bit are always stateful, the the logic to sanitize the LEN
> field shouldn't live here, and that will need to happen when consuming
> the effective value.
Your understanding of how these fields work matches mine, and writing
unimplemented values is part of the documented procedure for enumerating
the set of supported vector lengths.
As you note this is duplicated from the handling of ZCR_ELx, it's not
clear to me why the code does this but I figured there must be some good
reason for doing things this way that I just wasn't seeing and that it
was safer to fit in with the existing code. The handling for vector
lengths in general and especially with NV was quite unclear,
particularly prior to your fixes in 59419f10045b (KVM: arm64: Eagerly
switch ZCR_EL{1,2}).
The changelog for b3d29a823099 ("KVM: arm64: nv: Handle ZCR_EL2 traps")
which introduced this for ZCR_ELx has a mention of mapping the requested
VL but it's not entirely clear to me what it means by that. It does
mean that we can just load guest ZCR_EL2 and get the correct behaviour
for guest EL1 and EL0 when loading the guest state so perhaps that might
be all there is to it.
My expectation would have been to restrict the guest EL1/0 VL when we
load state into the registers as you allude to, something more like the
below (off the top of my head and completely untested, I'll pull this
into a proper patch later):
diff --git a/arch/arm64/kvm/hyp/include/hyp/switch.h b/arch/arm64/kvm/hyp/include/hyp/switch.h
index 98b2976837b1..ddf8c2246139 100644
--- a/arch/arm64/kvm/hyp/include/hyp/switch.h
+++ b/arch/arm64/kvm/hyp/include/hyp/switch.h
@@ -501,11 +501,11 @@ static inline void fpsimd_lazy_switch_to_guest(struct kvm_vcpu *vcpu)
return;
if (vcpu_has_sve(vcpu)) {
+ zcr_el2 = vcpu_sve_max_vq(vcpu) - 1;
+
/* A guest hypervisor may restrict the effective max VL. */
- if (is_nested_ctxt(vcpu))
- zcr_el2 = __vcpu_sys_reg(vcpu, ZCR_EL2);
- else
- zcr_el2 = vcpu_sve_max_vq(vcpu) - 1;
+ if (is_nested_ctxt(vcpu) && !is_hyp_ctxt(vcpu))
+ zcr_el2 = min(zcr_el2, __vcpu_sys_reg(vcpu, ZCR_EL2));
write_sysreg_el2(zcr_el2, SYS_ZCR);
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 148fc3400ea8..b48f41acff82 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -2874,9 +2874,7 @@ static bool access_zcr_el2(struct kvm_vcpu *vcpu,
return true;
}
- vq = SYS_FIELD_GET(ZCR_ELx, LEN, p->regval) + 1;
- vq = min(vq, vcpu_sve_max_vq(vcpu));
- __vcpu_assign_sys_reg(vcpu, ZCR_EL2, vq - 1);
+ __vcpu_assign_sys_reg(vcpu, ZCR_EL2, p->regval);
return true;
}
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply related
* Re: [PATCH] killswitch: add per-function short-circuit mitigation primitive
From: Sasha Levin @ 2026-05-11 13:56 UTC (permalink / raw)
To: Michal Hocko
Cc: Breno Leitao, Andrew Morton, corbet, skhan, linux-doc,
linux-kernel, linux-kselftest, gregkh
In-Reply-To: <agHeZPA3eHhJHIsQ@tiehlicka>
On Mon, May 11, 2026 at 03:49:24PM +0200, Michal Hocko wrote:
>On Mon 11-05-26 09:39:32, Sasha Levin wrote:
>> On Mon, May 11, 2026 at 03:07:51PM +0200, Michal Hocko wrote:
>> > On Mon 11-05-26 04:41:38, Breno Leitao wrote:
>> > > On Fri, May 08, 2026 at 05:47:04PM -0400, Sasha Levin wrote:
>> > > > On Fri, May 08, 2026 at 01:56:30PM -0700, Andrew Morton wrote:
>> > > > > On Thu, 7 May 2026 03:05:45 -0400 Sasha Levin <sashal@kernel.org> wrote:
>> > > > >
>> > > > > > When a (security) issue goes public, fleets stay exposed until a patched kernel
>> > > > > > is built, distributed, and rebooted into.
>> > > > > >
>> > > > > > For many such issues the simplest mitigation is to stop calling the buggy
>> > > > > > function. Killswitch provides that. An admin writes:
>> > > > > >
>> > > > > > echo "engage af_alg_sendmsg -1" \
>> > > > > > > /sys/kernel/security/killswitch/control
>> > > > >
>> > > > > It certainly sounds useful, but what would I know. How do we hunt down
>> > > > > suitable operations people (aka "target audience") to find out how
>> > > > > useful this is to them?
>> > > >
>> > > > I'm not entierly sure here... If folks have suggestions on folks to loop in,
>> > > > that'll be great!
>> > >
>> > > I work with these issues at Meta, and this approach would address a real
>> > > need we have.
>>
>> Thanks for the feedback!
>>
>> > > While livepatch could theoretically solve this problem, it's less suited
>> > > for rapid mitigation for a couple of reasons:
>> > >
>> > > 1) Livepatch rollout is inherently slower due to the blast radius if a
>> > > bug exists in the livepatch mechanism itself.
>> > >
>> > > 2) It's common to run hundreds of different kernel versions across a
>> > > fleet. Since livepatch is kernel-specific, a single CVE suddenly
>> > > requires building and deploying hundreds of individual livepatches—
>> > > far less practical than a simple sysfs write.
>> >
>> > LP is certainly a more laborous solution. I guess this is quite clear.
>> > It is also much safer option as it deals with all implementation details
>> > like consistency. All that is not done for fun. I am really wondering
>> > how admins are expected to a) know which kernel functions are ok/safe to
>> > disable and b) when it is safe to do so without introducing unsafe
>> > kernel state or introduce an outright bug that way.
>>
>> In a similar way to how they would know if a given livepatch is safe to apply -
>> ideally it would be communicated by the vendor/distro/kernel team.
>
>You have missed my point. KLP takes an extra steps to make sure patching
>a particular function is safe to modify or to put the change into the
>effect.
Safety checks like making sure the patched function is on the stack, or did you
mean something else?
>> "On Debian XX.YY, use the following command to mitigate CVE-AAAA-BBBB:
>>
>> echo "engage woops -1" > /sys/kernel/security/killswitch/control"
>>
>> > Thiking about this I can see how waiting for an official LP can be time
>> > consuming and sometimes creating those is far from trivial. But would it
>> > make sense to have automated LP creation tooling available that would
>> > allow to return early from a function and relly on the existing
>> > infrastructure to do the right thing?
>>
>> This would definitely help (and in light of how the last couple of weeks played
>> out, the case for livepatches definitely increased), but not all
>> vendors/distros provide livepatches.
>
>The point I've tried to make is that you (as an admin) shouldn't depend
>on your vendor to provide you with an official LP just to disable a
>certain function(ality). That is/should be a trivial case where the LP
>should be ideally generated automagically if you have a tooling
>available. I might be wrong and overlook some complexity here.
Module signing is what stops that approach for me.
--
Thanks,
Sasha
^ permalink raw reply
* [PATCH v2 4/4] cpufreq: Use policy->min/max init as QoS request
From: Pierre Gondois @ 2026-05-11 13:55 UTC (permalink / raw)
To: linux-kernel
Cc: Jie Zhan, Lifeng Zheng, Ionela Voinescu, Sumit Gupta,
Zhongqiu Han, Pierre Gondois, Rafael J. Wysocki, Viresh Kumar,
Jonathan Corbet, Shuah Khan, Huang Rui, Mario Limonciello,
Perry Yuan, K Prateek Nayak, Srinivas Pandruvada, Len Brown,
Saravana Kannan, linux-pm, linux-doc
In-Reply-To: <20260511135538.522653-1-pierre.gondois@arm.com>
Consider policy->min/max being set in the driver .init()
callback as a QoS request. Impacted driver are:
- gx-suspmod.c (min)
- cppc-cpufreq.c (min)
- longrun.c (min/max)
Update the documentation accordingly.
Signed-off-by: Pierre Gondois <pierre.gondois@arm.com>
---
Documentation/cpu-freq/cpu-drivers.rst | 10 ++++++++--
drivers/cpufreq/cpufreq.c | 12 ++++++++++--
2 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/Documentation/cpu-freq/cpu-drivers.rst b/Documentation/cpu-freq/cpu-drivers.rst
index c5635ac3de547..ab4f3c0f3a89b 100644
--- a/Documentation/cpu-freq/cpu-drivers.rst
+++ b/Documentation/cpu-freq/cpu-drivers.rst
@@ -114,8 +114,14 @@ Then, the driver must fill in the following values:
|policy->cur | The current operating frequency of |
| | this CPU (if appropriate) |
+-----------------------------------+--------------------------------------+
-|policy->min, | |
-|policy->max, | |
+|policy->min | If set by the driver in ->init(), |
+| | used as initial minimum frequency |
+| | QoS request. |
++-----------------------------------+--------------------------------------+
+|policy->max | If set by the driver in ->init(), |
+| | used as initial maximum frequency |
+| | QoS request. |
++-----------------------------------+--------------------------------------+
|policy->policy and, if necessary, | |
|policy->governor | must contain the "default policy" for|
| | this CPU. A few moments later, |
diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c
index 9e2d9d3fc5351..9a005367ed87b 100644
--- a/drivers/cpufreq/cpufreq.c
+++ b/drivers/cpufreq/cpufreq.c
@@ -1399,8 +1399,16 @@ static void cpufreq_policy_free(struct cpufreq_policy *policy)
static int cpufreq_policy_init_qos(struct cpufreq_policy *policy)
{
+ unsigned int min_freq, max_freq;
int ret;
+ /* Use policy->min/max set by the driver as QoS requests. */
+ min_freq = max(FREQ_QOS_MIN_DEFAULT_VALUE, policy->min);
+ if (policy->max)
+ max_freq = min(FREQ_QOS_MAX_DEFAULT_VALUE, policy->max);
+ else
+ max_freq = FREQ_QOS_MAX_DEFAULT_VALUE;
+
/*
* If the driver didn't set policy->min/max, set them as
* they are used to clamp frequency requests.
@@ -1418,12 +1426,12 @@ static int cpufreq_policy_init_qos(struct cpufreq_policy *policy)
}
ret = freq_qos_add_request(&policy->constraints, &policy->min_freq_req,
- FREQ_QOS_MIN, FREQ_QOS_MIN_DEFAULT_VALUE);
+ FREQ_QOS_MIN, min_freq);
if (ret < 0)
return ret;
ret = freq_qos_add_request(&policy->constraints, &policy->max_freq_req,
- FREQ_QOS_MAX, FREQ_QOS_MAX_DEFAULT_VALUE);
+ FREQ_QOS_MAX, max_freq);
if (ret < 0)
return ret;
--
2.43.0
^ permalink raw reply related
* [PATCH v2 3/4] cpufreq: Remove driver default policy->min/max init
From: Pierre Gondois @ 2026-05-11 13:55 UTC (permalink / raw)
To: linux-kernel
Cc: Jie Zhan, Lifeng Zheng, Ionela Voinescu, Sumit Gupta,
Zhongqiu Han, Pierre Gondois, Rafael J. Wysocki, Viresh Kumar,
Jonathan Corbet, Shuah Khan, Huang Rui, Mario Limonciello,
Perry Yuan, K Prateek Nayak, Srinivas Pandruvada, Len Brown,
Saravana Kannan, linux-pm, linux-doc
In-Reply-To: <20260511135538.522653-1-pierre.gondois@arm.com>
Prior to [1], drivers were setting policy->min/max and
the value was used as a QoS constraint. After that change,
the values were only temporarily used: cpufreq_set_policy()
ultimately overriding them through:
cpufreq_policy_online()
\-cpufreq_init_policy()
\-cpufreq_set_policy()
\-/* Set policy->min/max */
This patch reinstate the initial behaviour. This will allow
drivers to request min/max QoS frequencies if desired.
For instance, the cppc driver advertises a lowest non-linear
frequency, which should be used as a min QoS value.
To avoid having drivers setting policy->min/max to default
values which are considered as QoS values (i.e. the reason
why [1] was introduced), remove the initialization of
policy->min/max in .init() callbacks wherever the
policy->min/max values are identical to the
policy->cpuinfo.min/max_freq.
Indeed, the previous patch ("cpufreq: Set default
policy->min/max values for all drivers") makes this initialization
redundant.
The only drivers where these values are different are:
- gx-suspmod.c (min)
- cppc-cpufreq.c (min)
- longrun.c
[1]
commit 521223d8b3ec ("cpufreq: Fix initialization of min and
max frequency QoS requests")
Signed-off-by: Pierre Gondois <pierre.gondois@arm.com>
---
drivers/cpufreq/amd-pstate.c | 14 ++++++--------
drivers/cpufreq/cppc_cpufreq.c | 5 ++---
drivers/cpufreq/cpufreq-nforce2.c | 4 ++--
drivers/cpufreq/freq_table.c | 7 +++----
drivers/cpufreq/gx-suspmod.c | 2 +-
drivers/cpufreq/intel_pstate.c | 3 ---
drivers/cpufreq/pcc-cpufreq.c | 10 ++++------
drivers/cpufreq/pxa3xx-cpufreq.c | 5 ++---
drivers/cpufreq/sh-cpufreq.c | 6 ++----
drivers/cpufreq/virtual-cpufreq.c | 5 +----
10 files changed, 23 insertions(+), 38 deletions(-)
diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c
index 453084c67327f..ecc3779e047d5 100644
--- a/drivers/cpufreq/amd-pstate.c
+++ b/drivers/cpufreq/amd-pstate.c
@@ -1090,10 +1090,9 @@ static int amd_pstate_cpu_init(struct cpufreq_policy *policy)
perf = READ_ONCE(cpudata->perf);
- policy->cpuinfo.min_freq = policy->min = perf_to_freq(perf,
- cpudata->nominal_freq,
- perf.lowest_perf);
- policy->cpuinfo.max_freq = policy->max = cpudata->max_freq;
+ policy->cpuinfo.min_freq = perf_to_freq(perf, cpudata->nominal_freq,
+ perf.lowest_perf);
+ policy->cpuinfo.max_freq = cpudata->max_freq;
policy->driver_data = cpudata;
ret = amd_pstate_cppc_enable(policy);
@@ -1907,10 +1906,9 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy)
perf = READ_ONCE(cpudata->perf);
- policy->cpuinfo.min_freq = policy->min = perf_to_freq(perf,
- cpudata->nominal_freq,
- perf.lowest_perf);
- policy->cpuinfo.max_freq = policy->max = cpudata->max_freq;
+ policy->cpuinfo.min_freq = perf_to_freq(perf, cpudata->nominal_freq,
+ perf.lowest_perf);
+ policy->cpuinfo.max_freq = cpudata->max_freq;
policy->driver_data = cpudata;
ret = amd_pstate_cppc_enable(policy);
diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
index 7e7f9dfb7a24c..5abac50df7508 100644
--- a/drivers/cpufreq/cppc_cpufreq.c
+++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -660,8 +660,6 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)
* Section 8.4.7.1.1.5 of ACPI 6.1 spec)
*/
policy->min = cppc_perf_to_khz(caps, caps->lowest_nonlinear_perf);
- policy->max = cppc_perf_to_khz(caps, policy->boost_enabled ?
- caps->highest_perf : caps->nominal_perf);
/*
* Set cpuinfo.min_freq to Lowest to make the full range of performance
@@ -669,7 +667,8 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)
* nonlinear perf
*/
policy->cpuinfo.min_freq = cppc_perf_to_khz(caps, caps->lowest_perf);
- policy->cpuinfo.max_freq = policy->max;
+ policy->cpuinfo.max_freq = cppc_perf_to_khz(caps, policy->boost_enabled ?
+ caps->highest_perf : caps->nominal_perf);
policy->transition_delay_us = cppc_cpufreq_get_transition_delay_us(cpu);
policy->shared_type = cpu_data->shared_type;
diff --git a/drivers/cpufreq/cpufreq-nforce2.c b/drivers/cpufreq/cpufreq-nforce2.c
index fbbbe501cf2dc..831102522ad64 100644
--- a/drivers/cpufreq/cpufreq-nforce2.c
+++ b/drivers/cpufreq/cpufreq-nforce2.c
@@ -355,8 +355,8 @@ static int nforce2_cpu_init(struct cpufreq_policy *policy)
min_fsb = NFORCE2_MIN_FSB;
/* cpuinfo and default policy values */
- policy->min = policy->cpuinfo.min_freq = min_fsb * fid * 100;
- policy->max = policy->cpuinfo.max_freq = max_fsb * fid * 100;
+ policy->cpuinfo.min_freq = min_fsb * fid * 100;
+ policy->cpuinfo.max_freq = max_fsb * fid * 100;
return 0;
}
diff --git a/drivers/cpufreq/freq_table.c b/drivers/cpufreq/freq_table.c
index 5b364d8da4f92..ea994647abc88 100644
--- a/drivers/cpufreq/freq_table.c
+++ b/drivers/cpufreq/freq_table.c
@@ -49,16 +49,15 @@ int cpufreq_frequency_table_cpuinfo(struct cpufreq_policy *policy)
max_freq = freq;
}
- policy->min = policy->cpuinfo.min_freq = min_freq;
- policy->max = max_freq;
+ policy->cpuinfo.min_freq = min_freq;
/*
* If the driver has set its own cpuinfo.max_freq above max_freq, leave
* it as is.
*/
if (policy->cpuinfo.max_freq < max_freq)
- policy->max = policy->cpuinfo.max_freq = max_freq;
+ policy->cpuinfo.max_freq = max_freq;
- if (policy->min == ~0)
+ if (min_freq == ~0)
return -EINVAL;
else
return 0;
diff --git a/drivers/cpufreq/gx-suspmod.c b/drivers/cpufreq/gx-suspmod.c
index d269a4f26f98e..d40c9e0bbb740 100644
--- a/drivers/cpufreq/gx-suspmod.c
+++ b/drivers/cpufreq/gx-suspmod.c
@@ -421,7 +421,7 @@ static int cpufreq_gx_cpu_init(struct cpufreq_policy *policy)
policy->min = maxfreq / max_duration;
else
policy->min = maxfreq / POLICY_MIN_DIV;
- policy->max = maxfreq;
+
policy->cpuinfo.min_freq = maxfreq / max_duration;
policy->cpuinfo.max_freq = maxfreq;
diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c
index 1292da53e5fcb..68ccc6eb1ef30 100644
--- a/drivers/cpufreq/intel_pstate.c
+++ b/drivers/cpufreq/intel_pstate.c
@@ -3049,9 +3049,6 @@ static int __intel_pstate_cpu_init(struct cpufreq_policy *policy)
policy->cpuinfo.max_freq = READ_ONCE(global.no_turbo) ?
cpu->pstate.max_freq : cpu->pstate.turbo_freq;
- policy->min = policy->cpuinfo.min_freq;
- policy->max = policy->cpuinfo.max_freq;
-
intel_pstate_init_acpi_perf_limits(policy);
policy->fast_switch_possible = true;
diff --git a/drivers/cpufreq/pcc-cpufreq.c b/drivers/cpufreq/pcc-cpufreq.c
index ac2e90a65f0c4..0f185a13577f8 100644
--- a/drivers/cpufreq/pcc-cpufreq.c
+++ b/drivers/cpufreq/pcc-cpufreq.c
@@ -551,13 +551,11 @@ static int pcc_cpufreq_cpu_init(struct cpufreq_policy *policy)
goto out;
}
- policy->max = policy->cpuinfo.max_freq =
- ioread32(&pcch_hdr->nominal) * 1000;
- policy->min = policy->cpuinfo.min_freq =
- ioread32(&pcch_hdr->minimum_frequency) * 1000;
+ policy->cpuinfo.max_freq = ioread32(&pcch_hdr->nominal) * 1000;
+ policy->cpuinfo.min_freq = ioread32(&pcch_hdr->minimum_frequency) * 1000;
- pr_debug("init: policy->max is %d, policy->min is %d\n",
- policy->max, policy->min);
+ pr_debug("init: max_freq is %d, min_freq is %d\n",
+ policy->cpuinfo.max_freq, policy->cpuinfo.min_freq);
out:
return result;
}
diff --git a/drivers/cpufreq/pxa3xx-cpufreq.c b/drivers/cpufreq/pxa3xx-cpufreq.c
index 50ff3b6a69000..06b27cbc59d6a 100644
--- a/drivers/cpufreq/pxa3xx-cpufreq.c
+++ b/drivers/cpufreq/pxa3xx-cpufreq.c
@@ -185,9 +185,8 @@ static int pxa3xx_cpufreq_init(struct cpufreq_policy *policy)
int ret = -EINVAL;
/* set default policy and cpuinfo */
- policy->min = policy->cpuinfo.min_freq = 104000;
- policy->max = policy->cpuinfo.max_freq =
- (cpu_is_pxa320()) ? 806000 : 624000;
+ policy->cpuinfo.min_freq = 104000;
+ policy->cpuinfo.max_freq = (cpu_is_pxa320()) ? 806000 : 624000;
policy->cpuinfo.transition_latency = 1000; /* FIXME: 1 ms, assumed */
if (cpu_is_pxa300() || cpu_is_pxa310())
diff --git a/drivers/cpufreq/sh-cpufreq.c b/drivers/cpufreq/sh-cpufreq.c
index 642ddb9ea217e..3c99d7009cbe2 100644
--- a/drivers/cpufreq/sh-cpufreq.c
+++ b/drivers/cpufreq/sh-cpufreq.c
@@ -124,10 +124,8 @@ static int sh_cpufreq_cpu_init(struct cpufreq_policy *policy)
dev_notice(dev, "no frequency table found, falling back "
"to rate rounding.\n");
- policy->min = policy->cpuinfo.min_freq =
- (clk_round_rate(cpuclk, 1) + 500) / 1000;
- policy->max = policy->cpuinfo.max_freq =
- (clk_round_rate(cpuclk, ~0UL) + 500) / 1000;
+ policy->cpuinfo.min_freq = (clk_round_rate(cpuclk, 1) + 500) / 1000;
+ policy->cpuinfo.max_freq = (clk_round_rate(cpuclk, ~0UL) + 500) / 1000;
}
return 0;
diff --git a/drivers/cpufreq/virtual-cpufreq.c b/drivers/cpufreq/virtual-cpufreq.c
index 4159f31349b16..dc78b74409af4 100644
--- a/drivers/cpufreq/virtual-cpufreq.c
+++ b/drivers/cpufreq/virtual-cpufreq.c
@@ -164,10 +164,7 @@ static int virt_cpufreq_get_freq_info(struct cpufreq_policy *policy)
policy->cpuinfo.min_freq = 1;
policy->cpuinfo.max_freq = virt_cpufreq_get_perftbl_entry(policy->cpu, 0);
- policy->min = policy->cpuinfo.min_freq;
- policy->max = policy->cpuinfo.max_freq;
-
- policy->cur = policy->max;
+ policy->cur = policy->cpuinfo.max_freq;
return 0;
}
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox