Interrupt Control

This chapter covers managing interrupts: enabling/disabling, CPU affinity, trigger types, and interrupt domains for controllers.

Enabling and Disabling Interrupts

Local CPU Interrupts

#include <linux/irqflags.h>

/* Disable interrupts on local CPU */
local_irq_disable();
/* Critical section - interrupts disabled */
local_irq_enable();

/* Save and restore state (preferred) */
unsigned long flags;
local_irq_save(flags);    /* Disable and save state */
/* Critical section */
local_irq_restore(flags); /* Restore previous state */

Specific IRQ Line

#include <linux/interrupt.h>

/* Disable specific IRQ (all CPUs) */
disable_irq(irq);     /* Waits for running handlers */
/* ... */
enable_irq(irq);

/* Non-blocking disable */
disable_irq_nosync(irq);  /* Returns immediately */

When to Disable IRQs

flowchart TB
    Q1{Need atomic operation<br/>with hardware?}
    Q2{Sharing data with<br/>interrupt handler?}
    Q3{Short critical section?}

    SPIN[Use spinlock_irqsave]
    DISABLE[Use disable_irq]
    MUTEX[Use mutex instead]

    Q1 -->|Yes| Q3
    Q1 -->|No| Q2
    Q2 -->|Yes| SPIN
    Q2 -->|No| MUTEX
    Q3 -->|Yes| SPIN
    Q3 -->|No| DISABLE

IRQ Affinity

Control which CPUs can handle an interrupt:

Viewing Affinity

# View current affinity (hex bitmask)
cat /proc/irq/45/smp_affinity
# f    (all 4 CPUs: 0x1111)

# View as CPU list
cat /proc/irq/45/smp_affinity_list
# 0-3

# View effective affinity (after system adjustments)
cat /proc/irq/45/effective_affinity

Setting Affinity

# Pin to CPU 0 only
echo 1 > /proc/irq/45/smp_affinity

# Pin to CPU 2 only
echo 4 > /proc/irq/45/smp_affinity

# CPUs 0 and 2
echo 5 > /proc/irq/45/smp_affinity

# All CPUs on 8-core system
echo ff > /proc/irq/45/smp_affinity

# Using CPU list format
echo 0,2-3 > /proc/irq/45/smp_affinity_list

Programmatic Affinity Control

#include <linux/interrupt.h>
#include <linux/cpumask.h>

/* Set affinity to specific CPU */
int irq_set_affinity(unsigned int irq, const struct cpumask *mask);

/* Example: Pin to CPU 2 */
cpumask_t mask;
cpumask_clear(&mask);
cpumask_set_cpu(2, &mask);
irq_set_affinity(dev->irq, &mask);

/* Set affinity hint (advisory) */
int irq_set_affinity_hint(unsigned int irq, const struct cpumask *mask);

/* Per-device affinity in probe */
static int my_probe(struct platform_device *pdev)
{
    cpumask_t mask;
    int cpu;

    /* Use local CPU for this device's IRQ */
    cpu = raw_smp_processor_id();
    cpumask_clear(&mask);
    cpumask_set_cpu(cpu, &mask);

    irq_set_affinity_hint(dev->irq, &mask);

    return 0;
}

Edge vs Level Trigger Configuration

Setting Trigger Type

/* At request time */
request_irq(irq, handler, IRQF_TRIGGER_RISING, name, dev);
request_irq(irq, handler, IRQF_TRIGGER_FALLING, name, dev);
request_irq(irq, handler, IRQF_TRIGGER_HIGH, name, dev);
request_irq(irq, handler, IRQF_TRIGGER_LOW, name, dev);

/* Both edges */
request_irq(irq, handler,
            IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
            name, dev);

Checking Trigger Type

#include <linux/irq.h>

struct irq_data *data = irq_get_irq_data(irq);
u32 type = irqd_get_trigger_type(data);

if (type & IRQ_TYPE_EDGE_RISING)
    pr_info("Rising edge trigger\n");
if (type & IRQ_TYPE_LEVEL_HIGH)
    pr_info("Level high trigger\n");

Trigger Type Constants

IRQ_TYPE_NONE           /* Use device default */
IRQ_TYPE_EDGE_RISING    /* Rising edge */
IRQ_TYPE_EDGE_FALLING   /* Falling edge */
IRQ_TYPE_EDGE_BOTH      /* Both edges */
IRQ_TYPE_LEVEL_HIGH     /* Active high level */
IRQ_TYPE_LEVEL_LOW      /* Active low level */

IRQ Domain (for Controller Drivers)

IRQ domains map hardware interrupt numbers to Linux virtual IRQ numbers:

flowchart LR
    subgraph Hardware
        HW1[HW IRQ 0]
        HW2[HW IRQ 1]
        HW3[HW IRQ 2]
    end

    subgraph Domain["IRQ Domain"]
        MAP[Mapping Table]
    end

    subgraph Linux["Linux Virtual IRQs"]
        V1[virq 100]
        V2[virq 101]
        V3[virq 102]
    end

    HW1 --> MAP
    HW2 --> MAP
    HW3 --> MAP
    MAP --> V1
    MAP --> V2
    MAP --> V3

Creating an IRQ Domain

#include <linux/irqdomain.h>

static const struct irq_domain_ops my_domain_ops = {
    .xlate = irq_domain_xlate_twocell,
    .map = my_irq_domain_map,
};

static int my_irq_domain_map(struct irq_domain *d,
                             unsigned int virq,
                             irq_hw_number_t hwirq)
{
    struct my_irq_chip *chip = d->host_data;

    irq_set_chip_and_handler(virq, &chip->chip, handle_level_irq);
    irq_set_chip_data(virq, chip);

    return 0;
}

static int my_irq_controller_probe(struct platform_device *pdev)
{
    struct my_irq_chip *chip;

    chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
    if (!chip)
        return -ENOMEM;

    /* Create IRQ domain */
    chip->domain = irq_domain_add_linear(pdev->dev.of_node,
                                         32,  /* Number of IRQs */
                                         &my_domain_ops,
                                         chip);
    if (!chip->domain)
        return -ENOMEM;

    return 0;
}

IRQ Chip (for Controller Drivers)

#include <linux/irq.h>

static void my_irq_mask(struct irq_data *d)
{
    struct my_irq_chip *chip = irq_data_get_irq_chip_data(d);
    u32 mask = readl(chip->regs + IRQ_MASK);

    mask &= ~BIT(d->hwirq);
    writel(mask, chip->regs + IRQ_MASK);
}

static void my_irq_unmask(struct irq_data *d)
{
    struct my_irq_chip *chip = irq_data_get_irq_chip_data(d);
    u32 mask = readl(chip->regs + IRQ_MASK);

    mask |= BIT(d->hwirq);
    writel(mask, chip->regs + IRQ_MASK);
}

static void my_irq_ack(struct irq_data *d)
{
    struct my_irq_chip *chip = irq_data_get_irq_chip_data(d);

    writel(BIT(d->hwirq), chip->regs + IRQ_CLEAR);
}

static struct irq_chip my_irq_chip = {
    .name = "my-irq",
    .irq_mask = my_irq_mask,
    .irq_unmask = my_irq_unmask,
    .irq_ack = my_irq_ack,
};

Synchronization Helpers

Synchronize with Handler

/* Wait for any running handler to complete */
synchronize_irq(irq);

/* Use during cleanup */
static int my_remove(struct platform_device *pdev)
{
    struct my_device *dev = platform_get_drvdata(pdev);

    /* Stop device from generating interrupts */
    writel(0, dev->regs + IRQ_ENABLE);

    /* Wait for any running handler */
    synchronize_irq(dev->irq);

    /* Now safe to free resources handler uses */
    free_irq(dev->irq, dev);

    return 0;
}

Check Handler Running

#include <linux/interrupt.h>

/* Check if handler is currently running */
if (irq_has_action(irq)) {
    pr_info("IRQ %d has registered handler\n", irq);
}

IRQ Statistics

Viewing Statistics

# Interrupt counts per CPU
cat /proc/interrupts

# Detailed stats
cat /proc/irq/45/spurious

# Per-IRQ information
ls /sys/kernel/irq/45/
# actions  chip_name  hwirq  name  per_cpu_count  type  wakeup

Accessing Programmatically

#include <linux/irqdesc.h>

struct irq_desc *desc = irq_to_desc(irq);
if (desc) {
    unsigned int count;

    /* Total interrupt count */
    count = desc->irq_count;

    /* Per-CPU counts */
    count = *per_cpu_ptr(desc->kstat_irqs, cpu);
}

Wakeup IRQs

Configure an IRQ to wake the system from suspend:

/* Enable wakeup capability */
ret = enable_irq_wake(irq);
if (ret)
    dev_warn(dev, "Cannot enable wakeup: %d\n", ret);

/* Disable wakeup */
disable_irq_wake(irq);

/* Check if wakeup enabled */
if (irqd_is_wakeup_set(irq_get_irq_data(irq)))
    pr_info("Wakeup enabled for IRQ %d\n", irq);

Device Tree Wakeup

gpio-keys {
    compatible = "gpio-keys";
    wakeup-source;  /* Mark as wakeup capable */

    button {
        gpios = <&gpio 5 GPIO_ACTIVE_LOW>;
        interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
        wakeup-source;
    };
};

Common Patterns

Disable During Configuration

static int my_configure(struct my_device *dev, struct config *cfg)
{
    unsigned long flags;

    /* Disable IRQ during reconfiguration */
    disable_irq(dev->irq);

    /* Reconfigure hardware */
    writel(cfg->mode, dev->regs + MODE);
    writel(cfg->rate, dev->regs + RATE);

    /* Clear any pending interrupts */
    writel(0xFFFFFFFF, dev->regs + IRQ_CLEAR);

    enable_irq(dev->irq);

    return 0;
}

Affinity for Performance

/* Pin network IRQ to CPU handling network traffic */
static void optimize_net_irq(struct net_device *ndev)
{
    struct my_net_dev *priv = netdev_priv(ndev);
    cpumask_t mask;

    /* Use CPU 0 for RX, CPU 1 for TX */
    cpumask_clear(&mask);
    cpumask_set_cpu(0, &mask);
    irq_set_affinity_hint(priv->rx_irq, &mask);

    cpumask_clear(&mask);
    cpumask_set_cpu(1, &mask);
    irq_set_affinity_hint(priv->tx_irq, &mask);
}

Summary

  • Use local_irq_save/restore for short critical sections
  • Use disable_irq/enable_irq to manage specific IRQ lines
  • Set IRQ affinity for performance optimization
  • Configure trigger type at request time
  • IRQ domains map hardware to virtual IRQ numbers
  • synchronize_irq() ensures handlers complete before cleanup
  • Enable irq_wake for wakeup-capable interrupts

Next

Continue to Part 8: Platform Bus and Device Tree to learn about device tree and platform device integration.


Back to top

Linux Driver Development Guide is a community resource for learning kernel driver development. Not affiliated with the Linux Foundation. Content provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.