linux-arm-kernel.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* Re: [PATCH v4 0/4] arm64: cross-CPU NMI via SDEI
From: Kiryl Shutsemau @ 2026-07-02 13:57 UTC (permalink / raw)
  To: Catalin Marinas
  Cc: Will Deacon, James Morse, Mark Rutland, Marc Zyngier,
	Doug Anderson, Petr Mladek, Thomas Gleixner, Andrew Morton,
	Baoquan He, Puranjay Mohan, Usama Arif, Breno Leitao,
	Julien Thierry, Lecopzer Chen, Sumit Garg, kernel-team, kexec,
	linux-arm-kernel, linux-kernel
In-Reply-To: <akOT3yaYVM6BTm_4@thinkstation>

On Tue, Jun 30, 2026 at 11:04:14AM +0100, Kiryl Shutsemau wrote:
> On Mon, Jun 29, 2026 at 05:53:57PM +0100, Kiryl Shutsemau wrote:
> > On Mon, Jun 29, 2026 at 04:54:18PM +0100, Catalin Marinas wrote:
> > > Have you tried SDEI_EVENT_COMPLETE_AND_RESUME instead? Just COMPLETE
> > > won't return to the kernel. We have sdei_handler_abort() to complete the
> > > event and, hopefully, you can continue with the CPU_OFF. It's a work
> > > around the TF-A non-compliance but I think this is useful even if you
> > > don't issue the CPU_OFF (e.g. no CPU hotplug, just the park loop).
> > 
> > Tried it. The result is the opposite of what I expected, and it argues
> > against doing the complete at all under QEMU's TF-A.
> 
> I have to walk back on this. My test setup was broken. I will be back to
> you with proper data.

Back with proper data. Short version: CPU_OFF from the SDEI stop does not
work on Grace, whether or not the event is completed first, so the series
stays with the park. Details below.

What was broken before
======================
My QEMU kdump harness had three faults that together made CPU_OFF look
like it broke the capture kernel:

  - a race between the sysrq-c crash and the buddy detector -- the script
    that wedges the CPU wasn't pinned off it, so which crash path ran (and
    thus the outcome) was random;
  - the capture kernel's console went quiet at the boot-console handover,
    so "nothing after Bye!" was mostly lost output, not a hang (keep_bootcon
    shows it booting on);
  - the one genuine early stall was the capture kernel's CFI probe of the
    secure NOR flash -- it reproduces with the whole series compiled out
    (CONFIG_ARM_SDEI_NMI=n) and disappears with
    initcall_blacklist=physmap_init, i.e. nothing to do with SDEI.

With those fixed, QEMU/TF-A boots the capture kernel to userspace for both
park and raw CPU_OFF, and CPU_OFF really powers the PE off. But QEMU can't
decide the question that matters: its post-kexec hold-pen can't revive any
CPU_OFF'd core (even a cleanly hotplug-offlined one), so re-onlining is
undecidable there. Hence Grace.

Grace (Neoverse V2, EL3 TF-A)
=============================
  - Series as posted (park): the SDEI rung stops the wedged CPU, its
    context lands in the vmcore, the capture kernel boots. Works.
    Bringing up secondary CPUs complains that it cannot get the CPU up,
    but proceeds.
  - raw CPU_OFF from the uncompleted SDEI event: the capture kernel boots
    but hangs at "smp: Bringing up secondary CPUs ..." -- it cannot
    re-online the CPU that was CPU_OFF'd. This is exactly the TF-A point
    you raised: the SDEI dispatch is left dangling on CPU_OFF and the PE
    won't come back cleanly.
  - complete-then-CPU_OFF: same hang at secondary bring-up. Completing the
    event first does not help.

For that last one I did not use sdei_handler_abort(): it can't express
"complete, then CPU_OFF" from the handler, because its COMPLETE_AND_RESUME
lands on the "1: ret" trampoline, which resumes the *interrupted* (wedged)
context -- so nothing after it runs. I used a COMPLETE_AND_RESUME whose
resume PC is a PSCI CPU_OFF stub, so the event is genuinely completed and
then the PE powers off. It still can't be re-onlined. (I suspect the
"complete-then-CPU_OFF wedged EL3" I mentioned earlier was in fact the
sdei_handler_abort() path resuming the wedged loop, i.e. never doing the
CPU_OFF at all.)

Conclusion
==========
CPU_OFF from the SDEI stop is unusable on current TF-A, with or without
completing the event first -- a concrete real-HW consequence of the
non-compliance you flagged (TF-A subscribes SDEI to PSCI CPU_ON and
suspend-wakeup, but not CPU_OFF). So the series keeps the park: the dump
is complete either way, and only re-onlining the SDEI-stopped CPU in an
SMP capture kernel is lost -- the same fallback the shared stop path
already takes when CPU_OFF is unavailable. Powering the PE off for reuse
is a firmware follow-up (TF-A completing or tearing down the SDEI dispatch
on PSCI CPU_OFF), not something the kernel can paper over.

Please consider applying v5.

-- 
  Kiryl Shutsemau / Kirill A. Shutemov


^ permalink raw reply

* Re: [PATCH net-next v7 3/3] net: airoha: defer GDM3/GDM4 WAN mode and GDM2 loopback to QoS offload
From: Lorenzo Bianconi @ 2026-07-02 14:02 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: Simon Horman, Alexander Lobakin, linux-arm-kernel, linux-mediatek,
	netdev, Madhur Agrawal
In-Reply-To: <20260701-airoha-ethtool-priv_flags-v7-3-b4153bd44428@kernel.org>

[-- Attachment #1: Type: text/plain, Size: 12784 bytes --]

On Jul 01, Lorenzo Bianconi wrote:
> GDM3 and GDM4 ports require GDM2 loopback to be enabled for hardware
> QoS offload to function. Without it, HTB and ETS offload on these ports
> do not work.
> Previously, GDM3/GDM4 ports were automatically configured as WAN with
> GDM2 loopback enabled during ndo_init(). Add the capability to configure
> GDM3/GDM4 as WAN/LAN on demand when QoS offload is created or destroyed.
> Hook airoha_enable_qos_for_gdm34() into TC_HTB_CREATE so that requesting
> HTB offload on a GDM3/GDM4 LAN port switches it to WAN mode and enables
> GDM2 loopback, with proper rollback on failure. Introduce the
> AIROHA_DEV_F_QOS flag to track whether a device has an active HTB
> qdisc; clear it on TC_HTB_DESTROY. The device keeps its WAN role after
> qdisc teardown so that its configuration is preserved until another
> device explicitly needs the WAN role for QoS offload.
> If another GDM3/GDM4 device already holds the WAN role without an active
> QoS qdisc, demote it to LAN before promoting the requesting device. Skip
> the demotion when the requesting device is itself already the WAN device.
> Since airoha_dev_set_qdma() can now be called on a running device to
> migrate between QDMA blocks, make dev->qdma an RCU pointer so the TX
> path can safely dereference it without holding RTNL.
> Hold flow_offload_mutex in airoha_enable_qos_for_gdm34() and
> airoha_disable_qos_for_gdm34() around the dev->flags update,
> airoha_dev_set_qdma() and GDM2 loopback configuration, serializing
> against concurrent airoha_ppe_hw_init() in the TC_SETUP_CLSFLOWER
> offload path.
> Introduce airoha_qdma_deref() helper that wraps rcu_dereference_protected()
> with a lockdep condition accepting either rtnl_lock or flow_offload_mutex,
> and use it across all control-path dereferences of the RCU-protected
> dev->qdma pointer.
> Add airoha_disable_gdm2_loopback() to disable GDM2 hw loopback.
> 
> Tested-by: Madhur Agrawal <madhur.agrawal@airoha.com>
> Reviewed-by: Alexander Lobakin <aleksander.lobakin@intel.com>
> Reviewed-by: Simon Horman <horms@kernel.org>
> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>

Commenting on Sashiko's report:
https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260701-airoha-ethtool-priv_flags-v7-0-b4153bd44428%40kernel.org

> ---
>  drivers/net/ethernet/airoha/airoha_eth.c  | 219 ++++++++++++++++++++++++++----
>  drivers/net/ethernet/airoha/airoha_eth.h  |  13 +-
>  drivers/net/ethernet/airoha/airoha_ppe.c  |   9 +-
>  drivers/net/ethernet/airoha/airoha_regs.h |   1 +
>  4 files changed, 214 insertions(+), 28 deletions(-)
> 
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
> index 2c9ceb9f16f8..609a5ea67fb7 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.c
> +++ b/drivers/net/ethernet/airoha/airoha_eth.c

[...]

> +static void airoha_disable_qos_for_gdm34(struct net_device *netdev)
> +{
> +	struct airoha_gdm_dev *dev = netdev_priv(netdev);
> +	struct airoha_gdm_port *port = dev->port;
> +	int err;
> +
> +	if (port->id != AIROHA_GDM3_IDX &&
> +	    port->id != AIROHA_GDM4_IDX)
> +		return;
> +
> +	err = airoha_disable_gdm2_loopback(dev);
> +	if (err)
> +		netdev_warn(netdev,
> +			    "failed disabling GDM2 loopback: %d\n", err);
> +
> +	dev->flags &= ~AIROHA_DEV_F_WAN;
> +	airoha_dev_set_qdma(dev);
> +
> +	airoha_set_macaddr(dev, netdev->dev_addr);

- Should the return value of airoha_set_macaddr() be checked here?
  airoha_set_macaddr() can return -EINVAL when the device MAC MSBs do
  not match other same-role sibling devices:
  	if (!is_zero_ether_addr(ref_addr) && memcmp(ref_addr, addr, 3)) {
  		...
  		return -EINVAL;
  	}
  Since AIROHA_DEV_F_WAN has just been cleared and dev->qdma has been
  switched to QDMA0, the scan now iterates LAN-role peers.  If the
  ex-WAN MAC's top three bytes disagree with the LAN peers, the call
  returns -EINVAL and the HW MAC registers are left unwritten while
  the software state has already been flipped to LAN.
  - The asymmetry is intentional. airoha_disable_qos_for_gdm34() is a
    teardown path that returns void, the demotion has to happen regardless.
    Moreover, airoha_set_macaddr() can fail just if the device is
    misconfigured.


> +	if (netif_running(netdev))
> +		airoha_set_gdm_port_fwd_cfg(dev->eth,
> +					    REG_GDM_FWD_CFG(port->id),
> +					    FE_PSE_PORT_PPE1);
> +}
> +
> +static int airoha_enable_qos_for_gdm34(struct net_device *netdev,
> +				       struct netlink_ext_ack *extack)
> +{
> +	struct airoha_gdm_dev *wan_dev, *dev = netdev_priv(netdev);
> +	struct airoha_gdm_port *port = dev->port;
> +	struct airoha_eth *eth = dev->eth;
> +	int err = -EBUSY;
> +
> +	if (port->id != AIROHA_GDM3_IDX &&
> +	    port->id != AIROHA_GDM4_IDX) {
> +		/* HW QoS is always supported by GDM1 and GDM2 */
> +		return 0;
> +	}
> +
> +	if (!airoha_is_lan_gdm_dev(dev)) /* Already enabled */
> +		return 0;
> +
> +	mutex_lock(&flow_offload_mutex);
> +
> +	wan_dev = airoha_get_wan_gdm_dev(eth);
> +	if (wan_dev) {
> +		if ((wan_dev->flags & AIROHA_DEV_F_QOS) ||
> +		    wan_dev->port->id == AIROHA_GDM2_IDX) {
> +			NL_SET_ERR_MSG_MOD(extack,
> +					   "QoS configured for WAN device");
> +			goto error_unlock;
> +		}
> +		airoha_disable_qos_for_gdm34(netdev_from_priv(wan_dev));
> +	}
> +
> +	dev->flags |= AIROHA_DEV_F_WAN;
> +	airoha_dev_set_qdma(dev);
> +	err = airoha_enable_gdm2_loopback(dev);
> +	if (err)
> +		goto error_disable_wan;
> +
> +	err = airoha_set_macaddr(dev, netdev->dev_addr);
> +	if (err)
> +		goto error_disable_loopback;
> +
> +	if (netif_running(netdev)) {
> +		u32 pse_port;
> +
> +		pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2
> +							 : FE_PSE_PORT_PPE1;
> +		airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id),
> +					    pse_port);
> +	}
> +
> +	mutex_unlock(&flow_offload_mutex);
> +
> +	return 0;
> +
> +error_disable_loopback:
> +	airoha_disable_gdm2_loopback(dev);
> +error_disable_wan:
> +	dev->flags &= ~AIROHA_DEV_F_WAN;
> +	airoha_dev_set_qdma(dev);
> +error_unlock:
> +	mutex_unlock(&flow_offload_mutex);
> +
> +	return err;

- Does this failure path fully implement the "proper rollback on failure"
  described in the commit message?
  Earlier in the function, if a peer wan_dev exists it is demoted via:
  	airoha_disable_qos_for_gdm34(netdev_from_priv(wan_dev));
  That helper clears AIROHA_DEV_F_WAN on wan_dev, swaps its RCU qdma
  pointer to QDMA0, disables GDM2 loopback, rewrites its MAC to the LAN
  registers, and (if wan_dev was running) sets its REG_GDM_FWD_CFG to
  FE_PSE_PORT_PPE1.
  If a later step for the requesting device fails, the error labels
  error_disable_loopback / error_disable_wan / error_unlock only unwind
  the requesting device.  The demoted wan_dev is never re-promoted.
  For example, airoha_set_macaddr(dev, netdev->dev_addr) can return
  -EINVAL when the requesting device's MAC MSBs disagree with the other
  WAN-role peers:
  	if (!is_zero_ether_addr(ref_addr) && memcmp(ref_addr, addr, 3)) {
  		...
  		return -EINVAL;
  	}
  In that case the failed tc qdisc add returns to userspace, the
  requesting device is rolled back to LAN, and no GDM3/GDM4 device holds
  the WAN role anymore.  The previously working WAN device silently loses
  its WAN role and QDMA1 binding.
  Should the error paths re-promote wan_dev, or should the preconditions
  that can fail (MAC MSB check, GDM2 loopback setup) be validated before
  mutating the peer's state?
  - This issue has been already reported in the past. If the configuration
    fails, I think there is no point to move back the previous device as WAN.
    The user will be able to log again and re-apply the configuration.
    Moreover, airoha_enable_qos_for_gdm34() can fail just if the device is
    misconfigured (failures in airoha_enable_gdm2_loopback() or
    airoha_set_macaddr()).

Regards,
Lorenzo

> +}
> +
>  static int airoha_tc_htb_destroy(struct net_device *netdev)
>  {
>  	struct airoha_gdm_dev *dev = netdev_priv(netdev);
> @@ -3057,6 +3217,8 @@ static int airoha_tc_htb_destroy(struct net_device *netdev)
>  	for_each_set_bit(q, dev->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
>  		airoha_tc_remove_htb_queue(netdev, q);
>  
> +	dev->flags &= ~AIROHA_DEV_F_QOS;
> +
>  	return 0;
>  }
>  
> @@ -3076,24 +3238,33 @@ static int airoha_tc_get_htb_get_leaf_queue(struct net_device *netdev,
>  	return 0;
>  }
>  
> -static int airoha_tc_setup_qdisc_htb(struct net_device *dev,
> +static int airoha_tc_setup_qdisc_htb(struct net_device *netdev,
>  				     struct tc_htb_qopt_offload *opt)
>  {
>  	switch (opt->command) {
> -	case TC_HTB_CREATE:
> +	case TC_HTB_CREATE: {
> +		struct airoha_gdm_dev *dev = netdev_priv(netdev);
> +		int err;
> +
> +		err = airoha_enable_qos_for_gdm34(netdev, opt->extack);
> +		if (err)
> +			return err;
> +
> +		dev->flags |= AIROHA_DEV_F_QOS;
>  		break;
> +	}
>  	case TC_HTB_DESTROY:
> -		return airoha_tc_htb_destroy(dev);
> +		return airoha_tc_htb_destroy(netdev);
>  	case TC_HTB_NODE_MODIFY:
> -		return airoha_tc_htb_modify_queue(dev, opt);
> +		return airoha_tc_htb_modify_queue(netdev, opt);
>  	case TC_HTB_LEAF_ALLOC_QUEUE:
> -		return airoha_tc_htb_alloc_leaf_queue(dev, opt);
> +		return airoha_tc_htb_alloc_leaf_queue(netdev, opt);
>  	case TC_HTB_LEAF_DEL:
>  	case TC_HTB_LEAF_DEL_LAST:
>  	case TC_HTB_LEAF_DEL_LAST_FORCE:
> -		return airoha_tc_htb_delete_leaf_queue(dev, opt);
> +		return airoha_tc_htb_delete_leaf_queue(netdev, opt);
>  	case TC_HTB_LEAF_QUERY_QUEUE:
> -		return airoha_tc_get_htb_get_leaf_queue(dev, opt);
> +		return airoha_tc_get_htb_get_leaf_queue(netdev, opt);
>  	default:
>  		return -EOPNOTSUPP;
>  	}
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
> index ac5f571f3e53..a314330fcd48 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.h
> +++ b/drivers/net/ethernet/airoha/airoha_eth.h
> @@ -537,11 +537,12 @@ struct airoha_qdma {
>  
>  enum airoha_dev_flags {
>  	AIROHA_DEV_F_WAN = BIT(0),
> +	AIROHA_DEV_F_QOS = BIT(1),
>  };
>  
>  struct airoha_gdm_dev {
> +	struct airoha_qdma __rcu *qdma;
>  	struct airoha_gdm_port *port;
> -	struct airoha_qdma *qdma;
>  	struct airoha_eth *eth;
>  
>  	DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
> @@ -677,6 +678,16 @@ int airoha_get_fe_port(struct airoha_gdm_dev *dev);
>  bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
>  			     struct airoha_gdm_dev *dev);
>  
> +extern struct mutex flow_offload_mutex;
> +
> +static inline struct airoha_qdma *
> +airoha_qdma_deref(struct airoha_gdm_dev *dev)
> +{
> +	return rcu_dereference_protected(dev->qdma,
> +					 lockdep_rtnl_is_held() ||
> +					 lockdep_is_held(&flow_offload_mutex));
> +}
> +
>  void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport);
>  bool airoha_ppe_is_enabled(struct airoha_eth *eth, int index);
>  void airoha_ppe_check_skb(struct airoha_ppe_dev *dev, struct sk_buff *skb,
> diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
> index 42f4b0f21d17..0f260c50ac3c 100644
> --- a/drivers/net/ethernet/airoha/airoha_ppe.c
> +++ b/drivers/net/ethernet/airoha/airoha_ppe.c
> @@ -15,7 +15,10 @@
>  #include "airoha_regs.h"
>  #include "airoha_eth.h"
>  
> -static DEFINE_MUTEX(flow_offload_mutex);
> +/* Serialize airoha_gdm_dev flags, QDMA pointer and PPE CPU port
> + * configuration.
> + */
> +DEFINE_MUTEX(flow_offload_mutex);
>  static DEFINE_SPINLOCK(ppe_lock);
>  
>  static const struct rhashtable_params airoha_flow_table_params = {
> @@ -86,8 +89,8 @@ static u32 airoha_ppe_get_timestamp(struct airoha_ppe *ppe)
>  
>  void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport)
>  {
> -	struct airoha_qdma *qdma = dev->qdma;
> -	struct airoha_eth *eth = qdma->eth;
> +	struct airoha_qdma *qdma = airoha_qdma_deref(dev);
> +	struct airoha_eth *eth = dev->eth;
>  	u8 qdma_id = qdma - &eth->qdma[0];
>  	u32 fe_cpu_port;
>  
> diff --git a/drivers/net/ethernet/airoha/airoha_regs.h b/drivers/net/ethernet/airoha/airoha_regs.h
> index 436f3c8779c1..4e17dfbcf2b8 100644
> --- a/drivers/net/ethernet/airoha/airoha_regs.h
> +++ b/drivers/net/ethernet/airoha/airoha_regs.h
> @@ -376,6 +376,7 @@
>  
>  #define REG_SRC_PORT_FC_MAP6		0x2298
>  #define FC_ID_OF_SRC_PORT_MASK(_n)	GENMASK(4 + ((_n) << 3), ((_n) << 3))
> +#define FC_MAP6_DEF_VALUE		0x1b1a1918
>  
>  #define REG_CDM5_RX_OQ1_DROP_CNT	0x29d4
>  
> 
> -- 
> 2.54.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* [PATCH v2 0/4] can: rockchip: add RK3588 CAN support
From: 1579567540 @ 2026-07-02 14:06 UTC (permalink / raw)
  To: Marc Kleine-Budde, linux-can
  Cc: Vincent Mailhol, Rob Herring, Krzysztof Kozlowski, kernel,
	Conor Dooley, Heiko Stuebner, Dmitry Torokhov, Shengjiu Wang,
	Pengpeng Hou, Russell King, Eric Biggers, Mario Limonciello,
	Karl Mehltretter, Yixun Lan, Stephen Boyd, devicetree,
	linux-arm-kernel, linux-rockchip, linux-kernel, Cunhao Lu

From: Cunhao Lu <1579567540@qq.com>

Add support for the RK3588 CAN controller.

RK3588 integrates three CAN-FD controllers that are closely related to
the existing Rockchip CAN-FD IP already supported in the kernel. The
RK3588 variant is not fully compatible with RK3568v2 because the RX
FIFO count register field has a different layout.

This series therefore:

- extends the existing Rockchip CAN-FD binding with the
  rockchip,rk3588-canfd compatible
- adds a dedicated RK3588 match entry and devtype in the driver
- describes the three CAN controller nodes in rk3588-base.dtsi
- enables CAN on the RK3588 Tiger Haikou board

RK3588 encodes RX_FIFO_CNT in bits 7:5 instead of 6:4. This
difference was found by comparing Rockchip's vendor kernel 6.1 CAN
support for RK3568 and RK3588, and was also confirmed by runtime
testing.

RK3588 uses the existing erratum 5 empty-FIFO workaround. Based on
Heiko's testing, v2 also enables the erratum 6 workaround for extended
frames being transmitted as standard frames.

RKCANFD_QUIRK_CANFD_BROKEN remains enabled for RK3588, so CAN-FD stays
disabled for now. Local testing did not reproduce the two known CAN-FD
trigger frames that cause Error Interrupts on RK3568 variants. Instead,
RK3588 shows a different CAN-FD failure mode: CAN-FD frames without BRS
work in this setup, but BRS with a data bitrate different from the
nominal bitrate immediately drives the controller bus-off.

Tested on an embedfire,rk3588-lubancat-5io board with can0/can1
directly connected. Runtime testing used a 200 MHz CAN clock, nominal
bitrate 500 kbit/s, data bitrate 500 kbit/s and 1 Mbit/s, and included stress and
error-path coverage for the existing Rockchip errata handling.

Changes in v2:
- use real author name
- fold the single-compatible entries into an enum, as suggested by Krzysztof
- enable the erratum 6 workaround for RK3588 based on Heiko's testing
- add Heiko's RK3588 Tiger Haikou CAN enablement patch
- keep RKCANFD_QUIRK_CANFD_BROKEN enabled for RK3588 so CAN-FD stays disabled
- document the RK3588 CAN-FD/BRS bus-off failure mode

Cunhao Lu (3):
  dt-bindings: can: rockchip: add rk3588 CAN-FD compatible
  can: rockchip: add RK3588 CAN support
  arm64: dts: rockchip: add CAN-FD nodes for RK3588

Heiko Stuebner (1):
  arm64: dts: rockchip: Enable CAN controller on RK3588-Tiger-Haikou

 .../net/can/rockchip,rk3568v2-canfd.yaml      |  4 +-
 arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 39 +++++++++++++++++++
 .../boot/dts/rockchip/rk3588-tiger-haikou.dts |  6 +++
 .../net/can/rockchip/rockchip_canfd-core.c    | 12 ++++++
 drivers/net/can/rockchip/rockchip_canfd-rx.c  |  5 ++-
 drivers/net/can/rockchip/rockchip_canfd.h     | 26 ++++++++++++-
 6 files changed, 89 insertions(+), 3 deletions(-)

-- 
2.34.1



^ permalink raw reply

* [PATCH v2 1/4] dt-bindings: can: rockchip: add rk3588 CAN-FD compatible
From: 1579567540 @ 2026-07-02 14:06 UTC (permalink / raw)
  To: Marc Kleine-Budde, linux-can
  Cc: Vincent Mailhol, Rob Herring, Krzysztof Kozlowski, kernel,
	Conor Dooley, Heiko Stuebner, Dmitry Torokhov, Shengjiu Wang,
	Pengpeng Hou, Russell King, Eric Biggers, Mario Limonciello,
	Karl Mehltretter, Yixun Lan, Stephen Boyd, devicetree,
	linux-arm-kernel, linux-rockchip, linux-kernel, Cunhao Lu
In-Reply-To: <20260702140654.2961561-1-1579567540@qq.com>

From: Cunhao Lu <1579567540@qq.com>

RK3588 integrates a Rockchip CAN-FD controller variant that is not
fully compatible with RK3568v2. The RX FIFO count register field is
encoded in bits 7:5 on RK3588, while RK3568v2 uses bits 6:4.

Add a dedicated rockchip,rk3588-canfd compatible to describe this
variant. Do not use rockchip,rk3568v2-canfd as a fallback, because that
would describe a register layout that does not match the hardware.

Changes in v2:
- Use enum for the single-compatible entries, as suggested by Krzysztof.
- Reword the commit message to explain the hardware difference instead
  of referring to Linux driver match data.

Signed-off-by: Cunhao Lu <1579567540@qq.com>
---
 .../devicetree/bindings/net/can/rockchip,rk3568v2-canfd.yaml  | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Documentation/devicetree/bindings/net/can/rockchip,rk3568v2-canfd.yaml b/Documentation/devicetree/bindings/net/can/rockchip,rk3568v2-canfd.yaml
index a077c0330013..81e2b6dfeb02 100644
--- a/Documentation/devicetree/bindings/net/can/rockchip,rk3568v2-canfd.yaml
+++ b/Documentation/devicetree/bindings/net/can/rockchip,rk3568v2-canfd.yaml
@@ -16,7 +16,9 @@ allOf:
 properties:
   compatible:
     oneOf:
-      - const: rockchip,rk3568v2-canfd
+      - enum:
+          - rockchip,rk3568v2-canfd
+          - rockchip,rk3588-canfd
       - items:
           - const: rockchip,rk3568v3-canfd
           - const: rockchip,rk3568v2-canfd
-- 
2.34.1



^ permalink raw reply related

* [PATCH v2 2/4] can: rockchip: add RK3588 CAN support
From: 1579567540 @ 2026-07-02 14:06 UTC (permalink / raw)
  To: Marc Kleine-Budde, linux-can
  Cc: Vincent Mailhol, Rob Herring, Krzysztof Kozlowski, kernel,
	Conor Dooley, Heiko Stuebner, Dmitry Torokhov, Shengjiu Wang,
	Pengpeng Hou, Russell King, Eric Biggers, Mario Limonciello,
	Karl Mehltretter, Yixun Lan, Stephen Boyd, devicetree,
	linux-arm-kernel, linux-rockchip, linux-kernel, Cunhao Lu,
	Heiko Stuebner
In-Reply-To: <20260702140654.2961561-1-1579567540@qq.com>

From: Cunhao Lu <1579567540@qq.com>

Add support for the RK3588 CAN controller by introducing a dedicated
model ID and OF match entry.

The block is closely related to the existing RK3568 variants, but it
cannot reuse their match data unchanged. In particular, RK3588
encodes RX_FIFO_CNT in bits 7:5 instead of 6:4, so the RX path needs
SoC-specific handling.

The RX FIFO count bitfield difference was found by comparing Rockchip's
vendor kernel 6.1 CAN support for RK3568 and RK3588. Runtime testing on
RK3588 also confirms that bits 7:5 are needed.

Enable the existing erratum 5 empty-FIFO workaround for RK3588.
Heiko reproduced erratum 6 on RK3588, so enable that workaround as
well.

Keep RKCANFD_QUIRK_CANFD_BROKEN enabled for RK3588, so CAN-FD stays
disabled for now. Local testing did not reproduce the two known CAN-FD
trigger frames that cause Error Interrupts on RK3568 variants. Instead,
RK3588 shows a different CAN-FD failure mode: CAN-FD frames without BRS
work in this setup, but BRS with a data bitrate different from the
nominal bitrate immediately drives the controller bus-off.

Reported-by: Heiko Stuebner <heiko.stuebner@cherry.de>
Link: https://lore.kernel.org/lkml/20260630164336.3444550-4-heiko@sntech.de/

Signed-off-by: Cunhao Lu <1579567540@qq.com>
---
 .../net/can/rockchip/rockchip_canfd-core.c    | 12 +++++++++
 drivers/net/can/rockchip/rockchip_canfd-rx.c  |  5 +++-
 drivers/net/can/rockchip/rockchip_canfd.h     | 26 ++++++++++++++++++-
 3 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/drivers/net/can/rockchip/rockchip_canfd-core.c b/drivers/net/can/rockchip/rockchip_canfd-core.c
index 29de0c01e4ed..178d69edf1bb 100644
--- a/drivers/net/can/rockchip/rockchip_canfd-core.c
+++ b/drivers/net/can/rockchip/rockchip_canfd-core.c
@@ -50,6 +50,13 @@ static const struct rkcanfd_devtype_data rkcanfd_devtype_data_rk3568v3 = {
 		RKCANFD_QUIRK_CANFD_BROKEN,
 };
 
+static const struct rkcanfd_devtype_data rkcanfd_devtype_data_rk3588 = {
+	.model = RKCANFD_MODEL_RK3588,
+	.quirks = RKCANFD_QUIRK_RK3568_ERRATUM_5 |
+		RKCANFD_QUIRK_RK3568_ERRATUM_6 |
+		RKCANFD_QUIRK_CANFD_BROKEN,
+};
+
 static const char *__rkcanfd_get_model_str(enum rkcanfd_model model)
 {
 	switch (model) {
@@ -57,6 +64,8 @@ static const char *__rkcanfd_get_model_str(enum rkcanfd_model model)
 		return "rk3568v2";
 	case RKCANFD_MODEL_RK3568V3:
 		return "rk3568v3";
+	case RKCANFD_MODEL_RK3588:
+		return "rk3588";
 	}
 
 	return "<unknown>";
@@ -846,6 +855,9 @@ static const struct of_device_id rkcanfd_of_match[] = {
 	}, {
 		.compatible = "rockchip,rk3568v3-canfd",
 		.data = &rkcanfd_devtype_data_rk3568v3,
+	}, {
+		.compatible = "rockchip,rk3588-canfd",
+		.data = &rkcanfd_devtype_data_rk3588,
 	}, {
 		/* sentinel */
 	},
diff --git a/drivers/net/can/rockchip/rockchip_canfd-rx.c b/drivers/net/can/rockchip/rockchip_canfd-rx.c
index 475c0409e215..24e87daa1df0 100644
--- a/drivers/net/can/rockchip/rockchip_canfd-rx.c
+++ b/drivers/net/can/rockchip/rockchip_canfd-rx.c
@@ -281,7 +281,10 @@ rkcanfd_rx_fifo_get_len(const struct rkcanfd_priv *priv)
 {
 	const u32 reg = rkcanfd_read(priv, RKCANFD_REG_RX_FIFO_CTRL);
 
-	return FIELD_GET(RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT, reg);
+	if (priv->devtype_data.model == RKCANFD_MODEL_RK3588)
+		return FIELD_GET(RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT_RK3588, reg);
+
+	return FIELD_GET(RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT_RK3568, reg);
 }
 
 int rkcanfd_handle_rx_int(struct rkcanfd_priv *priv)
diff --git a/drivers/net/can/rockchip/rockchip_canfd.h b/drivers/net/can/rockchip/rockchip_canfd.h
index 93131c7d7f54..82a617e4ca66 100644
--- a/drivers/net/can/rockchip/rockchip_canfd.h
+++ b/drivers/net/can/rockchip/rockchip_canfd.h
@@ -214,7 +214,8 @@
 #define RKCANFD_REG_TXEVENT_FIFO_CTRL_TXE_FIFO_ENABLE BIT(0)
 
 #define RKCANFD_REG_RX_FIFO_CTRL 0x118
-#define RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT GENMASK(6, 4)
+#define RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT_RK3568 GENMASK(6, 4)
+#define RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT_RK3588 GENMASK(7, 5)
 #define RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_FULL_WATERMARK GENMASK(3, 1)
 #define RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_ENABLE BIT(0)
 
@@ -331,6 +332,11 @@
  * rarely with the standard clock of 300 MHz, but almost immediately
  * at 80 MHz.
  *
+ * Tests on the rk3588 show the same empty FIFO condition.
+ * In that setup rx_fifo_empty_errors increments when the bus
+ * transitions from idle to high CAN-FD load and stops growing once
+ * the bus reaches a steady state.
+ *
  * To workaround this problem, check for empty FIFO with
  * rkcanfd_fifo_header_empty() in rkcanfd_handle_rx_int_one() and exit
  * early.
@@ -344,6 +350,8 @@
 /* Erratum 6: The CAN controller's transmission of extended frames may
  * intermittently change into standard frames
  *
+ * Tests on the rk3588 show the same problem.
+ *
  * Work around this issue by activating self reception (RXSTX). If we
  * have pending TX CAN frames, check all RX'ed CAN frames in
  * rkcanfd_rxstx_filter().
@@ -408,6 +416,18 @@
  *     cansend can0 002##07217010000000000
  *   DUT:
  *     candump any,0:0,#FFFFFFFF -cexdHtA
+ *
+ * Tests on the rk3588 show a different CAN-FD failure mode: these two
+ * CAN-FD frames do not trigger Error Interrupt or Error-Warning. CAN-FD
+ * frames without bitrate switching work in this setup, but BRS with a
+ * data bitrate different from the nominal bitrate drives the controller
+ * bus-off immediately.
+ *
+ * To reproduce:
+ * host:
+ *   cangen can0 -I 2 -Li -Di -p 10 -f -g 1 -c32 -b
+ * DUT:
+ *   cansequence -rv can1 -f
  */
 #define RKCANFD_QUIRK_CANFD_BROKEN BIT(12)
 
@@ -424,6 +444,9 @@
  *     cansequence -rv -i 1
  *
  * - TX starvation after repeated Bus-Off
+ *   Tests on the rk3588 show the same problem. In a
+ *   10-cycle Bus-Off recovery test, 9 cycles failed to send after the
+ *   controller restarted.
  *   To reproduce:
  *   host:
  *     sleep 3 && cangen can0 -I2 -Li -Di -p10 -g 0.0
@@ -434,6 +457,7 @@
 enum rkcanfd_model {
 	RKCANFD_MODEL_RK3568V2 = 0x35682,
 	RKCANFD_MODEL_RK3568V3 = 0x35683,
+	RKCANFD_MODEL_RK3588 = 0x3588,
 };
 
 struct rkcanfd_devtype_data {
-- 
2.34.1



^ permalink raw reply related

* [PATCH v2 3/4] arm64: dts: rockchip: add CAN-FD nodes for RK3588
From: 1579567540 @ 2026-07-02 14:06 UTC (permalink / raw)
  To: Marc Kleine-Budde, linux-can
  Cc: Vincent Mailhol, Rob Herring, Krzysztof Kozlowski, kernel,
	Conor Dooley, Heiko Stuebner, Dmitry Torokhov, Shengjiu Wang,
	Pengpeng Hou, Russell King, Eric Biggers, Mario Limonciello,
	Karl Mehltretter, Yixun Lan, Stephen Boyd, devicetree,
	linux-arm-kernel, linux-rockchip, linux-kernel, Cunhao Lu
In-Reply-To: <20260702140654.2961561-1-1579567540@qq.com>

From: Cunhao Lu <1579567540@qq.com>

Describe the three CAN-FD controllers integrated in RK3588 in the base
SoC .dtsi.

Add CAN0, CAN1 and CAN2 nodes with their register ranges, interrupts,
clocks and resets, and keep them disabled by default so board DTS files
can enable them as needed.

Signed-off-by: Cunhao Lu <1579567540@qq.com>
---
 arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 39 +++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
index fc1fdbfd3162..b340973775c5 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
@@ -2648,6 +2648,45 @@ dmac1: dma-controller@fea30000 {
 		#dma-cells = <1>;
 	};
 
+	can0: can@fea50000 {
+		compatible = "rockchip,rk3588-canfd";
+		reg = <0x0 0xfea50000 0x0 0x1000>;
+		interrupts = <GIC_SPI 341 IRQ_TYPE_LEVEL_HIGH 0>;
+		clocks = <&cru CLK_CAN0>, <&cru PCLK_CAN0>;
+		clock-names = "baud", "pclk";
+		resets = <&cru SRST_CAN0>, <&cru SRST_P_CAN0>;
+		reset-names = "core", "apb";
+		pinctrl-names = "default";
+		pinctrl-0 = <&can0m0_pins>;
+		status = "disabled";
+	};
+
+	can1: can@fea60000 {
+		compatible = "rockchip,rk3588-canfd";
+		reg = <0x0 0xfea60000 0x0 0x1000>;
+		interrupts = <GIC_SPI 342 IRQ_TYPE_LEVEL_HIGH 0>;
+		clocks = <&cru CLK_CAN1>, <&cru PCLK_CAN1>;
+		clock-names = "baud", "pclk";
+		resets = <&cru SRST_CAN1>, <&cru SRST_P_CAN1>;
+		reset-names = "core", "apb";
+		pinctrl-names = "default";
+		pinctrl-0 = <&can1m0_pins>;
+		status = "disabled";
+	};
+
+	can2: can@fea70000 {
+		compatible = "rockchip,rk3588-canfd";
+		reg = <0x0 0xfea70000 0x0 0x1000>;
+		interrupts = <GIC_SPI 343 IRQ_TYPE_LEVEL_HIGH 0>;
+		clocks = <&cru CLK_CAN2>, <&cru PCLK_CAN2>;
+		clock-names = "baud", "pclk";
+		resets = <&cru SRST_CAN2>, <&cru SRST_P_CAN2>;
+		reset-names = "core", "apb";
+		pinctrl-names = "default";
+		pinctrl-0 = <&can2m0_pins>;
+		status = "disabled";
+	};
+
 	i2c1: i2c@fea90000 {
 		compatible = "rockchip,rk3588-i2c", "rockchip,rk3399-i2c";
 		reg = <0x0 0xfea90000 0x0 0x1000>;
-- 
2.34.1



^ permalink raw reply related

* [PATCH v2 4/4] arm64: dts: rockchip: Enable CAN controller on RK3588-Tiger-Haikou
From: 1579567540 @ 2026-07-02 14:06 UTC (permalink / raw)
  To: Marc Kleine-Budde, linux-can
  Cc: Vincent Mailhol, Rob Herring, Krzysztof Kozlowski, kernel,
	Conor Dooley, Heiko Stuebner, Dmitry Torokhov, Shengjiu Wang,
	Pengpeng Hou, Russell King, Eric Biggers, Mario Limonciello,
	Karl Mehltretter, Yixun Lan, Stephen Boyd, devicetree,
	linux-arm-kernel, linux-rockchip, linux-kernel, Heiko Stuebner,
	Cunhao Lu
In-Reply-To: <20260702140654.2961561-1-1579567540@qq.com>

From: Heiko Stuebner <heiko.stuebner@cherry.de>

CAN0 is piped through the Q7-connector to the CAN-Header on the Haikou
base-board, so enable support for it there.

At least on RK3588-Tiger, the CAN clocks default to 99MHz, limiting
usable CAN bitrates without skew. Errata documentation mentions
300MHz as the default frequency on RK3568, so replicate this here
to allow more bitrates.

Signed-off-by: Heiko Stuebner <heiko.stuebner@cherry.de>
Signed-off-by: Cunhao Lu <1579567540@qq.com>
---
 arch/arm64/boot/dts/rockchip/rk3588-tiger-haikou.dts | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/arm64/boot/dts/rockchip/rk3588-tiger-haikou.dts b/arch/arm64/boot/dts/rockchip/rk3588-tiger-haikou.dts
index 873fbeb8daa1..6273e695b039 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-tiger-haikou.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-tiger-haikou.dts
@@ -155,6 +155,12 @@ vddd_audio_1v6: regulator-vddd-audio-1v6 {
 	};
 };
 
+&can0 {
+	assigned-clocks = <&cru CLK_CAN0>;
+	assigned-clock-rates = <300000000>;
+	status = "okay";
+};
+
 &combphy2_psu {
 	status = "okay";
 };
-- 
2.34.1



^ permalink raw reply related

* Re: [PATCH v15 6/9] arm64: support copy_mc_[user]_highpage()
From: Ashok Raj @ 2026-07-02 14:22 UTC (permalink / raw)
  To: Ruidong Tian
  Cc: catalin.marinas, will, rafael, tony.luck, guohanjun, mchehab,
	xueshuai, tongtiangen, james.morse, robin.murphy, andreyknvl,
	dvyukov, vincenzo.frascino, mpe, npiggin, ryabinin.a.a, glider,
	christophe.leroy, aneesh.kumar, naveen.n.rao, tglx, mingo,
	linux-arm-kernel, linux-mm, linuxppc-dev, linux-kernel, kasan-dev,
	Ashok Raj
In-Reply-To: <20260618092124.3901230-7-tianruidong@linux.alibaba.com>

On Thu, Jun 18, 2026 at 05:21:20PM +0800, Ruidong Tian wrote:
> From: Tong Tiangen <tongtiangen@huawei.com>
> 
> Currently, many scenarios that can tolerate memory errors when copying page
> have been supported in the kernel[1~9], all of which are implemented by
> copy_mc_[user]_highpage(). arm64 should also support this mechanism.
> 
> Due to mte, arm64 needs to have its own copy_mc_[user]_highpage()
> architecture implementation, macros __HAVE_ARCH_COPY_MC_HIGHPAGE and
> __HAVE_ARCH_COPY_MC_USER_HIGHPAGE have been added to control it.
> 
> Add new helper copy_mc_page() which provide a page copy implementation with
> hardware memory error safe. The code logic of copy_mc_page() is the same as
> copy_page(), the main difference is that the ldp insn of copy_mc_page()
> contains the fixup type EX_TYPE_KACCESS_SEA, therefore, the
> main logic is extracted to copy_page_template.S. In addition, the fixup of
> MOPS insn is not considered at present.
> 

Hi Tong,

[snip]

> diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
> index fe60738e5943..831b20d45893 100644
> --- a/arch/arm64/Kconfig
> +++ b/arch/arm64/Kconfig
> @@ -21,6 +21,7 @@ config ARM64
>  	select ARCH_HAS_CACHE_LINE_SIZE
>  	select ARCH_HAS_CC_PLATFORM
>  	select ARCH_HAS_CPU_CACHE_INVALIDATE_MEMREGION
> +	select ARCH_HAS_COPY_MC if ACPI_APEI_GHES

ACPI_APEI_GHES seems like the wrong gate. The recovery this series enables hinges
on the in-kernel SEA claim path, and that is gated on CONFIG_ACPI_APEI_SEA,
not ACPI_APEI_GHES. From your own patch 2, include/acpi/ghes.h:

	#ifdef CONFIG_ACPI_APEI_SEA
	int ghes_notify_sea(enum ghes_exec_ctx context);
	#else
	static inline int ghes_notify_sea(enum ghes_exec_ctx context)
	{ return -ENOENT; }
	#endif

ACPI_APEI_SEA is "bool, depends on ARM64 && ACPI_APEI_GHES, default y", so
the config ACPI_APEI_GHES=y, ACPI_APEI_SEA=n is reachable. In that build we
still select ARCH_HAS_COPY_MC, but ghes_notify_sea() is the -ENOENT stub,
apei_claim_sea() never claims the abort in do_sea(), and every KERNEL_SEA
fixup added below is unreachable dead code. 

Maybe:

select ARCH_HAS_COPY_MC if ACPI_APEI_SEA

>  	select ARCH_HAS_CURRENT_STACK_POINTER
>  	select ARCH_HAS_DEBUG_VIRTUAL
>  	select ARCH_HAS_DEBUG_VM_PGTABLE
> diff --git a/arch/arm64/include/asm/asm-extable.h b/arch/arm64/include/asm/asm-extable.h
> index 8450ec5a3af6..9305ea77482a 100644
> --- a/arch/arm64/include/asm/asm-extable.h
> +++ b/arch/arm64/include/asm/asm-extable.h
> @@ -10,6 +10,10 @@
>  #define EX_TYPE_ACCESS_ERR_ZERO		2
>  #define EX_TYPE_UACCESS_CPY		3
>  #define EX_TYPE_LOAD_UNALIGNED_ZEROPAD	4
> +/*
> + * Kernel access: used in kernel context for both regular load/store
> + * instructions and MOPS (memory copy/set) instructions.
> + */
>  #define EX_TYPE_KACCESS_SEA		5
>  
>  /* Data fields for EX_TYPE_ACCESS_ERR_ZERO */
> diff --git a/arch/arm64/include/asm/mte.h b/arch/arm64/include/asm/mte.h
> index 7f7b97e09996..a0b1757f4847 100644
> --- a/arch/arm64/include/asm/mte.h
> +++ b/arch/arm64/include/asm/mte.h
> @@ -98,6 +98,11 @@ static inline bool try_page_mte_tagging(struct page *page)
>  void mte_zero_clear_page_tags(void *addr);
>  void mte_sync_tags(pte_t pte, unsigned int nr_pages);
>  void mte_copy_page_tags(void *kto, const void *kfrom);
> +
> +#ifdef CONFIG_ARCH_HAS_COPY_MC
> +int mte_copy_mc_page_tags(void *kto, const void *kfrom);
> +#endif
> +
>  void mte_thread_init_user(void);
>  void mte_thread_switch(struct task_struct *next);
>  void mte_cpu_setup(void);
> @@ -134,6 +139,10 @@ static inline void mte_sync_tags(pte_t pte, unsigned int nr_pages)
>  static inline void mte_copy_page_tags(void *kto, const void *kfrom)
>  {
>  }
> +static inline int mte_copy_mc_page_tags(void *kto, const void *kfrom)
> +{
> +	return 0;
> +}
>  static inline void mte_thread_init_user(void)
>  {
>  }
> diff --git a/arch/arm64/include/asm/page.h b/arch/arm64/include/asm/page.h
> index e25d0d18f6d7..5c4c9f974b68 100644
> --- a/arch/arm64/include/asm/page.h
> +++ b/arch/arm64/include/asm/page.h
> @@ -29,6 +29,18 @@ void copy_user_highpage(struct page *to, struct page *from,
>  void copy_highpage(struct page *to, struct page *from);
>  #define __HAVE_ARCH_COPY_HIGHPAGE
>  
> +#ifdef CONFIG_ARCH_HAS_COPY_MC
> +int copy_mc_page(void *to, const void *from);
> +#define __HAVE_ARCH_COPY_MC_PAGE
> +
> +int copy_mc_highpage(struct page *to, struct page *from);
> +#define __HAVE_ARCH_COPY_MC_HIGHPAGE
> +
> +int copy_mc_user_highpage(struct page *to, struct page *from,
> +		unsigned long vaddr, struct vm_area_struct *vma);
> +#define __HAVE_ARCH_COPY_MC_USER_HIGHPAGE
> +#endif
> +
>  struct folio *vma_alloc_zeroed_movable_folio(struct vm_area_struct *vma,
>  						unsigned long vaddr);
>  #define vma_alloc_zeroed_movable_folio vma_alloc_zeroed_movable_folio
> diff --git a/arch/arm64/lib/Makefile b/arch/arm64/lib/Makefile
> index 448c917494f3..1f4c3f743a20 100644
> --- a/arch/arm64/lib/Makefile
> +++ b/arch/arm64/lib/Makefile
> @@ -7,6 +7,8 @@ lib-y		:= clear_user.o delay.o copy_from_user.o		\
>  
>  lib-$(CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE) += uaccess_flushcache.o
>  
> +lib-$(CONFIG_ARCH_HAS_COPY_MC) += copy_mc_page.o
> +
>  obj-$(CONFIG_FUNCTION_ERROR_INJECTION) += error-inject.o
>  
>  obj-$(CONFIG_ARM64_MTE) += mte.o
> diff --git a/arch/arm64/lib/copy_mc_page.S b/arch/arm64/lib/copy_mc_page.S
> new file mode 100644
> index 000000000000..f936e0c98611
> --- /dev/null
> +++ b/arch/arm64/lib/copy_mc_page.S
> @@ -0,0 +1,44 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +
> +#include <linux/linkage.h>
> +#include <linux/const.h>
> +#include <asm/assembler.h>
> +#include <asm/page.h>
> +#include <asm/cpufeature.h>
> +#include <asm/alternative.h>
> +#include <asm/asm-extable.h>
> +#include <asm/asm-uaccess.h>
> +
> +/*
> + * Copy a page from src to dest (both are page aligned) with memory error safe
> + *
> + * Parameters:
> + *	x0 - dest
> + *	x1 - src
> + * Returns:
> + * 	x0 - Return 0 if copy success, or -EFAULT if anything goes wrong
> + *	     while copying.
> + */
> +	.macro ldp1 reg1, reg2, ptr, val
> +	KERNEL_SEA(9998f, ldp \reg1, \reg2, [\ptr, \val])
> +	.endm
> +
> +	.macro cpy1 dst, src, count
> +	.arch_extension mops
> +	KERNEL_SEA(9998f, cpypwn [\dst]!, [\src]!, \count!)
> +	KERNEL_SEA(9998f, cpymwn [\dst]!, [\src]!, \count!)
> +	KERNEL_SEA(9998f, cpyewn [\dst]!, [\src]!, \count!)
> +	.endm

You wrap the MOPS cpy* here (and in copy_page_template.S) in KERNEL_SEA, but
the commit message still says "the fixup of MOPS insn is not considered at
present." 

please reconcile the changelog with the code, and mention the FEAT_MOPS 
fixup support explicitly.

> +
> +SYM_FUNC_START(__pi_copy_mc_page)
> +#include "copy_page_template.S"
> +
> +	mov x0, #0
> +	ret
> +
> +9998:	mov x0, #-EFAULT
> +	ret
> +
> +SYM_FUNC_END(__pi_copy_mc_page)
> +SYM_FUNC_ALIAS(copy_mc_page, __pi_copy_mc_page)
> +EXPORT_SYMBOL(copy_mc_page)
> diff --git a/arch/arm64/lib/copy_page.S b/arch/arm64/lib/copy_page.S
> index e6374e7e5511..e520777b5150 100644
> --- a/arch/arm64/lib/copy_page.S
> +++ b/arch/arm64/lib/copy_page.S
> @@ -17,65 +17,20 @@
>   *	x0 - dest
>   *	x1 - src
>   */
> -SYM_FUNC_START(__pi_copy_page)
> -#ifdef CONFIG_AS_HAS_MOPS
> -	.arch_extension mops
> -alternative_if_not ARM64_HAS_MOPS
> -	b	.Lno_mops
> -alternative_else_nop_endif
> -
> -	mov	x2, #PAGE_SIZE
> -	cpypwn	[x0]!, [x1]!, x2!
> -	cpymwn	[x0]!, [x1]!, x2!
> -	cpyewn	[x0]!, [x1]!, x2!
> -	ret
> -.Lno_mops:
> -#endif
> -	ldp	x2, x3, [x1]
> -	ldp	x4, x5, [x1, #16]
> -	ldp	x6, x7, [x1, #32]
> -	ldp	x8, x9, [x1, #48]
> -	ldp	x10, x11, [x1, #64]
> -	ldp	x12, x13, [x1, #80]
> -	ldp	x14, x15, [x1, #96]
> -	ldp	x16, x17, [x1, #112]
> -
> -	add	x0, x0, #256
> -	add	x1, x1, #128
> -1:
> -	tst	x0, #(PAGE_SIZE - 1)
>  
> -	stnp	x2, x3, [x0, #-256]
> -	ldp	x2, x3, [x1]
> -	stnp	x4, x5, [x0, #16 - 256]
> -	ldp	x4, x5, [x1, #16]
> -	stnp	x6, x7, [x0, #32 - 256]
> -	ldp	x6, x7, [x1, #32]
> -	stnp	x8, x9, [x0, #48 - 256]
> -	ldp	x8, x9, [x1, #48]
> -	stnp	x10, x11, [x0, #64 - 256]
> -	ldp	x10, x11, [x1, #64]
> -	stnp	x12, x13, [x0, #80 - 256]
> -	ldp	x12, x13, [x1, #80]
> -	stnp	x14, x15, [x0, #96 - 256]
> -	ldp	x14, x15, [x1, #96]
> -	stnp	x16, x17, [x0, #112 - 256]
> -	ldp	x16, x17, [x1, #112]
> +	.macro ldp1 reg1, reg2, ptr, val
> +	ldp \reg1, \reg2, [\ptr, \val]
> +	.endm
>  
> -	add	x0, x0, #128
> -	add	x1, x1, #128
> -
> -	b.ne	1b
> -
> -	stnp	x2, x3, [x0, #-256]
> -	stnp	x4, x5, [x0, #16 - 256]
> -	stnp	x6, x7, [x0, #32 - 256]
> -	stnp	x8, x9, [x0, #48 - 256]
> -	stnp	x10, x11, [x0, #64 - 256]
> -	stnp	x12, x13, [x0, #80 - 256]
> -	stnp	x14, x15, [x0, #96 - 256]
> -	stnp	x16, x17, [x0, #112 - 256]
> +	.macro cpy1 dst, src, count
> +	.arch_extension mops
> +	cpypwn [\dst]!, [\src]!, \count!
> +	cpymwn [\dst]!, [\src]!, \count!
> +	cpyewn [\dst]!, [\src]!, \count!
> +	.endm
>  
> +SYM_FUNC_START(__pi_copy_page)
> +#include "copy_page_template.S"
>  	ret
>  SYM_FUNC_END(__pi_copy_page)
> new file mode 100644
> index 000000000000..e5afbeaaad25
> --- /dev/null
> +++ b/arch/arm64/lib/copy_page_template.S
> @@ -0,0 +1,70 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2012 ARM Ltd.
> + */
> +
> +/*
> + * Copy a page from src to dest (both are page aligned)
> + *
> + * Parameters:
> + *	x0 - dest
> + *	x1 - src
> + */
> +dstin	.req	x0
> +src	.req	x1
> +
> +#ifdef CONFIG_AS_HAS_MOPS
> +alternative_if_not ARM64_HAS_MOPS
> +	b	.Lno_mops
> +alternative_else_nop_endif
> +	mov	x2, #PAGE_SIZE
> +	cpy1	dst, src, x2

Should dst above be dstin instead?

Also the cpy1 instruction sits outside the alternatives block

Have you compiled with a toolchain that doesn't have MOPS? I suspect it
might break builds.

> +	b	.Lexitfunc
> +.Lno_mops:
> +#endif
> +
> +	ldp1	x2, x3, x1, #0
> +	ldp1	x4, x5, x1, #16
> +	ldp1	x6, x7, x1, #32
> +	ldp1	x8, x9, x1, #48
> +	ldp1	x10, x11, x1, #64
> +	ldp1	x12, x13, x1, #80
> +	ldp1	x14, x15, x1, #96
> +	ldp1	x16, x17, x1, #112
> +
> +	add	x0, x0, #256
> +	add	x1, x1, #128
> +1:
> +	tst	x0, #(PAGE_SIZE - 1)
> +
> +	stnp	x2, x3, [x0, #-256]
> +	ldp1	x2, x3, x1, #0
> +	stnp	x4, x5, [x0, #16 - 256]
> +	ldp1	x4, x5, x1, #16
> +	stnp	x6, x7, [x0, #32 - 256]
> +	ldp1	x6, x7, x1, #32
> +	stnp	x8, x9, [x0, #48 - 256]
> +	ldp1	x8, x9, x1, #48
> +	stnp	x10, x11, [x0, #64 - 256]
> +	ldp1	x10, x11, x1, #64
> +	stnp	x12, x13, [x0, #80 - 256]
> +	ldp1	x12, x13, x1, #80
> +	stnp	x14, x15, [x0, #96 - 256]
> +	ldp1	x14, x15, x1, #96
> +	stnp	x16, x17, [x0, #112 - 256]
> +	ldp1	x16, x17, x1, #112
> +
> +	add	x0, x0, #128
> +	add	x1, x1, #128
> +
> +	b.ne	1b
> +
> +	stnp	x2, x3, [x0, #-256]
> +	stnp	x4, x5, [x0, #16 - 256]
> +	stnp	x6, x7, [x0, #32 - 256]
> +	stnp	x8, x9, [x0, #48 - 256]
> +	stnp	x10, x11, [x0, #64 - 256]
> +	stnp	x12, x13, [x0, #80 - 256]
> +	stnp	x14, x15, [x0, #96 - 256]
> +	stnp	x16, x17, [x0, #112 - 256]
> +.Lexitfunc:
> diff --git a/arch/arm64/lib/mte.S b/arch/arm64/lib/mte.S
> index 5018ac03b6bf..1afe3ef1502c 100644
> --- a/arch/arm64/lib/mte.S
> +++ b/arch/arm64/lib/mte.S
> @@ -80,6 +80,35 @@ SYM_FUNC_START(mte_copy_page_tags)
>  	ret
>  SYM_FUNC_END(mte_copy_page_tags)
>  
> +#ifdef CONFIG_ARCH_HAS_COPY_MC
> +/*
> + * Copy the tags from the source page to the destination one with memory error safe
> + *   x0 - address of the destination page
> + *   x1 - address of the source page
> + * Returns:
> + *   x0 - Return 0 if copy success, or
> + *        -EFAULT if anything goes wrong while copying.
> + */
> +SYM_FUNC_START(mte_copy_mc_page_tags)
> +	mov	x2, x0
> +	mov	x3, x1
> +	multitag_transfer_size x5, x6
> +1:
> +KERNEL_SEA(2f, ldgm	x4, [x3])
> +	stgm	x4, [x2]
> +	add	x2, x2, x5
> +	add	x3, x3, x5
> +	tst	x2, #(PAGE_SIZE - 1)
> +	b.ne	1b
> +
> +	mov x0, #0
> +	ret
> +
> +2:	mov x0, #-EFAULT
> +	ret
> +SYM_FUNC_END(mte_copy_mc_page_tags)
> +#endif
> +
>  /*
>   * Read tags from a user buffer (one tag per byte) and set the corresponding
>   * tags at the given kernel address. Used by PTRACE_POKEMTETAGS.
> diff --git a/arch/arm64/mm/copypage.c b/arch/arm64/mm/copypage.c
> index cd5912ba617b..c22918ed0f3c 100644
> --- a/arch/arm64/mm/copypage.c
> +++ b/arch/arm64/mm/copypage.c
> @@ -72,3 +72,83 @@ void copy_user_highpage(struct page *to, struct page *from,
>  	flush_dcache_page(to);
>  }
>  EXPORT_SYMBOL_GPL(copy_user_highpage);
> +
> +#ifdef CONFIG_ARCH_HAS_COPY_MC
> +/*
> + * Return -EFAULT if anything goes wrong while copying page or mte.
> + */
> +int copy_mc_highpage(struct page *to, struct page *from)
> +{
> +	void *kto = page_address(to);
> +	void *kfrom = page_address(from);
> +	struct folio *src = page_folio(from);
> +	struct folio *dst = page_folio(to);
> +	unsigned int i, nr_pages;
> +	int ret;
> +
> +	ret = copy_mc_page(kto, kfrom);
> +	if (ret)
> +		return -EFAULT;
> +
> +	if (kasan_hw_tags_enabled())
> +		page_kasan_tag_reset(to);
> +
> +	if (!system_supports_mte())
> +		return 0;
> +
> +	if (folio_test_hugetlb(src)) {
> +		if (!folio_test_hugetlb_mte_tagged(src) ||
> +		    from != folio_page(src, 0))
> +			return 0;
> +
> +		WARN_ON_ONCE(!folio_try_hugetlb_mte_tagging(dst));
> +
> +		/*
> +		 * Populate tags for all subpages.
> +		 *
> +		 * Don't assume the first page is head page since
> +		 * huge page copy may start from any subpage.
> +		 */
> +		nr_pages = folio_nr_pages(src);
> +		for (i = 0; i < nr_pages; i++) {
> +			kfrom = page_address(folio_page(src, i));
> +			kto = page_address(folio_page(dst, i));
> +			ret = mte_copy_mc_page_tags(kto, kfrom);
> +			if (ret)
> +				return -EFAULT;
> +		}
> +		folio_set_hugetlb_mte_tagged(dst);
> +	} else if (page_mte_tagged(from)) {
> +		/* It's a new page, shouldn't have been tagged yet */
> +		WARN_ON_ONCE(!try_page_mte_tagging(to));
> +
> +		ret = mte_copy_mc_page_tags(kto, kfrom);
> +		if (ret)
> +			return -EFAULT;
> +		set_page_mte_tagged(to);
> +	}

I'm not certain, but this is a verbatim copy of copy_highpage()'s MTE
body? The only delta's are copy_mc_page()/mte_opy_mc_page_tags() return
an error instead of void. Leaves 2 copies.. is it possible to refactor
them?

Also on the -EFAULT paths, is the MTE tag state left half-updated?
like set_page_mte_tagged()/folio_set_hugetlb_mte() is never reached.

I'm not an MTE expert.. just checking, love to be wrong.

> +	/*
> +	 * memory_failure_queue() is not called here because on arm64
> +	 * the firmware (GHES) has already reported the hardware memory
> +	 * error and queued the page for memory_failure() handling via
> +	 * ghes_do_memory_failure().
> +	 */
> +	return 0;
> +}

So copy_mc_page()'s -EFAULT can only be produced by the KERNEL_SEA fixup
which is only reachable via fixup_exception_me() on the do_sea() path
after apei_claim_sea() has already queued memory_failure(). 

Is the ordering gauranteed? otherwise there might be a case where its
not offlined.

Cheers,
Ashok


^ permalink raw reply

* Re: [PATCH v2 1/4] dt-bindings: can: rockchip: add rk3588 CAN-FD compatible
From: Krzysztof Kozlowski @ 2026-07-02 14:23 UTC (permalink / raw)
  To: 1579567540, Marc Kleine-Budde, linux-can
  Cc: Vincent Mailhol, Rob Herring, Krzysztof Kozlowski, kernel,
	Conor Dooley, Heiko Stuebner, Dmitry Torokhov, Shengjiu Wang,
	Pengpeng Hou, Russell King, Eric Biggers, Mario Limonciello,
	Karl Mehltretter, Yixun Lan, Stephen Boyd, devicetree,
	linux-arm-kernel, linux-rockchip, linux-kernel
In-Reply-To: <tencent_3B2B6003D1DE4FB7A984665A062581766405@qq.com>

On 02/07/2026 16:06, 1579567540@qq.com wrote:
> From: Cunhao Lu <1579567540@qq.com>
> 
> RK3588 integrates a Rockchip CAN-FD controller variant that is not
> fully compatible with RK3568v2. The RX FIFO count register field is
> encoded in bits 7:5 on RK3588, while RK3568v2 uses bits 6:4.
> 
> Add a dedicated rockchip,rk3588-canfd compatible to describe this
> variant. Do not use rockchip,rk3568v2-canfd as a fallback, because that
> would describe a register layout that does not match the hardware.
> 
> Changes in v2:

Changelog goes to changelog, so ---.

See also submitting patches or just start using b4, which would solve
also your broken threading problem.

> - Use enum for the single-compatible entries, as suggested by Krzysztof.
> - Reword the commit message to explain the hardware difference instead
>   of referring to Linux driver match data.
> 
> Signed-off-by: Cunhao Lu <1579567540@qq.com>


Best regards,
Krzysztof


^ permalink raw reply

* Re: [PATCH v2 17/19] ARM: mark Cortex-M3/M4/M7 based boards as deprecated
From: Frank Li @ 2026-07-02 14:25 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: linux-arm-kernel, soc, linux-kernel, Arnd Bergmann, Aaro Koskinen,
	Alexander Sverdlin, Alexandre Belloni, Alexandre Torgue,
	Andrew Lunn, Ard Biesheuvel, Claudiu Beznea, Daniel Mack,
	Ethan Nelson-Moore, Frank Li, Gregory Clement, Haojian Zhuang,
	Jeremy J. Peper, Kristoffer Ericson, Krzysztof Kozlowski,
	Linus Walleij, Mark Brown, Marc Zyngier, Mike Rapoport,
	Nicolas Ferre, Patrice Chotard, Ralph Siemsen, Robert Jarzmik,
	Russell King, Sascha Hauer, Sebastian Hesselbarth, Stefan Agner,
	Stefan Wiehler, Tony Lindgren, Vladimir Zapolskiy, Will Deacon
In-Reply-To: <20260701212353.2196041-18-arnd@kernel.org>

On Wed, Jul 01, 2026 at 11:23:51PM +0200, Arnd Bergmann wrote:
> From: Arnd Bergmann <arnd@arndb.de>
>
> The Cortex-M3/M4/M7 cores were designed as a replacement for the earlier
> ARM7TDMI and ARM9TDMI microarchitectures used in older microcontrollers.
>
> At the moment, Linux can run these cores either when they are integrated
> into a larger SoC, or as standalone microcontrollers. While there was
> a lot of development work going into Cortex-M support from 2011 to 2016,
> this largely stopped when it became clear that Zephyr and other RTOS
> had taken over that market. To date, the only Cortex-M based based
> microcontroller boards supported upstream are reference implementations.
>
> Schedule these for removal after the next LTS kernel, so if any users
> remain that want to update their kernels, they can stay on that
> version for a few years before having to maintain the platform support
> out of tree.
>
> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
> ---
>  arch/arm/Kconfig            |  8 ++++++--
>  arch/arm/mach-at91/Kconfig  |  4 +++-
>  arch/arm/mach-imx/Kconfig   |  4 +++-

for imx part.

Acked-by: Frank Li <Frank.Li@nxp.com>



^ permalink raw reply

* [RFC PATCH 0/3] KVM: Dirty page logging for guest_memfd-only memslots
From: Alexandru Elisei @ 2026-07-02 14:29 UTC (permalink / raw)
  To: pbonzini, kvm, seanjc, david.hildenbrand, maz, oupton, joey.gouly,
	seiden, suzuki.poulose, yuzenghui, linux-arm-kernel, kvmarm,
	fuad.tabba, mark.rutland

The memory represented by guest_memfd-only memslots
(kvm_memslot_is_gmem_only() is true) is shared with userspace, which can
freely mmap it and access it. The only thing that is preventing dirty page
logging for such memslots is that KVM doesn't allow slots backed by
guest_memfd to have their flags changed; they can only be created and
deleted.

When KVM changes the flags for a memslot, it is possible for one reader
handling a guest fault to observe the old memslot, with the old flags, and
another reader to observe the new memslot, with the new flags. With the way
a guest_memfd file keeps track of the associated memslots, it is impossible
to avoid the WARN_ON_ONCE in __kvm_gmem_get_pfn() when one of the two
memslot pointers that the readers observe doesn't match the memslot pointer
stored in the file bindings.

To get around this, I decided to change the way guest_memfd keeps track of
the associated memslots: instead of an xarray of memslot pointers, use an
xarray which stores the memslot id (id and as_id, to be more precise),
which can be used to search for the memslot in the active memslots array.
Whenever guest_memfd wants to access a memslot, it deferences the
kvm->memslots RCU pointer under the RCU read lock, similar to how guest
faults are handled, or how the MMU notifiers work. All of this is
implemented in patch #1, "KVM: guest_memfd: Use memslot id to keep track of
associated memslots".

Building on that, toggling the KVM_MEM_LOG_DIRTY_PAGES flag for
guest_memfd-only memslots is implemented in patch #2, "KVM: Implement dirty
page logging for guest_memfd-only memslots". This is gated by a KVM
capability because it is a userspace visible change in behaviour.

The capability is also architecture specific, not because this is something
tied to a specific architecture, but because when I was testing the series
on arm64 I realized that the arm64 fault handling code required a minor
change, and I don't know enough about the other architectures to tell if
any changes are needed for them.

Just FYI, this is an RFC so it goes without saying that I'm open to any
suggestions, and I'll redo the whole thing if there's a better solution.

Tested the series using kvmtool on an arm64 machine, with guest_memfd
support added, as well as two command line arguments: --enable-dirty-log
and --disable-dirty-log. The arguments toggle the KVM_MEM_LOG_DIRTY_PAGES
memslot flag, they don't read or otherwise touch the list of dirty pages in
any way. Pushed a branch at [1].

[1] https://gitlab.arm.com/linux-arm/kvmtool-ae/-/tree/guest-memfd-v1-wip4-dirty-page-logging

Alexandru Elisei (3):
  KVM: guest_memfd: Use memslot id to keep track of associated memslots
  KVM: Implement dirty page logging for guest_memfd-only memslots
  KVM: arm64: Allow dirty page logging for guest_memfd-only memslots

 Documentation/virt/kvm/api.rst |   9 ++
 arch/arm64/kvm/arm.c           |  22 +++++
 arch/arm64/kvm/mmu.c           |   3 +-
 include/linux/kvm_host.h       |  15 +++
 include/uapi/linux/kvm.h       |   1 +
 virt/kvm/guest_memfd.c         | 165 ++++++++++++++++++++++++++++++---
 virt/kvm/kvm_main.c            |  44 ++++-----
 virt/kvm/kvm_mm.h              |  11 +++
 8 files changed, 232 insertions(+), 38 deletions(-)


base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
-- 
2.43.0



^ permalink raw reply

* [RFC PATCH 1/3] KVM: guest_memfd: Use memslot id to keep track of associated memslots
From: Alexandru Elisei @ 2026-07-02 14:29 UTC (permalink / raw)
  To: pbonzini, kvm, seanjc, david.hildenbrand, maz, oupton, joey.gouly,
	seiden, suzuki.poulose, yuzenghui, linux-arm-kernel, kvmarm,
	fuad.tabba, mark.rutland
In-Reply-To: <20260702142912.6395-1-alexandru.elisei@arm.com>

To enable memslot operations, KVM maintains two arrays of memslots, and an
RCU pointer to the active (in use) array. Changes are made first to the
inactive array, and the RCU pointer is updated to point to the inactive
array, which becomes active.

The guest_memfd file maintains an xarray of pointers to memslots that use
it as the memory provider. After the RCU pointer to the active memslots is
updated and until SRCU is synchronized, readers can observe the old or the
new value for the active array, and therefore the old or the new pointer
for a given memslot.  For memslot creation or deletion that is not an issue
for guest_memfd, as readers will either read the same memslot pointer saved
by the guest_memfd file, or a non-existing memslot.

But when changing the flags for a memslot, readers can read two different
and non-NULL memslot pointers. Since there is no easy way to ensure that
the memslot pointer that the guest_memfd stores is consistent with both
views at the same time, modify how the guest_memfd file keeps track of the
associated memslots: instead of storing the pointer directly, store the
memslot id and address space id (as_id), and use that to reach the memslot
in the active list of memslots.

This only changes how guest_memfd keeps track of memslots, userspace is not
allowed to make changes to a memslot yet.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 virt/kvm/guest_memfd.c | 95 +++++++++++++++++++++++++++++++++++-------
 1 file changed, 80 insertions(+), 15 deletions(-)

diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index db57c5766ab6..43ef8e908aaf 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -25,6 +25,7 @@ struct gmem_file {
 	struct kvm *kvm;
 	struct xarray bindings;
 	struct list_head entry;
+	bool found_memslot;	/* Used for balancing invalidations when punching a hole */
 };
 
 struct gmem_inode {
@@ -43,6 +44,29 @@ static __always_inline struct gmem_inode *GMEM_I(struct inode *inode)
 #define kvm_gmem_for_each_file(f, inode) \
 	list_for_each_entry(f, &GMEM_I(inode)->gmem_file_list, entry)
 
+static void *memslot_to_xa_value(struct kvm_memory_slot *slot)
+{
+	WARN_ON_ONCE(sizeof(slot->as_id) > 16);
+	WARN_ON_ONCE(sizeof(slot->id) > 16);
+	WARN_ON_ONCE(sizeof(slot->as_id) + sizeof(slot->id) > sizeof(unsigned long));
+
+	return xa_mk_value(((unsigned long)slot->as_id) << 16 | (unsigned long)slot->id);
+}
+
+static struct kvm_memory_slot *xa_value_to_memslot(struct kvm *kvm, const void *entry)
+{
+	unsigned long full_id = xa_to_value(entry);
+	u16 as_id = (full_id >> 16) & U16_MAX;
+	short id = full_id & U16_MAX;
+
+	/*
+	 * Do not ignore KVM_MEMSLOT_INVALID memslots, as we want
+	 * ->error_remove_folio(), when it races with memslot deletion, to have
+	 *  unmapped the memory upon completion.
+	 */
+	return id_to_memslot(__kvm_memslots(kvm, as_id), id);
+}
+
 /**
  * folio_file_pfn - like folio_file_page, but return a pfn.
  * @folio: The folio which contains this index.
@@ -157,7 +181,7 @@ static enum kvm_gfn_range_filter kvm_gmem_get_invalidate_filter(struct inode *in
 	return KVM_FILTER_PRIVATE;
 }
 
-static void __kvm_gmem_invalidate_start(struct gmem_file *f, pgoff_t start,
+static bool __kvm_gmem_invalidate_start(struct gmem_file *f, pgoff_t start,
 					pgoff_t end,
 					enum kvm_gfn_range_filter attr_filter)
 {
@@ -165,9 +189,15 @@ static void __kvm_gmem_invalidate_start(struct gmem_file *f, pgoff_t start,
 	struct kvm_memory_slot *slot;
 	struct kvm *kvm = f->kvm;
 	unsigned long index;
+	void *entry;
+
+	xa_for_each_range(&f->bindings, index, entry, start, end - 1) {
+		pgoff_t pgoff;
 
-	xa_for_each_range(&f->bindings, index, slot, start, end - 1) {
-		pgoff_t pgoff = slot->gmem.pgoff;
+		slot = xa_value_to_memslot(kvm, entry);
+		if (!slot)
+			continue;
+		pgoff = slot->gmem.pgoff;
 
 		struct kvm_gfn_range gfn_range = {
 			.start = slot->base_gfn + max(pgoff, start) - pgoff,
@@ -192,6 +222,8 @@ static void __kvm_gmem_invalidate_start(struct gmem_file *f, pgoff_t start,
 
 	if (found_memslot)
 		KVM_MMU_UNLOCK(kvm);
+
+	return found_memslot;
 }
 
 static void kvm_gmem_invalidate_start(struct inode *inode, pgoff_t start,
@@ -199,11 +231,22 @@ static void kvm_gmem_invalidate_start(struct inode *inode, pgoff_t start,
 {
 	enum kvm_gfn_range_filter attr_filter;
 	struct gmem_file *f;
+	struct kvm *kvm;
+	int idx;
 
 	attr_filter = kvm_gmem_get_invalidate_filter(inode);
 
-	kvm_gmem_for_each_file(f, inode)
-		__kvm_gmem_invalidate_start(f, start, end, attr_filter);
+	kvm_gmem_for_each_file(f, inode) {
+		kvm = f->kvm;
+		idx = srcu_read_lock(&kvm->srcu);
+		/*
+		 * This is safe to do because calls to
+		 * kvm_gmem_invalidate_start() are serialized by
+		 * filemap_invalidate_lock().
+		 */
+		f->found_memslot = __kvm_gmem_invalidate_start(f, start, end, attr_filter);
+		srcu_read_unlock(&kvm->srcu, idx);
+	}
 }
 
 static void __kvm_gmem_invalidate_end(struct gmem_file *f, pgoff_t start,
@@ -223,8 +266,11 @@ static void kvm_gmem_invalidate_end(struct inode *inode, pgoff_t start,
 {
 	struct gmem_file *f;
 
-	kvm_gmem_for_each_file(f, inode)
+	kvm_gmem_for_each_file(f, inode) {
+		if (!f->found_memslot)
+			continue;
 		__kvm_gmem_invalidate_end(f, start, end);
+	}
 }
 
 static long kvm_gmem_punch_hole(struct inode *inode, loff_t offset, loff_t len)
@@ -326,6 +372,7 @@ static int kvm_gmem_release(struct inode *inode, struct file *file)
 	struct kvm_memory_slot *slot;
 	struct kvm *kvm = f->kvm;
 	unsigned long index;
+	void *entry;
 
 	/*
 	 * Prevent concurrent attempts to *unbind* a memslot.  This is the last
@@ -344,17 +391,18 @@ static int kvm_gmem_release(struct inode *inode, struct file *file)
 
 	filemap_invalidate_lock(inode->i_mapping);
 
-	xa_for_each(&f->bindings, index, slot)
+	xa_for_each(&f->bindings, index, entry) {
+		slot = xa_value_to_memslot(kvm, entry);
 		WRITE_ONCE(slot->gmem.file, NULL);
+	}
 
 	/*
 	 * All in-flight operations are gone and new bindings can be created.
 	 * Zap all SPTEs pointed at by this file.  Do not free the backing
 	 * memory, as its lifetime is associated with the inode, not the file.
 	 */
-	__kvm_gmem_invalidate_start(f, 0, -1ul,
-				    kvm_gmem_get_invalidate_filter(inode));
-	__kvm_gmem_invalidate_end(f, 0, -1ul);
+	if (__kvm_gmem_invalidate_start(f, 0, -1ul, kvm_gmem_get_invalidate_filter(inode)))
+		__kvm_gmem_invalidate_end(f, 0, -1ul);
 
 	list_del(&f->entry);
 
@@ -498,14 +546,20 @@ static int kvm_gmem_migrate_folio(struct address_space *mapping,
 
 static int kvm_gmem_error_folio(struct address_space *mapping, struct folio *folio)
 {
+	enum kvm_gfn_range_filter attr_filter;
+	struct inode *inode = mapping->host;
+	struct gmem_file *f;
 	pgoff_t start, end;
+	bool found_memslot;
+	struct kvm *kvm;
+	int idx;
 
 	filemap_invalidate_lock_shared(mapping);
 
 	start = folio->index;
 	end = start + folio_nr_pages(folio);
 
-	kvm_gmem_invalidate_start(mapping->host, start, end);
+	attr_filter = kvm_gmem_get_invalidate_filter(inode);
 
 	/*
 	 * Do not truncate the range, what action is taken in response to the
@@ -515,8 +569,19 @@ static int kvm_gmem_error_folio(struct address_space *mapping, struct folio *fol
 	 * at which point KVM can either terminate the VM or propagate the
 	 * error to userspace.
 	 */
+	kvm_gmem_for_each_file(f, inode) {
+		kvm = f->kvm;
+
+		idx = srcu_read_lock(&kvm->srcu);
+		found_memslot = __kvm_gmem_invalidate_start(f, start, end, attr_filter);
+		srcu_read_unlock(&kvm->srcu, idx);
 
-	kvm_gmem_invalidate_end(mapping->host, start, end);
+		if (found_memslot) {
+			KVM_MMU_LOCK(kvm);
+			kvm_mmu_invalidate_end(kvm);
+			KVM_MMU_UNLOCK(kvm);
+		}
+	}
 
 	filemap_invalidate_unlock_shared(mapping);
 
@@ -691,7 +756,7 @@ int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot,
 	if (kvm_gmem_supports_mmap(inode))
 		slot->flags |= KVM_MEMSLOT_GMEM_ONLY;
 
-	xa_store_range(&f->bindings, start, end - 1, slot, GFP_KERNEL);
+	xa_store_range(&f->bindings, start, end - 1, memslot_to_xa_value(slot), GFP_KERNEL);
 	filemap_invalidate_unlock(inode->i_mapping);
 
 	/*
@@ -765,8 +830,8 @@ static struct folio *__kvm_gmem_get_pfn(struct file *file,
 		return ERR_PTR(-EFAULT);
 	}
 
-	if (xa_load(&f->bindings, index) != slot) {
-		WARN_ON_ONCE(xa_load(&f->bindings, index));
+	if (xa_load(&f->bindings, index) != memslot_to_xa_value(slot)) {
+		WARN_ON_ONCE(xa_to_value(xa_load(&f->bindings, index)));
 		return ERR_PTR(-EIO);
 	}
 
-- 
2.43.0



^ permalink raw reply related

* [RFC PATCH 3/3] KVM: arm64: Allow dirty page logging for guest_memfd-only memslots
From: Alexandru Elisei @ 2026-07-02 14:29 UTC (permalink / raw)
  To: pbonzini, kvm, seanjc, david.hildenbrand, maz, oupton, joey.gouly,
	seiden, suzuki.poulose, yuzenghui, linux-arm-kernel, kvmarm,
	fuad.tabba, mark.rutland
In-Reply-To: <20260702142912.6395-1-alexandru.elisei@arm.com>

Everything is in place to allow dirty page logging for guest_memfd-only
memslots, advertise it to userspace.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 arch/arm64/kvm/arm.c | 22 ++++++++++++++++++++++
 arch/arm64/kvm/mmu.c |  3 ++-
 2 files changed, 24 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 50adfff75be8..e92e5d7b533a 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -393,6 +393,9 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
 	case KVM_CAP_COUNTER_OFFSET:
 	case KVM_CAP_ARM_WRITABLE_IMP_ID_REGS:
 	case KVM_CAP_ARM_SEA_TO_USER:
+#ifdef CONFIG_KVM_GUEST_MEMFD
+	case KVM_CAP_GUEST_MEMFD_MMAP_LOG_DIRTY_PAGES:
+#endif
 		r = 1;
 		break;
 	case KVM_CAP_SET_GUEST_DEBUG2:
@@ -493,6 +496,25 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
 	return r;
 }
 
+#ifdef CONFIG_KVM_GUEST_MEMFD
+bool kvm_arch_supports_gmem_mmap_dirty_logging(struct kvm *kvm)
+{
+	/*
+	 * Protected pKVM VMs don't allow dirty page logging, fail early here
+	 * instead of in kvm_arch_prepare_memory_region().
+	 *
+	 * KVM_CAP_GUEST_MEMFD_MMAP_LOG_DIRTY_PAGES is not available for a
+	 * protected pKVM VM, and returning false means that
+	 * KVM_SET_USER_MEMORY_REGION2 fails with EINVAL, which is consistent
+	 * with unsupported memslot flags.
+	 */
+	if (!is_protected_kvm_enabled())
+		return true;
+
+	return kvm_pkvm_ext_allowed(kvm, KVM_CAP_GUEST_MEMFD_MMAP_LOG_DIRTY_PAGES);
+}
+#endif
+
 long kvm_arch_dev_ioctl(struct file *filp,
 			unsigned int ioctl, unsigned long arg)
 {
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 6c941aaa10c6..f6e6de153ce2 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1648,7 +1648,8 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
 		return ret;
 	}
 
-	if (!(s2fd->memslot->flags & KVM_MEM_READONLY))
+	if (!(s2fd->memslot->flags & KVM_MEM_READONLY) &&
+	    (!kvm_slot_dirty_track_enabled(s2fd->memslot) || write_fault))
 		prot |= KVM_PGTABLE_PROT_W;
 
 	if (s2fd->nested)
-- 
2.43.0



^ permalink raw reply related

* [RFC PATCH 2/3] KVM: Implement dirty page logging for guest_memfd-only memslots
From: Alexandru Elisei @ 2026-07-02 14:29 UTC (permalink / raw)
  To: pbonzini, kvm, seanjc, david.hildenbrand, maz, oupton, joey.gouly,
	seiden, suzuki.poulose, yuzenghui, linux-arm-kernel, kvmarm,
	fuad.tabba, mark.rutland
In-Reply-To: <20260702142912.6395-1-alexandru.elisei@arm.com>

The entire memory represented by guest_memfd-only memslot is shared and
accessible by userspace. Enable dirty page logging for such memslots, and
allow architectures to advertise support for it with the
KVM_CAP_GUEST_MEMFD_MMAP_LOG_DIRTY_PAGES capability.

No architecture supports it yet.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 Documentation/virt/kvm/api.rst |  9 +++++
 include/linux/kvm_host.h       | 15 ++++++++
 include/uapi/linux/kvm.h       |  1 +
 virt/kvm/guest_memfd.c         | 70 ++++++++++++++++++++++++++++++++++
 virt/kvm/kvm_main.c            | 44 ++++++++++-----------
 virt/kvm/kvm_mm.h              | 11 ++++++
 6 files changed, 128 insertions(+), 22 deletions(-)

diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index a5f9ee92f43e..5012afe6a9b5 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -9493,6 +9493,15 @@ take care to differentiate between these cases.
 The presence of this capability indicates that the nested KVM guest can
 start in ESA mode.
 
+8.48 KVM_CAP_GUEST_MEMFD_MMAP_LOG_DIRTY_PAGES
+---------------------------------------------
+
+:Architectures: all
+
+The presence of this capability indicates that memslots backed by a guest_memfd
+file descriptor created with the GUEST_MEMFD_FLAG_MMAP flag can have dirty
+page logging enabled.
+
 9. Known KVM API problems
 =========================
 
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index ab8cfaec82d3..c44e9253eb40 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -56,6 +56,7 @@
  */
 #define KVM_MEMSLOT_INVALID			(1UL << 16)
 #define KVM_MEMSLOT_GMEM_ONLY			(1UL << 17)
+#define MEMSLOT_USER_FLAGS_MASK			0xffff
 
 /*
  * Bit 63 of the memslot generation number is an "update in-progress flag",
@@ -731,6 +732,9 @@ static inline bool kvm_arch_has_private_mem(struct kvm *kvm)
 
 #ifdef CONFIG_KVM_GUEST_MEMFD
 bool kvm_arch_supports_gmem_init_shared(struct kvm *kvm);
+bool kvm_arch_supports_gmem_mmap_dirty_logging(struct kvm *kvm);
+int kvm_gmem_check_no_change(struct kvm *kvm, struct kvm_memory_slot *slot,
+			     unsigned int fd, loff_t offset);
 
 static inline u64 kvm_gmem_get_supported_flags(struct kvm *kvm)
 {
@@ -741,6 +745,17 @@ static inline u64 kvm_gmem_get_supported_flags(struct kvm *kvm)
 
 	return flags;
 }
+#else
+static inline bool kvm_arch_supports_gmem_mmap_dirty_logging(struct kvm *kvm)
+{
+	return false;
+}
+static inline int kvm_gmem_check_no_change(struct kvm *kvm, struct kvm_memory_slot *slot,
+					   unsigned int fd, loff_t offset)
+{
+	WARN_ON_ONCE(1);
+	return -EIO;
+}
 #endif
 
 #ifndef kvm_arch_has_readonly_mem
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 419011097fa8..5a53e2e19b2f 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -997,6 +997,7 @@ struct kvm_enable_cap {
 #define KVM_CAP_S390_KEYOP 247
 #define KVM_CAP_S390_VSIE_ESAMODE 248
 #define KVM_CAP_S390_HPAGE_2G 249
+#define KVM_CAP_GUEST_MEMFD_MMAP_LOG_DIRTY_PAGES 250
 
 struct kvm_irq_routing_irqchip {
 	__u32 irqchip;
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 43ef8e908aaf..210bdd76f0aa 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -622,6 +622,11 @@ bool __weak kvm_arch_supports_gmem_init_shared(struct kvm *kvm)
 	return true;
 }
 
+bool __weak kvm_arch_supports_gmem_mmap_dirty_logging(struct kvm *kvm)
+{
+	return false;
+}
+
 static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
 {
 	static const char *name = "[kvm-gmem]";
@@ -705,6 +710,66 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)
 	return __kvm_gmem_create(kvm, size, flags);
 }
 
+static int __kvm_gmem_check_no_change(struct kvm *kvm, struct kvm_memory_slot *old,
+				      struct file *old_file, unsigned int fd,
+				      loff_t offset)
+{
+	struct file *new_file;
+
+	new_file = fget(fd);
+	if (!new_file)
+		return -EBADF;
+	if (new_file != old_file) {
+		fput(new_file);
+		return -EBADF;
+	}
+	fput(new_file);
+
+	if (old->gmem.pgoff != offset >> PAGE_SHIFT)
+		return -EINVAL;
+
+	return 0;
+}
+
+int kvm_gmem_check_no_change(struct kvm *kvm, struct kvm_memory_slot *old,
+			     unsigned int fd, loff_t offset)
+{
+	CLASS(gmem_get_file, old_file)(old);
+
+	return __kvm_gmem_check_no_change(kvm, old, old_file, fd, offset);
+}
+
+int kvm_gmem_change_flags(struct kvm *kvm, struct kvm_memory_slot *old,
+			  struct kvm_memory_slot *new, unsigned int fd,
+			  loff_t offset)
+{
+	struct gmem_file *old_f;
+	int ret;
+
+	lockdep_assert_held(&kvm->slots_lock);
+
+	if (!kvm_memslot_is_gmem_only(old))
+		return -EINVAL;
+
+	CLASS(gmem_get_file, old_file)(old);
+
+	ret = __kvm_gmem_check_no_change(kvm, old, old_file, fd, offset);
+	if (ret)
+		return ret;
+
+	old_f = old_file->private_data;
+	if (xa_load(&old_f->bindings, old->gmem.pgoff) != memslot_to_xa_value(new)) {
+		WARN_ON_ONCE(xa_to_value(xa_load(&old_f->bindings, old->gmem.pgoff)));
+		return -EIO;
+	}
+
+	new->gmem.file = old->gmem.file;
+	new->gmem.pgoff = old->gmem.pgoff;
+	new->flags |= KVM_MEMSLOT_GMEM_ONLY;
+
+	return 0;
+}
+
 int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot,
 		  unsigned int fd, uoff_t offset)
 {
@@ -734,6 +799,11 @@ int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot,
 	if (!PAGE_ALIGNED(offset) || offset + size > i_size_read(inode))
 		goto err;
 
+	if (slot->flags & KVM_MEM_LOG_DIRTY_PAGES &&
+	    (!kvm_gmem_supports_mmap(inode) ||
+	     !kvm_arch_supports_gmem_mmap_dirty_logging(kvm)))
+		goto err;
+
 	filemap_invalidate_lock(inode->i_mapping);
 
 	start = offset >> PAGE_SHIFT;
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index e44c20c04961..af380e8f2b68 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -1573,14 +1573,14 @@ static void kvm_replace_memslot(struct kvm *kvm,
 static int check_memory_region_flags(struct kvm *kvm,
 				     const struct kvm_userspace_memory_region2 *mem)
 {
-	u32 valid_flags = KVM_MEM_LOG_DIRTY_PAGES;
+	u32 valid_flags = 0;
 
 	if (IS_ENABLED(CONFIG_KVM_GUEST_MEMFD))
 		valid_flags |= KVM_MEM_GUEST_MEMFD;
 
-	/* Dirty logging private memory is not currently supported. */
-	if (mem->flags & KVM_MEM_GUEST_MEMFD)
-		valid_flags &= ~KVM_MEM_LOG_DIRTY_PAGES;
+	if (!(mem->flags & KVM_MEM_GUEST_MEMFD) ||
+	    kvm_arch_supports_gmem_mmap_dirty_logging(kvm))
+		valid_flags |= KVM_MEM_LOG_DIRTY_PAGES;
 
 	/*
 	 * GUEST_MEMFD is incompatible with read-only memslots, as writes to
@@ -1739,16 +1739,6 @@ static void kvm_commit_memory_region(struct kvm *kvm,
 		 */
 		if (old->dirty_bitmap && !new->dirty_bitmap)
 			kvm_destroy_dirty_bitmap(old);
-
-		/*
-		 * Unbind the guest_memfd instance as needed; the @new slot has
-		 * already created its own binding.  TODO: Drop the WARN when
-		 * dirty logging guest_memfd memslots is supported.  Until then,
-		 * flags-only changes on guest_memfd slots should be impossible.
-		 */
-		if (WARN_ON_ONCE(old->flags & KVM_MEM_GUEST_MEMFD))
-			kvm_gmem_unbind(old);
-
 		/*
 		 * The final quirk.  Free the detached, old slot, but only its
 		 * memory, not any metadata.  Metadata, including arch specific
@@ -2073,22 +2063,27 @@ static int kvm_set_memory_region(struct kvm *kvm,
 		if ((kvm->nr_memslot_pages + npages) < kvm->nr_memslot_pages)
 			return -EINVAL;
 	} else { /* Modify an existing slot. */
-		/* Private memslots are immutable, they can only be deleted. */
-		if (mem->flags & KVM_MEM_GUEST_MEMFD)
-			return -EINVAL;
 		if ((mem->userspace_addr != old->userspace_addr) ||
 		    (npages != old->npages) ||
 		    ((mem->flags ^ old->flags) & (KVM_MEM_READONLY | KVM_MEM_GUEST_MEMFD)))
 			return -EINVAL;
 
-		if (base_gfn != old->base_gfn)
+		if (base_gfn != old->base_gfn) {
 			change = KVM_MR_MOVE;
-		else if (mem->flags != old->flags)
+		} else if (mem->flags != (old->flags & MEMSLOT_USER_FLAGS_MASK)) {
 			change = KVM_MR_FLAGS_ONLY;
-		else /* Nothing to change. */
+		} else if (mem->flags & KVM_MEM_GUEST_MEMFD) {
+			return kvm_gmem_check_no_change(kvm, old, mem->guest_memfd,
+							mem->guest_memfd_offset);
+		} else {
 			return 0;
+		}
 	}
 
+	if (mem->flags & KVM_MEM_GUEST_MEMFD &&
+	    change != KVM_MR_CREATE && change != KVM_MR_FLAGS_ONLY)
+		return -EINVAL;
+
 	if ((change == KVM_MR_CREATE || change == KVM_MR_MOVE) &&
 	    kvm_check_memslot_overlap(slots, id, base_gfn, base_gfn + npages))
 		return -EEXIST;
@@ -2105,7 +2100,12 @@ static int kvm_set_memory_region(struct kvm *kvm,
 	new->flags = mem->flags;
 	new->userspace_addr = mem->userspace_addr;
 	if (mem->flags & KVM_MEM_GUEST_MEMFD) {
-		r = kvm_gmem_bind(kvm, new, mem->guest_memfd, mem->guest_memfd_offset);
+		if (change == KVM_MR_CREATE) {
+			r = kvm_gmem_bind(kvm, new, mem->guest_memfd, mem->guest_memfd_offset);
+		} else if (change == KVM_MR_FLAGS_ONLY) {
+			r = kvm_gmem_change_flags(kvm, old, new, mem->guest_memfd,
+						  mem->guest_memfd_offset);
+		}
 		if (r)
 			goto out;
 	}
@@ -2117,7 +2117,7 @@ static int kvm_set_memory_region(struct kvm *kvm,
 	return 0;
 
 out_unbind:
-	if (mem->flags & KVM_MEM_GUEST_MEMFD)
+	if ((mem->flags & KVM_MEM_GUEST_MEMFD) && change == KVM_MR_CREATE)
 		kvm_gmem_unbind(new);
 out:
 	kfree(new);
diff --git a/virt/kvm/kvm_mm.h b/virt/kvm/kvm_mm.h
index 7510ca915dd1..c58dfeb0f3df 100644
--- a/virt/kvm/kvm_mm.h
+++ b/virt/kvm/kvm_mm.h
@@ -77,6 +77,9 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args);
 int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot,
 		  unsigned int fd, uoff_t offset);
 void kvm_gmem_unbind(struct kvm_memory_slot *slot);
+int kvm_gmem_change_flags(struct kvm *kvm, struct kvm_memory_slot *old,
+			  struct kvm_memory_slot *new, unsigned int fd,
+			  loff_t offset);
 #else
 static inline int kvm_gmem_init(struct module *module)
 {
@@ -95,6 +98,14 @@ static inline void kvm_gmem_unbind(struct kvm_memory_slot *slot)
 {
 	WARN_ON_ONCE(1);
 }
+static inline int kvm_gmem_change_flags(struct kvm *kvm,
+					struct kvm_memory_slot *old,
+					struct kvm_memory_slot *new,
+					unsigned int fd, loff_t offset)
+{
+	WARN_ON_ONCE(1);
+	return -EIO;
+}
 #endif /* CONFIG_KVM_GUEST_MEMFD */
 
 #endif /* __KVM_MM_H__ */
-- 
2.43.0



^ permalink raw reply related

* Re: [PATCH v15 5/9] mm/hwpoison: return -EFAULT when copy fail in copy_mc_[user]_highpage()
From: Ashok Raj @ 2026-07-02 14:37 UTC (permalink / raw)
  To: Ruidong Tian
  Cc: catalin.marinas, will, rafael, tony.luck, guohanjun, mchehab,
	xueshuai, tongtiangen, james.morse, robin.murphy, andreyknvl,
	dvyukov, vincenzo.frascino, mpe, npiggin, ryabinin.a.a, glider,
	christophe.leroy, aneesh.kumar, naveen.n.rao, tglx, mingo,
	linux-arm-kernel, linux-mm, linuxppc-dev, linux-kernel, kasan-dev,
	Jonathan Cameron, Mauro Carvalho Chehab, Ashok Raj
In-Reply-To: <20260618092124.3901230-6-tianruidong@linux.alibaba.com>

On Thu, Jun 18, 2026 at 05:21:19PM +0800, Ruidong Tian wrote:
> From: Tong Tiangen <tongtiangen@huawei.com>
> 
> Currently, copy_mc_[user]_highpage() returns zero on success, or in case
> of failures, the number of bytes that weren't copied.
> 
> While tracking the number of not copied works fine for x86 and PPC, There
> are some difficulties in doing the same thing on ARM64 because there is no
> available caller-saved register in copy_page()(lib/copy_page.S) to save
> "bytes not copied", and the following copy_mc_page() will also encounter
> the same problem.
> 
> Consider the caller of copy_mc_[user]_highpage() cannot do any processing
> on the remaining data(The page has hardware errors), they only check if
> copy was succeeded or not, make the interface more generic by using an
> error code when copy fails (-EFAULT) or return zero on success.

Agreed on the rationale -- none of the callers can act on partial byte
count, so a plain success/-EFAULT is better contract.

> 
> Signed-off-by: Tong Tiangen <tongtiangen@huawei.com>
> Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> Reviewed-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
> Reviewed-by: Shuai Xue <xueshuai@linux.alibaba.com>
> ---
>  include/linux/highmem.h | 8 ++++----
>  mm/khugepaged.c         | 4 ++--
>  2 files changed, 6 insertions(+), 6 deletions(-)
> 
> diff --git a/include/linux/highmem.h b/include/linux/highmem.h
> index af03db851a1d..18dc4aca4aa1 100644
> --- a/include/linux/highmem.h
> +++ b/include/linux/highmem.h
> @@ -427,8 +427,8 @@ static inline void copy_highpage(struct page *to, struct page *from)
>  /*
>   * If architecture supports machine check exception handling, define the
>   * #MC versions of copy_user_highpage and copy_highpage. They copy a memory
> - * page with #MC in source page (@from) handled, and return the number
> - * of bytes not copied if there was a #MC, otherwise 0 for success.
> + * page with #MC in source page (@from) handled, and return -EFAULT if there
> + * was a #MC, otherwise 0 for success.
>   */
>  static inline int copy_mc_user_highpage(struct page *to, struct page *from,
>  					unsigned long vaddr, struct vm_area_struct *vma)
> @@ -447,7 +447,7 @@ static inline int copy_mc_user_highpage(struct page *to, struct page *from,
>  	if (ret)
>  		memory_failure_queue(page_to_pfn(from), 0);
>  
> -	return ret;
> +	return ret ? -EFAULT : 0;
>  }
>  
>  static inline int copy_mc_highpage(struct page *to, struct page *from)
> @@ -466,7 +466,7 @@ static inline int copy_mc_highpage(struct page *to, struct page *from)
>  	if (ret)
>  		memory_failure_queue(page_to_pfn(from), 0);
>  
> -	return ret;
> +	return ret ? -EFAULT : 0;
>  }

the critical part of this change is the semantic flip: the return value
goes from a count >= 0 to one that is < 0 on error. 

You have correctly converted the two call sites in khugepaged.c, but
confirm if a tree wide audit has been done.


with the caller audit confirmed

Reviewed-by: Ashok Raj <ashok.raj@oss.qualcomm.com>


^ permalink raw reply

* Re: [PATCH] drm/sun4i: Remove dependency on DRM simple helpers
From: Jernej Škrabec @ 2026-07-02 14:39 UTC (permalink / raw)
  To: Chen-Yu Tsai, Maarten Lankhorst, Maxime Ripard, David Airlie,
	Simona Vetter, Samuel Holland, Diogo Silva, Thomas Zimmermann
  Cc: dri-devel, linux-arm-kernel, linux-sunxi, linux-kernel
In-Reply-To: <f3f2320f-dfe7-4728-9cf4-c3eaab33d09e@suse.de>

Dne ponedeljek, 29. junij 2026 ob 08:51:52 Srednjeevropski poletni čas je Thomas Zimmermann napisal(a):
> Hi
> 
> Am 27.06.26 um 08:55 schrieb Jernej Škrabec:
> > Dne torek, 23. junij 2026 ob 22:34:40 Srednjeevropski poletni čas je Diogo Silva napisal(a):
> >> Simple KMS helper are deprecated since they only add an intermediate
> >> layer between drivers and the atomic modesetting.
> >> This patch removes the dependency on drm simple helpers from sun4i
> >> DRM drivers.
> >>
> >> Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
> > This doesn't look as useful change. Is this tree-wide attempt? If so, it
> > should be done in one go in one patch series, coordinated with DRM
> > maintainers.
> 
> Yes, the interface is supposed to go away because it is fairly useless.  
> It's a low-effort task for beginners. Hence many people send out such 
> clean-up patches for the various drivers.  If the change is acceptable 
> to you, it would be appreciated if you could merge it into the driver.

Ok.

Reviewed-by: Jernej Skrabec <jernej.skrabec@gmail.com>

Best regards,
Jernej




^ permalink raw reply

* Re: [PATCH rc v7 0/7] iommu/arm-smmu-v3: Fix device crash on kdump kernel
From: Jason Gunthorpe @ 2026-07-02 14:41 UTC (permalink / raw)
  To: Pranjal Shrivastava
  Cc: Mostafa Saleh, Nicolin Chen, will, robin.murphy, joro, kees,
	baolu.lu, kevin.tian, miko.lenczewski, linux-arm-kernel, iommu,
	linux-kernel, stable, jamien
In-Reply-To: <akUX3T3fIoN42sdM@google.com>

On Wed, Jul 01, 2026 at 01:36:29PM +0000, Pranjal Shrivastava wrote:

> However, I agree with the overall problem, i.e. IF an active device
> unmaps the DMA addr after the transaction in the previous kernel, 
> (with the SMMU powered ON) but the TLBI was missed due to a crash/panic,
> Any new DMA in the new kernel may alias onto a memory in the previous 
> (crashed) kernel, not the kdump kernel.

It looks like there is an issue in this series, it isn't doing
anything with the VMIDs.

The VMIDs that are in-used by the adopted stream table have to be
removed from the idr as well (and similarly for ASID if we don't have
VMID HW support).

Then the VMIDs that may be dirtied by the prior kernel remain isolated
and are never re-used by the new kernel. When the new kernel wants to
do DMA it will replace the STE with a new, clean VMID, and there is no
problem.

Jason


^ permalink raw reply

* [PATCH v8 01/39] video/hdmi: Introduce HDMI version enum
From: Cristian Ciocaltea @ 2026-07-02 14:46 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Daniel Stone,
	Dave Stevenson, Maíra Canal, Raspberry Pi Kernel Maintenance
  Cc: kernel, dri-devel, linux-kernel, linux-arm-kernel, linux-rockchip
In-Reply-To: <20260702-dw-hdmi-qp-scramb-v8-0-d79890d00b6a@collabora.com>

Add an enum to represent HDMI specification versions.  This will be used
by upcoming changes to associate HDMI connectors and bridges with a
maximum supported version and to simplify handling of version-dependent
features.

Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
 include/linux/hdmi.h | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/include/linux/hdmi.h b/include/linux/hdmi.h
index 8dab78e1f61b..b80a5ee63bb2 100644
--- a/include/linux/hdmi.h
+++ b/include/linux/hdmi.h
@@ -27,6 +27,18 @@
 #include <linux/types.h>
 #include <linux/device.h>
 
+enum hdmi_version {
+	HDMI_VERSION_UNKNOWN,
+	HDMI_VERSION_1_0,
+	HDMI_VERSION_1_1,
+	HDMI_VERSION_1_2,
+	HDMI_VERSION_1_3,
+	HDMI_VERSION_1_4,
+	HDMI_VERSION_2_0,
+	HDMI_VERSION_2_1,
+	HDMI_VERSION_2_2,
+};
+
 enum hdmi_packet_type {
 	HDMI_PACKET_TYPE_NULL = 0x00,
 	HDMI_PACKET_TYPE_AUDIO_CLOCK_REGEN = 0x01,

-- 
2.54.0



^ permalink raw reply related

* [PATCH v8 03/39] drm/display: bridge_connector: Pass HDMI capabilities through caps struct
From: Cristian Ciocaltea @ 2026-07-02 14:46 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Daniel Stone,
	Dave Stevenson, Maíra Canal, Raspberry Pi Kernel Maintenance
  Cc: kernel, dri-devel, linux-kernel, linux-arm-kernel, linux-rockchip
In-Reply-To: <20260702-dw-hdmi-qp-scramb-v8-0-d79890d00b6a@collabora.com>

Switch drm_bridge_connector_init() to
drmm_connector_hdmi_init_with_caps(), collecting the bridge's HDMI
properties into a stack drm_connector_hdmi_caps so future HDMI 2.x
capabilities can be plumbed without growing the argument list.

Introduce struct drm_bridge::supported_hdmi_ver to let bridges declare
the HDMI specification version they are conformant with, and forward it
to the connector layer through the new caps struct so HDMI 2.x features
can be gated on real source capability rather than guesswork.

Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 29 ++++++++++++++------------
 include/drm/drm_bridge.h                       |  6 ++++++
 2 files changed, 22 insertions(+), 13 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 92f8a2d7aab4..796069dbf1a1 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -823,8 +823,10 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 	struct drm_connector *connector;
 	struct i2c_adapter *ddc = NULL;
 	struct drm_bridge *panel_bridge __free(drm_bridge_put) = NULL;
-	unsigned int supported_formats = BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444);
-	unsigned int max_bpc = 8;
+	struct drm_connector_hdmi_caps caps = {
+		.supported_formats = BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+		.max_bpc = 8,
+	};
 	bool support_hdcp = false;
 	int connector_type;
 	int ret;
@@ -885,6 +887,7 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 		if (bridge->ops & DRM_BRIDGE_OP_HDMI) {
 			if (bridge_connector->bridge_hdmi)
 				return ERR_PTR(-EBUSY);
+
 			if (!bridge->funcs->hdmi_write_avi_infoframe ||
 			    !bridge->funcs->hdmi_clear_avi_infoframe ||
 			    !bridge->funcs->hdmi_write_hdmi_infoframe ||
@@ -908,10 +911,12 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 
 			bridge_connector->bridge_hdmi = drm_bridge_get(bridge);
 
+			if (bridge->supported_hdmi_ver)
+				caps.supported_hdmi_ver = bridge->supported_hdmi_ver;
 			if (bridge->supported_formats)
-				supported_formats = bridge->supported_formats;
+				caps.supported_formats = bridge->supported_formats;
 			if (bridge->max_bpc)
-				max_bpc = bridge->max_bpc;
+				caps.max_bpc = bridge->max_bpc;
 		}
 
 		if (bridge->ops & DRM_BRIDGE_OP_HDMI_AUDIO) {
@@ -994,7 +999,7 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 
 	if (bridge_connector->bridge_hdmi) {
 		if (!connector->ycbcr_420_allowed)
-			supported_formats &= ~BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420);
+			caps.supported_formats &= ~BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420);
 
 		bridge_connector->hdmi_funcs = drm_bridge_connector_hdmi_funcs;
 
@@ -1010,14 +1015,12 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 			bridge_connector->hdmi_funcs.spd =
 				drm_bridge_connector_hdmi_spd_infoframe;
 
-		ret = drmm_connector_hdmi_init(drm, connector,
-					       bridge_connector->bridge_hdmi->vendor,
-					       bridge_connector->bridge_hdmi->product,
-					       &drm_bridge_connector_funcs,
-					       &bridge_connector->hdmi_funcs,
-					       connector_type, ddc,
-					       supported_formats,
-					       max_bpc);
+		ret = drmm_connector_hdmi_init_with_caps(drm, connector,
+							 bridge_connector->bridge_hdmi->vendor,
+							 bridge_connector->bridge_hdmi->product,
+							 &drm_bridge_connector_funcs,
+							 &bridge_connector->hdmi_funcs,
+							 connector_type, ddc, &caps);
 		if (ret)
 			return ERR_PTR(ret);
 	} else {
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 18f3db367dc1..2d8b5e4c64ba 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -1176,6 +1176,12 @@ struct drm_bridge {
 	 */
 	const char *product;
 
+	/**
+	 * @supported_hdmi_ver: HDMI version the bridge is conformant with.
+	 * This is only relevant if @DRM_BRIDGE_OP_HDMI is set.
+	 */
+	enum hdmi_version supported_hdmi_ver;
+
 	/**
 	 * @supported_formats: Bitmask of @drm_output_color_format listing
 	 * supported output formats. This is only relevant if

-- 
2.54.0



^ permalink raw reply related

* [PATCH v8 02/39] drm/connector: Add caps-based HDMI connector init helper
From: Cristian Ciocaltea @ 2026-07-02 14:46 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Daniel Stone,
	Dave Stevenson, Maíra Canal, Raspberry Pi Kernel Maintenance
  Cc: kernel, dri-devel, linux-kernel, linux-arm-kernel, linux-rockchip
In-Reply-To: <20260702-dw-hdmi-qp-scramb-v8-0-d79890d00b6a@collabora.com>

In preparation for adding HDMI 2.x source capabilities, introduce struct
drm_connector_hdmi_caps and a new drmm_connector_hdmi_init_with_caps()
helper.

The existing drmm_connector_hdmi_init() helper currently takes
individual capability arguments such as supported_formats and max_bpc.
Adding more HDMI-specific arguments to that function would not scale
well, so move those values into a dedicated capabilities structure and
implement the existing helper as a wrapper around the new caps-based
interface.

Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
 drivers/gpu/drm/drm_connector.c | 202 +++++++++++++++++++++++++++-------------
 include/drm/drm_connector.h     |  55 +++++++++++
 2 files changed, 193 insertions(+), 64 deletions(-)

diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index 8b4baed060f3..c7ce6b7bd8b0 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -542,6 +542,137 @@ int drmm_connector_init(struct drm_device *dev,
 }
 EXPORT_SYMBOL(drmm_connector_init);
 
+/**
+ * drmm_connector_hdmi_init_with_caps - Init a preallocated HDMI connector
+ * @dev: DRM device
+ * @connector: A pointer to the HDMI connector to init
+ * @vendor: HDMI Controller Vendor name
+ * @product: HDMI Controller Product name
+ * @funcs: callbacks for this connector
+ * @hdmi_funcs: HDMI-related callbacks for this connector
+ * @connector_type: user visible type of the connector
+ * @ddc: optional pointer to the associated ddc adapter
+ * @caps: optional HDMI connector capabilities
+ *
+ * Initialises a preallocated HDMI connector. Connectors can be
+ * subclassed as part of driver connector objects.
+ *
+ * Cleanup is automatically handled with a call to
+ * drm_connector_cleanup() in a DRM-managed action.
+ *
+ * The connector structure should be allocated with drmm_kzalloc().
+ *
+ * The @drm_connector_funcs.destroy hook must be NULL.
+ *
+ * Returns:
+ * Zero on success, error code on failure.
+ */
+int drmm_connector_hdmi_init_with_caps(struct drm_device *dev,
+				       struct drm_connector *connector,
+				       const char *vendor, const char *product,
+				       const struct drm_connector_funcs *funcs,
+				       const struct drm_connector_hdmi_funcs *hdmi_funcs,
+				       int connector_type,
+				       struct i2c_adapter *ddc,
+				       const struct drm_connector_hdmi_caps *caps)
+{
+	int ret;
+
+	if (!vendor || !product)
+		return -EINVAL;
+
+	if ((strlen(vendor) > DRM_CONNECTOR_HDMI_VENDOR_LEN) ||
+	    (strlen(product) > DRM_CONNECTOR_HDMI_PRODUCT_LEN))
+		return -EINVAL;
+
+	if (!(connector_type == DRM_MODE_CONNECTOR_HDMIA ||
+	      connector_type == DRM_MODE_CONNECTOR_HDMIB))
+		return -EINVAL;
+
+	if (!caps)
+		return -EINVAL;
+
+	if (!caps->supported_formats ||
+	    !(caps->supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444)))
+		return -EINVAL;
+
+	if (connector->ycbcr_420_allowed !=
+	    !!(caps->supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420)))
+		return -EINVAL;
+
+	if (!(caps->max_bpc == 8 || caps->max_bpc == 10 || caps->max_bpc == 12))
+		return -EINVAL;
+
+	if (!hdmi_funcs->avi.clear_infoframe ||
+	    !hdmi_funcs->avi.write_infoframe ||
+	    !hdmi_funcs->hdmi.clear_infoframe ||
+	    !hdmi_funcs->hdmi.write_infoframe)
+		return -EINVAL;
+
+	ret = drmm_connector_init(dev, connector, funcs, connector_type, ddc);
+	if (ret)
+		return ret;
+
+	connector->hdmi.supported_formats = caps->supported_formats;
+
+	/*
+	 * The supported HDMI version can be used to infer the maximum TMDS
+	 * character rate allowed by the specification. Some controllers,
+	 * however, may support a lower rate than that version would imply.
+	 *
+	 * A non-zero caps->max_tmds_char_rate lets drivers override the
+	 * inferred limit with the actual controller capability. A value of
+	 * zero keeps the default limit inferred from supported_hdmi_ver.
+	 */
+	if (caps->supported_hdmi_ver >= HDMI_VERSION_2_0)
+		connector->hdmi.max_tmds_char_rate = HDMI_2_0_TMDS_CHAR_RATE_MAX_HZ;
+	else if (caps->supported_hdmi_ver >= HDMI_VERSION_1_3)
+		connector->hdmi.max_tmds_char_rate = HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ;
+	else if (caps->supported_hdmi_ver >= HDMI_VERSION_1_0)
+		connector->hdmi.max_tmds_char_rate = HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ;
+
+	if (caps->max_tmds_char_rate) {
+		if (caps->max_tmds_char_rate > connector->hdmi.max_tmds_char_rate)
+			return -EINVAL;
+		connector->hdmi.max_tmds_char_rate = caps->max_tmds_char_rate;
+	}
+
+	strtomem_pad(connector->hdmi.vendor, vendor, 0);
+	strtomem_pad(connector->hdmi.product, product, 0);
+
+	/*
+	 * drm_connector_attach_max_bpc_property() requires the
+	 * connector to have a state.
+	 */
+	if (connector->funcs->atomic_create_state) {
+		struct drm_connector_state *state;
+
+		state = connector->funcs->atomic_create_state(connector);
+		if (IS_ERR(state))
+			return PTR_ERR(state);
+
+		connector->state = state;
+	} else if (connector->funcs->reset) {
+		connector->funcs->reset(connector);
+	}
+
+	drm_connector_attach_max_bpc_property(connector, 8, caps->max_bpc);
+	connector->max_bpc = caps->max_bpc;
+
+	if (caps->max_bpc > 8)
+		drm_connector_attach_hdr_output_metadata_property(connector);
+
+	ret = drm_connector_attach_color_format_property(connector,
+							 caps->supported_formats);
+	if (ret)
+		return ret;
+
+	connector->hdmi.funcs = hdmi_funcs;
+
+	return 0;
+}
+EXPORT_SYMBOL(drmm_connector_hdmi_init_with_caps);
+
 /**
  * drmm_connector_hdmi_init - Init a preallocated HDMI connector
  * @dev: DRM device
@@ -578,71 +709,14 @@ int drmm_connector_hdmi_init(struct drm_device *dev,
 			     unsigned long supported_formats,
 			     unsigned int max_bpc)
 {
-	int ret;
+	struct drm_connector_hdmi_caps caps = {
+		.supported_formats = supported_formats,
+		.max_bpc = max_bpc,
+	};
 
-	if (!vendor || !product)
-		return -EINVAL;
-
-	if ((strlen(vendor) > DRM_CONNECTOR_HDMI_VENDOR_LEN) ||
-	    (strlen(product) > DRM_CONNECTOR_HDMI_PRODUCT_LEN))
-		return -EINVAL;
-
-	if (!(connector_type == DRM_MODE_CONNECTOR_HDMIA ||
-	      connector_type == DRM_MODE_CONNECTOR_HDMIB))
-		return -EINVAL;
-
-	if (!supported_formats || !(supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444)))
-		return -EINVAL;
-
-	if (connector->ycbcr_420_allowed != !!(supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420)))
-		return -EINVAL;
-
-	if (!(max_bpc == 8 || max_bpc == 10 || max_bpc == 12))
-		return -EINVAL;
-
-	if (!hdmi_funcs->avi.clear_infoframe ||
-	    !hdmi_funcs->avi.write_infoframe ||
-	    !hdmi_funcs->hdmi.clear_infoframe ||
-	    !hdmi_funcs->hdmi.write_infoframe)
-		return -EINVAL;
-
-	ret = drmm_connector_init(dev, connector, funcs, connector_type, ddc);
-	if (ret)
-		return ret;
-
-	connector->hdmi.supported_formats = supported_formats;
-	strtomem_pad(connector->hdmi.vendor, vendor, 0);
-	strtomem_pad(connector->hdmi.product, product, 0);
-
-	/*
-	 * drm_connector_attach_max_bpc_property() requires the
-	 * connector to have a state.
-	 */
-	if (connector->funcs->atomic_create_state) {
-		struct drm_connector_state *state;
-
-		state = connector->funcs->atomic_create_state(connector);
-		if (IS_ERR(state))
-			return PTR_ERR(state);
-
-		connector->state = state;
-	} else if (connector->funcs->reset) {
-		connector->funcs->reset(connector);
-	}
-
-	drm_connector_attach_max_bpc_property(connector, 8, max_bpc);
-	connector->max_bpc = max_bpc;
-
-	if (max_bpc > 8)
-		drm_connector_attach_hdr_output_metadata_property(connector);
-
-	ret = drm_connector_attach_color_format_property(connector, supported_formats);
-	if (ret)
-		return ret;
-
-	connector->hdmi.funcs = hdmi_funcs;
-
-	return 0;
+	return drmm_connector_hdmi_init_with_caps(dev, connector, vendor,
+						  product, funcs, hdmi_funcs,
+						  connector_type, ddc, &caps);
 }
 EXPORT_SYMBOL(drmm_connector_hdmi_init);
 
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index af075c38f4db..961a729d0869 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -2029,6 +2029,47 @@ struct drm_connector_hdmi_audio {
 	int dai_port;
 };
 
+/**
+ * struct drm_connector_hdmi_caps - HDMI capabilities structure
+ *
+ * This structure is required to initialize the connector using
+ * drmm_connector_hdmi_init_with_caps().
+ */
+struct drm_connector_hdmi_caps {
+	/**
+	 * @supported_hdmi_ver:
+	 *
+	 * Maximum HDMI specification version supported by the controller side
+	 * of this connector. This describes the controller capability only;
+	 * the effective link capabilities may be further restricted by the
+	 * sink, bridge, or mode validation.
+	 *
+	 * HDMI_VERSION_UNKNOWN means legacy/default behaviour.
+	 */
+	enum hdmi_version supported_hdmi_ver;
+
+	/**
+	 * @supported_formats:
+	 *
+	 * Bitmask of @drm_output_color_format listing supported output formats.
+	 */
+	unsigned long supported_formats;
+
+	/**
+	 * @max_tmds_char_rate:
+	 *
+	 * Maximum TMDS character rate supported by the source, in Hz.
+	 * A value of 0 means the core should use the default limit implied by
+	 * @supported_hdmi_ver.
+	 */
+	unsigned long long max_tmds_char_rate;
+
+	/**
+	 * @max_bpc: Maximum bits per char the HDMI connector supports.
+	 */
+	unsigned int max_bpc;
+};
+
 /*
  * struct drm_connector_hdmi - DRM Connector HDMI-related structure
  */
@@ -2051,6 +2092,12 @@ struct drm_connector_hdmi {
 	 */
 	unsigned long supported_formats;
 
+	/**
+	 * @max_tmds_char_rate: Maximum TMDS character rate, in Hz,
+	 * supported by the controller.
+	 */
+	unsigned long long max_tmds_char_rate;
+
 	/**
 	 * @funcs: HDMI connector Control Functions
 	 */
@@ -2555,6 +2602,14 @@ int drmm_connector_hdmi_init(struct drm_device *dev,
 			     struct i2c_adapter *ddc,
 			     unsigned long supported_formats,
 			     unsigned int max_bpc);
+int drmm_connector_hdmi_init_with_caps(struct drm_device *dev,
+				       struct drm_connector *connector,
+				       const char *vendor, const char *product,
+				       const struct drm_connector_funcs *funcs,
+				       const struct drm_connector_hdmi_funcs *hdmi_funcs,
+				       int connector_type,
+				       struct i2c_adapter *ddc,
+				       const struct drm_connector_hdmi_caps *caps);
 void drm_connector_attach_edid_property(struct drm_connector *connector);
 int drm_connector_register(struct drm_connector *connector);
 int drm_connector_dynamic_register(struct drm_connector *connector);

-- 
2.54.0



^ permalink raw reply related

* [PATCH v8 00/39] Add HDMI 2.0 support to DW HDMI QP TX
From: Cristian Ciocaltea @ 2026-07-02 14:46 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Daniel Stone,
	Dave Stevenson, Maíra Canal, Raspberry Pi Kernel Maintenance
  Cc: kernel, dri-devel, linux-kernel, linux-arm-kernel, linux-rockchip,
	Dmitry Baryshkov, Diederik de Haas, Maud Spierings

Enable HDMI 2.0 display modes (e.g. 4K@60Hz) on the Synopsys DW HDMI QP
TX controller, as found in Rockchip RK3576 & RK3588 SoCs, by adding SCDC
management for high TMDS clock ratio and scrambling.

Since SCDC state is lost on sink disconnects, the bridge driver needs to
trigger a CRTC reset during connector detection.  To support this, the
series introduces the connector and bridge scrambling infrastructure
(patches 1-8), wires it up through the bridge connector layer with an
atomic-aware detect_ctx hook (patches 9-11), then implements the SCDC
scrambling feature in the DW HDMI QP bridge driver (patches 12-15).

Patches 16-18 are minor cleanups in the Rockchip platform driver.
Patches 19-23 improve HPD handling by deferring IRQ registration until
the connector is fully initialized, adding .enable_hpd()/.disable_hpd()
PHY ops, and restricting HPD events to the affected connector.

Patches 24-25 convert vc4 HDMI to the common SCDC scrambling helpers as
a proof of reuse, replacing the driver-local scrambling implementation.

Patches 26-30 add KUnit tests: connector scrambler validation, a new
4K@60Hz 600MHz EDID, hdmi_state_helper scrambling tests, and EDID
conformity fixes for some of the existing test blobs.

This has been tested on the following boards:

* Radxa ROCK 5B (RK3588)
* Radxa ROCK 4D (RK3576)
* Raspberry Pi 5 Model B Rev 1.1 (BCM2712 D0)

Note that commit d87773de9efe1 ("clocksource/drivers/arm_arch_timer:
Default to EL2 virtual timer when running VHE"), introduced in v7.2-rc1,
causes Raspberry Pi 5 to hang during boot.  Reverting the commit
restores normal boot.  This issue has already been reported in [1];
alternatively, the workaround proposed in [2] can be applied.

Regards,
Cristian

[1] https://lore.kernel.org/all/ea15cce1-b393-43f6-8d58-3d6f90f0c0cd@samsung.com/
[2] https://lore.kernel.org/all/20260619204832.586079-1-dan@reactivated.net/

Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
Changes in v8:
- Added an enum hdmi_version under video/hdmi to describe the HDMI
  specification version a source/sink supports (new patch 1)
- Introduced HDMI connector capabilities API and reworked the
  infrastructure patches on top of it: drmm_connector_hdmi_init() now
  takes a drm_connector_hdmi_caps struct that carries supported_formats,
  max_bpc, supported_hdmi_ver and max_tmds_char_rate (new patches 2-3)
- Moved all the new SCDC helpers covering both source and sink to
  display/drm_hdmi_helper.c
- Renamed drm_scdc_{start,stop}_scrambling() to
  drm_connector_hdmi_{enable,disable}_scrambling() and added a
  connector-state argument so the scrambler_needed flag is checked
  centrally instead of in every driver
- Renamed drm_scdc_sync_status() to drm_connector_hdmi_sync_scdc(),
  aligned its lifecycle with vc4_hdmi_reset_link(), and removed the
  internal drm_scdc_reset_crtc() helper; it now also gates on CRTC state
  and an in-flight commit (Maxime)
- Changed the prototype of drm_atomic_helper_connector_hdmi_hotplug() to
  take an acquire context and an int return, instead of adding a new
  _ctx variant (Maxime)
- Added drm_hdmi_mode_needs_scrambling() to centralise the above-340 MHz
  decision (new patch 7)
- Made SCDC source version negotiation optional and moved it to a
  dedicated patch "drm/display: hdmi: Advertise SCDC source version when
  scrambling" (new patch 8), backed by a new
  drm_scdc_set_source_version() helper in "drm/display: scdc-helper: Add
  helper to set SCDC version information" (new patch 6)
- Added a fallback TMDS rate validation patch using the connector-level
  max_tmds_char_rate when the driver provides no .tmds_char_rate_valid()
  hook (new patch 9)
- Split scrambler_needed flag handling into its own hdmi-state-helper
  dedicate change (new patch 11)
- VC4:
  * Replaced vc4_hdmi_mode_needs_scrambling() with
    drm_hdmi_mode_needs_scrambling() (new patch 30)
  * Restored drm_dev_{enter,exit}() pairing around the converted
    scrambling paths
  * Removed now unused output_{bpc,color_format} fields from vc4_hdmi as
    part of the SCDC conversion
- KUnit:
  * Covered the caps-based connector init path: NULL caps, inferred
    max_tmds_char_rate per supported_hdmi_ver, and override validation
    (new patch 32)
  * Switched drm_hdmi_state_helper_test to the caps-based init helper
    and exposed it through
    drm_kunit_helper_connector_hdmi_init_with_caps_edid_funcs() (new
    patch 34)
  * Added max_tmds_char_rate fallback coverage in
    drm_hdmi_state_helper_test (new patch 35)
- Rebased onto latest drm-misc-next and dropped the already applied
  "drm/fb-helper: Remove unused local variable in hotplug_event()" patch
- Link to v7: https://patch.msgid.link/20260602-dw-hdmi-qp-scramb-v7-0-445eb54ee1ed@collabora.com

Changes in v7:
- Split the scrambler_needed flag computation from the SCDC scrambling
  helpers into a standalone hdmi_state_helper patch for clarity (new
  patch 6)
- Added vc4 HDMI conversion to common TMDS char rate constants and SCDC
  scrambling helpers as a proof of reuse (new patches 24-25)
- Added KUnit tests for connector scrambler_supported/callback
  consistency validation (new patch 26)
- Added 4K@60Hz 600MHz TMDS test EDID for high-rate scrambling coverage
  (new patch 27)
- Added KUnit tests for hdmi_state_helper scrambler_needed flag:
  low-rate, high-rate, and source-no-support cases (new patch 28)
- Fixed EDID conformity issues (edid-decode v1.33.0 failures) in the
  existing 1080p+4K YUV420 200MHz and 4K RGB/YUV 340MHz test EDIDs
  without changing the capabilities used by existing tests (new patches
  29-30)
- Rebased onto latest drm-misc-next
- Link to v6: https://patch.msgid.link/20260520-dw-hdmi-qp-scramb-v6-0-24b74603b782@collabora.com

Changes in v6:
- Collected R-b & A-b tags from Dmitry and Heiko
- Restructured the series from 10 into 22 patches, splitting the SCDC
  scrambling helpers, connector infrastructure, bridge operations,
  bridge_connector plumbing, dw-hdmi-qp implementation, and Rockchip
  platform changes into distinct commits
- Added drm_scdc_dbg() macro to simplify debug messages (new patch)
- Added drm_scdc_start/stop/sync_status() helpers for full scrambling
  lifecycle management (reworked from v5 patch 5)
- Added drm_atomic_helper_connector_hdmi_hotplug_ctx() to propagate
  modeset acquire context for SCDC sync on hotplug (new patch)
- Added connector scrambler callbacks and SCDC work/flag infrastructure
  as a separate patch (split from v5 patch 5)
- Added DRM_BRIDGE_OP_HDMI_SCRAMBLER bridge operation with
  hdmi_scrambler_enable/disable callbacks (new patch)
- Implemented bridge_connector scrambler interface wiring (new patch)
- Added .enable_hpd()/.disable_hpd() PHY ops for dw-hdmi-qp bridge and
  Rockchip platform drivers, replacing the obsolete .setup_hpd() op
- Added dw_hdmi_qp_hpd_notify() helper for targeted connector-only HPD
  notification (split from v5 patch 10)
- Dropped drm_fb_helper_hotplug_event() unused variable (new cleanup)
- Dropped unused drm_simple_kms_helper.h include (new cleanup)
- Masked RK3576 HPD IRQ in io_init() for consistency with RK3588
- Rebased onto latest drm-misc-next
- Link to v5: https://patch.msgid.link/20260426-dw-hdmi-qp-scramb-v5-0-d778e70c317b@collabora.com

Changes in v5:
- Added new patches: 1/10, 3/10, 6/10, 7/10, 8/10
- Removed redundant no-op error check in drm_bridge_helper_reset_crtc()
  (patch 1)
- Removed the EDEADLK retry loop from the bridge .detect_ctx() callback,
  as that's already handled in the drm_bridge_detect_ctx() helper or by
  the caller when ctx is provided (patch 2)
- Refactored drm_bridge_detect() to delegate to drm_bridge_detect_ctx()
  and added a WARN_ON for unexpected negative return values (patch 2)
- Split the bridge-connector .detect_ctx() switch into a preparatory
  patch to use cached connector status in .get_modes() (patch 3)
- Improved error handling in SCDC scrambling setup: roll back high TMDS
  clock ratio on scrambling failure, reset scramb_enabled flag on
  set_scramb failure, and add SCDC version read/write error checks
  (patch 5)
- Annotated scramb_enabled with READ_ONCE/WRITE_ONCE for cross-context
  access between modeset paths and the scrambling work item (patch 5)
- Renamed SCDC_MIN_SOURCE_VERSION to SCDC_MAX_SOURCE_VERSION (patch 5)
- Rate limited i2c error messages (patch 6)
- Added missing newlines in dev_err_probe() messages (patch 7)
- Replaced indirect device pointer accesses with local dev variable in
  bind() (patch 8)
- Split the HPD connector restriction (formerly patch 4/4): register HPD
  IRQ after connector setup first (patch 9), then restrict HPD event to
  the affected connector (patch 10); also collected R-b from Heiko
- Rebased onto latest drm-misc-next
- Link to v4: https://lore.kernel.org/r/20260303-dw-hdmi-qp-scramb-v4-0-317d3b8bd219@collabora.com

Changes in v4:
- Fixed conflicts while rebasing onto latest drm-misc-next
- Link to v3: https://lore.kernel.org/r/20260119-dw-hdmi-qp-scramb-v3-0-bd8611730fc1@collabora.com

Changes in v3:
- Used drm_bridge_helper_reset_crtc() helper to reset the display
  pipeline and got rid of some boilerplate code (Maxime)
- Rebased onto latest drm-misc-next
- Link to v2: https://lore.kernel.org/r/20260113-dw-hdmi-qp-scramb-v2-0-ae7b2c58d24d@collabora.com

Changes in v2:
- Collected Tested-by tags from Diederik and Maud
- Rebased onto latest drm-misc-next
- Ensured the recently introduced 'no-hpd' support for dealing with
  unconnected/repurposed/broken HPD pin is limited to HDMI 1.4 rates
- Link to v1: https://lore.kernel.org/r/20251203-dw-hdmi-qp-scramb-v1-0-836fe7401a69@collabora.com

---
Cristian Ciocaltea (39):
      video/hdmi: Introduce HDMI version enum
      drm/connector: Add caps-based HDMI connector init helper
      drm/display: bridge_connector: Pass HDMI capabilities through caps struct
      drm/connector: Add HDMI 2.0 scrambler infrastructure
      drm/display: scdc-helper: Add macro for connector-prefixed debug messages
      drm/display: scdc-helper: Add helper to set SCDC version information
      drm/display: hdmi: Add HDMI 2.0 scrambling management helpers
      drm/display: hdmi: Advertise SCDC source version when scrambling
      drm/display: hdmi-state-helper: Add fallback TMDS rate validation
      drm/display: hdmi-state-helper: Sync SCDC state on hotplug
      drm/display: hdmi-state-helper: Set HDMI scrambling requirement
      drm/bridge: Remove redundant error check in drm_bridge_helper_reset_crtc()
      drm/bridge: Add bridge ops for source-side HDMI 2.0 scrambling
      drm/display: bridge_connector: Use cached connector status in .get_modes()
      drm/display: bridge_connector: Switch to .detect_ctx() connector helper
      drm/display: bridge_connector: Wire up HDMI 2.0 scrambler callbacks
      drm/bridge: dw-hdmi-qp: Rate limit i2c read error messages
      drm/bridge: dw-hdmi-qp: Provide .{enable|disable}_hpd() PHY ops
      drm/bridge: dw-hdmi-qp: Add HDMI 2.0 SCDC scrambling support
      drm/bridge: dw-hdmi-qp: Provide dw_hdmi_qp_hpd_notify() helper
      drm/rockchip: dw_hdmi_qp: Add missing newlines in dev_err_probe() messages
      drm/rockchip: dw_hdmi_qp: Use local dev variable consistently in bind()
      drm/rockchip: dw_hdmi_qp: Drop unnecessary #include
      drm/rockchip: dw_hdmi_qp: Defer HPD IRQ enable until after connector setup
      drm/rockchip: dw_hdmi_qp: Mask RK3576 HPD IRQ in io_init
      drm/rockchip: dw_hdmi_qp: Implement .{enable|disable}_hpd() PHY ops
      drm/rockchip: dw_hdmi_qp: Use dw_hdmi_qp_hpd_notify() for HPD reports
      drm/bridge: dw-hdmi-qp: Drop unused .setup_hpd() phy op
      drm/vc4: hdmi: Use common TMDS char rate constants
      drm/vc4: hdmi: Switch to drm_hdmi_mode_needs_scrambling()
      drm/vc4: hdmi: Convert to common SCDC scrambling infrastructure
      drm/tests: connector: Add HDMI caps-based init coverage
      drm/tests: connector: Add HDMI source-side scrambler coverage
      drm/tests: hdmi_state_helper: Use drmm_connector_hdmi_init_with_caps()
      drm/tests: hdmi_state_helper: Add max_tmds_char_rate fallback tests
      drm/tests: edid: Add 4K@60Hz EDID with 600MHz TMDS
      drm/tests: hdmi_state_helper: Cover source-side scrambling decision
      drm/tests: edid: Fix conformity for 1080p+4K YUV420 200MHz EDID
      drm/tests: edid: Fix conformity for 4K RGB/YUV 340MHz EDID

 drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c       | 129 ++++++--
 drivers/gpu/drm/display/drm_bridge_connector.c     | 146 +++++----
 drivers/gpu/drm/display/drm_hdmi_helper.c          | 267 +++++++++++++++++
 drivers/gpu/drm/display/drm_hdmi_state_helper.c    |  32 +-
 drivers/gpu/drm/display/drm_scdc_helper.c          |  70 ++++-
 drivers/gpu/drm/drm_bridge_helper.c                |   2 -
 drivers/gpu/drm/drm_connector.c                    | 222 ++++++++++----
 drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c     | 130 ++++----
 drivers/gpu/drm/tests/drm_connector_test.c         | 330 +++++++++++++++++++++
 drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c | 315 +++++++++++++++++++-
 drivers/gpu/drm/tests/drm_kunit_edid.c             | 251 +++++++++++++---
 drivers/gpu/drm/tests/drm_kunit_edid.h             |   1 +
 drivers/gpu/drm/vc4/vc4_hdmi.c                     | 301 ++++---------------
 drivers/gpu/drm/vc4/vc4_hdmi.h                     |  21 --
 include/drm/bridge/dw_hdmi_qp.h                    |   4 +-
 include/drm/display/drm_hdmi_helper.h              |  15 +
 include/drm/display/drm_hdmi_state_helper.h        |   6 +-
 include/drm/display/drm_scdc_helper.h              |   2 +
 include/drm/drm_bridge.h                           |  41 +++
 include/drm/drm_connector.h                        | 132 +++++++++
 include/linux/hdmi.h                               |  12 +
 21 files changed, 1869 insertions(+), 560 deletions(-)
---
base-commit: 44d19b8a7548aa25cbc6ebd5f27e958f7142c36b
change-id: 20251203-dw-hdmi-qp-scramb-cdbd8b57ccf9



^ permalink raw reply

* [PATCH v8 07/39] drm/display: hdmi: Add HDMI 2.0 scrambling management helpers
From: Cristian Ciocaltea @ 2026-07-02 14:46 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Daniel Stone,
	Dave Stevenson, Maíra Canal, Raspberry Pi Kernel Maintenance
  Cc: kernel, dri-devel, linux-kernel, linux-arm-kernel, linux-rockchip
In-Reply-To: <20260702-dw-hdmi-qp-scramb-v8-0-d79890d00b6a@collabora.com>

Add helpers to manage the full lifecycle of HDMI 2.0 SCDC scrambling on
both source and sink sides:

- drm_hdmi_mode_needs_scrambling(): checks whether a mode, bits per
  component and output format combination requires scrambling, which is
  needed when the resulting TMDS character rate exceeds the HDMI 1.3
  maximum of 340 MHz

- drm_connector_hdmi_enable_scrambling(): configures SCDC scrambling and
  high TMDS clock ratio and starts a periodic work item that monitors
  the sink's SCDC scrambling status, retrying setup when the sink loses
  state

- drm_connector_hdmi_disable_scrambling(): tears down scrambling on both
  sides and cancels the monitoring work

- drm_connector_hdmi_sync_scdc(): triggers a CRTC reset on reconnection
  to restore SCDC state lost during sink disconnects within an active
  display pipeline

Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
 drivers/gpu/drm/display/drm_hdmi_helper.c | 260 ++++++++++++++++++++++++++++++
 include/drm/display/drm_hdmi_helper.h     |  15 ++
 2 files changed, 275 insertions(+)

diff --git a/drivers/gpu/drm/display/drm_hdmi_helper.c b/drivers/gpu/drm/display/drm_hdmi_helper.c
index 5cb0b033b171..7c96cccf683d 100644
--- a/drivers/gpu/drm/display/drm_hdmi_helper.c
+++ b/drivers/gpu/drm/display/drm_hdmi_helper.c
@@ -4,12 +4,20 @@
 #include <linux/module.h>
 
 #include <drm/display/drm_hdmi_helper.h>
+#include <drm/display/drm_scdc_helper.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
 #include <drm/drm_connector.h>
+#include <drm/drm_crtc.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_modes.h>
+#include <drm/drm_modeset_lock.h>
 #include <drm/drm_print.h>
 #include <drm/drm_property.h>
 
+#define DRM_HDMI_SCDC_POLL_DELAY_MS	1000
+
 static inline bool is_eotf_supported(u8 output_eotf, u8 sink_eotf)
 {
 	return sink_eotf & BIT(output_eotf);
@@ -426,3 +434,255 @@ drm_hdmi_acr_get_n_cts(unsigned long long tmds_char_rate,
 	*out_cts = cts;
 }
 EXPORT_SYMBOL(drm_hdmi_acr_get_n_cts);
+
+/**
+ * drm_hdmi_mode_needs_scrambling() - Check if an HDMI mode requires scrambling
+ * @mode: Display mode to check
+ * @bpc: Bits per color component
+ * @fmt: Output pixel format
+ *
+ * Computes the TMDS character rate for the given mode, bits per component and
+ * output format, and checks whether it exceeds the HDMI 1.3 maximum TMDS
+ * character rate of 340 MHz.
+ *
+ * Returns:
+ * true if the mode requires scrambling, false otherwise.
+ */
+bool drm_hdmi_mode_needs_scrambling(const struct drm_display_mode *mode,
+				    unsigned int bpc,
+				    enum drm_output_color_format fmt)
+{
+	unsigned long long tmds_char_rate;
+
+	tmds_char_rate = drm_hdmi_compute_mode_clock(mode, bpc, fmt);
+
+	return tmds_char_rate > HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ;
+}
+EXPORT_SYMBOL(drm_hdmi_mode_needs_scrambling);
+
+static int drm_scdc_try_scrambling_setup(struct drm_connector *connector)
+{
+	bool done;
+
+	done = drm_scdc_set_high_tmds_clock_ratio(connector, true);
+	if (!done)
+		return -EIO;
+
+	done = drm_scdc_set_scrambling(connector, true);
+	if (!done)
+		return -EIO;
+
+	if (READ_ONCE(connector->hdmi.scrambler_enabled))
+		schedule_delayed_work(&connector->hdmi.scdc_work,
+				      msecs_to_jiffies(DRM_HDMI_SCDC_POLL_DELAY_MS));
+
+	return 0;
+}
+
+static void drm_scdc_monitor_scrambler(struct drm_connector *connector)
+{
+	if (READ_ONCE(connector->hdmi.scrambler_enabled) &&
+	    !drm_scdc_get_scrambling_status(connector))
+		drm_scdc_try_scrambling_setup(connector);
+}
+
+static bool drm_scdc_sink_supports_scrambling(struct drm_connector *connector)
+{
+	const struct drm_display_info *info = &connector->display_info;
+
+	return info->is_hdmi &&
+	       info->hdmi.scdc.supported &&
+	       info->hdmi.scdc.scrambling.supported;
+}
+
+/**
+ * drm_connector_hdmi_enable_scrambling - enable scrambling and monitor SCDC status
+ * @connector: connector
+ * @conn_state: connector state
+ *
+ * Enables scrambling and high TMDS clock ratio on both source and sink sides.
+ * Additionally, use a delayed work item to monitor the scrambling status on
+ * the sink side and retry the operation, as some displays refuse to set the
+ * scrambling bit right away.
+ *
+ * Returns:
+ * Zero if scrambling is set successfully, an error code otherwise.
+ */
+int drm_connector_hdmi_enable_scrambling(struct drm_connector *connector,
+					 const struct drm_connector_state *conn_state)
+{
+	struct drm_connector_hdmi *hdmi = &connector->hdmi;
+	struct drm_device *dev = connector->dev;
+	int ret;
+
+	if (!conn_state)
+		return -EINVAL;
+
+	if (!conn_state->hdmi.scrambler_needed)
+		return 0;
+
+	if (!hdmi->scrambler_supported) {
+		drm_dbg_kms(dev, "Source doesn't support scrambling.\n");
+		return -EINVAL;
+	}
+
+	if (!drm_scdc_sink_supports_scrambling(connector)) {
+		drm_dbg_kms(dev, "Sink doesn't support scrambling.\n");
+		return -EINVAL;
+	}
+
+	drm_dbg_kms(dev, "Enabling scrambling\n");
+
+	hdmi->scdc_cb = drm_scdc_monitor_scrambler;
+	WRITE_ONCE(hdmi->scrambler_enabled, true);
+
+	ret = drm_scdc_try_scrambling_setup(connector);
+	if (!ret)
+		ret = hdmi->funcs->scrambler_enable(connector);
+
+	if (ret) {
+		WRITE_ONCE(hdmi->scrambler_enabled, false);
+		cancel_delayed_work_sync(&hdmi->scdc_work);
+		hdmi->scdc_cb = NULL;
+
+		drm_scdc_set_scrambling(connector, false);
+		drm_scdc_set_high_tmds_clock_ratio(connector, false);
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_connector_hdmi_enable_scrambling);
+
+/**
+ * drm_connector_hdmi_disable_scrambling - disable scrambling and SCDC monitoring
+ * @connector: connector
+ *
+ * Disables scrambling and high TMDS clock ratio on both source and sink sides.
+ * Also cancels the SCDC status monitoring work item, if it is still pending.
+ *
+ * Returns:
+ * Zero if scrambling is reset successfully, an error code otherwise.
+ */
+int drm_connector_hdmi_disable_scrambling(struct drm_connector *connector)
+{
+	struct drm_connector_hdmi *hdmi = &connector->hdmi;
+	struct drm_device *dev = connector->dev;
+
+	if (!READ_ONCE(hdmi->scrambler_enabled))
+		return 0;
+
+	drm_dbg_kms(dev, "Disabling scrambling\n");
+
+	WRITE_ONCE(hdmi->scrambler_enabled, false);
+	cancel_delayed_work_sync(&hdmi->scdc_work);
+	hdmi->scdc_cb = NULL;
+
+	if (connector->status == connector_status_connected &&
+	    drm_scdc_sink_supports_scrambling(connector)) {
+		drm_scdc_set_scrambling(connector, false);
+		drm_scdc_set_high_tmds_clock_ratio(connector, false);
+	}
+
+	return hdmi->funcs->scrambler_disable(connector);
+}
+EXPORT_SYMBOL(drm_connector_hdmi_disable_scrambling);
+
+/**
+ * drm_connector_hdmi_sync_scdc - resync the sink-side SCDC upon reconnect
+ * @connector: connector
+ * @plugged: connector plugged status event
+ * @ctx: initialized lock acquisition context
+ *
+ * When receiving hotplug disconnect/reconnect event, while the display is
+ * still active (CRTC enabled), the SCDC status on the sink side is reset
+ * and must be explicitly restored.
+ *
+ * The typical solution for this is to trigger an empty modeset in
+ * drm_connector_helper_funcs.detect_ctx(), which is what this helper does
+ * by triggering a CRTC reset on reconnection.
+ *
+ * When making use of the HDMI connector framework, this is automatically
+ * triggered via drm_atomic_helper_connector_hdmi_hotplug().
+ *
+ * Returns:
+ * Zero on success, an error code otherwise, including -EDEADLK.
+ */
+int drm_connector_hdmi_sync_scdc(struct drm_connector *connector, bool plugged,
+				 struct drm_modeset_acquire_ctx *ctx)
+{
+	struct drm_connector_hdmi *hdmi = &connector->hdmi;
+	struct drm_device *dev = connector->dev;
+	struct drm_connector_state *conn_state;
+	struct drm_crtc *crtc;
+	u8 config;
+	int ret;
+
+	if (!connector || !plugged || !ctx)
+		return 0;
+
+	/*
+	 * This is normally part of .detect_ctx() call path, which already holds
+	 * connection_mutex through @ctx.  However, re-acquiring it with the
+	 * same context is a no-op and makes the helper safe under any caller.
+	 */
+	ret = drm_modeset_lock(&connector->dev->mode_config.connection_mutex, ctx);
+	if (ret)
+		return ret;
+
+	/* TODO: Also handle HDMI 2.1 FRL link training */
+
+	if (!hdmi->scrambler_supported || !READ_ONCE(hdmi->scrambler_enabled))
+		return 0;
+
+	conn_state = connector->state;
+	if (!conn_state)
+		return 0;
+
+	crtc = connector->state->crtc;
+	if (!crtc)
+		return 0;
+
+	ret = drm_modeset_lock(&crtc->mutex, ctx);
+	if (ret)
+		return ret;
+
+	if (!crtc->state || !crtc->state->active)
+		return 0;
+
+	if (!drm_scdc_sink_supports_scrambling(connector))
+		return 0;
+
+	if (conn_state->commit &&
+	    !try_wait_for_completion(&conn_state->commit->hw_done))
+		return 0;
+
+	ret = drm_scdc_readb(connector->ddc, SCDC_TMDS_CONFIG, &config);
+	if (ret) {
+		drm_dbg_kms(dev, "Failed to read TMDS config: %d\n", ret);
+		return ret;
+	}
+
+	if (config & SCDC_SCRAMBLING_ENABLE)
+		return 0;
+
+	/*
+	 * HDMI 2.0 requires that one should not send scrambled data prior
+	 * to configuring the sink scrambling, and that TMDS clock/data
+	 * transmission should be suspended when changing the TMDS clock rate
+	 * in the sink.  So let's just do a full modeset here, even though some
+	 * sinks would be perfectly happy if were to just reconfigure the SCDC
+	 * settings on the fly.
+	 *
+	 * The disable/re-enable cycle triggered by the reset should call
+	 * drm_connector_hdmi_enable_scrambling() during re-enable, thus
+	 * properly configuring the sink before data transmission resumes.
+	 */
+	drm_dbg_kms(dev, "Resetting CRTC to restore SCDC status\n");
+
+	ret = drm_atomic_helper_reset_crtc(crtc, ctx);
+	if (ret && ret != -EDEADLK)
+		drm_dbg_kms(dev, "Failed to reset CRTC: %d\n", ret);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_connector_hdmi_sync_scdc);
diff --git a/include/drm/display/drm_hdmi_helper.h b/include/drm/display/drm_hdmi_helper.h
index 9c31ed90516b..2cdd6e3bc090 100644
--- a/include/drm/display/drm_hdmi_helper.h
+++ b/include/drm/display/drm_hdmi_helper.h
@@ -8,6 +8,7 @@
 struct drm_connector;
 struct drm_connector_state;
 struct drm_display_mode;
+struct drm_modeset_acquire_ctx;
 enum drm_output_color_format;
 
 void
@@ -35,4 +36,18 @@ drm_hdmi_acr_get_n_cts(unsigned long long tmds_char_rate,
 		       unsigned int *out_n,
 		       unsigned int *out_cts);
 
+bool
+drm_hdmi_mode_needs_scrambling(const struct drm_display_mode *mode,
+			       unsigned int bpc, enum drm_output_color_format fmt);
+
+int
+drm_connector_hdmi_enable_scrambling(struct drm_connector *connector,
+				     const struct drm_connector_state *conn_state);
+int
+drm_connector_hdmi_disable_scrambling(struct drm_connector *connector);
+
+int
+drm_connector_hdmi_sync_scdc(struct drm_connector *connector, bool plugged,
+			     struct drm_modeset_acquire_ctx *ctx);
+
 #endif

-- 
2.54.0



^ permalink raw reply related

* [PATCH v8 13/39] drm/bridge: Add bridge ops for source-side HDMI 2.0 scrambling
From: Cristian Ciocaltea @ 2026-07-02 14:46 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Daniel Stone,
	Dave Stevenson, Maíra Canal, Raspberry Pi Kernel Maintenance
  Cc: kernel, dri-devel, linux-kernel, linux-arm-kernel, linux-rockchip
In-Reply-To: <20260702-dw-hdmi-qp-scramb-v8-0-d79890d00b6a@collabora.com>

Bridges advertising HDMI 2.0 must be able to enable and disable
source-side scrambling and the high TMDS clock ratio when the configured
mode exceeds 340 MHz.

Add .hdmi_scrambler_{enable|disable}() callbacks covering the
source-side programming only; sink-side SCDC negotiation remains the job
of the HDMI connector helpers.

Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
 include/drm/drm_bridge.h | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 2d8b5e4c64ba..236d1cabe669 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -656,6 +656,41 @@ struct drm_bridge_funcs {
 				     const struct drm_display_mode *mode,
 				     unsigned long long tmds_rate);
 
+	/**
+	 * @hdmi_scrambler_enable:
+	 *
+	 * Enable source-side scrambling and the high TMDS clock ratio for the
+	 * mode currently being applied.
+	 *
+	 * This only programs the source side. Sink-side SCDC configuration is
+	 * handled by the HDMI connector helpers.
+	 *
+	 * Mandatory for bridges that set the DRM_BRIDGE_OP_HDMI flag in their
+	 * &drm_bridge->ops and advertise HDMI_VERSION_2_0 or later via
+	 * &drm_bridge->supported_hdmi_ver; unused otherwise.
+	 *
+	 * Returns:
+	 * 0 on success, a negative error code otherwise.
+	 */
+	int (*hdmi_scrambler_enable)(struct drm_bridge *bridge);
+
+	/**
+	 * @hdmi_scrambler_disable:
+	 *
+	 * Disable source-side scrambling and the high TMDS clock ratio.
+	 *
+	 * This only programs the source side. Sink-side SCDC configuration is
+	 * handled by the HDMI connector helpers.
+	 *
+	 * Mandatory for bridges that set the DRM_BRIDGE_OP_HDMI flag in their
+	 * &drm_bridge->ops and advertise HDMI_VERSION_2_0 or later via
+	 * &drm_bridge->supported_hdmi_ver; unused otherwise.
+	 *
+	 * Returns:
+	 * 0 on success, a negative error code otherwise.
+	 */
+	int (*hdmi_scrambler_disable)(struct drm_bridge *bridge);
+
 	/**
 	 * @hdmi_clear_avi_infoframe:
 	 *

-- 
2.54.0



^ permalink raw reply related

* [PATCH v8 24/39] drm/rockchip: dw_hdmi_qp: Defer HPD IRQ enable until after connector setup
From: Cristian Ciocaltea @ 2026-07-02 14:46 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Daniel Stone,
	Dave Stevenson, Maíra Canal, Raspberry Pi Kernel Maintenance
  Cc: kernel, dri-devel, linux-kernel, linux-arm-kernel, linux-rockchip,
	Dmitry Baryshkov
In-Reply-To: <20260702-dw-hdmi-qp-scramb-v8-0-d79890d00b6a@collabora.com>

Request the HPD IRQ early in bind() with IRQF_NO_AUTOEN, keeping it
disabled until all DRM resources are fully initialized, at which point
enable_irq() arms it.  This prevents premature interrupt delivery
without forcing devm_request_threaded_irq() to the very end of the
initialization sequence, which would complicate error unwinding.

Note that IRQF_NO_AUTOEN is incompatible with IRQF_SHARED; the latter is
dropped as this IRQ has no other users.

Suggested-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
 drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
index 33036f194e34..3de4b3e6ea85 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
@@ -672,7 +672,7 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master,
 	ret = devm_request_threaded_irq(dev, irq,
 					cfg->ctrl_ops->hardirq_callback,
 					cfg->ctrl_ops->irq_callback,
-					IRQF_SHARED, "dw-hdmi-qp-hpd",
+					IRQF_NO_AUTOEN, "dw-hdmi-qp-hpd",
 					hdmi);
 	if (ret)
 		return ret;
@@ -694,6 +694,8 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master,
 		return dev_err_probe(dev, PTR_ERR(connector),
 				     "Failed to init bridge connector\n");
 
+	enable_irq(irq);
+
 	return 0;
 }
 

-- 
2.54.0



^ permalink raw reply related

* [PATCH v8 15/39] drm/display: bridge_connector: Switch to .detect_ctx() connector helper
From: Cristian Ciocaltea @ 2026-07-02 14:46 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Daniel Stone,
	Dave Stevenson, Maíra Canal, Raspberry Pi Kernel Maintenance
  Cc: kernel, dri-devel, linux-kernel, linux-arm-kernel, linux-rockchip,
	Diederik de Haas, Maud Spierings, Dmitry Baryshkov
In-Reply-To: <20260702-dw-hdmi-qp-scramb-v8-0-d79890d00b6a@collabora.com>

Replace the .detect() connector_funcs callback with the atomic-aware
.detect_ctx() connector_helper_funcs hook.

This propagates the modeset acquire context through
drm_atomic_helper_connector_hdmi_hotplug() to the HDMI connector
framework, enabling SCDC state recovery via CRTC reset when the sink
reconnects during an active display pipeline.

Tested-by: Diederik de Haas <diederik@cknow-tech.com>
Tested-by: Maud Spierings <maud_spierings@hotmail.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 74 ++++++++++++++------------
 1 file changed, 39 insertions(+), 35 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 57238ba49d70..17eb2548efad 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -208,40 +208,6 @@ static void drm_bridge_connector_disable_hpd(struct drm_connector *connector)
  * Bridge Connector Functions
  */
 
-static enum drm_connector_status
-drm_bridge_connector_detect(struct drm_connector *connector, bool force)
-{
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
-	struct drm_bridge *detect = bridge_connector->bridge_detect;
-	struct drm_bridge *hdmi = bridge_connector->bridge_hdmi;
-	enum drm_connector_status status;
-
-	if (detect) {
-		status = detect->funcs->detect(detect, connector);
-
-		if (hdmi)
-			drm_atomic_helper_connector_hdmi_hotplug(connector, status,
-								 NULL);
-
-		drm_bridge_connector_hpd_notify(connector, status);
-	} else {
-		switch (connector->connector_type) {
-		case DRM_MODE_CONNECTOR_DPI:
-		case DRM_MODE_CONNECTOR_LVDS:
-		case DRM_MODE_CONNECTOR_DSI:
-		case DRM_MODE_CONNECTOR_eDP:
-			status = connector_status_connected;
-			break;
-		default:
-			status = connector_status_unknown;
-			break;
-		}
-	}
-
-	return status;
-}
-
 static void drm_bridge_connector_force(struct drm_connector *connector)
 {
 	struct drm_bridge_connector *bridge_connector =
@@ -308,7 +274,6 @@ drm_bridge_connector_color_format(const struct drm_connector_state *conn_state)
 }
 
 static const struct drm_connector_funcs drm_bridge_connector_funcs = {
-	.detect = drm_bridge_connector_detect,
 	.force = drm_bridge_connector_force,
 	.fill_modes = drm_helper_probe_single_connector_modes,
 	.atomic_create_state = drm_bridge_connector_create_state,
@@ -323,6 +288,44 @@ static const struct drm_connector_funcs drm_bridge_connector_funcs = {
  * Bridge Connector Helper Functions
  */
 
+static int drm_bridge_connector_detect_ctx(struct drm_connector *connector,
+					   struct drm_modeset_acquire_ctx *ctx,
+					   bool force)
+{
+	struct drm_bridge_connector *bridge_connector =
+		to_drm_bridge_connector(connector);
+	struct drm_bridge *detect = bridge_connector->bridge_detect;
+	struct drm_bridge *hdmi = bridge_connector->bridge_hdmi;
+	enum drm_connector_status status;
+	int ret;
+
+	if (detect) {
+		status = detect->funcs->detect(detect, connector);
+
+		if (hdmi) {
+			ret = drm_atomic_helper_connector_hdmi_hotplug(connector, status, ctx);
+			if (ret == -EDEADLK)
+				return ret;
+		}
+
+		drm_bridge_connector_hpd_notify(connector, status);
+	} else {
+		switch (connector->connector_type) {
+		case DRM_MODE_CONNECTOR_DPI:
+		case DRM_MODE_CONNECTOR_LVDS:
+		case DRM_MODE_CONNECTOR_DSI:
+		case DRM_MODE_CONNECTOR_eDP:
+			status = connector_status_connected;
+			break;
+		default:
+			status = connector_status_unknown;
+			break;
+		}
+	}
+
+	return status;
+}
+
 static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector,
 					       struct drm_bridge *bridge)
 {
@@ -414,6 +417,7 @@ static int drm_bridge_connector_atomic_check(struct drm_connector *connector,
 
 static const struct drm_connector_helper_funcs drm_bridge_connector_helper_funcs = {
 	.get_modes = drm_bridge_connector_get_modes,
+	.detect_ctx = drm_bridge_connector_detect_ctx,
 	.mode_valid = drm_bridge_connector_mode_valid,
 	.enable_hpd = drm_bridge_connector_enable_hpd,
 	.disable_hpd = drm_bridge_connector_disable_hpd,

-- 
2.54.0



^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).