← Previous Lecture Next Lecture →
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.
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:
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.
Next up: DMA fundamentals for high-throughput device drivers.
Browse the Full Course
2 Comments