Re: [PATCH 1/2] Generic hardware error reporting mechanism

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [Linux IBM ACPI]     [Linux Power Management]     [Linux Kernel]     [Linux Laptop]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Device Mapper]     [Linux Resources]

  Powered by Linux