Refactor the estatus queue's pool notification routine from NOTIFY_NMI's handlers. This will allow another notification method to use the estatus queue without duplicating this code. Add rcu_read_lock()/rcu_read_unlock() around the list list_for_each_entry_rcu() walker. These aren't strictly necessary as the whole nmi_enter/nmi_exit() window is a spooky RCU read-side critical section. in_nmi_queue_one_entry() is separate from the rcu-list walker for a later caller that doesn't need to walk a list. Signed-off-by: James Morse <james.morse@xxxxxxx> Reviewed-by: Punit Agrawal <punit.agrawal@xxxxxxx> Tested-by: Tyler Baicar <tbaicar@xxxxxxxxxxxxxx> --- Changes since v7: * Moved err= onto a separate line to make this more readable * Dropped ghes_ prefix on new static functions * Renamed stuff, 'notify' has an overloaded meaning, Changes since v6: * Removed pool grow/remove code as this is no longer necessary. Changes since v3: * Removed duplicate or redundant paragraphs in commit message. * Fixed the style of a zero check. Changes since v1: * Tidied up _in_nmi_notify_one(). --- drivers/acpi/apei/ghes.c | 65 ++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index af3c10f47f20..cb3d88de711f 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -912,37 +912,58 @@ static void __process_error(struct ghes *ghes) #endif } -static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs) +static int ghes_in_nmi_queue_one_entry(struct ghes *ghes) { u64 buf_paddr; - struct ghes *ghes; - int sev, ret = NMI_DONE; + int sev; - if (!atomic_add_unless(&ghes_in_nmi, 1, 1)) - return ret; + if (ghes_read_estatus(ghes, &buf_paddr)) { + ghes_clear_estatus(ghes, buf_paddr); + return -ENOENT; + } - list_for_each_entry_rcu(ghes, &ghes_nmi, list) { - if (ghes_read_estatus(ghes, &buf_paddr)) { - ghes_clear_estatus(ghes, buf_paddr); - continue; - } else { - ret = NMI_HANDLED; - } + sev = ghes_severity(ghes->estatus->error_severity); + if (sev >= GHES_SEV_PANIC) { + ghes_print_queued_estatus(); + __ghes_panic(ghes, buf_paddr); + } - sev = ghes_severity(ghes->estatus->error_severity); - if (sev >= GHES_SEV_PANIC) { - ghes_print_queued_estatus(); - __ghes_panic(ghes, buf_paddr); - } + __process_error(ghes); + ghes_clear_estatus(ghes, buf_paddr); - __process_error(ghes); - ghes_clear_estatus(ghes, buf_paddr); + return 0; +} + +static int ghes_in_nmi_spool_from_list(struct list_head *rcu_list) +{ + int err, ret = -ENOENT; + struct ghes *ghes; + + rcu_read_lock(); + list_for_each_entry_rcu(ghes, rcu_list, list) { + err = ghes_in_nmi_queue_one_entry(ghes); + if (!err) + ret = 0; } + rcu_read_unlock(); -#ifdef CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG - if (ret == NMI_HANDLED) + if (IS_ENABLED(CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG) && !ret) irq_work_queue(&ghes_proc_irq_work); -#endif + + return ret; +} + +static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs) +{ + int err, ret = NMI_DONE; + + if (!atomic_add_unless(&ghes_in_nmi, 1, 1)) + return ret; + + err = ghes_in_nmi_spool_from_list(&ghes_nmi); + if (!err) + ret = NMI_HANDLED; + atomic_dec(&ghes_in_nmi); return ret; } -- 2.20.1