diff options
author | Keir Fraser <keir.fraser@citrix.com> | 2010-08-22 09:52:18 +0100 |
---|---|---|
committer | Keir Fraser <keir.fraser@citrix.com> | 2010-08-22 09:52:18 +0100 |
commit | 96590ca76049f2679ba72e43bc0bde6e9e537978 (patch) | |
tree | 77bd60fffd99e086e569b7faa1793595c2c1a44d /xen/drivers/acpi | |
parent | 37eb6d05fe1a343ec051f5d59e88b8ee5a450ceb (diff) | |
download | xen-96590ca76049f2679ba72e43bc0bde6e9e537978.tar.gz xen-96590ca76049f2679ba72e43bc0bde6e9e537978.tar.bz2 xen-96590ca76049f2679ba72e43bc0bde6e9e537978.zip |
Implement ACPI APEI ERST feature
APEI are ACPI4.0 new features. It consists of ERST, BERT, HEST, and
EINJ. ERST is used to save fault error log to a platform persistent
storage, so that when reboot os can retrieve the error log and handle
it.
This patch is used to implement ERST feature to Xen. It consists of
3-level hierarchy: operation level, action level, and instru= ction
level. Instruction do basic io; Action done by sequential
instructions parsed from ACPI ERST table; Operation done by
sequential actions defined by ACPI spec, providing erst_write/
erst_read/ erst_clear interfaces to MCE/ NMI/ PCIe error handling
mechanism, etc.
Signed-off-by: Liu, Jinsong <jinsong.liu@intel.com>
Diffstat (limited to 'xen/drivers/acpi')
-rw-r--r-- | xen/drivers/acpi/Makefile | 1 | ||||
-rw-r--r-- | xen/drivers/acpi/apei/Makefile | 3 | ||||
-rw-r--r-- | xen/drivers/acpi/apei/apei-base.c | 278 | ||||
-rw-r--r-- | xen/drivers/acpi/apei/apei-internal.h | 73 | ||||
-rw-r--r-- | xen/drivers/acpi/apei/apei-io.c | 328 | ||||
-rw-r--r-- | xen/drivers/acpi/apei/erst.c | 787 |
6 files changed, 1470 insertions, 0 deletions
diff --git a/xen/drivers/acpi/Makefile b/xen/drivers/acpi/Makefile index 7d108c43a9..f7a693f028 100644 --- a/xen/drivers/acpi/Makefile +++ b/xen/drivers/acpi/Makefile @@ -1,5 +1,6 @@ subdir-y += tables subdir-y += utilities +subdir-y += apei obj-y += tables.o obj-y += numa.o diff --git a/xen/drivers/acpi/apei/Makefile b/xen/drivers/acpi/apei/Makefile new file mode 100644 index 0000000000..af6ecb0261 --- /dev/null +++ b/xen/drivers/acpi/apei/Makefile @@ -0,0 +1,3 @@ +obj-y += erst.o +obj-y += apei-base.o +obj-y += apei-io.o diff --git a/xen/drivers/acpi/apei/apei-base.c b/xen/drivers/acpi/apei/apei-base.c new file mode 100644 index 0000000000..e9420f05e7 --- /dev/null +++ b/xen/drivers/acpi/apei/apei-base.c @@ -0,0 +1,278 @@ +/* + * apei-base.c - ACPI Platform Error Interface (APEI) supporting + * infrastructure + * + * APEI allows to report errors (for example from the chipset) to the + * the operating system. This improves NMI handling especially. In + * addition it supports error serialization and error injection. + * + * For more information about APEI, please refer to ACPI Specification + * version 4.0, chapter 17. + * + * This file has Common functions used by more than one APEI table, + * including framework of interpreter for ERST and EINJ; resource + * management for APEI registers. + * + * This feature is ported from linux acpi tree + * Copyright (C) 2009, Intel Corp. + * Author: Huang Ying <ying.huang@intel.com> + * Ported by: Liu, Jinsong <jinsong.liu@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <xen/kernel.h> +#include <xen/errno.h> +#include <xen/delay.h> +#include <xen/string.h> +#include <xen/types.h> +#include <xen/spinlock.h> +#include <xen/cper.h> +#include <asm/io.h> +#include <acpi/acpi.h> +#include <acpi/apei.h> + +#include "apei-internal.h" + +/* + * APEI ERST (Error Record Serialization Table) and EINJ (Error + * INJection) interpreter framework. + */ + +#define APEI_EXEC_PRESERVE_REGISTER 0x1 + +int apei_exec_ctx_init(struct apei_exec_context *ctx, + struct apei_exec_ins_type *ins_table, + u32 instructions, + struct acpi_whea_header *action_table, + u32 entries) +{ + if (!ctx) + return -EINVAL; + + ctx->ins_table = ins_table; + ctx->instructions = instructions; + ctx->action_table = action_table; + ctx->entries = entries; + return 0; +} + +int __apei_exec_read_register(struct acpi_whea_header *entry, u64 *val) +{ + int rc; + + rc = apei_read(val, &entry->register_region); + if (rc) + return rc; + *val >>= entry->register_region.bit_offset; + *val &= entry->mask; + + return 0; +} + +int apei_exec_read_register(struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + int rc; + u64 val = 0; + + rc = __apei_exec_read_register(entry, &val); + if (rc) + return rc; + ctx->value = val; + + return 0; +} + +int apei_exec_read_register_value(struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + int rc; + + rc = apei_exec_read_register(ctx, entry); + if (rc) + return rc; + ctx->value = (ctx->value == entry->value); + + return 0; +} + +int __apei_exec_write_register(struct acpi_whea_header *entry, u64 val) +{ + int rc; + + val &= entry->mask; + val <<= entry->register_region.bit_offset; + if (entry->flags & APEI_EXEC_PRESERVE_REGISTER) { + u64 valr = 0; + rc = apei_read(&valr, &entry->register_region); + if (rc) + return rc; + valr &= ~(entry->mask << entry->register_region.bit_offset); + val |= valr; + } + rc = apei_write(val, &entry->register_region); + + return rc; +} + +int apei_exec_write_register(struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + return __apei_exec_write_register(entry, ctx->value); +} + +int apei_exec_write_register_value(struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + int rc; + + ctx->value = entry->value; + rc = apei_exec_write_register(ctx, entry); + + return rc; +} + +int apei_exec_noop(struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + return 0; +} + +/* + * Interpret the specified action. Go through whole action table, + * execute all instructions belong to the action. + */ +int apei_exec_run(struct apei_exec_context *ctx, u8 action) +{ + int rc; + u32 i, ip; + struct acpi_whea_header *entry; + apei_exec_ins_func_t run; + + ctx->ip = 0; + + /* + * "ip" is the instruction pointer of current instruction, + * "ctx->ip" specifies the next instruction to executed, + * instruction "run" function may change the "ctx->ip" to + * implement "goto" semantics. + */ +rewind: + ip = 0; + for (i = 0; i < ctx->entries; i++) { + entry = &ctx->action_table[i]; + if (entry->action != action) + continue; + if (ip == ctx->ip) { + if (entry->instruction >= ctx->instructions || + !ctx->ins_table[entry->instruction].run) { + printk(KERN_WARNING + "Invalid action table, unknown instruction " + "type: %d\n", entry->instruction); + return -EINVAL; + } + run = ctx->ins_table[entry->instruction].run; + rc = run(ctx, entry); + if (rc < 0) + return rc; + else if (rc != APEI_EXEC_SET_IP) + ctx->ip++; + } + ip++; + if (ctx->ip < ip) + goto rewind; + } + + return 0; +} + +typedef int (*apei_exec_entry_func_t)(struct apei_exec_context *ctx, + struct acpi_whea_header *entry, + void *data); + +static int apei_exec_for_each_entry(struct apei_exec_context *ctx, + apei_exec_entry_func_t func, + void *data, + int *end) +{ + u8 ins; + int i, rc; + struct acpi_whea_header *entry; + struct apei_exec_ins_type *ins_table = ctx->ins_table; + + for (i = 0; i < ctx->entries; i++) { + entry = ctx->action_table + i; + ins = entry->instruction; + if (end) + *end = i; + if (ins >= ctx->instructions || !ins_table[ins].run) { + printk(KERN_WARNING "Invalid action table, " + "unknown instruction type: %d\n", ins); + return -EINVAL; + } + rc = func(ctx, entry, data); + if (rc) + return rc; + } + + return 0; +} + +static int pre_map_gar_callback(struct apei_exec_context *ctx, + struct acpi_whea_header *entry, + void *data) +{ + u8 ins = entry->instruction; + + if (ctx->ins_table[ins].flags & APEI_EXEC_INS_ACCESS_REGISTER) + return apei_pre_map_gar(&entry->register_region); + + return 0; +} + +/* Pre-map all GARs in action table. */ +int apei_exec_pre_map_gars(struct apei_exec_context *ctx) +{ + int rc, end; + + rc = apei_exec_for_each_entry(ctx, pre_map_gar_callback, + NULL, &end); + if (rc) { + struct apei_exec_context ctx_unmap; + memcpy(&ctx_unmap, ctx, sizeof(*ctx)); + ctx_unmap.entries = end; + apei_exec_post_unmap_gars(&ctx_unmap); + } + + return rc; +} + +static int post_unmap_gar_callback(struct apei_exec_context *ctx, + struct acpi_whea_header *entry, + void *data) +{ + u8 ins = entry->instruction; + + if (ctx->ins_table[ins].flags & APEI_EXEC_INS_ACCESS_REGISTER) + apei_post_unmap_gar(&entry->register_region); + + return 0; +} + +/* Post-unmap all GAR in action table. */ +int apei_exec_post_unmap_gars(struct apei_exec_context *ctx) +{ + return apei_exec_for_each_entry(ctx, post_unmap_gar_callback, + NULL, NULL); +} diff --git a/xen/drivers/acpi/apei/apei-internal.h b/xen/drivers/acpi/apei/apei-internal.h new file mode 100644 index 0000000000..77f31fb993 --- /dev/null +++ b/xen/drivers/acpi/apei/apei-internal.h @@ -0,0 +1,73 @@ +/* + * apei-internal.h - ACPI Platform Error Interface internal + * definations. + */ + +#ifndef APEI_INTERNAL_H +#define APEI_INTERNAL_H + +struct apei_exec_context; + +typedef int (*apei_exec_ins_func_t)(struct apei_exec_context *ctx, + struct acpi_whea_header *entry); + +#define APEI_EXEC_INS_ACCESS_REGISTER 0x0001 + +struct apei_exec_ins_type { + u32 flags; + apei_exec_ins_func_t run; +}; + +struct apei_exec_context { + u32 ip; + u64 value; + u64 var1; + u64 var2; + u64 src_base; + u64 dst_base; + struct apei_exec_ins_type *ins_table; + u32 instructions; + struct acpi_whea_header *action_table; + u32 entries; +}; + +int apei_exec_ctx_init(struct apei_exec_context *ctx, + struct apei_exec_ins_type *ins_table, + u32 instructions, + struct acpi_whea_header *action_table, + u32 entries); + +static inline void apei_exec_ctx_set_input(struct apei_exec_context *ctx, + u64 input) +{ + ctx->value = input; +} + +static inline u64 apei_exec_ctx_get_output(struct apei_exec_context *ctx) +{ + return ctx->value; +} + +int apei_exec_run(struct apei_exec_context *ctx, u8 action); + +/* Common instruction implementation */ + +/* IP has been set in instruction function */ +#define APEI_EXEC_SET_IP 1 + +int __apei_exec_read_register(struct acpi_whea_header *entry, u64 *val); +int __apei_exec_write_register(struct acpi_whea_header *entry, u64 val); +int apei_exec_read_register(struct apei_exec_context *ctx, + struct acpi_whea_header *entry); +int apei_exec_read_register_value(struct apei_exec_context *ctx, + struct acpi_whea_header *entry); +int apei_exec_write_register(struct apei_exec_context *ctx, + struct acpi_whea_header *entry); +int apei_exec_write_register_value(struct apei_exec_context *ctx, + struct acpi_whea_header *entry); +int apei_exec_noop(struct apei_exec_context *ctx, + struct acpi_whea_header *entry); +int apei_exec_pre_map_gars(struct apei_exec_context *ctx); +int apei_exec_post_unmap_gars(struct apei_exec_context *ctx); + +#endif diff --git a/xen/drivers/acpi/apei/apei-io.c b/xen/drivers/acpi/apei/apei-io.c new file mode 100644 index 0000000000..e77fae8da2 --- /dev/null +++ b/xen/drivers/acpi/apei/apei-io.c @@ -0,0 +1,328 @@ +/* + * apei-io.c - APEI IO memory pre-mapping/post-unmapping and access + * + * Copyright (C) 2009-2010, Intel Corp. + * Author: Huang Ying <ying.huang@intel.com> + * Ported by: Liu, Jinsong <jinsong.liu@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <xen/kernel.h> +#include <xen/errno.h> +#include <xen/delay.h> +#include <xen/string.h> +#include <xen/xmalloc.h> +#include <xen/types.h> +#include <xen/spinlock.h> +#include <xen/list.h> +#include <xen/cper.h> +#include <xen/prefetch.h> +#include <asm/fixmap.h> +#include <asm/io.h> +#include <acpi/acpi.h> +#include <acpi/apei.h> + +static LIST_HEAD(apei_iomaps); +/* + * Used for mutual exclusion between writers of apei_iomaps list, for + * synchronization between readers and writer. + */ +static DEFINE_SPINLOCK(apei_iomaps_lock); + +struct apei_iomap { + struct list_head list; + void __iomem *vaddr; + unsigned long size; + paddr_t paddr; +}; + +static struct apei_iomap *__apei_find_iomap(paddr_t paddr, + unsigned long size) +{ + struct apei_iomap *map; + + list_for_each_entry(map, &apei_iomaps, list) { + if (map->paddr + map->size >= paddr + size && + map->paddr <= paddr) + return map; + } + return NULL; +} + +static void __iomem *__apei_ioremap_fast(paddr_t paddr, + unsigned long size) +{ + struct apei_iomap *map; + + map = __apei_find_iomap(paddr, size); + if (map) + return map->vaddr + (paddr - map->paddr); + else + return NULL; +} + +static int apei_range_nr; + +static void __iomem *apei_range_map(paddr_t paddr, unsigned long size) +{ + int i, pg; + int start_nr, cur_nr; + + pg = ((((paddr + size -1) & PAGE_MASK) + - (paddr & PAGE_MASK)) >> PAGE_SHIFT) + 1; + if (apei_range_nr + pg > FIX_APEI_RANGE_MAX) + return NULL; + + start_nr = apei_range_nr + pg -1; + for (i = 0; i < pg; i++) { + cur_nr = start_nr - i; + set_fixmap_nocache(FIX_APEI_RANGE_BASE + cur_nr, + paddr + (i << PAGE_SHIFT)); + apei_range_nr++; + } + + return (void __iomem *)fix_to_virt(FIX_APEI_RANGE_BASE + start_nr); +} + +/* + * Used to pre-map the specified IO memory area. First try to find + * whether the area is already pre-mapped, if it is, return; otherwise, + * do the real map, and add the mapping into apei_iomaps list. + */ +void __iomem *apei_pre_map(paddr_t paddr, unsigned long size) +{ + void __iomem *vaddr; + struct apei_iomap *map; + unsigned long flags; + + spin_lock_irqsave(&apei_iomaps_lock, flags); + vaddr = __apei_ioremap_fast(paddr, size); + spin_unlock_irqrestore(&apei_iomaps_lock, flags); + if (vaddr) + return vaddr; + + map = xmalloc(struct apei_iomap); + if (!map) + return NULL; + + vaddr = apei_range_map(paddr, size); + if (!vaddr) { + xfree(map); + return NULL; + } + + INIT_LIST_HEAD(&map->list); + map->paddr = paddr & PAGE_MASK; + map->size = (((paddr + size + PAGE_SIZE -1) & PAGE_MASK) + - (paddr & PAGE_MASK)); + map->vaddr = vaddr; + + spin_lock_irqsave(&apei_iomaps_lock, flags); + list_add_tail(&map->list, &apei_iomaps); + spin_unlock_irqrestore(&apei_iomaps_lock, flags); + + return map->vaddr + (paddr - map->paddr); +} + +/* + * Used to post-unmap the specified IO memory area. + */ +static void apei_post_unmap(paddr_t paddr, unsigned long size) +{ + struct apei_iomap *map; + unsigned long flags; + + spin_lock_irqsave(&apei_iomaps_lock, flags); + map = __apei_find_iomap(paddr, size); + if (!map); + return; + + list_del(&map->list); + spin_unlock_irqrestore(&apei_iomaps_lock, flags); + + xfree(map); +} + +/* In NMI handler, should set silent = 1 */ +static int apei_check_gar(struct acpi_generic_address *reg, + u64 *paddr, int silent) +{ + u32 width, space_id; + + width = reg->bit_width; + space_id = reg->space_id; + /* Handle possible alignment issues */ + memcpy(paddr, ®->address, sizeof(*paddr)); + if (!*paddr) { + if (!silent) + printk(KERN_WARNING + "Invalid physical address in GAR\n"); + return -EINVAL; + } + + if ((width != 8) && (width != 16) && (width != 32) && (width != 64)) { + if (!silent) + printk(KERN_WARNING + "Invalid bit width in GAR\n"); + return -EINVAL; + } + + if (space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY && + space_id != ACPI_ADR_SPACE_SYSTEM_IO) { + if (!silent) + printk(KERN_WARNING + "Invalid address space type in GAR\n"); + return -EINVAL; + } + + return 0; +} + +/* Pre-map, working on GAR */ +int apei_pre_map_gar(struct acpi_generic_address *reg) +{ + u64 paddr; + void __iomem *vaddr; + int rc; + + if (reg->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY) + return 0; + + rc = apei_check_gar(reg, &paddr, 0); + if (rc) + return rc; + + vaddr = apei_pre_map(paddr, reg->bit_width / 8); + if (!vaddr) + return -EIO; + + return 0; +} + +/* Post-unmap, working on GAR */ +int apei_post_unmap_gar(struct acpi_generic_address *reg) +{ + u64 paddr; + int rc; + + if (reg->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY) + return 0; + + rc = apei_check_gar(reg, &paddr, 0); + if (rc) + return rc; + + apei_post_unmap(paddr, reg->bit_width / 8); + + return 0; +} + +static int apei_read_mem(u64 paddr, u64 *val, u32 width) +{ + void __iomem *addr; + u64 tmpval; + + addr = __apei_ioremap_fast(paddr, width); + switch (width) { + case 8: + *val = readb(addr); + break; + case 16: + *val = readw(addr); + break; + case 32: + *val = readl(addr); + break; + case 64: + tmpval = (u64)readl(addr); + tmpval |= ((u64)readl(addr+4)) << 32; + *val = tmpval; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int apei_write_mem(u64 paddr, u64 val, u32 width) +{ + void __iomem *addr; + u32 tmpval; + + addr = __apei_ioremap_fast(paddr, width); + switch (width) { + case 8: + writeb(val, addr); + break; + case 16: + writew(val, addr); + break; + case 32: + writel(val, addr); + break; + case 64: + tmpval = (u32)val; + writel(tmpval, addr); + tmpval = (u32)(val >> 32); + writel(tmpval, addr+4); + break; + default: + return -EINVAL; + } + + return 0; +} + +int apei_read(u64 *val, struct acpi_generic_address *reg) +{ + u64 paddr; + int rc; + + rc = apei_check_gar(reg, &paddr, 1); + if (rc) + return rc; + + *val = 0; + + /* currently all erst implementation take bit_width as real range */ + switch (reg->space_id) { + case ACPI_ADR_SPACE_SYSTEM_MEMORY: + return apei_read_mem(paddr, val, reg->bit_width); + case ACPI_ADR_SPACE_SYSTEM_IO: + return acpi_os_read_port(paddr, (u32 *)val, reg->bit_width); + default: + return -EINVAL; + } +} + +int apei_write(u64 val, struct acpi_generic_address *reg) +{ + u64 paddr; + int rc; + + rc = apei_check_gar(reg, &paddr, 1); + if (rc) + return rc; + + switch (reg->space_id) { + case ACPI_ADR_SPACE_SYSTEM_MEMORY: + return apei_write_mem(paddr, val, reg->bit_width); + case ACPI_ADR_SPACE_SYSTEM_IO: + return acpi_os_write_port(paddr, val, reg->bit_width); + default: + return -EINVAL; + } +} diff --git a/xen/drivers/acpi/apei/erst.c b/xen/drivers/acpi/apei/erst.c new file mode 100644 index 0000000000..0aeb8f2501 --- /dev/null +++ b/xen/drivers/acpi/apei/erst.c @@ -0,0 +1,787 @@ +/* + * APEI Error Record Serialization Table support + * + * ERST is a way provided by APEI to save and retrieve hardware error + * infomation to and from a persistent store. + * + * For more information about ERST, please refer to ACPI Specification + * version 4.0, section 17.4. + * + * This feature is ported from linux acpi tree + * Copyright 2010 Intel Corp. + * Author: Huang Ying <ying.huang@intel.com> + * Ported by: Liu, Jinsong <jinsong.liu@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <xen/kernel.h> +#include <xen/errno.h> +#include <xen/delay.h> +#include <xen/string.h> +#include <xen/types.h> +#include <xen/spinlock.h> +#include <xen/cper.h> +#include <asm/fixmap.h> +#include <asm/io.h> +#include <acpi/acpi.h> +#include <acpi/apei.h> + +#include "apei-internal.h" + +/* ERST command status */ +#define ERST_STATUS_SUCCESS 0x0 +#define ERST_STATUS_NOT_ENOUGH_SPACE 0x1 +#define ERST_STATUS_HARDWARE_NOT_AVAILABLE 0x2 +#define ERST_STATUS_FAILED 0x3 +#define ERST_STATUS_RECORD_STORE_EMPTY 0x4 +#define ERST_STATUS_RECORD_NOT_FOUND 0x5 + +#define ERST_TAB_ENTRY(tab) \ + ((struct acpi_whea_header *)((char *)(tab) + \ + sizeof(struct acpi_table_erst))) + +#define SPIN_UNIT 1 /* 1us */ +/* Firmware should respond within 1 miliseconds */ +#define FIRMWARE_TIMEOUT (1 * 1000) +#define FIRMWARE_MAX_STALL 50 /* 50us */ + +static struct acpi_table_erst *erst_tab; +static int erst_enabled; + +/* ERST Error Log Address Range atrributes */ +#define ERST_RANGE_RESERVED 0x0001 +#define ERST_RANGE_NVRAM 0x0002 +#define ERST_RANGE_SLOW 0x0004 + +/* + * ERST Error Log Address Range, used as buffer for reading/writing + * error records. + */ +static struct erst_erange { + u64 base; + u64 size; + void __iomem *vaddr; + u32 attr; +} erst_erange; + +/* + * Prevent ERST interpreter to run simultaneously, because the + * corresponding firmware implementation may not work properly when + * invoked simultaneously. + * + * It is used to provide exclusive accessing for ERST Error Log + * Address Range too. + */ +static DEFINE_SPINLOCK(erst_lock); + +static inline int erst_errno(int command_status) +{ + switch (command_status) { + case ERST_STATUS_SUCCESS: + return 0; + case ERST_STATUS_HARDWARE_NOT_AVAILABLE: + return -ENODEV; + case ERST_STATUS_NOT_ENOUGH_SPACE: + return -ENOSPC; + case ERST_STATUS_RECORD_STORE_EMPTY: + case ERST_STATUS_RECORD_NOT_FOUND: + return -ENOENT; + default: + return -EINVAL; + } +} + +static int erst_timedout(u64 *t, u64 spin_unit) +{ + if ((s64)*t < spin_unit) { + printk(XENLOG_WARNING "Firmware does not respond in time\n"); + return 1; + } + *t -= spin_unit; + udelay(spin_unit); + return 0; +} + +static int erst_exec_load_var1(struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + return __apei_exec_read_register(entry, &ctx->var1); +} + +static int erst_exec_load_var2(struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + return __apei_exec_read_register(entry, &ctx->var2); +} + +static int erst_exec_store_var1(struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + return __apei_exec_write_register(entry, ctx->var1); +} + +static int erst_exec_add(struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + ctx->var1 += ctx->var2; + return 0; +} + +static int erst_exec_subtract(struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + ctx->var1 -= ctx->var2; + return 0; +} + +static int erst_exec_add_value(struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + int rc; + u64 val; + + rc = __apei_exec_read_register(entry, &val); + if (rc) + return rc; + val += ctx->value; + rc = __apei_exec_write_register(entry, val); + return rc; +} + +static int erst_exec_subtract_value(struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + int rc; + u64 val; + + rc = __apei_exec_read_register(entry, &val); + if (rc) + return rc; + val -= ctx->value; + rc = __apei_exec_write_register(entry, val); + return rc; +} + +static int erst_exec_stall(struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + udelay((ctx->var1 > FIRMWARE_MAX_STALL) ? + FIRMWARE_MAX_STALL : + ctx->var1); + return 0; +} + +static int erst_exec_stall_while_true(struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + int rc; + u64 val; + u64 timeout = FIRMWARE_TIMEOUT; + u64 stall_time = (ctx->var1 > FIRMWARE_MAX_STALL) ? + FIRMWARE_MAX_STALL : + ctx->var1; + + for (;;) { + rc = __apei_exec_read_register(entry, &val); + if (rc) + return rc; + if (val != ctx->value) + break; + if (erst_timedout(&timeout, stall_time)) + return -EIO; + } + return 0; +} + +static int erst_exec_skip_next_instruction_if_true( + struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + int rc; + u64 val; + + rc = __apei_exec_read_register(entry, &val); + if (rc) + return rc; + if (val == ctx->value) { + ctx->ip += 2; + return APEI_EXEC_SET_IP; + } + + return 0; +} + +static int erst_exec_goto(struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + ctx->ip = ctx->value; + return APEI_EXEC_SET_IP; +} + +static int erst_exec_set_src_address_base(struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + return __apei_exec_read_register(entry, &ctx->src_base); +} + +static int erst_exec_set_dst_address_base(struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + return __apei_exec_read_register(entry, &ctx->dst_base); +} + +static int erst_exec_move_data(struct apei_exec_context *ctx, + struct acpi_whea_header *entry) +{ + int rc; + u64 offset; + + rc = __apei_exec_read_register(entry, &offset); + if (rc) + return rc; + memmove((void *)(unsigned long)(ctx->dst_base + offset), + (void *)(unsigned long)(ctx->src_base + offset), + ctx->var2); + + return 0; +} + +static struct apei_exec_ins_type erst_ins_type[] = { + [ACPI_ERST_READ_REGISTER] = { + .flags = APEI_EXEC_INS_ACCESS_REGISTER, + .run = apei_exec_read_register, + }, + [ACPI_ERST_READ_REGISTER_VALUE] = { + .flags = APEI_EXEC_INS_ACCESS_REGISTER, + .run = apei_exec_read_register_value, + }, + [ACPI_ERST_WRITE_REGISTER] = { + .flags = APEI_EXEC_INS_ACCESS_REGISTER, + .run = apei_exec_write_register, + }, + [ACPI_ERST_WRITE_REGISTER_VALUE] = { + .flags = APEI_EXEC_INS_ACCESS_REGISTER, + .run = apei_exec_write_register_value, + }, + [ACPI_ERST_NOOP] = { + .flags = 0, + .run = apei_exec_noop, + }, + [ACPI_ERST_LOAD_VAR1] = { + .flags = APEI_EXEC_INS_ACCESS_REGISTER, + .run = erst_exec_load_var1, + }, + [ACPI_ERST_LOAD_VAR2] = { + .flags = APEI_EXEC_INS_ACCESS_REGISTER, + .run = erst_exec_load_var2, + }, + [ACPI_ERST_STORE_VAR1] = { + .flags = APEI_EXEC_INS_ACCESS_REGISTER, + .run = erst_exec_store_var1, + }, + [ACPI_ERST_ADD] = { + .flags = 0, + .run = erst_exec_add, + }, + [ACPI_ERST_SUBTRACT] = { + .flags = 0, + .run = erst_exec_subtract, + }, + [ACPI_ERST_ADD_VALUE] = { + .flags = APEI_EXEC_INS_ACCESS_REGISTER, + .run = erst_exec_add_value, + }, + [ACPI_ERST_SUBTRACT_VALUE] = { + .flags = APEI_EXEC_INS_ACCESS_REGISTER, + .run = erst_exec_subtract_value, + }, + [ACPI_ERST_STALL] = { + .flags = 0, + .run = erst_exec_stall, + }, + [ACPI_ERST_STALL_WHILE_TRUE] = { + .flags = APEI_EXEC_INS_ACCESS_REGISTER, + .run = erst_exec_stall_while_true, + }, + [ACPI_ERST_SKIP_NEXT_IF_TRUE] = { + .flags = APEI_EXEC_INS_ACCESS_REGISTER, + .run = erst_exec_skip_next_instruction_if_true, + }, + [ACPI_ERST_GOTO] = { + .flags = 0, + .run = erst_exec_goto, + }, + [ACPI_ERST_SET_SRC_ADDRESS_BASE] = { + .flags = APEI_EXEC_INS_ACCESS_REGISTER, + .run = erst_exec_set_src_address_base, + }, + [ACPI_ERST_SET_DST_ADDRESS_BASE] = { + .flags = APEI_EXEC_INS_ACCESS_REGISTER, + .run = erst_exec_set_dst_address_base, + }, + [ACPI_ERST_MOVE_DATA] = { + .flags = APEI_EXEC_INS_ACCESS_REGISTER, + .run = erst_exec_move_data, + }, +}; + +static inline void erst_exec_ctx_init(struct apei_exec_context *ctx) +{ + apei_exec_ctx_init(ctx, erst_ins_type, ARRAY_SIZE(erst_ins_type), + ERST_TAB_ENTRY(erst_tab), erst_tab->entries); +} + +static int erst_get_erange(struct erst_erange *range) +{ + struct apei_exec_context ctx; + int rc; + + erst_exec_ctx_init(&ctx); + rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_RANGE); + if (rc) + return rc; + range->base = apei_exec_ctx_get_output(&ctx); + rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_LENGTH); + if (rc) + return rc; + range->size = apei_exec_ctx_get_output(&ctx); + rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_ATTRIBUTES); + if (rc) + return rc; + range->attr = apei_exec_ctx_get_output(&ctx); + + return 0; +} + +static size_t __erst_get_record_count(void) +{ + struct apei_exec_context ctx; + int rc; + + erst_exec_ctx_init(&ctx); + rc = apei_exec_run(&ctx, ACPI_ERST_GET_RECORD_COUNT); + if (rc) + return rc; + return apei_exec_ctx_get_output(&ctx); +} + +size_t erst_get_record_count(void) +{ + size_t count; + unsigned long flags; + + if (!erst_enabled) + return -ENODEV; + + spin_lock_irqsave(&erst_lock, flags); + count = __erst_get_record_count(); + spin_unlock_irqrestore(&erst_lock, flags); + + return count; +} + +static int __erst_get_next_record_id(u64 *record_id) +{ + struct apei_exec_context ctx; + int rc; + + erst_exec_ctx_init(&ctx); + rc = apei_exec_run(&ctx, ACPI_ERST_GET_RECORD_ID); + if (rc) + return rc; + *record_id = apei_exec_ctx_get_output(&ctx); + + return 0; +} + +/* + * Get the record ID of an existing error record on the persistent + * storage. If there is no error record on the persistent storage, the + * returned record_id is APEI_ERST_INVALID_RECORD_ID. + */ +int erst_get_next_record_id(u64 *record_id) +{ + int rc; + unsigned long flags; + + if (!erst_enabled) + return -ENODEV; + + spin_lock_irqsave(&erst_lock, flags); + rc = __erst_get_next_record_id(record_id); + spin_unlock_irqrestore(&erst_lock, flags); + + return rc; +} + +static int __erst_write_to_storage(u64 offset) +{ + struct apei_exec_context ctx; + u64 timeout = FIRMWARE_TIMEOUT; + u64 val; + int rc; + + erst_exec_ctx_init(&ctx); + rc = apei_exec_run(&ctx, ACPI_ERST_BEGIN_WRITE); + if (rc) + return rc; + apei_exec_ctx_set_input(&ctx, offset); + rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_OFFSET); + if (rc) + return rc; + rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION); + if (rc) + return rc; + for (;;) { + rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS); + if (rc) + return rc; + val = apei_exec_ctx_get_output(&ctx); + if (!val) + break; + if (erst_timedout(&timeout, SPIN_UNIT)) + return -EIO; + } + rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS); + if (rc) + return rc; + val = apei_exec_ctx_get_output(&ctx); + rc = apei_exec_run(&ctx, ACPI_ERST_END); + if (rc) + return rc; + + return erst_errno(val); +} + +static int __erst_read_from_storage(u64 record_id, u64 offset) +{ + struct apei_exec_context ctx; + u64 timeout = FIRMWARE_TIMEOUT; + u64 val; + int rc; + + erst_exec_ctx_init(&ctx); + rc = apei_exec_run(&ctx, ACPI_ERST_BEGIN_READ); + if (rc) + return rc; + apei_exec_ctx_set_input(&ctx, offset); + rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_OFFSET); + if (rc) + return rc; + apei_exec_ctx_set_input(&ctx, record_id); + rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_ID); + if (rc) + return rc; + rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION); + if (rc) + return rc; + for (;;) { + rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS); + if (rc) + return rc; + val = apei_exec_ctx_get_output(&ctx); + if (!val) + break; + if (erst_timedout(&timeout, SPIN_UNIT)) + return -EIO; + }; + rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS); + if (rc) + return rc; + val = apei_exec_ctx_get_output(&ctx); + rc = apei_exec_run(&ctx, ACPI_ERST_END); + if (rc) + return rc; + + return erst_errno(val); +} + +static int __erst_clear_from_storage(u64 record_id) +{ + struct apei_exec_context ctx; + u64 timeout = FIRMWARE_TIMEOUT; + u64 val; + int rc; + + erst_exec_ctx_init(&ctx); + rc = apei_exec_run(&ctx, ACPI_ERST_BEGIN_CLEAR); + if (rc) + return rc; + apei_exec_ctx_set_input(&ctx, record_id); + rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_ID); + if (rc) + return rc; + rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION); + if (rc) + return rc; + for (;;) { + rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS); + if (rc) + return rc; + val = apei_exec_ctx_get_output(&ctx); + if (!val) + break; + if (erst_timedout(&timeout, SPIN_UNIT)) + return -EIO; + } + rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS); + if (rc) + return rc; + val = apei_exec_ctx_get_output(&ctx); + rc = apei_exec_run(&ctx, ACPI_ERST_END); + if (rc) + return rc; + + return erst_errno(val); +} + +/* NVRAM ERST Error Log Address Range is not supported yet */ +static int __erst_write_to_nvram(const struct cper_record_header *record) +{ + /* do not print message, because printk is not safe for NMI */ + return -ENOSYS; +} + +static int __erst_read_to_erange_from_nvram(u64 record_id, u64 *offset) +{ + printk(KERN_WARNING + "NVRAM ERST Log Address Range is not implemented yet\n"); + return -ENOSYS; +} + +static int __erst_clear_from_nvram(u64 record_id) +{ + printk(KERN_WARNING + "NVRAM ERST Log Address Range is not implemented yet\n"); + return -ENOSYS; +} + +int erst_write(const struct cper_record_header *record) +{ + int rc; + unsigned long flags; + struct cper_record_header *rcd_erange; + + if (!record) + return -EINVAL; + + if (!erst_enabled) + return -ENODEV; + + if (memcmp(record->signature, CPER_SIG_RECORD, CPER_SIG_SIZE)) + return -EINVAL; + + if (erst_erange.attr & ERST_RANGE_NVRAM) { + if (!spin_trylock_irqsave(&erst_lock, flags)) + return -EBUSY; + rc = __erst_write_to_nvram(record); + spin_unlock_irqrestore(&erst_lock, flags); + return rc; + } + + if (record->record_length > erst_erange.size) + return -EINVAL; + + if (!spin_trylock_irqsave(&erst_lock, flags)) + return -EBUSY; + memcpy(erst_erange.vaddr, record, record->record_length); + rcd_erange = erst_erange.vaddr; + /* signature for serialization system */ + memcpy(&rcd_erange->persistence_information, "ER", 2); + + rc = __erst_write_to_storage(0); + spin_unlock_irqrestore(&erst_lock, flags); + + return rc; +} + +static int __erst_read_to_erange(u64 record_id, u64 *offset) +{ + int rc; + + if (erst_erange.attr & ERST_RANGE_NVRAM) + return __erst_read_to_erange_from_nvram( + record_id, offset); + + rc = __erst_read_from_storage(record_id, 0); + if (rc) + return rc; + *offset = 0; + + return 0; +} + +static size_t __erst_read(u64 record_id, struct cper_record_header *record, + size_t buflen) +{ + int rc; + u64 offset, len = 0; + struct cper_record_header *rcd_tmp; + + rc = __erst_read_to_erange(record_id, &offset); + if (rc) + return rc; + rcd_tmp = erst_erange.vaddr + offset; + len = rcd_tmp->record_length; + if (len <= buflen) + memcpy(record, rcd_tmp, len); + + return len; +} + +/* + * If return value > buflen, the buffer size is not big enough, + * else if return value < 0, something goes wrong, + * else everything is OK, and return value is record length + */ +size_t erst_read(u64 record_id, struct cper_record_header *record, + size_t buflen) +{ + size_t len; + unsigned long flags; + + if (!erst_enabled) + return -ENODEV; + + spin_lock_irqsave(&erst_lock, flags); + len = __erst_read(record_id, record, buflen); + spin_unlock_irqrestore(&erst_lock, flags); + return len; +} + +/* + * If return value > buflen, the buffer size is not big enough, + * else if return value = 0, there is no more record to read, + * else if return value < 0, something goes wrong, + * else everything is OK, and return value is record length + */ +size_t erst_read_next(struct cper_record_header *record, size_t buflen) +{ + int rc; + size_t len; + unsigned long flags; + u64 record_id; + + if (!erst_enabled) + return -ENODEV; + + spin_lock_irqsave(&erst_lock, flags); + rc = __erst_get_next_record_id(&record_id); + if (rc) { + spin_unlock_irqrestore(&erst_lock, flags); + return rc; + } + /* no more record */ + if (record_id == APEI_ERST_INVALID_RECORD_ID) { + spin_unlock_irqrestore(&erst_lock, flags); + return 0; + } + + len = __erst_read(record_id, record, buflen); + spin_unlock_irqrestore(&erst_lock, flags); + + return len; +} + +int erst_clear(u64 record_id) +{ + int rc; + unsigned long flags; + + if (!erst_enabled) + return -ENODEV; + + spin_lock_irqsave(&erst_lock, flags); + if (erst_erange.attr & ERST_RANGE_NVRAM) + rc = __erst_clear_from_nvram(record_id); + else + rc = __erst_clear_from_storage(record_id); + spin_unlock_irqrestore(&erst_lock, flags); + + return rc; +} + +static int erst_check_table(struct acpi_table_erst *erst_tab) +{ + if (erst_tab->header_length != sizeof(struct acpi_table_erst)) + return -EINVAL; + if (erst_tab->header.length < sizeof(struct acpi_table_erst)) + return -EINVAL; + if (erst_tab->entries != + (erst_tab->header.length - sizeof(struct acpi_table_erst)) / + sizeof(struct acpi_erst_entry)) + return -EINVAL; + + return 0; +} + +int erst_init(void) +{ + int rc = 0; + acpi_status status; + struct apei_exec_context ctx; + + if (acpi_disabled) + return -ENODEV; + + status = acpi_get_table(ACPI_SIG_ERST, 0, + (struct acpi_table_header **)&erst_tab); + if (status == AE_NOT_FOUND) { + printk(KERN_ERR "Table is not found!\n"); + return -ENODEV; + } else if (ACPI_FAILURE(status)) { + const char *msg = acpi_format_exception(status); + printk(KERN_ERR "Failed to get table, %s\n", msg); + return -EINVAL; + } + + rc = erst_check_table(erst_tab); + if (rc) { + printk(KERN_ERR "ERST table is invalid\n"); + return rc; + } + + erst_exec_ctx_init(&ctx); + rc = apei_exec_pre_map_gars(&ctx); + if (rc) + return rc; + + rc = erst_get_erange(&erst_erange); + if (rc) { + if (rc == -ENODEV) + printk(KERN_INFO + "The corresponding hardware device or firmware " + "implementation is not available.\n"); + else + printk(KERN_ERR + "Failed to get Error Log Address Range.\n"); + goto err_unmap_reg; + } + + erst_erange.vaddr = apei_pre_map(erst_erange.base, erst_erange.size); + if (!erst_erange.vaddr) { + rc = -ENOMEM; + goto err_unmap_reg; + } + + printk(KERN_INFO "Xen ERST support is initialized.\n"); + erst_enabled = 1; + + return 0; + +err_unmap_reg: + apei_exec_post_unmap_gars(&ctx); + return rc; +} |