Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Craig Gallek and committed by
Thomas Gleixner
ecb3f394 00b992de

+247 -2
+53
Documentation/ABI/testing/sysfs-kernel-irq
··· 1 + What: /sys/kernel/irq 2 + Date: September 2016 3 + KernelVersion: 4.9 4 + Contact: Craig Gallek <kraig@google.com> 5 + Description: Directory containing information about the system's IRQs. 6 + Specifically, data from the associated struct irq_desc. 7 + The information here is similar to that in /proc/interrupts 8 + but in a more machine-friendly format. This directory contains 9 + one subdirectory for each Linux IRQ number. 10 + 11 + What: /sys/kernel/irq/<irq>/actions 12 + Date: September 2016 13 + KernelVersion: 4.9 14 + Contact: Craig Gallek <kraig@google.com> 15 + Description: The IRQ action chain. A comma-separated list of zero or more 16 + device names associated with this interrupt. 17 + 18 + What: /sys/kernel/irq/<irq>/chip_name 19 + Date: September 2016 20 + KernelVersion: 4.9 21 + Contact: Craig Gallek <kraig@google.com> 22 + Description: Human-readable chip name supplied by the associated device 23 + driver. 24 + 25 + What: /sys/kernel/irq/<irq>/hwirq 26 + Date: September 2016 27 + KernelVersion: 4.9 28 + Contact: Craig Gallek <kraig@google.com> 29 + Description: When interrupt translation domains are used, this file contains 30 + the underlying hardware IRQ number used for this Linux IRQ. 31 + 32 + What: /sys/kernel/irq/<irq>/name 33 + Date: September 2016 34 + KernelVersion: 4.9 35 + Contact: Craig Gallek <kraig@google.com> 36 + Description: Human-readable flow handler name as defined by the irq chip 37 + driver. 38 + 39 + What: /sys/kernel/irq/<irq>/per_cpu_count 40 + Date: September 2016 41 + KernelVersion: 4.9 42 + Contact: Craig Gallek <kraig@google.com> 43 + Description: The number of times the interrupt has fired since boot. This 44 + is a comma-separated list of counters; one per CPU in CPU id 45 + order. NOTE: This file consistently shows counters for all 46 + CPU ids. This differs from the behavior of /proc/interrupts 47 + which only shows counters for online CPUs. 48 + 49 + What: /sys/kernel/irq/<irq>/type 50 + Date: September 2016 51 + KernelVersion: 4.9 52 + Contact: Craig Gallek <kraig@google.com> 53 + Description: The type of the interrupt. Either the string 'level' or 'edge'.
+3
include/linux/irqdesc.h
··· 2 2 #define _LINUX_IRQDESC_H 3 3 4 4 #include <linux/rcupdate.h> 5 + #include <linux/kobject.h> 5 6 6 7 /* 7 8 * Core internal functions to deal with irq descriptors ··· 44 43 * @force_resume_depth: number of irqactions on a irq descriptor with 45 44 * IRQF_FORCE_RESUME set 46 45 * @rcu: rcu head for delayed free 46 + * @kobj: kobject used to represent this struct in sysfs 47 47 * @dir: /proc/irq/ procfs entry 48 48 * @name: flow handler name for /proc/interrupts output 49 49 */ ··· 90 88 #endif 91 89 #ifdef CONFIG_SPARSE_IRQ 92 90 struct rcu_head rcu; 91 + struct kobject kobj; 93 92 #endif 94 93 int parent_irq; 95 94 struct module *owner;
+191 -2
kernel/irq/irqdesc.c
··· 15 15 #include <linux/radix-tree.h> 16 16 #include <linux/bitmap.h> 17 17 #include <linux/irqdomain.h> 18 + #include <linux/sysfs.h> 18 19 19 20 #include "internals.h" 20 21 ··· 124 123 125 124 #ifdef CONFIG_SPARSE_IRQ 126 125 126 + static void irq_kobj_release(struct kobject *kobj); 127 + 128 + #ifdef CONFIG_SYSFS 129 + static struct kobject *irq_kobj_base; 130 + 131 + #define IRQ_ATTR_RO(_name) \ 132 + static struct kobj_attribute _name##_attr = __ATTR_RO(_name) 133 + 134 + static ssize_t per_cpu_count_show(struct kobject *kobj, 135 + struct kobj_attribute *attr, char *buf) 136 + { 137 + struct irq_desc *desc = container_of(kobj, struct irq_desc, kobj); 138 + int cpu, irq = desc->irq_data.irq; 139 + ssize_t ret = 0; 140 + char *p = ""; 141 + 142 + for_each_possible_cpu(cpu) { 143 + unsigned int c = kstat_irqs_cpu(irq, cpu); 144 + 145 + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%s%u", p, c); 146 + p = ","; 147 + } 148 + 149 + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "\n"); 150 + return ret; 151 + } 152 + IRQ_ATTR_RO(per_cpu_count); 153 + 154 + static ssize_t chip_name_show(struct kobject *kobj, 155 + struct kobj_attribute *attr, char *buf) 156 + { 157 + struct irq_desc *desc = container_of(kobj, struct irq_desc, kobj); 158 + ssize_t ret = 0; 159 + 160 + raw_spin_lock_irq(&desc->lock); 161 + if (desc->irq_data.chip && desc->irq_data.chip->name) { 162 + ret = scnprintf(buf, PAGE_SIZE, "%s\n", 163 + desc->irq_data.chip->name); 164 + } 165 + raw_spin_unlock_irq(&desc->lock); 166 + 167 + return ret; 168 + } 169 + IRQ_ATTR_RO(chip_name); 170 + 171 + static ssize_t hwirq_show(struct kobject *kobj, 172 + struct kobj_attribute *attr, char *buf) 173 + { 174 + struct irq_desc *desc = container_of(kobj, struct irq_desc, kobj); 175 + ssize_t ret = 0; 176 + 177 + raw_spin_lock_irq(&desc->lock); 178 + if (desc->irq_data.domain) 179 + ret = sprintf(buf, "%d\n", (int)desc->irq_data.hwirq); 180 + raw_spin_unlock_irq(&desc->lock); 181 + 182 + return ret; 183 + } 184 + IRQ_ATTR_RO(hwirq); 185 + 186 + static ssize_t type_show(struct kobject *kobj, 187 + struct kobj_attribute *attr, char *buf) 188 + { 189 + struct irq_desc *desc = container_of(kobj, struct irq_desc, kobj); 190 + ssize_t ret = 0; 191 + 192 + raw_spin_lock_irq(&desc->lock); 193 + ret = sprintf(buf, "%s\n", 194 + irqd_is_level_type(&desc->irq_data) ? "level" : "edge"); 195 + raw_spin_unlock_irq(&desc->lock); 196 + 197 + return ret; 198 + 199 + } 200 + IRQ_ATTR_RO(type); 201 + 202 + static ssize_t name_show(struct kobject *kobj, 203 + struct kobj_attribute *attr, char *buf) 204 + { 205 + struct irq_desc *desc = container_of(kobj, struct irq_desc, kobj); 206 + ssize_t ret = 0; 207 + 208 + raw_spin_lock_irq(&desc->lock); 209 + if (desc->name) 210 + ret = scnprintf(buf, PAGE_SIZE, "%s\n", desc->name); 211 + raw_spin_unlock_irq(&desc->lock); 212 + 213 + return ret; 214 + } 215 + IRQ_ATTR_RO(name); 216 + 217 + static ssize_t actions_show(struct kobject *kobj, 218 + struct kobj_attribute *attr, char *buf) 219 + { 220 + struct irq_desc *desc = container_of(kobj, struct irq_desc, kobj); 221 + struct irqaction *action; 222 + ssize_t ret = 0; 223 + char *p = ""; 224 + 225 + raw_spin_lock_irq(&desc->lock); 226 + for (action = desc->action; action != NULL; action = action->next) { 227 + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%s%s", 228 + p, action->name); 229 + p = ","; 230 + } 231 + raw_spin_unlock_irq(&desc->lock); 232 + 233 + if (ret) 234 + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "\n"); 235 + 236 + return ret; 237 + } 238 + IRQ_ATTR_RO(actions); 239 + 240 + static struct attribute *irq_attrs[] = { 241 + &per_cpu_count_attr.attr, 242 + &chip_name_attr.attr, 243 + &hwirq_attr.attr, 244 + &type_attr.attr, 245 + &name_attr.attr, 246 + &actions_attr.attr, 247 + NULL 248 + }; 249 + 250 + static struct kobj_type irq_kobj_type = { 251 + .release = irq_kobj_release, 252 + .sysfs_ops = &kobj_sysfs_ops, 253 + .default_attrs = irq_attrs, 254 + }; 255 + 256 + static void irq_sysfs_add(int irq, struct irq_desc *desc) 257 + { 258 + if (irq_kobj_base) { 259 + /* 260 + * Continue even in case of failure as this is nothing 261 + * crucial. 262 + */ 263 + if (kobject_add(&desc->kobj, irq_kobj_base, "%d", irq)) 264 + pr_warn("Failed to add kobject for irq %d\n", irq); 265 + } 266 + } 267 + 268 + static int __init irq_sysfs_init(void) 269 + { 270 + struct irq_desc *desc; 271 + int irq; 272 + 273 + /* Prevent concurrent irq alloc/free */ 274 + irq_lock_sparse(); 275 + 276 + irq_kobj_base = kobject_create_and_add("irq", kernel_kobj); 277 + if (!irq_kobj_base) { 278 + irq_unlock_sparse(); 279 + return -ENOMEM; 280 + } 281 + 282 + /* Add the already allocated interrupts */ 283 + for_each_irq_desc(irq, desc) 284 + irq_sysfs_add(irq, desc); 285 + irq_unlock_sparse(); 286 + 287 + return 0; 288 + } 289 + postcore_initcall(irq_sysfs_init); 290 + 291 + #else /* !CONFIG_SYSFS */ 292 + 293 + static struct kobj_type irq_kobj_type = { 294 + .release = irq_kobj_release, 295 + }; 296 + 297 + static void irq_sysfs_add(int irq, struct irq_desc *desc) {} 298 + 299 + #endif /* CONFIG_SYSFS */ 300 + 127 301 static RADIX_TREE(irq_desc_tree, GFP_KERNEL); 128 302 129 303 static void irq_insert_desc(unsigned int irq, struct irq_desc *desc) ··· 363 187 364 188 desc_set_defaults(irq, desc, node, affinity, owner); 365 189 irqd_set(&desc->irq_data, flags); 190 + kobject_init(&desc->kobj, &irq_kobj_type); 366 191 367 192 return desc; 368 193 ··· 374 197 return NULL; 375 198 } 376 199 377 - static void delayed_free_desc(struct rcu_head *rhp) 200 + static void irq_kobj_release(struct kobject *kobj) 378 201 { 379 - struct irq_desc *desc = container_of(rhp, struct irq_desc, rcu); 202 + struct irq_desc *desc = container_of(kobj, struct irq_desc, kobj); 380 203 381 204 free_masks(desc); 382 205 free_percpu(desc->kstat_irqs); 383 206 kfree(desc); 207 + } 208 + 209 + static void delayed_free_desc(struct rcu_head *rhp) 210 + { 211 + struct irq_desc *desc = container_of(rhp, struct irq_desc, rcu); 212 + 213 + kobject_put(&desc->kobj); 384 214 } 385 215 386 216 static void free_desc(unsigned int irq) ··· 401 217 * kstat_irq_usr(). Once we deleted the descriptor from the 402 218 * sparse tree we can free it. Access in proc will fail to 403 219 * lookup the descriptor. 220 + * 221 + * The sysfs entry must be serialized against a concurrent 222 + * irq_sysfs_init() as well. 404 223 */ 405 224 mutex_lock(&sparse_irq_lock); 225 + kobject_del(&desc->kobj); 406 226 delete_irq_desc(irq); 407 227 mutex_unlock(&sparse_irq_lock); 408 228 ··· 449 261 goto err; 450 262 mutex_lock(&sparse_irq_lock); 451 263 irq_insert_desc(start + i, desc); 264 + irq_sysfs_add(start + i, desc); 452 265 mutex_unlock(&sparse_irq_lock); 453 266 } 454 267 return start;