Skip to content

Day 12 - Event-Driven GPIO Driver

Summary

Today I upgraded the GPIO IRQ driver from a simple IRQ demo into an event-driven Linux-style driver.

The driver now supports:

  • blocking read
  • non-blocking read
  • poll-based event notification

What I Implemented

1. Wait queue support

Added a wait queue so user-space readers can sleep until an event occurs.

wait_queue_head_t read_queue;

The queue is initialized in probe() and used by both read() and .poll().


2. Event state fields

Added simple event state fields:

int event_pending;
int event_btn_state;

These fields are used to bridge IRQ events to user-space reads.


3. IRQ wakeup path

Updated the IRQ handler so it:

  • reads the button state
  • stores the event
  • wakes sleeping readers

Core event path:

IRQ
 → store event
 → wake_up_interruptible()
 → user-space read/poll wakes up

4. Blocking read

Implemented blocking read() using:

wait_event_interruptible(...)

Behavior:

  • no event → sleep
  • event occurs → wake and return data

5. Non-blocking read

Implemented O_NONBLOCK behavior.

Behavior:

  • no event → -EAGAIN
  • event available → return immediately

This matches the usual Linux file I/O model.


6. poll support

Implemented .poll() so user space can use:

  • select()
  • poll()
  • epoll()

The poll path uses the same wait queue as blocking read.


What I Learned

read() and poll() are different

  • read() fetches data
  • poll() waits for readiness

This separation is a core idea in Linux event-driven I/O.


Linux does not use user callback style driver APIs

Instead of:

async_read(callback)

Linux uses:

poll() / select() / epoll()
read()

User space owns the event loop.


wait queue is the bridge

The wait queue is the mechanism that connects:

  • kernel event source
  • sleeping user task
  • readiness notification

Test Results

Blocking read

cat /dev/mygpio

Result:

  • blocked as expected
  • button event woke the read path
  • returned one event line

Non-blocking read

Used a test program with O_NONBLOCK.

Result:

  • returned -EAGAIN when no event was pending
  • returned event data when available

poll test

Used a test program based on poll().

Result:

  • poll() blocked correctly
  • IRQ woke the poll waiter
  • read() returned the expected event data

All expected behaviors were confirmed.


Current Limitations

1. Event overwrite

The current design uses a single pending flag.

This means multiple IRQs may overwrite each other before user space reads the event.


2. No debounce

Mechanical button bounce can generate multiple IRQs.

The driver currently reports raw button events.


3. Synchronization is still simple

The current version is suitable for learning, but it does not yet use stronger synchronization primitives such as:

  • spinlock
  • atomic variable
  • ring buffer protection

Next Steps

Possible next improvements:

  • add software debounce
  • move heavier work out of IRQ context
  • improve synchronization
  • replace single event flag with an event queue

Conclusion

Day 12 was an important step toward a more realistic Linux driver.

The driver now demonstrates the standard Linux event-driven model:

IRQ → wait queue → poll/read → user space

This is much closer to real Linux driver behavior than a simple polling-only character device.