Merge branch 'apei' into release

This commit is contained in:
Len Brown 2012-01-18 00:18:10 -05:00
commit 037d76f404
11 changed files with 324 additions and 58 deletions

View File

@ -714,7 +714,7 @@ void __init e820_mark_nosave_regions(unsigned long limit_pfn)
}
#endif
#ifdef CONFIG_HIBERNATION
#ifdef CONFIG_ACPI
/**
* Mark ACPI NVS memory region, so that we can save/restore it during
* hibernation and the subsequent resume.
@ -727,7 +727,7 @@ static int __init e820_mark_nvs_memory(void)
struct e820entry *ei = &e820.map[i];
if (ei->type == E820_NVS)
suspend_nvs_register(ei->addr, ei->size);
acpi_nvs_register(ei->addr, ei->size);
}
return 0;

View File

@ -20,11 +20,12 @@ obj-y += acpi.o \
# All the builtin files are in the "acpi." module_param namespace.
acpi-y += osl.o utils.o reboot.o
acpi-y += atomicio.o
acpi-y += nvs.o
# sleep related files
acpi-y += wakeup.o
acpi-y += sleep.o
acpi-$(CONFIG_ACPI_SLEEP) += proc.o nvs.o
acpi-$(CONFIG_ACPI_SLEEP) += proc.o
#

View File

@ -421,6 +421,17 @@ static int apei_resources_merge(struct apei_resources *resources1,
return 0;
}
int apei_resources_add(struct apei_resources *resources,
unsigned long start, unsigned long size,
bool iomem)
{
if (iomem)
return apei_res_add(&resources->iomem, start, size);
else
return apei_res_add(&resources->ioport, start, size);
}
EXPORT_SYMBOL_GPL(apei_resources_add);
/*
* EINJ has two groups of GARs (EINJ table entry and trigger table
* entry), so common resources are subtracted from the trigger table
@ -438,8 +449,19 @@ int apei_resources_sub(struct apei_resources *resources1,
}
EXPORT_SYMBOL_GPL(apei_resources_sub);
static int apei_get_nvs_callback(__u64 start, __u64 size, void *data)
{
struct apei_resources *resources = data;
return apei_res_add(&resources->iomem, start, size);
}
static int apei_get_nvs_resources(struct apei_resources *resources)
{
return acpi_nvs_for_each_region(apei_get_nvs_callback, resources);
}
/*
* IO memory/port rersource management mechanism is used to check
* IO memory/port resource management mechanism is used to check
* whether memory/port area used by GARs conflicts with normal memory
* or IO memory/port of devices.
*/
@ -448,21 +470,35 @@ int apei_resources_request(struct apei_resources *resources,
{
struct apei_res *res, *res_bak = NULL;
struct resource *r;
struct apei_resources nvs_resources;
int rc;
rc = apei_resources_sub(resources, &apei_resources_all);
if (rc)
return rc;
/*
* Some firmware uses ACPI NVS region, that has been marked as
* busy, so exclude it from APEI resources to avoid false
* conflict.
*/
apei_resources_init(&nvs_resources);
rc = apei_get_nvs_resources(&nvs_resources);
if (rc)
goto res_fini;
rc = apei_resources_sub(resources, &nvs_resources);
if (rc)
goto res_fini;
rc = -EINVAL;
list_for_each_entry(res, &resources->iomem, list) {
r = request_mem_region(res->start, res->end - res->start,
desc);
if (!r) {
pr_err(APEI_PFX
"Can not request iomem region <%016llx-%016llx> for GARs.\n",
"Can not request [mem %#010llx-%#010llx] for %s registers\n",
(unsigned long long)res->start,
(unsigned long long)res->end);
(unsigned long long)res->end - 1, desc);
res_bak = res;
goto err_unmap_iomem;
}
@ -472,9 +508,9 @@ int apei_resources_request(struct apei_resources *resources,
r = request_region(res->start, res->end - res->start, desc);
if (!r) {
pr_err(APEI_PFX
"Can not request ioport region <%016llx-%016llx> for GARs.\n",
"Can not request [io %#06llx-%#06llx] for %s registers\n",
(unsigned long long)res->start,
(unsigned long long)res->end);
(unsigned long long)res->end - 1, desc);
res_bak = res;
goto err_unmap_ioport;
}
@ -500,6 +536,8 @@ int apei_resources_request(struct apei_resources *resources,
break;
release_mem_region(res->start, res->end - res->start);
}
res_fini:
apei_resources_fini(&nvs_resources);
return rc;
}
EXPORT_SYMBOL_GPL(apei_resources_request);

View File

@ -95,6 +95,9 @@ static inline void apei_resources_init(struct apei_resources *resources)
}
void apei_resources_fini(struct apei_resources *resources);
int apei_resources_add(struct apei_resources *resources,
unsigned long start, unsigned long size,
bool iomem);
int apei_resources_sub(struct apei_resources *resources1,
struct apei_resources *resources2);
int apei_resources_request(struct apei_resources *resources,

View File

@ -194,8 +194,29 @@ static int einj_check_trigger_header(struct acpi_einj_trigger *trigger_tab)
return 0;
}
static struct acpi_generic_address *einj_get_trigger_parameter_region(
struct acpi_einj_trigger *trigger_tab, u64 param1, u64 param2)
{
int i;
struct acpi_whea_header *entry;
entry = (struct acpi_whea_header *)
((char *)trigger_tab + sizeof(struct acpi_einj_trigger));
for (i = 0; i < trigger_tab->entry_count; i++) {
if (entry->action == ACPI_EINJ_TRIGGER_ERROR &&
entry->instruction == ACPI_EINJ_WRITE_REGISTER_VALUE &&
entry->register_region.space_id ==
ACPI_ADR_SPACE_SYSTEM_MEMORY &&
(entry->register_region.address & param2) == (param1 & param2))
return &entry->register_region;
entry++;
}
return NULL;
}
/* Execute instructions in trigger error action table */
static int __einj_error_trigger(u64 trigger_paddr)
static int __einj_error_trigger(u64 trigger_paddr, u32 type,
u64 param1, u64 param2)
{
struct acpi_einj_trigger *trigger_tab = NULL;
struct apei_exec_context trigger_ctx;
@ -204,14 +225,16 @@ static int __einj_error_trigger(u64 trigger_paddr)
struct resource *r;
u32 table_size;
int rc = -EIO;
struct acpi_generic_address *trigger_param_region = NULL;
r = request_mem_region(trigger_paddr, sizeof(*trigger_tab),
"APEI EINJ Trigger Table");
if (!r) {
pr_err(EINJ_PFX
"Can not request iomem region <%016llx-%016llx> for Trigger table.\n",
"Can not request [mem %#010llx-%#010llx] for Trigger table\n",
(unsigned long long)trigger_paddr,
(unsigned long long)trigger_paddr+sizeof(*trigger_tab));
(unsigned long long)trigger_paddr +
sizeof(*trigger_tab) - 1);
goto out;
}
trigger_tab = ioremap_cache(trigger_paddr, sizeof(*trigger_tab));
@ -232,9 +255,9 @@ static int __einj_error_trigger(u64 trigger_paddr)
"APEI EINJ Trigger Table");
if (!r) {
pr_err(EINJ_PFX
"Can not request iomem region <%016llx-%016llx> for Trigger Table Entry.\n",
(unsigned long long)trigger_paddr+sizeof(*trigger_tab),
(unsigned long long)trigger_paddr + table_size);
"Can not request [mem %#010llx-%#010llx] for Trigger Table Entry\n",
(unsigned long long)trigger_paddr + sizeof(*trigger_tab),
(unsigned long long)trigger_paddr + table_size - 1);
goto out_rel_header;
}
iounmap(trigger_tab);
@ -255,6 +278,30 @@ static int __einj_error_trigger(u64 trigger_paddr)
rc = apei_resources_sub(&trigger_resources, &einj_resources);
if (rc)
goto out_fini;
/*
* Some firmware will access target address specified in
* param1 to trigger the error when injecting memory error.
* This will cause resource conflict with regular memory. So
* remove it from trigger table resources.
*/
if (param_extension && (type & 0x0038) && param2) {
struct apei_resources addr_resources;
apei_resources_init(&addr_resources);
trigger_param_region = einj_get_trigger_parameter_region(
trigger_tab, param1, param2);
if (trigger_param_region) {
rc = apei_resources_add(&addr_resources,
trigger_param_region->address,
trigger_param_region->bit_width/8, true);
if (rc)
goto out_fini;
rc = apei_resources_sub(&trigger_resources,
&addr_resources);
}
apei_resources_fini(&addr_resources);
if (rc)
goto out_fini;
}
rc = apei_resources_request(&trigger_resources, "APEI EINJ Trigger");
if (rc)
goto out_fini;
@ -324,7 +371,7 @@ static int __einj_error_inject(u32 type, u64 param1, u64 param2)
if (rc)
return rc;
trigger_paddr = apei_exec_ctx_get_output(&ctx);
rc = __einj_error_trigger(trigger_paddr);
rc = __einj_error_trigger(trigger_paddr, type, param1, param2);
if (rc)
return rc;
rc = apei_exec_run_optional(&ctx, ACPI_EINJ_END_OPERATION);
@ -465,10 +512,9 @@ static int __init einj_init(void)
status = acpi_get_table(ACPI_SIG_EINJ, 0,
(struct acpi_table_header **)&einj_tab);
if (status == AE_NOT_FOUND) {
pr_info(EINJ_PFX "Table is not found!\n");
if (status == AE_NOT_FOUND)
return -ENODEV;
} else if (ACPI_FAILURE(status)) {
else if (ACPI_FAILURE(status)) {
const char *msg = acpi_format_exception(status);
pr_err(EINJ_PFX "Failed to get table, %s\n", msg);
return -EINVAL;

View File

@ -1125,10 +1125,9 @@ static int __init erst_init(void)
status = acpi_get_table(ACPI_SIG_ERST, 0,
(struct acpi_table_header **)&erst_tab);
if (status == AE_NOT_FOUND) {
pr_info(ERST_PFX "Table is not found!\n");
if (status == AE_NOT_FOUND)
goto err;
} else if (ACPI_FAILURE(status)) {
else if (ACPI_FAILURE(status)) {
const char *msg = acpi_format_exception(status);
pr_err(ERST_PFX "Failed to get table, %s\n", msg);
rc = -EINVAL;

View File

@ -45,6 +45,8 @@
#include <linux/irq_work.h>
#include <linux/llist.h>
#include <linux/genalloc.h>
#include <linux/pci.h>
#include <linux/aer.h>
#include <acpi/apei.h>
#include <acpi/atomicio.h>
#include <acpi/hed.h>
@ -476,6 +478,27 @@ static void ghes_do_proc(const struct acpi_hest_generic_status *estatus)
}
#endif
}
#ifdef CONFIG_ACPI_APEI_PCIEAER
else if (!uuid_le_cmp(*(uuid_le *)gdata->section_type,
CPER_SEC_PCIE)) {
struct cper_sec_pcie *pcie_err;
pcie_err = (struct cper_sec_pcie *)(gdata+1);
if (sev == GHES_SEV_RECOVERABLE &&
sec_sev == GHES_SEV_RECOVERABLE &&
pcie_err->validation_bits & CPER_PCIE_VALID_DEVICE_ID &&
pcie_err->validation_bits & CPER_PCIE_VALID_AER_INFO) {
unsigned int devfn;
int aer_severity;
devfn = PCI_DEVFN(pcie_err->device_id.device,
pcie_err->device_id.function);
aer_severity = cper_severity_to_aer(sev);
aer_recover_queue(pcie_err->device_id.segment,
pcie_err->device_id.bus,
devfn, aer_severity);
}
}
#endif
}
}
@ -483,16 +506,22 @@ static void __ghes_print_estatus(const char *pfx,
const struct acpi_hest_generic *generic,
const struct acpi_hest_generic_status *estatus)
{
static atomic_t seqno;
unsigned int curr_seqno;
char pfx_seq[64];
if (pfx == NULL) {
if (ghes_severity(estatus->error_severity) <=
GHES_SEV_CORRECTED)
pfx = KERN_WARNING HW_ERR;
pfx = KERN_WARNING;
else
pfx = KERN_ERR HW_ERR;
pfx = KERN_ERR;
}
curr_seqno = atomic_inc_return(&seqno);
snprintf(pfx_seq, sizeof(pfx_seq), "%s{%u}" HW_ERR, pfx, curr_seqno);
printk("%s""Hardware error from APEI Generic Hardware Error Source: %d\n",
pfx, generic->header.source_id);
apei_estatus_print(pfx, estatus);
pfx_seq, generic->header.source_id);
apei_estatus_print(pfx_seq, estatus);
}
static int ghes_print_estatus(const char *pfx,
@ -711,26 +740,34 @@ static int ghes_notify_sci(struct notifier_block *this,
return ret;
}
static void ghes_proc_in_irq(struct irq_work *irq_work)
static struct llist_node *llist_nodes_reverse(struct llist_node *llnode)
{
struct llist_node *llnode, *next, *tail = NULL;
struct ghes_estatus_node *estatus_node;
struct acpi_hest_generic *generic;
struct acpi_hest_generic_status *estatus;
u32 len, node_len;
struct llist_node *next, *tail = NULL;
/*
* Because the time order of estatus in list is reversed,
* revert it back to proper order.
*/
llnode = llist_del_all(&ghes_estatus_llist);
while (llnode) {
next = llnode->next;
llnode->next = tail;
tail = llnode;
llnode = next;
}
llnode = tail;
return tail;
}
static void ghes_proc_in_irq(struct irq_work *irq_work)
{
struct llist_node *llnode, *next;
struct ghes_estatus_node *estatus_node;
struct acpi_hest_generic *generic;
struct acpi_hest_generic_status *estatus;
u32 len, node_len;
llnode = llist_del_all(&ghes_estatus_llist);
/*
* Because the time order of estatus in list is reversed,
* revert it back to proper order.
*/
llnode = llist_nodes_reverse(llnode);
while (llnode) {
next = llnode->next;
estatus_node = llist_entry(llnode, struct ghes_estatus_node,
@ -750,6 +787,32 @@ static void ghes_proc_in_irq(struct irq_work *irq_work)
}
}
static void ghes_print_queued_estatus(void)
{
struct llist_node *llnode;
struct ghes_estatus_node *estatus_node;
struct acpi_hest_generic *generic;
struct acpi_hest_generic_status *estatus;
u32 len, node_len;
llnode = llist_del_all(&ghes_estatus_llist);
/*
* Because the time order of estatus in list is reversed,
* revert it back to proper order.
*/
llnode = llist_nodes_reverse(llnode);
while (llnode) {
estatus_node = llist_entry(llnode, struct ghes_estatus_node,
llnode);
estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
len = apei_estatus_len(estatus);
node_len = GHES_ESTATUS_NODE_LEN(len);
generic = estatus_node->generic;
ghes_print_estatus(NULL, generic, estatus);
llnode = llnode->next;
}
}
static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs)
{
struct ghes *ghes, *ghes_global = NULL;
@ -775,7 +838,8 @@ static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs)
if (sev_global >= GHES_SEV_PANIC) {
oops_begin();
__ghes_print_estatus(KERN_EMERG HW_ERR, ghes_global->generic,
ghes_print_queued_estatus();
__ghes_print_estatus(KERN_EMERG, ghes_global->generic,
ghes_global->estatus);
/* reboot to log the error! */
if (panic_timeout == 0)

View File

@ -221,10 +221,9 @@ void __init acpi_hest_init(void)
status = acpi_get_table(ACPI_SIG_HEST, 0,
(struct acpi_table_header **)&hest_tab);
if (status == AE_NOT_FOUND) {
pr_info(HEST_PFX "Table not found.\n");
if (status == AE_NOT_FOUND)
goto err;
} else if (ACPI_FAILURE(status)) {
else if (ACPI_FAILURE(status)) {
const char *msg = acpi_format_exception(status);
pr_err(HEST_PFX "Failed to get table, %s\n", msg);
rc = -EINVAL;

View File

@ -32,6 +32,8 @@
#include <linux/rculist.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/highmem.h>
#include <acpi/atomicio.h>
#define ACPI_PFX "ACPI: "
@ -97,6 +99,37 @@ static void __iomem *__acpi_try_ioremap(phys_addr_t paddr,
return NULL;
}
#ifndef CONFIG_IA64
#define should_use_kmap(pfn) page_is_ram(pfn)
#else
/* ioremap will take care of cache attributes */
#define should_use_kmap(pfn) 0
#endif
static void __iomem *acpi_map(phys_addr_t pg_off, unsigned long pg_sz)
{
unsigned long pfn;
pfn = pg_off >> PAGE_SHIFT;
if (should_use_kmap(pfn)) {
if (pg_sz > PAGE_SIZE)
return NULL;
return (void __iomem __force *)kmap(pfn_to_page(pfn));
} else
return ioremap(pg_off, pg_sz);
}
static void acpi_unmap(phys_addr_t pg_off, void __iomem *vaddr)
{
unsigned long pfn;
pfn = pg_off >> PAGE_SHIFT;
if (page_is_ram(pfn))
kunmap(pfn_to_page(pfn));
else
iounmap(vaddr);
}
/*
* Used to pre-map the specified IO memory area. First try to find
* whether the area is already pre-mapped, if it is, increase the
@ -119,7 +152,7 @@ static void __iomem *acpi_pre_map(phys_addr_t paddr,
pg_off = paddr & PAGE_MASK;
pg_sz = ((paddr + size + PAGE_SIZE - 1) & PAGE_MASK) - pg_off;
vaddr = ioremap(pg_off, pg_sz);
vaddr = acpi_map(pg_off, pg_sz);
if (!vaddr)
return NULL;
map = kmalloc(sizeof(*map), GFP_KERNEL);
@ -135,7 +168,7 @@ static void __iomem *acpi_pre_map(phys_addr_t paddr,
vaddr = __acpi_try_ioremap(paddr, size);
if (vaddr) {
spin_unlock_irqrestore(&acpi_iomaps_lock, flags);
iounmap(map->vaddr);
acpi_unmap(pg_off, map->vaddr);
kfree(map);
return vaddr;
}
@ -144,7 +177,7 @@ static void __iomem *acpi_pre_map(phys_addr_t paddr,
return map->vaddr + (paddr - map->paddr);
err_unmap:
iounmap(vaddr);
acpi_unmap(pg_off, vaddr);
return NULL;
}
@ -177,7 +210,7 @@ static void acpi_post_unmap(phys_addr_t paddr, unsigned long size)
return;
synchronize_rcu();
iounmap(map->vaddr);
acpi_unmap(map->paddr, map->vaddr);
kfree(map);
}
@ -260,6 +293,21 @@ int acpi_post_unmap_gar(struct acpi_generic_address *reg)
}
EXPORT_SYMBOL_GPL(acpi_post_unmap_gar);
#ifdef readq
static inline u64 read64(const volatile void __iomem *addr)
{
return readq(addr);
}
#else
static inline u64 read64(const volatile void __iomem *addr)
{
u64 l, h;
l = readl(addr);
h = readl(addr+4);
return l | (h << 32);
}
#endif
/*
* Can be used in atomic (including NMI) or process context. RCU read
* lock can only be released after the IO memory area accessing.
@ -280,11 +328,9 @@ static int acpi_atomic_read_mem(u64 paddr, u64 *val, u32 width)
case 32:
*val = readl(addr);
break;
#ifdef readq
case 64:
*val = readq(addr);
*val = read64(addr);
break;
#endif
default:
return -EINVAL;
}
@ -293,6 +339,19 @@ static int acpi_atomic_read_mem(u64 paddr, u64 *val, u32 width)
return 0;
}
#ifdef writeq
static inline void write64(u64 val, volatile void __iomem *addr)
{
writeq(val, addr);
}
#else
static inline void write64(u64 val, volatile void __iomem *addr)
{
writel(val, addr);
writel(val>>32, addr+4);
}
#endif
static int acpi_atomic_write_mem(u64 paddr, u64 val, u32 width)
{
void __iomem *addr;
@ -309,11 +368,9 @@ static int acpi_atomic_write_mem(u64 paddr, u64 val, u32 width)
case 32:
writel(val, addr);
break;
#ifdef writeq
case 64:
writeq(val, addr);
write64(val, addr);
break;
#endif
default:
return -EINVAL;
}

View File

@ -15,6 +15,56 @@
#include <linux/acpi_io.h>
#include <acpi/acpiosxf.h>
/* ACPI NVS regions, APEI may use it */
struct nvs_region {
__u64 phys_start;
__u64 size;
struct list_head node;
};
static LIST_HEAD(nvs_region_list);
#ifdef CONFIG_ACPI_SLEEP
static int suspend_nvs_register(unsigned long start, unsigned long size);
#else
static inline int suspend_nvs_register(unsigned long a, unsigned long b)
{
return 0;
}
#endif
int acpi_nvs_register(__u64 start, __u64 size)
{
struct nvs_region *region;
region = kmalloc(sizeof(*region), GFP_KERNEL);
if (!region)
return -ENOMEM;
region->phys_start = start;
region->size = size;
list_add_tail(&region->node, &nvs_region_list);
return suspend_nvs_register(start, size);
}
int acpi_nvs_for_each_region(int (*func)(__u64 start, __u64 size, void *data),
void *data)
{
int rc;
struct nvs_region *region;
list_for_each_entry(region, &nvs_region_list, node) {
rc = func(region->phys_start, region->size, data);
if (rc)
return rc;
}
return 0;
}
#ifdef CONFIG_ACPI_SLEEP
/*
* Platforms, like ACPI, may want us to save some memory used by them during
* suspend and to restore the contents of this memory during the subsequent
@ -41,7 +91,7 @@ static LIST_HEAD(nvs_list);
* things so that the data from page-aligned addresses in this region will
* be copied into separate RAM pages.
*/
int suspend_nvs_register(unsigned long start, unsigned long size)
static int suspend_nvs_register(unsigned long start, unsigned long size)
{
struct nvs_page *entry, *next;
@ -159,3 +209,4 @@ void suspend_nvs_restore(void)
if (entry->data)
memcpy(entry->kaddr, entry->data, entry->size);
}
#endif

View File

@ -306,6 +306,11 @@ extern acpi_status acpi_pci_osc_control_set(acpi_handle handle,
u32 *mask, u32 req);
extern void acpi_early_init(void);
extern int acpi_nvs_register(__u64 start, __u64 size);
extern int acpi_nvs_for_each_region(int (*func)(__u64, __u64, void *),
void *data);
#else /* !CONFIG_ACPI */
#define acpi_disabled 1
@ -348,15 +353,18 @@ static inline int acpi_table_parse(char *id,
{
return -1;
}
#endif /* !CONFIG_ACPI */
#ifdef CONFIG_ACPI_SLEEP
int suspend_nvs_register(unsigned long start, unsigned long size);
#else
static inline int suspend_nvs_register(unsigned long a, unsigned long b)
static inline int acpi_nvs_register(__u64 start, __u64 size)
{
return 0;
}
#endif
static inline int acpi_nvs_for_each_region(int (*func)(__u64, __u64, void *),
void *data)
{
return 0;
}
#endif /* !CONFIG_ACPI */
#endif /*_LINUX_ACPI_H*/