drivers/gpu/drm/msm/dsi/dsi_host.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
The transfer function dsi_host_transfer() guards against the DSI host
being inactive by checking msm_host->power_on. However, power_on is
cleared at the end of msm_dsi_host_power_off(), after clocks have
already been disabled. This creates a window where a concurrent DCS
command (e.g. a brightness update from the backlight driver) can pass
the power_on check, call xfer_prepare()/xfer_restore(), and toggle
link clocks that are already being torn down, leaving
disp_cc_mdss_byte0_clk stuck in the 'on' state.
Checking enabled instead of power_on closes the race by rejecting
transfers as soon as the bridge starts tearing down, before any clocks
are touched.
[11816.846734] disp_cc_mdss_byte0_clk status stuck at 'on'
[11816.846752] WARNING: CPU: 4 PID: 26399 at drivers/clk/qcom/clk-branch.c:88 clk_branch_toggle+0x128/0x178
[11816.861715] Modules linked in: rfcomm rmnet algif_hash algif_skcipher q6asm_dai q6voice_dai q6routing q6afe_dai q6voice q6adm q6cvp q6afe q6asm q6mvm q6cvs q6voice_common snd_q6dsp_common q6core bnep gpio_wcd934x snd_soc_wcd934x snd_soc_wcd_mbhc soundwire_qcom snd_soc_wcd_classh venus_enc venus_dec imx371 wcd934x regmap_slimbus imx376 lc898217xc videobuf2_dma_contig fastrpc v4l2_cci qrtr_smd rpmsg_ctrl hci_uart btqca btbcm bluetooth ecdh_generic ecc pwrseq_core snd_soc_max98927 qcom_camss ath10k_snoc videobuf2_dma_sg snd_soc_sdm845 videobuf2_memops venus_core ath10k_core qcom_smbx leds_qcom_flash snd_soc_rt5663 leds_qcom_lpg ath v4l2_mem2mem snd_soc_qcom_sdw videobuf2_v4l2 v4l2_fwnode videobuf2_common v4l2_async snd_soc_qcom_common bq27xxx_battery_i2c qcom_pbs bq27xxx_battery led_class_multicolor mac80211 snd_soc_rl6231 rtc_pm8xxx libarc4 soundwire_bus videodev qcom_stats qcom_spmi_rradc reset_qcom_pdc i2c_qcom_cci cfg80211 camcc_sdm845 rfkill mc qcom_rng ipa qcom_q6v5_mss slim_qcom_ngd_ctrl qcom_wdt icc_bwmon qrtr
[11816.861812] qcom_q6v5_pas qcom_pil_info qcom_q6v5 qcom_sysmon qcom_common qcom_glink_smem joydev zram zsmalloc uhid uinput nft_reject_inet nft_reject nf_reject_ipv6 nf_reject_ipv4 nft_ct nf_conntrack nf_defrag_ipv6 nf_defrag_ipv4 nf_tables fuse nfnetlink ipv6 qcom_spmi_haptics rmi_i2c rmi_core
[11816.978965] CPU: 4 UID: 0 PID: 26399 Comm: (sd-bright) Tainted: G W 6.16.7-sdm845 #1000-postmarketos-qcom-sdm845 PREEMPT
[11816.991580] Tainted: [W]=WARN
[11816.994637] Hardware name: OnePlus 6 (DT)
[11816.998735] pstate: 604000c5 (nZCv daIF +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[11817.005844] pc : clk_branch_toggle+0x128/0x178
[11817.010384] lr : clk_branch_toggle+0x124/0x178
[11817.014956] sp : ffff8000991f3970
[11817.018358] x29: ffff8000991f3980 x28: ffff0000dad38000 x27: 0000000000000000
[11817.025634] x26: 0000000000000000 x25: 0000000000000000 x24: 0000000000000000
[11817.032887] x23: 0000000000000000 x22: ffffa63e2af42178 x21: ffffa63e2b56e128
[11817.040169] x20: ffffa63e29da5a90 x19: 0000000000000000 x18: 0000000000000003
[11817.047451] x17: 0000000000000000 x16: 000000000000000c x15: 0000000000000003
[11817.054701] x14: ffffa63e2b2f6a10 x13: 0000000000000003 x12: 0000000000000003
[11817.061982] x11: 00000000ffffefff x10: c0000000ffffefff x9 : e43cc05c4996c100
[11817.069260] x8 : e43cc05c4996c100 x7 : 7461206b63757473 x6 : 0000000000000027
[11817.076511] x5 : ffffa63e2b8726d3 x4 : ffffa63e2ae146c4 x3 : 0000000000000000
[11817.083792] x2 : 0000000000000000 x1 : ffff8000991f3710 x0 : 00000000fffffff0
[11817.091042] Call trace:
[11817.093572] clk_branch_toggle+0x128/0x178 (P)
[11817.098112] clk_branch2_disable+0x28/0x40
[11817.102308] clk_core_disable+0x64/0x1b8
[11817.106358] clk_core_disable_lock+0x90/0x120
[11817.110810] clk_disable+0x2c/0x40
[11817.114298] dsi_link_clk_disable_6g+0x78/0x98
[11817.118871] msm_dsi_host_xfer_restore+0xf0/0x120
[11817.123667] msm_dsi_manager_cmd_xfer+0xfc/0x178
[11817.128411] dsi_host_transfer+0x48/0x110
[11817.132509] mipi_dsi_dcs_set_display_brightness_large+0x8c/0xd0
[11817.138651] sofef00_panel_bl_update_status+0x3c/0x60
[11817.143802] backlight_device_set_brightness+0x88/0x128
[11817.149124] brightness_store+0x64/0xa8
[11817.153082] dev_attr_store+0x24/0x40
[11817.156835] sysfs_kf_write+0x8c/0xb8
[11817.160591] kernfs_fop_write_iter+0xe4/0x190
[11817.165073] do_iter_readv_writev+0x168/0x1c8
[11817.169515] vfs_writev+0x16c/0x378
[11817.173122] do_writev+0x84/0x130
[11817.176523] __arm64_sys_writev+0x2c/0x40
[11817.180618] invoke_syscall+0x48/0x100
[11817.184460] el0_svc_common+0x88/0xe8
[11817.188217] do_el0_svc+0x28/0x40
[11817.191621] el0_svc+0x38/0x88
[11817.194766] el0t_64_sync_handler+0x78/0x108
[11817.199161] el0t_64_sync+0x198/0x1a0
Signed-off-by: Cédric Bellegarde <cedric.bellegarde@adishatz.org>
---
drivers/gpu/drm/msm/dsi/dsi_host.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c
index 4d75529c0e85..f66f138cfba0 100644
--- a/drivers/gpu/drm/msm/dsi/dsi_host.c
+++ b/drivers/gpu/drm/msm/dsi/dsi_host.c
@@ -1652,7 +1652,7 @@ static ssize_t dsi_host_transfer(struct mipi_dsi_host *host,
struct msm_dsi_host *msm_host = to_msm_dsi_host(host);
int ret;
- if (!msg || !msm_host->power_on)
+ if (!msg || !msm_host->enabled)
return -EINVAL;
mutex_lock(&msm_host->cmd_mutex);
--
2.53.0
On Wed, Mar 18, 2026 at 11:56:35AM +0100, Cédric Bellegarde wrote: > The transfer function dsi_host_transfer() guards against the DSI host > being inactive by checking msm_host->power_on. However, power_on is > cleared at the end of msm_dsi_host_power_off(), after clocks have > already been disabled. This creates a window where a concurrent DCS > command (e.g. a brightness update from the backlight driver) can pass > the power_on check, call xfer_prepare()/xfer_restore(), and toggle > link clocks that are already being torn down, leaving > disp_cc_mdss_byte0_clk stuck in the 'on' state. > > Checking enabled instead of power_on closes the race by rejecting > transfers as soon as the bridge starts tearing down, before any clocks > are touched. Thanks, but it is not correct. The transfer callback is documented as requiring to power up the host if it is not on at the time it is called. Could you please implement corresponding logic? > > > Signed-off-by: Cédric Bellegarde <cedric.bellegarde@adishatz.org> > --- > drivers/gpu/drm/msm/dsi/dsi_host.c | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > > diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c > index 4d75529c0e85..f66f138cfba0 100644 > --- a/drivers/gpu/drm/msm/dsi/dsi_host.c > +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c > @@ -1652,7 +1652,7 @@ static ssize_t dsi_host_transfer(struct mipi_dsi_host *host, > struct msm_dsi_host *msm_host = to_msm_dsi_host(host); > int ret; > > - if (!msg || !msm_host->power_on) > + if (!msg || !msm_host->enabled) > return -EINVAL; > > mutex_lock(&msm_host->cmd_mutex); > -- > 2.53.0 > -- With best wishes Dmitry
Le 18/03/2026 à 14:57, Dmitry Baryshkov a écrit : > On Wed, Mar 18, 2026 at 11:56:35AM +0100, Cédric Bellegarde wrote: >> The transfer function dsi_host_transfer() guards against the DSI host >> being inactive by checking msm_host->power_on. However, power_on is >> cleared at the end of msm_dsi_host_power_off(), after clocks have >> already been disabled. This creates a window where a concurrent DCS >> command (e.g. a brightness update from the backlight driver) can pass >> the power_on check, call xfer_prepare()/xfer_restore(), and toggle >> link clocks that are already being torn down, leaving >> disp_cc_mdss_byte0_clk stuck in the 'on' state. >> >> Checking enabled instead of power_on closes the race by rejecting >> transfers as soon as the bridge starts tearing down, before any clocks >> are touched. > > Thanks, but it is not correct. The transfer callback is documented as > requiring to power up the host if it is not on at the time it is > called. Could you please implement corresponding logic? > >> >> >> Signed-off-by: Cédric Bellegarde <cedric.bellegarde@adishatz.org> >> --- >> drivers/gpu/drm/msm/dsi/dsi_host.c | 2 +- >> 1 file changed, 1 insertion(+), 1 deletion(-) >> >> diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c >> index 4d75529c0e85..f66f138cfba0 100644 >> --- a/drivers/gpu/drm/msm/dsi/dsi_host.c >> +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c >> @@ -1652,7 +1652,7 @@ static ssize_t dsi_host_transfer(struct mipi_dsi_host *host, >> struct msm_dsi_host *msm_host = to_msm_dsi_host(host); >> int ret; >> >> - if (!msg || !msm_host->power_on) >> + if (!msg || !msm_host->enabled) >> return -EINVAL; >> >> mutex_lock(&msm_host->cmd_mutex); >> -- >> 2.53.0 >> > After being able to boot linux 7.0.0 rc4, I can confirm that this issue is already fixed. Regards, -- Cédric Bellegarde
Le 18/03/2026 à 14:57, Dmitry Baryshkov a écrit : > On Wed, Mar 18, 2026 at 11:56:35AM +0100, Cédric Bellegarde wrote: >> The transfer function dsi_host_transfer() guards against the DSI host >> being inactive by checking msm_host->power_on. However, power_on is >> cleared at the end of msm_dsi_host_power_off(), after clocks have >> already been disabled. This creates a window where a concurrent DCS >> command (e.g. a brightness update from the backlight driver) can pass >> the power_on check, call xfer_prepare()/xfer_restore(), and toggle >> link clocks that are already being torn down, leaving >> disp_cc_mdss_byte0_clk stuck in the 'on' state. >> >> Checking enabled instead of power_on closes the race by rejecting >> transfers as soon as the bridge starts tearing down, before any clocks >> are touched. > Thanks, but it is not correct. The transfer callback is documented as > requiring to power up the host if it is not on at the time it is > called. Could you please implement corresponding logic? Hi, Thank you for your review. Looking at the mipi_dsi_host_ops documentation: "Also note that those callbacks can be called no matter the state the host is in. Drivers that need the underlying device to be powered to perform these operations will first need to make sure it's been properly enabled." I would argue that this sentence places the responsibility on the caller to ensure the host is properly enabled before calling transfer(), not on the transfer() implementation itself to power up the hardware. In our case, powering up the DSI host from within transfer() is not trivially achievable. These arguments and steps are owned by dsi_mgr_bridge_power_on() and are not available at transfer() time without significant architectural changes that would spread power management logic outside the manager. BTW, I'm quite new to kernel dev, my logic may be totally wrong. If that is indeed the expected behaviour, would a callback mechanism be acceptable, where the manager registers a power_on callback on the host at bridge init time, allowing transfer() to trigger the full power sequence owned by the manager without duplicating it ? >> >> Signed-off-by: Cédric Bellegarde <cedric.bellegarde@adishatz.org> >> --- >> drivers/gpu/drm/msm/dsi/dsi_host.c | 2 +- >> 1 file changed, 1 insertion(+), 1 deletion(-) >> >> diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c >> index 4d75529c0e85..f66f138cfba0 100644 >> --- a/drivers/gpu/drm/msm/dsi/dsi_host.c >> +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c >> @@ -1652,7 +1652,7 @@ static ssize_t dsi_host_transfer(struct mipi_dsi_host *host, >> struct msm_dsi_host *msm_host = to_msm_dsi_host(host); >> int ret; >> >> - if (!msg || !msm_host->power_on) >> + if (!msg || !msm_host->enabled) >> return -EINVAL; >> >> mutex_lock(&msm_host->cmd_mutex); >> -- >> 2.53.0 >> Regards, -- Cédric Bellegarde
© 2016 - 2026 Red Hat, Inc.