Potential deadlock in clk-pwm
Sean Anderson
sanderson at brivo.com
Mon Sep 21 12:02:19 PDT 2026
Hi,
I found a potential deadlock in clk-pwm with lockdep. The problem occurs
when a multi-channel PWM calls a clock function (such as clk_get_rate) in
its apply callback. If one channel of the PWM is used for a clk-pwm, and
another channel is used for some other purpose, then the following deadlock
can occur:
CPU0 CPU1
====================== =========================
pwm_get_state_hw(chip)
guard(pwmchip)(chip)
clk_get_scaled_duty_cycle(clkb)
clk_prepare_lock()
clk_pwm_get_duty_cycle(clkb)
pwm_get_state_hw(chip)
guard(pwmchip)(chip)
chip->get_state()
clk_get_rate(clka)
clk_prepare_lock()
The same scenario can play out with apply instead of get_state. I don't
think this can really be fixed in clk-pwm. That driver doesn't have control
over whether it's called with prepare_lock held or not, and it has to call
into the PWM API in order to do anything.
One approach could be to convert the PWM drivers to avoid calling any
(sleeping) clock functions from apply. Fortunately, it seems like there are
not that many drivers used in-tree:
- pwm-imx27
- pwm-meson (by far the most prolific user)
- pwm-renesas-tpu
- pwm-tiecap (doesn't call any clock functions from apply/get_state)
and none of the in-tree users use any of the other channels AFAICT.
Unfortunately, converting these drivers to be atomic is non-trivial.
All of them enable/prepare their parent clock only when necessary to save
power. Converting them to prepare/disable in request/free could result
in increased power usage if the parent turns on in prepare instead of
enable. Additionally, pwm-meson also sets the clock rate for improved
accuracy, and this would have to be removed.
I suspect that removing these features to fix a bug that can't occur on
existing hardware would be well-received. On the other hand, pwms are
exposed to userspace, so I think a privileged user could deadlock the
system just by reading /sys/kernel/debug/pwm and
/sys/kernel/debug/clk/<name>/clk_duty_cycle repeatedly.
Does anyone have better ideas? I don't know if there's a way to catch this
bug at runtime without lockdep. Non-atomic drivers are still OK as long as
they don't take prepare_lock.
--Sean
======================================================
WARNING: possible circular locking dependency detected
6.18.52-yocto-standard-00169-gbc0cb063dbe7-dirty #1 Not tainted
------------------------------------------------------
kworker/u16:2/47 is trying to acquire lock:
ffff8000827d8968 (prepare_lock){+.+.}-{4:4}, at: clk_prepare_lock (drivers/clk/clk.c:228)
but task is already holding lock:
c5ff000002bc77c8 (&chip->nonatomic_lock){+.+.}-{4:4}, at: pwm_apply_might_sleep (drivers/pwm/core.c:43 drivers/pwm/core.c:54 drivers/pwm/core.c:747)
which lock already depends on the new lock.
the existing dependency chain (in reverse order) is:
-> #1 (&chip->nonatomic_lock){+.+.}-{4:4}:
__mutex_lock (kernel/locking/mutex.c:598 kernel/locking/mutex.c:760)
mutex_lock_nested (kernel/locking/mutex.c:812 (discriminator 1))
pwm_get_state_hw (drivers/pwm/core.c:43 drivers/pwm/core.c:54 drivers/pwm/core.c:812)
clk_pwm_get_duty_cycle (drivers/clk/clk-pwm.c:71)
clk_core_update_duty_cycle_nolock (drivers/clk/clk.c:3095)
__clk_register (drivers/clk/clk.c:4032 drivers/clk/clk.c:4380)
devm_clk_hw_register (drivers/clk/clk.c:4460 (discriminator 1) drivers/clk/clk.c:4684 (discriminator 1))
clk_pwm_probe (drivers/clk/clk-pwm.c:152)
platform_probe (drivers/base/platform.c:1377)
really_probe (drivers/base/dd.c:640 drivers/base/dd.c:718)
__driver_probe_device (drivers/base/dd.c:880)
driver_probe_device (drivers/base/dd.c:910)
__device_attach_driver (drivers/base/dd.c:1038)
bus_for_each_drv (drivers/base/bus.c:462)
__device_attach (drivers/base/dd.c:1110)
device_initial_probe (drivers/base/dd.c:1159)
bus_probe_device (drivers/base/bus.c:583)
deferred_probe_work_func (drivers/base/dd.c:124)
process_one_work (kernel/workqueue.c:3294)
worker_thread (kernel/workqueue.c:3377 kernel/workqueue.c:3458)
kthread (kernel/kthread.c:432)
ret_from_fork (/usr/src/debug/linux-yocto/6.18.52+git/arch/arm64/kernel/entry.S:860)
-> #0 (prepare_lock){+.+.}-{4:4}:
__lock_acquire (kernel/locking/lockdep.c:3167 kernel/locking/lockdep.c:3286 kernel/locking/lockdep.c:3910 kernel/locking/lockdep.c:5239)
lock_acquire (kernel/locking/lockdep.c:5872 kernel/locking/lockdep.c:5829)
__mutex_lock (kernel/locking/mutex.c:598 kernel/locking/mutex.c:760)
mutex_lock_nested (kernel/locking/mutex.c:812 (discriminator 1))
clk_prepare_lock (drivers/clk/clk.c:228)
clk_set_rate (drivers/clk/clk.c:2592 drivers/clk/clk.c:2584)
meson_pwm_enable.isra.0 (drivers/pwm/pwm-meson.c:235)
meson_pwm_apply (drivers/pwm/pwm-meson.c:328)
__pwm_apply (drivers/pwm/core.c:707)
pwm_apply_might_sleep (drivers/pwm/core.c:761)
pwm_adjust_config (drivers/pwm/core.c:897)
pwm_regulator_probe (drivers/regulator/pwm-regulator.c:403)
platform_probe (drivers/base/platform.c:1377)
really_probe (drivers/base/dd.c:640 drivers/base/dd.c:718)
__driver_probe_device (drivers/base/dd.c:880)
driver_probe_device (drivers/base/dd.c:910)
__device_attach_driver (drivers/base/dd.c:1038)
bus_for_each_drv (drivers/base/bus.c:462)
__device_attach_async_helper (drivers/base/dd.c:1067)
async_run_entry_fn (kernel/async.c:129)
process_one_work (kernel/workqueue.c:3294)
worker_thread (kernel/workqueue.c:3377 kernel/workqueue.c:3458)
kthread (kernel/kthread.c:432)
ret_from_fork (/usr/src/debug/linux-yocto/6.18.52+git/arch/arm64/kernel/entry.S:860)
other info that might help us debug this:
Possible unsafe locking scenario:
CPU0 CPU1
---- ----
lock(&chip->nonatomic_lock);
lock(prepare_lock);
lock(&chip->nonatomic_lock);
lock(prepare_lock);
*** DEADLOCK ***
4 locks held by kworker/u16:2/47:
#0: 81ff0000003dc948 ((wq_completion)async){+.+.}-{0:0}, at: process_one_work (kernel/workqueue.c:3268)
#1: 02ff800083a37d10 ((work_completion)(&entry->work)){+.+.}-{0:0}, at: process_one_work (kernel/workqueue.c:3269 (discriminator 1))
#2: afff000000a57140 (&dev->mutex){....}-{4:4}, at: __device_attach_async_helper (include/linux/device.h:1013 drivers/base/dd.c:1053)
#3: c5ff000002bc77c8 (&chip->nonatomic_lock){+.+.}-{4:4}, at: pwm_apply_might_sleep (drivers/pwm/core.c:43 drivers/pwm/core.c:54 drivers/pwm/core.c:747)
stack backtrace:
CPU: 3 UID: 0 PID: 47 Comm: kworker/u16:2 Not tainted 6.18.52-yocto-standard-00169-gbc0cb063dbe7-dirty #1 PREEMPT
Hardware name: Blaze Edge 3.0 (DT)
Workqueue: async async_run_entry_fn
Call trace:
show_stack (arch/arm64/kernel/stacktrace.c:499) (C)
dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120)
dump_stack (lib/dump_stack.c:129)
print_circular_bug (kernel/locking/lockdep.c:2045)
check_noncircular (kernel/locking/lockdep.c:2177)
__lock_acquire (kernel/locking/lockdep.c:3167 kernel/locking/lockdep.c:3286 kernel/locking/lockdep.c:3910 kernel/locking/lockdep.c:5239)
lock_acquire (kernel/locking/lockdep.c:5872 kernel/locking/lockdep.c:5829)
__mutex_lock (kernel/locking/mutex.c:598 kernel/locking/mutex.c:760)
mutex_lock_nested (kernel/locking/mutex.c:812 (discriminator 1))
clk_prepare_lock (drivers/clk/clk.c:228)
clk_set_rate (drivers/clk/clk.c:2592 drivers/clk/clk.c:2584)
meson_pwm_enable.isra.0 (drivers/pwm/pwm-meson.c:235)
meson_pwm_apply (drivers/pwm/pwm-meson.c:328)
__pwm_apply (drivers/pwm/core.c:707)
pwm_apply_might_sleep (drivers/pwm/core.c:761)
pwm_adjust_config (drivers/pwm/core.c:897)
pwm_regulator_probe (drivers/regulator/pwm-regulator.c:403)
platform_probe (drivers/base/platform.c:1377)
really_probe (drivers/base/dd.c:640 drivers/base/dd.c:718)
__driver_probe_device (drivers/base/dd.c:880)
driver_probe_device (drivers/base/dd.c:910)
__device_attach_driver (drivers/base/dd.c:1038)
bus_for_each_drv (drivers/base/bus.c:462)
__device_attach_async_helper (drivers/base/dd.c:1067)
async_run_entry_fn (kernel/async.c:129)
process_one_work (kernel/workqueue.c:3294)
worker_thread (kernel/workqueue.c:3377 kernel/workqueue.c:3458)
kthread (kernel/kthread.c:432)
ret_from_fork (/usr/src/debug/linux-yocto/6.18.52+git/arch/arm64/kernel/entry.S:860)
More information about the linux-arm-kernel
mailing list