genirq: Expose interrupt information through sysfs

Information about interrupts is exposed via /proc/interrupts, but the
format of that file has changed over kernel versions and differs across
architectures. It also has varying column numbers depending on hardware.

That all makes it hard for tools to parse.

To solve this, expose the information through sysfs so each irq attribute
is in a separate file in a consistent, machine parsable way.

This feature is only available when both CONFIG_SPARSE_IRQ and
CONFIG_SYSFS are enabled.

Examples:
  /sys/kernel/irq/18/actions:	i801_smbus,ehci_hcd:usb1,uhci_hcd:usb7
  /sys/kernel/irq/18/chip_name:	IR-IO-APIC
  /sys/kernel/irq/18/hwirq:		18
  /sys/kernel/irq/18/name:		fasteoi
  /sys/kernel/irq/18/per_cpu_count:	0,0
  /sys/kernel/irq/18/type:		level

  /sys/kernel/irq/25/actions:	ahci0
  /sys/kernel/irq/25/chip_name:	IR-PCI-MSI
  /sys/kernel/irq/25/hwirq:		512000
  /sys/kernel/irq/25/name:		edge
  /sys/kernel/irq/25/per_cpu_count:	29036,0
  /sys/kernel/irq/25/type:		edge

[ tglx: Moved kobject_del() under sparse_irq_lock, massaged code comments
  	and changelog ]

Signed-off-by: Craig Gallek <kraig@google.com>
Cc: David Decotigny <decot@google.com>
Link: http://lkml.kernel.org/r/1473783291-122873-1-git-send-email-kraigatgoog@gmail.com
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
This commit is contained in:
Craig Gallek 2016-09-13 12:14:51 -04:00 committed by Thomas Gleixner
parent 00b992deaa
commit ecb3f394c5
3 changed files with 247 additions and 2 deletions

View File

@ -0,0 +1,53 @@
What: /sys/kernel/irq
Date: September 2016
KernelVersion: 4.9
Contact: Craig Gallek <kraig@google.com>
Description: Directory containing information about the system's IRQs.
Specifically, data from the associated struct irq_desc.
The information here is similar to that in /proc/interrupts
but in a more machine-friendly format. This directory contains
one subdirectory for each Linux IRQ number.
What: /sys/kernel/irq/<irq>/actions
Date: September 2016
KernelVersion: 4.9
Contact: Craig Gallek <kraig@google.com>
Description: The IRQ action chain. A comma-separated list of zero or more
device names associated with this interrupt.
What: /sys/kernel/irq/<irq>/chip_name
Date: September 2016
KernelVersion: 4.9
Contact: Craig Gallek <kraig@google.com>
Description: Human-readable chip name supplied by the associated device
driver.
What: /sys/kernel/irq/<irq>/hwirq
Date: September 2016
KernelVersion: 4.9
Contact: Craig Gallek <kraig@google.com>
Description: When interrupt translation domains are used, this file contains
the underlying hardware IRQ number used for this Linux IRQ.
What: /sys/kernel/irq/<irq>/name
Date: September 2016
KernelVersion: 4.9
Contact: Craig Gallek <kraig@google.com>
Description: Human-readable flow handler name as defined by the irq chip
driver.
What: /sys/kernel/irq/<irq>/per_cpu_count
Date: September 2016
KernelVersion: 4.9
Contact: Craig Gallek <kraig@google.com>
Description: The number of times the interrupt has fired since boot. This
is a comma-separated list of counters; one per CPU in CPU id
order. NOTE: This file consistently shows counters for all
CPU ids. This differs from the behavior of /proc/interrupts
which only shows counters for online CPUs.
What: /sys/kernel/irq/<irq>/type
Date: September 2016
KernelVersion: 4.9
Contact: Craig Gallek <kraig@google.com>
Description: The type of the interrupt. Either the string 'level' or 'edge'.

View File

@ -2,6 +2,7 @@
#define _LINUX_IRQDESC_H
#include <linux/rcupdate.h>
#include <linux/kobject.h>
/*
* Core internal functions to deal with irq descriptors
@ -43,6 +44,7 @@ struct pt_regs;
* @force_resume_depth: number of irqactions on a irq descriptor with
* IRQF_FORCE_RESUME set
* @rcu: rcu head for delayed free
* @kobj: kobject used to represent this struct in sysfs
* @dir: /proc/irq/ procfs entry
* @name: flow handler name for /proc/interrupts output
*/
@ -88,6 +90,7 @@ struct irq_desc {
#endif
#ifdef CONFIG_SPARSE_IRQ
struct rcu_head rcu;
struct kobject kobj;
#endif
int parent_irq;
struct module *owner;

View File

@ -15,6 +15,7 @@
#include <linux/radix-tree.h>
#include <linux/bitmap.h>
#include <linux/irqdomain.h>
#include <linux/sysfs.h>
#include "internals.h"
@ -123,6 +124,181 @@ static DECLARE_BITMAP(allocated_irqs, IRQ_BITMAP_BITS);
#ifdef CONFIG_SPARSE_IRQ
static void irq_kobj_release(struct kobject *kobj);
#ifdef CONFIG_SYSFS
static struct kobject *irq_kobj_base;
#define IRQ_ATTR_RO(_name) \
static struct kobj_attribute _name##_attr = __ATTR_RO(_name)
static ssize_t per_cpu_count_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct irq_desc *desc = container_of(kobj, struct irq_desc, kobj);
int cpu, irq = desc->irq_data.irq;
ssize_t ret = 0;
char *p = "";
for_each_possible_cpu(cpu) {
unsigned int c = kstat_irqs_cpu(irq, cpu);
ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%s%u", p, c);
p = ",";
}
ret += scnprintf(buf + ret, PAGE_SIZE - ret, "\n");
return ret;
}
IRQ_ATTR_RO(per_cpu_count);
static ssize_t chip_name_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct irq_desc *desc = container_of(kobj, struct irq_desc, kobj);
ssize_t ret = 0;
raw_spin_lock_irq(&desc->lock);
if (desc->irq_data.chip && desc->irq_data.chip->name) {
ret = scnprintf(buf, PAGE_SIZE, "%s\n",
desc->irq_data.chip->name);
}
raw_spin_unlock_irq(&desc->lock);
return ret;
}
IRQ_ATTR_RO(chip_name);
static ssize_t hwirq_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct irq_desc *desc = container_of(kobj, struct irq_desc, kobj);
ssize_t ret = 0;
raw_spin_lock_irq(&desc->lock);
if (desc->irq_data.domain)
ret = sprintf(buf, "%d\n", (int)desc->irq_data.hwirq);
raw_spin_unlock_irq(&desc->lock);
return ret;
}
IRQ_ATTR_RO(hwirq);
static ssize_t type_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct irq_desc *desc = container_of(kobj, struct irq_desc, kobj);
ssize_t ret = 0;
raw_spin_lock_irq(&desc->lock);
ret = sprintf(buf, "%s\n",
irqd_is_level_type(&desc->irq_data) ? "level" : "edge");
raw_spin_unlock_irq(&desc->lock);
return ret;
}
IRQ_ATTR_RO(type);
static ssize_t name_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct irq_desc *desc = container_of(kobj, struct irq_desc, kobj);
ssize_t ret = 0;
raw_spin_lock_irq(&desc->lock);
if (desc->name)
ret = scnprintf(buf, PAGE_SIZE, "%s\n", desc->name);
raw_spin_unlock_irq(&desc->lock);
return ret;
}
IRQ_ATTR_RO(name);
static ssize_t actions_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct irq_desc *desc = container_of(kobj, struct irq_desc, kobj);
struct irqaction *action;
ssize_t ret = 0;
char *p = "";
raw_spin_lock_irq(&desc->lock);
for (action = desc->action; action != NULL; action = action->next) {
ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%s%s",
p, action->name);
p = ",";
}
raw_spin_unlock_irq(&desc->lock);
if (ret)
ret += scnprintf(buf + ret, PAGE_SIZE - ret, "\n");
return ret;
}
IRQ_ATTR_RO(actions);
static struct attribute *irq_attrs[] = {
&per_cpu_count_attr.attr,
&chip_name_attr.attr,
&hwirq_attr.attr,
&type_attr.attr,
&name_attr.attr,
&actions_attr.attr,
NULL
};
static struct kobj_type irq_kobj_type = {
.release = irq_kobj_release,
.sysfs_ops = &kobj_sysfs_ops,
.default_attrs = irq_attrs,
};
static void irq_sysfs_add(int irq, struct irq_desc *desc)
{
if (irq_kobj_base) {
/*
* Continue even in case of failure as this is nothing
* crucial.
*/
if (kobject_add(&desc->kobj, irq_kobj_base, "%d", irq))
pr_warn("Failed to add kobject for irq %d\n", irq);
}
}
static int __init irq_sysfs_init(void)
{
struct irq_desc *desc;
int irq;
/* Prevent concurrent irq alloc/free */
irq_lock_sparse();
irq_kobj_base = kobject_create_and_add("irq", kernel_kobj);
if (!irq_kobj_base) {
irq_unlock_sparse();
return -ENOMEM;
}
/* Add the already allocated interrupts */
for_each_irq_desc(irq, desc)
irq_sysfs_add(irq, desc);
irq_unlock_sparse();
return 0;
}
postcore_initcall(irq_sysfs_init);
#else /* !CONFIG_SYSFS */
static struct kobj_type irq_kobj_type = {
.release = irq_kobj_release,
};
static void irq_sysfs_add(int irq, struct irq_desc *desc) {}
#endif /* CONFIG_SYSFS */
static RADIX_TREE(irq_desc_tree, GFP_KERNEL);
static void irq_insert_desc(unsigned int irq, struct irq_desc *desc)
@ -187,6 +363,7 @@ static struct irq_desc *alloc_desc(int irq, int node, unsigned int flags,
desc_set_defaults(irq, desc, node, affinity, owner);
irqd_set(&desc->irq_data, flags);
kobject_init(&desc->kobj, &irq_kobj_type);
return desc;
@ -197,15 +374,22 @@ static struct irq_desc *alloc_desc(int irq, int node, unsigned int flags,
return NULL;
}
static void delayed_free_desc(struct rcu_head *rhp)
static void irq_kobj_release(struct kobject *kobj)
{
struct irq_desc *desc = container_of(rhp, struct irq_desc, rcu);
struct irq_desc *desc = container_of(kobj, struct irq_desc, kobj);
free_masks(desc);
free_percpu(desc->kstat_irqs);
kfree(desc);
}
static void delayed_free_desc(struct rcu_head *rhp)
{
struct irq_desc *desc = container_of(rhp, struct irq_desc, rcu);
kobject_put(&desc->kobj);
}
static void free_desc(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq);
@ -217,8 +401,12 @@ static void free_desc(unsigned int irq)
* kstat_irq_usr(). Once we deleted the descriptor from the
* sparse tree we can free it. Access in proc will fail to
* lookup the descriptor.
*
* The sysfs entry must be serialized against a concurrent
* irq_sysfs_init() as well.
*/
mutex_lock(&sparse_irq_lock);
kobject_del(&desc->kobj);
delete_irq_desc(irq);
mutex_unlock(&sparse_irq_lock);
@ -261,6 +449,7 @@ static int alloc_descs(unsigned int start, unsigned int cnt, int node,
goto err;
mutex_lock(&sparse_irq_lock);
irq_insert_desc(start + i, desc);
irq_sysfs_add(start + i, desc);
mutex_unlock(&sparse_irq_lock);
}
return start;