Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ACPI / video: Fix circular lock dependency issue in the video-detect code

Before this commit, the following would happen:

a) acpi_video_get_backlight_type() gets called
b) acpi_video_get_backlight_type() calls acpi_video_init_backlight_type()
c) acpi_video_init_backlight_type() locks its function static init_mutex
d) acpi_video_init_backlight_type() calls backlight_register_notifier()
e) backlight_register_notifier() takes its notifier-chain lock

And when the backlight notifier chain gets called we've:

1) blocking_notifier_call_chain() gets called
2) blocking_notifier_call_chain() takes the notifier-chain lock
3) blocking_notifier_call_chain() calls acpi_video_backlight_notify()
4) acpi_video_backlight_notify() calls acpi_video_get_backlight_type()
5) acpi_video_get_backlight_type() calls acpi_video_init_backlight_type()
6) acpi_video_init_backlight_type() locks its function static init_mutex

So in the first call sequence we have:

a) init_mutex gets locked
b) notifier-chain gets locked

and in the second call sequence we have:

1) notifier-chain gets locked
2) init_mutex gets locked

And we've a circular locking dependency. This specific locking dependency
is fixable without using the big hammer otherwise known as a workqueue,
but further analysis shows a similar problem with the backlight notifier
chain lock vs register_count_mutex from drivers/acpi/acpi_video.c,
and fixing that becomes problematic.

So this commit simply fixes this with the big hammer, performance
wise this is a non issue as we expect the work to get scheduled
exactly zero or one times during normal system use.

Fixes: 93a291dfaf9c (ACPI / video: Move backlight notifier to video_detect.c)
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Reported-and-tested-by: Sedat Dilek <sedat.dilek@gmail.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

authored by

Hans de Goede and committed by
Rafael J. Wysocki
7231ed1a f7644cbf

+13 -3
+13 -3
drivers/acpi/video_detect.c
··· 32 32 #include <linux/module.h> 33 33 #include <linux/pci.h> 34 34 #include <linux/types.h> 35 + #include <linux/workqueue.h> 35 36 #include <acpi/video.h> 36 37 37 38 ACPI_MODULE_NAME("video"); ··· 42 41 43 42 static bool backlight_notifier_registered; 44 43 static struct notifier_block backlight_nb; 44 + static struct work_struct backlight_notify_work; 45 45 46 46 static enum acpi_backlight_type acpi_backlight_cmdline = acpi_backlight_undef; 47 47 static enum acpi_backlight_type acpi_backlight_dmi = acpi_backlight_undef; ··· 264 262 { }, 265 263 }; 266 264 265 + /* This uses a workqueue to avoid various locking ordering issues */ 266 + static void acpi_video_backlight_notify_work(struct work_struct *work) 267 + { 268 + if (acpi_video_get_backlight_type() != acpi_backlight_video) 269 + acpi_video_unregister_backlight(); 270 + } 271 + 267 272 static int acpi_video_backlight_notify(struct notifier_block *nb, 268 273 unsigned long val, void *bd) 269 274 { ··· 278 269 279 270 /* A raw bl registering may change video -> native */ 280 271 if (backlight->props.type == BACKLIGHT_RAW && 281 - val == BACKLIGHT_REGISTERED && 282 - acpi_video_get_backlight_type() != acpi_backlight_video) 283 - acpi_video_unregister_backlight(); 272 + val == BACKLIGHT_REGISTERED) 273 + schedule_work(&backlight_notify_work); 284 274 285 275 return NOTIFY_OK; 286 276 } ··· 312 304 acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, 313 305 ACPI_UINT32_MAX, find_video, NULL, 314 306 &video_caps, NULL); 307 + INIT_WORK(&backlight_notify_work, 308 + acpi_video_backlight_notify_work); 315 309 backlight_nb.notifier_call = acpi_video_backlight_notify; 316 310 backlight_nb.priority = 0; 317 311 if (backlight_register_notifier(&backlight_nb) == 0)