diff options
author | Liguang Zhang <zhangliguang@linux.alibaba.com> | 2020-02-21 17:35:07 +0100 |
---|---|---|
committer | Catalin Marinas <catalin.marinas@arm.com> | 2020-02-27 18:14:14 +0100 |
commit | c66d52b1026717135c5030c65e344750161d159b (patch) | |
tree | 4f8a3fe1c45ec321e2e4a9e5473bbc6725ec4638 /drivers/firmware/arm_sdei.c | |
parent | firmware: arm_sdei: fix double-lock on hibernate with shared events (diff) | |
download | linux-c66d52b1026717135c5030c65e344750161d159b.tar.xz linux-c66d52b1026717135c5030c65e344750161d159b.zip |
firmware: arm_sdei: fix possible double-lock on hibernate error path
We call sdei_reregister_event() with sdei_list_lock held, if the register
fails we call sdei_event_destroy() which also acquires sdei_list_lock
thus creating A-A deadlock.
Add '_llocked' to sdei_reregister_event(), to indicate the list lock
is held, and add a _llocked variant of sdei_event_destroy().
Fixes: da351827240e ("firmware: arm_sdei: Add support for CPU and system power states")
Signed-off-by: Liguang Zhang <zhangliguang@linux.alibaba.com>
[expanded subject, added wrappers instead of duplicating contents]
Signed-off-by: James Morse <james.morse@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
Diffstat (limited to 'drivers/firmware/arm_sdei.c')
-rw-r--r-- | drivers/firmware/arm_sdei.c | 19 |
1 files changed, 13 insertions, 6 deletions
diff --git a/drivers/firmware/arm_sdei.c b/drivers/firmware/arm_sdei.c index 77eaa9a2fd15..f15f459e9df0 100644 --- a/drivers/firmware/arm_sdei.c +++ b/drivers/firmware/arm_sdei.c @@ -280,13 +280,12 @@ static struct sdei_event *sdei_event_create(u32 event_num, return event; } -static void sdei_event_destroy(struct sdei_event *event) +static void sdei_event_destroy_llocked(struct sdei_event *event) { lockdep_assert_held(&sdei_events_lock); + lockdep_assert_held(&sdei_list_lock); - spin_lock(&sdei_list_lock); list_del(&event->list); - spin_unlock(&sdei_list_lock); if (event->type == SDEI_EVENT_TYPE_SHARED) kfree(event->registered); @@ -296,6 +295,13 @@ static void sdei_event_destroy(struct sdei_event *event) kfree(event); } +static void sdei_event_destroy(struct sdei_event *event) +{ + spin_lock(&sdei_list_lock); + sdei_event_destroy_llocked(event); + spin_unlock(&sdei_list_lock); +} + static int sdei_api_get_version(u64 *version) { return invoke_sdei_fn(SDEI_1_0_FN_SDEI_VERSION, 0, 0, 0, 0, 0, version); @@ -643,16 +649,17 @@ int sdei_event_register(u32 event_num, sdei_event_callback *cb, void *arg) } EXPORT_SYMBOL(sdei_event_register); -static int sdei_reregister_event(struct sdei_event *event) +static int sdei_reregister_event_llocked(struct sdei_event *event) { int err; lockdep_assert_held(&sdei_events_lock); + lockdep_assert_held(&sdei_list_lock); err = _sdei_event_register(event); if (err) { pr_err("Failed to re-register event %u\n", event->event_num); - sdei_event_destroy(event); + sdei_event_destroy_llocked(event); return err; } @@ -681,7 +688,7 @@ static int sdei_reregister_shared(void) continue; if (event->reregister) { - err = sdei_reregister_event(event); + err = sdei_reregister_event_llocked(event); if (err) break; } |