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 v17 06/15] arm64: syscall: Rework the syscall exit path in el0_svc_common()
From: Linus Walleij @ 2026-07-21  8:45 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: <CAD++jLmHw3ccWQj-AWvgrj7z6iwPk=MC0-UMaUqLeNpsbxW54g@mail.gmail.com>

On Tue, Jul 21, 2026 at 10:43 AM Linus Walleij <linusw@kernel.org> wrote:
> 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>

Aha I see you refactor it again in patch 8, no big deal then.
This is fine, add the Reviewed-by.

Yours,
Linus Walleij


^ permalink raw reply

* Re: [PATCH v17 08/15] arm64: ptrace: Extract arm64_syscall_exit_to_user_mode_work() helper
From: Linus Walleij @ 2026-07-21  8:45 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-9-ruanjinjie@huawei.com>

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

> 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>

Looks right to me!
Reviewed-by: Linus Walleij <linusw@kernel.org>

Yours,
Linus Walleij


^ permalink raw reply

* [PATCH v17 11/15] arm64: syscall: Simplify el0_svc_common() syscall exit path
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>

Remove the redundant nested conditional check within the system call
exit path of el0_svc_common() to streamline the exit sequence.

When entering this fast-path block, CONFIG_DEBUG_RSEQ is guaranteed to be
disabled so the call of rseq_syscall() is a no-op. Under this constraint,
the code logic inside the block becomes completely identical to what
the arm64_syscall_exit_to_user_mode_work() helper already does. Replace
that nested logic with a direct invocation of the helper, eliminating
redundant code.

Before:

| if (... && !IS_ENABLED(CONFIG_DEBUG_RSEQ)) {
| 	flags = read_thread_flags();
| 	if (unlikely(flags & _TIF_SYSCALL_EXIT_WORK) || flags & _TIF_SINGLESTEP)
| 		arm64_syscall_exit_to_user_mode_work(regs);
| 	return;
| }
| trace_exit:
|	arm64_syscall_exit_to_user_mode_work(regs);

After simplify:

| if (... && !IS_ENABLED(CONFIG_DEBUG_RSEQ)) {
|	arm64_syscall_exit_to_user_mode_work(regs);
|	return;
| }
| trace_exit:
|	arm64_syscall_exit_to_user_mode_work(regs);

Furthermore, Since both the conditional fast-path and the fallback
slow-path now uniformly invoke arm64_syscall_exit_to_user_mode_work(),
this explicit conditional branch is entirely redundant regardless of
whether the evaluation is true or false. Removing it collapses
the duplicated logic into a single, unconditional path.

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 | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/arch/arm64/kernel/syscall.c b/arch/arm64/kernel/syscall.c
index a8e0bf8d362e..3488afd45d20 100644
--- a/arch/arm64/kernel/syscall.c
+++ b/arch/arm64/kernel/syscall.c
@@ -114,19 +114,6 @@ static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,
 	}
 
 	invoke_syscall(regs, scno, sc_nr, syscall_table);
-
-	/*
-	 * The tracing status may have changed under our feet, so we have to
-	 * check again. However, if we were tracing entry, then we always trace
-	 * exit regardless, as the old entry assembly did.
-	 */
-	if (!(unlikely(flags & _TIF_SYSCALL_WORK)) && !IS_ENABLED(CONFIG_DEBUG_RSEQ)) {
-		flags = read_thread_flags();
-		if (unlikely(flags & _TIF_SYSCALL_EXIT_WORK) || flags & _TIF_SINGLESTEP)
-			arm64_syscall_exit_to_user_mode_work(regs);
-		return;
-	}
-
 trace_exit:
 	arm64_syscall_exit_to_user_mode_work(regs);
 }
-- 
2.34.1



^ permalink raw reply related

* [PATCH v17 10/15] arm64: syscall: Use exit-specific flags check 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>

Use exit-specific _TIF_SYSCALL_EXIT_WORK mask to filter out entry-only
flags during the system call exit path checks. This aligns arm64 with
the generic entry framework's SYSCALL_WORK_EXIT semantics.

[Rationale]
The current syscall exit path re-evaluates the thread flags using
the global _TIF_SYSCALL_WORK mask. However, _TIF_SYSCALL_WORK includes
flags that are strictly relevant to system call entry processing:

1. _TIF_SECCOMP: Seccomp filtering (__seccomp_permit_syscall()) only
  runs on entry. There is no seccomp callback for syscall exit.

2. _TIF_SYSCALL_EMU: In PTRACE_SYSEMU mode, the syscall is
  intercepted and skipped on entry. Since the syscall is never fully
  executed, reporting a separate syscall exit stop is unnecessary.

[Changes]
- _TIF_SYSCALL_EXIT_WORK: A new mask containing only flags
  requiring exit-time processing: _TIF_SYSCALL_TRACE, _TIF_SYSCALL_AUDIT,
  and _TIF_SYSCALL_TRACEPOINT.

- Optimize re-evaluation check: Use _TIF_SYSCALL_EXIT_WORK inside the
  el0_svc_common() fast-path block to prevent redundant exit work execution
  when entry-only flags fluctuate or are synchronously modified under
  the hood. The outermost gating maintains _TIF_SYSCALL_WORK to preserve
  architectural entry-exit symmetry for tracers.

- Cleanup: Remove the has_syscall_work() helper as it is no longer
  needed, supporting direct flag comparison to clearly distinguish between
  entry and exit mandates.

[Impact]
Unnecessary exit tracing and auditing processing are safely bypassed when
entry-specific flags fluctuate during the fast-path re-check block. This
safely streamlines the syscall exit sequence to mirror the generic entry
syscall exit behaviors without breaking debugger expectations.

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>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
 arch/arm64/kernel/syscall.c | 11 +++--------
 1 file changed, 3 insertions(+), 8 deletions(-)

diff --git a/arch/arm64/kernel/syscall.c b/arch/arm64/kernel/syscall.c
index 0061fc63e7ba..a8e0bf8d362e 100644
--- a/arch/arm64/kernel/syscall.c
+++ b/arch/arm64/kernel/syscall.c
@@ -54,11 +54,6 @@ static void invoke_syscall(struct pt_regs *regs, unsigned int scno,
 	syscall_set_return_value(current, regs, 0, ret);
 }
 
-static inline bool has_syscall_work(unsigned long flags)
-{
-	return unlikely(flags & _TIF_SYSCALL_WORK);
-}
-
 static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,
 			   const syscall_fn_t syscall_table[])
 {
@@ -95,7 +90,7 @@ static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,
 		return;
 	}
 
-	if (has_syscall_work(flags)) {
+	if (unlikely(flags & _TIF_SYSCALL_WORK)) {
 		/*
 		 * 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
@@ -125,9 +120,9 @@ static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,
 	 * check again. However, if we were tracing entry, then we always trace
 	 * exit regardless, as the old entry assembly did.
 	 */
-	if (!has_syscall_work(flags) && !IS_ENABLED(CONFIG_DEBUG_RSEQ)) {
+	if (!(unlikely(flags & _TIF_SYSCALL_WORK)) && !IS_ENABLED(CONFIG_DEBUG_RSEQ)) {
 		flags = read_thread_flags();
-		if (has_syscall_work(flags) || flags & _TIF_SINGLESTEP)
+		if (unlikely(flags & _TIF_SYSCALL_EXIT_WORK) || flags & _TIF_SINGLESTEP)
 			arm64_syscall_exit_to_user_mode_work(regs);
 		return;
 	}
-- 
2.34.1



^ permalink raw reply related

* Re: [PATCH v17 09/15] arm64: ptrace: Align syscall exit work semantics with generic entry
From: Linus Walleij @ 2026-07-21  8:46 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-10-ruanjinjie@huawei.com>

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

> 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>

It does indeed align to generic entry AFAICT.
Reviewed-by: Linus Walleij <linusw@kernel.org>

Yours,
Linus Walleij


^ permalink raw reply

* Re: [PATCH 3/4] drm/rockchip: lvds: add RK3568 support
From: Chaoyi Chen @ 2026-07-21  8:48 UTC (permalink / raw)
  To: Rok Markovič
  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: <3a5685992b5e335f2ebf50ee740b8a36@kanardia.eu>

On 7/21/2026 4:31 PM, Rok Markovič wrote:
> 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)?
> 

Just out of curiosity. I think it's fine as it is now :)


>>
>>> 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_ */
> 
> 

-- 
Best, 
Chaoyi



^ permalink raw reply

* Re: [PATCH v17 15/15] arm64: vdso: Expose sigreturn address on vdso to the kernel
From: Thomas Weißschuh @ 2026-07-21  8:51 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, linusw, linux-kernel, luto,
	james.morse, tglx, liqiang01, yeoreum.yun
In-Reply-To: <20260721081858.1169276-16-ruanjinjie@huawei.com>

On Tue, Jul 21, 2026 at 04:18:58PM +0800, Jinjie Ruan wrote:
> 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)

With the new label this code snippet here shouldnt be needed anymore.

> 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

From the subject and description I would not have expected this to be in here.
Split into its own patch?

>  	select ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH
>  	select ARCH_WANT_COMPAT_IPC_PARSE_VERSION if COMPAT
>  	select ARCH_WANT_DEFAULT_BPF_JIT

(...)

For the rest:

Reviewed-by: Thomas Weißschuh <thomas.weissschuh@linutronix.de>


Thomas


^ permalink raw reply

* Re: [PATCH v17 11/15] arm64: syscall: Simplify el0_svc_common() syscall exit path
From: Linus Walleij @ 2026-07-21  8:51 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-12-ruanjinjie@huawei.com>

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

> Remove the redundant nested conditional check within the system call
> exit path of el0_svc_common() to streamline the exit sequence.
>
> When entering this fast-path block, CONFIG_DEBUG_RSEQ is guaranteed to be
> disabled so the call of rseq_syscall() is a no-op.

Please explain why this is guaranteed.

> Under this constraint,
> the code logic inside the block becomes completely identical to what
> the arm64_syscall_exit_to_user_mode_work() helper already does. Replace
> that nested logic with a direct invocation of the helper, eliminating
> redundant code.
>
> Before:
>
> | if (... && !IS_ENABLED(CONFIG_DEBUG_RSEQ)) {
> |       flags = read_thread_flags();
> |       if (unlikely(flags & _TIF_SYSCALL_EXIT_WORK) || flags & _TIF_SINGLESTEP)
> |               arm64_syscall_exit_to_user_mode_work(regs);
> |       return;
> | }
> | trace_exit:
> |       arm64_syscall_exit_to_user_mode_work(regs);
>
> After simplify:
>
> | if (... && !IS_ENABLED(CONFIG_DEBUG_RSEQ)) {
> |       arm64_syscall_exit_to_user_mode_work(regs);
> |       return;
> | }
> | trace_exit:
> |       arm64_syscall_exit_to_user_mode_work(regs);
>
> Furthermore, Since both the conditional fast-path and the fallback
> slow-path now uniformly invoke arm64_syscall_exit_to_user_mode_work(),
> this explicit conditional branch is entirely redundant regardless of
> whether the evaluation is true or false. Removing it collapses
> the duplicated logic into a single, unconditional path.
>
> 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>

After looking at it for a while I conclude you are right, provided
the reasoning about !CONFIG_DEBUG_RSEQ holds. So with
that explained:
Reviewed-by: Linus Walleij <linusw@kernel.org>

Yours,
Linus Walleij


^ permalink raw reply

* Re: [PATCH v17 12/15] arm64: ptrace: Make return type of arm64_syscall_trace_enter() bool
From: Linus Walleij @ 2026-07-21  8:52 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-13-ruanjinjie@huawei.com>

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

> 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>

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

Yours,
Linus Walleij


^ permalink raw reply

* Re: [PATCH v1 2/4] drm: mxsfb: Allow optional LCDIF interface format override
From: Marco Felsch @ 2026-07-21  8:52 UTC (permalink / raw)
  To: Francesco Dolcini
  Cc: Marek Vasut, Stefan Agner, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Frank Li, Sascha Hauer,
	Pengutronix Kernel Team, Fabio Estevam, imx, devicetree,
	linux-kernel, dri-devel, Francesco Dolcini, linux-arm-kernel
In-Reply-To: <20260717121847.488148-3-francesco@dolcini.it>

Hi Francesco,

On 26-07-17, Francesco Dolcini wrote:
> From: Francesco Dolcini <francesco.dolcini@toradex.com>
> 
> LCDIF programs LCD_DATABUS_WIDTH from the selected media bus format. The
> format reported by the downstream panel or bridge describes the display
> input, but it does not describe how the LCDIF data pins are physically
> wired on the board.
> 
> These can differ. For example, a 16-bit LCDIF bus can be connected to a
> 24-bit display by wiring the available color bits to the corresponding
> display inputs. In that case, using the display's 24-bit format to
> configure LCDIF selects the wrong data-bus mode and changes the assignment
> of color bits on the LCD_DATA pins.
> 
> Read the optional interface-pix-fmt property from the LCDIF node and use
> it to select the media bus format used to configure LCDIF. This allows
> the LCDIF bus mode to describe the physical interface independently of
> the downstream display format.
> 
> When the optional property is absent, continue using the format reported
> by the downstream display device, preserving the existing behavior.
> 
> This follows the same approach already implemented in
> drivers/gpu/drm/imx/ipuv3/parallel-display.c.

According the driver comment, this is a legacy property. If you just
want to limit the physical bus width you could use the common of-graph
endpoint bus-width property.

Regards,
  Marco

> 
> Signed-off-by: Francesco Dolcini <francesco.dolcini@toradex.com>
> ---
>  drivers/gpu/drm/mxsfb/mxsfb_drv.c | 16 ++++++++++++++++
>  drivers/gpu/drm/mxsfb/mxsfb_drv.h |  2 ++
>  drivers/gpu/drm/mxsfb/mxsfb_kms.c |  8 ++++++++
>  3 files changed, 26 insertions(+)
> 
> diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.c b/drivers/gpu/drm/mxsfb/mxsfb_drv.c
> index 9b8fbda85d28..edf347968fd2 100644
> --- a/drivers/gpu/drm/mxsfb/mxsfb_drv.c
> +++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.c
> @@ -12,7 +12,9 @@
>  #include <linux/clk.h>
>  #include <linux/dma-mapping.h>
>  #include <linux/io.h>
> +#include <linux/media-bus-format.h>
>  #include <linux/module.h>
> +#include <linux/of.h>
>  #include <linux/platform_device.h>
>  #include <linux/property.h>
>  #include <linux/pm_runtime.h>
> @@ -209,7 +211,10 @@ static int mxsfb_load(struct drm_device *drm,
>  		      const struct mxsfb_devdata *devdata)
>  {
>  	struct platform_device *pdev = to_platform_device(drm->dev);
> +	struct device_node *np = pdev->dev.of_node;
>  	struct mxsfb_drm_private *mxsfb;
> +	u32 bus_format = 0;
> +	const char *fmt;
>  	int ret;
>  
>  	mxsfb = devm_kzalloc(&pdev->dev, sizeof(*mxsfb), GFP_KERNEL);
> @@ -236,6 +241,17 @@ static int mxsfb_load(struct drm_device *drm,
>  	if (IS_ERR(mxsfb->clk_disp_axi))
>  		mxsfb->clk_disp_axi = NULL;
>  
> +	ret = of_property_read_string(np, "interface-pix-fmt", &fmt);
> +	if (!ret) {
> +		if (!strcmp(fmt, "rgb24"))
> +			bus_format = MEDIA_BUS_FMT_RGB888_1X24;
> +		else if (!strcmp(fmt, "rgb565"))
> +			bus_format = MEDIA_BUS_FMT_RGB565_1X16;
> +		else if (!strcmp(fmt, "rgb666"))
> +			bus_format = MEDIA_BUS_FMT_RGB666_1X18;
> +	}
> +	mxsfb->bus_format = bus_format;
> +
>  	ret = dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32));
>  	if (ret)
>  		return ret;
> diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.h b/drivers/gpu/drm/mxsfb/mxsfb_drv.h
> index d160d921b25f..f64800f06a69 100644
> --- a/drivers/gpu/drm/mxsfb/mxsfb_drv.h
> +++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.h
> @@ -46,6 +46,8 @@ struct mxsfb_drm_private {
>  	struct drm_connector		*connector;
>  	struct drm_bridge		*bridge;
>  
> +	u32 bus_format;
> +
>  	bool				crc_active;
>  };
>  
> diff --git a/drivers/gpu/drm/mxsfb/mxsfb_kms.c b/drivers/gpu/drm/mxsfb/mxsfb_kms.c
> index d8ebebc5314b..8b482727aed5 100644
> --- a/drivers/gpu/drm/mxsfb/mxsfb_kms.c
> +++ b/drivers/gpu/drm/mxsfb/mxsfb_kms.c
> @@ -386,6 +386,14 @@ static void mxsfb_crtc_atomic_enable(struct drm_crtc *crtc,
>  	if (!bus_format)
>  		bus_format = MEDIA_BUS_FMT_RGB888_1X24;
>  
> +	/*
> +	 * Prefer bus format set via optional "interface-pix-fmt" DT property,
> +	 * over panel or next bridge bus format. This is necessary to support
> +	 * 24bit DPI panels connected to a 18bit interface, for example.
> +	 */
> +	if (mxsfb->bus_format)
> +		bus_format = mxsfb->bus_format;
> +
>  	mxsfb_crtc_mode_set_nofb(mxsfb, bridge_state, bus_format);
>  
>  	/* Write cur_buf as well to avoid an initial corrupt frame */
> -- 
> 2.47.3
> 
> 
> 

-- 
#gernperDu 
#CallMeByMyFirstName

Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | https://www.pengutronix.de/ |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-9    |


^ permalink raw reply

* Re: [PATCH v4 5/6] KVM: arm64: Add HDBSS fault handling and buffer flush
From: Inochi Amaoto @ 2026-07-21  8:53 UTC (permalink / raw)
  To: Leonardo Bras, Tian Zheng
  Cc: maz, oupton, catalin.marinas, will, yuzenghui, wangzhou1,
	yangjinqian1, caijian11, liuyonglong, yezhenyu2, yubihong,
	linuxarm, joey.gouly, kvmarm, kvm, linux-arm-kernel, linux-kernel,
	seiden, suzuki.poulose, Inochi Amaoto
In-Reply-To: <alpN16Q3MgVHJzsN@LeoBrasDK>

On Fri, Jul 17, 2026 at 04:44:24PM +0100, Leonardo Bras wrote:
> On Fri, Jul 17, 2026 at 02:51:12PM +0800, Tian Zheng wrote:
> > 
> > On 7/14/2026 10:19 PM, Leonardo Bras wrote:
> > > On Tue, Jul 14, 2026 at 09:27:15PM +0800, Tian Zheng wrote:
> > > > On 7/14/2026 6:50 PM, Leonardo Bras wrote:
> > > > > On Tue, Jul 14, 2026 at 03:38:39PM +0800, Tian Zheng wrote:
> > > > > > On 7/13/2026 10:06 PM, Leonardo Bras wrote:
> > > > > > > On Thu, Jul 09, 2026 at 06:40:25PM +0800, Tian Zheng wrote:
> > > > > > > > From: eillon <yezhenyu2@huawei.com>
> > > > > > > > 
> > > > > > > > Add HDBSS fault handling for buffer full, external abort, and general
> > > > > > > > protection fault (GPF) events. When the HDBSS buffer becomes full,
> > > > > > > > the hardware traps to EL2 with an HDBSSF event, which is handled by
> > > > > > > > setting a flush request.
> > > > > > > > 
> > > > > > > > Add kvm_flush_hdbss_buffer() to consume HDBSS buffer entries and
> > > > > > > > propagate dirty information into the userspace-visible dirty bitmap.
> > > > > > > > Flush is triggered on vcpu_put, check_vcpu_requests, and
> > > > > > > > sync_dirty_log.
> > > > > > > > 
> > > > > > > > Add esr_iss2_is_hdbssf() helper for HDBSS fault detection in guest
> > > > > > > > abort handling.
> > > > > > > > 
> > > > > > > > Signed-off-by: Eillon <yezhenyu2@huawei.com>
> > > > > > > > Signed-off-by: Tian Zheng <zhengtian10@huawei.com>
> > > > > > > > ---
> > > > > > > >     arch/arm64/include/asm/esr.h           |  5 +++
> > > > > > > >     arch/arm64/include/asm/kvm_dirty_bit.h | 11 +++++
> > > > > > > >     arch/arm64/include/asm/kvm_host.h      |  1 +
> > > > > > > >     arch/arm64/kvm/arm.c                   | 14 ++++++
> > > > > > > >     arch/arm64/kvm/dirty_bit.c             | 62 ++++++++++++++++++++++++++
> > > > > > > >     arch/arm64/kvm/mmu.c                   |  4 ++
> > > > > > > >     6 files changed, 97 insertions(+)
> > > > > > > > 
> > > > > > > > diff --git a/arch/arm64/include/asm/esr.h b/arch/arm64/include/asm/esr.h
> > > > > > > > index 81c17320a588..2e6b679b5908 100644
> > > > > > > > --- a/arch/arm64/include/asm/esr.h
> > > > > > > > +++ b/arch/arm64/include/asm/esr.h
> > > > > > > > @@ -437,6 +437,11 @@
> > > > > > > >     #ifndef __ASSEMBLER__
> > > > > > > >     #include <asm/types.h>
> > > > > > > > 
> > > > > > > > +static inline bool esr_iss2_is_hdbssf(unsigned long esr)
> > > > > > > > +{
> > > > > > > > +	return ESR_ELx_ISS2(esr) & ESR_ELx_HDBSSF;
> > > > > > > This will return a long, which will be casted as bool.
> > > > > > > In general, what I see in the kernel is something like:
> > > > > > > 
> > > > > > > 	return !!(ESR_ELx_ISS2(esr) & ESR_ELx_HDBSSF)
> > > > > > ok!
> > > > > > 
> > > > > > 
> > > > > > > > +}
> > > > > > > > +
> > > > > > > >     static inline unsigned long esr_brk_comment(unsigned long esr)
> > > > > > > >     {
> > > > > > > >     	return esr & ESR_ELx_BRK64_ISS_COMMENT_MASK;
> > > > > > > > diff --git a/arch/arm64/include/asm/kvm_dirty_bit.h b/arch/arm64/include/asm/kvm_dirty_bit.h
> > > > > > > > index 84b12f0a10af..4b28000e972f 100644
> > > > > > > > --- a/arch/arm64/include/asm/kvm_dirty_bit.h
> > > > > > > > +++ b/arch/arm64/include/asm/kvm_dirty_bit.h
> > > > > > > > @@ -10,7 +10,18 @@
> > > > > > > >     #include <asm/kvm_pgtable.h>
> > > > > > > >     #include <asm/sysreg.h>
> > > > > > > > 
> > > > > > > > +/* HDBSS entry field definitions */
> > > > > > > > +#define HDBSS_ENTRY_VALID	BIT(0)
> > > > > > > > +#define HDBSS_ENTRY_TTWL_SHIFT (1)
> > > > > > > > +#define HDBSS_ENTRY_TTWL_MASK (GENMASK(3, 1))
> > > > > > > > +#define HDBSS_ENTRY_TTWL(x) \
> > > > > > > > +	(((x) << HDBSS_ENTRY_TTWL_SHIFT) & HDBSS_ENTRY_TTWL_MASK)
> > > > > > > > +#define HDBSS_ENTRY_TTWL_RESV HDBSS_ENTRY_TTWL(-4)
> > > > > > > > +#define HDBSS_ENTRY_IPA	GENMASK_ULL(55, 12)
> > > > > > > > +
> > > > > > > >     int kvm_arm_vcpu_alloc_hdbss(struct kvm_vcpu *vcpu, unsigned int order);
> > > > > > > >     void kvm_arm_vcpu_free_hdbss(struct kvm_vcpu *vcpu);
> > > > > > > > +void kvm_flush_hdbss_buffer(struct kvm_vcpu *vcpu);
> > > > > > > > +int kvm_handle_hdbss_fault(struct kvm_vcpu *vcpu);
> > > > > > > > 
> > > > > > > >     #endif /* __ARM64_KVM_DIRTY_BIT_H__ */
> > > > > > > > diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
> > > > > > > > index c41ec6d9c45a..cecfb884a64f 100644
> > > > > > > > --- a/arch/arm64/include/asm/kvm_host.h
> > > > > > > > +++ b/arch/arm64/include/asm/kvm_host.h
> > > > > > > > @@ -55,6 +55,7 @@
> > > > > > > >     #define KVM_REQ_GUEST_HYP_IRQ_PENDING	KVM_ARCH_REQ(9)
> > > > > > > >     #define KVM_REQ_MAP_L1_VNCR_EL2		KVM_ARCH_REQ(10)
> > > > > > > >     #define KVM_REQ_VGIC_PROCESS_UPDATE	KVM_ARCH_REQ(11)
> > > > > > > > +#define KVM_REQ_FLUSH_HDBSS			KVM_ARCH_REQ(12)
> > > > > > > > 
> > > > > > > >     #define KVM_DIRTY_LOG_MANUAL_CAPS   (KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE | \
> > > > > > > >     				     KVM_DIRTY_LOG_INITIALLY_SET)
> > > > > > > > diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> > > > > > > > index bf6688245d83..566953a4e23a 100644
> > > > > > > > --- a/arch/arm64/kvm/arm.c
> > > > > > > > +++ b/arch/arm64/kvm/arm.c
> > > > > > > > @@ -755,6 +755,9 @@ void kvm_arch_vcpu_put(struct kvm_vcpu *vcpu)
> > > > > > > >     		kvm_vcpu_put_hw_mmu(vcpu);
> > > > > > > >     	kvm_arm_vmid_clear_active();
> > > > > > > > 
> > > > > > > > +	if (vcpu->kvm->arch.enable_hdbss)
> > > > > > > > +		kvm_flush_hdbss_buffer(vcpu);
> > > > > > > > +
> > > > > > > >     	vcpu_clear_on_unsupported_cpu(vcpu);
> > > > > > > >     	vcpu->cpu = -1;
> > > > > > > >     }
> > > > > > > > @@ -1157,6 +1160,9 @@ static int check_vcpu_requests(struct kvm_vcpu *vcpu)
> > > > > > > >     		if (kvm_dirty_ring_check_request(vcpu))
> > > > > > > >     			return 0;
> > > > > > > > 
> > > > > > > > +		if (kvm_check_request(KVM_REQ_FLUSH_HDBSS, vcpu))
> > > > > > > > +			kvm_flush_hdbss_buffer(vcpu);
> > > > > > > > +
> > > > > > > >     		check_nested_vcpu_requests(vcpu);
> > > > > > > >     	}
> > > > > > > > 
> > > > > > > > @@ -1971,7 +1977,15 @@ long kvm_arch_vcpu_unlocked_ioctl(struct file *filp, unsigned int ioctl,
> > > > > > > > 
> > > > > > > >     void kvm_arch_sync_dirty_log(struct kvm *kvm, struct kvm_memory_slot *memslot)
> > > > > > > >     {
> > > > > > > > +	/*
> > > > > > > > +	 * Flush all CPUs' dirty log buffers to the dirty_bitmap.  Called
> > > > > > > > +	 * before reporting dirty_bitmap to userspace. Send a request with
> > > > > > > > +	 * KVM_REQUEST_WAIT to flush buffer synchronously.
> > > > > > > > +	 */
> > > > > > > > +	if (!kvm->arch.enable_hdbss)
> > > > > > > > +		return;
> > > > > > > > 
> > > > > > > > +	kvm_make_all_cpus_request(kvm, KVM_REQ_FLUSH_HDBSS | KVM_REQUEST_WAIT);
> > > > > > > >     }
> > > > > > > > 
> > > > > > > >     static int kvm_vm_ioctl_set_device_addr(struct kvm *kvm,
> > > > > > > > diff --git a/arch/arm64/kvm/dirty_bit.c b/arch/arm64/kvm/dirty_bit.c
> > > > > > > > index 6c7a6ef66b5a..002366337637 100644
> > > > > > > > --- a/arch/arm64/kvm/dirty_bit.c
> > > > > > > > +++ b/arch/arm64/kvm/dirty_bit.c
> > > > > > > > @@ -50,3 +50,65 @@ void kvm_arm_vcpu_free_hdbss(struct kvm_vcpu *vcpu)
> > > > > > > > 
> > > > > > > >     	vcpu->arch.hdbss.hdbssbr_el2 = 0;
> > > > > > > >     }
> > > > > > > > +
> > > > > > > > +void kvm_flush_hdbss_buffer(struct kvm_vcpu *vcpu)
> > > > > > > > +{
> > > > > > > > +	int idx, curr_idx;
> > > > > > > > +	u64 *hdbss_buf;
> > > > > > > > +	struct kvm *kvm = vcpu->kvm;
> > > > > > > > +
> > > > > > > > +	if (!kvm->arch.enable_hdbss)
> > > > > > > > +		return;
> > > > > > > > +
> > > > > > > > +	curr_idx = HDBSSPROD_IDX(read_sysreg_s(SYS_HDBSSPROD_EL2));
> > > > > > > > +
> > > > > > > > +	/* Do nothing if HDBSS buffer is empty or br_el2 is NULL */
> > > > > > > > +	if (curr_idx == 0 || vcpu->arch.hdbss.hdbssbr_el2 == 0)
> > > > > > > > +		return;
> > > > > > > > +
> > > > > > > > +	hdbss_buf = page_address(phys_to_page(vcpu->arch.hdbss.base_phys));
> > > > > > > > +	if (!hdbss_buf)
> > > > > > > > +		return;
> > > > > > > > +
> > > > > > > > +	guard(write_lock_irqsave)(&vcpu->kvm->mmu_lock);
> > > > > > > > +	for (idx = 0; idx < curr_idx; idx++) {
> > > > > > > > +		u64 gpa;
> > > > > > > > +
> > > > > > > > +		gpa = hdbss_buf[idx];
> > > > > > > > +		if (!(gpa & HDBSS_ENTRY_VALID))
> > > > > > > > +			continue;
> > > > > > > > +
> > > > > > > > +		gpa &= HDBSS_ENTRY_IPA;
> > > > > > > > +		kvm_vcpu_mark_page_dirty(vcpu, gpa >> PAGE_SHIFT);
> > > > > > > You mention that it does not support dirty-ring, but above function will
> > > > > > > mark the page as dirty in the dirty-ring :/
> > > > > > > 
> > > > > > In kvm_arm_enable_hdbss_global(), we explicitly check and reject HDBSS
> > > > > > enablement if dirty-ring is active:
> > > > > > 
> > > > > > ```
> > > > > > if (kvm->dirty_ring_size)
> > > > > >       return 0;
> > > > > > ```
> > > > > > 
> > > > > > So when kvm_flush_hdbss_buffer() runs (which requires enable_hdbss = true),
> > > > > > we know for certain that
> > > > > > 
> > > > > > kvm->dirty_ring_size == 0. Therefore, kvm_vcpu_mark_page_dirty() will always
> > > > > > take the dirty_bitmap path,
> > > > > > 
> > > > > > never the dirty-ring path.
> > > > > > 
> > > > > > That said, I'll add a comment in kvm_flush_hdbss_buffer() before dirty ring
> > > > > > mode is supported, to make this explicit:
> > > > > > 
> > > > > > ```
> > > > > > /*
> > > > > >    * HDBSS is mutually exclusive with dirty-ring mode (see
> > > > > >    * kvm_arm_enable_hdbss_global()), so kvm_vcpu_mark_page_dirty()
> > > > > >    * will update the dirty_bitmap, not the dirty-ring.
> > > > > >    */
> > > > > > ```
> > > > > > 
> > > > > Got it :)
> > > > > 
> > > > > Out of curiosity: which issues have you found on supporting dirty-ring at
> > > > > this point?
> > > > > 
> > > > > Thanks!
> > > > > Leo
> > > > 
> > > > I haven't looked deeply into dirty-ring yet — my main concern is that if
> > > > both the dirty
> > > > 
> > > > ring and HDBSS buffer fill up, the flush path might get blocked or
> > > > complicated.
> > > > 
> > > > For now, I'm planning to match the HDBSS buffer size to the dirty ring size
> > > > in v5 and test it.
> > > > 
> > > > Ideally, the two buffers would be the same size, and the entire dirty
> > > > tracking path would use
> > > > 
> > > Ah, I see the point.
> > > 
> > > IIRC, when dirty-ring gets full, the kernel returns to userspace with
> > > run->exit_reason == KVM_EXIT_DIRTY_RING_FULL, which will warn the VMM to
> > > drain the dirty-ring, and that makes space for us draining HDBSS to the
> > > dirty-ring again.
> > > 
> > > The best way to achieve that, as I remember, is to always drain
> > > HDBSS as much as possible at guest_exitting. That will make more space to
> > > newer HDBSS entries, and we can get userspace to drain the dirty-ring
> > > earlier.
> > > 
> > > I would say to even make HDBSS buffer half (entries) the dirty-ring. Then
> > > we can generally fully drain to the dirty-ring and even report ring full
> > > if the ring is above a given threshold percentage full.
> > > > HDBSS exclusively — no fallback to the legacy dirty bitmap path. If that
> > > > works, I think this approach should be fine.
> > > > 
> > > > Let me know if you have any insights on dirty ring's full-buffer behavior —
> > > > that would be helpful.
> > > > 
> > > > 
> > > Will do!
> > > 
> > > Thanks!
> > > Leo
> > 
> > 
> > Thanks for the insights — reusing PML's reservation mechanism makes sense.
> > 
> > I think we can*keep the HDBSS buffer at 512 entries* (matching
> 
> I recommend using a PAGESIZE (512 in 4k, but bigger in other sizes)
> 
> > PML_LOG_NR_ENTRIES)
> > 
> > for now, and *not expose any ioctl for userspace to configure it*. Since the
> > kernel
> > 
> > auto-enables HDBSS, a userspace size knob would be confusing.
> > 
> 
> Humm, I am in favor of letting the user change it according to it's 
> workload. Why would that be confusing?
> 
> (Having a default size is useful just for enabling it to work without any 
> change in current VMs)
> 
> > 
> > The reservation logic would be:
> > 
> > - Implement kvm_cpu_dirty_log_size() on arm64 to return the HDBSS buffer
> > entry
> > 
> > count (512, or 0 if HDBSS is not enabled)
> 
> Why a get to log_size? does userspace need to know it's using HDBSS?
> 
> Just a set should do, as VMMs can just try to set a value, and if it fails 
> (IOCTL does not exist, or invalid value), then it can just go forward. 
> 
> > 
> > - Reuse kvm_dirty_ring_get_rsvd_entries() to reserve space for one full
> > flush:
> > 
> > KVM_DIRTY_RING_RSVD_ENTRIES + hdbss_entries
> > 
> > - So soft_limit = dirty_ring_size - (KVM_DIRTY_RING_RSVD_ENTRIES +
> > hdbss_entries),
> > 
> > guaranteeing a full flush always fits
> > 
> > kvm_flush_hdbss_buffer() at guest_exit pushes via mark_page_dirty_in_slot()
> > ->
> > 
> 
> I think I get the point here: since we can have sw dirtying as well as 
> HDBSS tracking, we may get to the point that we don't have enough space to 
> flush hdbss -> dirty-ring, right?
> 
> > kvm_dirty_ring_push() -> kvm_dirty_ring_soft_full, which triggers
> > 
> > KVM_EXIT_DIRTY_RING_FULL when needed.
> > 
> > If soft_limit is hit, KVM_REQ_DIRTY_RING_FULL is set and the next vcpu_run
> > exits
> > 
> > to userspace for QEMU to drain the ring.
> 
> So the software dirtying routine would be affected by the soft limit, but 
> HADBSS exit would not. It means the dirty-ring would be effectively smaller 
> than specified if we are having mostly sw dirtying.
> 
> Did I get that right?
> 
> > 
> > *One more thing: *when userspace sets the dirty ring size via
> > 
> > KVM_VM_IOCTL_ENABLE_DIRTY_LOG_RING, we already enforce that
> > 
> > size >= kvm_dirty_ring_get_rsvd_entries(kvm) * sizeof(struct kvm_dirty_gfn)
> > 
> > or size < PAGE_SIZE. With HDBSS, kvm_dirty_ring_get_rsvd_entries() will
> > include the
> > 
> > HDBSS entries via kvm_cpu_dirty_log_size(), so the same check will
> > automatically guarantee
> > 
> > the ring is large enough to accommodate the HDBSS buffer. No additional
> > validation is needed.
> > 
> 

I have a small question on this, since HDBSS supports 2MiB buffer,
it  will has more entries than the dirty ring. If we use 
kvm_cpu_dirty_log_size() to reserve entries. It will always enter
soft limit if the HDBSS buffer is huge. 

Since I think users set large buffer expect less context switch.
I think this may require some change on the dirty ring framework.
At least I think the soft limit check should be adjusted.

> An alternative is moving the remaining array entries to the beginning of 
> the array, and setting the registers accordingly. The tradeoff is an 
> overhead here vs having the full size of the array available later.
> 
> Thanks!
> Leo
> 

Regards,
Inochi


^ permalink raw reply

* Re: [PATCH v17 15/15] arm64: vdso: Expose sigreturn address on vdso to the kernel
From: Linus Walleij @ 2026-07-21  8:55 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-16-ruanjinjie@huawei.com>

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

> 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>

I'm a bit scared about SUD.

Certainly we can merge patches 1-14 first and iron out
any trouble related to that and split this off so a separate
series to be applied lated?

Yours,
Linus Walleij


^ permalink raw reply

* [PATCH v2] KVM: arm64: Optimize protected mode and FWB
From: Mostafa Saleh @ 2026-07-21  8:56 UTC (permalink / raw)
  To: linux-kernel, kvmarm, linux-arm-kernel
  Cc: maz, oupton, seiden, joey.gouly, suzuki.poulose, yuzenghui,
	catalin.marinas, will, vdonnefort, tabba, sebastianene, keirf,
	Mostafa Saleh, Fuad Tabba

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>
Reviewed-by: Fuad Tabba <fuad.tabba@linux.dev>
---
 arch/arm64/kvm/hyp/nvhe/mem_protect.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index 4e329e39a695..049a09be2bbf 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -261,6 +261,13 @@ static void __apply_guest_page(void *va, size_t size,
 
 static void clean_dcache_guest_page(void *va, size_t size)
 {
+	/*
+	 * Guest stage-2 uses FWB if available, making it safe to elide CMOs.
+	 * In contrast, the host stage-2 never has FWB enabled.
+	 */
+	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 related

* Re: [PATCH 1/4] dt-bindings: sound: fsl,imx-asrc: update port binding to support multiple pairs
From: Shengjiu Wang @ 2026-07-21  8:58 UTC (permalink / raw)
  To: Mark Brown
  Cc: shengjiu.wang, lgirdwood, robh, krzk+dt, conor+dt, Frank.Li,
	s.hauer, kernel, festevam, shawnguo, linux-sound, devicetree, imx,
	linux-arm-kernel, linux-kernel, Xiubo.Lee, nicoleotsuka, perex,
	tiwai, linuxppc-dev, Shengjiu Wang
In-Reply-To: <3ce4bfa8-b051-40af-9478-00540019680b@sirena.org.uk>

On Mon, Jul 20, 2026 at 10:28 PM Mark Brown <broonie@kernel.org> wrote:
>
> On Mon, Jul 20, 2026 at 05:54:47PM +0800, shengjiu.wang@oss.nxp.com wrote:
> > From: Shengjiu Wang <shengjiu.wang@nxp.com>
> >
> > The i.MX ASRC hardware supports up to three conversion pairs (A, B, C).
> > The existing binding exposed only a single generic audio-graph port,
> > which cannot represent the individual pair endpoints.
>
> Please submit patches using subject lines reflecting the style for the
> subsystem, this makes it easier for people to identify relevant patches.
> Look at what existing commits in the area you're changing are doing and
> make sure your subject lines visually resemble what they're doing.
> There's no need to resubmit to fix this alone.

Thanks for pointing this out. There are also some comments from sashiko.dev
I will try to address in the next version.

Best regards
Shengjiu Wang


^ permalink raw reply

* [PATCH v7 4/5] drm/debugfs: Move HDMI debugfs registration to state helper
From: Nicolas Frattaroli @ 2026-07-21  8:59 UTC (permalink / raw)
  To: Jani Nikula, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
	David Airlie, Simona Vetter, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Luca Ceresoli, Daniel Stone, Hans Verkuil, Chen-Yu Tsai,
	Samuel Holland, Dave Stevenson, Maíra Canal,
	Raspberry Pi Kernel Maintenance
  Cc: dri-devel, linux-kernel, kernel, linux-arm-kernel, linux-sunxi,
	Nicolas Frattaroli
In-Reply-To: <20260721-scdc-link-health-v7-0-92df1b6ad5fc@collabora.com>

The HDMI debugfs files are only useful for connectors that already make
use of the HDMI state helpers. In addition, relying on HDMI-specific
functions in drm_debugfs.c can lead to circular linking problems later
down the line.

Move hdmi_debugfs_add() and friends to a new helper in
drm_hdmi_state_helper.c, and make all current users (vc4, sun4i, bridge)
either use it as a debugfs_init func, or call it directly in its
debugfs_init func.

Suggested-by: Maxime Ripard <mripard@kernel.org>
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c  |   3 +
 drivers/gpu/drm/display/drm_hdmi_state_helper.c | 154 +++++++++++++++++++++++
 drivers/gpu/drm/drm_debugfs.c                   | 157 ------------------------
 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c          |   1 +
 drivers/gpu/drm/vc4/vc4_hdmi.c                  |   1 +
 include/drm/display/drm_hdmi_state_helper.h     |   3 +
 6 files changed, 162 insertions(+), 157 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 8b54069fa53a..00feb73e63d7 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -263,6 +263,9 @@ static void drm_bridge_connector_debugfs_init(struct drm_connector *connector,
 		if (bridge->funcs->debugfs_init)
 			bridge->funcs->debugfs_init(bridge, root);
 	}
+
+	if (bridge_connector->bridge_hdmi)
+		drm_hdmi_connector_debugfs_init(connector, root);
 }
 
 static struct drm_connector_state *
diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
index ce17eeefc2da..8d5ee10ceb87 100644
--- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c
+++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: MIT
 
+#include <linux/debugfs.h>
 #include <linux/export.h>
 
 #include <drm/drm_atomic.h>
@@ -965,6 +966,159 @@ drm_hdmi_connector_mode_valid(struct drm_connector *connector,
 }
 EXPORT_SYMBOL(drm_hdmi_connector_mode_valid);
 
+static ssize_t
+audio_infoframe_read(struct file *filp, char __user *ubuf, size_t count, loff_t *ppos)
+{
+	struct drm_connector_hdmi_infoframe *infoframe;
+	struct drm_connector *connector;
+	union hdmi_infoframe *frame;
+	u8 buf[HDMI_INFOFRAME_SIZE(AUDIO)];
+	ssize_t len = 0;
+
+	connector = filp->private_data;
+	mutex_lock(&connector->hdmi.infoframes.lock);
+
+	infoframe = &connector->hdmi.infoframes.audio;
+	if (!infoframe->set)
+		goto out;
+
+	frame = &infoframe->data;
+	len = hdmi_infoframe_pack(frame, buf, sizeof(buf));
+	if (len < 0)
+		goto out;
+
+	len = simple_read_from_buffer(ubuf, count, ppos, buf, len);
+
+out:
+	mutex_unlock(&connector->hdmi.infoframes.lock);
+	return len;
+}
+
+static const struct file_operations audio_infoframe_fops = {
+	.owner   = THIS_MODULE,
+	.open    = simple_open,
+	.read    = audio_infoframe_read,
+};
+
+static int create_hdmi_audio_infoframe_file(struct drm_connector *connector,
+					    struct dentry *parent)
+{
+	struct dentry *file;
+
+	if (!connector->hdmi.funcs ||
+	    !connector->hdmi.funcs->audio.write_infoframe)
+		return 0;
+
+	file = debugfs_create_file("audio", 0400, parent, connector, &audio_infoframe_fops);
+	if (IS_ERR(file))
+		return PTR_ERR(file);
+
+	return 0;
+}
+
+#define DEFINE_INFOFRAME_FILE(_f) \
+static ssize_t _f##_read_infoframe(struct file *filp, \
+				   char __user *ubuf, \
+				   size_t count,      \
+				   loff_t *ppos)      \
+{ \
+	struct drm_connector_hdmi_infoframe *infoframe; \
+	struct drm_connector_state *conn_state; \
+	struct drm_connector *connector; \
+	union hdmi_infoframe *frame; \
+	struct drm_device *dev; \
+	u8 buf[HDMI_INFOFRAME_SIZE(MAX)]; \
+	ssize_t len = 0; \
+	\
+	connector = filp->private_data; \
+	dev = connector->dev; \
+	\
+	drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); \
+	\
+	conn_state = connector->state; \
+	infoframe = &conn_state->hdmi.infoframes._f; \
+	if (!infoframe->set) \
+		goto out; \
+	\
+	frame = &infoframe->data; \
+	len = hdmi_infoframe_pack(frame, buf, sizeof(buf)); \
+	if (len < 0) \
+		goto out; \
+	\
+	len = simple_read_from_buffer(ubuf, count, ppos, buf, len); \
+	\
+out: \
+	drm_modeset_unlock(&dev->mode_config.connection_mutex); \
+	return len; \
+} \
+\
+static const struct file_operations _f##_infoframe_fops = { \
+	.owner = THIS_MODULE, \
+	.open = simple_open, \
+	.read = _f##_read_infoframe, \
+}; \
+\
+static int create_hdmi_## _f ## _infoframe_file(struct drm_connector *connector, \
+						struct dentry *parent) \
+{ \
+	struct dentry *file; \
+	\
+	if (!connector->hdmi.funcs || \
+	    !connector->hdmi.funcs->_f.write_infoframe) \
+		return 0; \
+	file = debugfs_create_file(#_f, 0400, parent, connector, &_f ## _infoframe_fops); \
+	if (IS_ERR(file)) \
+		return PTR_ERR(file); \
+	\
+	return 0; \
+}
+
+DEFINE_INFOFRAME_FILE(avi);
+DEFINE_INFOFRAME_FILE(hdmi);
+DEFINE_INFOFRAME_FILE(hdr_drm);
+DEFINE_INFOFRAME_FILE(spd);
+
+static int create_hdmi_infoframe_files(struct drm_connector *connector,
+				       struct dentry *parent)
+{
+	int ret;
+
+	ret = create_hdmi_audio_infoframe_file(connector, parent);
+	if (ret)
+		return ret;
+
+	ret = create_hdmi_avi_infoframe_file(connector, parent);
+	if (ret)
+		return ret;
+
+	ret = create_hdmi_hdmi_infoframe_file(connector, parent);
+	if (ret)
+		return ret;
+
+	ret = create_hdmi_hdr_drm_infoframe_file(connector, parent);
+	if (ret)
+		return ret;
+
+	ret = create_hdmi_spd_infoframe_file(connector, parent);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+void drm_hdmi_connector_debugfs_init(struct drm_connector *connector,
+				     struct dentry *root)
+{
+	struct dentry *dir;
+
+	dir = debugfs_create_dir("infoframes", root);
+	if (IS_ERR(dir))
+		return;
+
+	create_hdmi_infoframe_files(connector, dir);
+}
+EXPORT_SYMBOL(drm_hdmi_connector_debugfs_init);
+
 static int clear_infoframe(struct drm_connector *connector,
 			   const struct drm_connector_infoframe_funcs *funcs,
 			   const char *type)
diff --git a/drivers/gpu/drm/drm_debugfs.c b/drivers/gpu/drm/drm_debugfs.c
index ae1c6126c2c5..a72f9723d3a9 100644
--- a/drivers/gpu/drm/drm_debugfs.c
+++ b/drivers/gpu/drm/drm_debugfs.c
@@ -633,161 +633,6 @@ static const struct file_operations drm_connector_fops = {
 	.write = connector_write
 };
 
-static ssize_t
-audio_infoframe_read(struct file *filp, char __user *ubuf, size_t count, loff_t *ppos)
-{
-	struct drm_connector_hdmi_infoframe *infoframe;
-	struct drm_connector *connector;
-	union hdmi_infoframe *frame;
-	u8 buf[HDMI_INFOFRAME_SIZE(AUDIO)];
-	ssize_t len = 0;
-
-	connector = filp->private_data;
-	mutex_lock(&connector->hdmi.infoframes.lock);
-
-	infoframe = &connector->hdmi.infoframes.audio;
-	if (!infoframe->set)
-		goto out;
-
-	frame = &infoframe->data;
-	len = hdmi_infoframe_pack(frame, buf, sizeof(buf));
-	if (len < 0)
-		goto out;
-
-	len = simple_read_from_buffer(ubuf, count, ppos, buf, len);
-
-out:
-	mutex_unlock(&connector->hdmi.infoframes.lock);
-	return len;
-}
-
-static const struct file_operations audio_infoframe_fops = {
-	.owner   = THIS_MODULE,
-	.open    = simple_open,
-	.read    = audio_infoframe_read,
-};
-
-static int create_hdmi_audio_infoframe_file(struct drm_connector *connector,
-					    struct dentry *parent)
-{
-	struct dentry *file;
-
-	if (!connector->hdmi.funcs ||
-	    !connector->hdmi.funcs->audio.write_infoframe)
-		return 0;
-
-	file = debugfs_create_file("audio", 0400, parent, connector, &audio_infoframe_fops);
-	if (IS_ERR(file))
-		return PTR_ERR(file);
-
-	return 0;
-}
-
-#define DEFINE_INFOFRAME_FILE(_f) \
-static ssize_t _f##_read_infoframe(struct file *filp, \
-				   char __user *ubuf, \
-				   size_t count,      \
-				   loff_t *ppos)      \
-{ \
-	struct drm_connector_hdmi_infoframe *infoframe; \
-	struct drm_connector_state *conn_state; \
-	struct drm_connector *connector; \
-	union hdmi_infoframe *frame; \
-	struct drm_device *dev; \
-	u8 buf[HDMI_INFOFRAME_SIZE(MAX)]; \
-	ssize_t len = 0; \
-	\
-	connector = filp->private_data; \
-	dev = connector->dev; \
-	\
-	drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); \
-	\
-	conn_state = connector->state; \
-	infoframe = &conn_state->hdmi.infoframes._f; \
-	if (!infoframe->set) \
-		goto out; \
-	\
-	frame = &infoframe->data; \
-	len = hdmi_infoframe_pack(frame, buf, sizeof(buf)); \
-	if (len < 0) \
-		goto out; \
-	\
-	len = simple_read_from_buffer(ubuf, count, ppos, buf, len); \
-	\
-out: \
-	drm_modeset_unlock(&dev->mode_config.connection_mutex); \
-	return len; \
-} \
-\
-static const struct file_operations _f##_infoframe_fops = { \
-	.owner = THIS_MODULE, \
-	.open = simple_open, \
-	.read = _f##_read_infoframe, \
-}; \
-\
-static int create_hdmi_## _f ## _infoframe_file(struct drm_connector *connector, \
-						struct dentry *parent) \
-{ \
-	struct dentry *file; \
-	\
-	if (!connector->hdmi.funcs || \
-	    !connector->hdmi.funcs->_f.write_infoframe) \
-		return 0; \
-	file = debugfs_create_file(#_f, 0400, parent, connector, &_f ## _infoframe_fops); \
-	if (IS_ERR(file)) \
-		return PTR_ERR(file); \
-	\
-	return 0; \
-}
-
-DEFINE_INFOFRAME_FILE(avi);
-DEFINE_INFOFRAME_FILE(hdmi);
-DEFINE_INFOFRAME_FILE(hdr_drm);
-DEFINE_INFOFRAME_FILE(spd);
-
-static int create_hdmi_infoframe_files(struct drm_connector *connector,
-				       struct dentry *parent)
-{
-	int ret;
-
-	ret = create_hdmi_audio_infoframe_file(connector, parent);
-	if (ret)
-		return ret;
-
-	ret = create_hdmi_avi_infoframe_file(connector, parent);
-	if (ret)
-		return ret;
-
-	ret = create_hdmi_hdmi_infoframe_file(connector, parent);
-	if (ret)
-		return ret;
-
-	ret = create_hdmi_hdr_drm_infoframe_file(connector, parent);
-	if (ret)
-		return ret;
-
-	ret = create_hdmi_spd_infoframe_file(connector, parent);
-	if (ret)
-		return ret;
-
-	return 0;
-}
-
-static void hdmi_debugfs_add(struct drm_connector *connector)
-{
-	struct dentry *dir;
-
-	if (!(connector->connector_type == DRM_MODE_CONNECTOR_HDMIA ||
-	      connector->connector_type == DRM_MODE_CONNECTOR_HDMIB))
-		return;
-
-	dir = debugfs_create_dir("infoframes", connector->debugfs_entry);
-	if (IS_ERR(dir))
-		return;
-
-	create_hdmi_infoframe_files(connector, dir);
-}
-
 void drm_debugfs_connector_add(struct drm_connector *connector)
 {
 	struct drm_device *dev = connector->dev;
@@ -815,8 +660,6 @@ void drm_debugfs_connector_add(struct drm_connector *connector)
 	debugfs_create_file("output_bpc", 0444, root, connector,
 			    &output_bpc_fops);
 
-	hdmi_debugfs_add(connector);
-
 	if (connector->funcs->debugfs_init)
 		connector->funcs->debugfs_init(connector, root);
 }
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
index e99f52ebb26f..e6c4f121ad0c 100644
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
@@ -292,6 +292,7 @@ static void sun4i_hdmi_connector_reset(struct drm_connector *connector)
 }
 
 static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
+	.debugfs_init		= drm_hdmi_connector_debugfs_init,
 	.detect			= sun4i_hdmi_connector_detect,
 	.fill_modes		= drm_helper_probe_single_connector_modes,
 	.reset			= sun4i_hdmi_connector_reset,
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c
index 17c8635c5afa..ea1f2013b20e 100644
--- a/drivers/gpu/drm/vc4/vc4_hdmi.c
+++ b/drivers/gpu/drm/vc4/vc4_hdmi.c
@@ -513,6 +513,7 @@ static void vc4_hdmi_connector_reset(struct drm_connector *connector)
 }
 
 static const struct drm_connector_funcs vc4_hdmi_connector_funcs = {
+	.debugfs_init = drm_hdmi_connector_debugfs_init,
 	.force = drm_atomic_helper_connector_hdmi_force,
 	.fill_modes = drm_helper_probe_single_connector_modes,
 	.reset = vc4_hdmi_connector_reset,
diff --git a/include/drm/display/drm_hdmi_state_helper.h b/include/drm/display/drm_hdmi_state_helper.h
index 13375bd0f4ae..596c57d9d250 100644
--- a/include/drm/display/drm_hdmi_state_helper.h
+++ b/include/drm/display/drm_hdmi_state_helper.h
@@ -3,6 +3,7 @@
 #ifndef DRM_HDMI_STATE_HELPER_H_
 #define DRM_HDMI_STATE_HELPER_H_
 
+struct dentry;
 struct drm_atomic_commit;
 struct drm_connector;
 struct drm_connector_state;
@@ -29,5 +30,7 @@ void drm_atomic_helper_connector_hdmi_force(struct drm_connector *connector);
 enum drm_mode_status
 drm_hdmi_connector_mode_valid(struct drm_connector *connector,
 			      const struct drm_display_mode *mode);
+void drm_hdmi_connector_debugfs_init(struct drm_connector *connector,
+				     struct dentry *root);
 
 #endif // DRM_HDMI_STATE_HELPER_H_

-- 
2.55.0



^ permalink raw reply related

* [PATCH v7 1/5] drm/scdc-helper: Don't use ssize_t return type for scdc_read/write
From: Nicolas Frattaroli @ 2026-07-21  8:58 UTC (permalink / raw)
  To: Jani Nikula, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
	David Airlie, Simona Vetter, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Luca Ceresoli, Daniel Stone, Hans Verkuil, Chen-Yu Tsai,
	Samuel Holland, Dave Stevenson, Maíra Canal,
	Raspberry Pi Kernel Maintenance
  Cc: dri-devel, linux-kernel, kernel, linux-arm-kernel, linux-sunxi,
	Nicolas Frattaroli
In-Reply-To: <20260721-scdc-link-health-v7-0-92df1b6ad5fc@collabora.com>

drm_scdc_read and drm_scdc_write, both of which are only used within
drm_scdc_helper (although exported), use a ssize_t as their return type.

This would make sense if they returned the number of bytes read/written
on success, and negative errno otherwise. However, they return 0 on
success.

Demote them to "int" as their return type, in order to avoid needlessly
using 64 bits when less suffices.

No functional change.

Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Reviewed-by: Hans Verkuil <hverkuil+cisco@kernel.org>
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/display/drm_scdc_helper.c | 8 ++++----
 include/drm/display/drm_scdc_helper.h     | 8 ++++----
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_scdc_helper.c b/drivers/gpu/drm/display/drm_scdc_helper.c
index df878aad4a36..8403f2390ab6 100644
--- a/drivers/gpu/drm/display/drm_scdc_helper.c
+++ b/drivers/gpu/drm/display/drm_scdc_helper.c
@@ -67,8 +67,8 @@
  * Returns:
  * 0 on success, negative error code on failure.
  */
-ssize_t drm_scdc_read(struct i2c_adapter *adapter, u8 offset, void *buffer,
-		      size_t size)
+int drm_scdc_read(struct i2c_adapter *adapter, u8 offset, void *buffer,
+		  size_t size)
 {
 	int ret;
 	struct i2c_msg msgs[2] = {
@@ -107,8 +107,8 @@ EXPORT_SYMBOL(drm_scdc_read);
  * Returns:
  * 0 on success, negative error code on failure.
  */
-ssize_t drm_scdc_write(struct i2c_adapter *adapter, u8 offset,
-		       const void *buffer, size_t size)
+int drm_scdc_write(struct i2c_adapter *adapter, u8 offset, const void *buffer,
+		   size_t size)
 {
 	struct i2c_msg msg = {
 		.addr = SCDC_I2C_SLAVE_ADDRESS,
diff --git a/include/drm/display/drm_scdc_helper.h b/include/drm/display/drm_scdc_helper.h
index 34600476a1b9..e9ccaeba56dd 100644
--- a/include/drm/display/drm_scdc_helper.h
+++ b/include/drm/display/drm_scdc_helper.h
@@ -31,10 +31,10 @@
 struct drm_connector;
 struct i2c_adapter;
 
-ssize_t drm_scdc_read(struct i2c_adapter *adapter, u8 offset, void *buffer,
-		      size_t size);
-ssize_t drm_scdc_write(struct i2c_adapter *adapter, u8 offset,
-		       const void *buffer, size_t size);
+int drm_scdc_read(struct i2c_adapter *adapter, u8 offset, void *buffer,
+		  size_t size);
+int drm_scdc_write(struct i2c_adapter *adapter, u8 offset, const void *buffer,
+		   size_t size);
 
 /**
  * drm_scdc_readb - read a single byte from SCDC

-- 
2.55.0



^ permalink raw reply related

* [PATCH v7 0/5] Add SCDC information to connector debugfs
From: Nicolas Frattaroli @ 2026-07-21  8:58 UTC (permalink / raw)
  To: Jani Nikula, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
	David Airlie, Simona Vetter, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Luca Ceresoli, Daniel Stone, Hans Verkuil, Chen-Yu Tsai,
	Samuel Holland, Dave Stevenson, Maíra Canal,
	Raspberry Pi Kernel Maintenance
  Cc: dri-devel, linux-kernel, kernel, linux-arm-kernel, linux-sunxi,
	Nicolas Frattaroli

HDMI uses the DDC I2C bus for communicating various bits of link status
out of band with the actual HDMI video signal. This information can be
useful for debugging issues like questionable cables sabotaged by feline
teeth, Enthusiast Grade cables made of cow fencing wire, and other such
problems that ruin one's media viewing plans.

Consequently, this series exposes various bits of pertinent information
from the SCDC protocol in an HDMI connector's debugfs. To continually
poll the link status, userspace can poll the debugfs file.

---
Changes in v7:
- Move HDMI debugfs registration to new state helper, fix up all users
  (sun4i, vc4, bridge)
- Register scdc debugfs in new hdmi debugfs helper
- Link to v6: https://patch.msgid.link/20260611-scdc-link-health-v6-0-6307875a6b5e@collabora.com

Changes in v6:
- Fix off-by-one error in drm_scdc_read_state
- Link to v5: https://patch.msgid.link/20260604-scdc-link-health-v5-0-11173b0ac3de@collabora.com

Changes in v5:
- Read all SCDC data regardless of update flags
- Dump SCDC data as hex before the human-readable output. It's separated
  with "\n----------------\n\n".
- No longer write 0 to read-only registers
- Add Reed-Solomon Corrections counter parsing
- Parsing has been kept. A desire was expressed to get this data without
  any external userspace tooling, and the kernel will need to parse it
  eventually anyway to set the link status.
- Functions have been made static as of right now, since external users
  may do another pass over the function signatures anyway.
- Link to v4: https://patch.msgid.link/20260527-scdc-link-health-v4-0-622ea40a1f59@collabora.com

Changes in v4:
- Don't use C struct bitfields for parsing status flags. Switch to
  bitwise AND for boolean flags, and FIELD_GET for multi-bit values.
- Drop the superfluous !! and parens
- Drop the __pure attributes on static functions
- Initialise stack local arrays with {}, not { 0 }.
- I've kept the print macros and %-30s format. Reason being that I don't
  want to repeat the format specifier and str_yes_no(foo) a bunch, and I
  like the %-30s format because it means all values are aligned with the
  value of the longest field, which is 30 chars long.
- Link to v3: https://patch.msgid.link/20260526-scdc-link-health-v3-0-59e4a4aaead1@collabora.com

Changes in v3:
- Add patch to change return type of drm_scdc_read/write.
- Rework error counter reading to duplicate less code.
- Also check lane 3 counter valid flag when reading its error counter.
- Use memset to clear buf for error counters, rather than doing it in
  the loop.
- Make read_error_counters not accept 0 as num_lanes; fix it up in the
  caller instead.
- Link to v2: https://patch.msgid.link/20260520-scdc-link-health-v2-0-511af18cd64b@collabora.com

Changes in v2:
- Add HDMI 2.1 SCDC status reporting
- Link to v1: https://patch.msgid.link/20260415-scdc-link-health-v1-0-8e731e88eaf0@collabora.com

To: Jani Nikula <jani.nikula@linux.intel.com>
To: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
To: Maxime Ripard <mripard@kernel.org>
To: Thomas Zimmermann <tzimmermann@suse.de>
To: David Airlie <airlied@gmail.com>
To: Simona Vetter <simona@ffwll.ch>
To: Andrzej Hajda <andrzej.hajda@intel.com>
To: Neil Armstrong <neil.armstrong@linaro.org>
To: Robert Foss <rfoss@kernel.org>
To: Laurent Pinchart <Laurent.pinchart@ideasonboard.com>
To: Jonas Karlman <jonas@kwiboo.se>
To: Jernej Skrabec <jernej.skrabec@gmail.com>
To: Luca Ceresoli <luca.ceresoli@bootlin.com>
To: Daniel Stone <daniel@fooishbar.org>
To: Hans Verkuil <hverkuil+cisco@kernel.org>
To: Chen-Yu Tsai <wens@kernel.org>
To: Samuel Holland <samuel@sholland.org>
To: Dave Stevenson <dave.stevenson@raspberrypi.com>
To: Maíra Canal <mcanal@igalia.com>
To: Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
Cc: dri-devel@lists.freedesktop.org
Cc: linux-kernel@vger.kernel.org
Cc: kernel@collabora.com
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-sunxi@lists.linux.dev
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>

---
Nicolas Frattaroli (5):
      drm/scdc-helper: Don't use ssize_t return type for scdc_read/write
      drm/scdc-helper: Add scdc_status debugfs entry
      drm/scdc-helper: Implement parsing and printing HDMI 2.1 fields
      drm/debugfs: Move HDMI debugfs registration to state helper
      drm/display: hdmi-state-helper: Init SCDC debugfs for HDMI

 drivers/gpu/drm/display/drm_bridge_connector.c  |   3 +
 drivers/gpu/drm/display/drm_hdmi_state_helper.c | 156 +++++++++++++
 drivers/gpu/drm/display/drm_scdc_helper.c       | 285 +++++++++++++++++++++++-
 drivers/gpu/drm/drm_debugfs.c                   | 157 -------------
 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c          |   1 +
 drivers/gpu/drm/vc4/vc4_hdmi.c                  |   1 +
 include/drm/display/drm_hdmi_state_helper.h     |   3 +
 include/drm/display/drm_scdc.h                  |  21 +-
 include/drm/display/drm_scdc_helper.h           | 103 ++++++++-
 9 files changed, 564 insertions(+), 166 deletions(-)
---
base-commit: 97aed8a757dcf4d64b13c6387637821a2888586e
change-id: 20260413-scdc-link-health-89326013d96c

Best regards,
--  
Nicolas Frattaroli <nicolas.frattaroli@collabora.com>



^ permalink raw reply

* [PATCH v7 2/5] drm/scdc-helper: Add scdc_status debugfs entry
From: Nicolas Frattaroli @ 2026-07-21  8:58 UTC (permalink / raw)
  To: Jani Nikula, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
	David Airlie, Simona Vetter, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Luca Ceresoli, Daniel Stone, Hans Verkuil, Chen-Yu Tsai,
	Samuel Holland, Dave Stevenson, Maíra Canal,
	Raspberry Pi Kernel Maintenance
  Cc: dri-devel, linux-kernel, kernel, linux-arm-kernel, linux-sunxi,
	Nicolas Frattaroli
In-Reply-To: <20260721-scdc-link-health-v7-0-92df1b6ad5fc@collabora.com>

SCDC provides status information on the current display link. At the
very least, it may be useful to expose this info through debugfs.

Add a debugfs entry for it under the connector, which displays a few
more details parsed out of the SCDC registers. A new
drm_scdc_debugfs_init function can be called by the connector
implementation to initialise the debugfs file.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/display/drm_scdc_helper.c | 184 ++++++++++++++++++++++++++++++
 include/drm/display/drm_scdc_helper.h     |  32 ++++++
 2 files changed, 216 insertions(+)

diff --git a/drivers/gpu/drm/display/drm_scdc_helper.c b/drivers/gpu/drm/display/drm_scdc_helper.c
index 8403f2390ab6..5871fc101815 100644
--- a/drivers/gpu/drm/display/drm_scdc_helper.c
+++ b/drivers/gpu/drm/display/drm_scdc_helper.c
@@ -24,11 +24,14 @@
 #include <linux/export.h>
 #include <linux/i2c.h>
 #include <linux/slab.h>
+#include <linux/debugfs.h>
 #include <linux/delay.h>
+#include <linux/overflow.h>
 
 #include <drm/display/drm_scdc_helper.h>
 #include <drm/drm_connector.h>
 #include <drm/drm_device.h>
+#include <drm/drm_managed.h>
 #include <drm/drm_print.h>
 
 /**
@@ -55,6 +58,11 @@
 
 #define SCDC_I2C_SLAVE_ADDRESS 0x54
 
+struct scdc_debugfs_priv {
+	struct drm_connector *connector;
+	struct drm_scdc_state state;
+};
+
 /**
  * drm_scdc_read - read a block of data from SCDC
  * @adapter: I2C controller
@@ -276,3 +284,179 @@ bool drm_scdc_set_high_tmds_clock_ratio(struct drm_connector *connector,
 	return true;
 }
 EXPORT_SYMBOL(drm_scdc_set_high_tmds_clock_ratio);
+
+static void
+drm_scdc_parse_status0_flags(u8 val, struct drm_scdc_status_flags *flags)
+{
+	flags->clock_detected = val & SCDC_CLOCK_DETECT;
+	flags->ch0_locked = val & SCDC_CH0_LOCK;
+	flags->ch1_locked = val & SCDC_CH1_LOCK;
+	flags->ch2_locked = val & SCDC_CH2_LOCK;
+}
+
+static int drm_scdc_parse_error_counters(const u8 scdc[256], u16 counter[3])
+{
+	u8 sum = 0;
+	int i;
+
+	for (i = SCDC_ERR_DET_0_L; i <= SCDC_ERR_DET_CHECKSUM ; i++)
+		sum = wrapping_add(u8, sum, scdc[i]);
+
+	if (sum)
+		return -EPROTO;
+
+	for (i = 0; i < 3; i++) {
+		if (scdc[SCDC_ERR_DET_0_H + i * 2] & SCDC_CHANNEL_VALID)
+			counter[i] =  (scdc[SCDC_ERR_DET_0_H + i * 2] &
+				       ~SCDC_CHANNEL_VALID) << 8 |
+				       scdc[SCDC_ERR_DET_0_L + i * 2];
+		else
+			counter[i] = 0;
+	}
+
+	return 0;
+}
+
+/**
+ * drm_scdc_read_state - Update state from SCDC
+ * @connector: pointer to a &struct drm_connector on which to operate on
+ * @state: pointer to a &struct drm_scdc_state to fill
+ *
+ * Reads the entire 256 byte SCDC state and parses it.
+ *
+ * Returns: %0 on success, negative errno on failure.
+ */
+int drm_scdc_read_state(struct drm_connector *connector, struct drm_scdc_state *state)
+{
+	struct i2c_adapter *ddc;
+	struct drm_scdc *scdc;
+	u8 *buf = state->scdc;
+	int ret;
+
+	if (!state || !connector)
+		return -ENODEV;
+
+	scdc = &connector->display_info.hdmi.scdc;
+	ddc = connector->ddc;
+
+	if (!scdc->supported)
+		return -EOPNOTSUPP;
+
+	/* Read in 128-byte chunks, to work around DP<->HDMI converters with issues. */
+	ret = drm_scdc_read(ddc, 0, buf, 128);
+	if (ret)
+		return ret;
+
+	ret = drm_scdc_read(ddc, 128, &buf[128], 128);
+	if (ret)
+		return ret;
+
+	state->scrambling_enabled = buf[SCDC_TMDS_CONFIG] & SCDC_SCRAMBLING_ENABLE;
+	state->tmds_bclk_x40 = buf[SCDC_TMDS_CONFIG] & SCDC_TMDS_BIT_CLOCK_RATIO_BY_40;
+
+	state->scrambling_detected = buf[SCDC_SCRAMBLER_STATUS] & SCDC_SCRAMBLING_STATUS;
+
+	drm_scdc_parse_status0_flags(buf[SCDC_STATUS_FLAGS_0], &state->stf);
+	ret = drm_scdc_parse_error_counters(buf, state->error_count);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_scdc_read_state);
+
+#define scdc_print_str(_f, key, s) \
+	(seq_printf((_f), "%-30s: %s\n", (key), (s)))
+#define scdc_print_flag(_f, key, val) \
+	(scdc_print_str((_f), (key), str_yes_no((val))))
+#define scdc_print_dec(_f, key, val) \
+	(seq_printf((_f), "%-30s: %d\n", (key), (val)))
+
+static int scdc_status_show(struct seq_file *m, void *data)
+{
+	struct scdc_debugfs_priv *priv = m->private;
+	struct drm_scdc_state *st = &priv->state;
+	struct drm_connector *connector = priv->connector;
+	struct drm_scdc *scdc = &connector->display_info.hdmi.scdc;
+	int i, ret;
+
+	drm_connector_get(connector);
+
+	if (connector->status != connector_status_connected) {
+		ret = -ENODEV;
+		goto err_conn_put;
+	}
+
+	if (scdc->supported) {
+		ret = drm_scdc_read_state(connector, st);
+		if (ret)
+			goto err_conn_put;
+
+		for (i = 0; i < ARRAY_SIZE(st->scdc); i += 16)
+			seq_printf(m, "%*ph\n", 16, &st->scdc[i]);
+
+		seq_puts(m, "\n----------------\n\n");
+	}
+
+	scdc_print_flag(m, "SCDC Supported", scdc->supported);
+	if (!scdc->supported) {
+		ret = 0;
+		goto err_conn_put;
+	}
+
+	scdc_print_flag(m, "Sink Read Request Capable", scdc->read_request);
+	scdc_print_flag(m, "Scrambling Supported", scdc->scrambling.supported);
+	scdc_print_flag(m, "Low Rate Scrambling Supported", scdc->scrambling.low_rates);
+
+	drm_connector_put(connector);
+
+	scdc_print_flag(m, "Scrambling Enabled", st->scrambling_enabled);
+	scdc_print_flag(m, "Scrambling Detected", st->scrambling_detected);
+
+	if (st->tmds_bclk_x40)
+		scdc_print_str(m, "TMDS Bit Clock Ratio", "1/40");
+	else
+		scdc_print_str(m, "TMDS Bit Clock Ratio", "1/10");
+
+	scdc_print_flag(m, "Clock Detected", st->stf.clock_detected);
+	scdc_print_flag(m, "Channel 0 Locked", st->stf.ch0_locked);
+	scdc_print_flag(m, "Channel 1 Locked", st->stf.ch1_locked);
+	scdc_print_flag(m, "Channel 2 Locked", st->stf.ch2_locked);
+
+	scdc_print_dec(m, "Channel 0 Errors", st->error_count[0]);
+	scdc_print_dec(m, "Channel 1 Errors", st->error_count[1]);
+	scdc_print_dec(m, "Channel 2 Errors", st->error_count[2]);
+
+	return 0;
+
+err_conn_put:
+	drm_connector_put(connector);
+
+	return ret;
+}
+DEFINE_SHOW_ATTRIBUTE(scdc_status);
+
+/**
+ * drm_scdc_debugfs_init - Initialize scdc files in connector debugfs
+ * @connector: pointer to &struct drm_connector to operate on
+ * @root: debugfs &struct dentry for the debugfs root of @connector
+ *
+ * Creates SCDC-related debugfs files for @connector. Must be called after
+ * @root is already created.
+ */
+void drm_scdc_debugfs_init(struct drm_connector *connector, struct dentry *root)
+{
+	struct scdc_debugfs_priv *priv;
+
+	if (!root || !connector)
+		return;
+
+	priv = drmm_kzalloc(connector->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return;
+
+	priv->connector = connector;
+
+	debugfs_create_file("scdc_status", 0444, root, priv, &scdc_status_fops);
+}
+EXPORT_SYMBOL(drm_scdc_debugfs_init);
diff --git a/include/drm/display/drm_scdc_helper.h b/include/drm/display/drm_scdc_helper.h
index e9ccaeba56dd..e0b79d79e1ff 100644
--- a/include/drm/display/drm_scdc_helper.h
+++ b/include/drm/display/drm_scdc_helper.h
@@ -30,6 +30,34 @@
 
 struct drm_connector;
 struct i2c_adapter;
+struct dentry;
+
+struct drm_scdc_status_flags {
+	/* Status Register 0 */
+	bool clock_detected;
+	bool ch0_locked;
+	bool ch1_locked;
+	bool ch2_locked;
+};
+
+struct drm_scdc_state {
+	/** @stf: contents of the status flag registers */
+	struct drm_scdc_status_flags stf;
+	/** @scramling_enabled: true if TMDS scrambling is on */
+	bool scrambling_enabled;
+	/** @scrambling_detected: true if the sink actually detected scrambling */
+	bool scrambling_detected;
+	/**
+	 * @tmds_bclk_x40: true if TMDS bit period is 1/40th of the TMDS
+	 * clock period, false if it's 1/10th of the clock period.
+	 */
+	bool tmds_bclk_x40;
+	/** @error_count: character error counts for each channel */
+	u16 error_count[3];
+
+	/** @scdc: raw SCDC data buffer */
+	u8 scdc[256];
+};
 
 int drm_scdc_read(struct i2c_adapter *adapter, u8 offset, void *buffer,
 		  size_t size);
@@ -77,4 +105,8 @@ bool drm_scdc_get_scrambling_status(struct drm_connector *connector);
 bool drm_scdc_set_scrambling(struct drm_connector *connector, bool enable);
 bool drm_scdc_set_high_tmds_clock_ratio(struct drm_connector *connector, bool set);
 
+int drm_scdc_read_state(struct drm_connector *connector,
+			struct drm_scdc_state *state);
+void drm_scdc_debugfs_init(struct drm_connector *connector, struct dentry *root);
+
 #endif

-- 
2.55.0



^ permalink raw reply related

* [PATCH v7 3/5] drm/scdc-helper: Implement parsing and printing HDMI 2.1 fields
From: Nicolas Frattaroli @ 2026-07-21  8:58 UTC (permalink / raw)
  To: Jani Nikula, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
	David Airlie, Simona Vetter, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Luca Ceresoli, Daniel Stone, Hans Verkuil, Chen-Yu Tsai,
	Samuel Holland, Dave Stevenson, Maíra Canal,
	Raspberry Pi Kernel Maintenance
  Cc: dri-devel, linux-kernel, kernel, linux-arm-kernel, linux-sunxi,
	Nicolas Frattaroli
In-Reply-To: <20260721-scdc-link-health-v7-0-92df1b6ad5fc@collabora.com>

HDMI 2.1 redefines previously reserved fields in SCDC for various new
uses. No version check needs to be performed, as an HDMI 2.0 sink's
reserved SCDC fields are well-defined to be 0, and any zero-ness of
these fields for an HDMI 2.0 sink is not a surprise for SCDC parsers for
HDMI 2.1.

Implement reading and outputting these fields over debugfs.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/display/drm_scdc_helper.c | 99 ++++++++++++++++++++++++++++++-
 include/drm/display/drm_scdc.h            | 21 ++++++-
 include/drm/display/drm_scdc_helper.h     | 69 ++++++++++++++++++++-
 3 files changed, 182 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_scdc_helper.c b/drivers/gpu/drm/display/drm_scdc_helper.c
index 5871fc101815..7e17272d52f2 100644
--- a/drivers/gpu/drm/display/drm_scdc_helper.c
+++ b/drivers/gpu/drm/display/drm_scdc_helper.c
@@ -21,6 +21,7 @@
  * DEALINGS IN THE SOFTWARE.
  */
 
+#include <linux/bitfield.h>
 #include <linux/export.h>
 #include <linux/i2c.h>
 #include <linux/slab.h>
@@ -63,6 +64,38 @@ struct scdc_debugfs_priv {
 	struct drm_scdc_state state;
 };
 
+static const char *drm_scdc_frl_rate_str(enum drm_scdc_frl_rate rate)
+{
+	switch (rate) {
+	case SCDC_FRL_RATE_OFF:
+		return "Off";
+	case SCDC_FRL_RATE_3X3:
+		return "3 Gbit/s x 3 lanes";
+	case SCDC_FRL_RATE_6X3:
+		return "6 Gbit/s x 3 lanes";
+	case SCDC_FRL_RATE_6X4:
+		return "6 Gbit/s x 4 lanes";
+	case SCDC_FRL_RATE_8X4:
+		return "8 Gbit/s x 4 lanes";
+	case SCDC_FRL_RATE_10X4:
+		return "10 Gbit/s x 4 lanes";
+	case SCDC_FRL_RATE_12X4:
+		return "12 Gbit/s x 4 lanes";
+	case SCDC_FRL_RATE_RESV_7:
+	case SCDC_FRL_RATE_RESV_8:
+	case SCDC_FRL_RATE_RESV_9:
+	case SCDC_FRL_RATE_RESV_10:
+	case SCDC_FRL_RATE_RESV_11:
+	case SCDC_FRL_RATE_RESV_12:
+	case SCDC_FRL_RATE_RESV_13:
+	case SCDC_FRL_RATE_RESV_14:
+	case SCDC_FRL_RATE_RESV_15:
+		return "(Reserved)";
+	default:
+		return NULL;
+	}
+}
+
 /**
  * drm_scdc_read - read a block of data from SCDC
  * @adapter: I2C controller
@@ -292,14 +325,41 @@ drm_scdc_parse_status0_flags(u8 val, struct drm_scdc_status_flags *flags)
 	flags->ch0_locked = val & SCDC_CH0_LOCK;
 	flags->ch1_locked = val & SCDC_CH1_LOCK;
 	flags->ch2_locked = val & SCDC_CH2_LOCK;
+	flags->ln3_locked = val & SCDC_LN3_LOCK;
+	flags->flt_ready = val & SCDC_FLT_READY;
+	flags->dsc_fail = val & SCDC_DSC_FAIL;
+}
+
+static void
+drm_scdc_parse_status1_2_flags(u8 val_flag1, u8 val_flag2,
+			       struct drm_scdc_status_flags *flags)
+{
+	flags->ln0_training_pattern = FIELD_GET(SCDC_LN_EVEN_TRAIN_PTRN, val_flag1);
+	flags->ln1_training_pattern = FIELD_GET(SCDC_LN_ODD_TRAIN_PTRN, val_flag1);
+
+	flags->ln2_training_pattern = FIELD_GET(SCDC_LN_EVEN_TRAIN_PTRN, val_flag2);
+	flags->ln3_training_pattern = FIELD_GET(SCDC_LN_ODD_TRAIN_PTRN, val_flag2);
 }
 
-static int drm_scdc_parse_error_counters(const u8 scdc[256], u16 counter[3])
+static int drm_scdc_parse_error_counters(const u8 scdc[256], u16 counter[4],
+					 unsigned int num_lanes)
 {
+	u8 end_reg;
 	u8 sum = 0;
 	int i;
 
-	for (i = SCDC_ERR_DET_0_L; i <= SCDC_ERR_DET_CHECKSUM ; i++)
+	switch (num_lanes) {
+	case 3:
+		end_reg = SCDC_ERR_DET_CHECKSUM;
+		break;
+	case 4:
+		end_reg = SCDC_ERR_DET_3_H;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	for (i = SCDC_ERR_DET_0_L; i <= end_reg; i++)
 		sum = wrapping_add(u8, sum, scdc[i]);
 
 	if (sum)
@@ -314,6 +374,12 @@ static int drm_scdc_parse_error_counters(const u8 scdc[256], u16 counter[3])
 			counter[i] = 0;
 	}
 
+	if (num_lanes == 4 && scdc[SCDC_ERR_DET_3_H] & SCDC_CHANNEL_VALID)
+		counter[3] =  (scdc[SCDC_ERR_DET_3_H] & ~SCDC_CHANNEL_VALID) << 8 |
+			      scdc[SCDC_ERR_DET_3_L];
+	else
+		counter[3] = 0;
+
 	return 0;
 }
 
@@ -331,6 +397,7 @@ int drm_scdc_read_state(struct drm_connector *connector, struct drm_scdc_state *
 	struct i2c_adapter *ddc;
 	struct drm_scdc *scdc;
 	u8 *buf = state->scdc;
+	int num_lanes;
 	int ret;
 
 	if (!state || !connector)
@@ -356,11 +423,26 @@ int drm_scdc_read_state(struct drm_connector *connector, struct drm_scdc_state *
 
 	state->scrambling_detected = buf[SCDC_SCRAMBLER_STATUS] & SCDC_SCRAMBLING_STATUS;
 
+	state->rate = FIELD_GET(SCDC_FRL_RATE, buf[SCDC_CONFIG_1]);
+	num_lanes = drm_scdc_num_frl_lanes(state->rate);
+	if (num_lanes < 0)
+		return num_lanes;
+	if (!num_lanes)
+		num_lanes = 3;
+
+	state->ffe_levels = FIELD_GET(SCDC_FFE_LEVELS, buf[SCDC_CONFIG_1]);
+
 	drm_scdc_parse_status0_flags(buf[SCDC_STATUS_FLAGS_0], &state->stf);
-	ret = drm_scdc_parse_error_counters(buf, state->error_count);
+	drm_scdc_parse_status1_2_flags(buf[SCDC_STATUS_FLAGS_1],
+				       buf[SCDC_STATUS_FLAGS_2], &state->stf);
+	ret = drm_scdc_parse_error_counters(buf, state->error_count, num_lanes);
 	if (ret)
 		return ret;
 
+	if (num_lanes == 4 && (buf[SCDC_ERR_DET_RS_H] & SCDC_CHANNEL_VALID))
+		state->rs_corrections = (buf[SCDC_ERR_DET_RS_H] & ~SCDC_CHANNEL_VALID) << 8 |
+					buf[SCDC_ERR_DET_RS_L];
+
 	return 0;
 }
 EXPORT_SYMBOL(drm_scdc_read_state);
@@ -412,6 +494,8 @@ static int scdc_status_show(struct seq_file *m, void *data)
 
 	scdc_print_flag(m, "Scrambling Enabled", st->scrambling_enabled);
 	scdc_print_flag(m, "Scrambling Detected", st->scrambling_detected);
+	scdc_print_str(m, "FRL Rate", drm_scdc_frl_rate_str(st->rate));
+	scdc_print_dec(m, "FFE Levels", st->ffe_levels);
 
 	if (st->tmds_bclk_x40)
 		scdc_print_str(m, "TMDS Bit Clock Ratio", "1/40");
@@ -422,10 +506,19 @@ static int scdc_status_show(struct seq_file *m, void *data)
 	scdc_print_flag(m, "Channel 0 Locked", st->stf.ch0_locked);
 	scdc_print_flag(m, "Channel 1 Locked", st->stf.ch1_locked);
 	scdc_print_flag(m, "Channel 2 Locked", st->stf.ch2_locked);
+	if (drm_scdc_num_frl_lanes(st->rate) == 4)
+		scdc_print_flag(m, "Lane 3 Locked", st->stf.ln3_locked);
+
+	scdc_print_flag(m, "Sink Ready For Link Training", st->stf.flt_ready);
+	scdc_print_flag(m, "Sink Failed To Decode DSC", st->stf.dsc_fail);
 
 	scdc_print_dec(m, "Channel 0 Errors", st->error_count[0]);
 	scdc_print_dec(m, "Channel 1 Errors", st->error_count[1]);
 	scdc_print_dec(m, "Channel 2 Errors", st->error_count[2]);
+	if (drm_scdc_num_frl_lanes(st->rate) == 4) {
+		scdc_print_dec(m, "Lane 3 Errors", st->error_count[3]);
+		scdc_print_dec(m, "Reed-Solomon Corrections", st->rs_corrections);
+	}
 
 	return 0;
 
diff --git a/include/drm/display/drm_scdc.h b/include/drm/display/drm_scdc.h
index 3d58f37e8ed8..7f0b05b2f280 100644
--- a/include/drm/display/drm_scdc.h
+++ b/include/drm/display/drm_scdc.h
@@ -29,6 +29,8 @@
 #define SCDC_SOURCE_VERSION 0x02
 
 #define SCDC_UPDATE_0 0x10
+#define  SCDC_RSED_UPDATE (1 << 6)
+#define  SCDC_FLT_UPDATE (1 << 5)
 #define  SCDC_READ_REQUEST_TEST (1 << 2)
 #define  SCDC_CED_UPDATE (1 << 1)
 #define  SCDC_STATUS_UPDATE (1 << 0)
@@ -46,14 +48,25 @@
 #define SCDC_CONFIG_0 0x30
 #define  SCDC_READ_REQUEST_ENABLE (1 << 0)
 
+#define SCDC_CONFIG_1 0x31
+#define  SCDC_FRL_RATE   0x0f
+#define  SCDC_FFE_LEVELS 0xf0
+
 #define SCDC_STATUS_FLAGS_0 0x40
+#define  SCDC_DSC_FAIL (1 << 7)
+#define  SCDC_FLT_READY (1 << 6)
+#define  SCDC_LN3_LOCK (1 << 4)
 #define  SCDC_CH2_LOCK (1 << 3)
 #define  SCDC_CH1_LOCK (1 << 2)
 #define  SCDC_CH0_LOCK (1 << 1)
-#define  SCDC_CH_LOCK_MASK (SCDC_CH2_LOCK | SCDC_CH1_LOCK | SCDC_CH0_LOCK)
+#define  SCDC_CH_LOCK_MASK (SCDC_LN3_LOCK | SCDC_CH2_LOCK | SCDC_CH1_LOCK | \
+			    SCDC_CH0_LOCK)
 #define  SCDC_CLOCK_DETECT (1 << 0)
 
 #define SCDC_STATUS_FLAGS_1 0x41
+#define  SCDC_LN_EVEN_TRAIN_PTRN 0x0f
+#define  SCDC_LN_ODD_TRAIN_PTRN 0xf0
+#define SCDC_STATUS_FLAGS_2 0x42
 
 #define SCDC_ERR_DET_0_L 0x50
 #define SCDC_ERR_DET_0_H 0x51
@@ -65,6 +78,12 @@
 
 #define SCDC_ERR_DET_CHECKSUM 0x56
 
+#define SCDC_ERR_DET_3_L 0x57
+#define SCDC_ERR_DET_3_H 0x58
+
+#define SCDC_ERR_DET_RS_L 0x59
+#define SCDC_ERR_DET_RS_H 0x5a
+
 #define SCDC_TEST_CONFIG_0 0xc0
 #define  SCDC_TEST_READ_REQUEST (1 << 7)
 #define  SCDC_TEST_READ_REQUEST_DELAY(x) ((x) & 0x7f)
diff --git a/include/drm/display/drm_scdc_helper.h b/include/drm/display/drm_scdc_helper.h
index e0b79d79e1ff..a3b20adaac7e 100644
--- a/include/drm/display/drm_scdc_helper.h
+++ b/include/drm/display/drm_scdc_helper.h
@@ -24,6 +24,7 @@
 #ifndef DRM_SCDC_HELPER_H
 #define DRM_SCDC_HELPER_H
 
+#include <linux/errno.h>
 #include <linux/types.h>
 
 #include <drm/display/drm_scdc.h>
@@ -38,8 +39,65 @@ struct drm_scdc_status_flags {
 	bool ch0_locked;
 	bool ch1_locked;
 	bool ch2_locked;
+	bool ln3_locked;
+	bool flt_ready;
+	bool dsc_fail;
+
+	/* Status Register 1 */
+	u8 ln0_training_pattern : 4;
+	u8 ln1_training_pattern : 4;
+
+	/* Status Register 2 */
+	u8 ln2_training_pattern : 4;
+	u8 ln3_training_pattern : 4;
+};
+
+enum drm_scdc_frl_rate {
+	SCDC_FRL_RATE_OFF = 0,
+	SCDC_FRL_RATE_3X3 = 1,
+	SCDC_FRL_RATE_6X3 = 2,
+	SCDC_FRL_RATE_6X4 = 3,
+	SCDC_FRL_RATE_8X4 = 4,
+	SCDC_FRL_RATE_10X4 = 5,
+	SCDC_FRL_RATE_12X4 = 6,
+	SCDC_FRL_RATE_RESV_7 = 7,
+	SCDC_FRL_RATE_RESV_8 = 8,
+	SCDC_FRL_RATE_RESV_9 = 9,
+	SCDC_FRL_RATE_RESV_10 = 10,
+	SCDC_FRL_RATE_RESV_11 = 11,
+	SCDC_FRL_RATE_RESV_12 = 12,
+	SCDC_FRL_RATE_RESV_13 = 13,
+	SCDC_FRL_RATE_RESV_14 = 14,
+	SCDC_FRL_RATE_RESV_15 = 15
 };
 
+/**
+ * drm_scdc_num_frl_lanes - get number of lanes for a given FRL rate
+ * @rate: one of &enum drm_scdc_frl_rate
+ *
+ * For a given @rate, return the number of lanes it uses.
+ *
+ * Returns: %-EINVAL if @rate is not a valid FRL rate, or the number of lanes
+ * for a given &enum drm_scdc_frl_rate on success (including %0 for "off")
+ */
+static inline __pure int drm_scdc_num_frl_lanes(enum drm_scdc_frl_rate rate)
+{
+	switch (rate) {
+	case SCDC_FRL_RATE_OFF:
+		return 0;
+	case SCDC_FRL_RATE_3X3:
+	case SCDC_FRL_RATE_6X3:
+		return 3;
+	case SCDC_FRL_RATE_6X4:
+	case SCDC_FRL_RATE_8X4:
+	case SCDC_FRL_RATE_10X4:
+	case SCDC_FRL_RATE_12X4:
+		return 4;
+	default:
+		return -EINVAL;
+	}
+}
+
 struct drm_scdc_state {
 	/** @stf: contents of the status flag registers */
 	struct drm_scdc_status_flags stf;
@@ -52,9 +110,14 @@ struct drm_scdc_state {
 	 * clock period, false if it's 1/10th of the clock period.
 	 */
 	bool tmds_bclk_x40;
-	/** @error_count: character error counts for each channel */
-	u16 error_count[3];
-
+	/** @rate: FRL rate set by the source */
+	enum drm_scdc_frl_rate rate : 4;
+	/** @ffe_levels: The FFE levels for @rate set by the source */
+	u8 ffe_levels : 4;
+	/** @error_count: character error counts for each channel/link */
+	u16 error_count[4];
+	/** @rs_corrections: number of Reed-Solomon Corrections */
+	u16 rs_corrections;
 	/** @scdc: raw SCDC data buffer */
 	u8 scdc[256];
 };

-- 
2.55.0



^ permalink raw reply related

* [PATCH v7 5/5] drm/display: hdmi-state-helper: Init SCDC debugfs for HDMI
From: Nicolas Frattaroli @ 2026-07-21  8:59 UTC (permalink / raw)
  To: Jani Nikula, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
	David Airlie, Simona Vetter, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Luca Ceresoli, Daniel Stone, Hans Verkuil, Chen-Yu Tsai,
	Samuel Holland, Dave Stevenson, Maíra Canal,
	Raspberry Pi Kernel Maintenance
  Cc: dri-devel, linux-kernel, kernel, linux-arm-kernel, linux-sunxi,
	Nicolas Frattaroli
In-Reply-To: <20260721-scdc-link-health-v7-0-92df1b6ad5fc@collabora.com>

Initialise the SCDC debugfs file for connectors that call the HDMI state
helper's debugfs_init helper. This makes all connectors that use the
helper expose the scdc_status file in their connector debugfs.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/display/drm_hdmi_state_helper.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
index 8d5ee10ceb87..709524b40c26 100644
--- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c
+++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
@@ -13,6 +13,7 @@
 #include <drm/display/drm_hdmi_cec_helper.h>
 #include <drm/display/drm_hdmi_helper.h>
 #include <drm/display/drm_hdmi_state_helper.h>
+#include <drm/display/drm_scdc_helper.h>
 
 /**
  * DOC: hdmi helpers
@@ -1116,6 +1117,7 @@ void drm_hdmi_connector_debugfs_init(struct drm_connector *connector,
 		return;
 
 	create_hdmi_infoframe_files(connector, dir);
+	drm_scdc_debugfs_init(connector, root);
 }
 EXPORT_SYMBOL(drm_hdmi_connector_debugfs_init);
 

-- 
2.55.0



^ permalink raw reply related

* Re: [PATCH] media: stm32: dcmi: fix some error handling bugs in probe()
From: Alain Volmat @ 2026-07-21  9:02 UTC (permalink / raw)
  To: Dan Carpenter
  Cc: Hugues Fruchet, Mauro Carvalho Chehab, Maxime Coquelin,
	Alexandre Torgue, Sakari Ailus, linux-media, linux-stm32,
	linux-arm-kernel, linux-kernel, kernel-janitors
In-Reply-To: <alnx_dGHIBYpZuHo@stanley.mountain>

Hi Dan,

thanks for the patch.

On Fri, Jul 17, 2026 at 12:12:29PM +0300, Dan Carpenter wrote:
> There are a few issues here:
> 
> 1) After we assign:
>         chan = dma_request_chan(&pdev->dev, "tx");
>    Then the error paths need to clean up before returning.  The first
>    error path does a direct return.
> 2) The error paths check "dcmi->mdma_chan" but that is not assigned
>    until later so it results in memory leaks.  Test "mdma_chan"
>    instead.
> 3) The error handling calls dma_release_channel(dcmi->dma_chan) before
>    "dcmi->dma_chan" has been assigned which leads to a NULL pointer
>    dereference.  Use the "chan" variable instead.
> 
> I also moved the call to dma_release_channel() after the call to
> dma_release_channel() so it mirrors the allocation code better.
> 
> Fixes: bc901885fae0 ("media: stm32: dcmi: perform dmaengine_slave_config at probe")
> Signed-off-by: Dan Carpenter <error27@gmail.com>

Acked-by: Alain Volmat <alain.volmat@foss.st.com>

> ---
> From static analysis.  Untested.
> 
>  drivers/media/platform/st/stm32/stm32-dcmi.c | 13 ++++++++-----
>  1 file changed, 8 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/media/platform/st/stm32/stm32-dcmi.c b/drivers/media/platform/st/stm32/stm32-dcmi.c
> index eeb0199864dd..c9f08b2465be 100644
> --- a/drivers/media/platform/st/stm32/stm32-dcmi.c
> +++ b/drivers/media/platform/st/stm32/stm32-dcmi.c
> @@ -2024,8 +2024,10 @@ static int dcmi_probe(struct platform_device *pdev)
>  	mdma_chan = dma_request_chan(&pdev->dev, "mdma_tx");
>  	if (IS_ERR(mdma_chan)) {
>  		ret = PTR_ERR(mdma_chan);
> -		if (ret != -ENODEV)
> -			return dev_err_probe(&pdev->dev, ret, "Failed to request MDMA channel\n");
> +		if (ret != -ENODEV) {
> +			dev_err_probe(&pdev->dev, ret, "Failed to request MDMA channel\n");
> +			goto err_release_chan;
> +		}
>  		mdma_chan = NULL;
>  	}
>  
> @@ -2206,12 +2208,13 @@ static int dcmi_probe(struct platform_device *pdev)
>  err_media_device_cleanup:
>  	media_device_cleanup(&dcmi->mdev);
>  err_mdma_slave_config:
> -	if (dcmi->mdma_chan)
> +	if (mdma_chan)
>  		gen_pool_free(dcmi->sram_pool, (unsigned long)dcmi->sram_buf, dcmi->sram_buf_size);
>  err_dma_slave_config:
> -	dma_release_channel(dcmi->dma_chan);
> -	if (dcmi->mdma_chan)
> +	if (mdma_chan)
>  		dma_release_channel(mdma_chan);
> +err_release_chan:
> +	dma_release_channel(chan);
>  
>  	return ret;
>  }
> -- 
> 2.53.0
> 

Regards,
Alain


^ permalink raw reply

* Re: [RFC PATCH 30/36] arm64: irq: Report FEAT_NMI masking local IRQs
From: Jinjie Ruan @ 2026-07-21  9:06 UTC (permalink / raw)
  To: Vladimir Murzin, linux-arm-kernel
  Cc: mark.rutland, maz, Mark Brown, catalin.marinas, will
In-Reply-To: <20260709121333.23507-31-vladimir.murzin@arm.com>



On 7/9/2026 8:13 PM, Vladimir Murzin wrote:
> From: Ada Couprie Diaz <ada.coupriediaz@arm.com>
> 
> As we clear SCTLR_EL1.SPINTMASK when enabling FEAT_NMI, ALLINT masks
> IRQs and FIQs regardless of superpriority.
> Update irqflags.h and `regs_irqs_disabled()` to take it into account,
> so we properly keep track of
> 
> We have documentation at the top of irqflags.h which explains the DAIF
> masking. Since the additional masking with NMIs is related and also
> covers the IF in DAIF, extend the comment to note what's going on
> with NMIs.
> 
> Co-developed-by: Mark Brown <broonie@kernel.org>
> 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/irqflags.h | 17 +++++++++++++++++
>  arch/arm64/include/asm/ptrace.h   |  9 ++++++++-
>  2 files changed, 25 insertions(+), 1 deletion(-)
> 
> diff --git a/arch/arm64/include/asm/irqflags.h b/arch/arm64/include/asm/irqflags.h
> index b571d34bf11d..a291a231d644 100644
> --- a/arch/arm64/include/asm/irqflags.h
> +++ b/arch/arm64/include/asm/irqflags.h
> @@ -20,6 +20,16 @@
>   * always masked and unmasked together, and have no side effects for other
>   * flags. Keeping to this order makes it easier for entry.S to know which
>   * exceptions should be unmasked.
> + *
> + * With the addition of the FEAT_NMI extension we gain an additional
> + * class of superpriority IRQ/FIQ which is separately masked with a
> + * choice of modes controlled by SCTLR_ELn.{SPINTMASK,NMI}.
> + * Linux sets SPINTMASK to 0 and NMI to 1 which results in ALLINT.ALLINT
> + * masking both superpriority interrupts and IRQ/FIQ regardless of the
> + * I and F settings. Since these superpriority interrupts are being
> + * used as NMIs we do not include them in the interrupt masking here,
> + * anything that requires that NMIs be masked needs to explicitly do so,
> + * but we do check for ALLINT masking IRQs/FIQs.
>   */
>  
>   /*
> @@ -103,6 +113,9 @@ static __always_inline unsigned long arch_local_save_flags(void)
>  {
>  	arm64_exc_hwstate_t hwstate = { .daif = read_sysreg(daif) };
>  
> +	if (system_uses_nmi())
> +		hwstate.allint = read_sysreg_s(SYS_ALLINT);
> +
>  	if (system_uses_irq_prio_masking())
>  		hwstate.pmr = read_sysreg_s(SYS_ICC_PMR_EL1);
>  
> @@ -117,6 +130,10 @@ static __always_inline bool arch_irqs_disabled_flags(unsigned long flags)
>  	if (hwstate.daif & PSR_I_BIT)
>  		return true;
>  
> +	/* SCTLR_EL1.SPINTMASK is clear, so ALLINT masks *all* IRQs/FIQs. */
> +	if (system_uses_nmi() && hwstate.allint > 0)

The ">0" here doesn't seem very rigorous, maybe:

if (system_uses_nmi() && hwstate.allint & ALLINT_ALLINT)

> +		return true;
> +
>  	if (system_uses_irq_prio_masking() && hwstate.pmr < GIC_PRIO_IRQON)
>  		return true;
>  
> diff --git a/arch/arm64/include/asm/ptrace.h b/arch/arm64/include/asm/ptrace.h
> index c630165882e8..096d85d75bd7 100644
> --- a/arch/arm64/include/asm/ptrace.h
> +++ b/arch/arm64/include/asm/ptrace.h
> @@ -206,9 +206,16 @@ static inline void forget_syscall(struct pt_regs *regs)
>  		(regs)->pmr >= GIC_PRIO_IRQON :				\
>  		true)
>  
> +#define irqs_allint_clear(regs)				\
> +	(system_uses_nmi() ?				\
> +		((regs)->pstate & PSR_ALLINT_BIT) == 0:	\
> +		true)
> +
>  static __always_inline bool regs_irqs_disabled(const struct pt_regs *regs)
>  {
> -	return (regs->pstate & PSR_I_BIT) || !irqs_priority_unmasked(regs);
> +	return (regs->pstate & PSR_I_BIT) ||
> +		!irqs_priority_unmasked(regs) ||
> +		!irqs_allint_clear(regs);

Why not define the logic for disable? The semantics of
regs_irqs_disabled() is "interrupts disabled".

204 #define
irqs_priority_masked(regs)>----->------->------->------->-------\
205 >-------(system_uses_irq_prio_masking()
?>------>------->------->-------\
206 >------->-------(regs)->pmr == GIC_PRIO_IRQOFF
:>------->------->-------\
207 >------->-------false)
208
209 #define irqs_allint_masked(regs)>------->------->------->-------\
210 >-------(system_uses_nmi() ?>--->------->------->-------\
211 >------->-------((regs)->pstate & PSR_ALLINT_BIT) :>----\
212 >------->-------false)
213
214 static __always_inline bool regs_irqs_disabled(const struct pt_regs
*regs)
215 {
216 >-------return (regs->pstate & PSR_I_BIT) ||
217 >------->-------irqs_priority_masked(regs) ||
218 >------->-------irqs_allint_masked(regs);

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

>  }
>  
>  #define interrupts_enabled(regs)	(!regs_irqs_disabled(regs))



^ permalink raw reply

* Re: [PATCH v16 5/7] spi: pxa2xx: disable DMA for Apple MacBook8,1
From: Lukas Wunner @ 2026-07-21  9:00 UTC (permalink / raw)
  To: Shih-Yuan Lee
  Cc: Mark Brown, Andy Shevchenko, Mika Westerberg, Daniel Mack,
	Haojian Zhuang, Robert Jarzmik, linux-arm-kernel, linux-spi,
	linux-kernel
In-Reply-To: <20260720162117.32304-6-fourdollars@debian.org>

On Tue, Jul 21, 2026 at 12:21:14AM +0800, Shih-Yuan Lee wrote:
> On MacBook8,1 (early 2015 12" MacBook), the LPSS SPI controller at
> 00:15.4 suffers from hardware DMA handshake failures and interrupt
> routing bugs, causing keyboard/touchpad transactions to fail when
> DMA is enabled.
[...]
> Link: https://bugzilla.kernel.org/show_bug.cgi?id=108331

In the bugzilla comments, Leif Liddy writes:

   "I needed to modify drivers/dma/dw/pci.c to assign the DMA controller
    irq value to 21 (which is what is listed in the ACPI table) for it
    to work. The DMA controller was being assigned an irq value of 20
    for some reason."

It would seem better to fix up the incorrect interrupt number in a quirk,
rather than disabling DMA wholesale.

Thanks,

Lukas


^ permalink raw reply

* Re: [PATCH v6 03/16] power: sequencing: Add pwrseq_power_is_on()
From: Bartosz Golaszewski @ 2026-07-21  9:08 UTC (permalink / raw)
  To: Chen-Yu Tsai
  Cc: linux-acpi, driver-core, linux-pm, linux-usb, devicetree,
	linux-mediatek, linux-arm-kernel, linux-kernel,
	Manivannan Sadhasivam, Alan Stern, Bartosz Golaszewski,
	Bartosz Golaszewski, Greg Kroah-Hartman, Andy Shevchenko,
	Daniel Scally, Heikki Krogerus, Sakari Ailus, Rafael J. Wysocki,
	Danilo Krummrich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Matthias Brugger, AngeloGioacchino Del Regno
In-Reply-To: <20260721065413.2306137-4-wenst@chromium.org>

On Tue, 21 Jul 2026 08:53:58 +0200, Chen-Yu Tsai <wenst@chromium.org> said:
> The power sequencing consumer API already does power on state tracking
> internally. Expose the state to consumers through pwrseq_power_is_on()
> so that they don't have to reimplement it locally.
>
> Acked-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
> Signed-off-by: Chen-Yu Tsai <wenst@chromium.org>
> ---
> Changes since v5:
> - Reverted back to returning -EINVAL if descriptor is NULL
>
> Changes since v4:
> - Make pwrseq_power_is_on() return 1 if descriptor is NULL, i.e. if
>   the descriptor is optional, matching the other pwrseq consumer APIs
>
> Changes since v3:
> - Added missing stub function for !POWER_SEQUENCING
>
> Changes since v2:
> - New patch
>
> Needs to go in with "usb: hub: Power on connected M.2 E-key connectors"
> as it is a build time dependency. Bartosz wants the change on an
> immutable branch to pull into the pwrseq tree.
> ---
>  drivers/power/sequencing/core.c | 18 ++++++++++++++++++
>  include/linux/pwrseq/consumer.h |  6 ++++++
>  2 files changed, 24 insertions(+)
>
> diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c
> index 02f42da91598..72b96d36920e 100644
> --- a/drivers/power/sequencing/core.c
> +++ b/drivers/power/sequencing/core.c
> @@ -968,6 +968,24 @@ int pwrseq_power_off(struct pwrseq_desc *desc)
>  }
>  EXPORT_SYMBOL_GPL(pwrseq_power_off);
>
> +/**
> + * pwrseq_power_is_on() - Queries the last requested state of the power sequencer.
> + * @desc: Descriptor referencing the power sequencer.
> + *
> + * This returns the last requested state of the power sequencer.
> + *
> + * Returns:
> + * On success, 1 for on and 0 for off; negative error number on failure.
> + */
> +int pwrseq_power_is_on(struct pwrseq_desc *desc)
> +{
> +	if (!desc)
> +		return -EINVAL;
> +
> +	return desc->powered_on;
> +}
> +EXPORT_SYMBOL_GPL(pwrseq_power_is_on);
> +

Didn't we agree on introducing an enum to make it future-proof for when we
also pull in pwrseq_is_controllable() from Loic?

I think this should work like so:

enum {
	PWRSEQ_STATE_UNKNOWN,
	PWRSEQ_STATE_ON,
	PWRSEQ_STATE_OFF,
};

int pwrseq_get_state(struct pwrseq_desc *desc)
{
	/* Only once this is upstream. */
	if (!pwrseq_is_controllable(desc))
		return PWRSEQ_STATE_UNKNOWN;

	return desc->powred_on ? PWRSEQ_STATE_ON : PWRSEQ_STATE_OFF;
}

Bart


^ permalink raw reply


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