Sorry, forget to Cc: Greg for device model part. Best Regards, Huang Ying On Fri, 2010-11-19 at 16:10 +0800, Huang, Ying wrote: > There are many hardware error detecting and reporting components in > kernel, including x86 Machine Check, PCIe AER, EDAC, APEI GHES > etc. Each one has its error reporting implementation, including user > space interface, error record format, in kernel buffer, etc. This > patch provides a generic hardware error reporting mechanism to reduce > the duplicated effort and add more common services. > > > A highly extensible generic hardware error record data structure is > defined to accommodate various hardware error information from various > hardware error sources. The overall structure of error record is as > follow: > > ----------------------------------------------------------------- > | rcd hdr | sec 1 hdr | sec 1 data | sec 2 hdr | sec2 data | ... > ----------------------------------------------------------------- > > Several error sections can be incorporated into one error record to > accumulate information from multiple hardware components related to > one error. For example, for an error on a device on the secondary > side of a PCIe bridge, it is useful to record error information from > the PCIe bridge and the PCIe device. Multiple section can be used to > hold both the cooked and the raw error information. So that the > abstract information can be provided by the cooked one and no > information will be lost because the raw one is provided too. > > There are "reversion" (rev) and "length" field in record header and > "type" and "length" field in section header, so the user space error > daemon can skip unrecognized error record or error section. This > makes old version error daemon can work with the newer kernel. > > New error section type can be added to support new error type, error > sources. > > > The hardware error reporting mechanism designed by the patch > integrates well with device model in kernel. struct dev_herr_info is > defined and pointed to by "error" field of struct device. This is > used to hold error reporting related information for each device. One > sysfs directory "error" will be created for each hardware error > reporting device. Some files for error reporting statistics and > control are created in sysfs "error" directory. For example, the > "error" directory for APEI GHES is as follow. > > /sys/devices/platform/GHES.0/error/logs > /sys/devices/platform/GHES.0/error/overflows > /sys/devices/platform/GHES.0/error/throttles > > Where "logs" is number of error records logged; "throttles" is number > of error records not logged because the reporting rate is too high; > "overflows" is number of error records not logged because there is no > space available. > > Not all devices will report errors, so struct dev_herr_info and sysfs > directory/files are only allocated/created for devices explicitly > enable it. So to enumerate the error sources of system, you just need > to enumerate "error" directory for each device directory in > /sys/devices. > > > One device file (/dev/error/error) which mixed error records from all > hardware error reporting devices is created to convey error records > from kernel space to user space. Because hardware devices are dealt > with, a device file is the most natural way to do that. Because > hardware error reporting should not hurts system performance, the > throughput of the interface should be controlled to a low level (done > by user space error daemon), ordinary "read" is sufficient from > performance point of view. > > > The patch provides common services for hardware error reporting > devices too. > > A lock-less hardware error record allocator is provided. So for > hardware error that can be ignored (such as corrected errors), it is > not needed to pre-allocate the error record or allocate the error > record on stack. Because the possibility for two hardware parts to go > error simultaneously is very small, one big unified memory pool for > hardware errors is better than one memory pool or buffer for each > device. > > After filling in all necessary fields in hardware error record, the > error reporting is quite straightforward, just calling > herr_record_report, parameters are the error record itself and the > corresponding struct device. > > Hardware errors may burst, for example, same hardware errors may be > reported at high rate within a short interval, this will use up all > pre-allocated memory for error reporting, so that other hardware > errors come from same or different hardware device can not be logged. > To deal with this issue, a throttle algorithm is implemented. The > logging rate for errors come from one hardware error device is > throttled based on the available pre-allocated memory for error > reporting. In this way we can log as many kinds of errors as possible > comes from as many devices as possible. > > > This patch is designed by Andi Kleen and Huang Ying. > > Signed-off-by: Huang Ying <ying.huang@xxxxxxxxx> > Reviewed-by: Andi Kleen <ak@xxxxxxxxxxxxxxx> > --- > drivers/Kconfig | 2 > drivers/Makefile | 1 > drivers/base/Makefile | 1 > drivers/base/herror.c | 98 ++++++++ > drivers/herror/Kconfig | 5 > drivers/herror/Makefile | 1 > drivers/herror/herr-core.c | 488 ++++++++++++++++++++++++++++++++++++++++++ > include/linux/Kbuild | 1 > include/linux/device.h | 14 + > include/linux/herror.h | 35 +++ > include/linux/herror_record.h | 100 ++++++++ > kernel/Makefile | 1 > 12 files changed, 747 insertions(+) > create mode 100644 drivers/base/herror.c > create mode 100644 drivers/herror/Kconfig > create mode 100644 drivers/herror/Makefile > create mode 100644 drivers/herror/herr-core.c > create mode 100644 include/linux/herror.h > create mode 100644 include/linux/herror_record.h > > --- a/drivers/Kconfig > +++ b/drivers/Kconfig > @@ -111,4 +111,6 @@ source "drivers/xen/Kconfig" > source "drivers/staging/Kconfig" > > source "drivers/platform/Kconfig" > + > +source "drivers/herror/Kconfig" > endmenu > --- a/drivers/Makefile > +++ b/drivers/Makefile > @@ -115,3 +115,4 @@ obj-$(CONFIG_VLYNQ) += vlynq/ > obj-$(CONFIG_STAGING) += staging/ > obj-y += platform/ > obj-y += ieee802154/ > +obj-$(CONFIG_HERR_CORE) += herror/ > --- a/drivers/base/Makefile > +++ b/drivers/base/Makefile > @@ -18,6 +18,7 @@ ifeq ($(CONFIG_SYSFS),y) > obj-$(CONFIG_MODULES) += module.o > endif > obj-$(CONFIG_SYS_HYPERVISOR) += hypervisor.o > +obj-$(CONFIG_HERR_CORE) += herror.o > > ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG > > --- /dev/null > +++ b/drivers/base/herror.c > @@ -0,0 +1,98 @@ > +/* > + * Hardware error reporting related functions > + * > + * Copyright 2010 Intel Corp. > + * Author: Huang Ying <ying.huang@xxxxxxxxx> > + * > + * 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 <linux/kernel.h> > +#include <linux/device.h> > +#include <linux/slab.h> > + > +#define HERR_COUNTER_ATTR(_name) \ > + static ssize_t herr_##_name##_show(struct device *dev, \ > + struct device_attribute *attr, \ > + char *buf) \ > + { \ > + int counter; \ > + \ > + counter = atomic_read(&dev->error->_name); \ > + return sprintf(buf, "%d\n", counter); \ > + } \ > + static ssize_t herr_##_name##_store(struct device *dev, \ > + struct device_attribute *attr, \ > + const char *buf, \ > + size_t count) \ > + { \ > + atomic_set(&dev->error->_name, 0); \ > + return count; \ > + } \ > + static struct device_attribute herr_attr_##_name = \ > + __ATTR(_name, 0600, herr_##_name##_show, \ > + herr_##_name##_store) > + > +HERR_COUNTER_ATTR(logs); > +HERR_COUNTER_ATTR(overflows); > +HERR_COUNTER_ATTR(throttles); > + > +static struct attribute *herr_attrs[] = { > + &herr_attr_logs.attr, > + &herr_attr_overflows.attr, > + &herr_attr_throttles.attr, > + NULL, > +}; > + > +static struct attribute_group herr_attr_group = { > + .name = "error", > + .attrs = herr_attrs, > +}; > + > +static void device_herr_init(struct device *dev) > +{ > + atomic_set(&dev->error->logs, 0); > + atomic_set(&dev->error->overflows, 0); > + atomic_set(&dev->error->throttles, 0); > + atomic64_set(&dev->error->timestamp, 0); > +} > + > +int device_enable_error_reporting(struct device *dev) > +{ > + int rc; > + > + BUG_ON(dev->error); > + dev->error = kzalloc(sizeof(*dev->error), GFP_KERNEL); > + if (!dev->error) > + return -ENOMEM; > + device_herr_init(dev); > + rc = sysfs_create_group(&dev->kobj, &herr_attr_group); > + if (rc) > + goto err; > + return 0; > +err: > + kfree(dev->error); > + dev->error = NULL; > + return rc; > +} > +EXPORT_SYMBOL_GPL(device_enable_error_reporting); > + > +void device_disable_error_reporting(struct device *dev) > +{ > + if (dev->error) { > + sysfs_remove_group(&dev->kobj, &herr_attr_group); > + kfree(dev->error); > + } > +} > +EXPORT_SYMBOL_GPL(device_disable_error_reporting); > --- /dev/null > +++ b/drivers/herror/Kconfig > @@ -0,0 +1,5 @@ > +config HERR_CORE > + bool "Hardware error reporting" > + depends on ARCH_HAVE_NMI_SAFE_CMPXCHG > + select LLIST > + select GENERIC_ALLOCATOR > --- /dev/null > +++ b/drivers/herror/Makefile > @@ -0,0 +1 @@ > +obj-y += herr-core.o > --- /dev/null > +++ b/drivers/herror/herr-core.c > @@ -0,0 +1,488 @@ > +/* > + * Generic hardware error reporting support > + * > + * This file provides some common services for hardware error > + * reporting, including hardware error record lock-less allocator, > + * error reporting mechanism, user space interface etc. > + * > + * Copyright 2010 Intel Corp. > + * Author: Huang Ying <ying.huang@xxxxxxxxx> > + * > + * 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 <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/rculist.h> > +#include <linux/mutex.h> > +#include <linux/percpu.h> > +#include <linux/sched.h> > +#include <linux/slab.h> > +#include <linux/trace_clock.h> > +#include <linux/uaccess.h> > +#include <linux/poll.h> > +#include <linux/ratelimit.h> > +#include <linux/nmi.h> > +#include <linux/llist.h> > +#include <linux/genalloc.h> > +#include <linux/herror.h> > + > +#define HERR_NOTIFY_BIT 0 > + > +static unsigned long herr_flags; > + > +/* > + * Record list management and error reporting > + */ > + > +struct herr_node { > + struct llist_node llist; > + struct herr_record ercd __attribute__((aligned(HERR_MIN_ALIGN))); > +}; > + > +#define HERR_NODE_LEN(rcd_len) \ > + ((rcd_len) + sizeof(struct herr_node) - sizeof(struct herr_record)) > + > +#define HERR_MIN_ALLOC_ORDER HERR_MIN_ALIGN_ORDER > +#define HERR_CHUNKS_PER_CPU 2 > +#define HERR_RCD_LIST_NUM 2 > + > +struct herr_rcd_lists { > + struct llist_head *write; > + struct llist_head *read; > + struct llist_head heads[HERR_RCD_LIST_NUM]; > +}; > + > +static DEFINE_PER_CPU(struct herr_rcd_lists, herr_rcd_lists); > + > +static DEFINE_PER_CPU(struct gen_pool *, herr_gen_pool); > + > +static void herr_rcd_lists_init(void) > +{ > + int cpu, i; > + struct herr_rcd_lists *lists; > + > + for_each_possible_cpu(cpu) { > + lists = per_cpu_ptr(&herr_rcd_lists, cpu); > + for (i = 0; i < HERR_RCD_LIST_NUM; i++) > + init_llist_head(&lists->heads[i]); > + lists->write = &lists->heads[0]; > + lists->read = &lists->heads[1]; > + } > +} > + > +static void herr_pool_fini(void) > +{ > + struct gen_pool *pool; > + struct gen_pool_chunk *chunk; > + int cpu; > + > + for_each_possible_cpu(cpu) { > + pool = per_cpu(herr_gen_pool, cpu); > + gen_pool_for_each_chunk(chunk, pool) > + free_page(chunk->start_addr); > + gen_pool_destroy(pool); > + } > +} > + > +static int herr_pool_init(void) > +{ > + struct gen_pool **pool; > + int cpu, rc, nid, i; > + unsigned long addr; > + > + for_each_possible_cpu(cpu) { > + pool = per_cpu_ptr(&herr_gen_pool, cpu); > + rc = -ENOMEM; > + nid = cpu_to_node(cpu); > + *pool = gen_pool_create(HERR_MIN_ALLOC_ORDER, nid); > + if (!*pool) > + goto err_pool_fini; > + for (i = 0; i < HERR_CHUNKS_PER_CPU; i++) { > + rc = -ENOMEM; > + addr = __get_free_page(GFP_KERNEL); > + if (!addr) > + goto err_pool_fini; > + rc = gen_pool_add(*pool, addr, PAGE_SIZE, nid); > + if (rc) > + goto err_pool_fini; > + } > + } > + > + return 0; > +err_pool_fini: > + herr_pool_fini(); > + return rc; > +} > + > +/* Max interval: about 2 second */ > +#define HERR_THROTTLE_BASE_INTVL NSEC_PER_USEC > +#define HERR_THROTTLE_MAX_RATIO 21 > +#define HERR_THROTTLE_MAX_INTVL \ > + ((1ULL << HERR_THROTTLE_MAX_RATIO) * HERR_THROTTLE_BASE_INTVL) > +/* > + * Pool size/used ratio considered spare, before this, interval > + * between error reporting is ignored. After this, minimal interval > + * needed is increased exponentially to max interval. > + */ > +#define HERR_THROTTLE_SPARE_RATIO 3 > + > +static int herr_throttle(struct device *dev) > +{ > + struct gen_pool *pool; > + unsigned long long last, now, min_intvl; > + unsigned int size, used, ratio; > + > + pool = __get_cpu_var(herr_gen_pool); > + size = gen_pool_size(pool); > + used = size - gen_pool_avail(pool); > + if (HERR_THROTTLE_SPARE_RATIO * used < size) > + goto pass; > + now = trace_clock_local(); > + last = atomic64_read(&dev->error->timestamp); > + ratio = (used * HERR_THROTTLE_SPARE_RATIO - size) * \ > + HERR_THROTTLE_MAX_RATIO; > + ratio = ratio / (size * HERR_THROTTLE_SPARE_RATIO - size) + 1; > + min_intvl = (1ULL << ratio) * HERR_THROTTLE_BASE_INTVL; > + if ((long long)(now - last) > min_intvl) > + goto pass; > + atomic_inc(&dev->error->throttles); > + return 0; > +pass: > + return 1; > +} > + > +static u64 herr_record_next_id(void) > +{ > + static atomic64_t seq = ATOMIC64_INIT(0); > + > + if (!atomic64_read(&seq)) > + atomic64_set(&seq, (u64)get_seconds() << 32); > + > + return atomic64_inc_return(&seq); > +} > + > +void herr_record_init(struct herr_record *ercd) > +{ > + ercd->flags = 0; > + ercd->rev = HERR_RCD_REV1_0; > + ercd->id = herr_record_next_id(); > + ercd->timestamp = trace_clock_local(); > +} > +EXPORT_SYMBOL_GPL(herr_record_init); > + > +struct herr_record *herr_record_alloc(unsigned int len, struct device *dev, > + unsigned int flags) > +{ > + struct gen_pool *pool; > + struct herr_node *enode; > + struct herr_record *ercd = NULL; > + > + BUG_ON(!dev->error); > + preempt_disable(); > + if (!(flags & HERR_ALLOC_NO_THROTTLE)) { > + if (!herr_throttle(dev)) { > + preempt_enable_no_resched(); > + return NULL; > + } > + } > + > + pool = __get_cpu_var(herr_gen_pool); > + enode = (struct herr_node *)gen_pool_alloc(pool, HERR_NODE_LEN(len)); > + if (enode) { > + ercd = &enode->ercd; > + herr_record_init(ercd); > + ercd->length = len; > + > + atomic64_set(&dev->error->timestamp, trace_clock_local()); > + atomic_inc(&dev->error->logs); > + } else > + atomic_inc(&dev->error->overflows); > + preempt_enable_no_resched(); > + > + return ercd; > +} > +EXPORT_SYMBOL_GPL(herr_record_alloc); > + > +int herr_record_report(struct herr_record *ercd, struct device *dev) > +{ > + struct herr_rcd_lists *lists; > + struct herr_node *enode; > + > + preempt_disable(); > + lists = this_cpu_ptr(&herr_rcd_lists); > + enode = container_of(ercd, struct herr_node, ercd); > + llist_add(&enode->llist, lists->write); > + preempt_enable_no_resched(); > + > + set_bit(HERR_NOTIFY_BIT, &herr_flags); > + > + return 0; > +} > +EXPORT_SYMBOL_GPL(herr_record_report); > + > +void herr_record_free(struct herr_record *ercd) > +{ > + struct herr_node *enode; > + struct gen_pool *pool; > + > + enode = container_of(ercd, struct herr_node, ercd); > + pool = get_cpu_var(herr_gen_pool); > + gen_pool_free(pool, (unsigned long)enode, > + HERR_NODE_LEN(enode->ercd.length)); > + put_cpu_var(pool); > +} > +EXPORT_SYMBOL_GPL(herr_record_free); > + > +/* > + * The low 16 bit is freeze count, high 16 bit is thaw count. If they > + * are not equal, someone is freezing the reader > + */ > +static u32 herr_freeze_thaw; > + > +/* > + * Stop the reader to consume error records, so that the error records > + * can be checked in kernel space safely. > + */ > +static void herr_freeze_reader(void) > +{ > + u32 old, new; > + > + do { > + new = old = herr_freeze_thaw; > + new = ((new + 1) & 0xffff) | (old & 0xffff0000); > + } while (cmpxchg(&herr_freeze_thaw, old, new) != old); > +} > + > +static void herr_thaw_reader(void) > +{ > + u32 old, new; > + > + do { > + old = herr_freeze_thaw; > + new = old + 0x10000; > + } while (cmpxchg(&herr_freeze_thaw, old, new) != old); > +} > + > +static int herr_reader_is_frozen(void) > +{ > + u32 freeze_thaw = herr_freeze_thaw; > + return (freeze_thaw & 0xffff) != (freeze_thaw >> 16); > +} > + > +int herr_for_each_record(herr_traverse_func_t func, void *data) > +{ > + int i, cpu, rc = 0; > + struct herr_rcd_lists *lists; > + struct herr_node *enode; > + > + preempt_disable(); > + herr_freeze_reader(); > + for_each_possible_cpu(cpu) { > + lists = per_cpu_ptr(&herr_rcd_lists, cpu); > + for (i = 0; i < HERR_RCD_LIST_NUM; i++) { > + struct llist_head *head = &lists->heads[i]; > + llist_for_each_entry(enode, head->first, llist) { > + rc = func(&enode->ercd, data); > + if (rc) > + goto out; > + } > + } > + } > +out: > + herr_thaw_reader(); > + preempt_enable_no_resched(); > + return rc; > +} > +EXPORT_SYMBOL_GPL(herr_for_each_record); > + > +static ssize_t herr_rcd_lists_read(char __user *ubuf, size_t usize, > + struct mutex *read_mutex) > +{ > + int cpu, rc = 0, read; > + struct herr_rcd_lists *lists; > + struct gen_pool *pool; > + ssize_t len, rsize = 0; > + struct herr_node *enode; > + struct llist_head *old_read; > + struct llist_node *to_read; > + > + do { > + read = 0; > + for_each_possible_cpu(cpu) { > + lists = per_cpu_ptr(&herr_rcd_lists, cpu); > + pool = per_cpu(herr_gen_pool, cpu); > + if (llist_empty(lists->read)) { > + if (llist_empty(lists->write)) > + continue; > + /* > + * Error records are output in batch, so old > + * error records can be output before new ones. > + */ > + old_read = lists->read; > + lists->read = lists->write; > + lists->write = old_read; > + } > + rc = rsize ? 0 : -EBUSY; > + if (herr_reader_is_frozen()) > + goto out; > + to_read = llist_del_first(lists->read); > + if (herr_reader_is_frozen()) > + goto out_readd; > + enode = llist_entry(to_read, struct herr_node, llist); > + len = enode->ercd.length; > + rc = rsize ? 0 : -EINVAL; > + if (len > usize - rsize) > + goto out_readd; > + rc = -EFAULT; > + if (copy_to_user(ubuf + rsize, &enode->ercd, len)) > + goto out_readd; > + gen_pool_free(pool, (unsigned long)enode, > + HERR_NODE_LEN(len)); > + rsize += len; > + read = 1; > + } > + if (need_resched()) { > + mutex_unlock(read_mutex); > + cond_resched(); > + mutex_lock(read_mutex); > + } > + } while (read); > + rc = 0; > +out: > + return rc ? rc : rsize; > +out_readd: > + llist_add(to_read, lists->read); > + goto out; > +} > + > +static int herr_rcd_lists_is_empty(void) > +{ > + int cpu, i; > + struct herr_rcd_lists *lists; > + > + for_each_possible_cpu(cpu) { > + lists = per_cpu_ptr(&herr_rcd_lists, cpu); > + for (i = 0; i < HERR_RCD_LIST_NUM; i++) { > + if (!llist_empty(&lists->heads[i])) > + return 0; > + } > + } > + return 1; > +} > + > + > +/* > + * Hardware Error Mix Reporting Device > + */ > + > +static int herr_major; > +static DECLARE_WAIT_QUEUE_HEAD(herr_mix_wait); > + > +static char *herr_devnode(struct device *dev, mode_t *mode) > +{ > + return kasprintf(GFP_KERNEL, "error/%s", dev_name(dev)); > +} > + > +struct class herr_class = { > + .name = "error", > + .devnode = herr_devnode, > +}; > +EXPORT_SYMBOL_GPL(herr_class); > + > +void herr_notify(void) > +{ > + if (test_and_clear_bit(HERR_NOTIFY_BIT, &herr_flags)) > + wake_up_interruptible(&herr_mix_wait); > +} > +EXPORT_SYMBOL_GPL(herr_notify); > + > +static ssize_t herr_mix_read(struct file *filp, char __user *ubuf, > + size_t usize, loff_t *off) > +{ > + int rc; > + static DEFINE_MUTEX(read_mutex); > + > + if (*off != 0) > + return -EINVAL; > + > + rc = mutex_lock_interruptible(&read_mutex); > + if (rc) > + return rc; > + rc = herr_rcd_lists_read(ubuf, usize, &read_mutex); > + mutex_unlock(&read_mutex); > + > + return rc; > +} > + > +static unsigned int herr_mix_poll(struct file *file, poll_table *wait) > +{ > + poll_wait(file, &herr_mix_wait, wait); > + if (!herr_rcd_lists_is_empty()) > + return POLLIN | POLLRDNORM; > + return 0; > +} > + > +static const struct file_operations herr_mix_dev_fops = { > + .owner = THIS_MODULE, > + .read = herr_mix_read, > + .poll = herr_mix_poll, > +}; > + > +static int __init herr_mix_dev_init(void) > +{ > + struct device *dev; > + dev_t devt; > + > + devt = MKDEV(herr_major, 0); > + dev = device_create(&herr_class, NULL, devt, NULL, "error"); > + if (IS_ERR(dev)) > + return PTR_ERR(dev); > + > + return 0; > +} > +device_initcall(herr_mix_dev_init); > + > +static int __init herr_core_init(void) > +{ > + int rc; > + > + BUILD_BUG_ON(sizeof(struct herr_node) % HERR_MIN_ALIGN); > + BUILD_BUG_ON(sizeof(struct herr_record) % HERR_MIN_ALIGN); > + BUILD_BUG_ON(sizeof(struct herr_section) % HERR_MIN_ALIGN); > + > + herr_rcd_lists_init(); > + > + rc = herr_pool_init(); > + if (rc) > + goto err; > + > + rc = class_register(&herr_class); > + if (rc) > + goto err_free_pool; > + > + rc = herr_major = register_chrdev(0, "error", &herr_mix_dev_fops); > + if (rc < 0) > + goto err_free_class; > + > + return 0; > +err_free_class: > + class_unregister(&herr_class); > +err_free_pool: > + herr_pool_fini(); > +err: > + return rc; > +} > +/* Initialize data structure used by device driver, so subsys_initcall */ > +subsys_initcall(herr_core_init); > --- a/include/linux/Kbuild > +++ b/include/linux/Kbuild > @@ -141,6 +141,7 @@ header-y += gigaset_dev.h > header-y += hdlc.h > header-y += hdlcdrv.h > header-y += hdreg.h > +header-y += herror_record.h > header-y += hid.h > header-y += hiddev.h > header-y += hidraw.h > --- a/include/linux/device.h > +++ b/include/linux/device.h > @@ -394,6 +394,14 @@ extern int devres_release_group(struct d > extern void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp); > extern void devm_kfree(struct device *dev, void *p); > > +/* Device hardware error reporting related information */ > +struct dev_herr_info { > + atomic_t logs; > + atomic_t overflows; > + atomic_t throttles; > + atomic64_t timestamp; > +}; > + > struct device_dma_parameters { > /* > * a low level driver may set these to teach IOMMU code about > @@ -422,6 +430,9 @@ struct device { > void *platform_data; /* Platform specific data, device > core doesn't touch it */ > struct dev_pm_info power; > +#ifdef CONFIG_HERR_CORE > + struct dev_herr_info *error; /* Hardware error reporting info */ > +#endif > > #ifdef CONFIG_NUMA > int numa_node; /* NUMA node this device is close to */ > @@ -523,6 +534,9 @@ static inline bool device_async_suspend_ > return !!dev->power.async_suspend; > } > > +extern int device_enable_error_reporting(struct device *dev); > +extern void device_disable_error_reporting(struct device *dev); > + > static inline void device_lock(struct device *dev) > { > mutex_lock(&dev->mutex); > --- /dev/null > +++ b/include/linux/herror.h > @@ -0,0 +1,35 @@ > +#ifndef LINUX_HERROR_H > +#define LINUX_HERROR_H > + > +#include <linux/types.h> > +#include <linux/list.h> > +#include <linux/device.h> > +#include <linux/herror_record.h> > + > +/* > + * Hardware error reporting > + */ > + > +#define HERR_ALLOC_NO_THROTTLE 0x0001 > + > +struct herr_dev; > + > +/* allocate a herr_record lock-lessly */ > +struct herr_record *herr_record_alloc(unsigned int len, > + struct device *dev, > + unsigned int flags); > +void herr_record_init(struct herr_record *ercd); > +/* report error */ > +int herr_record_report(struct herr_record *ercd, struct device *dev); > +/* free the herr_record allocated before */ > +void herr_record_free(struct herr_record *ercd); > +/* > + * Notify waited user space hardware error daemon for the new error > + * record, can not be used in NMI context > + */ > +void herr_notify(void); > + > +/* Traverse all error records not consumed by user space */ > +typedef int (*herr_traverse_func_t)(struct herr_record *ercd, void *data); > +int herr_for_each_record(herr_traverse_func_t func, void *data); > +#endif > --- /dev/null > +++ b/include/linux/herror_record.h > @@ -0,0 +1,100 @@ > +#ifndef LINUX_HERROR_RECORD_H > +#define LINUX_HERROR_RECORD_H > + > +#include <linux/types.h> > + > +/* > + * Hardware Error Record Definition > + */ > +enum herr_severity { > + HERR_SEV_NONE, > + HERR_SEV_CORRECTED, > + HERR_SEV_RECOVERABLE, > + HERR_SEV_FATAL, > +}; > + > +#define HERR_RCD_REV1_0 0x0100 > +#define HERR_MIN_ALIGN_ORDER 3 > +#define HERR_MIN_ALIGN (1 << HERR_MIN_ALIGN_ORDER) > + > +enum herr_record_flags { > + HERR_RCD_PREV = 0x0001, /* record is for previous boot */ > + HERR_RCD_PERSIST = 0x0002, /* record is from flash, need to be > + * cleared after writing to disk */ > +}; > + > +/* > + * sizeof(struct herr_record) and sizeof(struct herr_section) should > + * be multiple of HERR_MIN_ALIGN to make error record packing easier. > + */ > +struct herr_record { > + __u16 length; > + __u16 flags; > + __u16 rev; > + __u8 severity; > + __u8 pad1; > + __u64 id; > + __u64 timestamp; > + __u8 data[0]; > +}; > + > +/* Section type ID are allocated here */ > +enum herr_section_type_id { > + /* 0x0 - 0xff are reserved by core */ > + /* 0x100 - 0x1ff are allocated to CPER */ > + HERR_TYPE_CPER = 0x0100, > + HERR_TYPE_GESR = 0x0110, /* acpi_hest_generic_status */ > + /* 0x200 - 0x2ff are allocated to PCI/PCIe subsystem */ > + HERR_TYPE_PCIE_AER = 0x0200, > +}; > + > +struct herr_section { > + __u16 length; > + __u16 flags; > + __u32 type; > + __u8 data[0]; > +}; > + > +#define herr_record_for_each_section(ercd, esec) \ > + for ((esec) = (struct herr_section *)(ercd)->data; \ > + (void *)(esec) - (void *)(ercd) < (ercd)->length; \ > + (esec) = (void *)(esec) + (esec)->length) > + > +#define HERR_SEC_LEN_ROUND(len) \ > + (((len) + HERR_MIN_ALIGN - 1) & ~(HERR_MIN_ALIGN - 1)) > +#define HERR_SEC_LEN(type) \ > + (sizeof(struct herr_section) + HERR_SEC_LEN_ROUND(sizeof(type))) > + > +#define HERR_RECORD_LEN_ROUND1(sec_len1) \ > + (sizeof(struct herr_record) + HERR_SEC_LEN_ROUND(sec_len1)) > +#define HERR_RECORD_LEN_ROUND2(sec_len1, sec_len2) \ > + (sizeof(struct herr_record) + HERR_SEC_LEN_ROUND(sec_len1) + \ > + HERR_SEC_LEN_ROUND(sec_len2)) > +#define HERR_RECORD_LEN_ROUND3(sec_len1, sec_len2, sec_len3) \ > + (sizeof(struct herr_record) + HERR_SEC_LEN_ROUND(sec_len1) + \ > + HERR_SEC_LEN_ROUND(sec_len2) + HERR_SEC_LEN_ROUND(sec_len3)) > + > +#define HERR_RECORD_LEN1(sec_type1) \ > + (sizeof(struct herr_record) + HERR_SEC_LEN(sec_type1)) > +#define HERR_RECORD_LEN2(sec_type1, sec_type2) \ > + (sizeof(struct herr_record) + HERR_SEC_LEN(sec_type1) + \ > + HERR_SEC_LEN(sec_type2)) > +#define HERR_RECORD_LEN3(sec_type1, sec_type2, sec_type3) \ > + (sizeof(struct herr_record) + HERR_SEC_LEN(sec_type1) + \ > + HERR_SEC_LEN(sec_type2) + HERR_SEC_LEN(sec_type3)) > + > +static inline struct herr_section *herr_first_sec(struct herr_record *ercd) > +{ > + return (struct herr_section *)(ercd + 1); > +} > + > +static inline struct herr_section *herr_next_sec(struct herr_section *esrc) > +{ > + return (void *)esrc + esrc->length; > +} > + > +static inline void *herr_sec_data(struct herr_section *esec) > +{ > + return (void *)(esec + 1); > +} > +#endif > --- a/kernel/Makefile > +++ b/kernel/Makefile > @@ -100,6 +100,7 @@ obj-$(CONFIG_FUNCTION_TRACER) += trace/ > obj-$(CONFIG_TRACING) += trace/ > obj-$(CONFIG_X86_DS) += trace/ > obj-$(CONFIG_RING_BUFFER) += trace/ > +obj-$(CONFIG_HERR_CORE) += trace/ > obj-$(CONFIG_SMP) += sched_cpupri.o > obj-$(CONFIG_IRQ_WORK) += irq_work.o > obj-$(CONFIG_PERF_EVENTS) += perf_event.o -- To unsubscribe from this list: send the line "unsubscribe linux-acpi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html