Skip to content

Day 13 - GPIO IRQ (Debounce + Bottom Half)

🎯 Learning Goals

  • Understand IRQ top half vs bottom half design
  • Implement software debounce using workqueue
  • Learn event-driven driver architecture
  • Introduce ring buffer for event queue
  • Integrate wait queue + poll/select

🔥 IRQ Design (Top Half vs Bottom Half)

Top Half (ISR)

Characteristics:

  • Runs in interrupt context
  • Must be fast
  • Cannot sleep
  • Should NOT:
  • call blocking APIs
  • perform heavy logic

Example:

static irqreturn_t mygpio_btn_isr(int irq, void *data)
{
    struct mygpio_dev *mydev = data;

    mod_delayed_work(system_wq,
                     &mydev->btn_dwork,
                     msecs_to_jiffies(mydev->debounce_ms));

    return IRQ_HANDLED;
}

👉 ISR only schedules work → best practice


Bottom Half (Workqueue)

Runs in process context:

  • Can sleep
  • Can use mutex
  • Can call gpiod_get_value_cansleep()

Example:

static void mygpio_btn_dwork_handler(struct work_struct *work)
{
    ...
    value = gpiod_get_value_cansleep(...);
}

⏱ Software Debounce

Why debounce?

  • Mechanical button produces noise (bounce)
  • Multiple IRQs triggered for one press

Solution:

  • Delay sampling after IRQ
  • Only report stable state

Flow:

IRQ → delayed_work (20ms) → read GPIO → compare state

🔄 Event Queue (Ring Buffer)

Structure:

event_head → write index
event_tail → read index

Full condition:

(next == tail) → buffer full → drop event

Empty condition:

(head == tail) → no data

Advantages:

  • Supports burst events
  • Avoids losing data
  • Decouples producer/consumer

🧠 Synchronization Strategy

Critical section (protected)

  • enqueue event
  • dequeue event

Use:

mutex_lock()
...
mutex_unlock()

Lockless checks

Example:

if (head == tail)

👉 Allowed to race (by design)


🚫 Why NOT use volatile?

Key concept:

volatile does NOT provide thread safety

Kernel approach:

  • Use mutex / spinlock
  • Use memory barrier
  • Use atomic operations if needed

💤 Wait Queue (Blocking I/O)

Used for blocking read:

wait_event_interruptible(queue, condition);

Behavior:

  • Sleep until condition true
  • Woken by:
wake_up_interruptible(&queue);

🔔 poll / select Support

Driver implements:

static __poll_t mygpio_poll(...)
{
    poll_wait(file, &queue, wait);

    if (data_available)
        return POLLIN;
}

🧩 Full Data Flow

[ IRQ ]
[ ISR (top half) ]
[ delayed work (debounce) ]
[ event queue (ring buffer) ]
[ wake_up_interruptible ]
[ user: poll/select ]
[ user: read() ]

⚠️ Common Pitfalls

1. Doing too much in ISR ❌

  • Reading GPIO
  • Logging
  • Sleeping

2. Missing wake_up ❌

→ read() will block forever


3. Using volatile ❌

→ does NOT fix race condition


4. Holding lock too long ❌

→ impacts latency


🚀 Summary

  • ISR should be minimal
  • Workqueue handles logic
  • Ring buffer decouples events
  • Wait queue enables blocking read
  • poll enables event-driven user space