How Device Drivers Talk to Hardware I/O Memory (Updated for Linux 6.x)-Linux Device Driver Training Online

Free Linux Kernel Programming Course
Lecture 7: How Device Drivers Talk to Hardware I/O Memory (Updated for Linux 6.x)
100% Free
Beginner Friendly
Kernel 6.x Ready
⬅ Previous Lecture: Next Lecture: ➡

If you are building a free linux device drivers course mindset and want to actually understand how a driver talks to a chip, this lecture is where the real fun begins. Every device driver, from a simple LED driver to a full network card driver, eventually has to read or write hardware registers. This lecture explains, in plain language, why the Linux kernel does not let you touch hardware memory directly and what the kernel gives you instead. This is part of our free linux kernel development course and works perfectly as a companion piece to any free embedded systems course you may already be following.

What You Will Learn in This Lecture

Lecture Roadmap
✅ Why a driver cannot poke hardware memory directly on a modern OS
✅ How virtual addresses get translated to physical addresses
✅ The two hardware access styles: memory-mapped I/O and port-mapped I/O
✅ How to politely “ask” the kernel for an I/O region before you use it

Prerequisites for This Linux Device Driver Lecture

You should be comfortable with basic C programming and have already built at least one “hello world” style kernel module. If you have not, go through the earlier lectures of this free linux kernel programming course first, then come back here. A guest Linux virtual machine running any modern distribution with kernel 6.x headers installed is enough to follow along; you do not need special hardware for this lecture.

Why Can’t a Driver Just Read Hardware Memory Directly?

Here is the mental picture that trips up most beginners: a hardware chip has registers, so surely a driver can just read a memory address and get the value, right? On a modern operating system the answer is “not so fast.” Linux is a virtual-memory operating system, which means every address your code touches, whether in a user application or inside the kernel itself, is a virtual address, not a real physical wire on the motherboard.

Before that virtual address can actually reach real hardware, the processor has to convert it into a physical address. This conversion is handled by a piece of silicon called the Memory Management Unit (MMU), working together with in-memory structures called page tables. The kernel cannot skip this step for hardware registers either, because hardware I/O memory is not ordinary RAM; it lives on a peripheral chip, often reached over a completely different electrical path than main memory.

Understanding Virtual-to-Physical Address Translation

Address Translation Flow (Simplified)
Driver Code Uses Virtual Address
→
CPU Cache Check
→
MMU + TLB Lookup
→
Page Table Walk
→
Physical Address on Bus

Two things make this fast in practice. The CPU cache may already hold the data, giving a quick “cache hit.” If not, the MMU checks the Translation Lookaside Buffer (TLB), a small on-chip cache of recent virtual-to-physical mappings. Only when both of those miss does the processor walk the full page tables, which is the slowest path. The exact order of cache-check versus MMU-check differs by CPU architecture, but the end goal is always the same: turn a virtual address into a physical one before any real read or write happens.

Since hardware I/O memory is not RAM, the kernel needs a controlled way to create a virtual mapping that points at that peripheral memory instead of at ordinary RAM. That controlled mechanism is exactly what the rest of this lecture is about.

Two Ways the Kernel Talks to Hardware Registers

Modern processors give kernel developers two broad strategies for reaching a peripheral’s registers or memory. Understanding both is essential groundwork for this free linux device drivers course, because the API you reach for later depends entirely on which style your target hardware and CPU architecture use.

Access Style How It Works Typical Use
Memory-Mapped I/O (MMIO) Peripheral registers are given addresses inside the normal processor address space and read/written with regular load/store instructions Almost all modern peripherals: PCIe cards, ARM SoC peripherals, USB controllers
Port-Mapped I/O (PMIO) A separate, dedicated address space reached only through special CPU instructions Legacy x86 peripherals such as the classic parallel port or older serial hardware

On x86 systems both styles exist side by side. On most ARM and RISC-V based embedded boards, which power the majority of today’s IoT and embedded devices, only memory-mapped I/O is available. If you are following this as part of a free embedded systems course on an ARM board, you will use MMIO almost exclusively.

Step One: Politely Asking the Kernel for Permission

Even once you know which style your hardware uses, you cannot simply start reading and writing. The kernel is the system’s resource manager, and it keeps an internal bookkeeping record of which driver owns which I/O memory range or port range. This prevents two drivers from accidentally fighting over the same hardware resource. The workflow always follows the same three-step pattern:

The Request → Use → Release Pattern
1. Request
Ask the kernel to reserve the region for your driver
2. Use
Map and perform the actual reads/writes to registers
3. Release
Hand the region back so other code can use it later

Modern kernel drivers (6.x) almost always prefer the “managed” or devm_ family of helpers, because they automatically release the resource when the driver is unloaded, removing an entire category of bugs where a developer forgets step three. In classic-style code you would call a raw request function and remember to release it yourself; in a modern driver you instead call the managed version once during probe and never worry about cleanup at all.

Classic vs Managed Resource Requests
Access Style Classic Request Modern Managed Version
Memory-mapped I/O request_mem_region() devm_request_mem_region()
Port-mapped I/O request_region() devm_request_region()

A quick note before we move on: reserving the region only stops other drivers from claiming the same physical resource. It does not by itself give you a usable pointer you can dereference. For memory-mapped I/O you still need to create a virtual mapping through ioremap() style calls, which we cover in full detail in the next lecture. For port-mapped I/O you use dedicated CPU instructions rather than a pointer at all, which the following lecture also covers.

Common Mistakes Beginners Make

Mistake Why It Breaks Things
Casting a physical address straight to a pointer and dereferencing it Crashes the kernel; physical addresses are never valid virtual pointers on their own
Forgetting to release a requested region on the classic (non-managed) APIs Leaves the resource permanently reserved, blocking future driver loads until reboot
Assuming every board supports port-mapped I/O Most ARM and RISC-V embedded boards only support memory-mapped I/O

Best Practices for Modern Kernel Drivers

  • Prefer the devm_ managed APIs in new driver code targeting kernel 6.x.
  • Always check the return value of a request call before touching hardware.
  • Read your SoC or chip datasheet to confirm whether the peripheral is memory-mapped or port-mapped before writing a single line of code.
  • Keep register access code isolated in small, well-named helper functions rather than scattering raw reads and writes across your driver.

Key Takeaways

Hardware registers need controlled access Virtual addresses always need translation MMIO vs PMIO depends on your hardware Always request before you touch a region Prefer devm_ managed APIs today

Frequently Asked Questions

Why can’t my driver just read a hardware address directly like a normal variable?

Because Linux is a virtual memory operating system. Every address your driver uses is virtual and must be translated by the MMU before it reaches real hardware, and hardware registers are not ordinary RAM, so the kernel must set up a special mapping first.

What is the difference between memory-mapped I/O and port-mapped I/O?

Memory-mapped I/O places hardware registers inside the normal address space so you access them with regular pointer reads and writes. Port-mapped I/O uses a completely separate address space that can only be reached with special CPU instructions, and it is mainly found on legacy x86 hardware.

Do ARM-based embedded boards support port-mapped I/O?

Almost never. The vast majority of ARM and RISC-V based SoCs only expose memory-mapped I/O, so if you are on embedded ARM hardware you will be using the MMIO APIs covered in the next lecture.

What happens if I forget to release a requested I/O region?

With the classic, non-managed APIs the region stays reserved until the system reboots, which can block your driver or another driver from loading correctly the next time. This is exactly why modern drivers prefer the devm_ managed variants, which release the region automatically.

Is this lecture still relevant on the latest Linux 6.x kernels?

Yes. The underlying concepts of virtual memory, the MMU, and the request-use-release pattern have not changed. What has evolved is a stronger push toward the managed devm_ style APIs, which this lecture highlights alongside the classic calls.

Do I need real hardware to follow this free Linux kernel programming course?

Not for this lecture. Understanding the concepts and API patterns only requires a Linux virtual machine with kernel headers installed. Real hardware becomes useful once you start writing and testing complete drivers in later lectures.

Continue This Free Linux Kernel Programming Course

Next up: Memory-Mapped I/O in practice, with a complete modern kernel driver walkthrough.

⬅ Previous Lecture: Next Lecture: ➡

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *