Port-Mapped I/O (PMIO) in Linux Device Drivers (Kernel 6.x)-Free Linux Kernel Development Course

Free Linux Kernel Programming Course
Lecture 9: Port-Mapped I/O (PMIO) in Linux Device Drivers (Kernel 6.x)
100% Free
x86 Focused
Hands-On Code

We close out this mini-series in our free linux device drivers course with port-mapped I/O (PMIO), the second of the two hardware access styles introduced two lectures ago. PMIO is less common on modern hardware than memory-mapped I/O, but it still appears on x86 platforms and is a core topic in any thorough free linux kernel development course or free embedded systems course.

What You Will Learn

Lecture Roadmap
✅ What makes port-mapped I/O different from memory-mapped I/O
✅ How to reserve a range of I/O ports before using them
✅ The inb/outb family of accessor functions
✅ When to choose PMIO versus MMIO on hardware that supports both

Prerequisites

This lecture assumes you have completed the two earlier lectures in this mini-series covering I/O memory basics and memory-mapped I/O. Some familiarity with x86 architecture is helpful, since port-mapped I/O is primarily an x86 concept.

What Is Port-Mapped I/O?

Unlike memory-mapped I/O, where a peripheral’s registers simply occupy addresses inside the normal address space, port-mapped I/O uses a completely separate, smaller address space that the CPU can only reach through dedicated instructions. On x86 processors these are the IN and OUT assembly instructions, and the kernel exposes them to driver code through C wrapper functions rather than requiring you to write inline assembly yourself.

MMIO vs PMIO Address Spaces
Memory Address Space
Shared by RAM and MMIO peripherals, reached with ordinary load/store instructions
I/O Port Address Space
A separate, dedicated space reached only through IN/OUT style CPU instructions

Step 1: Reserving an I/O Port Range

Just like with memory-mapped I/O, you must reserve a port range before using it, so the kernel’s bookkeeping reflects that your driver owns it. On kernel 6.x the managed variant is preferred:

#define MYDEV_IOPORT_BASE  0x300
#define MYDEV_IOPORT_SIZE  8

static int mydevice_probe_pmio(struct pci_dev *pdev)
{
    if (!devm_request_region(&pdev->dev, MYDEV_IOPORT_BASE,
                              MYDEV_IOPORT_SIZE, "mydevice"))
        return -EBUSY;

    /* Port range is now reserved for this driver */
    return 0;
}

As with the memory-mapped case, the devm_ prefixed version automatically releases the port range when the driver is detached, so you do not need a matching manual release call in a normal teardown path.

Step 2: Reading and Writing Ports

Once the range is reserved, you access individual ports with a small family of functions rather than a pointer. There is no mapping step here at all, since ports are not part of the regular address space.

Port Width Read Function Write Function
8-bit inb(port) outb(val, port)
16-bit inw(port) outw(val, port)
32-bit inl(port) outl(val, port)

A Simple Original PMIO Example

Here is a small, original status-check pattern showing the same read-modify-write idea from the MMIO lecture, adapted to ports instead of a mapped pointer:

#define STATUS_PORT_OFFSET  0x02
#define READY_BIT           BIT(1)

static bool mydevice_is_ready(unsigned int base_port)
{
    u8 status = inb(base_port + STATUS_PORT_OFFSET);

    return status & READY_BIT;
}

static void mydevice_reset(unsigned int base_port)
{
    outb(0x01, base_port + STATUS_PORT_OFFSET);
}

Real-World Use Case

Port-mapped I/O today mostly survives in legacy PC hardware paths: the classic PC parallel port, some older serial UART implementations, and a handful of very old PCI devices that never migrated to a pure memory-mapped BAR layout. Most new silicon, including new x86 peripherals, has moved entirely to memory-mapped I/O, so treat this lecture as important background knowledge rather than something you will use in every driver you write.

MMIO vs PMIO: Which Should You Use?

Situation Recommended Style
Writing a new driver for modern silicon Memory-mapped I/O (MMIO)
Supporting legacy x86 hardware that only exposes ports Port-mapped I/O (PMIO)
Targeting ARM, RISC-V, or most embedded SoCs Memory-mapped I/O only; PMIO is unavailable

Common Mistakes and Troubleshooting

Mistake Fix
Assuming inb/outb work the same on ARM Port I/O instructions are x86-specific; check architecture support before relying on them
Skipping the region request step Always call devm_request_region() first so the kernel tracks ownership correctly

Key Takeaways

PMIO uses a separate address space from RAM inb/outb replace pointer dereferencing Reserve ports with devm_request_region PMIO is mostly legacy x86 hardware today

Frequently Asked Questions

Is port-mapped I/O still relevant on modern hardware?

It is far less common than memory-mapped I/O today, but it still appears on x86 systems for certain legacy peripherals, so understanding it remains useful for anyone working across a broad range of x86 hardware.

Can I use inb and outb on an ARM embedded board?

No. IN and OUT style port instructions are specific to the x86 architecture. ARM and most other embedded architectures only support memory-mapped I/O.

Do I need to map port addresses with ioremap like I do for MMIO?

No. I/O ports are accessed directly through the inb/outb family of functions using the port number itself, with no mapping step required, unlike memory-mapped I/O.

How do I decide whether my hardware uses MMIO or PMIO?

Check your chip or board’s datasheet. Most modern peripherals, including nearly everything on ARM and RISC-V, use memory-mapped I/O, while port-mapped I/O is generally limited to specific legacy x86 devices.

What happens if I forget to reserve a port range before using it?

Another driver could claim or conflict with the same ports, leading to unpredictable hardware behavior. Always reserve the range first with a request function before performing any inb/outb calls.

You’ve Completed This Mini-Series!

You now understand the full picture of hardware I/O memory access in Linux device drivers, from the underlying virtual memory theory through practical MMIO and PMIO driver code.

2 Comments

Leave a Reply

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