|
@@ -12,10 +12,6 @@
|
|
* For more information about Generic Hardware Error Source, please
|
|
* For more information about Generic Hardware Error Source, please
|
|
* refer to ACPI Specification version 4.0, section 17.3.2.6
|
|
* refer to ACPI Specification version 4.0, section 17.3.2.6
|
|
*
|
|
*
|
|
- * Now, only SCI notification type and memory errors are
|
|
|
|
- * supported. More notification type and hardware error type will be
|
|
|
|
- * added later.
|
|
|
|
- *
|
|
|
|
* Copyright 2010 Intel Corp.
|
|
* Copyright 2010 Intel Corp.
|
|
* Author: Huang Ying <ying.huang@intel.com>
|
|
* Author: Huang Ying <ying.huang@intel.com>
|
|
*
|
|
*
|
|
@@ -39,14 +35,18 @@
|
|
#include <linux/acpi.h>
|
|
#include <linux/acpi.h>
|
|
#include <linux/io.h>
|
|
#include <linux/io.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/interrupt.h>
|
|
|
|
+#include <linux/timer.h>
|
|
#include <linux/cper.h>
|
|
#include <linux/cper.h>
|
|
#include <linux/kdebug.h>
|
|
#include <linux/kdebug.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/mutex.h>
|
|
|
|
+#include <linux/ratelimit.h>
|
|
|
|
+#include <linux/vmalloc.h>
|
|
#include <acpi/apei.h>
|
|
#include <acpi/apei.h>
|
|
#include <acpi/atomicio.h>
|
|
#include <acpi/atomicio.h>
|
|
#include <acpi/hed.h>
|
|
#include <acpi/hed.h>
|
|
#include <asm/mce.h>
|
|
#include <asm/mce.h>
|
|
|
|
+#include <asm/tlbflush.h>
|
|
|
|
|
|
#include "apei-internal.h"
|
|
#include "apei-internal.h"
|
|
|
|
|
|
@@ -55,42 +55,131 @@
|
|
#define GHES_ESTATUS_MAX_SIZE 65536
|
|
#define GHES_ESTATUS_MAX_SIZE 65536
|
|
|
|
|
|
/*
|
|
/*
|
|
- * One struct ghes is created for each generic hardware error
|
|
|
|
- * source.
|
|
|
|
- *
|
|
|
|
|
|
+ * One struct ghes is created for each generic hardware error source.
|
|
* It provides the context for APEI hardware error timer/IRQ/SCI/NMI
|
|
* It provides the context for APEI hardware error timer/IRQ/SCI/NMI
|
|
- * handler. Handler for one generic hardware error source is only
|
|
|
|
- * triggered after the previous one is done. So handler can uses
|
|
|
|
- * struct ghes without locking.
|
|
|
|
|
|
+ * handler.
|
|
*
|
|
*
|
|
* estatus: memory buffer for error status block, allocated during
|
|
* estatus: memory buffer for error status block, allocated during
|
|
* HEST parsing.
|
|
* HEST parsing.
|
|
*/
|
|
*/
|
|
#define GHES_TO_CLEAR 0x0001
|
|
#define GHES_TO_CLEAR 0x0001
|
|
|
|
+#define GHES_EXITING 0x0002
|
|
|
|
|
|
struct ghes {
|
|
struct ghes {
|
|
struct acpi_hest_generic *generic;
|
|
struct acpi_hest_generic *generic;
|
|
struct acpi_hest_generic_status *estatus;
|
|
struct acpi_hest_generic_status *estatus;
|
|
- struct list_head list;
|
|
|
|
u64 buffer_paddr;
|
|
u64 buffer_paddr;
|
|
unsigned long flags;
|
|
unsigned long flags;
|
|
|
|
+ union {
|
|
|
|
+ struct list_head list;
|
|
|
|
+ struct timer_list timer;
|
|
|
|
+ unsigned int irq;
|
|
|
|
+ };
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+static int ghes_panic_timeout __read_mostly = 30;
|
|
|
|
+
|
|
/*
|
|
/*
|
|
- * Error source lists, one list for each notification method. The
|
|
|
|
- * members in lists are struct ghes.
|
|
|
|
|
|
+ * All error sources notified with SCI shares one notifier function,
|
|
|
|
+ * so they need to be linked and checked one by one. This is applied
|
|
|
|
+ * to NMI too.
|
|
*
|
|
*
|
|
- * The list members are only added in HEST parsing and deleted during
|
|
|
|
- * module_exit, that is, single-threaded. So no lock is needed for
|
|
|
|
- * that.
|
|
|
|
- *
|
|
|
|
- * But the mutual exclusion is needed between members adding/deleting
|
|
|
|
- * and timer/IRQ/SCI/NMI handler, which may traverse the list. RCU is
|
|
|
|
- * used for that.
|
|
|
|
|
|
+ * RCU is used for these lists, so ghes_list_mutex is only used for
|
|
|
|
+ * list changing, not for traversing.
|
|
*/
|
|
*/
|
|
static LIST_HEAD(ghes_sci);
|
|
static LIST_HEAD(ghes_sci);
|
|
|
|
+static LIST_HEAD(ghes_nmi);
|
|
static DEFINE_MUTEX(ghes_list_mutex);
|
|
static DEFINE_MUTEX(ghes_list_mutex);
|
|
|
|
|
|
|
|
+/*
|
|
|
|
+ * NMI may be triggered on any CPU, so ghes_nmi_lock is used for
|
|
|
|
+ * mutual exclusion.
|
|
|
|
+ */
|
|
|
|
+static DEFINE_RAW_SPINLOCK(ghes_nmi_lock);
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Because the memory area used to transfer hardware error information
|
|
|
|
+ * from BIOS to Linux can be determined only in NMI, IRQ or timer
|
|
|
|
+ * handler, but general ioremap can not be used in atomic context, so
|
|
|
|
+ * a special version of atomic ioremap is implemented for that.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Two virtual pages are used, one for NMI context, the other for
|
|
|
|
+ * IRQ/PROCESS context
|
|
|
|
+ */
|
|
|
|
+#define GHES_IOREMAP_PAGES 2
|
|
|
|
+#define GHES_IOREMAP_NMI_PAGE(base) (base)
|
|
|
|
+#define GHES_IOREMAP_IRQ_PAGE(base) ((base) + PAGE_SIZE)
|
|
|
|
+
|
|
|
|
+/* virtual memory area for atomic ioremap */
|
|
|
|
+static struct vm_struct *ghes_ioremap_area;
|
|
|
|
+/*
|
|
|
|
+ * These 2 spinlock is used to prevent atomic ioremap virtual memory
|
|
|
|
+ * area from being mapped simultaneously.
|
|
|
|
+ */
|
|
|
|
+static DEFINE_RAW_SPINLOCK(ghes_ioremap_lock_nmi);
|
|
|
|
+static DEFINE_SPINLOCK(ghes_ioremap_lock_irq);
|
|
|
|
+
|
|
|
|
+static int ghes_ioremap_init(void)
|
|
|
|
+{
|
|
|
|
+ ghes_ioremap_area = __get_vm_area(PAGE_SIZE * GHES_IOREMAP_PAGES,
|
|
|
|
+ VM_IOREMAP, VMALLOC_START, VMALLOC_END);
|
|
|
|
+ if (!ghes_ioremap_area) {
|
|
|
|
+ pr_err(GHES_PFX "Failed to allocate virtual memory area for atomic ioremap.\n");
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void ghes_ioremap_exit(void)
|
|
|
|
+{
|
|
|
|
+ free_vm_area(ghes_ioremap_area);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void __iomem *ghes_ioremap_pfn_nmi(u64 pfn)
|
|
|
|
+{
|
|
|
|
+ unsigned long vaddr;
|
|
|
|
+
|
|
|
|
+ vaddr = (unsigned long)GHES_IOREMAP_NMI_PAGE(ghes_ioremap_area->addr);
|
|
|
|
+ ioremap_page_range(vaddr, vaddr + PAGE_SIZE,
|
|
|
|
+ pfn << PAGE_SHIFT, PAGE_KERNEL);
|
|
|
|
+
|
|
|
|
+ return (void __iomem *)vaddr;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void __iomem *ghes_ioremap_pfn_irq(u64 pfn)
|
|
|
|
+{
|
|
|
|
+ unsigned long vaddr;
|
|
|
|
+
|
|
|
|
+ vaddr = (unsigned long)GHES_IOREMAP_IRQ_PAGE(ghes_ioremap_area->addr);
|
|
|
|
+ ioremap_page_range(vaddr, vaddr + PAGE_SIZE,
|
|
|
|
+ pfn << PAGE_SHIFT, PAGE_KERNEL);
|
|
|
|
+
|
|
|
|
+ return (void __iomem *)vaddr;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void ghes_iounmap_nmi(void __iomem *vaddr_ptr)
|
|
|
|
+{
|
|
|
|
+ unsigned long vaddr = (unsigned long __force)vaddr_ptr;
|
|
|
|
+ void *base = ghes_ioremap_area->addr;
|
|
|
|
+
|
|
|
|
+ BUG_ON(vaddr != (unsigned long)GHES_IOREMAP_NMI_PAGE(base));
|
|
|
|
+ unmap_kernel_range_noflush(vaddr, PAGE_SIZE);
|
|
|
|
+ __flush_tlb_one(vaddr);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void ghes_iounmap_irq(void __iomem *vaddr_ptr)
|
|
|
|
+{
|
|
|
|
+ unsigned long vaddr = (unsigned long __force)vaddr_ptr;
|
|
|
|
+ void *base = ghes_ioremap_area->addr;
|
|
|
|
+
|
|
|
|
+ BUG_ON(vaddr != (unsigned long)GHES_IOREMAP_IRQ_PAGE(base));
|
|
|
|
+ unmap_kernel_range_noflush(vaddr, PAGE_SIZE);
|
|
|
|
+ __flush_tlb_one(vaddr);
|
|
|
|
+}
|
|
|
|
+
|
|
static struct ghes *ghes_new(struct acpi_hest_generic *generic)
|
|
static struct ghes *ghes_new(struct acpi_hest_generic *generic)
|
|
{
|
|
{
|
|
struct ghes *ghes;
|
|
struct ghes *ghes;
|
|
@@ -101,7 +190,6 @@ static struct ghes *ghes_new(struct acpi_hest_generic *generic)
|
|
if (!ghes)
|
|
if (!ghes)
|
|
return ERR_PTR(-ENOMEM);
|
|
return ERR_PTR(-ENOMEM);
|
|
ghes->generic = generic;
|
|
ghes->generic = generic;
|
|
- INIT_LIST_HEAD(&ghes->list);
|
|
|
|
rc = acpi_pre_map_gar(&generic->error_status_address);
|
|
rc = acpi_pre_map_gar(&generic->error_status_address);
|
|
if (rc)
|
|
if (rc)
|
|
goto err_free;
|
|
goto err_free;
|
|
@@ -158,22 +246,41 @@ static inline int ghes_severity(int severity)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-/* SCI handler run in work queue, so ioremap can be used here */
|
|
|
|
-static int ghes_copy_tofrom_phys(void *buffer, u64 paddr, u32 len,
|
|
|
|
- int from_phys)
|
|
|
|
|
|
+static void ghes_copy_tofrom_phys(void *buffer, u64 paddr, u32 len,
|
|
|
|
+ int from_phys)
|
|
{
|
|
{
|
|
- void *vaddr;
|
|
|
|
-
|
|
|
|
- vaddr = ioremap_cache(paddr, len);
|
|
|
|
- if (!vaddr)
|
|
|
|
- return -ENOMEM;
|
|
|
|
- if (from_phys)
|
|
|
|
- memcpy(buffer, vaddr, len);
|
|
|
|
- else
|
|
|
|
- memcpy(vaddr, buffer, len);
|
|
|
|
- iounmap(vaddr);
|
|
|
|
-
|
|
|
|
- return 0;
|
|
|
|
|
|
+ void __iomem *vaddr;
|
|
|
|
+ unsigned long flags = 0;
|
|
|
|
+ int in_nmi = in_nmi();
|
|
|
|
+ u64 offset;
|
|
|
|
+ u32 trunk;
|
|
|
|
+
|
|
|
|
+ while (len > 0) {
|
|
|
|
+ offset = paddr - (paddr & PAGE_MASK);
|
|
|
|
+ if (in_nmi) {
|
|
|
|
+ raw_spin_lock(&ghes_ioremap_lock_nmi);
|
|
|
|
+ vaddr = ghes_ioremap_pfn_nmi(paddr >> PAGE_SHIFT);
|
|
|
|
+ } else {
|
|
|
|
+ spin_lock_irqsave(&ghes_ioremap_lock_irq, flags);
|
|
|
|
+ vaddr = ghes_ioremap_pfn_irq(paddr >> PAGE_SHIFT);
|
|
|
|
+ }
|
|
|
|
+ trunk = PAGE_SIZE - offset;
|
|
|
|
+ trunk = min(trunk, len);
|
|
|
|
+ if (from_phys)
|
|
|
|
+ memcpy_fromio(buffer, vaddr + offset, trunk);
|
|
|
|
+ else
|
|
|
|
+ memcpy_toio(vaddr + offset, buffer, trunk);
|
|
|
|
+ len -= trunk;
|
|
|
|
+ paddr += trunk;
|
|
|
|
+ buffer += trunk;
|
|
|
|
+ if (in_nmi) {
|
|
|
|
+ ghes_iounmap_nmi(vaddr);
|
|
|
|
+ raw_spin_unlock(&ghes_ioremap_lock_nmi);
|
|
|
|
+ } else {
|
|
|
|
+ ghes_iounmap_irq(vaddr);
|
|
|
|
+ spin_unlock_irqrestore(&ghes_ioremap_lock_irq, flags);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
static int ghes_read_estatus(struct ghes *ghes, int silent)
|
|
static int ghes_read_estatus(struct ghes *ghes, int silent)
|
|
@@ -194,10 +301,8 @@ static int ghes_read_estatus(struct ghes *ghes, int silent)
|
|
if (!buf_paddr)
|
|
if (!buf_paddr)
|
|
return -ENOENT;
|
|
return -ENOENT;
|
|
|
|
|
|
- rc = ghes_copy_tofrom_phys(ghes->estatus, buf_paddr,
|
|
|
|
- sizeof(*ghes->estatus), 1);
|
|
|
|
- if (rc)
|
|
|
|
- return rc;
|
|
|
|
|
|
+ ghes_copy_tofrom_phys(ghes->estatus, buf_paddr,
|
|
|
|
+ sizeof(*ghes->estatus), 1);
|
|
if (!ghes->estatus->block_status)
|
|
if (!ghes->estatus->block_status)
|
|
return -ENOENT;
|
|
return -ENOENT;
|
|
|
|
|
|
@@ -212,17 +317,15 @@ static int ghes_read_estatus(struct ghes *ghes, int silent)
|
|
goto err_read_block;
|
|
goto err_read_block;
|
|
if (apei_estatus_check_header(ghes->estatus))
|
|
if (apei_estatus_check_header(ghes->estatus))
|
|
goto err_read_block;
|
|
goto err_read_block;
|
|
- rc = ghes_copy_tofrom_phys(ghes->estatus + 1,
|
|
|
|
- buf_paddr + sizeof(*ghes->estatus),
|
|
|
|
- len - sizeof(*ghes->estatus), 1);
|
|
|
|
- if (rc)
|
|
|
|
- return rc;
|
|
|
|
|
|
+ ghes_copy_tofrom_phys(ghes->estatus + 1,
|
|
|
|
+ buf_paddr + sizeof(*ghes->estatus),
|
|
|
|
+ len - sizeof(*ghes->estatus), 1);
|
|
if (apei_estatus_check(ghes->estatus))
|
|
if (apei_estatus_check(ghes->estatus))
|
|
goto err_read_block;
|
|
goto err_read_block;
|
|
rc = 0;
|
|
rc = 0;
|
|
|
|
|
|
err_read_block:
|
|
err_read_block:
|
|
- if (rc && !silent)
|
|
|
|
|
|
+ if (rc && !silent && printk_ratelimit())
|
|
pr_warning(FW_WARN GHES_PFX
|
|
pr_warning(FW_WARN GHES_PFX
|
|
"Failed to read error status block!\n");
|
|
"Failed to read error status block!\n");
|
|
return rc;
|
|
return rc;
|
|
@@ -255,11 +358,26 @@ static void ghes_do_proc(struct ghes *ghes)
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
}
|
|
|
|
+}
|
|
|
|
|
|
- if (!processed && printk_ratelimit())
|
|
|
|
- pr_warning(GHES_PFX
|
|
|
|
- "Unknown error record from generic hardware error source: %d\n",
|
|
|
|
- ghes->generic->header.source_id);
|
|
|
|
|
|
+static void ghes_print_estatus(const char *pfx, struct ghes *ghes)
|
|
|
|
+{
|
|
|
|
+ /* Not more than 2 messages every 5 seconds */
|
|
|
|
+ static DEFINE_RATELIMIT_STATE(ratelimit, 5*HZ, 2);
|
|
|
|
+
|
|
|
|
+ if (pfx == NULL) {
|
|
|
|
+ if (ghes_severity(ghes->estatus->error_severity) <=
|
|
|
|
+ GHES_SEV_CORRECTED)
|
|
|
|
+ pfx = KERN_WARNING HW_ERR;
|
|
|
|
+ else
|
|
|
|
+ pfx = KERN_ERR HW_ERR;
|
|
|
|
+ }
|
|
|
|
+ if (__ratelimit(&ratelimit)) {
|
|
|
|
+ printk(
|
|
|
|
+ "%s""Hardware error from APEI Generic Hardware Error Source: %d\n",
|
|
|
|
+ pfx, ghes->generic->header.source_id);
|
|
|
|
+ apei_estatus_print(pfx, ghes->estatus);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
static int ghes_proc(struct ghes *ghes)
|
|
static int ghes_proc(struct ghes *ghes)
|
|
@@ -269,6 +387,7 @@ static int ghes_proc(struct ghes *ghes)
|
|
rc = ghes_read_estatus(ghes, 0);
|
|
rc = ghes_read_estatus(ghes, 0);
|
|
if (rc)
|
|
if (rc)
|
|
goto out;
|
|
goto out;
|
|
|
|
+ ghes_print_estatus(NULL, ghes);
|
|
ghes_do_proc(ghes);
|
|
ghes_do_proc(ghes);
|
|
|
|
|
|
out:
|
|
out:
|
|
@@ -276,6 +395,42 @@ out:
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static void ghes_add_timer(struct ghes *ghes)
|
|
|
|
+{
|
|
|
|
+ struct acpi_hest_generic *g = ghes->generic;
|
|
|
|
+ unsigned long expire;
|
|
|
|
+
|
|
|
|
+ if (!g->notify.poll_interval) {
|
|
|
|
+ pr_warning(FW_WARN GHES_PFX "Poll interval is 0 for generic hardware error source: %d, disabled.\n",
|
|
|
|
+ g->header.source_id);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ expire = jiffies + msecs_to_jiffies(g->notify.poll_interval);
|
|
|
|
+ ghes->timer.expires = round_jiffies_relative(expire);
|
|
|
|
+ add_timer(&ghes->timer);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void ghes_poll_func(unsigned long data)
|
|
|
|
+{
|
|
|
|
+ struct ghes *ghes = (void *)data;
|
|
|
|
+
|
|
|
|
+ ghes_proc(ghes);
|
|
|
|
+ if (!(ghes->flags & GHES_EXITING))
|
|
|
|
+ ghes_add_timer(ghes);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static irqreturn_t ghes_irq_func(int irq, void *data)
|
|
|
|
+{
|
|
|
|
+ struct ghes *ghes = data;
|
|
|
|
+ int rc;
|
|
|
|
+
|
|
|
|
+ rc = ghes_proc(ghes);
|
|
|
|
+ if (rc)
|
|
|
|
+ return IRQ_NONE;
|
|
|
|
+
|
|
|
|
+ return IRQ_HANDLED;
|
|
|
|
+}
|
|
|
|
+
|
|
static int ghes_notify_sci(struct notifier_block *this,
|
|
static int ghes_notify_sci(struct notifier_block *this,
|
|
unsigned long event, void *data)
|
|
unsigned long event, void *data)
|
|
{
|
|
{
|
|
@@ -292,10 +447,63 @@ static int ghes_notify_sci(struct notifier_block *this,
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static int ghes_notify_nmi(struct notifier_block *this,
|
|
|
|
+ unsigned long cmd, void *data)
|
|
|
|
+{
|
|
|
|
+ struct ghes *ghes, *ghes_global = NULL;
|
|
|
|
+ int sev, sev_global = -1;
|
|
|
|
+ int ret = NOTIFY_DONE;
|
|
|
|
+
|
|
|
|
+ if (cmd != DIE_NMI)
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
|
|
+ raw_spin_lock(&ghes_nmi_lock);
|
|
|
|
+ list_for_each_entry_rcu(ghes, &ghes_nmi, list) {
|
|
|
|
+ if (ghes_read_estatus(ghes, 1)) {
|
|
|
|
+ ghes_clear_estatus(ghes);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ sev = ghes_severity(ghes->estatus->error_severity);
|
|
|
|
+ if (sev > sev_global) {
|
|
|
|
+ sev_global = sev;
|
|
|
|
+ ghes_global = ghes;
|
|
|
|
+ }
|
|
|
|
+ ret = NOTIFY_STOP;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (ret == NOTIFY_DONE)
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ if (sev_global >= GHES_SEV_PANIC) {
|
|
|
|
+ oops_begin();
|
|
|
|
+ ghes_print_estatus(KERN_EMERG HW_ERR, ghes_global);
|
|
|
|
+ /* reboot to log the error! */
|
|
|
|
+ if (panic_timeout == 0)
|
|
|
|
+ panic_timeout = ghes_panic_timeout;
|
|
|
|
+ panic("Fatal hardware error!");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ list_for_each_entry_rcu(ghes, &ghes_nmi, list) {
|
|
|
|
+ if (!(ghes->flags & GHES_TO_CLEAR))
|
|
|
|
+ continue;
|
|
|
|
+ /* Do not print estatus because printk is not NMI safe */
|
|
|
|
+ ghes_do_proc(ghes);
|
|
|
|
+ ghes_clear_estatus(ghes);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+out:
|
|
|
|
+ raw_spin_unlock(&ghes_nmi_lock);
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
static struct notifier_block ghes_notifier_sci = {
|
|
static struct notifier_block ghes_notifier_sci = {
|
|
.notifier_call = ghes_notify_sci,
|
|
.notifier_call = ghes_notify_sci,
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+static struct notifier_block ghes_notifier_nmi = {
|
|
|
|
+ .notifier_call = ghes_notify_nmi,
|
|
|
|
+};
|
|
|
|
+
|
|
static int __devinit ghes_probe(struct platform_device *ghes_dev)
|
|
static int __devinit ghes_probe(struct platform_device *ghes_dev)
|
|
{
|
|
{
|
|
struct acpi_hest_generic *generic;
|
|
struct acpi_hest_generic *generic;
|
|
@@ -306,18 +514,27 @@ static int __devinit ghes_probe(struct platform_device *ghes_dev)
|
|
if (!generic->enabled)
|
|
if (!generic->enabled)
|
|
return -ENODEV;
|
|
return -ENODEV;
|
|
|
|
|
|
- if (generic->error_block_length <
|
|
|
|
- sizeof(struct acpi_hest_generic_status)) {
|
|
|
|
- pr_warning(FW_BUG GHES_PFX
|
|
|
|
-"Invalid error block length: %u for generic hardware error source: %d\n",
|
|
|
|
- generic->error_block_length,
|
|
|
|
|
|
+ switch (generic->notify.type) {
|
|
|
|
+ case ACPI_HEST_NOTIFY_POLLED:
|
|
|
|
+ case ACPI_HEST_NOTIFY_EXTERNAL:
|
|
|
|
+ case ACPI_HEST_NOTIFY_SCI:
|
|
|
|
+ case ACPI_HEST_NOTIFY_NMI:
|
|
|
|
+ break;
|
|
|
|
+ case ACPI_HEST_NOTIFY_LOCAL:
|
|
|
|
+ pr_warning(GHES_PFX "Generic hardware error source: %d notified via local interrupt is not supported!\n",
|
|
generic->header.source_id);
|
|
generic->header.source_id);
|
|
goto err;
|
|
goto err;
|
|
|
|
+ default:
|
|
|
|
+ pr_warning(FW_WARN GHES_PFX "Unknown notification type: %u for generic hardware error source: %d\n",
|
|
|
|
+ generic->notify.type, generic->header.source_id);
|
|
|
|
+ goto err;
|
|
}
|
|
}
|
|
- if (generic->records_to_preallocate == 0) {
|
|
|
|
- pr_warning(FW_BUG GHES_PFX
|
|
|
|
-"Invalid records to preallocate: %u for generic hardware error source: %d\n",
|
|
|
|
- generic->records_to_preallocate,
|
|
|
|
|
|
+
|
|
|
|
+ rc = -EIO;
|
|
|
|
+ if (generic->error_block_length <
|
|
|
|
+ sizeof(struct acpi_hest_generic_status)) {
|
|
|
|
+ pr_warning(FW_BUG GHES_PFX "Invalid error block length: %u for generic hardware error source: %d\n",
|
|
|
|
+ generic->error_block_length,
|
|
generic->header.source_id);
|
|
generic->header.source_id);
|
|
goto err;
|
|
goto err;
|
|
}
|
|
}
|
|
@@ -327,38 +544,43 @@ static int __devinit ghes_probe(struct platform_device *ghes_dev)
|
|
ghes = NULL;
|
|
ghes = NULL;
|
|
goto err;
|
|
goto err;
|
|
}
|
|
}
|
|
- if (generic->notify.type == ACPI_HEST_NOTIFY_SCI) {
|
|
|
|
|
|
+ switch (generic->notify.type) {
|
|
|
|
+ case ACPI_HEST_NOTIFY_POLLED:
|
|
|
|
+ ghes->timer.function = ghes_poll_func;
|
|
|
|
+ ghes->timer.data = (unsigned long)ghes;
|
|
|
|
+ init_timer_deferrable(&ghes->timer);
|
|
|
|
+ ghes_add_timer(ghes);
|
|
|
|
+ break;
|
|
|
|
+ case ACPI_HEST_NOTIFY_EXTERNAL:
|
|
|
|
+ /* External interrupt vector is GSI */
|
|
|
|
+ if (acpi_gsi_to_irq(generic->notify.vector, &ghes->irq)) {
|
|
|
|
+ pr_err(GHES_PFX "Failed to map GSI to IRQ for generic hardware error source: %d\n",
|
|
|
|
+ generic->header.source_id);
|
|
|
|
+ goto err;
|
|
|
|
+ }
|
|
|
|
+ if (request_irq(ghes->irq, ghes_irq_func,
|
|
|
|
+ 0, "GHES IRQ", ghes)) {
|
|
|
|
+ pr_err(GHES_PFX "Failed to register IRQ for generic hardware error source: %d\n",
|
|
|
|
+ generic->header.source_id);
|
|
|
|
+ goto err;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case ACPI_HEST_NOTIFY_SCI:
|
|
mutex_lock(&ghes_list_mutex);
|
|
mutex_lock(&ghes_list_mutex);
|
|
if (list_empty(&ghes_sci))
|
|
if (list_empty(&ghes_sci))
|
|
register_acpi_hed_notifier(&ghes_notifier_sci);
|
|
register_acpi_hed_notifier(&ghes_notifier_sci);
|
|
list_add_rcu(&ghes->list, &ghes_sci);
|
|
list_add_rcu(&ghes->list, &ghes_sci);
|
|
mutex_unlock(&ghes_list_mutex);
|
|
mutex_unlock(&ghes_list_mutex);
|
|
- } else {
|
|
|
|
- unsigned char *notify = NULL;
|
|
|
|
-
|
|
|
|
- switch (generic->notify.type) {
|
|
|
|
- case ACPI_HEST_NOTIFY_POLLED:
|
|
|
|
- notify = "POLL";
|
|
|
|
- break;
|
|
|
|
- case ACPI_HEST_NOTIFY_EXTERNAL:
|
|
|
|
- case ACPI_HEST_NOTIFY_LOCAL:
|
|
|
|
- notify = "IRQ";
|
|
|
|
- break;
|
|
|
|
- case ACPI_HEST_NOTIFY_NMI:
|
|
|
|
- notify = "NMI";
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- if (notify) {
|
|
|
|
- pr_warning(GHES_PFX
|
|
|
|
-"Generic hardware error source: %d notified via %s is not supported!\n",
|
|
|
|
- generic->header.source_id, notify);
|
|
|
|
- } else {
|
|
|
|
- pr_warning(FW_WARN GHES_PFX
|
|
|
|
-"Unknown notification type: %u for generic hardware error source: %d\n",
|
|
|
|
- generic->notify.type, generic->header.source_id);
|
|
|
|
- }
|
|
|
|
- rc = -ENODEV;
|
|
|
|
- goto err;
|
|
|
|
|
|
+ break;
|
|
|
|
+ case ACPI_HEST_NOTIFY_NMI:
|
|
|
|
+ mutex_lock(&ghes_list_mutex);
|
|
|
|
+ if (list_empty(&ghes_nmi))
|
|
|
|
+ register_die_notifier(&ghes_notifier_nmi);
|
|
|
|
+ list_add_rcu(&ghes->list, &ghes_nmi);
|
|
|
|
+ mutex_unlock(&ghes_list_mutex);
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ BUG();
|
|
}
|
|
}
|
|
platform_set_drvdata(ghes_dev, ghes);
|
|
platform_set_drvdata(ghes_dev, ghes);
|
|
|
|
|
|
@@ -379,7 +601,14 @@ static int __devexit ghes_remove(struct platform_device *ghes_dev)
|
|
ghes = platform_get_drvdata(ghes_dev);
|
|
ghes = platform_get_drvdata(ghes_dev);
|
|
generic = ghes->generic;
|
|
generic = ghes->generic;
|
|
|
|
|
|
|
|
+ ghes->flags |= GHES_EXITING;
|
|
switch (generic->notify.type) {
|
|
switch (generic->notify.type) {
|
|
|
|
+ case ACPI_HEST_NOTIFY_POLLED:
|
|
|
|
+ del_timer_sync(&ghes->timer);
|
|
|
|
+ break;
|
|
|
|
+ case ACPI_HEST_NOTIFY_EXTERNAL:
|
|
|
|
+ free_irq(ghes->irq, ghes);
|
|
|
|
+ break;
|
|
case ACPI_HEST_NOTIFY_SCI:
|
|
case ACPI_HEST_NOTIFY_SCI:
|
|
mutex_lock(&ghes_list_mutex);
|
|
mutex_lock(&ghes_list_mutex);
|
|
list_del_rcu(&ghes->list);
|
|
list_del_rcu(&ghes->list);
|
|
@@ -387,12 +616,23 @@ static int __devexit ghes_remove(struct platform_device *ghes_dev)
|
|
unregister_acpi_hed_notifier(&ghes_notifier_sci);
|
|
unregister_acpi_hed_notifier(&ghes_notifier_sci);
|
|
mutex_unlock(&ghes_list_mutex);
|
|
mutex_unlock(&ghes_list_mutex);
|
|
break;
|
|
break;
|
|
|
|
+ case ACPI_HEST_NOTIFY_NMI:
|
|
|
|
+ mutex_lock(&ghes_list_mutex);
|
|
|
|
+ list_del_rcu(&ghes->list);
|
|
|
|
+ if (list_empty(&ghes_nmi))
|
|
|
|
+ unregister_die_notifier(&ghes_notifier_nmi);
|
|
|
|
+ mutex_unlock(&ghes_list_mutex);
|
|
|
|
+ /*
|
|
|
|
+ * To synchronize with NMI handler, ghes can only be
|
|
|
|
+ * freed after NMI handler finishes.
|
|
|
|
+ */
|
|
|
|
+ synchronize_rcu();
|
|
|
|
+ break;
|
|
default:
|
|
default:
|
|
BUG();
|
|
BUG();
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
- synchronize_rcu();
|
|
|
|
ghes_fini(ghes);
|
|
ghes_fini(ghes);
|
|
kfree(ghes);
|
|
kfree(ghes);
|
|
|
|
|
|
@@ -412,6 +652,8 @@ static struct platform_driver ghes_platform_driver = {
|
|
|
|
|
|
static int __init ghes_init(void)
|
|
static int __init ghes_init(void)
|
|
{
|
|
{
|
|
|
|
+ int rc;
|
|
|
|
+
|
|
if (acpi_disabled)
|
|
if (acpi_disabled)
|
|
return -ENODEV;
|
|
return -ENODEV;
|
|
|
|
|
|
@@ -420,12 +662,25 @@ static int __init ghes_init(void)
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
|
|
- return platform_driver_register(&ghes_platform_driver);
|
|
|
|
|
|
+ rc = ghes_ioremap_init();
|
|
|
|
+ if (rc)
|
|
|
|
+ goto err;
|
|
|
|
+
|
|
|
|
+ rc = platform_driver_register(&ghes_platform_driver);
|
|
|
|
+ if (rc)
|
|
|
|
+ goto err_ioremap_exit;
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+err_ioremap_exit:
|
|
|
|
+ ghes_ioremap_exit();
|
|
|
|
+err:
|
|
|
|
+ return rc;
|
|
}
|
|
}
|
|
|
|
|
|
static void __exit ghes_exit(void)
|
|
static void __exit ghes_exit(void)
|
|
{
|
|
{
|
|
platform_driver_unregister(&ghes_platform_driver);
|
|
platform_driver_unregister(&ghes_platform_driver);
|
|
|
|
+ ghes_ioremap_exit();
|
|
}
|
|
}
|
|
|
|
|
|
module_init(ghes_init);
|
|
module_init(ghes_init);
|