firmware: arm_sdei: fix double-lock on hibernate with shared events
[ Upstream commit6ded0b61cf
] 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:da35182724
("firmware: arm_sdei: Add support for CPU and system power states") Reported-by: Liguang Zhang <zhangliguang@linux.alibaba.com> Signed-off-by: James Morse <james.morse@arm.com> Signed-off-by: Catalin Marinas <catalin.marinas@arm.com> Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
parent
7bf2c31ba0
commit
f9ee512dd9
|
@ -491,11 +491,6 @@ static int _sdei_event_unregister(struct sdei_event *event)
|
|||
{
|
||||
lockdep_assert_held(&sdei_events_lock);
|
||||
|
||||
spin_lock(&sdei_list_lock);
|
||||
event->reregister = false;
|
||||
event->reenable = false;
|
||||
spin_unlock(&sdei_list_lock);
|
||||
|
||||
if (event->type == SDEI_EVENT_TYPE_SHARED)
|
||||
return sdei_api_event_unregister(event->event_num);
|
||||
|
||||
|
@ -518,6 +513,11 @@ int sdei_event_unregister(u32 event_num)
|
|||
break;
|
||||
}
|
||||
|
||||
spin_lock(&sdei_list_lock);
|
||||
event->reregister = false;
|
||||
event->reenable = false;
|
||||
spin_unlock(&sdei_list_lock);
|
||||
|
||||
err = _sdei_event_unregister(event);
|
||||
if (err)
|
||||
break;
|
||||
|
@ -585,26 +585,15 @@ static int _sdei_event_register(struct sdei_event *event)
|
|||
|
||||
lockdep_assert_held(&sdei_events_lock);
|
||||
|
||||
spin_lock(&sdei_list_lock);
|
||||
event->reregister = true;
|
||||
spin_unlock(&sdei_list_lock);
|
||||
|
||||
if (event->type == SDEI_EVENT_TYPE_SHARED)
|
||||
return sdei_api_event_register(event->event_num,
|
||||
sdei_entry_point,
|
||||
event->registered,
|
||||
SDEI_EVENT_REGISTER_RM_ANY, 0);
|
||||
|
||||
|
||||
err = sdei_do_cross_call(_local_event_register, event);
|
||||
if (err) {
|
||||
spin_lock(&sdei_list_lock);
|
||||
event->reregister = false;
|
||||
event->reenable = false;
|
||||
spin_unlock(&sdei_list_lock);
|
||||
|
||||
if (err)
|
||||
sdei_do_cross_call(_local_event_unregister, event);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
@ -632,8 +621,17 @@ int sdei_event_register(u32 event_num, sdei_event_callback *cb, void *arg)
|
|||
break;
|
||||
}
|
||||
|
||||
spin_lock(&sdei_list_lock);
|
||||
event->reregister = true;
|
||||
spin_unlock(&sdei_list_lock);
|
||||
|
||||
err = _sdei_event_register(event);
|
||||
if (err) {
|
||||
spin_lock(&sdei_list_lock);
|
||||
event->reregister = false;
|
||||
event->reenable = false;
|
||||
spin_unlock(&sdei_list_lock);
|
||||
|
||||
sdei_event_destroy(event);
|
||||
pr_warn("Failed to register event %u: %d\n", event_num,
|
||||
err);
|
||||
|
|
Loading…
Reference in New Issue