@@ -341,6 +341,8 @@ static void mce_panic(const char *msg, struct mce *final, char *exp)
if (!apei_err)
apei_err = apei_write_mce(final);
}
+ /* Print possible additional cper error info, get cper cleared */
+ ghes_in_mce_cper_entry_check();
if (cpu_missing)
pr_emerg(HW_ERR "Some CPUs didn't answer in synchronization\n");
if (exp)
@@ -633,6 +633,10 @@ int apei_map_generic_address(struct acpi_generic_address *reg)
if (rc)
return rc;
+ /* IO space doesn't need mapping */
+ if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_IO)
+ return 0;
+
if (!acpi_os_map_generic_address(reg))
return -ENXIO;
@@ -1147,9 +1147,76 @@ static void ghes_nmi_remove(struct ghes *ghes)
*/
synchronize_rcu();
}
+
+int ghes_in_mce_cper_entry_check(void)
+{
+ int rc = -ENOENT;
+ struct ghes *ghes;
+ struct list_head *rcu_list = &ghes_nmi;
+ enum fixed_addresses fixmap_idx = FIX_APEI_GHES_NMI;
+ struct acpi_hest_generic_status *estatus, tmp_header;
+ struct ghes_estatus_node *estatus_node;
+ u32 len, node_len;
+ u64 buf_paddr;
+ int sev;
+
+ /* if NMI handler already in process, let NMI do its job */
+ if (!atomic_add_unless(&ghes_in_nmi, 1, 1))
+ return 0;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(ghes, rcu_list, list) {
+ rc = __ghes_peek_estatus(ghes, &tmp_header, &buf_paddr, fixmap_idx);
+ if (rc) {
+ ghes_clear_estatus(ghes, &tmp_header, buf_paddr, fixmap_idx);
+ return rc;
+ }
+
+ rc = __ghes_check_estatus(ghes, &tmp_header);
+ if (rc) {
+ ghes_clear_estatus(ghes, &tmp_header, buf_paddr, fixmap_idx);
+ return rc;
+ }
+
+ len = cper_estatus_len(&tmp_header);
+ node_len = GHES_ESTATUS_NODE_LEN(len);
+ estatus_node = (void *)gen_pool_alloc(ghes_estatus_pool, node_len);
+ if (!estatus_node) {
+ /*
+ * Going to panic, No need to keep the error.
+ */
+ ghes_clear_estatus(ghes, &tmp_header, buf_paddr, fixmap_idx);
+ return -ENOMEM;
+ }
+ estatus_node->ghes = ghes;
+ estatus_node->generic = ghes->generic;
+ estatus_node->task_work.func = NULL;
+ estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
+
+ if (__ghes_read_estatus(estatus, buf_paddr, fixmap_idx, len)) {
+ ghes_clear_estatus(ghes, estatus, buf_paddr, fixmap_idx);
+ return -ENOENT;
+ }
+
+ /*
+ * As we are going to panic, and preemt the possible NMI handing,
+ * dump all the info and get it cleared.
+ */
+ ghes_print_queued_estatus();
+ __ghes_print_estatus(KERN_EMERG, ghes->generic, estatus);
+ ghes_clear_estatus(ghes, estatus, buf_paddr, fixmap_idx);
+
+ gen_pool_free(ghes_estatus_pool, (unsigned long)estatus_node,
+ node_len);
+ }
+ rcu_read_unlock();
+ atomic_dec(&ghes_in_nmi);
+ return rc;
+}
#else /* CONFIG_HAVE_ACPI_APEI_NMI */
static inline void ghes_nmi_add(struct ghes *ghes) { }
static inline void ghes_nmi_remove(struct ghes *ghes) { }
+int ghes_in_mce_cper_entry_check(void) {}
#endif /* CONFIG_HAVE_ACPI_APEI_NMI */
static void ghes_nmi_init_cxt(void)
@@ -145,4 +145,6 @@ int ghes_notify_sea(void);
static inline int ghes_notify_sea(void) { return -ENOENT; }
#endif
+int ghes_in_mce_cper_entry_check(void);
+
#endif /* GHES_H */