What is Linux Port I/O Programming: inb(), outb() and a Working Example-Free Linux Device Driver Training in Hyderabad

← Previous Lecture Next Lecture →

Linux Port I/O Programming: inb(), outb() and a Working Example
Hands-on Port Mapped I/O Coding for Kernel Modules
Level: Beginner-Intermediate
Reading Time: 15 min
Kernel Version: 6.x

This lecture continues our coverage of Linux port I/O programming with the practical side: the kernel functions used to read and write hardware ports, how to safely reserve a port range first, and a complete, original kernel module example. If you haven’t read the concepts lecture on port mapped I/O yet, start there — this page assumes you already know what a port is and why x86 keeps a separate address space for it.

inb outb Linux Port I/O Programming request_region Free Linux Device Drivers Course Kernel Module Example

What You Will Learn

  • The kernel functions for 8, 16, and 32-bit port I/O programming
  • How to correctly reserve a port range before touching hardware
  • A complete, original kernel module that performs port I/O programming safely
  • Common bugs and how to avoid them

Prerequisites

  • Our previous lecture: Port Mapped I/O in Linux Kernel (concepts)
  • Comfort building and loading a basic out-of-tree kernel module
  • An x86_64 test machine or VM (port I/O instructions are x86-specific)

The Port I/O Programming API

The kernel exposes a small, stable family of functions for port I/O programming, split by register width. Reads come in three widths, and writes mirror them:

Port I/O Function Family
8-bit
inb() / outb()
16-bit
inw() / outw()
32-bit
inl() / outl()

All six live behind a single header:

#include <asm/io.h>

u8  inb(unsigned long port);
u16 inw(unsigned long port);
u32 inl(unsigned long port);

void outb(u8  value, unsigned long port);
void outw(u16 value, unsigned long port);
void outl(u32 value, unsigned long port);

Reads return the current value sitting on that port at the instant of the call; writes push a value out and, on x86, are guaranteed complete before the next instruction executes — so you never need a manual flush after an outb-family call.

Step 1: Reserve the Port Range

Before any port I/O programming call, the driver must claim the range so no other driver can collide with it. Modern drivers favor the device-managed variant, which ties the reservation to the device’s lifetime and removes the need for manual cleanup on every error path:

#include <linux/ioport.h>

if (!devm_request_region(dev, port_base, port_count, "epath_pio_demo")) {
    dev_err(dev, "port range busy\n");
    return -EBUSY;
}

If you are writing a standalone module without a struct device handy, the classic pair still works and simply needs an explicit release on unload:

if (!request_region(port_base, port_count, "epath_pio_demo"))
    return -EBUSY;

/* ... on module exit ... */
release_region(port_base, port_count);

Step 2: A Complete, Original Example Module

The example below targets the legacy PC parallel port base address (0x378), a well-documented, publicly known legacy I/O range — useful purely for teaching port I/O programming on a generic x86 test VM. It reserves one byte-wide port, writes a value, reads it back, and cleans up on exit.

#include <linux/module.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <asm/io.h>

#define EPATH_PORT_BASE   0x378
#define EPATH_PORT_COUNT  1

static int __init epath_pio_init(void)
{
    u8 value;

    if (!request_region(EPATH_PORT_BASE, EPATH_PORT_COUNT, "epath_pio_demo")) {
        pr_err("epath_pio: port 0x%x busy\n", EPATH_PORT_BASE);
        return -EBUSY;
    }

    outb(0x55, EPATH_PORT_BASE);
    value = inb(EPATH_PORT_BASE);

    pr_info("epath_pio: wrote 0x55, read back 0x%02x\n", value);
    return 0;
}

static void __exit epath_pio_exit(void)
{
    release_region(EPATH_PORT_BASE, EPATH_PORT_COUNT);
    pr_info("epath_pio: port released\n");
}

module_init(epath_pio_init);
module_exit(epath_pio_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala port I/O programming demo");

Building and Testing

$ make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
$ sudo insmod epath_pio_demo.ko
$ dmesg | tail -5
$ sudo rmmod epath_pio_demo

On a real parallel port, the value read back may not exactly equal 0x55 unless the pins are wired for loopback — the point of the exercise is the reservation and call sequence, not the specific chip.

Common Mistakes and Troubleshooting

Symptom Likely Cause Fix
insmod fails with -EBUSY Another driver already owns that port range Check /proc/ioports before reserving
Module builds but crashes on ARM inb/outb family is x86-only Use MMIO accessors on non-x86 targets
Port never freed after rmmod release_region() call missing or skipped on an error path Prefer devm_request_region for automatic cleanup

Best Practices for Port I/O Programming

  • Match the register width exactly — using inl() on an 8-bit register can read neighboring, unrelated ports
  • Wrap raw inb/outb calls in small named helper functions per register, mirroring the datasheet
  • Prefer the managed (devm_) reservation API in any driver bound to a struct device
  • Never hard-code a port address without checking the datasheet or device tree/ACPI resource

Security and Performance Considerations

Port I/O programming is a privileged kernel-level operation; user-space code cannot issue inb/outb directly without an explicit, narrow permission grant, which is intentionally hard to obtain and rarely appropriate outside specialized system tools. On the performance side, each call is a synchronous round trip to the peripheral bus, so tight loops of port I/O programming calls should be avoided in latency-sensitive paths — batch status checks where possible, and reach for DMA when moving real data volumes.

Summary / Key Takeaways

  • inb/outb/inw/outw/inl/outl form the complete port I/O programming API, split by width
  • Always reserve a port range first, preferably with the device-managed API
  • Port I/O programming calls are x86-specific and are wrapped onto MMIO elsewhere
  • Match register width precisely and keep hardware datasheets close at hand

Conclusion

You now have both halves of port I/O programming in the Linux kernel: the concepts from the previous lecture, and a working, original example here showing reservation, read, write, and cleanup. This closes out the PMIO portion of our free Linux kernel programming course; the next lectures move into DMA and interrupt handling, the techniques real high-throughput drivers rely on.

Frequently Asked Questions

Q1. Do I need real hardware to try this example?
No — it builds and loads on any x86_64 VM; the read-back value simply won’t be meaningful without wired hardware.

Q2. Why prefer devm_request_region over request_region?
It ties the reservation to the device’s lifetime so cleanup happens automatically, reducing leak-on-error bugs.

Q3. Can I use these functions on a Raspberry Pi or other ARM board?
The functions exist for source compatibility but do not correspond to real port hardware; use MMIO accessors instead on ARM.

Q4. What happens if I use the wrong width function?
You may read or write neighboring ports unintentionally, since widths determine how many address lines are touched.

Q5. Is this part of a complete free course?
Yes — this is a lecture in EmbeddedPathashala’s free Linux kernel programming and device drivers course.

Q6. Where do I find the correct port address for my chip?
Always check the chip’s datasheet or the platform’s device tree/ACPI resource description — never guess.

Continue Your Free Linux Device Drivers Course

Next up: DMA fundamentals for high-throughput device drivers.

Browse the Full Course

← Previous Lecture Next Lecture →

2 Comments

Leave a Reply

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