* Re: [PATCH v14 07/44] arm64: RMI: Configure the RMM with the host's page size
From: Gavin Shan @ 2026-05-21 0:51 UTC (permalink / raw)
To: Steven Price, kvm, kvmarm
Cc: Catalin Marinas, Marc Zyngier, Will Deacon, James Morse,
Oliver Upton, Suzuki K Poulose, Zenghui Yu, linux-arm-kernel,
linux-kernel, Joey Gouly, Alexandru Elisei, Christoffer Dall,
Fuad Tabba, linux-coco, Ganapatrao Kulkarni, Shanker Donthineni,
Alper Gun, Aneesh Kumar K . V, Emi Kisanuki, Vishal Annapurve,
WeiLin.Chang, Lorenzo.Pieralisi2
In-Reply-To: <20260513131757.116630-8-steven.price@arm.com>
Hi Steven,
On 5/13/26 11:17 PM, Steven Price wrote:
> RMM v2.0 brings the ability to set the RMM's granule size. Check the
> feature registers and configure the RMM so that it matches the host's
> page size. This means that operations can be done with a granulatity
> equal to PAGE_SIZE.
>
> Signed-off-by: Steven Price <steven.price@arm.com>
> ---
> Changes since v13:
> * Moved out of KVM.
> ---
> arch/arm64/kernel/rmi.c | 42 +++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 42 insertions(+)
>
> diff --git a/arch/arm64/kernel/rmi.c b/arch/arm64/kernel/rmi.c
> index 99c1ccc35c11..a14ead5dedda 100644
> --- a/arch/arm64/kernel/rmi.c
> +++ b/arch/arm64/kernel/rmi.c
> @@ -49,6 +49,45 @@ static int rmi_check_version(void)
> return 0;
> }
>
> +static int rmi_configure(void)
> +{
> + struct rmm_config *config __free(free_page) = NULL;
> + unsigned long ret;
> +
> + config = (struct rmm_config *)get_zeroed_page(GFP_KERNEL);
> + if (!config)
> + return -ENOMEM;
> +
> + switch (PAGE_SIZE) {
> + case SZ_4K:
> + config->rmi_granule_size = RMI_GRANULE_SIZE_4KB;
> + break;
> + case SZ_16K:
> + config->rmi_granule_size = RMI_GRANULE_SIZE_16KB;
> + break;
> + case SZ_64K:
> + config->rmi_granule_size = RMI_GRANULE_SIZE_64KB;
> + break;
> + default:
> + pr_err("Unsupported PAGE_SIZE for RMM\n");
> + return -EINVAL;
> + }
> +
> + ret = rmi_rmm_config_set(virt_to_phys(config));
> + if (ret) {
> + pr_err("RMM config set failed\n");
> + return -EINVAL;
> + }
> +
Looking at branch 'topics/rmm-v2.0-poc_2' of RMM implementation, the granule size
is fixed to be 4KB at present. I'm not sure if I have looked into correct RMM
implementation, but 'topics/rmm-v2.0-poc_2' is recommended one in the cover
letter.
Besides, there has checks in the handler of the RMI command to make sure that
struct rmm_config::tracking_region_size to be 1GB, indicated by zero. It maybe
worthy to set it before call to rmi_rmm_config_set().
config.tracking_region_size = 0; /* 1GB */
ret = rmi_rmm_config_set(virt_to_phys(config));
> + ret = rmi_rmm_activate();
> + if (ret) {
> + pr_err("RMM activate failed\n");
> + return -ENXIO;
> + }
> +
> + return 0;
> +}
> +
> static int __init arm64_init_rmi(void)
> {
> /* Continue without realm support if we can't agree on a version */
> @@ -60,6 +99,9 @@ static int __init arm64_init_rmi(void)
> if (WARN_ON(rmi_features(1, &rmm_feat_reg1)))
> return 0;
>
> + if (rmi_configure())
> + return 0;
> +
> return 0;
> }
> subsys_initcall(arm64_init_rmi);
Thanks,
Gavin
^ permalink raw reply
* Re: [PATCH] media: rkvdec: hevc: cap EXT SPS RPS control counts before descriptor assembly
From: Michael Bommarito @ 2026-05-21 0:57 UTC (permalink / raw)
To: Detlev Casanova
Cc: Ezequiel Garcia, Mauro Carvalho Chehab, Heiko Stuebner,
linux-media, linux-rockchip, linux-arm-kernel, linux-kernel
In-Reply-To: <67bd72ba-6dab-4bdb-a391-27545e287e94@collabora.com>
On Tue, May 19, 2026 at 9:04 AM Detlev Casanova
<detlev.casanova@collabora.com> wrote:
> Still, did you try just changing the cap to 64 (.cfg.dims = { 64 },) ?
> You'd need a test that sets the control from userspace though.
>
> It should refuse setting the control if there are more than 64 elements,
> therefore the hevc decoder will not run any function using the count
> values from the SPS (See rkvdec-vdpu381-hevc.c:601)
Sure, I can test that and send a v2 for ST. My understanding is that
we have four spots we need to check across the flow though:
1. ST count > 64
2. LT count > 32
3. num_negative_pics / num_positive_pics > 16
4. delta_idx_minus1 + 1 > i
So would you also want the same .cfg approach for the LT cap?
Thanks,
Mike
^ permalink raw reply
* Re: [PATCH v14 08/44] arm64: RMI: Ensure that the RMM has GPT entries for memory
From: Gavin Shan @ 2026-05-21 0:58 UTC (permalink / raw)
To: Steven Price, kvm, kvmarm
Cc: Catalin Marinas, Marc Zyngier, Will Deacon, James Morse,
Oliver Upton, Suzuki K Poulose, Zenghui Yu, linux-arm-kernel,
linux-kernel, Joey Gouly, Alexandru Elisei, Christoffer Dall,
Fuad Tabba, linux-coco, Ganapatrao Kulkarni, Shanker Donthineni,
Alper Gun, Aneesh Kumar K . V, Emi Kisanuki, Vishal Annapurve,
WeiLin.Chang, Lorenzo.Pieralisi2
In-Reply-To: <20260513131757.116630-9-steven.price@arm.com>
Hi Steven,
On 5/13/26 11:17 PM, Steven Price wrote:
> The RMM maintains the state of all the granules in the system to make
> sure that the host is abiding by the rules. This state can be maintained
> at different granularity, per page (TRACKING_FINE) or per region
> (TRACKING_COARSE). The region size depends on the underlying
> "RMI_GRANULE_SIZE". For a "coarse" region all pages in the region must
> be of the same state, this implies we need to have "fine" tracking for
> DRAM, so that we can delegated individual pages.
>
> For now we only support a statically carved out memory for tracking
> granules for the "fine" regions. This can be extended in the future to
> allow modifying the tracking granularity and remove the need for a
> static allocation.
>
> Similarly, the firmware may create L0 GPT entries describing the total
> address space. But if we change the "PAS" (Physical Address Space) of a
> granule then the firmware may need to create L1 tables to track the PAS
> at a finer granularity.
>
> Note: support is currently missing for SROs which means that if the RMM
> needs memory donating this will fail (and render CCA unusable in Linux).
> This effectively means that the L1 GPT tables must be created before
> Linux starts.
>
> Signed-off-by: Steven Price <steven.price@arm.com>
> ---
> Changes since v13:
> * Moved out of KVM
> ---
> arch/arm64/include/asm/rmi_cmds.h | 2 +
> arch/arm64/kernel/rmi.c | 103 ++++++++++++++++++++++++++++++
> 2 files changed, 105 insertions(+)
>
> diff --git a/arch/arm64/include/asm/rmi_cmds.h b/arch/arm64/include/asm/rmi_cmds.h
> index 9179934925c5..9078a2920a7c 100644
> --- a/arch/arm64/include/asm/rmi_cmds.h
> +++ b/arch/arm64/include/asm/rmi_cmds.h
> @@ -33,6 +33,8 @@ struct rmi_sro_state {
> } while (RMI_RETURN_STATUS(res.a0) == RMI_BUSY || \
> RMI_RETURN_STATUS(res.a0) == RMI_BLOCKED)
>
> +bool rmi_is_available(void);
> +
> unsigned long rmi_sro_execute(struct rmi_sro_state *sro, gfp_t gfp);
> void rmi_sro_free(struct rmi_sro_state *sro);
>
> diff --git a/arch/arm64/kernel/rmi.c b/arch/arm64/kernel/rmi.c
> index a14ead5dedda..52a415e99500 100644
> --- a/arch/arm64/kernel/rmi.c
> +++ b/arch/arm64/kernel/rmi.c
> @@ -7,6 +7,8 @@
>
> #include <asm/rmi_cmds.h>
>
> +static bool arm64_rmi_is_available;
> +
> unsigned long rmm_feat_reg0;
> unsigned long rmm_feat_reg1;
>
> @@ -88,6 +90,102 @@ static int rmi_configure(void)
> return 0;
> }
>
> +/*
> + * For now we set the tracking_region_size to 0 for RMI_RMM_CONFIG_SET().
> + * TODO: Support other tracking sizes (via Kconfig option).
> + */
> +#ifdef CONFIG_PAGE_SIZE_4KB
> +#define RMM_GRANULE_TRACKING_SIZE SZ_1G
> +#elif defined(CONFIG_PAGE_SIZE_16KB)
> +#define RMM_GRANULE_TRACKING_SIZE SZ_32M
> +#elif defined(CONFIG_PAGE_SIZE_64KB)
> +#define RMM_GRANULE_TRACKING_SIZE SZ_512M
> +#endif
> +
RMM_GRANULE_TRACKING_SIZE is never used in this series.
> +/*
> + * Make sure the area is tracked by RMM at FINE granularity.
> + * We do not support changing the tracking yet.
> + */
> +static int rmi_verify_memory_tracking(phys_addr_t start, phys_addr_t end)
> +{
> + while (start < end) {
> + unsigned long ret, category, state, next;
> +
> + ret = rmi_granule_tracking_get(start, end, &category, &state, &next);
> + if (ret != RMI_SUCCESS ||
> + state != RMI_TRACKING_FINE ||
> + category != RMI_MEM_CATEGORY_CONVENTIONAL) {
> + /* TODO: Set granule tracking in this case */
> + pr_err("Granule tracking for region isn't fine/conventional: %llx",
> + start);
> + return -ENODEV;
> + }
> + start = next;
> + }
> +
> + return 0;
> +}
> +
> +static unsigned long rmi_l0gpt_size(void)
> +{
> + return 1UL << (30 + FIELD_GET(RMI_FEATURE_REGISTER_1_L0GPTSZ,
> + rmm_feat_reg1));
> +}
> +
rmi_l0gpt_size() is only used by rmi_create_gpts(), its logic can be
combined to that function.
> +static int rmi_create_gpts(phys_addr_t start, phys_addr_t end)
> +{
> + unsigned long l0gpt_sz = rmi_l0gpt_size();
> +
> + start = ALIGN_DOWN(start, l0gpt_sz);
> + end = ALIGN(end, l0gpt_sz);
> +
> + while (start < end) {
> + int ret = rmi_gpt_l1_create(start);
> +
> + /*
> + * Make sure the L1 GPT tables are created for the region.
> + * RMI_ERROR_GPT indicates the L1 table already exists.
> + */
> + if (ret && ret != RMI_ERROR_GPT) {
> + /*
> + * FIXME: Handle SRO so that memory can be donated for
> + * the tables.
> + */
> + pr_err("GPT Level1 table missing for %llx\n", start);
> + return -ENOMEM;
> + }
> + start += l0gpt_sz;
> + }
> +
> + return 0;
> +}
> +
> +static int rmi_init_metadata(void)
> +{
> + phys_addr_t start, end;
> + const struct memblock_region *r;
> +
> + for_each_mem_region(r) {
> + int ret;
> +
> + start = memblock_region_memory_base_pfn(r) << PAGE_SHIFT;
> + end = memblock_region_memory_end_pfn(r) << PAGE_SHIFT;
> + ret = rmi_verify_memory_tracking(start, end);
> + if (ret)
> + return ret;
> + ret = rmi_create_gpts(start, end);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +bool rmi_is_available(void)
> +{
> + return arm64_rmi_is_available;
> +}
> +
> static int __init arm64_init_rmi(void)
> {
> /* Continue without realm support if we can't agree on a version */
> @@ -101,6 +199,11 @@ static int __init arm64_init_rmi(void)
>
> if (rmi_configure())
> return 0;
> + if (rmi_init_metadata())
> + return 0;
> +
> + arm64_rmi_is_available = true;
> + pr_info("RMI configured");
>
> return 0;
> }
Thanks,
Gavin
^ permalink raw reply
* Re: [PATCH v2] media: rkvdec: fix PM runtime teardown ordering in remove
From: Nicolas Dufresne @ 2026-05-21 1:10 UTC (permalink / raw)
To: Francesco Saverio Pavone, jonas, detlev.casanova, hverkuil,
mchehab
Cc: ezequiel, heiko, stable, linux-media, linux-rockchip,
linux-arm-kernel, linux-kernel
In-Reply-To: <f9e63fbbd99c11f303ac8e8f5aec6b2bd528cf99.camel@collabora.com>
[-- Attachment #1: Type: text/plain, Size: 4150 bytes --]
Le mercredi 20 mai 2026 à 20:51 -0400, Nicolas Dufresne a écrit :
> Le lundi 18 mai 2026 à 16:54 +0200, Francesco Saverio Pavone a écrit :
> > From: Jonas Karlman <jonas@kwiboo.se>
> >
> > The current remove() path calls rkvdec_v4l2_cleanup() and
> > pm_runtime_disable() before pm_runtime_dont_use_autosuspend(), and
> > frees the empty IOMMU domain after that. With autosuspend still
> > armed when the domain goes away, the VDPU381 can be left in a dirty
> > state across module reload and suspend/resume cycles.
> >
> > On RK3588 this surfaces as a VP9 inter-prediction bug: from the
> > second ALTREF frame onward, motion blocks decode with U=V=0 (BT.709
> > green), while intra and static blocks stay correct. Reordering the
> > teardown to dont_use_autosuspend() -> iommu_domain_free() ->
> > pm_runtime_disable() -> v4l2_cleanup() makes the symptom go away.
> >
> > Tested on a Radxa Rock 5B+ (RK3588, 8 GB LPDDR5) with both the
> > libva-v4l2-request mpv pipeline and Chromium's V4L2 stateless
> > decoder. With the fix, 300 random pixel samples on VP9 Profile 0
> > clips at 1080p and 1440p match a libvpx software reference exactly
> > (worst delta 0). Without it, the same 1080p sample at frame 4,
> > pixel (960, 270) reads HW=(0,112,0) vs SW=(204,147,116). HEVC and
> > H.264 stateless decoding via mpv keep running on hardware with no
> > fallback.
> >
> > Fixes: ff8c5622f9f7 ("media: rkvdec: Restore iommu addresses on errors")
> > Cc: <stable@vger.kernel.org>
> > Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
> > Tested-by: Francesco Saverio Pavone <pavone.lawyer@gmail.com>
> > Signed-off-by: Francesco Saverio Pavone <pavone.lawyer@gmail.com>
>
> Tested-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
> Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
>
> cheers,
> Nicolas
>
> > ---
> > Changes in v2:
> > - Add Cc: <stable@vger.kernel.org>; media-CI flagged that the
> > Fixes: target (ff8c5622f9f7) is present in the 6.17, 6.18, 6.19
> > and 7.0 stable branches, so the fix should reach them too.
> > Link to v1:
> > https://lore.kernel.org/all/20260518105413.42147-1-pavone.lawyer@gmail.com/
> > Media-CI report:
> > https://linux-media.pages.freedesktop.org/-/users/patchwork/-/jobs/100124849/artifacts/report.htm
> >
> > drivers/media/platform/rockchip/rkvdec/rkvdec.c | 5 +++--
> > 1 file changed, 3 insertions(+), 2 deletions(-)
> >
> > diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.c
> > b/drivers/media/platform/rockchip/rkvdec/rkvdec.c
> > index 6f5f0422d317..bb95b090a25b 100644
> > --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.c
> > +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.c
> > @@ -2066,12 +2066,13 @@ static void rkvdec_remove(struct platform_device
> > *pdev)
> >
> > cancel_delayed_work_sync(&rkvdec->watchdog_work);
> >
> > - rkvdec_v4l2_cleanup(rkvdec);
> > - pm_runtime_disable(&pdev->dev);
> > pm_runtime_dont_use_autosuspend(&pdev->dev);
> >
> > if (rkvdec->empty_domain)
> > iommu_domain_free(rkvdec->empty_domain);
> > +
> > + pm_runtime_disable(&pdev->dev);
> > + rkvdec_v4l2_cleanup(rkvdec);
After consulting the sashiko.dev report, this made me reconsider the fix. A
problem that pre-existed it seems, but made a little worse. Basically, userspace
can still open and call into the API until rkvdec_v4l2_cleanup() is called.
Didn't research too much, but may you can extract:
media_device_unregister(&rkvdec->mdev);
video_unregister_device(&rkvdec->vdev);
And move this at the top of the remove function. This will prevent further
access by userspace, avoiding races. While at it, remove useless
rkvdec_v4l2_cleanup() helper and merge it in, its only used once.
For the rest of your report, I'm under the impression remove won't be called
unless all the open devices has been closed, which will call
v4l2_m2m_ctx_release(), which synchronously abort any pending job.
https://sashiko.dev/#/patchset/20260518145414.64514-1-pavone.lawyer%40gmail.com
> > }
> >
> > #ifdef CONFIG_PM
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* [PATCH v3 0/3] clk: nuvoton: ma35d1: fix PLL frequency calculation
From: Joey Lu @ 2026-05-21 1:42 UTC (permalink / raw)
To: mturquette, sboyd
Cc: ychuang3, schung, yclu4, linux-arm-kernel, linux-clk,
linux-kernel, Joey Lu
Fix four bugs in the MA35D1 PLL clock driver that cause incorrect
frequency values returned from recalc_rate() and determine_rate().
v1 combined all fixes into a single commit. At reviewer request,
split into one patch per logical fix:
1/3 - fix div_u64 return value being discarded (affects both
ma35d1_calc_smic_pll_freq and ma35d1_calc_pll_freq INT mode)
2/3 - fix PLL_CTL1_FRAC mask width (8-bit -> 24-bit) and update
the fractional-mode arithmetic accordingly
3/3 - fix ma35d1_clk_pll_determine_rate: move find_closest() into
the configurable-PLL branch; unify read-only PLL handling
Changes in v3 (vs v2):
- 2/3: replace the manual round-to-nearest expression
"(u32)(((u64)x * 1000 + 500) >> 24)" with the kernel helper
DIV_ROUND_CLOSEST_ULL((u64)x * 1000, 1ULL << 24); the result
is mathematically identical but more readable and idiomatic
Joey Lu (3):
clk: nuvoton: ma35d1: fix ignored div_u64 return values in PLL freq
calculation
clk: nuvoton: ma35d1: fix PLL_CTL1_FRAC bit field width and fractional
calc
clk: nuvoton: ma35d1: fix ma35d1_clk_pll_determine_rate logic
drivers/clk/nuvoton/clk-ma35d1-pll.c | 38 ++++++++++++++--------------
1 file changed, 19 insertions(+), 19 deletions(-)
--
2.43.0
^ permalink raw reply
* [PATCH v3 3/3] clk: nuvoton: ma35d1: fix ma35d1_clk_pll_determine_rate logic
From: Joey Lu @ 2026-05-21 1:42 UTC (permalink / raw)
To: mturquette, sboyd
Cc: ychuang3, schung, yclu4, linux-arm-kernel, linux-clk,
linux-kernel, Joey Lu
In-Reply-To: <20260521014220.77955-1-a0987203069@gmail.com>
ma35d1_clk_pll_determine_rate() called ma35d1_pll_find_closest()
unconditionally before the switch statement, and then every case
branch overwrote pll_freq by reading the current hardware registers.
For CAPLL and DDRPLL this means find_closest() ran unnecessarily
(and incorrectly, since those PLLs are read-only) and its result
was silently discarded.
Fix by moving the find_closest() call inside the APLL/EPLL/VPLL
branch where it belongs. Group CAPLL and DDRPLL together as
read-only PLLs that simply report their current rate; handle them
with an explicit if/else to keep the CAPLL (SMIC design) and DDRPLL
(standard design) paths distinct.
Fixes: 691521a367cf ("clk: nuvoton: Add clock driver for ma35d1 clock controller")
Signed-off-by: Joey Lu <a0987203069@gmail.com>
---
drivers/clk/nuvoton/clk-ma35d1-pll.c | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/drivers/clk/nuvoton/clk-ma35d1-pll.c b/drivers/clk/nuvoton/clk-ma35d1-pll.c
index eb9d69d2077b..c7c0dc91a012 100644
--- a/drivers/clk/nuvoton/clk-ma35d1-pll.c
+++ b/drivers/clk/nuvoton/clk-ma35d1-pll.c
@@ -255,32 +255,32 @@ static int ma35d1_clk_pll_determine_rate(struct clk_hw *hw,
if (req->best_parent_rate < PLL_FREF_MIN_FREQ || req->best_parent_rate > PLL_FREF_MAX_FREQ)
return -EINVAL;
- ret = ma35d1_pll_find_closest(pll, req->rate, req->best_parent_rate,
- reg_ctl, &pll_freq);
- if (ret < 0)
- return ret;
-
switch (pll->id) {
case CAPLL:
+ case DDRPLL:
+ /* Read-only PLLs: return current rate */
reg_ctl[0] = readl_relaxed(pll->ctl0_base);
- pll_freq = ma35d1_calc_smic_pll_freq(reg_ctl[0], req->best_parent_rate);
+ if (pll->id == CAPLL) {
+ pll_freq = ma35d1_calc_smic_pll_freq(reg_ctl[0], req->best_parent_rate);
+ } else {
+ reg_ctl[1] = readl_relaxed(pll->ctl1_base);
+ pll_freq = ma35d1_calc_pll_freq(pll->mode, reg_ctl, req->best_parent_rate);
+ }
req->rate = pll_freq;
-
return 0;
- case DDRPLL:
case APLL:
case EPLL:
case VPLL:
- reg_ctl[0] = readl_relaxed(pll->ctl0_base);
- reg_ctl[1] = readl_relaxed(pll->ctl1_base);
- pll_freq = ma35d1_calc_pll_freq(pll->mode, reg_ctl, req->best_parent_rate);
+ /* Configurable PLLs: find closest achievable rate */
+ ret = ma35d1_pll_find_closest(pll, req->rate, req->best_parent_rate,
+ reg_ctl, &pll_freq);
+ if (ret < 0)
+ return ret;
req->rate = pll_freq;
-
return 0;
}
req->rate = 0;
-
return 0;
}
--
2.43.0
^ permalink raw reply related
* [PATCH v3 1/3] clk: nuvoton: ma35d1: fix ignored div_u64 return values in PLL freq calculation
From: Joey Lu @ 2026-05-21 1:42 UTC (permalink / raw)
To: mturquette, sboyd
Cc: ychuang3, schung, yclu4, linux-arm-kernel, linux-clk,
linux-kernel, Joey Lu, Brian Masney
In-Reply-To: <20260521014220.77955-1-a0987203069@gmail.com>
div_u64() does not modify its argument in place; the return value must
be assigned. Both ma35d1_calc_smic_pll_freq() and ma35d1_calc_pll_freq()
called div_u64() and discarded the result, leaving pll_freq holding the
undivided product and thus returning a frequency orders of magnitude too
high.
Fixes: 691521a367cf ("clk: nuvoton: Add clock driver for ma35d1 clock controller")
Reviewed-by: Brian Masney <bmasney@redhat.com>
Signed-off-by: Joey Lu <a0987203069@gmail.com>
---
drivers/clk/nuvoton/clk-ma35d1-pll.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/clk/nuvoton/clk-ma35d1-pll.c b/drivers/clk/nuvoton/clk-ma35d1-pll.c
index 4620acfe47e8..bfedd45bd04b 100644
--- a/drivers/clk/nuvoton/clk-ma35d1-pll.c
+++ b/drivers/clk/nuvoton/clk-ma35d1-pll.c
@@ -92,7 +92,7 @@ static unsigned long ma35d1_calc_smic_pll_freq(u32 pll0_ctl0,
p = FIELD_GET(SPLL0_CTL0_OUTDIV, pll0_ctl0);
outdiv = 1 << p;
pll_freq = (u64)parent_rate * n;
- div_u64(pll_freq, m * outdiv);
+ pll_freq = div_u64(pll_freq, m * outdiv);
return pll_freq;
}
@@ -110,7 +110,7 @@ static unsigned long ma35d1_calc_pll_freq(u8 mode, u32 *reg_ctl, unsigned long p
if (mode == PLL_MODE_INT) {
pll_freq = (u64)parent_rate * n;
- div_u64(pll_freq, m * p);
+ pll_freq = div_u64(pll_freq, m * p);
} else {
x = FIELD_GET(PLL_CTL1_FRAC, reg_ctl[1]);
/* 2 decimal places floating to integer (ex. 1.23 to 123) */
--
2.43.0
^ permalink raw reply related
* [PATCH v3 2/3] clk: nuvoton: ma35d1: fix PLL_CTL1_FRAC bit field width and fractional calc
From: Joey Lu @ 2026-05-21 1:42 UTC (permalink / raw)
To: mturquette, sboyd
Cc: ychuang3, schung, yclu4, linux-arm-kernel, linux-clk,
linux-kernel, Joey Lu
In-Reply-To: <20260521014220.77955-1-a0987203069@gmail.com>
PLL_CTL1_FRAC was defined as GENMASK(31, 24), covering only 8 bits.
The hardware fractional field occupies bits [31:8] (24 bits), so the
mask must be GENMASK(31, 8).
The previous fractional-mode calculation used FIELD_MAX(PLL_CTL1_FRAC)
as the denominator to obtain 2 decimal places. With the corrected 24-bit
mask the old divisor is wrong; replace the arithmetic with a proper
24-bit fixed-point rounding to 3 decimal places using the kernel's
DIV_ROUND_CLOSEST_ULL helper:
n_frac = n * 1000 + DIV_ROUND_CLOSEST_ULL(x * 1000, 1 << 24)
Fixes: 691521a367cf ("clk: nuvoton: Add clock driver for ma35d1 clock controller")
Signed-off-by: Joey Lu <a0987203069@gmail.com>
---
drivers/clk/nuvoton/clk-ma35d1-pll.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/clk/nuvoton/clk-ma35d1-pll.c b/drivers/clk/nuvoton/clk-ma35d1-pll.c
index bfedd45bd04b..eb9d69d2077b 100644
--- a/drivers/clk/nuvoton/clk-ma35d1-pll.c
+++ b/drivers/clk/nuvoton/clk-ma35d1-pll.c
@@ -48,7 +48,7 @@
#define PLL_CTL1_PD BIT(0)
#define PLL_CTL1_BP BIT(1)
#define PLL_CTL1_OUTDIV GENMASK(6, 4)
-#define PLL_CTL1_FRAC GENMASK(31, 24)
+#define PLL_CTL1_FRAC GENMASK(31, 8)
#define PLL_CTL2_SLOPE GENMASK(23, 0)
#define INDIV_MIN 1
@@ -113,9 +113,9 @@ static unsigned long ma35d1_calc_pll_freq(u8 mode, u32 *reg_ctl, unsigned long p
pll_freq = div_u64(pll_freq, m * p);
} else {
x = FIELD_GET(PLL_CTL1_FRAC, reg_ctl[1]);
- /* 2 decimal places floating to integer (ex. 1.23 to 123) */
- n = n * 100 + ((x * 100) / FIELD_MAX(PLL_CTL1_FRAC));
- pll_freq = div_u64(parent_rate * n, 100 * m * p);
+ /* convert 24-bit fraction to 3 decimal digits, rounding to closest */
+ n = n * 1000 + DIV_ROUND_CLOSEST_ULL((u64)x * 1000, 1ULL << 24);
+ pll_freq = div_u64((u64)parent_rate * n, 1000 * m * p);
}
return pll_freq;
}
--
2.43.0
^ permalink raw reply related
* [PATCH v2] arm64: dts: ti: Add LincolnTech OLDI LCD-185 Overlay for AM625-BeaglePlay
From: Swamil Jain @ 2026-05-21 2:06 UTC (permalink / raw)
To: nm, vigneshr, kristo, robh, krzk+dt, conor+dt, tomi.valkeinen
Cc: r-sharma3, devarsht, praneeth, linux-arm-kernel, devicetree,
linux-kernel, s-jain1
From: Aradhya Bhatia <a-bhatia1@ti.com>
The panel is Lincoln Technology Solutions LCD185-101CT[0]. It is a
Dual-Link LVDS panel and supports WUXGA resolution (1920x1200).
Furthermore, it has an i2c based touch controller: Goodix-GT928.
Add DT overlay for the OLDI panel to connect with BeaglePlay platform.
[0]: https://lincolntechsolutions.com/wp-content/uploads/2024/09/LCD185-101CTL1ARNTT_DS_R1.3.pdf
Signed-off-by: Aradhya Bhatia <a-bhatia1@ti.com>
Signed-off-by: Swamil Jain <s-jain1@ti.com>
---
Changelog:
v1->v2:
Move overlay-specific pinmux configurations from base device tree to
k3-am625-beagleplay-lincolntech-lcd185-panel.dtso file.
link to v1: https://lore.kernel.org/all/20260514225502.2327771-1-s-jain1@ti.com/
---
arch/arm64/boot/dts/ti/Makefile | 4 +
...5-beagleplay-lincolntech-lcd185-panel.dtso | 165 ++++++++++++++++++
2 files changed, 169 insertions(+)
create mode 100644 arch/arm64/boot/dts/ti/k3-am625-beagleplay-lincolntech-lcd185-panel.dtso
diff --git a/arch/arm64/boot/dts/ti/Makefile b/arch/arm64/boot/dts/ti/Makefile
index 7642c06ca834..f0436a102fce 100644
--- a/arch/arm64/boot/dts/ti/Makefile
+++ b/arch/arm64/boot/dts/ti/Makefile
@@ -12,6 +12,7 @@
dtb-$(CONFIG_ARCH_K3) += k3-am625-beagleplay.dtb
dtb-$(CONFIG_ARCH_K3) += k3-am625-beagleplay-csi2-ov5640.dtbo
dtb-$(CONFIG_ARCH_K3) += k3-am625-beagleplay-csi2-tevi-ov5640.dtbo
+dtb-$(CONFIG_ARCH_K3) += k3-am625-beagleplay-lincolntech-lcd185-panel.dtbo
dtb-$(CONFIG_ARCH_K3) += k3-am625-phyboard-lyra-rdk.dtb
dtb-$(CONFIG_ARCH_K3) += k3-am625-sk.dtb
dtb-$(CONFIG_ARCH_K3) += k3-am625-tqma62xx-mba62xx.dtb
@@ -177,6 +178,8 @@ k3-am625-beagleplay-csi2-ov5640-dtbs := k3-am625-beagleplay.dtb \
k3-am625-beagleplay-csi2-ov5640.dtbo
k3-am625-beagleplay-csi2-tevi-ov5640-dtbs := k3-am625-beagleplay.dtb \
k3-am625-beagleplay-csi2-tevi-ov5640.dtbo
+k3-am625-beagleplay-lincolntech-lcd185-panel-dtbs := k3-am625-beagleplay.dtb \
+ k3-am625-beagleplay-lincolntech-lcd185-panel.dtbo
k3-am625-phyboard-lyra-disable-eth-phy-dtbs := k3-am625-phyboard-lyra-rdk.dtb \
k3-am6xx-phycore-disable-eth-phy.dtbo
k3-am625-phyboard-lyra-disable-rtc-dtbs := k3-am625-phyboard-lyra-rdk.dtb \
@@ -287,6 +290,7 @@ k3-j784s4-evm-usxgmii-exp1-exp2-dtbs := k3-j784s4-evm.dtb \
k3-j784s4-evm-usxgmii-exp1-exp2.dtbo
dtb- += k3-am625-beagleplay-csi2-ov5640.dtb \
k3-am625-beagleplay-csi2-tevi-ov5640.dtb \
+ k3-am625-beagleplay-lincolntech-lcd185-panel.dtb \
k3-am625-phyboard-lyra-disable-eth-phy.dtb \
k3-am625-phyboard-lyra-disable-rtc.dtb \
k3-am625-phyboard-lyra-disable-spi-nor.dtb \
diff --git a/arch/arm64/boot/dts/ti/k3-am625-beagleplay-lincolntech-lcd185-panel.dtso b/arch/arm64/boot/dts/ti/k3-am625-beagleplay-lincolntech-lcd185-panel.dtso
new file mode 100644
index 000000000000..e7cadd48d439
--- /dev/null
+++ b/arch/arm64/boot/dts/ti/k3-am625-beagleplay-lincolntech-lcd185-panel.dtso
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+/**
+ * Lincoln tech Solutions OLDI panel (LCD185-101CT) and touch DT overlay for AM625-BeaglePlay
+ *
+ * AM625-BeaglePlay: https://www.beagleboard.org/boards/beagleplay
+ * Panel datasheet: https://lincolntechsolutions.com/wp-content/uploads/2024/09/LCD185-101CTL1ARNTT_DS_R1.3.pdf
+ *
+ * Copyright (C) 2026 Texas Instruments Incorporated - http://www.ti.com/
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include "k3-pinctrl.h"
+
+&{/} {
+ backlight: backlight {
+ compatible = "pwm-backlight";
+ pinctrl-names = "default";
+ pinctrl-0 = <&backlight_pins_default>;
+ brightness-levels = <0 4 8 16 32 64 128 255>;
+ default-brightness-level = <6>;
+ enable-gpios = <&main_gpio0 0 GPIO_ACTIVE_HIGH>;
+ pwms = <&epwm0 1 20000 0>;
+ };
+
+ lcd {
+ compatible = "lincolntech,lcd185-101ct";
+ backlight = <&backlight>;
+ /*
+ * Note that the OLDI TX 0 transmits the odd set of pixels
+ * while the OLDI TX 1 transmits the even set. This is a
+ * fixed configuration in the IP integration and is not
+ * changeable. The properties, "dual-lvds-odd-pixels" and
+ * "dual-lvds-even-pixels" have been used to merely
+ * identify if a Dual Link configuration is required.
+ * Swapping them will cause an error in the dss oldi driver.
+ */
+ power-supply = <&vsys_5v0>;
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ dual-lvds-odd-pixels;
+ lcd_in0: endpoint {
+ remote-endpoint = <&oldi_0_out>;
+ };
+ };
+ port@1 {
+ reg = <1>;
+ dual-lvds-even-pixels;
+ lcd_in1: endpoint {
+ remote-endpoint = <&oldi_1_out>;
+ };
+ };
+ };
+ };
+};
+
+&main_pmx0 {
+ touchscreen_pins_default: touchscreen-default-pins {
+ pinctrl-single,pins = <
+ AM62X_IOPAD(0x01b4, PIN_OUTPUT, 7) /* (A13) SPI0_CS0.GPIO1_15 */
+ AM62X_IOPAD(0x00a0, PIN_INPUT, 7) /* (K25) GPMC0_WPn.GPIO0_39 */
+ >;
+ };
+
+ backlight_pins_default: bl-default-pins {
+ pinctrl-single,pins = <
+ AM62X_IOPAD(0x0000, PIN_OUTPUT, 7) /* (H24) OSPI0_CLK.GPIO0_0 */
+ AM62X_IOPAD(0x01b8, PIN_OUTPUT, 2) /* (C13) SPI0_CS1.EHRPWM0_B */
+ >;
+ };
+};
+
+&dss {
+ status = "okay";
+};
+
+&oldi0 {
+ status = "okay";
+ ti,companion-oldi = <&oldi1>;
+};
+
+&oldi1 {
+ status = "okay";
+ ti,secondary-oldi;
+ ti,companion-oldi = <&oldi0>;
+};
+
+&oldi0_port0 {
+ oldi_0_in: endpoint {
+ remote-endpoint = <&dpi0_out0>;
+ };
+};
+
+&oldi0_port1 {
+ oldi_0_out: endpoint {
+ remote-endpoint = <&lcd_in0>;
+ };
+};
+
+&oldi1_port0 {
+ oldi_1_in: endpoint {
+ remote-endpoint = <&dpi0_out1>;
+ };
+};
+
+&oldi1_port1 {
+ oldi_1_out: endpoint {
+ remote-endpoint = <&lcd_in1>;
+ };
+};
+
+&dss_ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ /* VP1: Output to OLDI */
+ port@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ dpi0_out0: endpoint@0 {
+ reg = <0>;
+ remote-endpoint = <&oldi_0_in>;
+ };
+ dpi0_out1: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&oldi_1_in>;
+ };
+ };
+};
+
+&main_i2c2 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ eeprom@57 {
+ compatible = "atmel,24c256";
+ reg = <0x57>;
+ };
+
+ touchscreen@5d {
+ compatible = "goodix,gt928";
+ reg = <0x5d>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&touchscreen_pins_default>;
+ interrupt-parent = <&main_gpio0>;
+ interrupts = <39 IRQ_TYPE_EDGE_FALLING>;
+ irq-gpios = <&main_gpio0 39 GPIO_ACTIVE_HIGH>;
+ reset-gpios = <&main_gpio1 15 GPIO_ACTIVE_HIGH>;
+ touchscreen-size-x = <1920>;
+ touchscreen-size-y = <1200>;
+ };
+};
+
+&epwm0 {
+ status = "okay";
+};
^ permalink raw reply related
* Re: [PATCH] Bluetooth: btmtk: remove extra copy in cmd array init
From: Jiajia Liu @ 2026-05-21 2:26 UTC (permalink / raw)
To: Luiz Augusto von Dentz
Cc: Marcel Holtmann, Matthias Brugger, AngeloGioacchino Del Regno,
linux-bluetooth, linux-kernel, linux-arm-kernel, linux-mediatek
In-Reply-To: <CABBYNZLLNFNuSv0UBNQ7C2HTTg5W2m41hBTNpPw822GMAVNuhQ@mail.gmail.com>
On Wed, May 20, 2026 at 08:55:46AM -0400, Luiz Augusto von Dentz wrote:
> Hi Jiajia,
>
> On Tue, May 19, 2026 at 10:15 PM Jiajia Liu <liujiajia@kylinos.cn> wrote:
> >
> > In btmtk_setup_firmware_79xx, the data length indicated by wmt_params.dlen
> > in the cmd buffer is MTK_SEC_MAP_NEED_SEND_SIZE + 1. Except for the first
> > byte, the remaining length is MTK_SEC_MAP_NEED_SEND_SIZE. memcpy copied one
> > more byte to cmd + 1 than the remaining length. Align the length passed to
> > memcpy to avoid exceeding current section map.
> >
> > Signed-off-by: Jiajia Liu <liujiajia@kylinos.cn>
> > ---
> > drivers/bluetooth/btmtk.c | 2 +-
> > 1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c
> > index ea7a031000cd..53cba71cb07f 100644
> > --- a/drivers/bluetooth/btmtk.c
> > +++ b/drivers/bluetooth/btmtk.c
> > @@ -188,7 +188,7 @@ int btmtk_setup_firmware_79xx(struct hci_dev *hdev, const char *fwname,
> > MTK_FW_ROM_PATCH_GD_SIZE +
> > MTK_FW_ROM_PATCH_SEC_MAP_SIZE * i +
> > MTK_SEC_MAP_COMMON_SIZE,
> > - MTK_SEC_MAP_NEED_SEND_SIZE + 1);
> > + MTK_SEC_MAP_NEED_SEND_SIZE);
> >
> > wmt_params.op = BTMTK_WMT_PATCH_DWNLD;
> > wmt_params.status = &status;
> > --
> > 2.53.0
> >
>
> Have you tested this on the actual hardware? If not we need a Tested-by.
Yes, I have tested with MT7922 (0489:e0d8) on linux 7.1-rc4 applied this patch
and the following two.
Bluetooth: btmtk: accept too short WMT FUNC_CTRL events
Bluetooth: btmtk: fix urb->setup_packet leak in error paths
setup log of boot and rfkill switch:
$ dmesg | grep hci0
[ 6.108240] Bluetooth: hci0: HW/SW Version: 0x008a008a, Build Time: 20260224103448
[ 8.933508] Bluetooth: hci0: Device setup in 2765295 usecs
[ 8.938846] Bluetooth: hci0: HCI Enhanced Setup Synchronous Connection command is advertised, but not supported.
[ 57.209143] Bluetooth: hci0: HW/SW Version: 0x008a008a, Build Time: 20260224103448
[ 57.366004] Bluetooth: hci0: Device setup in 160450 usecs
[ 57.371248] Bluetooth: hci0: HCI Enhanced Setup Synchronous Connection command is advertised, but not supported.
[ 203.687643] Bluetooth: hci0: HW/SW Version: 0x008a008a, Build Time: 20260224103448
[ 203.844163] Bluetooth: hci0: Device setup in 158989 usecs
[ 203.849426] Bluetooth: hci0: HCI Enhanced Setup Synchronous Connection command is advertised, but not supported.
[ 214.723250] Bluetooth: hci0: HW/SW Version: 0x008a008a, Build Time: 20260224103448
[ 214.879380] Bluetooth: hci0: Device setup in 155239 usecs
[ 214.884644] Bluetooth: hci0: HCI Enhanced Setup Synchronous Connection command is advertised, but not supported.
>
> --
> Luiz Augusto von Dentz
^ permalink raw reply
* [soc:zx/soc 1/1] htmldocs: Documentation/arch/arm/zte/zx297520v3.rst:66: WARNING: Title underline too short.
From: kernel test robot @ 2026-05-21 2:57 UTC (permalink / raw)
To: Stefan Dösinger
Cc: oe-kbuild-all, linux-arm-kernel, arm, Linus Walleij,
Krzysztof Kozlowski, linux-doc
tree: https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc.git zx/soc
head: 220ae5d36dba278003d265aabd080ffa78553f5a
commit: 220ae5d36dba278003d265aabd080ffa78553f5a [1/1] ARM: zte: Add zx297520v3 platform support
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
docutils: docutils (Docutils 0.21.2, Python 3.13.5, on linux)
reproduce: (https://download.01.org/0day-ci/archive/20260521/202605210401.8D6jRbz8-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202605210401.8D6jRbz8-lkp@intel.com/
All warnings (new ones prefixed by >>):
WARNING: Documentation/ABI/testing/sysfs-class-reboot-mode-reboot_modes:36: abi_sys_class_reboot_mode_driver_reboot_modes doesn't have a description
WARNING: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/os_mode is defined 2 times: Documentation/ABI/testing/sysfs-driver-hid-lenovo-go:364; Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s:234
WARNING: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/os_mode_index is defined 2 times: Documentation/ABI/testing/sysfs-driver-hid-lenovo-go:373; Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s:243
WARNING: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/touchpad/enabled is defined 2 times: Documentation/ABI/testing/sysfs-driver-hid-lenovo-go:636; Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s:252
WARNING: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/touchpad/enabled_index is defined 2 times: Documentation/ABI/testing/sysfs-driver-hid-lenovo-go:645; Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s:261
>> Documentation/arch/arm/zte/zx297520v3.rst:66: WARNING: Title underline too short.
--
3. Building for built-in U-Boot
--------------------------- [docutils]
>> Documentation/arch/arm/zte/zx297520v3.rst:90: WARNING: Enumerated list ends without a blank line; unexpected unindent. [docutils]
>> Documentation/arch/arm/zte/zx297520v3.rst:116: WARNING: Inline literal start-string without end-string. [docutils]
Documentation/arch/arm/zte/zx297520v3.rst:137: ERROR: Unexpected indentation. [docutils]
>> Documentation/arch/arm/zte/zx297520v3.rst:138: WARNING: Block quote ends without a blank line; unexpected unindent. [docutils]
Documentation/arch/arm/zte/zx297520v3.rst:164: WARNING: Inline literal start-string without end-string. [docutils]
>> Documentation/arch/arm/zte/zx297520v3.rst:164: WARNING: Inline interpreted text or phrase reference start-string without end-string. [docutils]
>> Documentation/arch/arm/zte/zx297520v3.rst:7: WARNING: Document or section may not begin with a transition. [docutils]
Documentation/arch/riscv/zicfilp.rst:79: WARNING: Inline literal start-string without end-string. [docutils]
Documentation/core-api/kref:328: ./include/linux/kref.h:72: WARNING: Invalid C declaration: Expected end of definition. [error at 96]
int kref_put_mutex (struct kref *kref, void (*release)(struct kref *kref), struct mutex *mutex) __cond_acquires(true# mutex)
------------------------------------------------------------------------------------------------^
Documentation/core-api/kref:328: ./include/linux/kref.h:94: WARNING: Invalid C declaration: Expected end of definition. [error at 92]
vim +66 Documentation/arch/arm/zte/zx297520v3.rst
6
> 7 ...............................................................................
8
9 Author: Stefan Dösinger
10
11 Date : 27 Jan 2026
12
13 1. Hardware description
14 ---------------------------
15 Zx297520v3 SoCs use a 64 bit capable Cortex-A53 CPU and GICv3, although they
16 run in arm32 mode only. The CPU has support EL3, but no hypervisor (EL2) and
17 it seems to lack VFP and NEON.
18
19 The SoC is used in a number of cheap LTE to WiFi routers, both battery powered
20 MiFis and stationary CPEs. In addition to the CPU these devices usually have
21 64 MB Ram (although some is shared with the LTE chip), 128 MB NAND flash, an
22 SDIO connected RTL8192-type Wifi chip limited to 2.4 ghz operation, USB 2,
23 and buttons. Devices with as low as 32 MB or as high as 128 MB ram exist, as
24 do devices with 8 or 16 MB of NOR flash.
25
26 Some devices, especially the stationary ones, have 100 mbit Ethernet and an
27 Ethernet switch.
28
29 Usually the devices have LEDs for status indication, although some have SPI or
30 I2C connected displays
31
32 Some have an SD card slot. If it exists, it is a better choice for the root
33 file system because it easily outperforms the built-in NAND.
34
35 The LTE interface runs on a separate DSP called ZSP880. It is probably derived
36 from LSI ZSPs and has an undocumented instruction set. The ZSP communicates
37 with the main CPU via SRAM and DRAM and a mailbox hardware that can generate
38 IRQs on either ends.
39
40 There is also a Cortex M0 CPU, which is responsible for early HW initialization
41 and starting the Cortex A53 CPU. It does not have any essential purpose once
42 U-Boot is started. A SRAM-Based handover protocol exists to run custom code on
43 this CPU.
44
45 2. Booting via USB
46 ---------------------------
47
48 The Boot ROM has support for booting custom code via USB. This mode can be
49 entered by connecting a Boot PIN to GND or by modifying the third byte on NAND
50 (set it to anything other than 0x5A aka 'Z'). A free software tool to start
51 custom U-Boot and kernels can be found here:
52
53 https://github.com/zx297520v3-mainline/zx297520v3-loader
54
55 If USB download mode is entered but no boot commands are sent through USB, the
56 device will proceed to boot normally after a few seconds. It is therefore
57 possible to enable USB boot permanently and still leave the default boot files
58 in place.
59
60 https://github.com/zx297520v3-mainline/u-boot-mainline
61
62 Contains an U-Boot version that can be used with the USB loader and sets up the
63 CPU and interrupt controller to comply with Linux's booting requirements.
64
65 3. Building for built-in U-Boot
> 66 ---------------------------
67 The devices come with an ancient U-Boot that loads legacy uImages from NAND and
68 boots them without a chance for the user to interrupt. The images are stored in
69 files ap_cpuap.bin and ap_recovery.bin on a jffs2 partition named imagefs,
70 usually mtd4. A file named "fotaflag" switches between the two modes.
71
72 In addition to the uImage header, those files have a 384 byte signature header,
73 which is used for authenticating the images on some devices. Most devices have
74 this authentication disabled and it is enough to pad the uImage files with 384
75 zero bytes.
76
77 Builtin U-Boot also poorly sets up the CPU. Read the next section for details
78 on this. It has no support for loading DTBs, so CONFIG_ARM_APPENDED_DTB is
79 needed.
80
81 So to build an image that boots from NAND the following steps are necessary:
82
83 1) Patch the assembly code from section 3 into arch/arm/kernel/head.S.
84 2) make zx29_defconfig
85 3) make [-j x]
86 4) cat arch/arm/boot/zImage arch/arm/boot/dts/zte/[device].dtb > kernel+dtb
87 5) mkimage -A arm -O linux -T kernel -C none -a 0x20008000 -d kernel+dtb uimg
88 6) dd if=/dev/zero bs=1 count=384 of=ap_recovery.bin
89 7) cat uimg >> ap_recovery.bin
> 90 8) Place this file onto imagefs on the device. Delete ap_cpuap.bin if the
91 free space is not enough.
92 9) Create the file fotaflag: echo -n FOTA-RECOVERY > fotaflag
93
94 For development, booting ap_recovery.bin is recommended because the normal boot
95 mode arms the watchdog before starting the kernel.
96
97 4. CPU and GIC Setup
98 ---------------------------
99
100 Generally CPU and GICv3 need to be set up according to the requirements spelled
101 out in Documentation/arch/arm64/booting.rst. For zx297520v3 this means:
102
103 1. GICD_CTLR.DS=1 to disable GIC security
104 2. Enable access to ICC_SRE
105 3. Disable trapping IRQs into monitor mode
106 4. Configure EL2 and below to run in insecure mode.
107 5. Configure timer PPIs to active-low.
108
109 The kernel sources provided by ZTE do not boot either (interrupts do not work
110 at all). They are incomplete in other aspects too, so it is assumed that there
111 is some workaround similar to the one described in this document somewhere in
112 the binary blobs.
113
114 The assembly code below is given as an example of how to achieve this:
115
> 116 ```
117 #include <linux/irqchip/arm-gic-v3.h>
118 #include <asm/assembler.h>
119 #include <asm/cp15.h>
120
121 @ Detect sane bootloaders and skip the hack
122 ldr r3, =0xf2000000
123 ldr r3, [r3]
124 ldr r4, =(GICD_CTLR_ARE_NS | GICD_CTLR_DS)
125 cmp r3, r4
126 beq skip_zx_hack
127 @ This allows EL1 to handle ints hat are normally handled by EL2/3.
128 ldr r3, =0xf2000000
129 str r4, [r3]
130
131 cps #MON_MODE
132
133 @ Work in non-secure physical address space: SCR_EL3.NS = 1. At least the UART
134 @ seems to respond only to non-secure addresses. I have taken insipiration from
135 @ Raspberry pi's armstub7.S here.
136 mov r3, #0x131 @ non-secure, Make F, A bits in CPSR writeable
137 @ Allow hypervisor call.
> 138 mcr p15, 0, r3, c1, c1, 0
139
140 @ AP_PPI_MODE_REG: Configure timer PPIs (10, 11, 13, 14) to active-low.
141 ldr r3, =0xF22020a8
142 ldr r4, =0x50
143 str r4, [r3]
144 ldr r3, =0xF22020ac
145 ldr r4, =0x14
146 str r4, [r3]
147
148 @ Enable EL2 access to ICC_SRE (bit 3, ICC_SRE_EL3.Enable). Enable system reg
149 @ access to GICv3 registers (bit 0, ICC_SRE_EL3.SRE) for EL1 and EL3.
150 mrc p15, 6, r3, c12, c12, 5 @ ICC_SRE_EL3
151 orr r3, #0x9 @ FIXME: No defines for SRE_EL3 values?
152 mcr p15, 6, r3, c12, c12, 5
153 mrc p15, 0, r3, c12, c12, 5 @ ICC_SRE_EL1
154 orr r3, #(ICC_SRE_EL1_SRE)
155 mcr p15, 0, r3, c12, c12, 5
156
157 @ Like ICC_SRE_EL3, enable EL1 access to ICC_SRE and system register access
158 @ for EL2.
159 mrc p15, 4, r3, c12, c9, 5 @ ICC_SRE_EL2 aka ICC_HSRE
160 orr r3, r3, #(ICC_SRE_EL2_ENABLE | ICC_SRE_EL2_SRE)
161 mcr p15, 4, r3, c12, c9, 5
162 isb
163
> 164 @ Back to SVC mode
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH 2/8] bpf: Recover arena kernel faults with scratch page
From: Emil Tsalapatis @ 2026-05-21 3:16 UTC (permalink / raw)
To: Tejun Heo, David Vernet, Andrea Righi, Changwoo Min,
Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Martin KaFai Lau, Kumar Kartikeya Dwivedi
Cc: Peter Zijlstra, Catalin Marinas, Will Deacon, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, Dave Hansen, Andrew Morton,
David Hildenbrand, Mike Rapoport, Emil Tsalapatis, sched-ext, bpf,
x86, linux-arm-kernel, linux-mm, linux-kernel
In-Reply-To: <20260520235052.4180316-3-tj@kernel.org>
On Wed May 20, 2026 at 7:50 PM EDT, Tejun Heo wrote:
> From: Kumar Kartikeya Dwivedi <memxor@gmail.com>
>
> BPF arena usage is becoming more prevalent, but kernel <-> BPF communication
> over arena memory is awkward today. Data has to be staged through a trusted
> kernel pointer with extra code and copying on the BPF side. While reads
> through arena pointers can use a fault-safe helper, writes don't have a good
> solution. The in-line alternative would need instruction emulation or asm
> fixup labels.
>
> Enable direct kernel-side reads and writes within GUARD_SZ / 2 of any
> handed-in arena pointer, without bounds checking. A per-arena scratch page
> is installed by the arch fault path into empty arena kernel PTEs - x86 from
> page_fault_oops() for not-present faults, arm64 from __do_kernel_fault() for
> translation faults, both after the existing exception-table and KFENCE
> handling. The faulting instruction retries and the access is also reported
> through the program's BPF stream, preserving error reporting.
>
> bpf_prog_find_from_stack() resolves the current BPF program (and its arena)
> from the kernel stack - no new bpf_run_ctx state is added. Recovery covers
> the 4 GiB arena plus the upper half-guard (GUARD_SZ / 2). The lower
> half-guard is excluded because well-behaved kfuncs only access forward from
> arena pointers. The kfunc-author contract - access at most GUARD_SZ / 2 past
> a handed-in pointer - is documented in Documentation/bpf/kfuncs.rst.
>
> The install is lock-free via ptep_try_set(). On race-loss the winning
> installer's PTE is already valid, so the access retry succeeds. The arena
> clear path uses ptep_get_and_clear() so installer and clearer race through
> atomic accessors. No flush_tlb_kernel_range() afterwards. Stale "not mapped"
> entries just cause one extra re-fault, cheaper than a global IPI on every
> install.
>
> Scratch exists only to keep the kernel from oopsing on an in-line arena
> access. Its presence at a PTE means the BPF program has already
> malfunctioned, and the violation is reported through the program's BPF
> stream. The only requirement for behavior on a scratched PTE is that the
> kernel doesn't crash. In particular, any user-side access through such a PTE
> may segfault. The shared scratch page is freed once during map destruction.
>
> BPF instruction faults continue to use the existing JIT exception-table
> path. This patch changes only the kernel-text fault path. No UAPI flag is
> added. The new behavior is the default.
>
> v2: Use ptep_get_and_clear() in apply_range_clear_cb(). (David)
>
> Suggested-by: Alexei Starovoitov <ast@kernel.org>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> Signed-off-by: Tejun Heo <tj@kernel.org>
> Cc: David Hildenbrand <david@kernel.org>
> ---
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
> Documentation/bpf/kfuncs.rst | 14 +++
> arch/arm64/mm/fault.c | 10 +-
> arch/x86/mm/fault.c | 12 ++-
> include/linux/bpf.h | 1 +
> include/linux/bpf_defs.h | 11 +++
> kernel/bpf/arena.c | 177 +++++++++++++++++++++++++++--------
> kernel/bpf/core.c | 5 +
> 7 files changed, 183 insertions(+), 47 deletions(-)
> create mode 100644 include/linux/bpf_defs.h
>
> diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
> index 75e6c078e0e7..6d497e720998 100644
> --- a/Documentation/bpf/kfuncs.rst
> +++ b/Documentation/bpf/kfuncs.rst
> @@ -462,6 +462,20 @@ In order to accommodate such requirements, the verifier will enforce strict
> PTR_TO_BTF_ID type matching if two types have the exact same name, with one
> being suffixed with ``___init``.
>
> +2.8 Accessing arena memory through kfunc arguments
> +--------------------------------------------------
> +
> +A read or write at any address inside an arena does not oops the kernel.
> +Unallocated arena pages are lazily backed by a scratch page and the
> +access is reported through the program's BPF stream as an error. Only
> +the BPF program's correctness is affected; the kernel itself remains
> +intact.
> +
> +The arena is followed by a ``GUARD_SZ / 2`` (32 KiB) guard region that
> +is also covered by this recovery. A kfunc handed an arena pointer may
> +therefore access up to ``GUARD_SZ / 2`` past it without bounds-checking
> +against the arena. Larger accesses must verify the range explicitly.
> +
> .. _BPF_kfunc_lifecycle_expectations:
>
> 3. kfunc lifecycle expectations
> diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c
> index 920a8b244d59..0d58d667fcd8 100644
> --- a/arch/arm64/mm/fault.c
> +++ b/arch/arm64/mm/fault.c
> @@ -9,6 +9,7 @@
>
> #include <linux/acpi.h>
> #include <linux/bitfield.h>
> +#include <linux/bpf_defs.h>
> #include <linux/extable.h>
> #include <linux/kfence.h>
> #include <linux/signal.h>
> @@ -416,9 +417,12 @@ static void __do_kernel_fault(unsigned long addr, unsigned long esr,
> } else if (addr < PAGE_SIZE) {
> msg = "NULL pointer dereference";
> } else {
> - if (esr_fsc_is_translation_fault(esr) &&
> - kfence_handle_page_fault(addr, esr & ESR_ELx_WNR, regs))
> - return;
> + if (esr_fsc_is_translation_fault(esr)) {
> + if (kfence_handle_page_fault(addr, esr & ESR_ELx_WNR, regs))
> + return;
> + if (bpf_arena_handle_page_fault(addr, esr & ESR_ELx_WNR, regs->pc))
> + return;
> + }
>
> msg = "paging request";
> }
> diff --git a/arch/x86/mm/fault.c b/arch/x86/mm/fault.c
> index f0e77e084482..b0f103ddbd23 100644
> --- a/arch/x86/mm/fault.c
> +++ b/arch/x86/mm/fault.c
> @@ -8,6 +8,7 @@
> #include <linux/sched/task_stack.h> /* task_stack_*(), ... */
> #include <linux/kdebug.h> /* oops_begin/end, ... */
> #include <linux/memblock.h> /* max_low_pfn */
> +#include <linux/bpf_defs.h> /* bpf_arena_handle_page_fault */
> #include <linux/kfence.h> /* kfence_handle_page_fault */
> #include <linux/kprobes.h> /* NOKPROBE_SYMBOL, ... */
> #include <linux/mmiotrace.h> /* kmmio_handler, ... */
> @@ -688,10 +689,13 @@ page_fault_oops(struct pt_regs *regs, unsigned long error_code,
> if (IS_ENABLED(CONFIG_EFI))
> efi_crash_gracefully_on_page_fault(address);
>
> - /* Only not-present faults should be handled by KFENCE. */
> - if (!(error_code & X86_PF_PROT) &&
> - kfence_handle_page_fault(address, error_code & X86_PF_WRITE, regs))
> - return;
> + /* Only not-present faults should be handled by KFENCE or BPF arena. */
> + if (!(error_code & X86_PF_PROT)) {
> + if (kfence_handle_page_fault(address, error_code & X86_PF_WRITE, regs))
> + return;
> + if (bpf_arena_handle_page_fault(address, error_code & X86_PF_WRITE, regs->ip))
> + return;
> + }
>
> oops:
> /*
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index 0136a108d083..831996c411cf 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -6,6 +6,7 @@
>
> #include <uapi/linux/bpf.h>
> #include <uapi/linux/filter.h>
> +#include <linux/bpf_defs.h>
>
> #include <crypto/sha2.h>
> #include <linux/workqueue.h>
> diff --git a/include/linux/bpf_defs.h b/include/linux/bpf_defs.h
> new file mode 100644
> index 000000000000..d98e033b8c0b
> --- /dev/null
> +++ b/include/linux/bpf_defs.h
> @@ -0,0 +1,11 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Subset of bpf.h declarations, split out so files that need only these
> + * declarations can avoid bpf.h's full include cost.
> + */
> +#ifndef _LINUX_BPF_DEFS_H
> +#define _LINUX_BPF_DEFS_H
> +
> +bool bpf_arena_handle_page_fault(unsigned long addr, bool is_write, unsigned long fault_ip);
> +
> +#endif /* _LINUX_BPF_DEFS_H */
> diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
> index 08d008cc471e..1c0b87ecc817 100644
> --- a/kernel/bpf/arena.c
> +++ b/kernel/bpf/arena.c
> @@ -53,6 +53,7 @@ struct bpf_arena {
> u64 user_vm_start;
> u64 user_vm_end;
> struct vm_struct *kern_vm;
> + struct page *scratch_page;
> struct range_tree rt;
> /* protects rt */
> rqspinlock_t spinlock;
> @@ -118,6 +119,11 @@ struct apply_range_data {
> int i;
> };
>
> +struct clear_range_data {
> + struct llist_head *free_pages;
> + struct page *scratch_page;
> +};
> +
> static int apply_range_set_cb(pte_t *pte, unsigned long addr, void *data)
> {
> struct apply_range_data *d = data;
> @@ -144,33 +150,59 @@ static void flush_vmap_cache(unsigned long start, unsigned long size)
> flush_cache_vmap(start, start + size);
> }
>
> -static int apply_range_clear_cb(pte_t *pte, unsigned long addr, void *free_pages)
> +static int apply_range_clear_cb(pte_t *pte, unsigned long addr, void *data)
> {
> + struct clear_range_data *d = data;
> pte_t old_pte;
> struct page *page;
>
> - /* sanity check */
> - old_pte = ptep_get(pte);
> + /*
> + * Pairs with ptep_try_set() in the kernel-fault scratch installer.
> + * Both sides must be atomic.
> + */
> + old_pte = ptep_get_and_clear(&init_mm, addr, pte);
> if (pte_none(old_pte) || !pte_present(old_pte))
> - return 0; /* nothing to do */
> + return 0;
>
> page = pte_page(old_pte);
> if (WARN_ON_ONCE(!page))
> return -EINVAL;
>
> - pte_clear(&init_mm, addr, pte);
> + /*
> + * Skip the per-arena scratch page. A kernel fault on an unallocated uaddr
> + * scratches its PTE. A later bpf_arena_free_pages() over that range walks
> + * here. Without the skip, scratch_page would be freed.
> + */
> + if (page == d->scratch_page)
> + return 0;
> +
> + __llist_add(&page->pcp_llist, d->free_pages);
> + return 0;
> +}
>
> - /* Add page to the list so it is freed later */
> - if (free_pages)
> - __llist_add(&page->pcp_llist, free_pages);
> +static int apply_range_set_scratch_cb(pte_t *pte, unsigned long addr, void *data)
> +{
> + struct page *scratch_page = data;
>
> + if (!pte_none(ptep_get(pte)))
> + return 0;
> + /*
> + * Best-effort install. ptep_try_set() returns false only if another
> + * installer (real allocation or concurrent fault) won the cmpxchg.
> + * Their PTE is already valid, so the access retry succeeds.
> + *
> + * No flush_tlb_kernel_range() needed. Stale "not mapped" entries just
> + * cause one extra re-fault through this same path.
> + */
> + ptep_try_set(pte, mk_pte(scratch_page, PAGE_KERNEL));
> return 0;
> }
>
> static int populate_pgtable_except_pte(struct bpf_arena *arena)
> {
> + /* Populate intermediates for the recovery range (4 GiB + upper half-guard). */
> return apply_to_page_range(&init_mm, bpf_arena_get_kern_vm_start(arena),
> - KERN_VM_SZ - GUARD_SZ, apply_range_set_cb, NULL);
> + SZ_4G + GUARD_SZ / 2, apply_range_set_cb, NULL);
> }
>
> static struct bpf_map *arena_map_alloc(union bpf_attr *attr)
> @@ -221,22 +253,29 @@ static struct bpf_map *arena_map_alloc(union bpf_attr *attr)
> init_irq_work(&arena->free_irq, arena_free_irq);
> INIT_WORK(&arena->free_work, arena_free_worker);
> bpf_map_init_from_attr(&arena->map, attr);
> +
> + err = bpf_map_alloc_pages(&arena->map, NUMA_NO_NODE, 1, &arena->scratch_page);
> + if (err)
> + goto err_free_arena;
> +
> range_tree_init(&arena->rt);
> err = range_tree_set(&arena->rt, 0, attr->max_entries);
> - if (err) {
> - bpf_map_area_free(arena);
> - goto err;
> - }
> + if (err)
> + goto err_free_scratch;
> mutex_init(&arena->lock);
> raw_res_spin_lock_init(&arena->spinlock);
> err = populate_pgtable_except_pte(arena);
> - if (err) {
> - range_tree_destroy(&arena->rt);
> - bpf_map_area_free(arena);
> - goto err;
> - }
> + if (err)
> + goto err_destroy_rt;
>
> return &arena->map;
> +
> +err_destroy_rt:
> + range_tree_destroy(&arena->rt);
> +err_free_scratch:
> + __free_page(arena->scratch_page);
> +err_free_arena:
> + bpf_map_area_free(arena);
> err:
> free_vm_area(kern_vm);
> return ERR_PTR(err);
> @@ -244,6 +283,7 @@ static struct bpf_map *arena_map_alloc(union bpf_attr *attr)
>
> static int existing_page_cb(pte_t *ptep, unsigned long addr, void *data)
> {
> + struct bpf_arena *arena = data;
> struct page *page;
> pte_t pte;
>
> @@ -251,6 +291,12 @@ static int existing_page_cb(pte_t *ptep, unsigned long addr, void *data)
> if (!pte_present(pte)) /* sanity check */
> return 0;
> page = pte_page(pte);
> + /*
> + * Skip the scratch page. The walk is page-table-driven, not range-tree-driven,
> + * so it can visit scratch PTEs at uaddrs the BPF program never allocated.
> + */
> + if (page == arena->scratch_page)
> + return 0;
> /*
> * We do not update pte here:
> * 1. Nobody should be accessing bpf_arena's range outside of a kernel bug
> @@ -286,9 +332,10 @@ static void arena_map_free(struct bpf_map *map)
> * free those pages.
> */
> apply_to_existing_page_range(&init_mm, bpf_arena_get_kern_vm_start(arena),
> - KERN_VM_SZ - GUARD_SZ, existing_page_cb, NULL);
> + SZ_4G + GUARD_SZ / 2, existing_page_cb, arena);
> free_vm_area(arena->kern_vm);
> range_tree_destroy(&arena->rt);
> + __free_page(arena->scratch_page);
> bpf_map_area_free(arena);
> }
>
> @@ -374,33 +421,37 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
> return VM_FAULT_RETRY;
>
> page = vmalloc_to_page((void *)kaddr);
> - if (page)
> + if (page) {
> + if (page == arena->scratch_page)
> + /* BPF triggered scratch here; don't lazy-alloc over it */
> + goto out_sigsegv;
> /* already have a page vmap-ed */
> goto out;
> + }
>
> bpf_map_memcg_enter(&arena->map, &old_memcg, &new_memcg);
>
> if (arena->map.map_flags & BPF_F_SEGV_ON_FAULT)
> /* User space requested to segfault when page is not allocated by bpf prog */
> - goto out_unlock_sigsegv;
> + goto out_sigsegv_memcg;
>
> ret = range_tree_clear(&arena->rt, vmf->pgoff, 1);
> if (ret)
> - goto out_unlock_sigsegv;
> + goto out_sigsegv_memcg;
>
> struct apply_range_data data = { .pages = &page, .i = 0 };
> /* Account into memcg of the process that created bpf_arena */
> ret = bpf_map_alloc_pages(map, NUMA_NO_NODE, 1, &page);
> if (ret) {
> range_tree_set(&arena->rt, vmf->pgoff, 1);
> - goto out_unlock_sigsegv;
> + goto out_sigsegv_memcg;
> }
>
> ret = apply_to_page_range(&init_mm, kaddr, PAGE_SIZE, apply_range_set_cb, &data);
> if (ret) {
> range_tree_set(&arena->rt, vmf->pgoff, 1);
> free_pages_nolock(page, 0);
> - goto out_unlock_sigsegv;
> + goto out_sigsegv_memcg;
> }
> flush_vmap_cache(kaddr, PAGE_SIZE);
> bpf_map_memcg_exit(old_memcg, new_memcg);
> @@ -409,8 +460,9 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
> raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
> vmf->page = page;
> return 0;
> -out_unlock_sigsegv:
> +out_sigsegv_memcg:
> bpf_map_memcg_exit(old_memcg, new_memcg);
> +out_sigsegv:
> raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
> return VM_FAULT_SIGSEGV;
> }
> @@ -668,6 +720,7 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,
> struct llist_head free_pages;
> struct llist_node *pos, *t;
> struct arena_free_span *s;
> + struct clear_range_data cdata;
> unsigned long flags;
> int ret = 0;
>
> @@ -696,9 +749,11 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,
> range_tree_set(&arena->rt, pgoff, page_cnt);
>
> init_llist_head(&free_pages);
> + cdata.free_pages = &free_pages;
> + cdata.scratch_page = arena->scratch_page;
> /* clear ptes and collect struct pages */
> apply_to_existing_page_range(&init_mm, kaddr, page_cnt << PAGE_SHIFT,
> - apply_range_clear_cb, &free_pages);
> + apply_range_clear_cb, &cdata);
>
> /* drop the lock to do the tlb flush and zap pages */
> raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
> @@ -788,6 +843,7 @@ static void arena_free_worker(struct work_struct *work)
> struct arena_free_span *s;
> u64 arena_vm_start, user_vm_start;
> struct llist_head free_pages;
> + struct clear_range_data cdata;
> struct page *page;
> unsigned long full_uaddr;
> long kaddr, page_cnt, pgoff;
> @@ -801,6 +857,8 @@ static void arena_free_worker(struct work_struct *work)
> bpf_map_memcg_enter(&arena->map, &old_memcg, &new_memcg);
>
> init_llist_head(&free_pages);
> + cdata.free_pages = &free_pages;
> + cdata.scratch_page = arena->scratch_page;
> arena_vm_start = bpf_arena_get_kern_vm_start(arena);
> user_vm_start = bpf_arena_get_user_vm_start(arena);
>
> @@ -813,7 +871,7 @@ static void arena_free_worker(struct work_struct *work)
>
> /* clear ptes and collect pages in free_pages llist */
> apply_to_existing_page_range(&init_mm, kaddr, page_cnt << PAGE_SHIFT,
> - apply_range_clear_cb, &free_pages);
> + apply_range_clear_cb, &cdata);
>
> range_tree_set(&arena->rt, pgoff, page_cnt);
> }
> @@ -928,23 +986,12 @@ static int __init kfunc_init(void)
> }
> late_initcall(kfunc_init);
>
> -void bpf_prog_report_arena_violation(bool write, unsigned long addr, unsigned long fault_ip)
> +static void __bpf_prog_report_arena_violation(struct bpf_prog *prog, bool write,
> + unsigned long addr, unsigned long fault_ip)
> {
> struct bpf_stream_stage ss;
> - struct bpf_prog *prog;
> u64 user_vm_start;
>
> - /*
> - * The RCU read lock is held to safely traverse the latch tree, but we
> - * don't need its protection when accessing the prog, since it will not
> - * disappear while we are handling the fault.
> - */
> - rcu_read_lock();
> - prog = bpf_prog_ksym_find(fault_ip);
> - rcu_read_unlock();
> - if (!prog)
> - return;
> -
> /* Use main prog for stream access */
> prog = prog->aux->main_prog_aux->prog;
>
> @@ -957,3 +1004,53 @@ void bpf_prog_report_arena_violation(bool write, unsigned long addr, unsigned lo
> bpf_stream_dump_stack(ss);
> }));
> }
> +
> +bool bpf_arena_handle_page_fault(unsigned long addr, bool is_write, unsigned long fault_ip)
> +{
> + struct bpf_arena *arena;
> + struct bpf_prog *prog;
> + unsigned long kbase;
> + unsigned long page_addr = addr & PAGE_MASK;
> +
> + prog = bpf_prog_find_from_stack();
> + if (!prog)
> + return false;
> +
> + arena = prog->aux->arena;
> + /* a prog not using arena may be on stack, so arena can be NULL */
> + if (!arena)
> + return false;
> +
> + kbase = bpf_arena_get_kern_vm_start(arena);
> +
> + /*
> + * Recovery covers the 4 GiB mappable band plus the upper half-guard.
> + * Lower guard is unreachable from kfuncs; an address there indicates
> + * a different bug class - leave it to the regular kernel oops path.
> + */
> + if (page_addr < kbase || page_addr >= kbase + SZ_4G + GUARD_SZ / 2)
> + return false;
> +
> + apply_to_page_range(&init_mm, page_addr, PAGE_SIZE,
> + apply_range_set_scratch_cb, arena->scratch_page);
> + flush_vmap_cache(page_addr, PAGE_SIZE);
> + __bpf_prog_report_arena_violation(prog, is_write, page_addr - kbase, fault_ip);
> + return true;
> +}
> +
> +void bpf_prog_report_arena_violation(bool write, unsigned long addr, unsigned long fault_ip)
> +{
> + struct bpf_prog *prog;
> +
> + /*
> + * The RCU read lock is held to safely traverse the latch tree, but we
> + * don't need its protection when accessing the prog, since it will not
> + * disappear while we are handling the fault.
> + */
> + rcu_read_lock();
> + prog = bpf_prog_ksym_find(fault_ip);
> + rcu_read_unlock();
> + if (!prog)
> + return;
> + __bpf_prog_report_arena_violation(prog, write, addr, fault_ip);
> +}
> diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
> index 066b86e7233c..fa368d8920d9 100644
> --- a/kernel/bpf/core.c
> +++ b/kernel/bpf/core.c
> @@ -3290,6 +3290,11 @@ __weak u64 bpf_arena_get_kern_vm_start(struct bpf_arena *arena)
> {
> return 0;
> }
> +__weak bool bpf_arena_handle_page_fault(unsigned long addr, bool is_write,
> + unsigned long fault_ip)
> +{
> + return false;
> +}
>
> #ifdef CONFIG_BPF_SYSCALL
> static int __init bpf_global_ma_init(void)
^ permalink raw reply
* Re: [PATCH 3/8] bpf: Add sleepable variant of bpf_arena_alloc_pages for kernel callers
From: Emil Tsalapatis @ 2026-05-21 3:17 UTC (permalink / raw)
To: Tejun Heo, David Vernet, Andrea Righi, Changwoo Min,
Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Martin KaFai Lau, Kumar Kartikeya Dwivedi
Cc: Peter Zijlstra, Catalin Marinas, Will Deacon, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, Dave Hansen, Andrew Morton,
David Hildenbrand, Mike Rapoport, Emil Tsalapatis, sched-ext, bpf,
x86, linux-arm-kernel, linux-mm, linux-kernel
In-Reply-To: <20260520235052.4180316-4-tj@kernel.org>
On Wed May 20, 2026 at 7:50 PM EDT, Tejun Heo wrote:
> The existing kernel-side export of bpf_arena_alloc_pages is _non_sleepable
> only - it's used by the verifier to inline the kfunc when the call site is
> non-sleepable. There is no sleepable equivalent for kernel callers; the
> kfunc bpf_arena_alloc_pages itself is BPF-only.
>
> sched_ext needs sleepable kernel-side allocs for its arena pool init/grow
> paths. Add bpf_arena_alloc_pages_sleepable() mirroring the _non_sleepable
> wrapper but passing sleepable=true to arena_alloc_pages().
>
> Signed-off-by: Tejun Heo <tj@kernel.org>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
> ---
> include/linux/bpf.h | 8 ++++++++
> kernel/bpf/arena.c | 13 +++++++++++++
> 2 files changed, 21 insertions(+)
>
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index 831996c411cf..64968ca6db51 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -679,6 +679,8 @@ int bpf_dynptr_from_file_sleepable(struct file *file, u32 flags,
> void *bpf_arena_alloc_pages_non_sleepable(void *p__map, void *addr__ign, u32 page_cnt, int node_id,
> u64 flags);
> void bpf_arena_free_pages_non_sleepable(void *p__map, void *ptr__ign, u32 page_cnt);
> +void *bpf_arena_alloc_pages_sleepable(void *p__map, void *addr__ign, u32 page_cnt, int node_id,
> + u64 flags);
> #else
> static inline void *bpf_arena_alloc_pages_non_sleepable(void *p__map, void *addr__ign, u32 page_cnt,
> int node_id, u64 flags)
> @@ -689,6 +691,12 @@ static inline void *bpf_arena_alloc_pages_non_sleepable(void *p__map, void *addr
> static inline void bpf_arena_free_pages_non_sleepable(void *p__map, void *ptr__ign, u32 page_cnt)
> {
> }
> +
> +static inline void *bpf_arena_alloc_pages_sleepable(void *p__map, void *addr__ign, u32 page_cnt,
> + int node_id, u64 flags)
> +{
> + return NULL;
> +}
> #endif
>
> extern const struct bpf_map_ops bpf_map_offload_ops;
> diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
> index 1c0b87ecc817..a811cf6170fa 100644
> --- a/kernel/bpf/arena.c
> +++ b/kernel/bpf/arena.c
> @@ -934,6 +934,19 @@ void *bpf_arena_alloc_pages_non_sleepable(void *p__map, void *addr__ign, u32 pag
>
> return (void *)arena_alloc_pages(arena, (long)addr__ign, page_cnt, node_id, false);
> }
> +
> +void *bpf_arena_alloc_pages_sleepable(void *p__map, void *addr__ign, u32 page_cnt,
> + int node_id, u64 flags)
> +{
> + struct bpf_map *map = p__map;
> + struct bpf_arena *arena = container_of(map, struct bpf_arena, map);
> +
> + if (map->map_type != BPF_MAP_TYPE_ARENA || flags || !page_cnt)
> + return NULL;
> +
> + return (void *)arena_alloc_pages(arena, (long)addr__ign, page_cnt, node_id, true);
> +}
> +
> __bpf_kfunc void bpf_arena_free_pages(void *p__map, void *ptr__ign, u32 page_cnt)
> {
> struct bpf_map *map = p__map;
^ permalink raw reply
* [PATCH] arm64: mm: call pagetable dtor when freeing hot-removed page tables
From: Alistair Popple @ 2026-05-21 3:27 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, linux-mm, catalin.marinas, will, david, akpm,
Alistair Popple
Since 5e8eb9aeeda3 ("arm64: mm: always call PTE/PMD ctor in
__create_pgd_mapping()") page-table allocation on ARM64 always
calls pagetable_{pte,pmd,pud,p4d}_ctor(). This sets the page_type
to PGTY_table, increments NR_PAGETABLE and possible allocates a PTL.
However the matching pagetable_dtor() calls were never added.
With DEBUG_VM enabled on kernel versions prior to v6.17 without
2dfcd1608f3a9 ("mm/page_alloc: let page freeing clear any set page
type") this leads to the following warning when freeing these pages due
to page->page_type sharing page->_mapcount:
BUG: Bad page state in process ... pfn:284fbb
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x284fbb
flags: 0x17fffc000000000(node=0|zone=2|lastcpupid=0x1ffff)
page_type: f2(table)
page dumped because: nonzero mapcount
Call trace:
bad_page+0x13c/0x160
__free_frozen_pages+0x6cc/0x860
___free_pages+0xf4/0x180
free_pages+0x54/0x80
free_hotplug_page_range.part.0+0x58/0x90
free_empty_tables+0x438/0x500
__remove_pgd_mapping.constprop.0+0x60/0xa8
arch_remove_memory+0x48/0x80
try_remove_memory+0x158/0x1d8
offline_and_remove_memory+0x138/0x180
It can also lead to leaking the ptl allocation if ALLOC_SPLIT_PTLOCKS
is defined and incorrect NR_PAGETABLE stats. Fix this by calling
pagetable_dtor() in free_hotplug_pgtable_page() prior to freeing the
page to undo the effects of calling pagetable_*_ctor().
Fixes: 5e8eb9aeeda3 ("arm64: mm: always call PTE/PMD ctor in __create_pgd_mapping()")
Signed-off-by: Alistair Popple <apopple@nvidia.com>
---
arch/arm64/mm/mmu.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
index 8e1d80a7033e..0c24fe650e95 100644
--- a/arch/arm64/mm/mmu.c
+++ b/arch/arm64/mm/mmu.c
@@ -1422,6 +1422,7 @@ static void free_hotplug_page_range(struct page *page, size_t size,
static void free_hotplug_pgtable_page(struct page *page)
{
+ pagetable_dtor(page_ptdesc(page));
free_hotplug_page_range(page, PAGE_SIZE, NULL);
}
--
2.54.0
^ permalink raw reply related
* [PATCH 0/5] drm/bridge: Implement generic USB Type-C DP HPD bridge
From: Chaoyi Chen @ 2026-05-21 3:28 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang,
Heiko Stübner, Andy Yan, Vinod Koul
Cc: Heikki Krogerus, Dmitry Baryshkov, Luca Ceresoli, linux-kernel,
dri-devel, linux-arm-kernel, linux-rockchip, linux-phy,
Chaoyi Chen
From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
This series is split from the v15 "Add Type-C DP support for RK3399 EVB
IND board" series [1]. It focuses on the DRM bridge and Rockchip
platform CDN-DP controller changes.
[1] https://lore.kernel.org/all/20260304094152.92-1-kernel@airkyi.com/
====
1. Generic Type-C DP HPD bridge
Currently, several USB-C controller drivers register their own DP HPD
bridge via aux-hpd-bridge.c, each duplicating the same logic. For
devicetree based platforms, the USB-C controller may vary across boards,
and not every USB-C controller driver implements this feature. Patch 1
implements a generic DP HPD bridge that monitors Type-C bus events and
automatically creates an HPD bridge when a Type-C port device with DP
SVID is registered.
2. Multiple bridge model for CDN-DP
The RK3399 has two USB/DP combo PHY and one CDN-DP controller. Patch 5
introduces a multi-bridge model where each PHY port gets a separate
encoder and bridge, allowing flexible selection of the output PHY port.
This is based on the DRM AUX HPD bridge rather than extcon.
====
Patch 1 adds generic USB Type-C DP HPD bridge (Dmitry, Heikki).
Patch 2 adds new API drm_aux_bridge_register_from_node() (Neil).
Patch 3 adds DRM AUX bridge support for RK3399 USBDP PHY (Neil).
Patch 4 drops CDN-DP's extcon dependency when Type-C is present (Dmitry).
Patch 5 adds multiple bridges to support PHY port selection (Dmitry, Luca).
Chaoyi Chen (5):
drm/bridge: Implement generic USB Type-C DP HPD bridge
drm/bridge: aux: Add drm_aux_bridge_register_from_node()
phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge
drm/rockchip: cdn-dp: Support handle lane info without extcon
drm/rockchip: cdn-dp: Add multiple bridges to support PHY port
selection
drivers/gpu/drm/bridge/Kconfig | 10 +
drivers/gpu/drm/bridge/Makefile | 1 +
drivers/gpu/drm/bridge/aux-bridge.c | 24 +-
.../gpu/drm/bridge/aux-hpd-typec-dp-bridge.c | 49 +++
drivers/gpu/drm/rockchip/Kconfig | 1 +
drivers/gpu/drm/rockchip/cdn-dp-core.c | 349 ++++++++++++++----
drivers/gpu/drm/rockchip/cdn-dp-core.h | 18 +-
drivers/phy/rockchip/Kconfig | 2 +
drivers/phy/rockchip/phy-rockchip-typec.c | 13 +-
include/drm/bridge/aux-bridge.h | 6 +
10 files changed, 404 insertions(+), 69 deletions(-)
create mode 100644 drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
--
2.53.0
^ permalink raw reply
* [PATCH 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node()
From: Chaoyi Chen @ 2026-05-21 3:28 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang,
Heiko Stübner, Andy Yan, Vinod Koul
Cc: Heikki Krogerus, Dmitry Baryshkov, Luca Ceresoli, linux-kernel,
dri-devel, linux-arm-kernel, linux-rockchip, linux-phy,
Chaoyi Chen
In-Reply-To: <20260521032854.103-1-kernel@airkyi.com>
From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
The drm_aux_bridge_register() uses the device->of_node as the
bridge->of_node.
This patch adds drm_aux_bridge_register_from_node() to allow
specifying the of_node corresponding to the bridge.
Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
---
drivers/gpu/drm/bridge/aux-bridge.c | 24 ++++++++++++++++++++++--
include/drm/bridge/aux-bridge.h | 6 ++++++
2 files changed, 28 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/bridge/aux-bridge.c b/drivers/gpu/drm/bridge/aux-bridge.c
index 1ed21a8713bf..f50283abed5f 100644
--- a/drivers/gpu/drm/bridge/aux-bridge.c
+++ b/drivers/gpu/drm/bridge/aux-bridge.c
@@ -35,6 +35,7 @@ static void drm_aux_bridge_unregister_adev(void *_adev)
/**
* drm_aux_bridge_register - Create a simple bridge device to link the chain
* @parent: device instance providing this bridge
+ * @np: device node pointer corresponding to this bridge instance
*
* Creates a simple DRM bridge that doesn't implement any drm_bridge
* operations. Such bridges merely fill a place in the bridge chain linking
@@ -42,7 +43,7 @@ static void drm_aux_bridge_unregister_adev(void *_adev)
*
* Return: zero on success, negative error code on failure
*/
-int drm_aux_bridge_register(struct device *parent)
+int drm_aux_bridge_register_from_node(struct device *parent, struct device_node *np)
{
struct auxiliary_device *adev;
int ret;
@@ -62,7 +63,10 @@ int drm_aux_bridge_register(struct device *parent)
adev->dev.parent = parent;
adev->dev.release = drm_aux_bridge_release;
- device_set_of_node_from_dev(&adev->dev, parent);
+ if (np)
+ device_set_node(&adev->dev, of_fwnode_handle(np));
+ else
+ device_set_of_node_from_dev(&adev->dev, parent);
ret = auxiliary_device_init(adev);
if (ret) {
@@ -80,6 +84,22 @@ int drm_aux_bridge_register(struct device *parent)
return devm_add_action_or_reset(parent, drm_aux_bridge_unregister_adev, adev);
}
+EXPORT_SYMBOL_GPL(drm_aux_bridge_register_from_node);
+
+/**
+ * drm_aux_bridge_register - Create a simple bridge device to link the chain
+ * @parent: device instance providing this bridge
+ *
+ * Creates a simple DRM bridge that doesn't implement any drm_bridge
+ * operations. Such bridges merely fill a place in the bridge chain linking
+ * surrounding DRM bridges.
+ *
+ * Return: zero on success, negative error code on failure
+ */
+int drm_aux_bridge_register(struct device *parent)
+{
+ return drm_aux_bridge_register_from_node(parent, NULL);
+}
EXPORT_SYMBOL_GPL(drm_aux_bridge_register);
struct drm_aux_bridge_data {
diff --git a/include/drm/bridge/aux-bridge.h b/include/drm/bridge/aux-bridge.h
index c2f5a855512f..7dd1f17a1354 100644
--- a/include/drm/bridge/aux-bridge.h
+++ b/include/drm/bridge/aux-bridge.h
@@ -13,11 +13,17 @@ struct auxiliary_device;
#if IS_ENABLED(CONFIG_DRM_AUX_BRIDGE)
int drm_aux_bridge_register(struct device *parent);
+int drm_aux_bridge_register_from_node(struct device *parent, struct device_node *np);
#else
static inline int drm_aux_bridge_register(struct device *parent)
{
return 0;
}
+
+static inline int drm_aux_bridge_register_from_node(struct device *parent, struct device_node *np)
+{
+ return 0;
+}
#endif
#if IS_ENABLED(CONFIG_DRM_AUX_HPD_BRIDGE)
--
2.53.0
^ permalink raw reply related
* [PATCH 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge
From: Chaoyi Chen @ 2026-05-21 3:28 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang,
Heiko Stübner, Andy Yan, Vinod Koul
Cc: Heikki Krogerus, Dmitry Baryshkov, Luca Ceresoli, linux-kernel,
dri-devel, linux-arm-kernel, linux-rockchip, linux-phy,
Chaoyi Chen
In-Reply-To: <20260521032854.103-1-kernel@airkyi.com>
From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
The HPD function of Type-C DP is implemented through
drm_connector_oob_hotplug_event(). For embedded DP, it is required
that the DRM connector fwnode corresponds to the Type-C port fwnode.
To describe the relationship between the DP controller and the Type-C
port device, we usually using drm_bridge to build a bridge chain.
Now several USB-C controller drivers have already implemented the DP
HPD bridge function provided by aux-hpd-bridge.c, it will build a DP
HPD bridge on USB-C connector port device.
But this requires the USB-C controller driver to manually register the
HPD bridge. If the driver does not implement this feature, the bridge
will not be create.
So this patch implements a generic DP HPD bridge based on
aux-hpd-bridge.c. It will monitor Type-C bus events, and when a
Type-C port device containing the DP svid is registered, it will
create an HPD bridge for it without the need for the USB-C controller
driver to implement it.
Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
---
drivers/gpu/drm/bridge/Kconfig | 10 ++++
drivers/gpu/drm/bridge/Makefile | 1 +
.../gpu/drm/bridge/aux-hpd-typec-dp-bridge.c | 49 +++++++++++++++++++
3 files changed, 60 insertions(+)
create mode 100644 drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index c3209b0f4678..d92e93875793 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -30,6 +30,16 @@ config DRM_AUX_HPD_BRIDGE
Simple bridge that terminates the bridge chain and provides HPD
support.
+if DRM_AUX_HPD_BRIDGE
+config DRM_AUX_HPD_TYPEC_BRIDGE
+ tristate
+ depends on TYPEC || !TYPEC
+ default TYPEC
+ help
+ Simple bridge that terminates the bridge chain and provides HPD
+ support. It build bridge on each USB-C connector device node.
+endif
+
menu "Display Interface Bridges"
depends on DRM && DRM_BRIDGE
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index beab5b695a6e..c4761526ba0a 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_DRM_AUX_BRIDGE) += aux-bridge.o
obj-$(CONFIG_DRM_AUX_HPD_BRIDGE) += aux-hpd-bridge.o
+obj-$(CONFIG_DRM_AUX_HPD_TYPEC_BRIDGE) += aux-hpd-typec-dp-bridge.o
obj-$(CONFIG_DRM_CHIPONE_ICN6211) += chipone-icn6211.o
obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
obj-$(CONFIG_DRM_CROS_EC_ANX7688) += cros-ec-anx7688.o
diff --git a/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
new file mode 100644
index 000000000000..d915e0fb0668
--- /dev/null
+++ b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <linux/of.h>
+#include <linux/usb/typec_altmode.h>
+#include <linux/usb/typec_dp.h>
+
+#include <drm/bridge/aux-bridge.h>
+
+static int drm_typec_bus_event(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct device *dev = (struct device *)data;
+ struct typec_altmode *alt = to_typec_altmode(dev);
+
+ if (action != BUS_NOTIFY_ADD_DEVICE)
+ goto done;
+
+ /*
+ * alt->dev.parent->parent : USB-C controller device
+ * alt->dev.parent : USB-C connector device
+ */
+ if (is_typec_port_altmode(&alt->dev) && alt->svid == USB_TYPEC_DP_SID)
+ drm_dp_hpd_bridge_register(alt->dev.parent->parent,
+ to_of_node(alt->dev.parent->fwnode));
+
+done:
+ return NOTIFY_OK;
+}
+
+static struct notifier_block drm_typec_event_nb = {
+ .notifier_call = drm_typec_bus_event,
+};
+
+static void drm_aux_hpd_typec_dp_bridge_module_exit(void)
+{
+ bus_unregister_notifier(&typec_bus, &drm_typec_event_nb);
+}
+
+static int __init drm_aux_hpd_typec_dp_bridge_module_init(void)
+{
+ bus_register_notifier(&typec_bus, &drm_typec_event_nb);
+
+ return 0;
+}
+
+module_init(drm_aux_hpd_typec_dp_bridge_module_init);
+module_exit(drm_aux_hpd_typec_dp_bridge_module_exit);
+
+MODULE_DESCRIPTION("DRM TYPEC DP HPD BRIDGE");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection
From: Chaoyi Chen @ 2026-05-21 3:28 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang,
Heiko Stübner, Andy Yan, Vinod Koul
Cc: Heikki Krogerus, Dmitry Baryshkov, Luca Ceresoli, linux-kernel,
dri-devel, linux-arm-kernel, linux-rockchip, linux-phy,
Chaoyi Chen
In-Reply-To: <20260521032854.103-1-kernel@airkyi.com>
From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
The RK3399 has two USB/DP combo PHY and one CDN-DP controller. And
the CDN-DP can be switched to output to one of the PHYs. If both ports
are plugged into DP, DP will select the first port for output.
This patch adds support for multiple bridges, enabling users to flexibly
select the output port. For each PHY port, a separate encoder and bridge
are registered.
The change is based on the DRM AUX HPD bridge, rather than the
extcon approach. This requires the DT to correctly describe the
connections between the first bridge in bridge chain and DP
controller. For example, the bridge chain may be like this:
PHY aux birdge -> fsa4480 analog audio switch bridge ->
onnn,nb7vpq904m USB reminder bridge -> USB-C controller AUX HPD bridge
In this case, the connection relationships among the PHY aux bridge
and the DP contorller need to be described in DT.
In addition, the cdn_dp_parse_next_bridge_dt() will parses it and
determines whether to register one or two bridges.
Since there is only one DP controller, only one of the PHY ports can
output at a time. The key is how to switch between different PHYs,
which is handled by cdn_dp_switch_port() and cdn_dp_enable().
There are two cases:
1. Neither bridge is enabled. In this case, both bridges can
independently read the EDID, and the PHY port may switch before
reading the EDID.
2. One bridge is already enabled. In this case, other bridges are not
allowed to read the EDID. So we will try to return the cached EDID.
Since the scenario of two ports plug in at the same time is rare,
I don't have a board which support two TypeC connector to test this.
Therefore, I tested forced switching on a single PHY port, as well as
output using a fake PHY port alongside a real PHY port.
Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Reviewed-by: Heiko Stuebner <heiko@sntech.de>
---
drivers/gpu/drm/rockchip/Kconfig | 1 +
drivers/gpu/drm/rockchip/cdn-dp-core.c | 324 ++++++++++++++++++++-----
drivers/gpu/drm/rockchip/cdn-dp-core.h | 18 +-
3 files changed, 286 insertions(+), 57 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
index 1479b8c4ed40..cb97690c5a5d 100644
--- a/drivers/gpu/drm/rockchip/Kconfig
+++ b/drivers/gpu/drm/rockchip/Kconfig
@@ -59,6 +59,7 @@ config ROCKCHIP_CDN_DP
select DRM_DISPLAY_HELPER
select DRM_BRIDGE_CONNECTOR
select DRM_DISPLAY_DP_HELPER
+ select DRM_AUX_HPD_BRIDGE
help
This selects support for Rockchip SoC specific extensions
for the cdn DP driver. If you want to enable Dp on
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c
index 9068118859e2..b9ba279ca653 100644
--- a/drivers/gpu/drm/rockchip/cdn-dp-core.c
+++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c
@@ -28,16 +28,17 @@
#include "cdn-dp-core.h"
#include "cdn-dp-reg.h"
-static inline struct cdn_dp_device *bridge_to_dp(struct drm_bridge *bridge)
+static int cdn_dp_switch_port(struct cdn_dp_device *dp, struct cdn_dp_port *prev_port,
+ struct cdn_dp_port *port);
+
+static inline struct cdn_dp_bridge *bridge_to_dp_bridge(struct drm_bridge *bridge)
{
- return container_of(bridge, struct cdn_dp_device, bridge);
+ return container_of(bridge, struct cdn_dp_bridge, bridge);
}
-static inline struct cdn_dp_device *encoder_to_dp(struct drm_encoder *encoder)
+static inline struct cdn_dp_device *bridge_to_dp(struct drm_bridge *bridge)
{
- struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
-
- return container_of(rkencoder, struct cdn_dp_device, encoder);
+ return bridge_to_dp_bridge(bridge)->parent;
}
#define GRF_SOC_CON9 0x6224
@@ -192,14 +193,27 @@ static int cdn_dp_get_sink_count(struct cdn_dp_device *dp, u8 *sink_count)
static struct cdn_dp_port *cdn_dp_connected_port(struct cdn_dp_device *dp)
{
struct cdn_dp_port *port;
- int i, lanes;
+ int i, lanes[MAX_PHY];
for (i = 0; i < dp->ports; i++) {
port = dp->port[i];
- lanes = cdn_dp_get_port_lanes(port);
- if (lanes)
+ lanes[i] = cdn_dp_get_port_lanes(port);
+ if (!dp->next_bridge_valid)
return port;
}
+
+ if (dp->next_bridge_valid) {
+ /* If more than one port is available, pick the last active port */
+ if (dp->active_port > 0 && lanes[dp->active_port])
+ return dp->port[dp->active_port];
+
+ /* If the last active port is not available, pick an available port in order */
+ for (i = 0; i < dp->bridge_count; i++) {
+ if (lanes[i])
+ return dp->port[i];
+ }
+ }
+
return NULL;
}
@@ -254,12 +268,45 @@ static const struct drm_edid *
cdn_dp_bridge_edid_read(struct drm_bridge *bridge, struct drm_connector *connector)
{
struct cdn_dp_device *dp = bridge_to_dp(bridge);
- const struct drm_edid *drm_edid;
+ struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge);
+ struct cdn_dp_port *port = dp->port[dp_bridge->id];
+ struct cdn_dp_port *prev_port;
+ const struct drm_edid *drm_edid = NULL;
+ int i, ret;
mutex_lock(&dp->lock);
+
+ /* More than one port is available */
+ if (dp->bridge_count > 1 && !port->phy_enabled) {
+ for (i = 0; i < dp->bridge_count; i++) {
+ /* Another port already enable */
+ if (dp->bridge_list[i] != dp_bridge && dp->bridge_list[i]->enabled)
+ goto get_cache;
+ /* Find already enabled port */
+ if (dp->port[i]->phy_enabled)
+ prev_port = dp->port[i];
+ }
+
+ /* Switch to current port */
+ if (prev_port) {
+ ret = cdn_dp_switch_port(dp, prev_port, port);
+ if (ret)
+ goto get_cache;
+ }
+ }
+
drm_edid = drm_edid_read_custom(connector, cdn_dp_get_edid_block, dp);
+ /* replace edid cache */
+ if (dp->edid_cache[dp_bridge->id])
+ drm_edid_free(dp->edid_cache[dp_bridge->id]);
+ dp->edid_cache[dp_bridge->id] = drm_edid_dup(drm_edid);
+
mutex_unlock(&dp->lock);
+ return drm_edid;
+get_cache:
+ drm_edid = drm_edid_dup(dp->edid_cache[dp_bridge->id]);
+ mutex_unlock(&dp->lock);
return drm_edid;
}
@@ -268,12 +315,13 @@ cdn_dp_bridge_mode_valid(struct drm_bridge *bridge,
const struct drm_display_info *display_info,
const struct drm_display_mode *mode)
{
+ struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge);
struct cdn_dp_device *dp = bridge_to_dp(bridge);
u32 requested, actual, rate, sink_max, source_max = 0;
u8 lanes, bpc;
/* If DP is disconnected, every mode is invalid */
- if (!dp->connected)
+ if (!dp_bridge->connected || !dp->connected)
return MODE_BAD;
switch (display_info->bpc) {
@@ -551,6 +599,54 @@ static bool cdn_dp_check_link_status(struct cdn_dp_device *dp)
return drm_dp_channel_eq_ok(link_status, min(port->lanes, sink_lanes));
}
+static int cdn_dp_switch_port(struct cdn_dp_device *dp, struct cdn_dp_port *prev_port,
+ struct cdn_dp_port *port)
+{
+ int ret;
+
+ if (dp->active)
+ return 0;
+
+ ret = cdn_dp_disable_phy(dp, prev_port);
+ if (ret)
+ goto out;
+ ret = cdn_dp_enable_phy(dp, port);
+ if (ret)
+ goto out;
+
+ ret = cdn_dp_get_sink_capability(dp);
+ if (ret) {
+ cdn_dp_disable_phy(dp, port);
+ goto out;
+ }
+
+ dp->active = true;
+ dp->lanes = port->lanes;
+
+ if (!cdn_dp_check_link_status(dp)) {
+ dev_info(dp->dev, "Connected with sink; re-train link\n");
+
+ ret = cdn_dp_train_link(dp);
+ if (ret) {
+ dev_err(dp->dev, "Training link failed: %d\n", ret);
+ goto out;
+ }
+
+ ret = cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE);
+ if (ret) {
+ dev_err(dp->dev, "Failed to idle video %d\n", ret);
+ goto out;
+ }
+
+ ret = cdn_dp_config_video(dp);
+ if (ret)
+ dev_err(dp->dev, "Failed to configure video: %d\n", ret);
+ }
+
+out:
+ return ret;
+}
+
static void cdn_dp_display_info_update(struct cdn_dp_device *dp,
struct drm_display_info *display_info)
{
@@ -572,6 +668,7 @@ static void cdn_dp_display_info_update(struct cdn_dp_device *dp,
static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_atomic_state *state)
{
struct cdn_dp_device *dp = bridge_to_dp(bridge);
+ struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge);
struct drm_connector *connector;
int ret, val;
@@ -581,7 +678,7 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at
cdn_dp_display_info_update(dp, &connector->display_info);
- ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, &dp->encoder.encoder);
+ ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, &dp_bridge->encoder.encoder);
if (ret < 0) {
DRM_DEV_ERROR(dp->dev, "Could not get vop id, %d", ret);
return;
@@ -600,6 +697,9 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at
mutex_lock(&dp->lock);
+ if (dp->next_bridge_valid)
+ dp->active_port = dp_bridge->id;
+
ret = cdn_dp_enable(dp);
if (ret) {
DRM_DEV_ERROR(dp->dev, "Failed to enable bridge %d\n",
@@ -632,6 +732,7 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at
goto out;
}
+ dp_bridge->enabled = true;
out:
mutex_unlock(&dp->lock);
}
@@ -639,9 +740,11 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at
static void cdn_dp_bridge_atomic_disable(struct drm_bridge *bridge, struct drm_atomic_state *state)
{
struct cdn_dp_device *dp = bridge_to_dp(bridge);
+ struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge);
int ret;
mutex_lock(&dp->lock);
+ dp_bridge->enabled = false;
if (dp->active) {
ret = cdn_dp_disable(dp);
@@ -828,6 +931,16 @@ static int cdn_dp_audio_mute_stream(struct drm_bridge *bridge,
return ret;
}
+static void cdn_dp_bridge_hpd_notify(struct drm_bridge *bridge,
+ enum drm_connector_status status)
+{
+ struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge);
+ struct cdn_dp_device *dp = bridge_to_dp(bridge);
+
+ dp->bridge_list[dp_bridge->id]->connected = status == connector_status_connected;
+ schedule_work(&dp->event_work);
+}
+
static const struct drm_bridge_funcs cdn_dp_bridge_funcs = {
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
@@ -838,6 +951,7 @@ static const struct drm_bridge_funcs cdn_dp_bridge_funcs = {
.atomic_disable = cdn_dp_bridge_atomic_disable,
.mode_valid = cdn_dp_bridge_mode_valid,
.mode_set = cdn_dp_bridge_mode_set,
+ .hpd_notify = cdn_dp_bridge_hpd_notify,
.dp_audio_prepare = cdn_dp_audio_prepare,
.dp_audio_mute_stream = cdn_dp_audio_mute_stream,
@@ -886,7 +1000,8 @@ static void cdn_dp_pd_event_work(struct work_struct *work)
{
struct cdn_dp_device *dp = container_of(work, struct cdn_dp_device,
event_work);
- int ret;
+ bool connected;
+ int i, ret;
mutex_lock(&dp->lock);
@@ -945,9 +1060,12 @@ static void cdn_dp_pd_event_work(struct work_struct *work)
out:
mutex_unlock(&dp->lock);
- drm_bridge_hpd_notify(&dp->bridge,
- dp->connected ? connector_status_connected
- : connector_status_disconnected);
+ for (i = 0; i < dp->bridge_count; i++) {
+ connected = dp->connected && dp->bridge_list[i]->connected;
+ drm_bridge_hpd_notify(&dp->bridge_list[i]->bridge,
+ connected ? connector_status_connected
+ : connector_status_disconnected);
+ }
}
static int cdn_dp_pd_event(struct notifier_block *nb,
@@ -967,28 +1085,16 @@ static int cdn_dp_pd_event(struct notifier_block *nb,
return NOTIFY_DONE;
}
-static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
+static int cdn_bridge_add(struct device *dev,
+ struct drm_bridge *bridge,
+ struct drm_bridge *next_bridge,
+ struct drm_encoder *encoder)
{
struct cdn_dp_device *dp = dev_get_drvdata(dev);
- struct drm_encoder *encoder;
+ struct drm_device *drm_dev = dp->drm_dev;
+ struct drm_bridge *last_bridge __free(drm_bridge_put) = NULL;
struct drm_connector *connector;
- struct cdn_dp_port *port;
- struct drm_device *drm_dev = data;
- int ret, i;
-
- ret = cdn_dp_parse_dt(dp);
- if (ret < 0)
- return ret;
-
- dp->drm_dev = drm_dev;
- dp->connected = false;
- dp->active = false;
- dp->active_port = -1;
- dp->fw_loaded = false;
-
- INIT_WORK(&dp->event_work, cdn_dp_pd_event_work);
-
- encoder = &dp->encoder.encoder;
+ int ret;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
dev->of_node);
@@ -1003,26 +1109,35 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
drm_encoder_helper_add(encoder, &cdn_dp_encoder_helper_funcs);
- dp->bridge.ops =
- DRM_BRIDGE_OP_DETECT |
- DRM_BRIDGE_OP_EDID |
- DRM_BRIDGE_OP_HPD |
- DRM_BRIDGE_OP_DP_AUDIO;
- dp->bridge.of_node = dp->dev->of_node;
- dp->bridge.type = DRM_MODE_CONNECTOR_DisplayPort;
- dp->bridge.hdmi_audio_dev = dp->dev;
- dp->bridge.hdmi_audio_max_i2s_playback_channels = 8;
- dp->bridge.hdmi_audio_spdif_playback = 1;
- dp->bridge.hdmi_audio_dai_port = -1;
-
- ret = devm_drm_bridge_add(dev, &dp->bridge);
+ bridge->ops =
+ DRM_BRIDGE_OP_DETECT |
+ DRM_BRIDGE_OP_EDID |
+ DRM_BRIDGE_OP_HPD |
+ DRM_BRIDGE_OP_DP_AUDIO;
+ bridge->of_node = dp->dev->of_node;
+ bridge->type = DRM_MODE_CONNECTOR_DisplayPort;
+ bridge->hdmi_audio_dev = dp->dev;
+ bridge->hdmi_audio_max_i2s_playback_channels = 8;
+ bridge->hdmi_audio_spdif_playback = 1;
+ bridge->hdmi_audio_dai_port = -1;
+
+ ret = devm_drm_bridge_add(dev, bridge);
if (ret)
return ret;
- ret = drm_bridge_attach(encoder, &dp->bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+ ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret)
return ret;
+ if (next_bridge) {
+ ret = drm_bridge_attach(encoder, next_bridge, bridge,
+ DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+ if (ret)
+ return ret;
+
+ last_bridge = drm_bridge_chain_get_last_bridge(bridge->encoder);
+ }
+
connector = drm_bridge_connector_init(drm_dev, encoder);
if (IS_ERR(connector)) {
ret = PTR_ERR(connector);
@@ -1030,8 +1145,99 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
return ret;
}
+ if (last_bridge)
+ connector->fwnode = fwnode_handle_get(of_fwnode_handle(last_bridge->of_node));
+
drm_connector_attach_encoder(connector, encoder);
+ return 0;
+}
+
+static int cdn_dp_parse_next_bridge_dt(struct cdn_dp_device *dp)
+{
+ struct device_node *np = dp->dev->of_node;
+ struct device_node *port __free(device_node) = of_graph_get_port_by_id(np, 1);
+ struct drm_bridge *bridge;
+ int count = 0;
+ int ret = 0;
+ int i;
+
+ /* If device use extcon, do not use hpd bridge */
+ for (i = 0; i < dp->ports; i++) {
+ if (dp->port[i]->extcon) {
+ dp->bridge_count = 1;
+ return 0;
+ }
+ }
+
+ /* One endpoint may correspond to one next bridge. */
+ for_each_of_graph_port_endpoint(port, dp_ep) {
+ struct device_node *next_bridge_node __free(device_node) =
+ of_graph_get_remote_port_parent(dp_ep);
+
+ bridge = of_drm_find_bridge(next_bridge_node);
+ if (!bridge) {
+ ret = -EPROBE_DEFER;
+ goto out;
+ }
+
+ dp->next_bridge_valid = true;
+ dp->next_bridge_list[count] = drm_bridge_get(bridge);
+ count++;
+ }
+
+out:
+ dp->bridge_count = count ? count : 1;
+ return ret;
+}
+
+static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
+{
+ struct cdn_dp_device *dp = dev_get_drvdata(dev);
+ struct drm_bridge *bridge, *next_bridge;
+ struct drm_encoder *encoder;
+ struct cdn_dp_port *port;
+ struct drm_device *drm_dev = data;
+ struct cdn_dp_bridge *dp_bridge;
+ int ret, i;
+
+ ret = cdn_dp_parse_dt(dp);
+ if (ret < 0)
+ return ret;
+
+ ret = cdn_dp_parse_next_bridge_dt(dp);
+ if (ret)
+ return ret;
+
+ dp->drm_dev = drm_dev;
+ dp->connected = false;
+ dp->active = false;
+ dp->active_port = -1;
+ dp->fw_loaded = false;
+
+ for (i = 0; i < dp->bridge_count; i++) {
+ dp_bridge = devm_drm_bridge_alloc(dev, struct cdn_dp_bridge, bridge,
+ &cdn_dp_bridge_funcs);
+ if (IS_ERR(dp_bridge))
+ return PTR_ERR(dp_bridge);
+ dp_bridge->id = i;
+ dp_bridge->parent = dp;
+ if (!dp->next_bridge_valid)
+ dp_bridge->connected = true;
+ dp->bridge_list[i] = dp_bridge;
+ }
+
+ for (i = 0; i < dp->bridge_count; i++) {
+ encoder = &dp->bridge_list[i]->encoder.encoder;
+ bridge = &dp->bridge_list[i]->bridge;
+ next_bridge = dp->next_bridge_list[i];
+ ret = cdn_bridge_add(dev, bridge, next_bridge, encoder);
+ if (ret)
+ return ret;
+ }
+
+ INIT_WORK(&dp->event_work, cdn_dp_pd_event_work);
+
for (i = 0; i < dp->ports; i++) {
port = dp->port[i];
@@ -1059,10 +1265,18 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
static void cdn_dp_unbind(struct device *dev, struct device *master, void *data)
{
struct cdn_dp_device *dp = dev_get_drvdata(dev);
- struct drm_encoder *encoder = &dp->encoder.encoder;
+ struct drm_encoder *encoder;
+ int i;
cancel_work_sync(&dp->event_work);
- encoder->funcs->destroy(encoder);
+ for (i = 0; i < dp->bridge_count; i++) {
+ encoder = &dp->bridge_list[i]->encoder.encoder;
+ encoder->funcs->destroy(encoder);
+ drm_bridge_put(dp->next_bridge_list[i]);
+ }
+
+ for (i = 0; i < MAX_PHY; i++)
+ drm_edid_free(dp->edid_cache[i]);
pm_runtime_disable(dev);
if (dp->fw_loaded)
@@ -1113,10 +1327,10 @@ static int cdn_dp_probe(struct platform_device *pdev)
int ret;
int i;
- dp = devm_drm_bridge_alloc(dev, struct cdn_dp_device, bridge,
- &cdn_dp_bridge_funcs);
- if (IS_ERR(dp))
- return PTR_ERR(dp);
+ dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
+ if (!dp)
+ return -ENOMEM;
+
dp->dev = dev;
match = of_match_node(cdn_dp_dt_ids, pdev->dev.of_node);
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.h b/drivers/gpu/drm/rockchip/cdn-dp-core.h
index e9c30b9fd543..c10e423bbf06 100644
--- a/drivers/gpu/drm/rockchip/cdn-dp-core.h
+++ b/drivers/gpu/drm/rockchip/cdn-dp-core.h
@@ -38,6 +38,8 @@ enum vic_pxl_encoding_format {
Y_ONLY = 0x10,
};
+struct cdn_dp_device;
+
struct video_info {
bool h_sync_polarity;
bool v_sync_polarity;
@@ -63,16 +65,28 @@ struct cdn_dp_port {
u8 id;
};
+struct cdn_dp_bridge {
+ struct cdn_dp_device *parent;
+ struct drm_bridge bridge;
+ struct rockchip_encoder encoder;
+ bool connected;
+ bool enabled;
+ int id;
+};
+
struct cdn_dp_device {
struct device *dev;
struct drm_device *drm_dev;
- struct drm_bridge bridge;
- struct rockchip_encoder encoder;
+ int bridge_count;
+ struct cdn_dp_bridge *bridge_list[MAX_PHY];
+ struct drm_bridge *next_bridge_list[MAX_PHY];
+ const struct drm_edid *edid_cache[MAX_PHY];
struct drm_display_mode mode;
struct platform_device *audio_pdev;
struct work_struct event_work;
struct mutex lock;
+ bool next_bridge_valid;
bool connected;
bool active;
bool suspended;
--
2.53.0
^ permalink raw reply related
* [PATCH 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge
From: Chaoyi Chen @ 2026-05-21 3:28 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang,
Heiko Stübner, Andy Yan, Vinod Koul
Cc: Heikki Krogerus, Dmitry Baryshkov, Luca Ceresoli, linux-kernel,
dri-devel, linux-arm-kernel, linux-rockchip, linux-phy,
Chaoyi Chen
In-Reply-To: <20260521032854.103-1-kernel@airkyi.com>
From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
Using the DRM_AUX_BRIDGE helper to create the transparent DRM bridge
device.
Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
---
drivers/phy/rockchip/Kconfig | 2 ++
drivers/phy/rockchip/phy-rockchip-typec.c | 13 +++++++++++--
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig
index 14698571b607..9173d3b4fef4 100644
--- a/drivers/phy/rockchip/Kconfig
+++ b/drivers/phy/rockchip/Kconfig
@@ -119,6 +119,8 @@ config PHY_ROCKCHIP_SNPS_PCIE3
config PHY_ROCKCHIP_TYPEC
tristate "Rockchip TYPEC PHY Driver"
depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST)
+ depends on DRM || DRM=n
+ select DRM_AUX_BRIDGE if DRM_BRIDGE
select EXTCON
select GENERIC_PHY
select RESET_CONTROLLER
diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c
index d9701b6106d5..48070b50416e 100644
--- a/drivers/phy/rockchip/phy-rockchip-typec.c
+++ b/drivers/phy/rockchip/phy-rockchip-typec.c
@@ -54,6 +54,7 @@
#include <linux/mfd/syscon.h>
#include <linux/phy/phy.h>
+#include <drm/bridge/aux-bridge.h>
#define CMN_SSM_BANDGAP (0x21 << 2)
#define CMN_SSM_BIAS (0x22 << 2)
@@ -1162,16 +1163,24 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev)
for_each_available_child_of_node(np, child_np) {
struct phy *phy;
+ ret = 0;
- if (of_node_name_eq(child_np, "dp-port"))
+ if (of_node_name_eq(child_np, "dp-port")) {
phy = devm_phy_create(dev, child_np,
&rockchip_dp_phy_ops);
- else if (of_node_name_eq(child_np, "usb3-port"))
+ ret = drm_aux_bridge_register_from_node(dev, child_np);
+ } else if (of_node_name_eq(child_np, "usb3-port"))
phy = devm_phy_create(dev, child_np,
&rockchip_usb3_phy_ops);
else
continue;
+ if (ret) {
+ pm_runtime_disable(dev);
+ of_node_put(child_np);
+ return ret;
+ }
+
if (IS_ERR(phy)) {
dev_err(dev, "failed to create phy: %pOFn\n",
child_np);
--
2.53.0
^ permalink raw reply related
* [PATCH 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon
From: Chaoyi Chen @ 2026-05-21 3:28 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang,
Heiko Stübner, Andy Yan, Vinod Koul
Cc: Heikki Krogerus, Dmitry Baryshkov, Luca Ceresoli, linux-kernel,
dri-devel, linux-arm-kernel, linux-rockchip, linux-phy,
Chaoyi Chen
In-Reply-To: <20260521032854.103-1-kernel@airkyi.com>
From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
This patch add support for get PHY lane info without help of extcon.
There is no extcon needed if the Type-C controller is present. In this
case, the lane info can be get from PHY instead of extcon.
The extcon device should still be supported if Type-C controller is
not present.
Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
Reviewed-by: Heiko Stuebner <heiko@sntech.de>
---
drivers/gpu/drm/rockchip/cdn-dp-core.c | 25 +++++++++++++++++--------
1 file changed, 17 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c
index 177e30445ee8..9068118859e2 100644
--- a/drivers/gpu/drm/rockchip/cdn-dp-core.c
+++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c
@@ -157,6 +157,9 @@ static int cdn_dp_get_port_lanes(struct cdn_dp_port *port)
int dptx;
u8 lanes;
+ if (!edev)
+ return phy_get_bus_width(port->phy);
+
dptx = extcon_get_state(edev, EXTCON_DISP_DP);
if (dptx > 0) {
extcon_get_property(edev, EXTCON_DISP_DP,
@@ -220,7 +223,7 @@ static bool cdn_dp_check_sink_connection(struct cdn_dp_device *dp)
* some docks need more time to power up.
*/
while (time_before(jiffies, timeout)) {
- if (!extcon_get_state(port->extcon, EXTCON_DISP_DP))
+ if (port->extcon && !extcon_get_state(port->extcon, EXTCON_DISP_DP))
return false;
if (!cdn_dp_get_sink_count(dp, &sink_count))
@@ -386,11 +389,14 @@ static int cdn_dp_enable_phy(struct cdn_dp_device *dp, struct cdn_dp_port *port)
goto err_power_on;
}
- ret = extcon_get_property(port->extcon, EXTCON_DISP_DP,
- EXTCON_PROP_USB_TYPEC_POLARITY, &property);
- if (ret) {
- DRM_DEV_ERROR(dp->dev, "get property failed\n");
- goto err_power_on;
+ property.intval = 0;
+ if (port->extcon) {
+ ret = extcon_get_property(port->extcon, EXTCON_DISP_DP,
+ EXTCON_PROP_USB_TYPEC_POLARITY, &property);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "get property failed\n");
+ goto err_power_on;
+ }
}
port->lanes = cdn_dp_get_port_lanes(port);
@@ -1029,6 +1035,9 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
for (i = 0; i < dp->ports; i++) {
port = dp->port[i];
+ if (!port->extcon)
+ continue;
+
port->event_nb.notifier_call = cdn_dp_pd_event;
ret = devm_extcon_register_notifier(dp->dev, port->extcon,
EXTCON_DISP_DP,
@@ -1121,14 +1130,14 @@ static int cdn_dp_probe(struct platform_device *pdev)
PTR_ERR(phy) == -EPROBE_DEFER)
return -EPROBE_DEFER;
- if (IS_ERR(extcon) || IS_ERR(phy))
+ if (IS_ERR(phy) || PTR_ERR(extcon) != -ENODEV)
continue;
port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
if (!port)
return -ENOMEM;
- port->extcon = extcon;
+ port->extcon = IS_ERR(extcon) ? NULL : extcon;
port->phy = phy;
port->dp = dp;
port->id = i;
--
2.53.0
^ permalink raw reply related
* [soc:soc/drivers] BUILD SUCCESS b1700f8d6c8031948e2b898d2c839dfabe0ba68e
From: kernel test robot @ 2026-05-21 3:22 UTC (permalink / raw)
To: Arnd Bergmann; +Cc: linux-arm-kernel, arm
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc.git soc/drivers
branch HEAD: b1700f8d6c8031948e2b898d2c839dfabe0ba68e Merge tag 'renesas-drivers-for-v7.2-tag1' of git://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-devel into soc/drivers
elapsed time: 759m
configs tested: 227
configs skipped: 3
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
alpha defconfig gcc-15.2.0
arc allmodconfig clang-16
arc allmodconfig gcc-15.2.0
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-23
arc allyesconfig gcc-15.2.0
arc defconfig gcc-15.2.0
arc randconfig-001-20260521 gcc-8.5.0
arc randconfig-002-20260521 gcc-8.5.0
arm allnoconfig clang-23
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm allyesconfig gcc-15.2.0
arm defconfig gcc-15.2.0
arm randconfig-001-20260521 gcc-8.5.0
arm randconfig-002-20260521 gcc-8.5.0
arm randconfig-003-20260521 gcc-8.5.0
arm randconfig-004-20260521 gcc-8.5.0
arm64 allmodconfig clang-19
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-15.2.0
arm64 defconfig gcc-15.2.0
arm64 randconfig-001-20260521 gcc-8.5.0
arm64 randconfig-002-20260521 gcc-8.5.0
arm64 randconfig-003-20260521 gcc-8.5.0
arm64 randconfig-004-20260521 gcc-8.5.0
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky defconfig gcc-15.2.0
csky randconfig-001-20260521 gcc-8.5.0
csky randconfig-002-20260521 gcc-8.5.0
hexagon allmodconfig clang-17
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig clang-23
hexagon allnoconfig gcc-15.2.0
hexagon defconfig gcc-15.2.0
hexagon randconfig-001-20260521 gcc-11.5.0
hexagon randconfig-002-20260521 gcc-11.5.0
i386 allmodconfig clang-20
i386 allmodconfig gcc-14
i386 allnoconfig gcc-14
i386 allnoconfig gcc-15.2.0
i386 allyesconfig clang-20
i386 buildonly-randconfig-001-20260521 clang-20
i386 buildonly-randconfig-002-20260521 clang-20
i386 buildonly-randconfig-003-20260521 clang-20
i386 buildonly-randconfig-004-20260521 clang-20
i386 buildonly-randconfig-005-20260521 clang-20
i386 buildonly-randconfig-006-20260521 clang-20
i386 defconfig gcc-15.2.0
i386 randconfig-001-20260521 clang-20
i386 randconfig-002-20260521 clang-20
i386 randconfig-002-20260521 gcc-14
i386 randconfig-003-20260521 clang-20
i386 randconfig-004-20260521 clang-20
i386 randconfig-004-20260521 gcc-14
i386 randconfig-005-20260521 clang-20
i386 randconfig-006-20260521 clang-20
i386 randconfig-007-20260521 clang-20
i386 randconfig-011-20260521 gcc-14
i386 randconfig-012-20260521 gcc-14
i386 randconfig-013-20260521 gcc-14
i386 randconfig-014-20260521 gcc-14
i386 randconfig-015-20260521 gcc-14
i386 randconfig-016-20260521 gcc-14
i386 randconfig-017-20260521 gcc-14
loongarch allmodconfig clang-19
loongarch allmodconfig clang-23
loongarch allnoconfig clang-23
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001-20260521 gcc-11.5.0
loongarch randconfig-002-20260521 gcc-11.5.0
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig clang-16
m68k allyesconfig gcc-15.2.0
m68k defconfig clang-19
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
mips cavium_octeon_defconfig gcc-15.2.0
mips rt305x_defconfig clang-23
nios2 allmodconfig clang-23
nios2 allmodconfig gcc-11.5.0
nios2 allnoconfig clang-23
nios2 allnoconfig gcc-11.5.0
nios2 defconfig clang-19
nios2 randconfig-001-20260521 gcc-11.5.0
nios2 randconfig-002-20260521 gcc-11.5.0
openrisc allmodconfig clang-23
openrisc allmodconfig gcc-15.2.0
openrisc allnoconfig clang-23
openrisc allnoconfig gcc-15.2.0
openrisc defconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc allnoconfig gcc-15.2.0
parisc allyesconfig clang-19
parisc allyesconfig gcc-15.2.0
parisc defconfig gcc-15.2.0
parisc randconfig-001-20260521 gcc-12.5.0
parisc randconfig-002-20260521 gcc-12.5.0
parisc64 defconfig clang-19
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-23
powerpc allnoconfig gcc-15.2.0
powerpc mgcoge_defconfig clang-23
powerpc randconfig-001-20260521 gcc-12.5.0
powerpc randconfig-002-20260521 gcc-12.5.0
powerpc stx_gp3_defconfig gcc-15.2.0
powerpc64 randconfig-001-20260521 gcc-12.5.0
powerpc64 randconfig-002-20260521 gcc-12.5.0
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allnoconfig gcc-15.2.0
riscv allyesconfig clang-16
riscv defconfig gcc-15.2.0
riscv randconfig-001 gcc-15.2.0
riscv randconfig-001-20260521 gcc-15.2.0
riscv randconfig-002 gcc-15.2.0
riscv randconfig-002-20260521 gcc-15.2.0
s390 allmodconfig clang-18
s390 allmodconfig clang-19
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
s390 debug_defconfig gcc-15.2.0
s390 defconfig gcc-15.2.0
s390 randconfig-001 gcc-15.2.0
s390 randconfig-001-20260521 gcc-15.2.0
s390 randconfig-002 gcc-15.2.0
s390 randconfig-002-20260521 gcc-15.2.0
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh allnoconfig gcc-15.2.0
sh allyesconfig clang-19
sh allyesconfig gcc-15.2.0
sh defconfig gcc-14
sh randconfig-001 gcc-15.2.0
sh randconfig-001-20260521 gcc-15.2.0
sh randconfig-002 gcc-15.2.0
sh randconfig-002-20260521 gcc-15.2.0
sparc allnoconfig clang-23
sparc allnoconfig gcc-15.2.0
sparc defconfig gcc-15.2.0
sparc randconfig-001-20260520 gcc-12.5.0
sparc randconfig-001-20260521 gcc-8.5.0
sparc randconfig-002-20260520 gcc-14.3.0
sparc randconfig-002-20260521 gcc-8.5.0
sparc64 allmodconfig clang-23
sparc64 defconfig gcc-14
sparc64 randconfig-001-20260520 gcc-13.4.0
sparc64 randconfig-001-20260521 gcc-8.5.0
sparc64 randconfig-002-20260520 gcc-13.4.0
sparc64 randconfig-002-20260521 gcc-8.5.0
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-14
um allyesconfig gcc-15.2.0
um defconfig gcc-14
um i386_defconfig gcc-14
um randconfig-001-20260520 clang-23
um randconfig-001-20260521 gcc-8.5.0
um randconfig-002-20260520 gcc-14
um randconfig-002-20260521 gcc-8.5.0
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-20
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001 gcc-12
x86_64 buildonly-randconfig-001-20260520 clang-20
x86_64 buildonly-randconfig-001-20260521 clang-20
x86_64 buildonly-randconfig-002 clang-20
x86_64 buildonly-randconfig-002-20260520 clang-20
x86_64 buildonly-randconfig-002-20260521 clang-20
x86_64 buildonly-randconfig-003 gcc-14
x86_64 buildonly-randconfig-003-20260520 gcc-14
x86_64 buildonly-randconfig-003-20260521 clang-20
x86_64 buildonly-randconfig-004 gcc-14
x86_64 buildonly-randconfig-004-20260520 gcc-14
x86_64 buildonly-randconfig-004-20260521 clang-20
x86_64 buildonly-randconfig-005 gcc-14
x86_64 buildonly-randconfig-005-20260520 gcc-14
x86_64 buildonly-randconfig-005-20260521 clang-20
x86_64 buildonly-randconfig-006 clang-20
x86_64 buildonly-randconfig-006-20260520 gcc-14
x86_64 buildonly-randconfig-006-20260521 clang-20
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-001-20260521 clang-20
x86_64 randconfig-002-20260521 clang-20
x86_64 randconfig-003-20260521 clang-20
x86_64 randconfig-004-20260521 clang-20
x86_64 randconfig-005-20260521 clang-20
x86_64 randconfig-006-20260521 clang-20
x86_64 randconfig-011-20260521 gcc-14
x86_64 randconfig-012-20260521 gcc-14
x86_64 randconfig-013-20260521 gcc-14
x86_64 randconfig-014-20260521 gcc-14
x86_64 randconfig-015-20260521 gcc-14
x86_64 randconfig-016-20260521 gcc-14
x86_64 randconfig-071-20260521 clang-20
x86_64 randconfig-072-20260521 clang-20
x86_64 randconfig-073-20260521 clang-20
x86_64 randconfig-074-20260521 clang-20
x86_64 randconfig-075-20260521 clang-20
x86_64 randconfig-076-20260521 clang-20
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig clang-23
xtensa allnoconfig gcc-15.2.0
xtensa allyesconfig clang-23
xtensa randconfig-001-20260520 gcc-8.5.0
xtensa randconfig-001-20260521 gcc-8.5.0
xtensa randconfig-002-20260520 gcc-11.5.0
xtensa randconfig-002-20260521 gcc-8.5.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* RE: [PATCH V3 0/8] PCI: imx6: Integrate pwrctrl API and update device trees
From: Hongxing Zhu (OSS) @ 2026-05-21 3:37 UTC (permalink / raw)
To: Sherry Sun (OSS), robh@kernel.org, krzk+dt@kernel.org,
conor+dt@kernel.org, Frank Li, s.hauer@pengutronix.de,
kernel@pengutronix.de, festevam@gmail.com, lpieralisi@kernel.org,
kwilczynski@kernel.org, mani@kernel.org, bhelgaas@google.com,
l.stach@pengutronix.de
Cc: imx@lists.linux.dev, linux-pci@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, Sherry Sun
In-Reply-To: <20260520084904.2424253-1-sherry.sun@oss.nxp.com>
> -----Original Message-----
> From: Sherry Sun (OSS) <sherry.sun@oss.nxp.com>
> Sent: Wednesday, May 20, 2026 4:49 PM
> To: robh@kernel.org; krzk+dt@kernel.org; conor+dt@kernel.org; Frank Li
> <frank.li@nxp.com>; s.hauer@pengutronix.de; kernel@pengutronix.de;
> festevam@gmail.com; lpieralisi@kernel.org; kwilczynski@kernel.org;
> mani@kernel.org; bhelgaas@google.com; Hongxing Zhu
> <hongxing.zhu@nxp.com>; l.stach@pengutronix.de
> Cc: imx@lists.linux.dev; linux-pci@vger.kernel.org; linux-arm-
> kernel@lists.infradead.org; devicetree@vger.kernel.org; linux-
> kernel@vger.kernel.org; Sherry Sun <sherry.sun@nxp.com>
> Subject: [PATCH V3 0/8] PCI: imx6: Integrate pwrctrl API and update device trees
>
> From: Sherry Sun <sherry.sun@nxp.com>
>
> This series integrates the PCI pwrctrl framework into the pci-imx6 driver and
> updates i.MX EVK board device trees to support it.
>
> Patches 2-8 update device trees for i.MX EVK boards which maintained by NXP to
> move power supply properties from the PCIe controller node to the Root Port
> child node, which is required for pwrctrl framework.
> Affected boards:
> - i.MX6Q/DL SABRESD
> - i.MX6SX SDB
> - i.MX8MM EVK
> - i.MX8MP EVK
> - i.MX8MQ EVK
> - i.MX8DXL/QM/QXP EVK
> - i.MX95 15x15/19x19 EVK
>
> The driver maintains legacy regulator handling for device trees that haven't been
> updated yet. Both old and new device tree structures are supported.
>
> Signed-off-by: Sherry Sun <sherry.sun@nxp.com>
Hi Sherry:
Since the vpcie3v3aux is used to power up the WAKE#, it is always on in this
pwrctrl framework whatever the system is in suspend or not, right?
Best Regards
Richard Zhu
> ---
> Changes in V3:
> 1. Rebased on top of latest 7.1.0-rc4
>
> Changes in V2:
> 1. After commit 2d8c5098b847 ("PCI/pwrctrl: Do not power off on pwrctrl
> device removal"), the pwrctrl drivers no longer power off devices
> during removal. Update pci-imx6 driver's shutdown callback in patch#1
> to explicitly call pci_pwrctrl_power_off_devices() before
> pci_pwrctrl_destroy_devices() to ensure devices are properly powered
> off.
> ---
>
> Sherry Sun (8):
> PCI: imx6: Integrate new pwrctrl API for pci-imx6
> arm: dts: imx6qdl-sabresd: Move power supply property to Root Port
> node
> arm: dts: imx6sx-sdb: Move power supply property to Root Port node
> arm64: dts: imx8mm-evk: Move power supply property to Root Port node
> arm64: dts: imx8mp-evk: Move power supply properties to Root Port node
> arm64: dts: imx8mq-evk: Move power supply properties to Root Port node
> arm64: dts: imx8dxl/qm/qxp: Move power supply properties to Root Port
> node
> arm64: dts: imx95: Move power supply properties to Root Port node
>
> .../arm/boot/dts/nxp/imx/imx6qdl-sabresd.dtsi | 2 +-
> arch/arm/boot/dts/nxp/imx/imx6sx-sdb.dtsi | 2 +-
> arch/arm64/boot/dts/freescale/imx8dxl-evk.dts | 4 ++--
> arch/arm64/boot/dts/freescale/imx8mm-evk.dtsi | 2 +-
> arch/arm64/boot/dts/freescale/imx8mp-evk.dts | 4 ++--
> arch/arm64/boot/dts/freescale/imx8mq-evk.dts | 4 ++--
> arch/arm64/boot/dts/freescale/imx8qm-mek.dts | 4 ++--
> arch/arm64/boot/dts/freescale/imx8qxp-mek.dts | 4 ++--
> .../boot/dts/freescale/imx95-15x15-evk.dts | 4 ++--
> .../boot/dts/freescale/imx95-19x19-evk.dts | 8 +++----
> drivers/pci/controller/dwc/Kconfig | 1 +
> drivers/pci/controller/dwc/pci-imx6.c | 24 ++++++++++++++++++-
> 12 files changed, 43 insertions(+), 20 deletions(-)
>
> --
> 2.37.1
^ permalink raw reply
* Re: [RFC V2 01/14] mm: Abstract printing of pxd_val()
From: Anshuman Khandual @ 2026-05-21 3:43 UTC (permalink / raw)
To: David Hildenbrand (Arm), Dave Hansen, linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ryan Roberts, Mark Rutland,
Lorenzo Stoakes, Andrew Morton, Mike Rapoport, Linu Cherian,
Usama Arif, linux-kernel, linux-mm
In-Reply-To: <b976ec4e-d5cb-4856-885d-dd301b3ddd83@kernel.org>
On 20/05/26 4:11 PM, David Hildenbrand (Arm) wrote:
> On 5/19/26 16:28, Dave Hansen wrote:
>> On 5/12/26 21:45, Anshuman Khandual wrote:
>>> if (!p4d_present(p4d) || p4d_leaf(p4d)) {
>>> - pr_alert("pgd:%08llx p4d:%08llx\n", pgdv, p4dv);
>>> + pr_alert("pgd:%" __PRIpxx " p4d:%" __PRIpxx "\n",
>>> + __PRIpxx_args(pgdv), __PRIpxx_args(p4dv));
>>> return;
>>> }
>>
>> That's not the most readable result. Could a printk() format specifier
>> make this nicer? Maybe use "%pT"?
>>
>> pr_alert("pgd:%pT p4d:%pT\n", &pgd, &p4d);
>>
>> I _think_ it could even get rid of the p??v variables.
>
> That would be nicer indeed, if that works.
I had attempted something similar earlier.
https://lore.kernel.org/all/20250618041235.1716143-1-anshuman.khandual@arm.com/
But current proposal was to solve the problem with minimum possible churn
in generic MM to handle 128 bit page table entries for its value printing
purpose.
Please find an example WIP patch in this regard (tested very lightly). Please do
let me know if this is in the right direction and should be followed up instead.
Although special_hex_number() might have to support 128 bit values.
=====================================
diff --git a/Documentation/core-api/printk-formats.rst b/Documentation/core-api/printk-formats.rst
index c0b1b6089307..e69f91a9dd9d 100644
--- a/Documentation/core-api/printk-formats.rst
+++ b/Documentation/core-api/printk-formats.rst
@@ -696,6 +696,25 @@ Rust
Only intended to be used from Rust code to format ``core::fmt::Arguments``.
Do *not* use it from C.
+Page Table Entry
+----------------
+
+::
+
+ %p[pgd|p4dp|pud|pmd|pte]
+
+Print page table entry at any level.
+
+Passed by reference.
+
+Examples for a 64 bit page table entry, given &(u64)0xc0ffee::
+
+ %ppte 0x0000000000c0ffee
+ %ppmd 0x0000000000c0ffee
+ %ppud 0x0000000000c0ffee
+ %pp4d 0x0000000000c0ffee
+ %ppgd 0x0000000000c0ffee
+
Thanks
======
diff --git a/lib/tests/printf_kunit.c b/lib/tests/printf_kunit.c
index bb70b9cddadd..ab7f55499eb7 100644
--- a/lib/tests/printf_kunit.c
+++ b/lib/tests/printf_kunit.c
@@ -791,6 +791,73 @@ errptr(struct kunit *kunittest)
#endif
}
+struct pxd_test {
+ u64 val;
+ const char *name;
+};
+
+static struct pxd_test pxd_test_cases[] = {
+ { .val = 0xc0ffee, .name = "0x0000000000c0ffee"},
+ { .val = 0xdeadbeef, .name = "0x00000000deadbeef"},
+ { .val = 0xaabbcc, .name = "0x0000000000aabbcc"},
+ { .val = 0xcc, .name = "0x00000000000000cc"},
+ { .val = 0x1, .name = "0x0000000000000001"},
+ { .val = 0x11, .name = "0x0000000000000011"},
+ { .val = 0x111, .name = "0x0000000000000111"},
+ { .val = 0x10000010001, .name = "0x0000010000010001"},
+ { .val = 0xc0ffeec0ffee, .name = "0x0000c0ffeec0ffee"},
+ { .val = 0x10000000000, .name = "0x0000010000000000"},
+ { .val = 0x11000000000, .name = "0x0000011000000000"},
+ { .val = 0x1000000000000000, .name = "0x1000000000000000"},
+ { .val = 0x1100000000000000, .name = "0x1100000000000000"},
+ { .val = 0x1110000000000000, .name = "0x1110000000000000"},
+};
+
+static void
+pxd(struct kunit *kunittest)
+{
+ char buf[64];
+ int i;
+
+ if (sizeof(pte_t) != 8)
+ kunit_skip(kunittest, "pte_t size is not 64 bits");
+
+ for (i = 0; i < ARRAY_SIZE(pxd_test_cases); i++) {
+ pte_t pte = __pte(pxd_test_cases[i].val);
+
+ snprintf(buf, sizeof(buf), "%ppte", &pte);
+ KUNIT_EXPECT_STREQ(kunittest, buf, pxd_test_cases[i].name);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(pxd_test_cases); i++) {
+ pmd_t pmd = __pmd(pxd_test_cases[i].val);
+
+ snprintf(buf, sizeof(buf), "%ppmd", &pmd);
+ KUNIT_EXPECT_STREQ(kunittest, buf, pxd_test_cases[i].name);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(pxd_test_cases); i++) {
+ pud_t pud = __pud(pxd_test_cases[i].val);
+
+ snprintf(buf, sizeof(buf), "%ppud", &pud);
+ KUNIT_EXPECT_STREQ(kunittest, buf, pxd_test_cases[i].name);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(pxd_test_cases); i++) {
+ p4d_t p4d = __p4d(pxd_test_cases[i].val);
+
+ snprintf(buf, sizeof(buf), "%pp4d", &p4d);
+ KUNIT_EXPECT_STREQ(kunittest, buf, pxd_test_cases[i].name);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(pxd_test_cases); i++) {
+ pgd_t pgd = __pgd(pxd_test_cases[i].val);
+
+ snprintf(buf, sizeof(buf), "%ppgd", &pgd);
+ KUNIT_EXPECT_STREQ(kunittest, buf, pxd_test_cases[i].name);
+ }
+}
+
static int printf_suite_init(struct kunit_suite *suite)
{
total_tests = 0;
@@ -839,6 +906,7 @@ static struct kunit_case printf_test_cases[] = {
KUNIT_CASE(errptr),
KUNIT_CASE(fwnode_pointer),
KUNIT_CASE(fourcc_pointer),
+ KUNIT_CASE(pxd),
{}
};
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 9f359b31c8d1..937499c51ecd 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -856,6 +856,51 @@ static char *default_pointer(char *buf, char *end, const void *ptr,
return ptr_to_id(buf, end, ptr, spec);
}
+static char *pxd_pointer(char *buf, char *end, const void *ptr,
+ struct printf_spec spec, const char *fmt)
+{
+
+ if (check_pointer(&buf, end, ptr, spec))
+ return buf;
+
+ static_assert((sizeof(pte_t) == 4) || (sizeof(pte_t) == 8));
+ static_assert((sizeof(pmd_t) == 4) || (sizeof(pmd_t) == 8));
+ static_assert((sizeof(pud_t) == 4) || (sizeof(pud_t) == 8));
+ static_assert((sizeof(p4d_t) == 4) || (sizeof(p4d_t) == 8));
+ static_assert((sizeof(pgd_t) == 4) || (sizeof(pgd_t) == 8));
+
+ if (fmt[1] == 't' && fmt[2] == 'e') {
+ pte_t *pte = (pte_t *)ptr;
+
+ return special_hex_number(buf, end, pte_val(*pte), sizeof(pte_t));
+ }
+
+ if (fmt[1] == 'm' && fmt[2] == 'd') {
+ pmd_t *pmd = (pmd_t *)ptr;
+
+ return special_hex_number(buf, end, pmd_val(*pmd), sizeof(pmd_t));
+ }
+
+ if (fmt[1] == 'u' && fmt[2] == 'd') {
+ pud_t *pud = (pud_t *)ptr;
+
+ return special_hex_number(buf, end, pud_val(*pud), sizeof(pud_t));
+ }
+
+ if (fmt[1] == '4' && fmt[2] == 'd') {
+ p4d_t *p4d = (p4d_t *)ptr;
+
+ return special_hex_number(buf, end, p4d_val(*p4d), sizeof(p4d_t));
+ }
+
+ if (fmt[1] == 'g' && fmt[2] == 'd') {
+ pgd_t *pgd = (pgd_t *)ptr;
+
+ return special_hex_number(buf, end, pgd_val(*pgd), sizeof(pgd_t));
+ }
+ return default_pointer(buf, end, ptr, spec);
+}
+
int kptr_restrict __read_mostly;
static noinline_for_stack
@@ -2506,6 +2551,9 @@ early_param("no_hash_pointers", no_hash_pointers_enable);
* Without an option prints the full name of the node
* f full name
* P node name, including a possible unit address
+ * - 'p[g|4|u|m|t|][d|e]' For a page table entry, this prints its
+ * contents in a hexadecimal format
+ *
* - 'x' For printing the address unmodified. Equivalent to "%lx".
* Please read the documentation (path below) before using!
* - '[ku]s' For a BPF/tracing related format specifier, e.g. used out of
@@ -2615,6 +2663,8 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr,
default:
return error_string(buf, end, "(einval)", spec);
}
+ case 'p':
+ return pxd_pointer(buf, end, ptr, spec, fmt);
default:
return default_pointer(buf, end, ptr, spec);
}
diff --git a/mm/memory.c b/mm/memory.c
index ea6568571131..838e06cc377d 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -521,7 +521,6 @@ static bool is_bad_page_map_ratelimited(void)
static void __print_bad_page_map_pgtable(struct mm_struct *mm, unsigned long addr)
{
- unsigned long long pgdv, p4dv, pudv, pmdv;
p4d_t p4d, *p4dp;
pud_t pud, *pudp;
pmd_t pmd, *pmdp;
@@ -532,34 +531,30 @@ static void __print_bad_page_map_pgtable(struct mm_struct *mm, unsigned long add
* see locking requirements for print_bad_page_map().
*/
pgdp = pgd_offset(mm, addr);
- pgdv = pgd_val(*pgdp);
if (!pgd_present(*pgdp) || pgd_leaf(*pgdp)) {
- pr_alert("pgd:%08llx\n", pgdv);
+ pr_alert("pgd:%ppgd\n", pgdp);
return;
}
p4dp = p4d_offset(pgdp, addr);
p4d = p4dp_get(p4dp);
- p4dv = p4d_val(p4d);
if (!p4d_present(p4d) || p4d_leaf(p4d)) {
- pr_alert("pgd:%08llx p4d:%08llx\n", pgdv, p4dv);
+ pr_alert("pgd:%ppgd p4d:%pp4d\n", pgdp, p4dp);
return;
}
pudp = pud_offset(p4dp, addr);
pud = pudp_get(pudp);
- pudv = pud_val(pud);
if (!pud_present(pud) || pud_leaf(pud)) {
- pr_alert("pgd:%08llx p4d:%08llx pud:%08llx\n", pgdv, p4dv, pudv);
+ pr_alert("pgd:%ppgd p4d:%pp4d pud:%ppud\n", pgdp, p4dp, pudp);
return;
}
pmdp = pmd_offset(pudp, addr);
pmd = pmdp_get(pmdp);
- pmdv = pmd_val(pmd);
/*
* Dumping the PTE would be nice, but it's tricky with CONFIG_HIGHPTE,
@@ -567,8 +562,8 @@ static void __print_bad_page_map_pgtable(struct mm_struct *mm, unsigned long add
* doing another map would be bad. print_bad_page_map() should
* already take care of printing the PTE.
*/
- pr_alert("pgd:%08llx p4d:%08llx pud:%08llx pmd:%08llx\n", pgdv,
- p4dv, pudv, pmdv);
+ pr_alert("pgd:%ppgd p4d:%pp4d pud:%ppud pmd:%ppmd\n", pgdp,
+ p4dp, pudp, pmdp);
}
/*
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index 0492d6afc9a1..9dd17e501bfa 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -6975,7 +6975,7 @@ sub process {
my $fmt = get_quoted_string($lines[$count - 1], raw_line($count, 0));
$fmt =~ s/%%//g;
- while ($fmt =~ /(\%[\*\d\.]*p(\w)(\w*))/g) {
+ while ($fmt =~ /(\%[\*\d\.]*p(\w)(\w*)(\te)(\md)(\ud)(\4d)(\gd))/g) {
$specifier = $1;
$extension = $2;
$qualifier = $3;
^ permalink raw reply related
* Re: [PATCH 4/8] bpf: Add bpf_struct_ops_for_each_prog()
From: Emil Tsalapatis @ 2026-05-21 4:07 UTC (permalink / raw)
To: Tejun Heo, David Vernet, Andrea Righi, Changwoo Min,
Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Martin KaFai Lau, Kumar Kartikeya Dwivedi
Cc: Peter Zijlstra, Catalin Marinas, Will Deacon, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, Dave Hansen, Andrew Morton,
David Hildenbrand, Mike Rapoport, Emil Tsalapatis, sched-ext, bpf,
x86, linux-arm-kernel, linux-mm, linux-kernel
In-Reply-To: <20260520235052.4180316-5-tj@kernel.org>
On Wed May 20, 2026 at 7:50 PM EDT, Tejun Heo wrote:
> Add a helper that walks the member progs of the struct_ops map
> containing a given @kdata vmtable. struct_ops ->reg() callbacks (and
> similar) sometimes need to inspect the loaded BPF programs, e.g. to
> discover maps they reference via prog->aux->used_maps.
>
> The implementation mirrors bpf_struct_ops_id(): container_of @kdata
> to recover the bpf_struct_ops_map, then iterate st_map->links[i]->prog
> for i in [0, funcs_cnt). Same access pattern, no new locking - by the
> time ->reg() fires st_map is fully populated and stable.
>
> A sched_ext follow-up walks the member progs of a cid-form scheduler's
> struct_ops map, reads prog->aux->arena directly, and requires all member
> progs to reference exactly one arena, without requiring the BPF program
> to call a registration kfunc.
>
> Signed-off-by: Tejun Heo <tj@kernel.org>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
> ---
> include/linux/bpf.h | 3 +++
> kernel/bpf/bpf_struct_ops.c | 36 ++++++++++++++++++++++++++++++++++++
> 2 files changed, 39 insertions(+)
>
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index 64968ca6db51..5b99d786e98c 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -2129,6 +2129,9 @@ int bpf_prog_assoc_struct_ops(struct bpf_prog *prog, struct bpf_map *map);
> void bpf_prog_disassoc_struct_ops(struct bpf_prog *prog);
> void *bpf_prog_get_assoc_struct_ops(const struct bpf_prog_aux *aux);
> u32 bpf_struct_ops_id(const void *kdata);
> +int bpf_struct_ops_for_each_prog(const void *kdata,
> + int (*cb)(struct bpf_prog *prog, void *data),
> + void *data);
>
> #ifdef CONFIG_NET
> /* Define it here to avoid the use of forward declaration */
> diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
> index 05b366b821c3..16aec18ed31b 100644
> --- a/kernel/bpf/bpf_struct_ops.c
> +++ b/kernel/bpf/bpf_struct_ops.c
> @@ -1203,6 +1203,42 @@ u32 bpf_struct_ops_id(const void *kdata)
> }
> EXPORT_SYMBOL_GPL(bpf_struct_ops_id);
>
> +/**
> + * bpf_struct_ops_for_each_prog - Invoke @cb for each member prog
> + * @kdata: kernel-side struct_ops vmtable (the @kdata arg to ->reg/->update/->unreg)
> + * @cb: callback invoked once per member prog; non-zero return stops iteration
> + * @data: opaque argument passed to @cb
> + *
> + * Walks the struct_ops member progs registered on the map containing @kdata.
> + * Intended for use from struct_ops ->reg() callbacks (and similar) that need to
> + * inspect the loaded BPF programs (for example to discover maps they reference
> + * via @prog->aux->used_maps).
> + *
> + * Return 0 if iteration completed, otherwise the first non-zero @cb return.
> + */
> +int bpf_struct_ops_for_each_prog(const void *kdata,
> + int (*cb)(struct bpf_prog *prog, void *data),
> + void *data)
> +{
> + struct bpf_struct_ops_value *kvalue;
> + struct bpf_struct_ops_map *st_map;
> + u32 i;
> + int ret;
> +
> + kvalue = container_of(kdata, struct bpf_struct_ops_value, data);
> + st_map = container_of(kvalue, struct bpf_struct_ops_map, kvalue);
> +
> + for (i = 0; i < st_map->funcs_cnt; i++) {
> + if (!st_map->links[i])
> + continue;
> + ret = cb(st_map->links[i]->prog, data);
> + if (ret)
> + return ret;
> + }
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(bpf_struct_ops_for_each_prog);
> +
> static bool bpf_struct_ops_valid_to_reg(struct bpf_map *map)
> {
> struct bpf_struct_ops_map *st_map = (struct bpf_struct_ops_map *)map;
^ permalink raw reply
* Re: [PATCH 5/8] bpf/arena: Add bpf_arena_map_kern_vm_start() and bpf_prog_arena()
From: Emil Tsalapatis @ 2026-05-21 4:08 UTC (permalink / raw)
To: Tejun Heo, David Vernet, Andrea Righi, Changwoo Min,
Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Martin KaFai Lau, Kumar Kartikeya Dwivedi
Cc: Peter Zijlstra, Catalin Marinas, Will Deacon, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, Dave Hansen, Andrew Morton,
David Hildenbrand, Mike Rapoport, Emil Tsalapatis, sched-ext, bpf,
x86, linux-arm-kernel, linux-mm, linux-kernel
In-Reply-To: <20260520235052.4180316-6-tj@kernel.org>
On Wed May 20, 2026 at 7:50 PM EDT, Tejun Heo wrote:
> struct bpf_arena is opaque to callers outside arena.c. Add two helpers
> for struct_ops subsystems that need to reach into an arena:
>
> bpf_arena_map_kern_vm_start(struct bpf_map *map)
> returns @map's kern_vm_start. A sched_ext follow-up needs this
> to translate kern_va <-> uaddr.
>
> bpf_prog_arena(struct bpf_prog *prog)
> returns the bpf_map of the arena referenced by @prog (NULL if
> @prog references no arena). The verifier enforces at most one
> arena per program. Used by struct_ops callers that auto-discover
> an arena from a member prog and need to take a map reference.
>
> Suggested-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> Signed-off-by: Tejun Heo <tj@kernel.org>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
> ---
> include/linux/bpf.h | 2 ++
> kernel/bpf/arena.c | 26 ++++++++++++++++++++++++++
> 2 files changed, 28 insertions(+)
>
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index 5b99d786e98c..e1ba57c10aaa 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -618,6 +618,8 @@ void bpf_rb_root_free(const struct btf_field *field, void *rb_root,
> struct bpf_spin_lock *spin_lock);
> u64 bpf_arena_get_kern_vm_start(struct bpf_arena *arena);
> u64 bpf_arena_get_user_vm_start(struct bpf_arena *arena);
> +u64 bpf_arena_map_kern_vm_start(struct bpf_map *map);
> +struct bpf_map *bpf_prog_arena(struct bpf_prog *prog);
> int bpf_obj_name_cpy(char *dst, const char *src, unsigned int size);
>
> struct bpf_offload_dev;
> diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
> index a811cf6170fa..51b9ae36feb6 100644
> --- a/kernel/bpf/arena.c
> +++ b/kernel/bpf/arena.c
> @@ -84,6 +84,32 @@ u64 bpf_arena_get_user_vm_start(struct bpf_arena *arena)
> return arena ? arena->user_vm_start : 0;
> }
>
> +/**
> + * bpf_arena_map_kern_vm_start - kern_vm_start lookup by struct bpf_map *
> + * @map: a BPF_MAP_TYPE_ARENA map
> + *
> + * Return @map's kern_vm_start.
> + */
> +u64 bpf_arena_map_kern_vm_start(struct bpf_map *map)
> +{
> + return bpf_arena_get_kern_vm_start(container_of(map, struct bpf_arena, map));
> +}
> +
> +/**
> + * bpf_prog_arena - return the bpf_map of the arena referenced by @prog
> + * @prog: a loaded BPF program
> + *
> + * The verifier enforces at most one arena per program and stores it in
> + * prog->aux->arena. Return that arena's underlying bpf_map, or NULL if
> + * @prog does not reference an arena.
> + */
> +struct bpf_map *bpf_prog_arena(struct bpf_prog *prog)
> +{
> + struct bpf_arena *arena = prog->aux->arena;
> +
> + return arena ? &arena->map : NULL;
> +}
> +
> static long arena_map_peek_elem(struct bpf_map *map, void *value)
> {
> return -EOPNOTSUPP;
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox