Skip to content

Commit 6ded0b6

Browse files
James Morsectmarinas
authored andcommitted
firmware: arm_sdei: fix double-lock on hibernate with shared events
SDEI has private events that must be registered on each CPU. When CPUs come and go they must re-register and re-enable their private events. Each event has flags to indicate whether this should happen to protect against an event being registered on a CPU coming online, while all the others are unregistering the event. These flags are protected by the sdei_list_lock spinlock, because the cpuhp callbacks can't take the mutex. Hibernate needs to unregister all events, but keep the in-memory re-register and re-enable as they are. sdei_unregister_shared() takes the spinlock to walk the list, then calls _sdei_event_unregister() on each shared event. _sdei_event_unregister() tries to take the same spinlock to update re-register and re-enable. This doesn't go so well. Push the re-register and re-enable updates out to their callers. sdei_unregister_shared() doesn't want these values updated, so doesn't need to do anything. This also fixes shared events getting lost over hibernate as this path made them look unregistered. Fixes: da35182 ("firmware: arm_sdei: Add support for CPU and system power states") Reported-by: Liguang Zhang <[email protected]> Signed-off-by: James Morse <[email protected]> Signed-off-by: Catalin Marinas <[email protected]>
1 parent f8788d8 commit 6ded0b6

File tree

1 file changed

+15
-17
lines changed

1 file changed

+15
-17
lines changed

drivers/firmware/arm_sdei.c

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -491,11 +491,6 @@ static int _sdei_event_unregister(struct sdei_event *event)
491491
{
492492
lockdep_assert_held(&sdei_events_lock);
493493

494-
spin_lock(&sdei_list_lock);
495-
event->reregister = false;
496-
event->reenable = false;
497-
spin_unlock(&sdei_list_lock);
498-
499494
if (event->type == SDEI_EVENT_TYPE_SHARED)
500495
return sdei_api_event_unregister(event->event_num);
501496

@@ -518,6 +513,11 @@ int sdei_event_unregister(u32 event_num)
518513
break;
519514
}
520515

516+
spin_lock(&sdei_list_lock);
517+
event->reregister = false;
518+
event->reenable = false;
519+
spin_unlock(&sdei_list_lock);
520+
521521
err = _sdei_event_unregister(event);
522522
if (err)
523523
break;
@@ -585,26 +585,15 @@ static int _sdei_event_register(struct sdei_event *event)
585585

586586
lockdep_assert_held(&sdei_events_lock);
587587

588-
spin_lock(&sdei_list_lock);
589-
event->reregister = true;
590-
spin_unlock(&sdei_list_lock);
591-
592588
if (event->type == SDEI_EVENT_TYPE_SHARED)
593589
return sdei_api_event_register(event->event_num,
594590
sdei_entry_point,
595591
event->registered,
596592
SDEI_EVENT_REGISTER_RM_ANY, 0);
597593

598-
599594
err = sdei_do_cross_call(_local_event_register, event);
600-
if (err) {
601-
spin_lock(&sdei_list_lock);
602-
event->reregister = false;
603-
event->reenable = false;
604-
spin_unlock(&sdei_list_lock);
605-
595+
if (err)
606596
sdei_do_cross_call(_local_event_unregister, event);
607-
}
608597

609598
return err;
610599
}
@@ -632,8 +621,17 @@ int sdei_event_register(u32 event_num, sdei_event_callback *cb, void *arg)
632621
break;
633622
}
634623

624+
spin_lock(&sdei_list_lock);
625+
event->reregister = true;
626+
spin_unlock(&sdei_list_lock);
627+
635628
err = _sdei_event_register(event);
636629
if (err) {
630+
spin_lock(&sdei_list_lock);
631+
event->reregister = false;
632+
event->reenable = false;
633+
spin_unlock(&sdei_list_lock);
634+
637635
sdei_event_destroy(event);
638636
pr_warn("Failed to register event %u: %d\n", event_num,
639637
err);

0 commit comments

Comments
 (0)