Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* Re: [PATCH v17 06/15] arm64: syscall: Rework the syscall exit path in el0_svc_common()
From: Linus Walleij @ 2026-07-21  8:43 UTC (permalink / raw)
  To: Jinjie Ruan
  Cc: mark.rutland, kees, catalin.marinas, oleg, will, peterz, thuth,
	ryan.roberts, anshuman.khandual, kevin.brodsky, pengcan, broonie,
	linux-arm-kernel, wad, linux-mm, yeoreum.yun, linux-kernel, luto,
	james.morse, tglx, liqiang01
In-Reply-To: <20260721081858.1169276-7-ruanjinjie@huawei.com>

On Tue, Jul 21, 2026 at 10:19 AM Jinjie Ruan <ruanjinjie@huawei.com> wrote:


> In preparation for moving arm64 over to the generic entry,
> invert the nested conditional flags check within el0_svc_common().
>
> No functional changes.
(...)
>         if (!has_syscall_work(flags) && !IS_ENABLED(CONFIG_DEBUG_RSEQ)) {
>                 flags = read_thread_flags();
> -               if (!has_syscall_work(flags) && !(flags & _TIF_SINGLESTEP))
> -                       return;
> +               if (has_syscall_work(flags) || flags & _TIF_SINGLESTEP)
> +                       syscall_trace_exit(regs);
> +               return;

What about just:

if (...)
  goto trace_exit;

This is easier to follow IMO.

With that:
Reviewed-by: Linus Walleij <linusw@kernel.org>

Yours,
Linus Walleij


^ permalink raw reply

* Re: [PATCH] KVM: arm64: Optimize protected mode and FWB
From: Mostafa Saleh @ 2026-07-21  8:42 UTC (permalink / raw)
  To: Fuad Tabba
  Cc: linux-kernel, kvmarm, linux-arm-kernel, maz, oupton, seiden,
	joey.gouly, suzuki.poulose, yuzenghui, catalin.marinas, will,
	vdonnefort, sebastianene, keirf
In-Reply-To: <CA+EHjTw+BypEvWRccxqsSGR+btEX58nCUC2ypuSjw04Qo3nN=A@mail.gmail.com>

On Tue, Jul 21, 2026 at 08:49:26AM +0100, Fuad Tabba wrote:
> On Mon, 20 Jul 2026 at 21:35, Mostafa Saleh <smostafa@google.com> wrote:
> >
> > KVM opportunistically enables FWB if supported by the system for guest
> > VMs, which allows it to elude cache maintenance for data as they are
> > forced to be cacheable from stage-2.
> > In that case, __clean_dcache_guest_page() will immediately return.
> > However in protected mode, before calling __clean_dcache_guest_page()
> > it loops over the range and fix_map/unmap it, issuing TLB
> > invalidations, dsb() and isb() unnecessarily.
> >
> > This can be optimized by returning early if FWB is supported,
> > kvm_pgtable_stage2_map() already issues dsb() and tlb invalidation
> > functions issue dsb() for the unmap path.
> >
> > Signed-off-by: Mostafa Saleh <smostafa@google.com>
> > ---
> >  arch/arm64/kvm/hyp/nvhe/mem_protect.c | 4 ++++
> >  1 file changed, 4 insertions(+)
> >
> > diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
> > index 4e329e39a695..6e9229106a25 100644
> > --- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
> > +++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
> > @@ -261,6 +261,10 @@ static void __apply_guest_page(void *va, size_t size,
> >
> >  static void clean_dcache_guest_page(void *va, size_t size)
> >  {
> > +       /* See __clean_dcache_guest_page() */
> 
> This looks good to me. One comment: this file also has
> hyp_poison_page(), which deliberately avoids
> __clean_dcache_guest_page() because the FWB elision would be wrong
> there (it even notes "Prefer kvm_flush_dcache_to_poc() over
> __clean_dcache_guest_page()"). Given the two opposite treatments in
> the same file, could this comment say why eliding is safe here, e.g.
> that the consumer is the guest via the FWB-forced stage-2? It would
> save the next reader (if they're anything like me) from reconciling
> the two.

__clean_dcache_guest_page() already have this comment.
	/*
	 * With FWB, we ensure that the guest always accesses memory using
	 * cacheable attributes, and we don't have to clean to PoC when
	 * faulting in pages. Furthermore, FWB implies IDC, so cleaning to
	 * PoU is not required either in this case.
	 */

I will add comment to clarify guest vs host:
	/*
	 * Guest stage-2 uses FWB if it exists, in that case it is
	 * safe to elide CMOs.
	 * Unlike the host stage-2 which never have FWB enabled.
	 */

Thanks,
Mostafa

> 
> That fuller comment could also replace "See
> __clean_dcache_guest_page()", which mostly just points back at the
> function passed in the __apply_guest_page() call below.
> 
> With that fixed:
> 
> Reviewed-by: Fuad Tabba <fuad.tabba@linux.dev>
> 
> Cheers,
> /fuad
> 
> > +       if (cpus_have_final_cap(ARM64_HAS_STAGE2_FWB))
> > +               return;
> > +
> >         __apply_guest_page(va, size, __clean_dcache_guest_page);
> >  }
> >
> > --
> > 2.55.0.229.g6434b31f56-goog
> >


^ permalink raw reply

* Re: [PATCH v1 07/11] arm_mpam: Initialize all of struct mon_read in mpam_restore_mbwu_state()
From: Ben Horgan @ 2026-07-21  8:40 UTC (permalink / raw)
  To: Lee Trager
  Cc: james.morse, reinette.chatre, fenghuay, linux-kernel,
	linux-arm-kernel, dave.martin, andre.przywara
In-Reply-To: <0e117330-36ed-4917-8a63-384d1e4f77fc@trager.us>

Hi Lee,

On 7/21/26 00:05, Lee Trager wrote:
> On 7/10/26 4:55 AM, Ben Horgan wrote:
> 
>> m->err may be read before initialization in __ris_msmon_read() when called
>> from mpam_restore_mbwu_state().
>>
>> Initialize the whole struct mon_read in mpam_restore_mbwu_state() and fix
>> the spelling of mbwu in the name.
>>
>> Fixes: 41e8a14950e1 ("arm_mpam: Track bandwidth counter state for power management")
>> Signed-off-by: Ben Horgan <ben.horgan@arm.com>
>> ---
>>   drivers/resctrl/mpam_devices.c | 13 +++++++------
>>   1 file changed, 7 insertions(+), 6 deletions(-)
>>
>> diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
>> index a49f426aefc0..c9adc450f087 100644
>> --- a/drivers/resctrl/mpam_devices.c
>> +++ b/drivers/resctrl/mpam_devices.c
>> @@ -1640,7 +1640,6 @@ static int mpam_restore_mbwu_state(void *_ris)
>>   {
>>       int i;
>>       u64 val;
> val is still uninitialized. Its passed to to __ris_mon_read() below which does *m->val += now;

Ok, I don't think this causes any actual problems as an unitialized automatic variable has an
indeterminate value, we're just adding to it rather than making any decisions or persisting the
value. As it's unsigned the addition is defined even if it wraps around. Having said that, it's
clearer just to initialize it. I'll set it to 0.

Thanks,

Ben

>> -    struct mon_read mwbu_arg;
>>       struct mpam_msc_ris *ris = _ris;
>>       struct msmon_mbwu_state *mbwu_state;
>>       struct mpam_msc *msc = ris->vmsc->msc;
>> @@ -1653,16 +1652,18 @@ static int mpam_restore_mbwu_state(void *_ris)
>>               return -EIO;
>>             if (ris->mbwu_state[i].enabled) {
>> -            mwbu_arg.ris = ris;
>> -            mwbu_arg.ctx = &ris->mbwu_state[i].cfg;
>> -            mwbu_arg.type = mpam_msmon_choose_counter(class);
>> -            mwbu_arg.val = &val;
>> +            struct mon_read mbwu_arg = {
>> +                .ris = ris,
>> +                .ctx = &ris->mbwu_state[i].cfg,
>> +                .type = mpam_msmon_choose_counter(class),
>> +                .val = &val
>> +            };
>>                 mbwu_state->reset_on_next_read = true;
>>                 mpam_mon_sel_unlock(msc);
>>   -            __ris_msmon_read(&mwbu_arg);
>> +            __ris_msmon_read(&mbwu_arg);
>>           } else {
>>               mpam_mon_sel_unlock(msc);
>>           }



^ permalink raw reply

* Re: [PATCH v17 04/15] arm64: ptrace: Rename and clean up syscall_trace_enter()
From: Linus Walleij @ 2026-07-21  8:39 UTC (permalink / raw)
  To: Jinjie Ruan
  Cc: mark.rutland, kees, catalin.marinas, oleg, will, peterz, thuth,
	ryan.roberts, anshuman.khandual, kevin.brodsky, pengcan, broonie,
	linux-arm-kernel, wad, linux-mm, yeoreum.yun, linux-kernel, luto,
	james.morse, tglx, liqiang01
In-Reply-To: <20260721081858.1169276-5-ruanjinjie@huawei.com>

On Tue, Jul 21, 2026 at 10:19 AM Jinjie Ruan <ruanjinjie@huawei.com> wrote:

> Rename syscall_trace_enter() to arm64_syscall_trace_enter() to avoid
> name collisions and clarify the boundary when arm64 eventually switches
> to the generic entry infrastructure.
>
> In addition, replace direct accesses to regs->syscallno with the standard
> syscall_get_nr() helper for both trace_sys_enter() and the return value.
> This decouples the tracing logic from architecture-specific struct pt_regs
> layouts, aligning the implementation with the generic entry pattern.
>
> No functional changes intended; this is a preparation step for
> converting arm64 to the generic entry infrastructure.
>
> Cc: Mark Rutland <mark.rutland@arm.com>
> Cc: Will Deacon <will@kernel.org>
> Cc: Catalin Marinas <catalin.marinas@arm.com>
> Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>

Reviewed-by: Linus Walleij <linusw@kernel.org>

Yours,
Linus Walleij


^ permalink raw reply

* [PATCH v14 3/3] media: nxp: Add i.MX95 CSI pixel formatter v4l2 driver
From: guoniu.zhou @ 2026-07-21  8:40 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Shawn Guo, Sascha Hauer, Pengutronix Kernel Team,
	Fabio Estevam, Laurent Pinchart, Frank Li, Loic Poulain,
	Bryan O'Donoghue, Abel Vesa, Peng Fan, Michael Turquette,
	Stephen Boyd
  Cc: imx, linux-media, devicetree, linux-arm-kernel, linux-kernel,
	linux-clk, Guoniu Zhou, Frank Li
In-Reply-To: <20260721-csi_formatter-v14-0-ca239fbb441f@oss.nxp.com>

From: Guoniu Zhou <guoniu.zhou@nxp.com>

The CSI pixel formatter is a module found on i.MX95 used to reformat
packet info, pixel and non-pixel data from CSI-2 host controller to
match Pixel Link(PL) definition.

Add data formatting support.

Signed-off-by: Guoniu Zhou <guoniu.zhou@nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Reviewed-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
---
Changes in v14:
- Use select MFD_SYSCON instead of depends on MFD_SYSCON (Laurent)
- Remove unused v4l2-ctrls.h and v4l2-event.h includes (Laurent)
- Rename csi_sd to remote_sd and group with remote_pad (Laurent)
- Move reg_offset after clk to group generic resources (Laurent)
- Remove mutex lock as enable/disable_streams hold state lock (Laurent)
- Rename formatter_dt_to_index_map to csi_formatter_dt_to_index_map (Laurent)
- Rename formats to csi_formatter_formats (Laurent)
- Declare loop variables in for-loop and add braces (Laurent)
- Simplify set_fmt, rename sdformat to format, drop NULL check (Laurent)
- Set field to V4L2_FIELD_NONE as interlaced not yet supported (Laurent)
- Add streaming check in set_fmt to match set_routing (Laurent)
- Add WARN_ON for unsupported data type as it indicates driver bug (Laurent)
- Return -EPIPE instead of -EINVAL for invalid VC (Laurent)
- Refactor ENOIOCTLCMD handling and remove redundant pad check (Laurent)
- Use strscpy instead of snprintf for sd->name (Laurent)
- Remove redundant dev_dbg in notify_bound (Laurent)
- Simplify remote_pad assignment and drop NULL check (Laurent)
- Read reg property directly into formatter->reg_offset (Laurent)
- Store formatter pointer in drvdata to avoid sd_to_formatter calls (Laurent)
- Enable runtime PM autosuspend with 1s delay (Laurent)
- Use PM clock framework instead of manual clock handling (Loic)
- Add Reviewed-by tag from Loic Poulain

Changes in v13:
- Replace pr_warn_once() with dev_warn() in csi_formatter_get_index_by_dt()
  to provide device context and warn on every occurrence (Loic)
- Add WARN_ON() check for csi_formatter_find_format() return value in
  start/stop_stream functions to catch unexpected NULL (Loic)
- Use regmap_set_bits()/regmap_clear_bits() instead of read-modify-write
  pattern and remove unused csi_formatter_read/write helper functions (Loic)
- Add Reviewed-by tag from Frank Li

Changes in v12:
- Fix stream ID handling: iterate routing table instead of assuming
  stream ID equals loop index (0-7)
- Remove stream_to_vc[] array: derive VC from routing table and frame
  descriptor on each start/stop operation
- Remove V4L2_SUBDEV_FL_HAS_EVENTS flag since driver does not generate events
- Support stream IDs 0-63 by using BIT_ULL() for stream masks
- Add get_frame_desc call in stop_stream with proper error handling
- Add csi_formatter_read() helper function for register reads
- Use read-modify-write for CSI_VC_PIXEL_DATA_TYPE register to support
  multiplexed streams sharing the same virtual channel
- Use route->sink_pad instead of hardcoded CSI_FORMATTER_PAD_SINK
- Write back coerced format in set_fmt before propagating to source stream
- Drop Frank's Reviewed-by tag due to significant changes, requesting re-review

Changes in v10:
- Use u8 for vc in csi_formatter_get_vc() and drop vc < 0 check
- Add MFD_SYSCON dependency to Kconfig
- Fix stream/VC mapping potential mismatch in start_stop_stream functions

Changes in v8:
- Remove fmt field and look up format from subdev state instead
- Unify function and structure naming to use csi_formatter_ prefix
- Remove misleading alignment comment from set_fmt function
- Optimize get_frame_desc to call once per start_stream
- Replace V4L2_FRAME_DESC_ENTRY_MAX with CSI_FORMATTER_VC_NUM in loops
- Remove redundant debug message in enable_streams
- Use MEDIA_PAD_FL_MUST_CONNECT flag instead of manual link check
- Fix typo: Formater -> Formatter in Kconfig help text
- Improve grammar in data type index mapping comment

Changes in v7:
- Update references from imx9 to imx95 for consistency with dt-bindings
- Enable PM runtime before async registration

Changes in v6:
- Remove unused header includes
- Unify macro naming: VCx/VCX -> VC and parameter x -> vc
- Remove unused format field from csi_formatter struct
- Use compact initialization for formats array
- Make find_csi_format() return NULL instead of default format
- Use unsigned int for array index in find_csi_format()
- Add err_ prefix to error handling labels
- Add v4l2_subdev_cleanup() and reorder cleanup sequence
- Update enable_streams debug output format
- Rename VC_MAX to VC_NUM and fix boundary check
- Update CSI formatter Kconfig description
- Use v4l2_subdev_get_frame_desc_passthrough() helper
- Fix error paths in async registration and probe
- Add mutex to protect enabled_streams
- Switch to devm_pm_runtime_enable()
- Remove redundant num_routes check in set_routing
- Optimize get_index_by_dt() and add warning for unsupported type
- csi_formatter_start/stop_stream: Process all streams in mask
---
 MAINTAINERS                                      |   8 +
 drivers/media/platform/nxp/Kconfig               |  16 +
 drivers/media/platform/nxp/Makefile              |   1 +
 drivers/media/platform/nxp/imx95-csi-formatter.c | 751 +++++++++++++++++++++++
 4 files changed, 776 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index efbf808063e5..05009228b162 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19275,6 +19275,14 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml
 F:	drivers/media/platform/nxp/imx-jpeg
 
+NXP i.MX 95 CSI PIXEL FORMATTER V4L2 DRIVER
+M:	Guoniu Zhou <guoniu.zhou@nxp.com>
+L:	imx@lists.linux.dev
+L:	linux-media@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/media/fsl,imx95-csi-formatter.yaml
+F:	drivers/media/platform/nxp/imx95-csi-formatter.c
+
 NXP i.MX CLOCK DRIVERS
 M:	Abel Vesa <abelvesa@kernel.org>
 R:	Peng Fan <peng.fan@nxp.com>
diff --git a/drivers/media/platform/nxp/Kconfig b/drivers/media/platform/nxp/Kconfig
index 40e3436669e2..e714bdaf65c9 100644
--- a/drivers/media/platform/nxp/Kconfig
+++ b/drivers/media/platform/nxp/Kconfig
@@ -28,6 +28,22 @@ config VIDEO_IMX8MQ_MIPI_CSI2
 	  Video4Linux2 driver for the MIPI CSI-2 receiver found on the i.MX8MQ
 	  SoC.
 
+config VIDEO_IMX95_CSI_FORMATTER
+	tristate "NXP i.MX95 CSI Pixel Formatter driver"
+	depends on ARCH_MXC || COMPILE_TEST
+	depends on VIDEO_DEV
+	select MEDIA_CONTROLLER
+	select MFD_SYSCON
+	select PM_CLK
+	select V4L2_FWNODE
+	select VIDEO_V4L2_SUBDEV_API
+	help
+	  This driver provides support for the CSI Pixel Formatter found on
+	  i.MX95 series SoCs. This module unpacks the pixels received from the
+	  CSI-2 interface and reformats them to meet pixel link requirements.
+
+	  Say Y here to enable CSI Pixel Formatter module for i.MX95 SoC.
+
 config VIDEO_IMX_MIPI_CSIS
 	tristate "NXP MIPI CSI-2 CSIS receiver found on i.MX7 and i.MX8 models"
 	depends on ARCH_MXC || COMPILE_TEST
diff --git a/drivers/media/platform/nxp/Makefile b/drivers/media/platform/nxp/Makefile
index 4d90eb713652..6410115d870e 100644
--- a/drivers/media/platform/nxp/Makefile
+++ b/drivers/media/platform/nxp/Makefile
@@ -6,6 +6,7 @@ obj-y += imx8-isi/
 
 obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o
 obj-$(CONFIG_VIDEO_IMX8MQ_MIPI_CSI2) += imx8mq-mipi-csi2.o
+obj-$(CONFIG_VIDEO_IMX95_CSI_FORMATTER) += imx95-csi-formatter.o
 obj-$(CONFIG_VIDEO_IMX_MIPI_CSIS) += imx-mipi-csis.o
 obj-$(CONFIG_VIDEO_IMX_PXP) += imx-pxp.o
 obj-$(CONFIG_VIDEO_MX2_EMMAPRP) += mx2_emmaprp.o
diff --git a/drivers/media/platform/nxp/imx95-csi-formatter.c b/drivers/media/platform/nxp/imx95-csi-formatter.c
new file mode 100644
index 000000000000..7ccb3eee3a5e
--- /dev/null
+++ b/drivers/media/platform/nxp/imx95-csi-formatter.c
@@ -0,0 +1,751 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2025 NXP
+ */
+
+#include <linux/bits.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_clock.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include <media/mipi-csi2.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mc.h>
+#include <media/v4l2-subdev.h>
+
+/* CSI Pixel Formatter registers map */
+
+#define CSI_VC_INTERLACED_LINE_CNT(vc)		(0x00 + (vc) * 0x04)
+#define INTERLACED_ODD_LINE_CNT_SET(x)		FIELD_PREP(GENMASK(13, 0), (x))
+#define INTERLACED_EVEN_LINE_CNT_SET(x)		FIELD_PREP(GENMASK(29, 16), (x))
+
+#define CSI_VC_INTERLACED_CTRL			0x20
+
+#define CSI_VC_INTERLACED_ERR			0x24
+#define CSI_VC_ERR_MASK				GENMASK(7, 0)
+#define CSI_VC_ERR(vc)				BIT((vc))
+
+#define CSI_VC_YUV420_FIRST_LINE_EVEN		0x28
+#define YUV420_FIRST_LINE_EVEN(vc)		BIT((vc))
+
+#define CSI_RAW32_CTRL				0x30
+#define CSI_VC_RAW32_MODE(vc)			BIT((vc))
+#define CSI_VC_RAW32_SWAP_MODE(vc)		BIT((vc) + 8)
+
+#define CSI_STREAM_FENCING_CTRL			0x34
+#define CSI_VC_STREAM_FENCING(vc)		BIT((vc))
+#define CSI_VC_STREAM_FENCING_RST(vc)		BIT((vc) + 8)
+
+#define CSI_STREAM_FENCING_STS			0x38
+#define CSI_STREAM_FENCING_STS_MASK		GENMASK(7, 0)
+
+#define CSI_VC_NON_PIXEL_DATA_TYPE(vc)		(0x40 + (vc) * 0x04)
+
+#define CSI_VC_PIXEL_DATA_CTRL(vc)		(0x60 + (vc) * 0x04)
+#define NEW_VC(vc)				FIELD_PREP(GENMASK(3, 1), vc)
+#define REROUTE_VC_ENABLE			BIT(0)
+
+#define CSI_VC_ROUTE_PIXEL_DATA_TYPE(vc)	(0x80 + (vc) * 0x04)
+
+#define CSI_VC_NON_PIXEL_DATA_CTRL(vc)		(0xa0 + (vc) * 0x04)
+
+#define CSI_VC_PIXEL_DATA_TYPE(vc)		(0xc0 + (vc) * 0x04)
+
+#define CSI_VC_PIXEL_DATA_TYPE_ERR(vc)		(0xe0 + (vc) * 0x04)
+
+#define CSI_FORMATTER_PAD_SINK			0
+#define CSI_FORMATTER_PAD_SOURCE		1
+#define CSI_FORMATTER_PAD_NUM			2
+
+#define CSI_FORMATTER_VC_NUM			8 /* Number of virtual channels */
+
+struct csi_formatter_pix_format {
+	u32 code;
+	u32 data_type;
+};
+
+struct csi_formatter {
+	struct device *dev;
+	struct regmap *regs;
+	u32 reg_offset;
+
+	struct v4l2_subdev sd;
+	struct v4l2_async_notifier notifier;
+	struct media_pad pads[CSI_FORMATTER_PAD_NUM];
+
+	struct v4l2_subdev *remote_sd;
+	u32 remote_pad;
+
+	u64 enabled_streams;
+};
+
+struct csi_formatter_dt_index {
+	u8 dtype;
+	u8 index;
+};
+
+/*
+ * The index corresponds to the bit index in the register that enables
+ * the data type of pixel data transported by the Formatter.
+ */
+static const struct csi_formatter_dt_index csi_formatter_dt_to_index_map[] = {
+	{ .dtype = MIPI_CSI2_DT_YUV420_8B,        .index = 0 },
+	{ .dtype = MIPI_CSI2_DT_YUV420_8B_LEGACY, .index = 2 },
+	{ .dtype = MIPI_CSI2_DT_YUV422_8B,        .index = 6 },
+	{ .dtype = MIPI_CSI2_DT_RGB444,		  .index = 8 },
+	{ .dtype = MIPI_CSI2_DT_RGB555,           .index = 9 },
+	{ .dtype = MIPI_CSI2_DT_RGB565,           .index = 10 },
+	{ .dtype = MIPI_CSI2_DT_RGB666,           .index = 11 },
+	{ .dtype = MIPI_CSI2_DT_RGB888,           .index = 12 },
+	{ .dtype = MIPI_CSI2_DT_RAW6,             .index = 16 },
+	{ .dtype = MIPI_CSI2_DT_RAW7,             .index = 17 },
+	{ .dtype = MIPI_CSI2_DT_RAW8,             .index = 18 },
+	{ .dtype = MIPI_CSI2_DT_RAW10,            .index = 19 },
+	{ .dtype = MIPI_CSI2_DT_RAW12,            .index = 20 },
+	{ .dtype = MIPI_CSI2_DT_RAW14,            .index = 21 },
+	{ .dtype = MIPI_CSI2_DT_RAW16,            .index = 22 },
+};
+
+static const struct csi_formatter_pix_format csi_formatter_formats[] = {
+	/* YUV formats */
+	{ MEDIA_BUS_FMT_UYVY8_1X16,	MIPI_CSI2_DT_YUV422_8B },
+	/* RGB formats */
+	{ MEDIA_BUS_FMT_RGB565_1X16,	MIPI_CSI2_DT_RGB565 },
+	{ MEDIA_BUS_FMT_RGB888_1X24,	MIPI_CSI2_DT_RGB888 },
+	/* RAW (Bayer and greyscale) formats */
+	{ MEDIA_BUS_FMT_SBGGR8_1X8,	MIPI_CSI2_DT_RAW8 },
+	{ MEDIA_BUS_FMT_SGBRG8_1X8,	MIPI_CSI2_DT_RAW8 },
+	{ MEDIA_BUS_FMT_SGRBG8_1X8,	MIPI_CSI2_DT_RAW8 },
+	{ MEDIA_BUS_FMT_SRGGB8_1X8,	MIPI_CSI2_DT_RAW8 },
+	{ MEDIA_BUS_FMT_Y8_1X8,		MIPI_CSI2_DT_RAW8 },
+	{ MEDIA_BUS_FMT_SBGGR10_1X10,	MIPI_CSI2_DT_RAW10 },
+	{ MEDIA_BUS_FMT_SGBRG10_1X10,	MIPI_CSI2_DT_RAW10 },
+	{ MEDIA_BUS_FMT_SGRBG10_1X10,	MIPI_CSI2_DT_RAW10 },
+	{ MEDIA_BUS_FMT_SRGGB10_1X10,	MIPI_CSI2_DT_RAW10 },
+	{ MEDIA_BUS_FMT_Y10_1X10,	MIPI_CSI2_DT_RAW10 },
+	{ MEDIA_BUS_FMT_SBGGR12_1X12,	MIPI_CSI2_DT_RAW12 },
+	{ MEDIA_BUS_FMT_SGBRG12_1X12,	MIPI_CSI2_DT_RAW12 },
+	{ MEDIA_BUS_FMT_SGRBG12_1X12,	MIPI_CSI2_DT_RAW12 },
+	{ MEDIA_BUS_FMT_SRGGB12_1X12,	MIPI_CSI2_DT_RAW12 },
+	{ MEDIA_BUS_FMT_Y12_1X12,	MIPI_CSI2_DT_RAW12 },
+	{ MEDIA_BUS_FMT_SBGGR14_1X14,	MIPI_CSI2_DT_RAW14 },
+	{ MEDIA_BUS_FMT_SGBRG14_1X14,	MIPI_CSI2_DT_RAW14 },
+	{ MEDIA_BUS_FMT_SGRBG14_1X14,	MIPI_CSI2_DT_RAW14 },
+	{ MEDIA_BUS_FMT_SRGGB14_1X14,	MIPI_CSI2_DT_RAW14 },
+	{ MEDIA_BUS_FMT_SBGGR16_1X16,	MIPI_CSI2_DT_RAW16 },
+	{ MEDIA_BUS_FMT_SGBRG16_1X16,	MIPI_CSI2_DT_RAW16 },
+	{ MEDIA_BUS_FMT_SGRBG16_1X16,	MIPI_CSI2_DT_RAW16 },
+	{ MEDIA_BUS_FMT_SRGGB16_1X16,	MIPI_CSI2_DT_RAW16 },
+};
+
+static const struct v4l2_mbus_framefmt formatter_default_fmt = {
+	.code = MEDIA_BUS_FMT_UYVY8_1X16,
+	.width = 1920U,
+	.height = 1080U,
+	.field = V4L2_FIELD_NONE,
+	.colorspace = V4L2_COLORSPACE_SMPTE170M,
+	.xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(V4L2_COLORSPACE_SMPTE170M),
+	.ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(V4L2_COLORSPACE_SMPTE170M),
+	.quantization = V4L2_QUANTIZATION_LIM_RANGE,
+};
+
+static const struct csi_formatter_pix_format *csi_formatter_find_format(u32 code)
+{
+	for (unsigned int i = 0; i < ARRAY_SIZE(csi_formatter_formats); i++) {
+		if (code == csi_formatter_formats[i].code)
+			return &csi_formatter_formats[i];
+	}
+
+	return NULL;
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 subdev operations
+ */
+
+static inline struct csi_formatter *sd_to_formatter(struct v4l2_subdev *sdev)
+{
+	return container_of(sdev, struct csi_formatter, sd);
+}
+
+static int __csi_formatter_subdev_set_routing(struct v4l2_subdev *sd,
+					      struct v4l2_subdev_state *state,
+					      struct v4l2_subdev_krouting *routing)
+{
+	int ret;
+
+	ret = v4l2_subdev_routing_validate(sd, routing,
+					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
+	if (ret)
+		return ret;
+
+	return v4l2_subdev_set_routing_with_fmt(sd, state, routing,
+						&formatter_default_fmt);
+}
+
+static int csi_formatter_subdev_init_state(struct v4l2_subdev *sd,
+					   struct v4l2_subdev_state *sd_state)
+{
+	struct v4l2_subdev_route routes[] = {
+		{
+			.sink_pad = CSI_FORMATTER_PAD_SINK,
+			.sink_stream = 0,
+			.source_pad = CSI_FORMATTER_PAD_SOURCE,
+			.source_stream = 0,
+			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+		},
+	};
+
+	struct v4l2_subdev_krouting routing = {
+		.num_routes = ARRAY_SIZE(routes),
+		.routes = routes,
+	};
+
+	return __csi_formatter_subdev_set_routing(sd, sd_state, &routing);
+}
+
+static int csi_formatter_subdev_enum_mbus_code(struct v4l2_subdev *sd,
+					       struct v4l2_subdev_state *sd_state,
+					       struct v4l2_subdev_mbus_code_enum *code)
+{
+	if (code->pad == CSI_FORMATTER_PAD_SOURCE) {
+		struct v4l2_mbus_framefmt *fmt;
+
+		if (code->index > 0)
+			return -EINVAL;
+
+		fmt = v4l2_subdev_state_get_format(sd_state, code->pad,
+						   code->stream);
+		code->code = fmt->code;
+		return 0;
+	}
+
+	if (code->index >= ARRAY_SIZE(csi_formatter_formats))
+		return -EINVAL;
+
+	code->code = csi_formatter_formats[code->index].code;
+
+	return 0;
+}
+
+static int csi_formatter_subdev_set_fmt(struct v4l2_subdev *sd,
+					struct v4l2_subdev_state *sd_state,
+					struct v4l2_subdev_format *format)
+{
+	struct v4l2_mbus_framefmt *fmt;
+
+	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
+	    media_entity_is_streaming(&sd->entity))
+		return -EBUSY;
+
+	if (format->pad == CSI_FORMATTER_PAD_SOURCE)
+		return v4l2_subdev_get_fmt(sd, sd_state, format);
+
+	if (!csi_formatter_find_format(format->format.code))
+		format->format.code = csi_formatter_formats[0].code;
+
+	v4l_bound_align_image(&format->format.width, 1, 0xffff, 2,
+			      &format->format.height, 1, 0xffff, 0, 0);
+
+	/* TODO: Add interlaced format support */
+	format->format.field = V4L2_FIELD_NONE;
+
+	fmt = v4l2_subdev_state_get_format(sd_state, format->pad,
+					   format->stream);
+	*fmt = format->format;
+
+	/* Propagate the format from sink stream to source stream */
+	fmt = v4l2_subdev_state_get_opposite_stream_format(sd_state, format->pad,
+							   format->stream);
+	*fmt = format->format;
+
+	return 0;
+}
+
+static int csi_formatter_subdev_set_routing(struct v4l2_subdev *sd,
+					    struct v4l2_subdev_state *state,
+					    enum v4l2_subdev_format_whence which,
+					    struct v4l2_subdev_krouting *routing)
+{
+	if (which == V4L2_SUBDEV_FORMAT_ACTIVE &&
+	    media_entity_is_streaming(&sd->entity))
+		return -EBUSY;
+
+	return __csi_formatter_subdev_set_routing(sd, state, routing);
+}
+
+static u8 csi_formatter_get_index_by_dt(struct csi_formatter *formatter,
+					u8 data_type)
+{
+	for (unsigned int i = 0; i < ARRAY_SIZE(csi_formatter_dt_to_index_map); ++i) {
+		const struct csi_formatter_dt_index *entry =
+			&csi_formatter_dt_to_index_map[i];
+
+		if (data_type == entry->dtype)
+			return entry->index;
+	}
+
+	dev_warn(formatter->dev, "Unsupported data type 0x%x, using default\n",
+		 data_type);
+	WARN_ON(1);
+
+	return csi_formatter_dt_to_index_map[0].index;
+}
+
+static int csi_formatter_get_vc(struct csi_formatter *formatter,
+				struct v4l2_mbus_frame_desc *fd,
+				unsigned int stream)
+{
+	struct v4l2_mbus_frame_desc_entry *entry = NULL;
+	u8 vc;
+
+	for (unsigned int i = 0; i < fd->num_entries; ++i) {
+		if (fd->entry[i].stream == stream) {
+			entry = &fd->entry[i];
+			break;
+		}
+	}
+
+	if (!entry) {
+		dev_err(formatter->dev,
+			"No frame desc entry for stream %u\n", stream);
+		return -EPIPE;
+	}
+
+	vc = entry->bus.csi2.vc;
+
+	if (vc >= CSI_FORMATTER_VC_NUM) {
+		dev_err(formatter->dev, "Invalid virtual channel %u\n", vc);
+		return -EPIPE;
+	}
+
+	return vc;
+}
+
+static void csi_formatter_stop_stream(struct csi_formatter *formatter,
+				      struct v4l2_subdev_state *state,
+				      u64 stream_mask)
+{
+	const struct csi_formatter_pix_format *pix_fmt;
+	struct v4l2_mbus_frame_desc fd = {};
+	struct v4l2_subdev_route *route;
+	struct v4l2_mbus_framefmt *fmt;
+	unsigned int reg;
+	unsigned int mask;
+	int vc;
+	int ret;
+
+	ret = v4l2_subdev_call(formatter->remote_sd, pad, get_frame_desc,
+			       formatter->remote_pad, &fd);
+	if (ret < 0 && ret != -ENOIOCTLCMD) {
+		dev_err(formatter->dev, "Failed to get frame desc: %d\n", ret);
+		return;
+	}
+
+	if (ret == -ENOIOCTLCMD) {
+		fd.type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
+		fd.num_entries = 1;
+	}
+
+	for_each_active_route(&state->routing, route) {
+		if (!(stream_mask & BIT_ULL(route->source_stream)))
+			continue;
+
+		vc = csi_formatter_get_vc(formatter, &fd, route->sink_stream);
+		if (vc < 0)
+			continue;
+
+		fmt = v4l2_subdev_state_get_format(state, route->sink_pad,
+						   route->sink_stream);
+
+		pix_fmt = csi_formatter_find_format(fmt->code);
+		if (WARN_ON(!pix_fmt))
+			continue;
+
+		reg = CSI_VC_PIXEL_DATA_TYPE(vc) + formatter->reg_offset;
+		mask = BIT(csi_formatter_get_index_by_dt(formatter,
+							 pix_fmt->data_type));
+
+		/* Clear the data type bit to disable this VC */
+		regmap_clear_bits(formatter->regs, reg, mask);
+	}
+}
+
+static int csi_formatter_start_stream(struct csi_formatter *formatter,
+				      struct v4l2_subdev_state *state,
+				      u64 stream_mask)
+{
+	const struct csi_formatter_pix_format *pix_fmt;
+	struct v4l2_subdev_route *route;
+	struct v4l2_mbus_framefmt *fmt;
+	struct v4l2_mbus_frame_desc fd = {};
+	u64 configured_streams = 0;
+	unsigned int reg;
+	unsigned int mask;
+	int vc;
+	int ret;
+
+	ret = v4l2_subdev_call(formatter->remote_sd, pad, get_frame_desc,
+			       formatter->remote_pad, &fd);
+	if (ret < 0 && ret != -ENOIOCTLCMD) {
+		dev_err(formatter->dev, "Failed to get frame desc: %d\n", ret);
+		return ret;
+	}
+
+	/*
+	 * If the source doesn't implement .get_frame_desc(), assume a single
+	 * stream on VC 0. fd is zero-initialized, only set the fields that have
+	 * a non-zero value.
+	 */
+	if (ret == -ENOIOCTLCMD) {
+		fd.type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
+		fd.num_entries = 1;
+	}
+
+	for_each_active_route(&state->routing, route) {
+		if (!(stream_mask & BIT_ULL(route->source_stream)))
+			continue;
+
+		vc = csi_formatter_get_vc(formatter, &fd, route->sink_stream);
+		if (vc < 0) {
+			ret = vc;
+			goto err_cleanup;
+		}
+
+		fmt = v4l2_subdev_state_get_format(state, route->sink_pad,
+						   route->sink_stream);
+
+		pix_fmt = csi_formatter_find_format(fmt->code);
+		if (WARN_ON(!pix_fmt)) {
+			ret = -EINVAL;
+			goto err_cleanup;
+		}
+
+		reg = CSI_VC_PIXEL_DATA_TYPE(vc) + formatter->reg_offset;
+		mask = BIT(csi_formatter_get_index_by_dt(formatter,
+							 pix_fmt->data_type));
+
+		/* Set the data type bit to enable this VC */
+		regmap_set_bits(formatter->regs, reg, mask);
+
+		configured_streams |= BIT_ULL(route->source_stream);
+	}
+
+	return 0;
+
+err_cleanup:
+	csi_formatter_stop_stream(formatter, state, configured_streams);
+	return ret;
+}
+
+static int csi_formatter_subdev_enable_streams(struct v4l2_subdev *sd,
+					       struct v4l2_subdev_state *state,
+					       u32 pad, u64 streams_mask)
+{
+	struct csi_formatter *formatter = sd_to_formatter(sd);
+	struct device *dev = formatter->dev;
+	u64 sink_streams;
+	int ret;
+
+	sink_streams = v4l2_subdev_state_xlate_streams(state,
+						       CSI_FORMATTER_PAD_SOURCE,
+						       CSI_FORMATTER_PAD_SINK,
+						       &streams_mask);
+	if (!sink_streams || !streams_mask)
+		return -EINVAL;
+
+	if (!formatter->enabled_streams) {
+		ret = pm_runtime_resume_and_get(formatter->dev);
+		if (ret < 0) {
+			dev_err(dev, "Failed to resume runtime PM: %d\n", ret);
+			return ret;
+		}
+	}
+
+	ret = csi_formatter_start_stream(formatter, state, streams_mask);
+	if (ret)
+		goto err_runtime_put;
+
+	ret = v4l2_subdev_enable_streams(formatter->remote_sd,
+					 formatter->remote_pad,
+					 sink_streams);
+	if (ret)
+		goto err_stop_stream;
+
+	formatter->enabled_streams |= streams_mask;
+
+	return 0;
+
+err_stop_stream:
+	csi_formatter_stop_stream(formatter, state, streams_mask);
+err_runtime_put:
+	if (!formatter->enabled_streams)
+		pm_runtime_put(formatter->dev);
+	return ret;
+}
+
+static int csi_formatter_subdev_disable_streams(struct v4l2_subdev *sd,
+						struct v4l2_subdev_state *state,
+						u32 pad, u64 streams_mask)
+{
+	struct csi_formatter *formatter = sd_to_formatter(sd);
+	u64 sink_streams;
+	int ret;
+
+	sink_streams = v4l2_subdev_state_xlate_streams(state,
+						       CSI_FORMATTER_PAD_SOURCE,
+						       CSI_FORMATTER_PAD_SINK,
+						       &streams_mask);
+	if (!sink_streams || !streams_mask)
+		return -EINVAL;
+
+	ret = v4l2_subdev_disable_streams(formatter->remote_sd, formatter->remote_pad,
+					  sink_streams);
+	if (ret)
+		dev_err(formatter->dev, "Failed to disable streams: %d\n", ret);
+
+	csi_formatter_stop_stream(formatter, state, streams_mask);
+
+	formatter->enabled_streams &= ~streams_mask;
+
+	if (!formatter->enabled_streams)
+		pm_runtime_put(formatter->dev);
+
+	return ret;
+}
+
+static const struct v4l2_subdev_pad_ops formatter_subdev_pad_ops = {
+	.enum_mbus_code		= csi_formatter_subdev_enum_mbus_code,
+	.get_fmt		= v4l2_subdev_get_fmt,
+	.set_fmt		= csi_formatter_subdev_set_fmt,
+	.get_frame_desc		= v4l2_subdev_get_frame_desc_passthrough,
+	.set_routing		= csi_formatter_subdev_set_routing,
+	.enable_streams		= csi_formatter_subdev_enable_streams,
+	.disable_streams	= csi_formatter_subdev_disable_streams,
+};
+
+static const struct v4l2_subdev_ops formatter_subdev_ops = {
+	.pad = &formatter_subdev_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops formatter_internal_ops = {
+	.init_state = csi_formatter_subdev_init_state,
+};
+
+/* -----------------------------------------------------------------------------
+ * Media entity operations
+ */
+
+static const struct media_entity_operations formatter_entity_ops = {
+	.link_validate	= v4l2_subdev_link_validate,
+	.get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
+};
+
+static int csi_formatter_subdev_init(struct csi_formatter *formatter)
+{
+	struct v4l2_subdev *sd = &formatter->sd;
+	int ret;
+
+	v4l2_subdev_init(sd, &formatter_subdev_ops);
+
+	strscpy(sd->name, dev_name(formatter->dev), sizeof(sd->name));
+	sd->internal_ops = &formatter_internal_ops;
+
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
+		     V4L2_SUBDEV_FL_STREAMS;
+	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
+	sd->entity.ops = &formatter_entity_ops;
+	sd->dev = formatter->dev;
+
+	formatter->pads[CSI_FORMATTER_PAD_SINK].flags = MEDIA_PAD_FL_SINK
+						      | MEDIA_PAD_FL_MUST_CONNECT;
+	formatter->pads[CSI_FORMATTER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&sd->entity, CSI_FORMATTER_PAD_NUM,
+				     formatter->pads);
+	if (ret) {
+		dev_err(formatter->dev, "Failed to init pads\n");
+		return ret;
+	}
+
+	ret = v4l2_subdev_init_finalize(sd);
+	if (ret)
+		media_entity_cleanup(&sd->entity);
+
+	return ret;
+}
+
+static inline struct csi_formatter *
+notifier_to_csi_formatter(struct v4l2_async_notifier *n)
+{
+	return container_of(n, struct csi_formatter, notifier);
+}
+
+static int csi_formatter_notify_bound(struct v4l2_async_notifier *notifier,
+				      struct v4l2_subdev *sd,
+				      struct v4l2_async_connection *asc)
+{
+	const unsigned int link_flags = MEDIA_LNK_FL_IMMUTABLE
+				      | MEDIA_LNK_FL_ENABLED;
+	struct csi_formatter *formatter = notifier_to_csi_formatter(notifier);
+	struct v4l2_subdev *sdev = &formatter->sd;
+	struct media_pad *sink = &sdev->entity.pads[CSI_FORMATTER_PAD_SINK];
+	int ret;
+
+	formatter->remote_sd = sd;
+
+	ret = v4l2_create_fwnode_links_to_pad(sd, sink, link_flags);
+	if (ret < 0)
+		return ret;
+
+	formatter->remote_pad = media_pad_remote_pad_first(sink)->index;
+
+	return 0;
+}
+
+static const struct v4l2_async_notifier_operations formatter_notify_ops = {
+	.bound = csi_formatter_notify_bound,
+};
+
+static int csi_formatter_async_register(struct csi_formatter *formatter)
+{
+	struct device *dev = formatter->dev;
+	struct v4l2_async_connection *asc;
+	int ret;
+
+	struct fwnode_handle *ep __free(fwnode_handle) =
+		fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0,
+						FWNODE_GRAPH_ENDPOINT_NEXT);
+	if (!ep)
+		return -ENOTCONN;
+
+	v4l2_async_subdev_nf_init(&formatter->notifier, &formatter->sd);
+
+	asc = v4l2_async_nf_add_fwnode_remote(&formatter->notifier, ep,
+					      struct v4l2_async_connection);
+	if (IS_ERR(asc)) {
+		ret = PTR_ERR(asc);
+		goto err_cleanup_notifier;
+	}
+
+	formatter->notifier.ops = &formatter_notify_ops;
+
+	ret = v4l2_async_nf_register(&formatter->notifier);
+	if (ret)
+		goto err_cleanup_notifier;
+
+	ret = v4l2_async_register_subdev(&formatter->sd);
+	if (ret)
+		goto err_unregister_notifier;
+
+	return 0;
+
+err_unregister_notifier:
+	v4l2_async_nf_unregister(&formatter->notifier);
+err_cleanup_notifier:
+	v4l2_async_nf_cleanup(&formatter->notifier);
+	return ret;
+}
+
+static void csi_formatter_async_unregister(struct csi_formatter *formatter)
+{
+	v4l2_async_unregister_subdev(&formatter->sd);
+	v4l2_async_nf_unregister(&formatter->notifier);
+	v4l2_async_nf_cleanup(&formatter->notifier);
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(csi_formatter_pm_ops,
+				 pm_clk_suspend, pm_clk_resume, NULL);
+
+static int csi_formatter_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct csi_formatter *formatter;
+	int ret;
+
+	formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL);
+	if (!formatter)
+		return -ENOMEM;
+
+	formatter->dev = dev;
+
+	formatter->regs = syscon_node_to_regmap(dev->parent->of_node);
+	if (IS_ERR(formatter->regs))
+		return dev_err_probe(dev, PTR_ERR(formatter->regs),
+				     "Failed to get csi formatter regmap\n");
+
+	ret = of_property_read_u32(dev->of_node, "reg", &formatter->reg_offset);
+	if (ret < 0)
+		return dev_err_probe(dev, ret,
+				     "Failed to get csi formatter reg property\n");
+
+	ret = devm_pm_clk_create(dev);
+	if (ret)
+		return ret;
+
+	ret = of_pm_clk_add_clks(dev);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to add clocks\n");
+
+	ret = csi_formatter_subdev_init(formatter);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to initialize formatter subdev\n");
+
+	platform_set_drvdata(pdev, formatter);
+
+	/* Enable runtime PM with autosuspend. */
+	pm_runtime_set_autosuspend_delay(dev, 1000);
+	pm_runtime_use_autosuspend(dev);
+	ret = devm_pm_runtime_enable(dev);
+	if (ret)
+		goto err_cleanup_subdev;
+
+	ret = csi_formatter_async_register(formatter);
+	if (ret < 0) {
+		dev_err_probe(dev, ret, "Failed to register async subdevice\n");
+		goto err_cleanup_subdev;
+	}
+
+	return 0;
+
+err_cleanup_subdev:
+	v4l2_subdev_cleanup(&formatter->sd);
+	media_entity_cleanup(&formatter->sd.entity);
+	return ret;
+}
+
+static void csi_formatter_remove(struct platform_device *pdev)
+{
+	struct csi_formatter *formatter = platform_get_drvdata(pdev);
+
+	csi_formatter_async_unregister(formatter);
+
+	v4l2_subdev_cleanup(&formatter->sd);
+	media_entity_cleanup(&formatter->sd.entity);
+}
+
+static const struct of_device_id csi_formatter_of_match[] = {
+	{ .compatible = "fsl,imx95-csi-formatter" },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, csi_formatter_of_match);
+
+static struct platform_driver csi_formatter_device_driver = {
+	.driver = {
+		.name           = "csi-pixel-formatter",
+		.of_match_table = csi_formatter_of_match,
+		.pm             = pm_ptr(&csi_formatter_pm_ops),
+	},
+	.probe  = csi_formatter_probe,
+	.remove = csi_formatter_remove,
+};
+
+module_platform_driver(csi_formatter_device_driver);
+
+MODULE_AUTHOR("NXP Semiconductor, Inc.");
+MODULE_DESCRIPTION("NXP i.MX95 CSI Pixel Formatter driver");
+MODULE_LICENSE("GPL");

-- 
2.34.1



^ permalink raw reply related

* [PATCH v14 2/3] dt-bindings: clock: imx95-blk-ctl: Define formatter child node schema
From: guoniu.zhou @ 2026-07-21  8:40 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Shawn Guo, Sascha Hauer, Pengutronix Kernel Team,
	Fabio Estevam, Laurent Pinchart, Frank Li, Loic Poulain,
	Bryan O'Donoghue, Abel Vesa, Peng Fan, Michael Turquette,
	Stephen Boyd
  Cc: imx, linux-media, devicetree, linux-arm-kernel, linux-kernel,
	linux-clk, Guoniu Zhou, Frank Li, Krzysztof Kozlowski
In-Reply-To: <20260721-csi_formatter-v14-0-ca239fbb441f@oss.nxp.com>

From: Guoniu Zhou <guoniu.zhou@nxp.com>

The Camera CSR contains control registers for multiple CSI formatter IPs
at different register offsets. Each formatter is an independent hardware
block with its own clock input and media pipeline connection.

Define schema to allow formatter child nodes under nxp,imx95-camera-csr,
with 'reg' property specifying the formatter's register offset within the
CSR address space.

Signed-off-by: Guoniu Zhou <guoniu.zhou@nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
Changes in v14:
- Add Reviewed-by tag from Laurent Pinchart

Changes in v13:
- Add Reviewed-by tag from Frank and Krzysztof

Changes in v11:
- Move properties to top-level and use if:then:else (Krzysztof/Frank)

Changes in v10:
- Use single quotes for regex pattern to be consistent (Krzysztof Kozlowski)
- Add formatter subnode binding and camera-csr syscon example
- Update commit title and message

Changes in v9:
- New patch to address the issue of formatter acting as a child node of syscon
---
 .../bindings/clock/nxp,imx95-blk-ctl.yaml          | 71 ++++++++++++++++++++++
 1 file changed, 71 insertions(+)

diff --git a/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml b/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml
index 27403b4c52d6..fbbf1b3f1790 100644
--- a/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml
+++ b/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml
@@ -39,6 +39,18 @@ properties:
       ID in its "clocks" phandle cell. See
       include/dt-bindings/clock/nxp,imx95-clock.h
 
+  '#address-cells':
+    const: 1
+
+  '#size-cells':
+    const: 1
+
+patternProperties:
+  '^formatter@[0-9a-f]+$':
+    type: object
+    $ref: /schemas/media/fsl,imx95-csi-formatter.yaml#
+    unevaluatedProperties: false
+
 required:
   - compatible
   - reg
@@ -46,6 +58,23 @@ required:
   - power-domains
   - clocks
 
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: nxp,imx95-camera-csr
+    then:
+      required:
+        - '#address-cells'
+        - '#size-cells'
+    else:
+      properties:
+        '#address-cells': false
+        '#size-cells': false
+      patternProperties:
+        '^formatter@[0-9a-f]+$': false
+
 additionalProperties: false
 
 examples:
@@ -57,4 +86,46 @@ examples:
       clocks = <&scmi_clk 114>;
       power-domains = <&scmi_devpd 21>;
     };
+
+  - |
+    #include <dt-bindings/clock/nxp,imx95-clock.h>
+
+    syscon@4ac10000 {
+      compatible = "nxp,imx95-camera-csr", "syscon";
+      reg = <0x4ac10000 0x10000>;
+      #address-cells = <1>;
+      #size-cells = <1>;
+      #clock-cells = <1>;
+      clocks = <&scmi_clk 62>;
+      power-domains = <&scmi_devpd 3>;
+
+      formatter@20 {
+        compatible = "fsl,imx95-csi-formatter";
+        reg = <0x20 0x100>;
+        clocks = <&cameramix_csr IMX95_CLK_CAMBLK_CSI2_FOR0>;
+        power-domains = <&scmi_devpd 3>;
+
+        ports {
+          #address-cells = <1>;
+          #size-cells = <0>;
+
+          port@0 {
+            reg = <0>;
+
+            endpoint {
+              remote-endpoint = <&mipi_csi_0_out>;
+            };
+
+          };
+
+          port@1 {
+            reg = <1>;
+
+            endpoint {
+              remote-endpoint = <&isi_in_2>;
+            };
+          };
+        };
+      };
+    };
 ...

-- 
2.34.1



^ permalink raw reply related

* [PATCH v14 1/3] media: dt-bindings: Add CSI Pixel Formatter DT bindings
From: guoniu.zhou @ 2026-07-21  8:40 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Shawn Guo, Sascha Hauer, Pengutronix Kernel Team,
	Fabio Estevam, Laurent Pinchart, Frank Li, Loic Poulain,
	Bryan O'Donoghue, Abel Vesa, Peng Fan, Michael Turquette,
	Stephen Boyd
  Cc: imx, linux-media, devicetree, linux-arm-kernel, linux-kernel,
	linux-clk, Guoniu Zhou, Frank Li, Krzysztof Kozlowski
In-Reply-To: <20260721-csi_formatter-v14-0-ca239fbb441f@oss.nxp.com>

From: Guoniu Zhou <guoniu.zhou@nxp.com>

The i.MX95 CSI pixel formatting module uses packet info, pixel and
non-pixel data from the CSI-2 host controller and reformat them to
match Pixel Link(PL) definition.

Signed-off-by: Guoniu Zhou <guoniu.zhou@nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
Changes in v14:
- Add Rb tag from Laurent pinchart

Changes in v11:
- Add Rb tags from Frank Li and Krzysztof Kozlowski

Changes in v10:
- Drop syscon parent node from example
- Drop Reviewed-by tags from Frank and Krzysztof due to binding changes
- Add description for reg property
- Add space after formatter@20 before opening brace in example
- Enhance the port description with more detailed information
- Delete the blank line immediately following the endpoint in example

Changes in v9:
- Use direct node instead of syscon wrapper in example

Changes in v8:
- Use standard port reference instead of video-interfaces.yaml
- Add parent syscon node in example to show device integration
- Add required constraints for port@0 and port@1 in ports node

Changes in v7:
- Change compatible to imx95-csi-formatter as IP is i.MX95 specific per Marco's suggestion
  Link: https://lore.kernel.org/linux-media/20260511-csi_formatter-v6-0-01028e312e2b@oss.nxp.com/T/#mcd135b3de179b3cb69daa1fd6e0e8e27c85b3332
---
 .../bindings/media/fsl,imx95-csi-formatter.yaml    | 88 ++++++++++++++++++++++
 1 file changed, 88 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/fsl,imx95-csi-formatter.yaml b/Documentation/devicetree/bindings/media/fsl,imx95-csi-formatter.yaml
new file mode 100644
index 000000000000..58c4e1cc056b
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/fsl,imx95-csi-formatter.yaml
@@ -0,0 +1,88 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/fsl,imx95-csi-formatter.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: i.MX95 CSI Pixel Formatter
+
+maintainers:
+  - Guoniu Zhou <guoniu.zhou@nxp.com>
+
+description:
+  The CSI pixel formatting module found on i.MX95 uses packet info, pixel
+  and non-pixel data from the CSI-2 host controller and reformat them to
+  match Pixel Link(PL) definition.
+
+properties:
+  compatible:
+    const: fsl,imx95-csi-formatter
+
+  reg:
+    maxItems: 1
+    description: Register offset and size within the parent syscon
+
+  clocks:
+    maxItems: 1
+
+  power-domains:
+    maxItems: 1
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/properties/port
+        description:
+          Input port, connects to MIPI CSI-2 receiver output (IDI interface)
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        description:
+          Output port, connects to ISI input via Pixel Link (PL)
+
+    required:
+      - port@0
+      - port@1
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - power-domains
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/nxp,imx95-clock.h>
+
+    formatter@20 {
+        compatible = "fsl,imx95-csi-formatter";
+        reg = <0x20 0x100>;
+        clocks = <&cameramix_csr IMX95_CLK_CAMBLK_CSI2_FOR0>;
+        power-domains = <&scmi_devpd 3>;
+
+        ports {
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            port@0 {
+                reg = <0>;
+
+                endpoint {
+                    remote-endpoint = <&mipi_csi_0_out>;
+                };
+            };
+
+            port@1 {
+                reg = <1>;
+
+                endpoint {
+                    remote-endpoint = <&isi_in_2>;
+                };
+            };
+        };
+    };

-- 
2.34.1



^ permalink raw reply related

* [PATCH v14 0/3] media: nxp: Add CSI Pixel Formatter support
From: guoniu.zhou @ 2026-07-21  8:40 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Shawn Guo, Sascha Hauer, Pengutronix Kernel Team,
	Fabio Estevam, Laurent Pinchart, Frank Li, Loic Poulain,
	Bryan O'Donoghue, Abel Vesa, Peng Fan, Michael Turquette,
	Stephen Boyd
  Cc: imx, linux-media, devicetree, linux-arm-kernel, linux-kernel,
	linux-clk, Guoniu Zhou, Frank Li, Krzysztof Kozlowski

CSI Pixel Formatter is a module found on i.MX95. It could unpack the
pixels received by the formatter and reformat them to meet the pixel
link format requirement.

This patch series adds a new V4L2 driver for CSI Pixel Formatter.

Background
----------
The Camera CSR binding was upstreamed during the pre-silicon phase when
the hardware integration details were not fully finalized. At that time,
the syscon binding focused on the CSR's primary role as a shared register
provider for clock gating, QoS, and error handling across Camera domain IPs.

However, the Camera CSR hardware is actually a mixed-function register
container with varying control levels over different IP blocks:
- ISP/CSI: Clock gating only
- ISI: QoS configuration
- CSI: Buffer overflow handling
- LPCAC: Error handling only
- ISP: Pixel link selection
- Formatter: Complete IP control (full register set)

Why Child Nodes Now
-------------------
The CSI formatter is unique among these IPs - it's an independent hardware
block whose complete control registers reside within the CSR address
space, not just a few configuration bits. This architectural detail was
not reflected in the original binding because:

1. The pre-silicon binding focused on simple register-level controls
2. The syscon pattern for complete IP control was not initially considered

Discussion with original CSR author(Peng Fan) confirmed this reflects the
intended hardware design that wasn't fully captured during pre-silicon
upstreaming.

v4l2-compliance 1.28.1-5233, 64 bits, 64-bit time_t
v4l2-compliance SHA: fc15e229d9d3 2024-07-23 19:22:15

Compliance test for device /dev/v4l-subdev9:

Driver Info:
	Driver version   : 7.1.0
	Capabilities     : 0x00000002
		Streams Support
	Client Capabilities: 0x0000000000000003
streams interval-uses-which
Required ioctls:
	test VIDIOC_SUDBEV_QUERYCAP: OK
	test invalid ioctls: OK

Allow for multiple opens:
	test second /dev/v4l-subdev9 open: OK
	test VIDIOC_SUBDEV_QUERYCAP: OK
	test for unlimited opens: OK

Debug ioctls:
	test VIDIOC_LOG_STATUS: OK (Not Supported)

Input ioctls:
	test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
	test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
	test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
	test VIDIOC_ENUMAUDIO: OK (Not Supported)
	test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
	test VIDIOC_G/S_AUDIO: OK (Not Supported)
	Inputs: 0 Audio Inputs: 0 Tuners: 0

Output ioctls:
	test VIDIOC_G/S_MODULATOR: OK (Not Supported)
	test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
	test VIDIOC_ENUMAUDOUT: OK (Not Supported)
	test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
	test VIDIOC_G/S_AUDOUT: OK (Not Supported)
	Outputs: 0 Audio Outputs: 0 Modulators: 0

Input/Output configuration ioctls:
	test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
	test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
	test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
	test VIDIOC_G/S_EDID: OK (Not Supported)

Sub-Device routing ioctls:
	test Try VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING: OK
	test Active VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING: OK

Control ioctls:
	test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
	test VIDIOC_QUERYCTRL: OK (Not Supported)
	test VIDIOC_G/S_CTRL: OK (Not Supported)
	test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
	test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
	test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
	Standard Controls: 0 Private Controls: 0

Format ioctls:
	test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK (Not Supported)
	test VIDIOC_G/S_PARM: OK (Not Supported)
	test VIDIOC_G_FBUF: OK (Not Supported)
	test VIDIOC_G_FMT: OK (Not Supported)
	test VIDIOC_TRY_FMT: OK (Not Supported)
	test VIDIOC_S_FMT: OK (Not Supported)
	test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
	test Cropping: OK (Not Supported)
	test Composing: OK (Not Supported)
	test Scaling: OK (Not Supported)

Codec ioctls:
	test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
	test VIDIOC_G_ENC_INDEX: OK (Not Supported)
	test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
	test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK (Not Supported)
	test CREATE_BUFS maximum buffers: OK
	test VIDIOC_REMOVE_BUFS: OK
	test VIDIOC_EXPBUF: OK (Not Supported)
	test Requests: OK (Not Supported)

Total for device /dev/v4l-subdev9: 47, Succeeded: 47, Failed: 0, Warnings: 0

Signed-off-by: Guoniu Zhou <guoniu.zhou@nxp.com>
---
Changes in v14:
- [3/3] Use select MFD_SYSCON instead of depends on MFD_SYSCON (Laurent)
- [3/3] Remove unused v4l2-ctrls.h and v4l2-event.h includes (Laurent)
- [3/3] Rename csi_sd to remote_sd and group with remote_pad (Laurent)
- [3/3] Move reg_offset after clk to group generic resources (Laurent)
- [3/3] Remove mutex lock as enable/disable_streams hold state lock (Laurent)
- [3/3] Rename formatter_dt_to_index_map to csi_formatter_dt_to_index_map (Laurent)
- [3/3] Rename formats to csi_formatter_formats (Laurent)
- [3/3] Declare loop variables in for-loop and add braces (Laurent)
- [3/3] Simplify set_fmt, rename sdformat to format, drop NULL check (Laurent)
- [3/3] Set field to V4L2_FIELD_NONE as interlaced not yet supported (Laurent)
- [3/3] Add streaming check in set_fmt to match set_routing (Laurent)
- [3/3] Add WARN_ON for unsupported data type as it indicates driver bug (Laurent)
- [3/3] Return -EPIPE instead of -EINVAL for invalid VC (Laurent)
- [3/3] Refactor ENOIOCTLCMD handling and remove redundant pad check (Laurent)
- [3/3] Use strscpy instead of snprintf for sd->name (Laurent)
- [3/3] Remove redundant dev_dbg in notify_bound (Laurent)
- [3/3] Simplify remote_pad assignment and drop NULL check (Laurent)
- [3/3] Read reg property directly into formatter->reg_offset (Laurent)
- [3/3] Store formatter pointer in drvdata to avoid sd_to_formatter calls (Laurent)
- [3/3] Enable runtime PM autosuspend with 1s delay (Laurent)
- [3/3] Use PM clock framework instead of manual clock handling (Loic)
- [3/3] Add Reviewed-by tag from Loic Poulain
- [3/2] Add Reviewed-by tag from Laurent Pinchart
- [3/1] Add Reviewed-by tag from Laurent Pinchart
- Link to v13: https://lore.kernel.org/r/20260720-csi_formatter-v13-0-4dc9a80e4cfd@oss.nxp.com

Changes in v13:
- [2/3] Add Reviewed-by tag from Frank and Krzysztof
- [3/3] Add Reviewed-by tag from Frank
- [3/3] Replace pr_warn_once() with dev_warn() in csi_formatter_get_index_by_dt()
  to provide device context and warn on every occurrence (Loic)
- [3/3] Add WARN_ON() check for csi_formatter_find_format() return value in
  start/stop_stream functions to catch unexpected NULL (Loic)
- [3/3] Use regmap_set_bits()/regmap_clear_bits() instead of read-modify-write
  pattern and remove unused csi_formatter_read/write helper functions (Loic)
- Link to v12: https://lore.kernel.org/r/20260716-csi_formatter-v12-0-2601d17fc4af@oss.nxp.com

Changes in v12:
- Fix stream ID handling: iterate routing table instead of assuming
  stream ID equals loop index (0-7)
- Remove stream_to_vc[] array: derive VC from routing table and frame
  descriptor on each start/stop operation
- Remove V4L2_SUBDEV_FL_HAS_EVENTS flag since driver does not generate events
- Support stream IDs 0-63 by using BIT_ULL() for stream masks
- Add get_frame_desc call in stop_stream with proper error handling
- Add csi_formatter_read() helper function for register reads
- Use read-modify-write for CSI_VC_PIXEL_DATA_TYPE register to support
  multiplexed streams sharing the same virtual channel
- Use route->sink_pad instead of hardcoded CSI_FORMATTER_PAD_SINK
- Write back coerced format in set_fmt before propagating to source stream
- Drop Frank's Reviewed-by tag due to significant changes, requesting re-review
- Link to v11: https://lore.kernel.org/r/20260623-csi_formatter-v11-0-a792fe9c1502@oss.nxp.com

Changes in v11:
- Drop [PATCH v10 1/4] dt-bindings: clock: imx95-blk-ctl: Use single quotes consistently (Krzysztof)
- Move properties to top-level and use if:then:else (Krzysztof/Frank)
- Link to v10: https://lore.kernel.org/r/20260618-csi_formatter-v10-0-f23830312ba5@oss.nxp.com

Changes in v10:
- Rebase to latest media/next
- [NEW PATCH] Use single quotes consistently (Krzysztof Kozlowski)
- Drop syscon parent node from example
- Drop Reviewed-by tags from Frank and Krzysztof due to binding changes,
  requesting re-review
- Add description for reg property
- Add space after formatter@20 before opening brace in example
- Enhance the port description with more detailed information
- Delete the blank line immediately following the endpoint in example
- Use single quotes for regex pattern to be consistent (Krzysztof Kozlowski)
- Add formatter subnode binding and camera-csr syscon example
- Update commit title and message
- Use u8 for vc in csi_formatter_get_vc() and drop vc < 0 check
- Add MFD_SYSCON dependency to Kconfig
- Fix stream/VC mapping potential mismatch in start/stop_stream functions
- Link to v9: https://lore.kernel.org/r/20260526-csi_formatter-v9-0-ca3d8c334c39@oss.nxp.com

Changes in v9:
- [NEW PATCH] Fix formatter as syscon child node issue
- Link to v8: https://lore.kernel.org/r/20260525-csi_formatter-v8-0-6b646231224b@oss.nxp.com

Changes in v8:
- Rebase to latest media/next
- Use standard port reference instead of video-interfaces.yaml
- Add parent syscon node in example to show device integration
- Remove fmt field and look up format from subdev state instead
- Unify function and structure naming to use csi_formatter_ prefix
- Remove misleading alignment comment from set_fmt function
- Optimize get_frame_desc to call once per start_stream
- Replace V4L2_FRAME_DESC_ENTRY_MAX with CSI_FORMATTER_VC_NUM in loops
- Remove redundant debug message in enable_streams
- Use MEDIA_PAD_FL_MUST_CONNECT flag instead of manual link check
- Link to v7: https://lore.kernel.org/r/20260518-csi_formatter-v7-0-562b750557e3@oss.nxp.com

Changes in v7:
- Change compatible to imx95-csi-formatter as IP is i.MX95 specific per Marco's suggestion
  Link: https://lore.kernel.org/linux-media/20260511-csi_formatter-v6-0-01028e312e2b@oss.nxp.com/T/#mcd135b3de179b3cb69daa1fd6e0e8e27c85b3332
- Update references from imx9 to imx95 for consistency with dt-bindings
- Enable PM runtime before async registration
- Link to v6: https://lore.kernel.org/r/20260511-csi_formatter-v6-0-01028e312e2b@oss.nxp.com

Changes in v6:
- Rebase to latest media/next
- Update v4l2-compliace test
- Remove unused header includes
- Unify macro naming: VCx/VCX -> VC and parameter x -> vc
- Remove unused format field from csi_formatter struct
- Use compact initialization for formats array
- Make find_csi_format() return NULL instead of default format
- Use unsigned int for array index in find_csi_format()
- Add err_ prefix to error handling labels
- Add v4l2_subdev_cleanup() and reorder cleanup sequence
- Update enable_streams debug output format
- Rename VC_MAX to VC_NUM and fix boundary check
- Update CSI formatter Kconfig description
- Use v4l2_subdev_get_frame_desc_passthrough() helper
- Fix error paths in async registration and probe
- Add mutex to protect enabled_streams
- Switch to devm_pm_runtime_enable()
- Remove redundant num_routes check in set_routing
- Optimize get_index_by_dt() and add warning for unsupported type
- csi_formatter_start/stop_stream: Process all streams in mask
- Link to v5: https://lore.kernel.org/r/20260123-csi_formatter-v5-0-d5b803f867bf@nxp.com

Changes in v5:
- Remove CSI_FORMATTER_DRV_NAME macro since only use once.
- Remove sd->owner = THIS_MODULE;
- Simplify code by using DEFINE_RUNTIME_DEV_PM_OPS macro.
- Link to v4: https://lore.kernel.org/r/20260122-csi_formatter-v4-0-6f6fcad1c33a@nxp.com

Changes in v4:
- Rebase to latest media/next.
- Add comments to describe the index field in formatter_dt_to_index_map array.
- Link to v3: https://lore.kernel.org/r/20251219-csi_formatter-v3-0-8680d6d87091@nxp.com

Changes in v3:
- Rename nxp,imx9-csi-formatter.yaml to fsl,imx9-csi-formatter.yaml.
- Drop clock-names property.
- Drop macro IMX95_PD_CAMERA definition and use a constant directly.
  [PATCH 1/2] media: dt-bindings: Add CSI Pixel Formatter DT bindings
- Remove the assignment driver.owner = THIS_MODULE.
- Assign struct fwnode_handle *ep __free(fwnode_handle) when definition.
- Update yaml file name for csi formatter in MAINTAINERS.
  [PATCH 2/2] media: nxp: Add i.MX9 CSI pixel formatter v4l2 driver
- Link to v2: https://lore.kernel.org/r/20251217-csi_formatter-v2-0-62168af80210@nxp.com

Changes in v2:
- Delete "|" for description key. 
- Add empty line between child node and property.
- Delete labels for endpoint of child nodes.
  [PATCH 1/2] media: dt-bindings: Add CSI Pixel Formatter DT bindings

- Update commit message.
- Use the value defined by bellow macros directly since they are used only once.
  #define CSI_FORMATTER_DEF_MBUS_CODE	MEDIA_BUS_FMT_UYVY8_1X16
  #define CSI_FORMATTER_DEF_PIX_WIDTH	1920U
  #define CSI_FORMATTER_DEF_PIX_HEIGHT	1080U
  #define CSI_FORMATTER_MAX_PIX_WIDTH	0xffff
  #define CSI_FORMATTER_MAX_PIX_HEIGHT	0xffff
- Use macro pm_ptr() to fix build warning when CONFIG_PM is disabled. 
- Finish route loop by break statement, instead of goto.
- Return dev_err_probe() when meet errors in probe() function instead of dev_err().
- Remove MODULE_ALIAS().
- Refine .enable(.dsable)_stream callback implementation, include bellow changes:
  Add stream checking.
  Fix potential pm runtime count unbalance issue.
  Add stop stream error handling when enabling remote subdev stream.
- Use __free(fwnode_handle) to drop reference to a device node automatically.
  [PATCH 2/2] media: nxp: Add i.MX9 CSI pixel formatter v4l2 driver

- Link to v1: https://lore.kernel.org/r/20251203-csi_formatter-v1-0-eb9e1147b49e@nxp.com

---
Guoniu Zhou (3):
      media: dt-bindings: Add CSI Pixel Formatter DT bindings
      dt-bindings: clock: imx95-blk-ctl: Define formatter child node schema
      media: nxp: Add i.MX95 CSI pixel formatter v4l2 driver

 .../bindings/clock/nxp,imx95-blk-ctl.yaml          |  71 ++
 .../bindings/media/fsl,imx95-csi-formatter.yaml    |  88 +++
 MAINTAINERS                                        |   8 +
 drivers/media/platform/nxp/Kconfig                 |  16 +
 drivers/media/platform/nxp/Makefile                |   1 +
 drivers/media/platform/nxp/imx95-csi-formatter.c   | 751 +++++++++++++++++++++
 6 files changed, 935 insertions(+)
---
base-commit: 06cb687a5132fcffe624c0070576ab852ac6b568
change-id: 20251125-csi_formatter-e6d29316dce6

Best regards,
-- 
Guoniu Zhou <guoniu.zhou@oss.nxp.com>



^ permalink raw reply

* Re: [RFC PATCH 2/2] KVM: arm64: Support BBM level 3
From: Mostafa Saleh @ 2026-07-21  8:34 UTC (permalink / raw)
  To: Marc Zyngier
  Cc: Oliver Upton, linux-kernel, kvmarm, linux-arm-kernel, seiden,
	joey.gouly, suzuki.poulose, yuzenghui, catalin.marinas, will,
	vdonnefort, tabba
In-Reply-To: <8633xcg87z.wl-maz@kernel.org>

On Tue, Jul 21, 2026 at 07:36:16AM +0100, Marc Zyngier wrote:
> On Mon, 20 Jul 2026 21:41:04 +0100,
> Mostafa Saleh <smostafa@google.com> wrote:
> > 
> > On Sat, Jul 18, 2026 at 8:55 PM Mostafa Saleh <smostafa@google.com> wrote:
> > >
> > > Hi Oliver,
> > >
> > > On Fri, Jul 17, 2026 at 01:56:03PM -0700, Oliver Upton wrote:
> > > > Hi Mostafa,
> > > >
> > > > On Fri, Jul 17, 2026 at 01:09:00PM +0000, Mostafa Saleh wrote:
> > > > > If the system supports hardware Break-Before-Make (BBM) level 3, use it
> > > > > to replace stage-2 PTEs directly instead of falling back to the software
> > > > > break-before-make sequence.
> > > > >
> > > > > 1) Get a reference count on the containing table for the new PTE.
> > > > > 2) Atomically update the PTE with the new valid descriptor.
> > > > > 3) Invalidate the TLB for the old PTE.
> > > > > 4) Drop the reference count holding the old PTE.
> > > > >
> > > > > One interesting case, as BBML3 will update the PTE atomically, it
> > > > > can only know it raced with another core at the point of the cmpxchg
> > > > > failing, unlike the SW implementation which locks the PTE first.
> > > > > And as we must issue CMOs to the new mapped page before the update,
> > > > > that means with BBML3 racing cores will issue redundant CMOs,
> > > >
> > > > I'd rather we just predicate BBML3-style transformations on an
> > > > implementation having FEAT_S2FWB and DIC. You can definitely come along
> > > > later and enable it when using a stage-2 in an SMMU makes this
> > > > mandatory, possibly at the expense of some extra CMOs.
> > >
> > > Makes sense, I will do that in v2.
> > >
> > 
> > Looking into this, I see some existing inefficiencies (or maybe I do
> > not understand it well)
> > - pKVM still do some work for dcache with FWB I posted a patch for that:
> > https://lore.kernel.org/all/20260720203529.1276355-1-smostafa@google.com/
> > 
> > - KVM does not elide the icache maintainence with DIC, it seems we
> > should have something similar for the FWB check in
> > __clean_dcache_guest_page() as
> > 
> > diff --git a/arch/arm64/include/asm/kvm_mmu.h b/arch/arm64/include/asm/kvm_mmu.h
> > index 6eae7e7e2a68..d0a4ae66b069 100644
> > --- a/arch/arm64/include/asm/kvm_mmu.h
> > +++ b/arch/arm64/include/asm/kvm_mmu.h
> > @@ -247,6 +247,9 @@ static inline size_t __invalidate_icache_max_range(void)
> > 
> >  static inline void __invalidate_icache_guest_page(void *va, size_t size)
> >  {
> > +       if (cpus_have_final_cap(ARM64_HAS_CACHE_DIC))
> > +               return;
> > +
> >         /*
> >          * Blow the whole I-cache if it is aliasing (i.e. VIPT) or the
> >          * invalidation range exceeds our arbitrary limit on invadations by
> > 
> > or I am missing something?
> 
> The latter. The shortcuts are in the individual helpers.
> We have:
> 
> 	static __always_inline void icache_inval_all_pou(void)
> 	{
> 		if (alternative_has_cap_unlikely(ARM64_HAS_CACHE_DIC))
> 			return;
> 
> 		asm("ic	ialluis");
> 		dsb(ish);
> 	}
> 
> and
> 
> 	SYM_FUNC_START(icache_inval_pou)
> 	alternative_if ARM64_HAS_CACHE_DIC
>         	isb
>         	ret
> 	alternative_else_nop_endif
> 
>         	invalidate_icache_by_line x0, x1, x2, x3
> 	        ret
> 	SYM_FUNC_END(icache_inval_pou)
> 
> it's not completely obvious to me why we have an ISB in
> icache_inval_pou(), but at least the invalidation elision is already
> there.

Ah, sorry about the noise! That’s why I shouldn't send late emails :)

I am not sure why the isb is needed, at least for KVM it should be
OK, besides that kexec uses it.

Thanks,
Mostafa

> 
> Thanks,
> 
> 	M.
> 
> -- 
> Without deviation from the norm, progress is not possible.


^ permalink raw reply

* Re: [RFC PATCH 31/36] arm64: nmi: Add handling of superpriority interrupts as NMIs
From: Jinjie Ruan @ 2026-07-21  8:33 UTC (permalink / raw)
  To: Vladimir Murzin, linux-arm-kernel
  Cc: mark.rutland, maz, Mark Brown, catalin.marinas, will
In-Reply-To: <20260709121333.23507-32-vladimir.murzin@arm.com>



On 7/9/2026 8:13 PM, Vladimir Murzin wrote:
> From: Mark Brown <broonie@kernel.org>
> 
> Our goal with superpriority interrupts is to use them as NMIs, taking
> advantage of the much smaller regions where they are masked to allow
> prompt handling of the most time-critical interrupts.
> 
> When an interrupt is configured with superpriority, we enter EL1 as we
> do for any other interrupt. The presence of a superpriority interrupt
> is indicated by a status bit in ISR_EL1. We check this bit before
> unmasking interrupts in elX_interrupt(), and if a superpriority
> interrupt is pending, we handle it as an NMI. Otherwise, normal
> interrupts are handled as usual.
> 
> Since superpriority interrupts are always handled as NMIs, the
> interrupt controller can rely on in_nmi() to distinguish them from
> ordinary interrupts.
> 
> Enable IPIs to use superpriority interrupts as NMIs, matching the
> existing pseudo-NMI behaviour.
> Signed-off-by: Mark Brown <broonie@kernel.org>
> Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
> Signed-off-by: Vladimir Murzin <vladimir.murzin@arm.com>
> ---
>  arch/arm64/include/asm/entry-common.h |  7 ++++
>  arch/arm64/kernel/entry-common.c      | 59 ++++++++++++++++++---------
>  arch/arm64/kernel/smp.c               |  2 +-
>  3 files changed, 48 insertions(+), 20 deletions(-)
> 
> diff --git a/arch/arm64/include/asm/entry-common.h b/arch/arm64/include/asm/entry-common.h
> index 73d82a8d8e95..0681ba91ac3b 100644
> --- a/arch/arm64/include/asm/entry-common.h
> +++ b/arch/arm64/include/asm/entry-common.h
> @@ -37,6 +37,13 @@ static inline bool arch_irqentry_exit_need_resched(void)
>  	if (system_uses_irq_prio_masking() && read_sysreg(daif))
>  		return false;
>  
> +	/*
> +	 * If AllInt is set then we must have handled an NMI, so skip
> +	 * preemption
> +	 */
> +	if (system_uses_nmi() && read_sysreg_s(SYS_ALLINT))
> +		return false;
> +
>  	/*
>  	 * Preempting a task from an IRQ means we leave copies of PSTATE
>  	 * on the stack. cpufeature's enable calls may modify PSTATE, but
> diff --git a/arch/arm64/kernel/entry-common.c b/arch/arm64/kernel/entry-common.c
> index a13653b228b7..de71d5a3a6a1 100644
> --- a/arch/arm64/kernel/entry-common.c
> +++ b/arch/arm64/kernel/entry-common.c
> @@ -525,8 +525,8 @@ asmlinkage void noinstr el1h_64_sync_handler(struct pt_regs *regs)
>  	arm64_debug_exc_context(CRITICAL_CONTEXT);
>  }
>  
> -static __always_inline void __el1_pnmi(struct pt_regs *regs,
> -				       void (*handler)(struct pt_regs *))
> +static __always_inline void __el1_nmi(struct pt_regs *regs,
> +				      void (*handler)(struct pt_regs *))
>  {
>  	arm64_exc_hwstate_t hwstate;
>  	irqentry_state_t state;
> @@ -545,7 +545,10 @@ static __always_inline void __el1_irq(struct pt_regs *regs,
>  
>  	state = arm64_enter_from_kernel_mode(regs);
>  
> -	arm64_unmask_exc_context(NONMI_CONTEXT);
> +	if (system_uses_nmi())
> +		arm64_unmask_exc_context(NOIRQ_CONTEXT);
> +	else
> +		arm64_unmask_exc_context(NONMI_CONTEXT);

if (gic_supports_pseudo_nmis())
	arm64_unmask_exc_context(NONMI_CONTEXT);
else
	arm64_unmask_exc_context(NOIRQ_CONTEXT);

>  
>  	irq_enter_rcu();
>  	do_interrupt_handler(regs, handler);
> @@ -565,8 +568,11 @@ static __always_inline void __el1_irq(struct pt_regs *regs,
>  static void noinstr el1_interrupt(struct pt_regs *regs,
>  				  void (*handler)(struct pt_regs *))
>  {
> -	if (IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) && regs_irqs_disabled(regs))
> -		__el1_pnmi(regs, handler);
> +	/* Is there a NMI to handle? */
> +	if (regs_irqs_disabled(regs))
> +		__el1_nmi(regs, handler);
> +	else if (system_uses_nmi() && (read_sysreg(isr_el1) & (ISR_EL1_IS | ISR_EL1_FS)))
> +		__el1_nmi(regs, handler);

I think it would be more readable to add a helper below.

static inline bool is_nmi(regs)
{
	if (regs_irqs_disabled(regs))
		return true;

	if (system_uses_nmi())
		return read_sysreg(isr_el1) & (ISR_EL1_IS | ISR_EL1_FS);

	return false;
}

>  static void noinstr el1_interrupt(struct pt_regs *regs,
>  				  void (*handler)(struct pt_regs *))
>  {
> -	if (IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) && regs_irqs_disabled(regs))
> -		__el1_pnmi(regs, handler);
> +	/* Is there a NMI to handle? */
> +	if (is_nmi(regs))
> +		__el1_nmi(regs, handler);

>  	else
>  		__el1_irq(regs, handler);
>  
> @@ -906,24 +912,39 @@ asmlinkage void noinstr el0t_64_sync_handler(struct pt_regs *regs)
>  static void noinstr el0_interrupt(struct pt_regs *regs,
>  				  void (*handler)(struct pt_regs *))
>  {
> -	arm64_enter_from_user_mode(regs);
> -
> -	arm64_unmask_exc_context(NONMI_CONTEXT);
> -
>  	if (regs->pc & BIT(55))
>  		arm64_apply_bp_hardening();
>  
> -	irq_enter_rcu();
> -	do_interrupt_handler(regs, handler);
> -	irq_exit_rcu();
> +	/* Is there a NMI to handle? */
> +	if (system_uses_nmi() && (read_sysreg(isr_el1) & (ISR_EL1_IS | ISR_EL1_FS))) {
> +		irqentry_state_t state;
> +		arm64_exc_hwstate_t hwstate;
> +
> +		state = irqentry_nmi_enter(regs);
> +		hwstate = arm64_unmask_exc_context(NONMI_CONTEXT);
> +		do_interrupt_handler(regs, handler);
> +		arm64_mask_exc_context(hwstate);
> +		irqentry_nmi_exit(regs, state);
> +	} else {
> +		arm64_enter_from_user_mode(regs);
> +
> +		if (system_uses_nmi())
> +			arm64_unmask_exc_context(NOIRQ_CONTEXT);
> +		else
> +			arm64_unmask_exc_context(NONMI_CONTEXT);

if (gic_supports_pseudo_nmis())
	arm64_unmask_exc_context(NONMI_CONTEXT);
else
	arm64_unmask_exc_context(NOIRQ_CONTEXT);

> +
> +		irq_enter_rcu();
> +		do_interrupt_handler(regs, handler);
> +		irq_exit_rcu();
> +		/*
> +		 * For the same reason as in el1_irq() we effectivly
> +		 * have NOIRQ_CONTEXT on return from handler - keep
> +		 * track of it
> +		 */
> +		arm64_debug_exc_context(NOIRQ_CONTEXT);
> +		arm64_exit_to_user_mode(regs, arm64_exc_hwstate_of_context(NOIRQ_CONTEXT));

Here we assert that we are in NOIRQ_CONTEXT, and then we switch to
NOIRQ_CONTEXT again. Is this redundant?

> +	}

Can you split them into two sub-functions, such as el0_nmi and el0_irq
as shown below, so that it looks clearer?

static inline void el0_nmi(struct pt_regs *regs, void (*handler)(struct
pt_regs *))
{
	arm64_exc_hwstate_t hwstate;
	irqentry_state_t state;
	
	state = irqentry_nmi_enter(regs);
	hwstate = arm64_unmask_exc_context(NONMI_CONTEXT);
	do_interrupt_handler(regs, handler);
	arm64_mask_exc_context(hwstate);
	irqentry_nmi_exit(regs, state);
}

static inline void el0_irq(struct pt_regs *regs, void (*handler)(struct
pt_regs *))
{
	arm64_enter_from_user_mode(regs);

	if (gic_supports_pseudo_nmis())
		arm64_unmask_exc_context(NONMI_CONTEXT);
	else
		arm64_unmask_exc_context(NOIRQ_CONTEXT);

	irq_enter_rcu();
	do_interrupt_handler(regs, handler);
	irq_exit_rcu();
	/*
	 * For the same reason as in el1_irq() we effectivly
    	 * have NOIRQ_CONTEXT on return from handler - keep
         * track of it
      	 */
	arm64_debug_exc_context(NOIRQ_CONTEXT);			
	arm64_exit_to_user_mode(regs, 	
arm64_exc_hwstate_of_context(NOIRQ_CONTEXT));
}

static void noinstr el0_interrupt(struct pt_regs *regs,
 				  void (*handler)(struct pt_regs *))
{
	if (is_nmi(regs))
		el0_nmi(regs);
	else
		el0_irq(regs);
}

otherwise, LGTM
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>


>  
> -	/*
> -	 * For the same reason as in el1_irq() we effectivly
> -	 * have NOIRQ_CONTEXT on return from handler - keep
> -	 * track of it
> -	 */
> -	arm64_debug_exc_context(NOIRQ_CONTEXT);
> -	arm64_exit_to_user_mode(regs, arm64_exc_hwstate_of_context(NOIRQ_CONTEXT));
>  	arm64_debug_exc_context(CRITICAL_CONTEXT);
>  }
>  
> diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
> index 257d50529d14..80e35a8e5c8f 100644
> --- a/arch/arm64/kernel/smp.c
> +++ b/arch/arm64/kernel/smp.c
> @@ -1035,7 +1035,7 @@ static void smp_cross_call(const struct cpumask *target, unsigned int ipinr)
>  
>  static bool ipi_should_be_nmi(enum ipi_msg_type ipi)
>  {
> -	if (!system_uses_irq_prio_masking())
> +	if (!system_uses_nmi() && !system_uses_irq_prio_masking())
>  		return false;
>  
>  	switch (ipi) {



^ permalink raw reply

* Re: [PATCH 3/4] drm/rockchip: lvds: add RK3568 support
From: Rok Markovič @ 2026-07-21  8:31 UTC (permalink / raw)
  To: Chaoyi Chen
  Cc: Heiko Stuebner, Sandy Huang, Andy Yan, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, dri-devel,
	devicetree, linux-arm-kernel, linux-rockchip, linux-kernel,
	Alibek Omarov
In-Reply-To: <9219738d-c3f2-4034-b88f-84335f77fbad@rock-chips.com>

20.7.2026 3:54, je Chaoyi Chen napisal
> Hello Rok,
> 
> Thank you for your patch. Please see the comment below:
> 
> On 7/17/2026 8:00 PM, Rok Markovic wrote:
>> The RK3568 LVDS transmitter has no register block of its own. It is
>> driven entirely through the GRF and re-uses the MIPI DSI0 D-PHY in
>> PHY_MODE_LVDS, which phy-rockchip-inno-dsidphy already supports.
>> 
>> Based on Alibek Omarov's earlier posting [1], with the changes below.
>> 
>> Power the D-PHY from the encoder enable path rather than from probe.
>> phy_power_on() runs the phy driver's whole LVDS bring-up: PLL and
>> bandgap power-on, a settle, PLL mode select, then a reset pulse of the
>> serializer and the lane enables. None of that is safe at probe time -
>> the GRF has not yet switched the block to LVDS mode (LVDS0_MODE_EN is
>> set from rk3568_lvds_poweron(), i.e. the enable path) and the VOP is
>> not driving dclk. The serializer is clocked from dclk and latches its
>> state coming out of that reset, so it comes up dead and stays dead.
>> The failure is silent: every register in the phy, the GRF and the VOP
>> reads back exactly as on a working system while the lanes sit at
>> common mode and never toggle.
>> 
> 
> Could you please confirm this? Based on my earlier tests, after PHY
> poweron, re-disabling and re-enabling the GRF did not reproduce the
> issue you described.

I can confirm that this is not working but I testedwith writing 
registers
manually with devmem and I could make something wrong.

Should I leave it this way or should I move this back to probe and try 
to
make it work in enable by some trick (disabel/enable GRF)?

> 
>> Program RK3568_LVDS0_DCLK_INV_SEL from the CRTC state's bus_flags so
>> the panel's declared pixelclk-active is honoured on the LVDS block as
>> well as on the VOP pin polarity. Both have to agree with the edge the
>> panel samples on.
>> 
>> Re-assert RK3568_LVDS0_P2S_EN in rk3568_lvds_poweron().
>> rk3568_lvds_poweroff() clears MODE_EN and P2S_EN together, so setting
>> P2S_EN once at probe would leave the parallel-to-serial converter off
>> after the first disable/enable cycle.
>> 
>> Use regmap_write() rather than regmap_update_bits() for the GRF. These
>> registers are write-masked - the upper 16 bits select which of the
>> lower 16 a write may change - so there is nothing to preserve and no
>> reason to read first. Passing a FIELD_PREP_WM16() value as both the
>> mask and the value of an update_bits() applies the masking twice and
>> only works by accident.
>> 
>> Between the two, nothing needs programming at probe at all: the GRF is
>> written entirely from the enable path, so px30_lvds_probe() is left
>> alone rather than being refactored into a shared phy helper.
>> 
>> [1] 
>> https://lore.kernel.org/all/20230119184807.171132-1-a1ba.omarov@gmail.com/
>> 
>> Co-developed-by: Alibek Omarov <a1ba.omarov@gmail.com>
>> Signed-off-by: Alibek Omarov <a1ba.omarov@gmail.com>
>> Signed-off-by: Rok Markovic <rok@kanardia.eu>
>> Assisted-by: Claude:claude-opus-4-8
>> ---
>>  drivers/gpu/drm/rockchip/rockchip_lvds.c | 161 
>> +++++++++++++++++++++++
>>  drivers/gpu/drm/rockchip/rockchip_lvds.h |  10 ++
>>  2 files changed, 171 insertions(+)
>> 
>> diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.c 
>> b/drivers/gpu/drm/rockchip/rockchip_lvds.c
>> index 95fa0a9..f45d04a 100644
>> --- a/drivers/gpu/drm/rockchip/rockchip_lvds.c
>> +++ b/drivers/gpu/drm/rockchip/rockchip_lvds.c
>> @@ -435,6 +435,133 @@ static void px30_lvds_encoder_disable(struct 
>> drm_encoder *encoder)
>>  	drm_panel_unprepare(lvds->panel);
>>  }
>> 
>> +static int rk3568_lvds_poweron(struct rockchip_lvds *lvds)
>> +{
>> +	int ret;
>> +
>> +	ret = clk_enable(lvds->pclk);
>> +	if (ret < 0) {
>> +		DRM_DEV_ERROR(lvds->dev, "failed to enable lvds pclk %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = pm_runtime_get_sync(lvds->dev);
>> +	if (ret < 0) {
>> +		DRM_DEV_ERROR(lvds->dev, "failed to get pm runtime: %d\n", ret);
>> +		clk_disable(lvds->pclk);
>> +		return ret;
>> +	}
>> +
>> +	/*
>> +	 * Enable LVDS mode and the parallel-to-serial converter. These are
>> +	 * write-masked registers, so a plain write only touches the bits 
>> named
>> +	 * here; there is nothing to preserve and no need to read first.
>> +	 */
> 
> I think this comment is redundant. We all know this is a common design
> on Rockchip platform, right? :)
> 

Done

>> +	return regmap_write(lvds->grf, RK3568_GRF_VO_CON2,
>> +			    RK3568_LVDS0_MODE_EN(1) |
>> +			    RK3568_LVDS0_P2S_EN(1));
>> +}
>> +
>> +static void rk3568_lvds_poweroff(struct rockchip_lvds *lvds)
>> +{
>> +	regmap_write(lvds->grf, RK3568_GRF_VO_CON2,
>> +		     RK3568_LVDS0_MODE_EN(0) | RK3568_LVDS0_P2S_EN(0));
>> +
>> +	pm_runtime_put(lvds->dev);
>> +	clk_disable(lvds->pclk);
>> +}
>> +
>> +static int rk3568_lvds_grf_config(struct drm_encoder *encoder,
>> +				  struct drm_display_mode *mode)
>> +{
>> +	struct rockchip_lvds *lvds = encoder_to_lvds(encoder);
>> +	struct rockchip_crtc_state *s =
>> +		to_rockchip_crtc_state(encoder->crtc->state);
>> +	bool negedge = !!(s->bus_flags & 
>> DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE);
>> +
>> +	if (lvds->output != DISPLAY_OUTPUT_LVDS) {
>> +		DRM_DEV_ERROR(lvds->dev, "Unsupported display output %d\n",
>> +			      lvds->output);
>> +		return -EINVAL;
>> +	}
>> +
>> +	/*
>> +	 * The LVDS block has its own dclk inversion select, separate from 
>> the
>> +	 * VOP's pin polarity. Both have to agree with what the panel 
>> samples on.
>> +	 */
>> +	regmap_write(lvds->grf, RK3568_GRF_VO_CON2,
>> +		     RK3568_LVDS0_DCLK_INV_SEL(negedge));
>> +
>> +	/* Set format */
>> +	return regmap_write(lvds->grf, RK3568_GRF_VO_CON0,
>> +			    RK3568_LVDS0_SELECT(lvds->format) |
>> +			    RK3568_LVDS0_MSBSEL(1));
>> +}
> 
> I think rk3568_lvds_poweron() and rk3568_lvds_grf_config() can be 
> merged
> to reduce extra register operations.

Done

>> +
>> +static void rk3568_lvds_encoder_enable(struct drm_encoder *encoder)
>> +{
>> +	struct rockchip_lvds *lvds = encoder_to_lvds(encoder);
>> +	struct drm_display_mode *mode = 
>> &encoder->crtc->state->adjusted_mode;
>> +	int ret;
>> +
>> +	drm_panel_prepare(lvds->panel);
>> +
>> +	ret = rk3568_lvds_poweron(lvds);
>> +	if (ret) {
>> +		DRM_DEV_ERROR(lvds->dev, "failed to power on LVDS: %d\n", ret);
>> +		drm_panel_unprepare(lvds->panel);
>> +		return;
>> +	}
>> +
>> +	ret = rk3568_lvds_grf_config(encoder, mode);
>> +	if (ret) {
>> +		DRM_DEV_ERROR(lvds->dev, "failed to configure LVDS: %d\n", ret);
>> +		drm_panel_unprepare(lvds->panel);
>> +		return;
>> +	}
>> +
>> +	/*
>> +	 * Only now bring the D-PHY up. phy_power_on() runs the whole
>> +	 * inno_dsidphy_lvds_mode_enable() sequence - PLL and bandgap 
>> power-on,
>> +	 * a settle, PLL mode select, then a reset pulse of the serializer 
>> and
>> +	 * the lane enables. All of that has to happen with the block 
>> already
>> +	 * switched to LVDS mode in the GRF (above) and with the VOP's dclk
>> +	 * running, because the serializer is clocked from dclk and latches 
>> its
>> +	 * state out of that reset.
>> +	 *
>> +	 * Doing it at probe instead - as this driver used to - resets and
>> +	 * enables the serializer against a block that is not in LVDS mode 
>> yet
>> +	 * and has no input clock. Every register then reads back correct 
>> while
>> +	 * the lanes sit at common mode forever. Rockchip's BSP orders it 
>> this
>> +	 * way (GRF writes, then phy_set_mode + phy_power_on).
>> +	 */
> 
> I think this comment is redundant. These are internal details of
> phy_set_mode() and don't need to be explained here. Also, the
> ordering requirements are quite common. You can describe them in the
> commit message.
> 

Done

>> +	ret = phy_set_mode(lvds->dphy, PHY_MODE_LVDS);
>> +	if (ret) {
>> +		DRM_DEV_ERROR(lvds->dev, "failed to set phy mode: %d\n", ret);
>> +		drm_panel_unprepare(lvds->panel);
>> +		return;
>> +	}
>> +
>> +	ret = phy_power_on(lvds->dphy);
>> +	if (ret) {
>> +		DRM_DEV_ERROR(lvds->dev, "failed to power on phy: %d\n", ret);
>> +		drm_panel_unprepare(lvds->panel);
>> +		return;
>> +	}
>> +
>> +	drm_panel_enable(lvds->panel);
>> +}
>> +
>> +static void rk3568_lvds_encoder_disable(struct drm_encoder *encoder)
>> +{
>> +	struct rockchip_lvds *lvds = encoder_to_lvds(encoder);
>> +
>> +	drm_panel_disable(lvds->panel);
>> +	phy_power_off(lvds->dphy);
>> +	rk3568_lvds_poweroff(lvds);
>> +	drm_panel_unprepare(lvds->panel);
>> +}
>> +
>>  static const
>>  struct drm_encoder_helper_funcs rk3288_lvds_encoder_helper_funcs = {
>>  	.enable = rk3288_lvds_encoder_enable,
>> @@ -449,6 +576,13 @@ struct drm_encoder_helper_funcs 
>> px30_lvds_encoder_helper_funcs = {
>>  	.atomic_check = rockchip_lvds_encoder_atomic_check,
>>  };
>> 
>> +static const
>> +struct drm_encoder_helper_funcs rk3568_lvds_encoder_helper_funcs = {
>> +	.enable = rk3568_lvds_encoder_enable,
>> +	.disable = rk3568_lvds_encoder_disable,
>> +	.atomic_check = rockchip_lvds_encoder_atomic_check,
>> +};
>> +
>>  static int rk3288_lvds_probe(struct platform_device *pdev,
>>  			     struct rockchip_lvds *lvds)
>>  {
>> @@ -512,6 +646,22 @@ static int px30_lvds_probe(struct platform_device 
>> *pdev,
>>  	return phy_power_on(lvds->dphy);
>>  }
>> 
>> +static int rk3568_lvds_probe(struct platform_device *pdev,
>> +			     struct rockchip_lvds *lvds)
>> +{
>> +	/*
>> +	 * Grab and init the phy, but do NOT power it on here - that is done 
>> in
>> +	 * rk3568_lvds_encoder_enable() once the GRF is in LVDS mode and 
>> dclk is
>> +	 * running. See the comment there. The GRF is not touched at probe
>> +	 * either: every bit of it is programmed from the enable path.
>> +	 */
> 
> Please see the comments above.

Done

> 
>> +	lvds->dphy = devm_phy_get(&pdev->dev, "dphy");
>> +	if (IS_ERR(lvds->dphy))
>> +		return PTR_ERR(lvds->dphy);
>> +
>> +	return phy_init(lvds->dphy);
>> +}
>> +
>>  static const struct rockchip_lvds_soc_data rk3288_lvds_data = {
>>  	.probe = rk3288_lvds_probe,
>>  	.helper_funcs = &rk3288_lvds_encoder_helper_funcs,
>> @@ -522,6 +672,11 @@ static const struct rockchip_lvds_soc_data 
>> px30_lvds_data = {
>>  	.helper_funcs = &px30_lvds_encoder_helper_funcs,
>>  };
>> 
>> +static const struct rockchip_lvds_soc_data rk3568_lvds_data = {
>> +	.probe = rk3568_lvds_probe,
>> +	.helper_funcs = &rk3568_lvds_encoder_helper_funcs,
>> +};
>> +
>>  static const struct of_device_id rockchip_lvds_dt_ids[] = {
>>  	{
>>  		.compatible = "rockchip,rk3288-lvds",
>> @@ -531,6 +686,10 @@ static const struct of_device_id 
>> rockchip_lvds_dt_ids[] = {
>>  		.compatible = "rockchip,px30-lvds",
>>  		.data = &px30_lvds_data
>>  	},
>> +	{
>> +		.compatible = "rockchip,rk3568-lvds",
>> +		.data = &rk3568_lvds_data
>> +	},
>>  	{}
>>  };
>>  MODULE_DEVICE_TABLE(of, rockchip_lvds_dt_ids);
>> @@ -601,6 +760,8 @@ static int rockchip_lvds_bind(struct device *dev, 
>> struct device *master,
>>  	encoder = &lvds->encoder.encoder;
>>  	encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
>>  							     dev->of_node);
>> +	rockchip_drm_encoder_set_crtc_endpoint_id(&lvds->encoder,
>> +						  dev->of_node, 0, 0);
>> 
>>  	ret = drm_simple_encoder_init(drm_dev, encoder, 
>> DRM_MODE_ENCODER_LVDS);
>>  	if (ret < 0) {
>> diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.h 
>> b/drivers/gpu/drm/rockchip/rockchip_lvds.h
>> index 2d92447..93d3415 100644
>> --- a/drivers/gpu/drm/rockchip/rockchip_lvds.h
>> +++ b/drivers/gpu/drm/rockchip/rockchip_lvds.h
>> @@ -121,4 +121,14 @@
>>  #define   PX30_LVDS_P2S_EN(val)			FIELD_PREP_WM16(BIT(6), (val))
>>  #define   PX30_LVDS_VOP_SEL(val)		FIELD_PREP_WM16(BIT(1), (val))
>> 
>> +#define RK3568_GRF_VO_CON0			0x0360
>> +#define   RK3568_LVDS0_SELECT(val)		FIELD_PREP_WM16(GENMASK(5, 4), 
>> (val))
>> +#define   RK3568_LVDS0_MSBSEL(val)		FIELD_PREP_WM16(BIT(3), (val))
>> +
>> +#define RK3568_GRF_VO_CON2			0x0368
>> +#define   RK3568_LVDS0_DCLK_INV_SEL(val)	FIELD_PREP_WM16(BIT(9), 
>> (val))
>> +#define   RK3568_LVDS0_DCLK_DIV2_SEL(val)	FIELD_PREP_WM16(BIT(8), 
>> (val))
>> +#define   RK3568_LVDS0_MODE_EN(val)		FIELD_PREP_WM16(BIT(1), (val))
>> +#define   RK3568_LVDS0_P2S_EN(val)		FIELD_PREP_WM16(BIT(0), (val))
>> +
>>  #endif /* _ROCKCHIP_LVDS_ */


^ permalink raw reply

* Re: [PATCH] thermal/drivers/mediatek/lvts_thermal: Make reset optional for MT8196
From: Philipp Zabel @ 2026-07-21  7:53 UTC (permalink / raw)
  To: AngeloGioacchino Del Regno, rafael
  Cc: daniel.lezcano, rui.zhang, lukasz.luba, matthias.bgg, laura.nao,
	fshao, wenst, jiapeng.chong, frank-w, linux-pm, linux-kernel,
	linux-arm-kernel, linux-mediatek, kernel
In-Reply-To: <20260720144244.88877-1-angelogioacchino.delregno@collabora.com>

On Mo, 2026-07-20 at 16:42 +0200, AngeloGioacchino Del Regno wrote:
> Depending on the SoC+Firmware combination, the LVTS hardware may be
> may be actively used by one or even multiple concurrent MCUs!
> In this case, resetting it may produce either a severe slowdown of
> the entire system, or even a thermal protection AP reset, as some
> MCU(s) may be reading a very high or very low temperature while the
> LVTS is being reset.
> 
> On those, don't fail if no reset is found as that may be omitted on
> purpose, but still check if there's one, because some board(s) may
> be running on a different bootchain with reduced firmwares or using
> firmwares with reduced functionality.
> 
> Add a new "optional_reset" member to lvts_data and use it to check
> whether the resets should be mandatory or not.
> 
> Also, while at it, since devm_reset_control_get_by_index() is now
> deprecated, change the probe function to instead call function
> devm_reset_control_get_exclusive_by_index(), which does the same.

Why is this using _by_index() at all? mediatek,lvts-thermal.yaml
specifies a single reset, so the driver should just
devm_reset_control_get_exclusive(dev, NULL).

> 
> Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
> ---
>  drivers/thermal/mediatek/lvts_thermal.c | 29 ++++++++++++++++++++++---
>  1 file changed, 26 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/thermal/mediatek/lvts_thermal.c b/drivers/thermal/mediatek/lvts_thermal.c
> index 92711896ce24..6670b5458f2c 100644
> --- a/drivers/thermal/mediatek/lvts_thermal.c
> +++ b/drivers/thermal/mediatek/lvts_thermal.c
> @@ -160,6 +160,7 @@ struct lvts_data {
>  	int gt_calib_bit_offset;
>  	unsigned int def_calibration;
>  	u16 msr_offset;
> +	bool optional_reset;

No need to complicate things. It's not the driver's business to check
device tree correctness. Just make the reset optional, as the commit
message says.

>  };
>  
>  struct lvts_sensor {
> @@ -1473,9 +1474,29 @@ static int lvts_probe(struct platform_device *pdev)
>  	if (IS_ERR(lvts_td->base))
>  		return dev_err_probe(dev, PTR_ERR(lvts_td->base), "Failed to map io resource\n");
>  
> -	lvts_td->reset = devm_reset_control_get_by_index(dev, 0);
> -	if (IS_ERR(lvts_td->reset))
> -		return dev_err_probe(dev, PTR_ERR(lvts_td->reset), "Failed to get reset control\n");
> +	/*
> +	 * Depending on the SoC+Firmware combination, the LVTS hardware may be
> +	 * may be actively used by one or even multiple concurrent MCUs!
> +	 * In this case, resetting it may produce either a severe slowdown of
> +	 * the entire system, or even a thermal protection AP reset, as some
> +	 * MCU(s) may be reading a very high or very low temperature while the
> +	 * LVTS is being reset.
> +	 *
> +	 * On those, don't fail if no reset is found as that may be omitted on
> +	 * purpose, but still check if there's one, because some board(s) may
> +	 * be running on a different bootchain with reduced firmwares or using
> +	 * firmwares with reduced functionality.
> +	 */
> +	lvts_td->reset = devm_reset_control_get_exclusive_by_index(dev, 0);

So this should be:

	lvts_td->reset = devm_reset_control_get_optional_exclusive(dev, NULL);

> +	if (IS_ERR(lvts_td->reset)) {
> +		if (lvts_data->optional_reset) {
> +			dev_dbg(dev, "No reset found. LVTS may be used by firmware.\n");
> +			lvts_td->reset = NULL;

This is not necessary then.

> +		} else {
> +			return dev_err_probe(dev, PTR_ERR(lvts_td->reset),
> +					     "Failed to get reset control\n");
> +		}

And this part could be kept as before.

> +	}
>  
>  	irq = platform_get_irq(pdev, 0);
>  	if (irq < 0)
> @@ -2163,6 +2184,7 @@ static const struct lvts_data mt8196_lvts_mcu_data = {
>  	.num_cal_offsets = LVTS_NUM_CAL_OFFSETS_MT8196,
>  	.msr_offset = LVTS_MSR_OFFSET_MT8196,
>  	.ops = &lvts_platform_ops_mt8196,
> +	.optional_reset = true,

But mediatek,lvts-thermal.yaml still specifies the reset as required
for mediatek,mt8196-lvts-ap. That should be changed instead.

>  };
>  
>  static const struct lvts_data mt8196_lvts_ap_data = {
> @@ -2175,6 +2197,7 @@ static const struct lvts_data mt8196_lvts_ap_data = {
>  	.num_cal_offsets = LVTS_NUM_CAL_OFFSETS_MT8196,
>  	.msr_offset = LVTS_MSR_OFFSET_MT8196,
>  	.ops = &lvts_platform_ops_mt8196,
> +	.optional_reset = true,

Same as above, but for mediatek,mt8196-lvts-mcu.

regards
Philipp


^ permalink raw reply

* Re: [PATCH v2] drivers/crypto: Mark QCE as BROKEN
From: Bartosz Golaszewski @ 2026-07-21  8:24 UTC (permalink / raw)
  To: Herbert Xu
  Cc: Bartosz Golaszewski, demiobenour, Russell King, David S. Miller,
	Eric Biggers, linux-arm-kernel, linux-kernel, linux-crypto,
	linux-arm-msm, stable, brgl
In-Reply-To: <alnlaCXmoLRAlr96@gondor.apana.org.au>

On Fri, Jul 17, 2026 at 06:18:48PM +1000, Herbert Xu wrote:
> On Sun, Jul 12, 2026 at 05:31:31PM -0400, Demi Marie Obenour via B4 Relay wrote:
> > From: Demi Marie Obenour <demiobenour@gmail.com>
> >
> > This driver is harmful:
> >
> > - It is much slower than the CPU [1] [2].
> > - It Has a history of bugs [2] [3].
> > - It does not have exclusive access to the hardware [4], causing races
> >   with the secure world.
> > - It register its implementations with too low a cra_priority for them
> >   to be actually used [5].
> >
> > Therefore, disable it to ensure that nobody builds it into kernels they
> > intend to ship.
> >
> > In the future, the driver will be used for processing restricted media
> > content.  However, the kernel does not currently support this.  Since
> > the driver will have future uses, allow building it if COMPILE_TEST is
> > enabled.
> >
> > [1]: https://lore.kernel.org/r/20250704070322.20692-1-ebiggers@kernel.org/
> > [2]: https://lore.kernel.org/r/20250615031807.GA81869@sol/
> > [3]: https://lore.kernel.org/r/20260706-qce-fix-self-tests-v5-0-86f461ff1829@oss.qualcomm.com/
> > [4]: https://lore.kernel.org/r/20260629-qcom-qce-cmd-descr-v20-0-56f67da84c05@oss.qualcomm.com/
> > [5]: https://lore.kernel.org/r/20260524204537.GB110177@quark/
> >
> > Fixes: ec8f5d8f6f76 ("crypto: qce - Qualcomm crypto engine driver")
> > Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
> > ---
> > Changes in v2:
> > - Add Fixes: ec8f5d8f6f76 ("crypto: qce - Qualcomm crypto engine driver")
> > - Add Cc: stable@vger.kernel.org
> > - Link to v1: https://lore.kernel.org/r/20260712-qce-broken-v1-1-85e2bff17871@gmail.com
> > ---
> >  arch/arm/configs/multi_v7_defconfig | 1 -
> >  arch/arm64/configs/defconfig        | 1 -
> >  drivers/crypto/Kconfig              | 6 +++++-
> >  3 files changed, 5 insertions(+), 3 deletions(-)
>
> Patch applied.  Thanks.

Hi Herbert,

I'd like to ask you to back this patch out. Admittedly I should have NAKed this
under the v1 thread but I thought the discussion we had under the Shikra series
and the subsequent patch making myself the maintainer of this driver (note: Demi
did not Cc me on this nor the Qcom mailing list at linux-arm-msm) as well as the
pending series[1] fixing the QCE issues was enough to imply we should not mark
it as broken. Please consider applying [1] if the fixes in v6 are correct.

Bartosz

[1] https://lore.kernel.org/all/20260717-qce-fix-self-tests-v6-0-455775fe5f6c@oss.qualcomm.com/


^ permalink raw reply

* Re: [PATCH v3 1/9] regulator: core: Add "enable and wait" functions
From: Chen-Yu Tsai @ 2026-07-21  8:22 UTC (permalink / raw)
  To: sashiko-reviews, Mark Brown
  Cc: conor+dt, dmitry.torokhov, robh, linux-input, devicetree,
	linux-arm-kernel
In-Reply-To: <20260721080829.4153C1F000E9@smtp.kernel.org>

On Tue, Jul 21, 2026 at 4:08 PM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] The prototype for `regulator_enable_and_wait` in `consumer.h` incorrectly names the delay parameter `ms` instead of `wait_us`, inviting developers to pass millisecond values that will be incorrectly interpreted as microseconds.
> - [High] Sleeping for the consumer delay while holding the recursive `ww_mutex` for the regulator and its entire parent supply chain severely degrades concurrency, blocking shared regulators and serializing async bulk enablement.
> --
>
> --- Patch [1]: [PATCH v3 1/9] regulator: core: Add "enable and wait" functions ---
> commit 73793dc0bdc3050ff22074a860d438d5d9cc0b49
> Author: Chen-Yu Tsai <wenst@chromium.org>
>
> regulator: core: Add "enable and wait" functions
>
> This commit introduces new "enable and wait" functions to the regulator core,
> allowing consumers to ensure a minimum amount of time has passed since a
> regulator was actually turned on. It adds a last_on timestamp to the
> regulator device and updates single and bulk enable APIs to accept a wait time.
>
> > diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
> > index 1797929dfe566..d14ce86d8f7bb 100644
> > --- a/drivers/regulator/core.c
> > +++ b/drivers/regulator/core.c
>
> [ ... ]
>
> > @@ -3159,13 +3162,24 @@ static int _regulator_enable(struct regulator *regulator)
> >               } else if (ret < 0) {
> >                       rdev_err(rdev, "is_enabled() failed: %pe\n", ERR_PTR(ret));
> >                       goto err_consumer_disable;
> > +             } else {
> > +                     /* regulator already enabled somehow, but timestamp might be invalid */
> > +                     if (!rdev->last_on)
> > +                             rdev->last_on = ktime_get_boottime();
> >               }
> > -             /* Fallthrough on positive return values - already enabled */
> >       }
> >
> >       if (regulator->enable_count == 1)
> >               rdev->use_count++;
> >
> > +     if (wait_us) {
> > +             ktime_t end = ktime_add_us(rdev->last_on, wait_us);
> > +             s64 remaining = ktime_us_delta(end, ktime_get_boottime());
> > +
> > +             if (remaining > 0)
> > +                     fsleep(remaining);
> > +     }
> > +
>
> [Severity: High]
> Does sleeping here while holding the recursive ww_mutex for the regulator and
> its parent supply chain cause concurrency issues?
>
> Since regulator_enable_and_wait() acquires these locks via
> regulator_lock_dependent() before calling _regulator_enable_and_wait(), this
> sleep happens while the locks are held.
>
> In cases like regulator_bulk_enable_and_wait() where async workers are created
> to enable regulators in parallel, won't this serialize the wait times for any
> consumers sharing a parent supply, defeating the purpose of the async design?

I suppose the fsleep() needs to be moved outside the lock region.

So instead of changing _regulator_enable(), make it return the last_on
timestamp, and then act on it in regulator_enable_and_wait() after the
lock is released.

> >       return 0;
> >
> >  err_consumer_disable:
>
> [ ... ]
>
> > diff --git a/include/linux/regulator/consumer.h b/include/linux/regulator/consumer.h
> > index 56fe2693d9b22..a69157c9b5b51 100644
> > --- a/include/linux/regulator/consumer.h
> > +++ b/include/linux/regulator/consumer.h
>
> [ ... ]
>
> > @@ -192,7 +193,7 @@ int devm_regulator_bulk_register_supply_alias(struct device *dev,
> >                                             int num_id);
> >
> >  /* regulator output control and status */
> > -int __must_check regulator_enable(struct regulator *regulator);
> > +int __must_check regulator_enable_and_wait(struct regulator *regulator, unsigned int ms);
>
> [Severity: High]
> Could the naming of this parameter cause consumers to inadvertently pass
> millisecond values that get interpreted as microseconds?

Yes I missed this part when I changed the API to use microseconds
instead of milliseconds. Will fix in the next version.


ChenYu

> The prototype here defines the parameter as ms, but the implementation and
> the parameter passed to ktime_add_us() inside _regulator_enable_and_wait()
> treat it strictly as microseconds.
>
> If a driver developer passes 10 expecting a 10-millisecond delay based on this
> header, the core will interpret it as 10 microseconds. Could this lead to
> hardware initialization failures due to premature access to unstable power
> supplies?
>
> >  int regulator_disable(struct regulator *regulator);
> >  int regulator_force_disable(struct regulator *regulator);
> >  int regulator_is_enabled(struct regulator *regulator);
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260721075226.2347933-1-wenst@chromium.org?part=1


^ permalink raw reply

* Re: [PATCH] iommu/io-pgtable-arm: Add support for contiguous hint bit
From: Vijayanand Jitta @ 2026-07-21  8:21 UTC (permalink / raw)
  To: Robin Murphy, Jason Gunthorpe
  Cc: Will Deacon, Joerg Roedel (AMD), linux-arm-msm, iommu,
	linux-kernel, linux-arm-kernel, Prakash Gupta
In-Reply-To: <787fa0d3-c1d4-442e-beaa-91a03238f511@arm.com>



On 7/16/2026 5:29 PM, Robin Murphy wrote:
> On 15/07/2026 7:09 pm, Jason Gunthorpe wrote:
>> On Wed, Jul 15, 2026 at 06:45:09PM +0100, Robin Murphy wrote:
>>
>>>> AFAICR the biggest issue with arm-smmu was it using IO_PGTABLE_ARMV7S
>>>> as well. I think it would be fine to implement all the unique LPAE
>>>> features it needs in iommupt, I did most of them already.
>>>>
>>>> I do have an ARMV7S implementation for iommupt, but I did not solve
>>>> the sub page problem. So while it is functionally working it is not
>>>> usable since it wastes so much memory. That's a tricky problem to
>>>> solve since the algorithms depend on the gather->freelist.
>>>>
>>>> I didn't spend any time trying to do anything about this as I was not
>>>> intending to touch arm-smmu
>>>
>>> Indeed between the irregular table sizes (before you even get to the
>>> Mediatek shenanigans...), and the awkward GFP_DMA limitations and/or reality
>>> that many of the systems using it really don't have memory to waste, I'd
>>> have considered v7s pretty much terminally incompatible with iommu_pages and
>>> the abstraction that iommupt is trying to be... :/
>>
>> At least Matthew has talked about having a sub page allocator with
>> meta data, maybe with the memdesc support someday. Even today we could
>> have iommu-pages allocate a companion struct pointed to from struct
>> page that was sized 4x so it could be used for the freelist.
>>
>> There are some other options for alterantive algorithms that could
>> work without requiring the free list. I had some thoughts about placing
>> the free list inside the data itself as a non-present entry.
>>
>> It is not insolvable, I'm just not sure it is worth doing vs leaving
>> arm-smmu to keep using iopgtable from ARMv7.
> 
> Yeah, if you did want to keep bashing at it, then ultimately there are at least a couple more short-descriptor inspired implementations in exynos and omap too, however at this point I don't see the cost:benefit ratio looking at all favourable...
> 
>> Otherwise to have arm-smmu use iommupt you'd end up with two
>> iommu_domains specialized to 32 and 64 bit page tables, with their own
>> ops and a shared invalidation like how the tlbi struct is providing
>> for smmuv3.
>>
>> It is not outrageously bad, but it certainly is a chunk of work.
>>
>> If the goal is arm-smmu support of CONT then maybe there is some merit
>> in doing iopgtable as a one off feature. But there have been many
>> attempts so far to add CONT and they all had troubles, I had the
>> impression is is not so easy..
> 
> I think now that partial unmaps are firmly ruled out and we already have the multi-page map/unmap design it should be pretty minor. I'm also more than happy to stop there, have dirty tracking, splitting/merging and all the fancy new IOMMUFD stuff require iommupt, and keep io-pgtable strictly for the GPUs and "media" IOMMUs that only need to keep providing map/unmap/iova_to_phys for iommu-dma or equivalent usage. By now I'd also say we can reasonably park arm-smmu in the latter category - while technically it could try to support stuff like nesting and vIOMMU (at least on MMU-500), the hardware is in embedded platforms which either don't care at all, or at worst where virtualisation is Xen's problem not ours. As far as I'm aware even the NXP Layerscape platforms are happy with the status quo of just basic VFIO type 1 for DPDK.
> 
> Thanks,
> Robin.


FYI, I've just posted v2 addressing the review comments from the
earlier round — it doesn't touch the io-pgtable/iommupt split discussed
above, just the CONT-hint quirk in the existing io-pgtable-arm code.

Thanks,
Vijay


^ permalink raw reply

* [PATCH v17 15/15] arm64: vdso: Expose sigreturn address on vdso to the kernel
From: Jinjie Ruan @ 2026-07-21  8:18 UTC (permalink / raw)
  To: catalin.marinas, will, kees, oleg, luto, wad, thuth, peterz,
	mark.rutland, linusw, ada.coupriediaz, kevin.brodsky, yeoreum.yun,
	anshuman.khandual, james.morse, tglx, broonie, pengcan,
	ryan.roberts, liqiang01, linux-arm-kernel, linux-kernel, linux-mm
  Cc: ruanjinjie
In-Reply-To: <20260721081858.1169276-1-ruanjinjie@huawei.com>

Syscall User Dispatch (SUD) must not intercept the signal trampoline
code, otherwise returning from a signal with a locked selector may
cause infinite recursion into the signal handler.

Provide an arm64 implementation of arch_syscall_is_vdso_sigreturn()
to exclude these sigreturn syscalls from dispatch.

For native 64-bit tasks, check whether the current PC matches the 'svc #0'
instruction inside the vDSO sigreturn trampoline (defined in
arch/arm64/kernel/vdso/sigreturn.S as below).

	SYM_CODE_START(__kernel_rt_sigreturn)
		mov     x8, #__NR_rt_sigreturn
		svc     #0
	SYM_CODE_END(__kernel_rt_sigreturn)

For COMPAT tasks, verify that the instruction falls within the dedicated
'sigpage' range, since compat signal handlers use this architecture-
specific page (not the vDSO) for their return trampoline. This prevents
SUD from incorrectly dispatching syscalls issued during signal return.

Suggested-by: Thomas Weißschuh <thomas.weissschuh@linutronix.de>
Suggested-by: Kevin Brodsky <kevin.brodsky@arm.com>
Suggested-by: kemal <kmal@cock.li>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
 arch/arm64/Kconfig                 |  1 +
 arch/arm64/include/asm/elf.h       |  1 +
 arch/arm64/kernel/vdso.c           | 16 ++++++++++++++++
 arch/arm64/kernel/vdso/sigreturn.S |  1 +
 arch/arm64/kernel/vdso/vdso.lds.S  |  1 +
 5 files changed, 20 insertions(+)

diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index aea686dce3d5..a41d2b593899 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -86,6 +86,7 @@ config ARM64
 	select ARCH_SUPPORTS_SCHED_SMT
 	select ARCH_SUPPORTS_SCHED_CLUSTER
 	select ARCH_SUPPORTS_SCHED_MC
+	select ARCH_SUPPORTS_SYSCALL_USER_DISPATCH
 	select ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH
 	select ARCH_WANT_COMPAT_IPC_PARSE_VERSION if COMPAT
 	select ARCH_WANT_DEFAULT_BPF_JIT
diff --git a/arch/arm64/include/asm/elf.h b/arch/arm64/include/asm/elf.h
index d2779d604c7b..a8b232c5c6d9 100644
--- a/arch/arm64/include/asm/elf.h
+++ b/arch/arm64/include/asm/elf.h
@@ -293,6 +293,7 @@ static inline int arch_check_elf(void *ehdr, bool has_interp,
 	return 0;
 }
 
+extern bool arch_syscall_is_vdso_sigreturn(struct pt_regs *regs);
 #endif /* !__ASSEMBLER__ */
 
 #endif
diff --git a/arch/arm64/kernel/vdso.c b/arch/arm64/kernel/vdso.c
index 592dd8668de4..37cdc7c72e15 100644
--- a/arch/arm64/kernel/vdso.c
+++ b/arch/arm64/kernel/vdso.c
@@ -343,3 +343,19 @@ int arch_setup_additional_pages(struct linux_binprm *bprm, int uses_interp)
 
 	return ret;
 }
+
+bool arch_syscall_is_vdso_sigreturn(struct pt_regs *regs)
+{
+	unsigned long landing_pad;
+
+#ifdef CONFIG_COMPAT
+	if (is_compat_task()) {
+		unsigned long sigpage = (unsigned long)current->mm->context.sigpage;
+
+		return regs->pc >= sigpage && regs->pc < (sigpage + PAGE_SIZE);
+	}
+#endif
+	landing_pad = (unsigned long)VDSO_SYMBOL(current->mm->context.vdso, sigreturn_landing_pad);
+
+	return regs->pc == landing_pad;
+}
diff --git a/arch/arm64/kernel/vdso/sigreturn.S b/arch/arm64/kernel/vdso/sigreturn.S
index 0e18729abc3b..8d2d413ff45e 100644
--- a/arch/arm64/kernel/vdso/sigreturn.S
+++ b/arch/arm64/kernel/vdso/sigreturn.S
@@ -73,6 +73,7 @@ SYM_CODE_START(__kernel_rt_sigreturn)
 	mov	x8, #__NR_rt_sigreturn
 //	PLEASE DO NOT MODIFY
 	svc	#0
+	SYM_INNER_LABEL(__sigreturn_landing_pad, SYM_L_GLOBAL)
 //	PLEASE DO NOT MODIFY
 //	.cfi_endproc
 SYM_CODE_END(__kernel_rt_sigreturn)
diff --git a/arch/arm64/kernel/vdso/vdso.lds.S b/arch/arm64/kernel/vdso/vdso.lds.S
index 52314be29191..da61d6fc6fe9 100644
--- a/arch/arm64/kernel/vdso/vdso.lds.S
+++ b/arch/arm64/kernel/vdso/vdso.lds.S
@@ -112,3 +112,4 @@ VERSION
  * Make the sigreturn code visible to the kernel.
  */
 VDSO_sigtramp		= __kernel_rt_sigreturn;
+VDSO_sigreturn_landing_pad	= __sigreturn_landing_pad;
-- 
2.34.1



^ permalink raw reply related

* [PATCH v17 13/15] arm64: entry: Convert to generic entry
From: Jinjie Ruan @ 2026-07-21  8:18 UTC (permalink / raw)
  To: catalin.marinas, will, kees, oleg, luto, wad, thuth, peterz,
	mark.rutland, linusw, ada.coupriediaz, kevin.brodsky, yeoreum.yun,
	anshuman.khandual, james.morse, tglx, broonie, pengcan,
	ryan.roberts, liqiang01, linux-arm-kernel, linux-kernel, linux-mm
  Cc: ruanjinjie
In-Reply-To: <20260721081858.1169276-1-ruanjinjie@huawei.com>

Implement the generic entry framework for arm64 to handle system call
entry and exit. This follows the migration of x86, RISC-V, and LoongArch,
consolidating architecture-specific syscall tracing and auditing into
the common kernel entry infrastructure, making it easier to enable
advanced features on arm64, such as "Syscall User Dispatch" and "rseq time
slice extension" optimization.

[Background]
Arm64 has already adopted generic IRQ entry. Completing the conversion
to the generic syscall entry framework reduces architectural divergence,
simplifies maintenance, and allows arm64 to automatically benefit from
improvements in the common entry code.

[Changes]

1. Kconfig and Infrastructure:
- Select GENERIC_ENTRY and remove GENERIC_IRQ_ENTRY (now implied).

- Migrate struct thread_info to use the syscall_work field instead
  of TIF flags for syscall-related tasks.

2. Thread Info and Flags:
- Remove definitions for TIF_SYSCALL_TRACE, TIF_SYSCALL_AUDIT,
  TIF_SYSCALL_TRACEPOINT, TIF_SECCOMP, and TIF_SYSCALL_EMU.

- Replace _TIF_SYSCALL_WORK and _TIF_SYSCALL_EXIT_WORK with the
  generic SYSCALL_WORK bitmask.

- Map single-step state to SYSCALL_EXIT_TRAP in debug-monitors.c.

3. Architecture-Specific Hooks (asm/entry-common.h): Implement following
  arch function by porting the existing arm64 logic to the generic
  interface:
  - arch_ptrace_report_syscall_permit_entry()
  - arch_ptrace_report_syscall_exit()

4. Cleanup and Refactoring: Remove redundant arm64-specific syscall
  tracing functions from ptrace.c, including following key functions
  and related audit/step helpers:
  - arm64_syscall_exit_to_user_mode_work()
  - arm64_syscall_trace_enter()
  - arm64_syscall_exit_work().

- Update el0_svc_common() in syscall.c to use the generic
  syscall_work checks and entry/exit call sites.

[Why this matters]
- Unified Interface: Aligns arm64 with the modern kernel entry standard.

- Improved Maintainability: Bug fixes in kernel/entry/common.c now
  apply to arm64 automatically.

- Feature Readiness: Simplifies the implementation of future
  cross-architecture syscall features.

[Compatibility]
This conversion maintains full ABI compatibility with existing
userspace. The ptrace register-saving behavior, seccomp filtering,
and syscall tracing semantics remain identical to the previous
implementation.

Additionally, since arm64 now does not select HAVE_GENERIC_TIF_BITS, there
is no functional change regarding rseq management, as those generic entry
pathways remain a no-op for this architecture."

Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Thomas Gleixner <tglx@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Tested-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
Reviewed-by: Kevin Brodsky <kevin.brodsky@arm.com>
Suggested-by: Kevin Brodsky <kevin.brodsky@arm.com>
Suggested-by: Mark Rutland <mark.rutland@arm.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
 arch/arm64/Kconfig                    |   2 +-
 arch/arm64/include/asm/entry-common.h |  77 +++++++++++++++
 arch/arm64/include/asm/syscall.h      |  14 ---
 arch/arm64/include/asm/thread_info.h  |  19 +---
 arch/arm64/kernel/debug-monitors.c    |   8 ++
 arch/arm64/kernel/entry-common.c      |   2 +-
 arch/arm64/kernel/ptrace.c            | 136 --------------------------
 arch/arm64/kernel/signal.c            |   2 +-
 arch/arm64/kernel/syscall.c           |   9 +-
 9 files changed, 95 insertions(+), 174 deletions(-)

diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index b3afe0688919..aea686dce3d5 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -128,9 +128,9 @@ config ARM64
 	select GENERIC_CPU_DEVICES
 	select GENERIC_CPU_VULNERABILITIES
 	select GENERIC_EARLY_IOREMAP
+	select GENERIC_ENTRY
 	select GENERIC_IDLE_POLL_SETUP
 	select GENERIC_IOREMAP
-	select GENERIC_IRQ_ENTRY
 	select GENERIC_IRQ_IPI
 	select GENERIC_IRQ_KEXEC_CLEAR_VM_FORWARD
 	select GENERIC_IRQ_PROBE
diff --git a/arch/arm64/include/asm/entry-common.h b/arch/arm64/include/asm/entry-common.h
index cab8cd78f693..2b053acbe10f 100644
--- a/arch/arm64/include/asm/entry-common.h
+++ b/arch/arm64/include/asm/entry-common.h
@@ -3,14 +3,21 @@
 #ifndef _ASM_ARM64_ENTRY_COMMON_H
 #define _ASM_ARM64_ENTRY_COMMON_H
 
+#include <linux/ptrace.h>
 #include <linux/thread_info.h>
 
+#include <asm/compat.h>
 #include <asm/cpufeature.h>
 #include <asm/daifflags.h>
 #include <asm/fpsimd.h>
 #include <asm/mte.h>
 #include <asm/stacktrace.h>
 
+enum ptrace_syscall_dir {
+	PTRACE_SYSCALL_ENTER = 0,
+	PTRACE_SYSCALL_EXIT,
+};
+
 #define ARCH_EXIT_TO_USER_MODE_WORK (_TIF_MTE_ASYNC_FAULT | _TIF_FOREIGN_FPSTATE)
 
 static __always_inline void arch_exit_to_user_mode_work(struct pt_regs *regs,
@@ -54,4 +61,74 @@ static inline bool arch_irqentry_exit_need_resched(void)
 
 #define arch_irqentry_exit_need_resched arch_irqentry_exit_need_resched
 
+static __always_inline unsigned long ptrace_save_reg(struct pt_regs *regs,
+						     enum ptrace_syscall_dir dir,
+						     int *regno)
+{
+	unsigned long saved_reg;
+
+	/*
+	 * We have some ABI weirdness here in the way that we handle syscall
+	 * exit stops because we indicate whether or not the stop has been
+	 * signalled from syscall entry or syscall exit by clobbering a general
+	 * purpose register (ip/r12 for AArch32, x7 for AArch64) in the tracee
+	 * and restoring its old value after the stop. This means that:
+	 *
+	 * - Any writes by the tracer to this register during the stop are
+	 *   ignored/discarded.
+	 *
+	 * - The actual value of the register is not available during the stop,
+	 *   so the tracer cannot save it and restore it later.
+	 *
+	 * - Syscall stops behave differently to seccomp and pseudo-step traps
+	 *   (the latter do not nobble any registers).
+	 */
+	*regno = (is_compat_task() ? 12 : 7);
+	saved_reg = regs->regs[*regno];
+	regs->regs[*regno] = dir;
+
+	return saved_reg;
+}
+
+static __always_inline bool arch_ptrace_report_syscall_permit_entry(struct pt_regs *regs)
+{
+	unsigned long saved_reg;
+	bool permit;
+	int regno;
+
+	saved_reg = ptrace_save_reg(regs, PTRACE_SYSCALL_ENTER, &regno);
+	permit = ptrace_report_syscall_permit_entry(regs);
+	if (!permit)
+		forget_syscall(regs);
+	regs->regs[regno] = saved_reg;
+
+	return permit;
+}
+
+#define arch_ptrace_report_syscall_permit_entry arch_ptrace_report_syscall_permit_entry
+
+static __always_inline void arch_ptrace_report_syscall_exit(struct pt_regs *regs,
+							    int step)
+{
+	unsigned long saved_reg;
+	int regno;
+
+	saved_reg = ptrace_save_reg(regs, PTRACE_SYSCALL_EXIT, &regno);
+	if (!step) {
+		ptrace_report_syscall_exit(regs, 0);
+		regs->regs[regno] = saved_reg;
+	} else {
+		regs->regs[regno] = saved_reg;
+
+		/*
+		 * Signal a pseudo-step exception since we are stepping but
+		 * tracer modifications to the registers may have rewound the
+		 * state machine.
+		 */
+		ptrace_report_syscall_exit(regs, 1);
+	}
+}
+
+#define arch_ptrace_report_syscall_exit arch_ptrace_report_syscall_exit
+
 #endif /* _ASM_ARM64_ENTRY_COMMON_H */
diff --git a/arch/arm64/include/asm/syscall.h b/arch/arm64/include/asm/syscall.h
index f19d4a8824ba..e3e716575846 100644
--- a/arch/arm64/include/asm/syscall.h
+++ b/arch/arm64/include/asm/syscall.h
@@ -120,18 +120,4 @@ static inline int syscall_get_arch(struct task_struct *task)
 
 	return AUDIT_ARCH_AARCH64;
 }
-
-bool arm64_syscall_trace_enter(struct pt_regs *regs, unsigned long flags);
-void arm64_syscall_exit_work(struct pt_regs *regs, unsigned long flags);
-
-static __always_inline void arm64_syscall_exit_to_user_mode_work(struct pt_regs *regs)
-{
-	unsigned long flags = read_thread_flags();
-
-	rseq_syscall(regs);
-
-	if (unlikely(flags & _TIF_SYSCALL_EXIT_WORK) || flags & _TIF_SINGLESTEP)
-		arm64_syscall_exit_work(regs, flags);
-}
-
 #endif	/* __ASM_SYSCALL_H */
diff --git a/arch/arm64/include/asm/thread_info.h b/arch/arm64/include/asm/thread_info.h
index 56a2c9426a32..3f621ba0f961 100644
--- a/arch/arm64/include/asm/thread_info.h
+++ b/arch/arm64/include/asm/thread_info.h
@@ -46,6 +46,7 @@ struct thread_info {
 	u64			mpam_partid_pmg;
 #endif
 	u32			cpu;
+	unsigned long		syscall_work;   /* SYSCALL_WORK_ flags */
 };
 
 #define thread_saved_pc(tsk)	\
@@ -68,11 +69,6 @@ void arch_setup_new_exec(void);
 #define TIF_UPROBE		5	/* uprobe breakpoint or singlestep */
 #define TIF_MTE_ASYNC_FAULT	6	/* MTE Asynchronous Tag Check Fault */
 #define TIF_NOTIFY_SIGNAL	7	/* signal notifications exist */
-#define TIF_SYSCALL_TRACE	8	/* syscall trace active */
-#define TIF_SYSCALL_AUDIT	9	/* syscall auditing */
-#define TIF_SYSCALL_TRACEPOINT	10	/* syscall tracepoint for ftrace */
-#define TIF_SECCOMP		11	/* syscall secure computing */
-#define TIF_SYSCALL_EMU		12	/* syscall emulation active */
 #define TIF_PATCH_PENDING	13	/* pending live patching update */
 #define TIF_MEMDIE		18	/* is terminating due to OOM killer */
 #define TIF_FREEZE		19
@@ -94,27 +90,14 @@ void arch_setup_new_exec(void);
 #define _TIF_NEED_RESCHED_LAZY	(1 << TIF_NEED_RESCHED_LAZY)
 #define _TIF_NOTIFY_RESUME	(1 << TIF_NOTIFY_RESUME)
 #define _TIF_FOREIGN_FPSTATE	(1 << TIF_FOREIGN_FPSTATE)
-#define _TIF_SYSCALL_TRACE	(1 << TIF_SYSCALL_TRACE)
-#define _TIF_SYSCALL_AUDIT	(1 << TIF_SYSCALL_AUDIT)
-#define _TIF_SYSCALL_TRACEPOINT	(1 << TIF_SYSCALL_TRACEPOINT)
-#define _TIF_SECCOMP		(1 << TIF_SECCOMP)
-#define _TIF_SYSCALL_EMU	(1 << TIF_SYSCALL_EMU)
 #define _TIF_PATCH_PENDING	(1 << TIF_PATCH_PENDING)
 #define _TIF_UPROBE		(1 << TIF_UPROBE)
-#define _TIF_SINGLESTEP		(1 << TIF_SINGLESTEP)
 #define _TIF_32BIT		(1 << TIF_32BIT)
 #define _TIF_SVE		(1 << TIF_SVE)
 #define _TIF_MTE_ASYNC_FAULT	(1 << TIF_MTE_ASYNC_FAULT)
 #define _TIF_NOTIFY_SIGNAL	(1 << TIF_NOTIFY_SIGNAL)
 #define _TIF_TSC_SIGSEGV	(1 << TIF_TSC_SIGSEGV)
 
-#define _TIF_SYSCALL_WORK	(_TIF_SYSCALL_TRACE | _TIF_SYSCALL_AUDIT | \
-				 _TIF_SYSCALL_TRACEPOINT | _TIF_SECCOMP | \
-				 _TIF_SYSCALL_EMU)
-
-#define _TIF_SYSCALL_EXIT_WORK	(_TIF_SYSCALL_TRACE | _TIF_SYSCALL_AUDIT | \
-				 _TIF_SYSCALL_TRACEPOINT)
-
 #ifdef CONFIG_SHADOW_CALL_STACK
 #define INIT_SCS							\
 	.scs_base	= init_shadow_call_stack,			\
diff --git a/arch/arm64/kernel/debug-monitors.c b/arch/arm64/kernel/debug-monitors.c
index 29307642f4c9..c53683839155 100644
--- a/arch/arm64/kernel/debug-monitors.c
+++ b/arch/arm64/kernel/debug-monitors.c
@@ -385,11 +385,19 @@ void user_enable_single_step(struct task_struct *task)
 
 	if (!test_and_set_ti_thread_flag(ti, TIF_SINGLESTEP))
 		set_regs_spsr_ss(task_pt_regs(task));
+
+	/*
+	 * Ensure that the generic entry code triggers a trap once stepping
+	 * out of a system call prior to executing any user instruction,
+	 * as the generic entry code does not natively check for TIF_SINGLESTEP.
+	 */
+	set_task_syscall_work(task, SYSCALL_EXIT_TRAP);
 }
 NOKPROBE_SYMBOL(user_enable_single_step);
 
 void user_disable_single_step(struct task_struct *task)
 {
 	clear_ti_thread_flag(task_thread_info(task), TIF_SINGLESTEP);
+	clear_task_syscall_work(task, SYSCALL_EXIT_TRAP);
 }
 NOKPROBE_SYMBOL(user_disable_single_step);
diff --git a/arch/arm64/kernel/entry-common.c b/arch/arm64/kernel/entry-common.c
index ceb4eb11232a..9eac76804bd9 100644
--- a/arch/arm64/kernel/entry-common.c
+++ b/arch/arm64/kernel/entry-common.c
@@ -6,7 +6,7 @@
  */
 
 #include <linux/context_tracking.h>
-#include <linux/irq-entry-common.h>
+#include <linux/entry-common.h>
 #include <linux/kasan.h>
 #include <linux/linkage.h>
 #include <linux/livepatch.h>
diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c
index b2b28dcb2826..8bdee1805c93 100644
--- a/arch/arm64/kernel/ptrace.c
+++ b/arch/arm64/kernel/ptrace.c
@@ -8,7 +8,6 @@
  * Copyright (C) 2012 ARM Ltd.
  */
 
-#include <linux/audit.h>
 #include <linux/compat.h>
 #include <linux/kernel.h>
 #include <linux/sched/signal.h>
@@ -18,7 +17,6 @@
 #include <linux/smp.h>
 #include <linux/ptrace.h>
 #include <linux/user.h>
-#include <linux/seccomp.h>
 #include <linux/security.h>
 #include <linux/init.h>
 #include <linux/signal.h>
@@ -37,13 +35,9 @@
 #include <asm/mte.h>
 #include <asm/pointer_auth.h>
 #include <asm/stacktrace.h>
-#include <asm/syscall.h>
 #include <asm/traps.h>
 #include <asm/system_misc.h>
 
-#define CREATE_TRACE_POINTS
-#include <trace/events/syscalls.h>
-
 struct pt_regs_offset {
 	const char *name;
 	int offset;
@@ -2388,136 +2382,6 @@ long arch_ptrace(struct task_struct *child, long request,
 	return ptrace_request(child, request, addr, data);
 }
 
-enum ptrace_syscall_dir {
-	PTRACE_SYSCALL_ENTER = 0,
-	PTRACE_SYSCALL_EXIT,
-};
-
-static __always_inline unsigned long ptrace_save_reg(struct pt_regs *regs,
-						     enum ptrace_syscall_dir dir,
-						     int *regno)
-{
-	unsigned long saved_reg;
-
-	/*
-	 * We have some ABI weirdness here in the way that we handle syscall
-	 * exit stops because we indicate whether or not the stop has been
-	 * signalled from syscall entry or syscall exit by clobbering a general
-	 * purpose register (ip/r12 for AArch32, x7 for AArch64) in the tracee
-	 * and restoring its old value after the stop. This means that:
-	 *
-	 * - Any writes by the tracer to this register during the stop are
-	 *   ignored/discarded.
-	 *
-	 * - The actual value of the register is not available during the stop,
-	 *   so the tracer cannot save it and restore it later.
-	 *
-	 * - Syscall stops behave differently to seccomp and pseudo-step traps
-	 *   (the latter do not nobble any registers).
-	 */
-	*regno = (is_compat_task() ? 12 : 7);
-	saved_reg = regs->regs[*regno];
-	regs->regs[*regno] = dir;
-
-	return saved_reg;
-}
-
-static int report_syscall_entry(struct pt_regs *regs)
-{
-	unsigned long saved_reg;
-	int regno, ret;
-
-	saved_reg = ptrace_save_reg(regs, PTRACE_SYSCALL_ENTER, &regno);
-	ret = !ptrace_report_syscall_permit_entry(regs);
-	if (ret)
-		forget_syscall(regs);
-	regs->regs[regno] = saved_reg;
-
-	return ret;
-}
-
-static void report_syscall_exit(struct pt_regs *regs)
-{
-	unsigned long saved_reg;
-	int regno;
-
-	saved_reg = ptrace_save_reg(regs, PTRACE_SYSCALL_EXIT, &regno);
-	if (!test_thread_flag(TIF_SINGLESTEP)) {
-		ptrace_report_syscall_exit(regs, 0);
-		regs->regs[regno] = saved_reg;
-	} else {
-		regs->regs[regno] = saved_reg;
-
-		/*
-		 * Signal a pseudo-step exception since we are stepping but
-		 * tracer modifications to the registers may have rewound the
-		 * state machine.
-		 */
-		ptrace_report_syscall_exit(regs, 1);
-	}
-}
-
-#ifdef CONFIG_AUDITSYSCALL
-static inline void syscall_enter_audit(struct pt_regs *regs)
-{
-	long syscall = syscall_get_nr(current, regs);
-	unsigned long args[6];
-
-	syscall_get_arguments(current, regs, args);
-	__audit_syscall_entry(syscall, args[0], args[1], args[2], args[3]);
-}
-#endif
-
-bool arm64_syscall_trace_enter(struct pt_regs *regs, unsigned long flags)
-{
-	int ret;
-
-	if (flags & (_TIF_SYSCALL_EMU | _TIF_SYSCALL_TRACE)) {
-		ret = report_syscall_entry(regs);
-		if (ret || (flags & _TIF_SYSCALL_EMU))
-			return false;
-
-		/* ptrace might have changed thread flags */
-		flags = read_thread_flags();
-	}
-
-	/* Do the secure computing after ptrace; failures should be fast. */
-	if (unlikely(flags & _TIF_SECCOMP)) {
-		if (!__seccomp_permit_syscall())
-			return false;
-	}
-
-	if (test_thread_flag(TIF_SYSCALL_TRACEPOINT))
-		trace_sys_enter(regs, syscall_get_nr(current, regs));
-
-	if (unlikely(audit_context()))
-		syscall_enter_audit(regs);
-
-	return true;
-}
-
-static inline bool report_single_step(unsigned long flags)
-{
-	if (flags & _TIF_SYSCALL_EMU)
-		return false;
-
-	return flags & _TIF_SINGLESTEP;
-}
-
-void arm64_syscall_exit_work(struct pt_regs *regs, unsigned long flags)
-{
-	bool step;
-
-	audit_syscall_exit(regs);
-
-	if (flags & _TIF_SYSCALL_TRACEPOINT)
-		trace_sys_exit(regs, syscall_get_return_value(current, regs));
-
-	step = report_single_step(flags);
-	if (step || flags & _TIF_SYSCALL_TRACE)
-		report_syscall_exit(regs);
-}
-
 /*
  * SPSR_ELx bits which are always architecturally RES0 per ARM DDI 0487D.a.
  * We permit userspace to set SSBS (AArch64 bit 12, AArch32 bit 23) which is
diff --git a/arch/arm64/kernel/signal.c b/arch/arm64/kernel/signal.c
index 38e6fa204c17..093eebb9d764 100644
--- a/arch/arm64/kernel/signal.c
+++ b/arch/arm64/kernel/signal.c
@@ -8,8 +8,8 @@
 
 #include <linux/cache.h>
 #include <linux/compat.h>
+#include <linux/entry-common.h>
 #include <linux/errno.h>
-#include <linux/irq-entry-common.h>
 #include <linux/kernel.h>
 #include <linux/signal.h>
 #include <linux/freezer.h>
diff --git a/arch/arm64/kernel/syscall.c b/arch/arm64/kernel/syscall.c
index 72c6e8b7ab21..4a13108defb0 100644
--- a/arch/arm64/kernel/syscall.c
+++ b/arch/arm64/kernel/syscall.c
@@ -2,6 +2,7 @@
 
 #include <linux/compiler.h>
 #include <linux/context_tracking.h>
+#include <linux/entry-common.h>
 #include <linux/errno.h>
 #include <linux/nospec.h>
 #include <linux/ptrace.h>
@@ -58,6 +59,7 @@ static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,
 			   const syscall_fn_t syscall_table[])
 {
 	unsigned long flags = read_thread_flags();
+	unsigned long work;
 
 	regs->orig_x0 = regs->regs[0];
 	regs->syscallno = scno;
@@ -90,7 +92,8 @@ static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,
 		return;
 	}
 
-	if (unlikely(flags & _TIF_SYSCALL_WORK)) {
+	work = READ_ONCE(current_thread_info()->syscall_work);
+	if (unlikely(work & SYSCALL_WORK_ENTER)) {
 		/*
 		 * The de-facto standard way to skip a system call using ptrace
 		 * is to set the system call to -1 (NO_SYSCALL) and set x0 to a
@@ -108,7 +111,7 @@ static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,
 		 */
 		if (scno == NO_SYSCALL)
 			syscall_set_return_value(current, regs, -ENOSYS, 0);
-		if (!arm64_syscall_trace_enter(regs, read_thread_flags()))
+		if (!syscall_trace_enter(regs, work, scno))
 			goto trace_exit;
 
 		/* Reread the syscall number as it might have been modified */
@@ -119,7 +122,7 @@ static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,
 
 	invoke_syscall(regs, scno, sc_nr, syscall_table);
 trace_exit:
-	arm64_syscall_exit_to_user_mode_work(regs);
+	syscall_exit_to_user_mode_work(regs);
 }
 
 void do_el0_svc(struct pt_regs *regs)
-- 
2.34.1



^ permalink raw reply related

* [PATCH v17 14/15] arm64: Inline el0_svc_common()
From: Jinjie Ruan @ 2026-07-21  8:18 UTC (permalink / raw)
  To: catalin.marinas, will, kees, oleg, luto, wad, thuth, peterz,
	mark.rutland, linusw, ada.coupriediaz, kevin.brodsky, yeoreum.yun,
	anshuman.khandual, james.morse, tglx, broonie, pengcan,
	ryan.roberts, liqiang01, linux-arm-kernel, linux-kernel, linux-mm
  Cc: ruanjinjie
In-Reply-To: <20260721081858.1169276-1-ruanjinjie@huawei.com>

After converting arm64 to Generic Entry framework, the compiler no longer
inlines el0_svc_common() into its caller do_el0_svc(). This introduces
a small but measurable overhead in the critical system call path.

Manually forcing el0_svc_common() to be inlined restores the
performance. Benchmarking with perf bench syscall basic on a
Kunpeng 920 platform (based on v6.19-rc1) shows a ~1% performance
uplift.

Inlining this function reduces function prologue/epilogue overhead
and allows for better compiler optimization in the hot system call
dispatch path.

| Metric     | W/O this patch | With this patch | Change    |
| ---------- | -------------- | --------------- | --------- |
| Total time | 2.195 [sec]    | 2.171 [sec]     |  ↓1.1%   |
| usecs/op   | 0.219575       | 0.217192        |  ↓1.1%   |
| ops/sec    | 4,554,260      | 4,604,225       |  ↑1.1%    |

Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
Reviewed-by: Kevin Brodsky <kevin.brodsky@arm.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
 arch/arm64/kernel/syscall.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/kernel/syscall.c b/arch/arm64/kernel/syscall.c
index 4a13108defb0..2535cae9413d 100644
--- a/arch/arm64/kernel/syscall.c
+++ b/arch/arm64/kernel/syscall.c
@@ -55,8 +55,8 @@ static void invoke_syscall(struct pt_regs *regs, unsigned int scno,
 	syscall_set_return_value(current, regs, 0, ret);
 }
 
-static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,
-			   const syscall_fn_t syscall_table[])
+static __always_inline void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,
+					   const syscall_fn_t syscall_table[])
 {
 	unsigned long flags = read_thread_flags();
 	unsigned long work;
-- 
2.34.1



^ permalink raw reply related

* [PATCH v17 00/15] arm64: entry: Convert to Generic Entry
From: Jinjie Ruan @ 2026-07-21  8:18 UTC (permalink / raw)
  To: catalin.marinas, will, kees, oleg, luto, wad, thuth, peterz,
	mark.rutland, linusw, ada.coupriediaz, kevin.brodsky, yeoreum.yun,
	anshuman.khandual, james.morse, tglx, broonie, pengcan,
	ryan.roberts, liqiang01, linux-arm-kernel, linux-kernel, linux-mm
  Cc: ruanjinjie

This is the 17th iteration of the patch series. I would like to express
my sincere gratitude to everyone who has contributed to the reviews
and testing across the previous versions, which helped significantly
refine this work.

Currently, architectures such as x86, Riscv, Loongarch and S390 use
the Generic Entry, which significantly eases maintenance burden
and improves code elegance. Given that arm64 has already successfully
adopted the Generic IRQ Entry in commit b3cf07851b6c ("arm64: entry: Switch
to generic IRQ entry"), it is now the right time to completely convert
arm64 to the Generic Entry infrastructure.

The objective of this series is to bring arm64 in line with other major
architectures, reducing duplicated boilerplate code and simplifying the
sharing of future improvements in entry/exit paths, such as "Syscall User
Dispatch" and "rseq time slice extension" optimizations.

This patch set is rebased on "core/entry". It contains the foundational
updates required for arm64. Following the suggestion from Linus Walleij,
these patches are being submitted separately for inclusion via
the arm64 tree.

Tested successfully on "Kunpeng HIP09" using the following test cases:
 - stress-ng: CPU, Virtual Memory, Syscall, and Interrupt stress tests.
 - hackbench: Evaluated in both thread and pipe modes.
 - kselftests (ptrace): get_syscall_info, set_syscall_info, peeksiginfo.
 - kselftests (breakpoints): breakpoint_test_arm64.
 - kselftests (arm64/abi): syscall-abi and ptrace.
 - kselftests (arm64/fp): fp-ptrace, sve-ptrace, and za-ptrace.
 - kselftests (vDSO): vdso_test_getrandom.
 - ptrace stress test: tracing stress-ng system calls under load.
 - Pseudo-NMI load test using perf.

Changes in v17:
- Rebased on core/entry.
- Make return type of arm64_syscall_trace_enter() bool.
- Use vdso label for arch_syscall_is_vdso_sigreturn() to make it more
  clear as Thomas suggested.
- Update the rework of audit_syscall_entry().
- Prefix these functions with arm64_ to clearly distinguish arch-specific
  logic when switching to the generic entry framework.
- Merge some related patches to make it clearer, and adjust the patch
  sequence so that bugfix patches come first.
- Update the commit message.
- Link to v16: https://lore.kernel.org/all/20260629130616.642022-1-ruanjinjie@huawei.com/

Changes in v16:
- Rebased on v7.2-rc1.
- Convert __secure_computing() to return boolean as Thomas suggested.
- Introduce a weak vesion for arch_syscall_is_vdso_sigreturn()
  to simplify the code.
- Split out the implementation of arch_syscall_is_vdso_sigreturn()
  into a separate patch to facilitate reviewing.
- Break down the syscall_exit_to_user_mode_work() refactoring into
  5 smaller, bite-sized patches for easier review and justification.
- Update the comment and commit message as Ada suggeted.
- Clarify patch titles and align title prefixes for consistency.
- Collect Reviewed-by and Tesed-by.
- Link to v15: https://lore.kernel.org/all/20260511092103.1974980-1-ruanjinjie@huawei.com/

Changes in v15:
- Rebased on v7.1-rc1 and Mark's fix patch in [1].
- Solve issues Sashiko AI pointed out, "Fix potential syscall truncation
  in syscall_trace_enter()".
- Make syscall_exit_to_user_mode_work() __always_inline to keep
  the fast-path performance as Sashiko pointed out.

Changes in v14:
- Initialize ret = 0 in syscall_trace_enter().
- Split into two patch sets as Linus Walleij suggested, so this patch set
  can be applied separately to the arm64 tree.
- Rebased on arm64 for-next/core branch.
- Collect Reviewed-by and Acked-by.
- Link to v13 resend: https://lore.kernel.org/all/20260317082020.737779-15-ruanjinjie@huawei.com/

Changes in v13 resend:
- Fix exit_to_user_mode_prepare_legacy() issues.
- Also move TIF_SINGLESTEP to generic TIF infrastructure for loongarch.
- Use generic TIF bits for arm64 and moving TIF_SINGLESTEP to
  generic TIF for related architectures separately.
- Refactor syscall_trace_enter/exit() to accept flags and Use
  syscall_get_nr() helper separately.
- Tested with slice_test for rseq optimizations.
- Add acked-by.
- Link to v13: https://lore.kernel.org/all/20260313094738.3985794-1-ruanjinjie@huawei.com/

Changes in v13:
- Rebased on v7.0-rc3, so drop the firt applied arm64 patch.
- Use generic TIF bits to enables RSEQ optimization.
- Update most of the commit message to make it more clear.
- Link to v12: https://lore.kernel.org/all/20260203133728.848283-1-ruanjinjie@huawei.com/

Changes in v12:
- Rebased on "sched/core", so remove the four generic entry patches.
- Move "Expand secure_computing() in place" and
  "Use syscall_get_arguments() helper" patch forward, which will group all
  non-functional cleanups at the front.
- Adjust the explanation for moving rseq_syscall() before
  audit_syscall_exit().
- Link to v11: https://lore.kernel.org/all/20260128031934.3906955-1-ruanjinjie@huawei.com/

Changes in v11:
- Remove unused syscall in syscall_trace_enter().
- Update and provide a detailed explanation of the differences after
  moving rseq_syscall() before audit_syscall_exit().
- Rebased on arm64 (for-next/entry), and remove the first applied 3 patchs.
- syscall_exit_to_user_mode_work() for arch reuse instead of adding
  new syscall_exit_to_user_mode_work_prepare() helper.
- Link to v10: https://lore.kernel.org/all/20251222114737.1334364-1-ruanjinjie@huawei.com/

Changes in v10:
- Rebased on v6.19-rc1, rename syscall_exit_to_user_mode_prepare() to
  syscall_exit_to_user_mode_work_prepare() to avoid conflict.
- Also inline syscall_trace_enter().
- Support aarch64 for sud_benchmark.
- Update and correct the commit message.
- Add Reviewed-by.
- Link to v9: https://lore.kernel.org/all/20251204082123.2792067-1-ruanjinjie@huawei.com/

Changes in v9:
- Move "Return early for ptrace_report_syscall_entry() error" patch ahead
  to make it not introduce a regression.
- Not check _TIF_SECCOMP/SYSCALL_EMU for syscall_exit_work() in
  a separate patch.
- Do not report_syscall_exit() for PTRACE_SYSEMU_SINGLESTEP in a separate
  patch.
- Add two performance patch to improve the arm64 performance.
- Add Reviewed-by.
- Link to v8: https://lore.kernel.org/all/20251126071446.3234218-1-ruanjinjie@huawei.com/

Changes in v8:
- Rename "report_syscall_enter()" to "report_syscall_entry()".
- Add ptrace_save_reg() to avoid duplication.
- Remove unused _TIF_WORK_MASK in a standalone patch.
- Align syscall_trace_enter() return value with the generic version.
- Use "scno" instead of regs->syscallno in el0_svc_common().
- Move rseq_syscall() ahead in a standalone patch to clarify it clearly.
- Rename "syscall_trace_exit()" to "syscall_exit_work()".
- Keep the goto in el0_svc_common().
- No argument was passed to __secure_computing() and check -1 not -1L.
- Remove "Add has_syscall_work() helper" patch.
- Move "Add syscall_exit_to_user_mode_prepare() helper" patch later.
- Add miss header for asm/entry-common.h.
- Update the implementation of arch_syscall_is_vdso_sigreturn().
- Add "ARCH_SYSCALL_WORK_EXIT" to be defined as "SECCOMP | SYSCALL_EMU"
  to keep the behaviour unchanged.
- Add more testcases test.
- Add Reviewed-by.
- Update the commit message.
- Link to v7: https://lore.kernel.org/all/20251117133048.53182-1-ruanjinjie@huawei.com/

Jinjie Ruan (15):
  arm64: ptrace: Skip syscall exit reporting for
    PTRACE_SYSEMU_SINGLESTEP
  arm64: ptrace: Rework audit_syscall_entry()
  arm64: ptrace: Open-code seccomp check in syscall_trace_enter()
  arm64: ptrace: Rename and clean up syscall_trace_enter()
  arm64: ptrace: Protect rseq_syscall() from tracer PC modifications
  arm64: syscall: Rework the syscall exit path in el0_svc_common()
  arm64: ptrace: Pass thread flags to trace enter/exit
  arm64: ptrace: Extract arm64_syscall_exit_to_user_mode_work() helper
  arm64: ptrace: Align syscall exit work semantics with generic entry
  arm64: syscall: Use exit-specific flags check in el0_svc_common()
  arm64: syscall: Simplify el0_svc_common() syscall exit path
  arm64: ptrace: Make return type of arm64_syscall_trace_enter() bool
  arm64: entry: Convert to generic entry
  arm64: Inline el0_svc_common()
  arm64: vdso: Expose sigreturn address on vdso to the kernel

 arch/arm64/Kconfig                    |   3 +-
 arch/arm64/include/asm/elf.h          |   1 +
 arch/arm64/include/asm/entry-common.h |  77 +++++++++++++++++
 arch/arm64/include/asm/syscall.h      |   5 +-
 arch/arm64/include/asm/thread_info.h  |  16 +---
 arch/arm64/kernel/debug-monitors.c    |   8 ++
 arch/arm64/kernel/entry-common.c      |   2 +-
 arch/arm64/kernel/ptrace.c            | 115 --------------------------
 arch/arm64/kernel/signal.c            |   2 +-
 arch/arm64/kernel/syscall.c           |  34 +++-----
 arch/arm64/kernel/vdso.c              |  16 ++++
 arch/arm64/kernel/vdso/sigreturn.S    |   1 +
 arch/arm64/kernel/vdso/vdso.lds.S     |   1 +
 13 files changed, 122 insertions(+), 159 deletions(-)

-- 
2.34.1



^ permalink raw reply

* [PATCH v17 09/15] arm64: ptrace: Align syscall exit work semantics with generic entry
From: Jinjie Ruan @ 2026-07-21  8:18 UTC (permalink / raw)
  To: catalin.marinas, will, kees, oleg, luto, wad, thuth, peterz,
	mark.rutland, linusw, ada.coupriediaz, kevin.brodsky, yeoreum.yun,
	anshuman.khandual, james.morse, tglx, broonie, pengcan,
	ryan.roberts, liqiang01, linux-arm-kernel, linux-kernel, linux-mm
  Cc: ruanjinjie
In-Reply-To: <20260721081858.1169276-1-ruanjinjie@huawei.com>

Refactor the syscall exit path to better match the generic entry
framework. Specifically:

- Introduce `_TIF_SYSCALL_EXIT_WORK` to aggregate exit-time thread
  flags (trace, audit, tracepoint).

- Rename `syscall_trace_exit()` to `arm64_syscall_exit_work()` to
  better reflect its role.

- Move `rseq_syscall()` out of `arm64_syscall_exit_work()` and into
  `arm64_syscall_exit_to_user_mode_work()` so that it runs
  unconditionally on all exits, consistent with generic entry.

- Gate `arm64_syscall_exit_work()` behind the new flag check,
  mirroring the generic entry exit's pattern.

Gating audit on `_TIF_SYSCALL_AUDIT` is equivalent to the previous
`audit_context()` check: the audit context and flag are statically
allocated at fork and freed at exit, remaining stable throughout
syscall execution.

No functional changes intended.

Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
 arch/arm64/include/asm/syscall.h     | 8 ++++++--
 arch/arm64/include/asm/thread_info.h | 3 +++
 arch/arm64/kernel/ptrace.c           | 5 +----
 3 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/arch/arm64/include/asm/syscall.h b/arch/arm64/include/asm/syscall.h
index 3e4672ab4a4f..7cc872fde019 100644
--- a/arch/arm64/include/asm/syscall.h
+++ b/arch/arm64/include/asm/syscall.h
@@ -8,6 +8,7 @@
 #include <uapi/linux/audit.h>
 #include <linux/compat.h>
 #include <linux/err.h>
+#include <linux/rseq.h>
 
 typedef long (*syscall_fn_t)(const struct pt_regs *regs);
 
@@ -121,13 +122,16 @@ static inline int syscall_get_arch(struct task_struct *task)
 }
 
 int arm64_syscall_trace_enter(struct pt_regs *regs, unsigned long flags);
-void syscall_trace_exit(struct pt_regs *regs, unsigned long flags);
+void arm64_syscall_exit_work(struct pt_regs *regs, unsigned long flags);
 
 static __always_inline void arm64_syscall_exit_to_user_mode_work(struct pt_regs *regs)
 {
 	unsigned long flags = read_thread_flags();
 
-	syscall_trace_exit(regs, flags);
+	rseq_syscall(regs);
+
+	if (unlikely(flags & _TIF_SYSCALL_EXIT_WORK) || flags & _TIF_SINGLESTEP)
+		arm64_syscall_exit_work(regs, flags);
 }
 
 #endif	/* __ASM_SYSCALL_H */
diff --git a/arch/arm64/include/asm/thread_info.h b/arch/arm64/include/asm/thread_info.h
index 5d7fe3e153c8..56a2c9426a32 100644
--- a/arch/arm64/include/asm/thread_info.h
+++ b/arch/arm64/include/asm/thread_info.h
@@ -112,6 +112,9 @@ void arch_setup_new_exec(void);
 				 _TIF_SYSCALL_TRACEPOINT | _TIF_SECCOMP | \
 				 _TIF_SYSCALL_EMU)
 
+#define _TIF_SYSCALL_EXIT_WORK	(_TIF_SYSCALL_TRACE | _TIF_SYSCALL_AUDIT | \
+				 _TIF_SYSCALL_TRACEPOINT)
+
 #ifdef CONFIG_SHADOW_CALL_STACK
 #define INIT_SCS							\
 	.scs_base	= init_shadow_call_stack,			\
diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c
index 18fa29ae1425..10ead67b4eaf 100644
--- a/arch/arm64/kernel/ptrace.c
+++ b/arch/arm64/kernel/ptrace.c
@@ -28,7 +28,6 @@
 #include <linux/hw_breakpoint.h>
 #include <linux/regset.h>
 #include <linux/elf.h>
-#include <linux/rseq.h>
 
 #include <asm/compat.h>
 #include <asm/cpufeature.h>
@@ -2505,12 +2504,10 @@ static inline bool report_single_step(unsigned long flags)
 	return flags & _TIF_SINGLESTEP;
 }
 
-void syscall_trace_exit(struct pt_regs *regs, unsigned long flags)
+void arm64_syscall_exit_work(struct pt_regs *regs, unsigned long flags)
 {
 	bool step;
 
-	rseq_syscall(regs);
-
 	audit_syscall_exit(regs);
 
 	if (flags & _TIF_SYSCALL_TRACEPOINT)
-- 
2.34.1



^ permalink raw reply related

* [PATCH v17 08/15] arm64: ptrace: Extract arm64_syscall_exit_to_user_mode_work() helper
From: Jinjie Ruan @ 2026-07-21  8:18 UTC (permalink / raw)
  To: catalin.marinas, will, kees, oleg, luto, wad, thuth, peterz,
	mark.rutland, linusw, ada.coupriediaz, kevin.brodsky, yeoreum.yun,
	anshuman.khandual, james.morse, tglx, broonie, pengcan,
	ryan.roberts, liqiang01, linux-arm-kernel, linux-kernel, linux-mm
  Cc: ruanjinjie
In-Reply-To: <20260721081858.1169276-1-ruanjinjie@huawei.com>

Introduce a helper arm64_syscall_exit_to_user_mode_work() that
encapsulates the re-reading of thread flags and the call to
syscall_trace_exit().  Use it in el0_svc_common() to replace
the open-coded instances, removing the duplicated flags read
and making the control flow simpler.

This aligns the arm64 exit path with the pattern expected by the
generic entry infrastructure, which uses a similar
syscall_exit_to_user_mode_work() callback.

No functional changes intended; this is a preparation step for converting
arm64 to the generic entry infrastructure.

Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
 arch/arm64/include/asm/syscall.h | 7 +++++++
 arch/arm64/kernel/syscall.c      | 9 +++------
 2 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/arch/arm64/include/asm/syscall.h b/arch/arm64/include/asm/syscall.h
index 696739f5a250..3e4672ab4a4f 100644
--- a/arch/arm64/include/asm/syscall.h
+++ b/arch/arm64/include/asm/syscall.h
@@ -123,4 +123,11 @@ static inline int syscall_get_arch(struct task_struct *task)
 int arm64_syscall_trace_enter(struct pt_regs *regs, unsigned long flags);
 void syscall_trace_exit(struct pt_regs *regs, unsigned long flags);
 
+static __always_inline void arm64_syscall_exit_to_user_mode_work(struct pt_regs *regs)
+{
+	unsigned long flags = read_thread_flags();
+
+	syscall_trace_exit(regs, flags);
+}
+
 #endif	/* __ASM_SYSCALL_H */
diff --git a/arch/arm64/kernel/syscall.c b/arch/arm64/kernel/syscall.c
index e778aac6fab9..0061fc63e7ba 100644
--- a/arch/arm64/kernel/syscall.c
+++ b/arch/arm64/kernel/syscall.c
@@ -127,16 +127,13 @@ static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,
 	 */
 	if (!has_syscall_work(flags) && !IS_ENABLED(CONFIG_DEBUG_RSEQ)) {
 		flags = read_thread_flags();
-		if (has_syscall_work(flags) || flags & _TIF_SINGLESTEP) {
-			flags = read_thread_flags();
-			syscall_trace_exit(regs, flags);
-		}
+		if (has_syscall_work(flags) || flags & _TIF_SINGLESTEP)
+			arm64_syscall_exit_to_user_mode_work(regs);
 		return;
 	}
 
 trace_exit:
-	flags = read_thread_flags();
-	syscall_trace_exit(regs, flags);
+	arm64_syscall_exit_to_user_mode_work(regs);
 }
 
 void do_el0_svc(struct pt_regs *regs)
-- 
2.34.1



^ permalink raw reply related

* [PATCH v17 12/15] arm64: ptrace: Make return type of arm64_syscall_trace_enter() bool
From: Jinjie Ruan @ 2026-07-21  8:18 UTC (permalink / raw)
  To: catalin.marinas, will, kees, oleg, luto, wad, thuth, peterz,
	mark.rutland, linusw, ada.coupriediaz, kevin.brodsky, yeoreum.yun,
	anshuman.khandual, james.morse, tglx, broonie, pengcan,
	ryan.roberts, liqiang01, linux-arm-kernel, linux-kernel, linux-mm
  Cc: ruanjinjie
In-Reply-To: <20260721081858.1169276-1-ruanjinjie@huawei.com>

In preparation for migrating arm64 to the generic entry infrastructure,
decouple the decision to execute the syscall from the syscall number
itself. The ptrace and seccomp now returns a boolean flag rather than
overloading NO_SYSCALL (-1).

Changes:
- arm64_syscall_trace_enter() returns bool:
  - false: abort syscall execution
  - true: proceed, with syscall number reread from regs
- If we decide to execute the syscall, reread the syscall number
  and check if it is NO_SYSCALL again. Because when a tracer sets
  the syscall number to -1 and provides a custom return value in x0,
  this avoids calling invoke_syscall() which would overwrite the custom
  value with -ENOSYS by sys_ni_syscall().

Behavioral impact (unchanged):
- User-issued syscall(-1): unchanged, returns -ENOSYS.
- Tracer skip with custom x0: unchanged, returns custom x0
- Tracer skip without setting x0: unchanged, returns original
  argument (garbage)
- Normal syscalls and seccomp rejects: unaffected

No functional changes.

Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
 arch/arm64/include/asm/syscall.h | 2 +-
 arch/arm64/kernel/ptrace.c       | 8 ++++----
 arch/arm64/kernel/syscall.c      | 6 +++++-
 3 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/arch/arm64/include/asm/syscall.h b/arch/arm64/include/asm/syscall.h
index 7cc872fde019..f19d4a8824ba 100644
--- a/arch/arm64/include/asm/syscall.h
+++ b/arch/arm64/include/asm/syscall.h
@@ -121,7 +121,7 @@ static inline int syscall_get_arch(struct task_struct *task)
 	return AUDIT_ARCH_AARCH64;
 }
 
-int arm64_syscall_trace_enter(struct pt_regs *regs, unsigned long flags);
+bool arm64_syscall_trace_enter(struct pt_regs *regs, unsigned long flags);
 void arm64_syscall_exit_work(struct pt_regs *regs, unsigned long flags);
 
 static __always_inline void arm64_syscall_exit_to_user_mode_work(struct pt_regs *regs)
diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c
index 10ead67b4eaf..b2b28dcb2826 100644
--- a/arch/arm64/kernel/ptrace.c
+++ b/arch/arm64/kernel/ptrace.c
@@ -2468,14 +2468,14 @@ static inline void syscall_enter_audit(struct pt_regs *regs)
 }
 #endif
 
-int arm64_syscall_trace_enter(struct pt_regs *regs, unsigned long flags)
+bool arm64_syscall_trace_enter(struct pt_regs *regs, unsigned long flags)
 {
 	int ret;
 
 	if (flags & (_TIF_SYSCALL_EMU | _TIF_SYSCALL_TRACE)) {
 		ret = report_syscall_entry(regs);
 		if (ret || (flags & _TIF_SYSCALL_EMU))
-			return NO_SYSCALL;
+			return false;
 
 		/* ptrace might have changed thread flags */
 		flags = read_thread_flags();
@@ -2484,7 +2484,7 @@ int arm64_syscall_trace_enter(struct pt_regs *regs, unsigned long flags)
 	/* Do the secure computing after ptrace; failures should be fast. */
 	if (unlikely(flags & _TIF_SECCOMP)) {
 		if (!__seccomp_permit_syscall())
-			return NO_SYSCALL;
+			return false;
 	}
 
 	if (test_thread_flag(TIF_SYSCALL_TRACEPOINT))
@@ -2493,7 +2493,7 @@ int arm64_syscall_trace_enter(struct pt_regs *regs, unsigned long flags)
 	if (unlikely(audit_context()))
 		syscall_enter_audit(regs);
 
-	return syscall_get_nr(current, regs);
+	return true;
 }
 
 static inline bool report_single_step(unsigned long flags)
diff --git a/arch/arm64/kernel/syscall.c b/arch/arm64/kernel/syscall.c
index 3488afd45d20..72c6e8b7ab21 100644
--- a/arch/arm64/kernel/syscall.c
+++ b/arch/arm64/kernel/syscall.c
@@ -108,7 +108,11 @@ static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,
 		 */
 		if (scno == NO_SYSCALL)
 			syscall_set_return_value(current, regs, -ENOSYS, 0);
-		scno = arm64_syscall_trace_enter(regs, read_thread_flags());
+		if (!arm64_syscall_trace_enter(regs, read_thread_flags()))
+			goto trace_exit;
+
+		/* Reread the syscall number as it might have been modified */
+		scno = syscall_get_nr(current, regs);
 		if (scno == NO_SYSCALL)
 			goto trace_exit;
 	}
-- 
2.34.1



^ permalink raw reply related

* [PATCH v17 03/15] arm64: ptrace: Open-code seccomp check in syscall_trace_enter()
From: Jinjie Ruan @ 2026-07-21  8:18 UTC (permalink / raw)
  To: catalin.marinas, will, kees, oleg, luto, wad, thuth, peterz,
	mark.rutland, linusw, ada.coupriediaz, kevin.brodsky, yeoreum.yun,
	anshuman.khandual, james.morse, tglx, broonie, pengcan,
	ryan.roberts, liqiang01, linux-arm-kernel, linux-kernel, linux-mm
  Cc: ruanjinjie
In-Reply-To: <20260721081858.1169276-1-ruanjinjie@huawei.com>

Refactor syscall_trace_enter() by open-coding the seccomp check to
align with the generic entry framework.

The generic entry implementation expands the seccomp check in-place
by testing SYSCALL_WORK_SECCOMP and directly calling the underlying
__seccomp_permit_syscall() function. Moreover, generic entry explicitly
re-reads work flags after ptrace handling to ensure any updates to
seccomp work flags during the ptrace stop are observed.

Bring arm64 in line with this behavior:
- Re-read thread flags after ptrace handling.

- Test the updated flags for _TIF_SECCOMP and call
  __seccomp_permit_syscall().

No functional changes are intended; this change simplifies future
migration to the generic entry framework.

Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Link: https://lore.kernel.org/all/20260713025712.416366-1-ruanjinjie@huawei.com/
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
Reviewed-by: Kevin Brodsky <kevin.brodsky@arm.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
 arch/arm64/kernel/ptrace.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c
index 643e63a65734..cd0607ec70ef 100644
--- a/arch/arm64/kernel/ptrace.c
+++ b/arch/arm64/kernel/ptrace.c
@@ -2478,11 +2478,16 @@ int syscall_trace_enter(struct pt_regs *regs)
 		ret = report_syscall_entry(regs);
 		if (ret || (flags & _TIF_SYSCALL_EMU))
 			return NO_SYSCALL;
+
+		/* ptrace might have changed thread flags */
+		flags = read_thread_flags();
 	}
 
 	/* Do the secure computing after ptrace; failures should be fast. */
-	if (!seccomp_permit_syscall())
-		return NO_SYSCALL;
+	if (unlikely(flags & _TIF_SECCOMP)) {
+		if (!__seccomp_permit_syscall())
+			return NO_SYSCALL;
+	}
 
 	if (test_thread_flag(TIF_SYSCALL_TRACEPOINT))
 		trace_sys_enter(regs, regs->syscallno);
-- 
2.34.1



^ permalink raw reply related

* [PATCH v17 06/15] arm64: syscall: Rework the syscall exit path in el0_svc_common()
From: Jinjie Ruan @ 2026-07-21  8:18 UTC (permalink / raw)
  To: catalin.marinas, will, kees, oleg, luto, wad, thuth, peterz,
	mark.rutland, linusw, ada.coupriediaz, kevin.brodsky, yeoreum.yun,
	anshuman.khandual, james.morse, tglx, broonie, pengcan,
	ryan.roberts, liqiang01, linux-arm-kernel, linux-kernel, linux-mm
  Cc: ruanjinjie
In-Reply-To: <20260721081858.1169276-1-ruanjinjie@huawei.com>

In preparation for moving arm64 over to the generic entry,
invert the nested conditional flags check within el0_svc_common().

No functional changes.

Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
 arch/arm64/kernel/syscall.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/kernel/syscall.c b/arch/arm64/kernel/syscall.c
index 3e78e159b2a1..b8d7d29a431b 100644
--- a/arch/arm64/kernel/syscall.c
+++ b/arch/arm64/kernel/syscall.c
@@ -127,8 +127,9 @@ static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,
 	 */
 	if (!has_syscall_work(flags) && !IS_ENABLED(CONFIG_DEBUG_RSEQ)) {
 		flags = read_thread_flags();
-		if (!has_syscall_work(flags) && !(flags & _TIF_SINGLESTEP))
-			return;
+		if (has_syscall_work(flags) || flags & _TIF_SINGLESTEP)
+			syscall_trace_exit(regs);
+		return;
 	}
 
 trace_exit:
-- 
2.34.1



^ permalink raw reply related

* [PATCH v17 05/15] arm64: ptrace: Protect rseq_syscall() from tracer PC modifications
From: Jinjie Ruan @ 2026-07-21  8:18 UTC (permalink / raw)
  To: catalin.marinas, will, kees, oleg, luto, wad, thuth, peterz,
	mark.rutland, linusw, ada.coupriediaz, kevin.brodsky, yeoreum.yun,
	anshuman.khandual, james.morse, tglx, broonie, pengcan,
	ryan.roberts, liqiang01, linux-arm-kernel, linux-kernel, linux-mm
  Cc: ruanjinjie
In-Reply-To: <20260721081858.1169276-1-ruanjinjie@huawei.com>

Move the rseq_syscall() check earlier in the syscall exit path to ensure
it operates on the original instruction pointer (regs->pc) before any
potential modification by a tracer.

[Background]
When CONFIG_DEBUG_RSEQ is enabled, rseq_syscall() verifies that a system
call was not executed within an rseq critical section by examining
regs->pc. If a violation is detected, it triggers a SIGSEGV.

[Problem]
Currently, arm64 invokes rseq_syscall() after report_syscall_exit().
However, during report_syscall_exit(), a ptrace tracer can modify the
task's instruction pointer via PTRACE_SETREGSET (with NT_PRSTATUS). This
leads to an inconsistency where rseq may analyze a post-trace PC instead
of the actual PC at the time of syscall exit.

[Why this matters]
The rseq check is intended to validate the execution context of the
syscall itself. Analyzing a tracer-modified PC can lead to incorrect
detection or missed violations. Moving the check earlier ensures rseq
sees the authentic state of the task.

[Alignment]
This change aligns arm64 with:
- Generic entry, which calls rseq_syscall() first.
- arm32 implementation, which also performs the check before audit.

[Impact]
There is no functional change to signal delivery; SIGSEGV will still be
processed in arm64_exit_to_user_mode() at the end of the exit path.

Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Thomas Gleixner <tglx@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
Reviewed-by: Kevin Brodsky <kevin.brodsky@arm.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
 arch/arm64/kernel/ptrace.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c
index 4c7dba12629c..a7091333a9e9 100644
--- a/arch/arm64/kernel/ptrace.c
+++ b/arch/arm64/kernel/ptrace.c
@@ -2511,6 +2511,8 @@ void syscall_trace_exit(struct pt_regs *regs)
 	unsigned long flags = read_thread_flags();
 	bool step;
 
+	rseq_syscall(regs);
+
 	audit_syscall_exit(regs);
 
 	if (flags & _TIF_SYSCALL_TRACEPOINT)
@@ -2519,8 +2521,6 @@ void syscall_trace_exit(struct pt_regs *regs)
 	step = report_single_step(flags);
 	if (step || flags & _TIF_SYSCALL_TRACE)
 		report_syscall_exit(regs);
-
-	rseq_syscall(regs);
 }
 
 /*
-- 
2.34.1



^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox