I/O Ports vs Memory-Mapped I/O
When writing device drivers in C, you need to communicate directly with hardware components. Two primary techniques exist for this: using traditional I/O ports and using memory-mapped I/O. Each method provides a way to read from and write to device registers, but they differ in how the CPU accesses these registers.
I/O ports are a legacy mechanism where special CPU instructions are used to interact with specific hardware addresses outside the normal memory space. These instructions, such as inb and outb, allow you to send or receive data directly to and from a hardware device using port numbers. This approach is common on x86 systems and is often used for simple devices or legacy hardware.
io_port_example.c
123456789101112131415161718192021222324252627282930313233#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <asm/io.h> #define PORT 0x60 // Example port (keyboard data port) static int __init io_port_init(void) { unsigned char value; // Read a byte from the port value = inb(PORT); printk(KERN_INFO "Read value 0x%02x from port 0x%02x\n", value, PORT); // Write a byte to the port outb(0x55, PORT); printk(KERN_INFO "Wrote value 0x55 to port 0x%02x\n", PORT); return 0; } static void __exit io_port_exit(void) { printk(KERN_INFO "I/O Port example exiting\n"); } module_init(io_port_init); module_exit(io_port_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Device Driver Course"); MODULE_DESCRIPTION("Example of I/O port access using inb and outb");
While I/O ports use specialized instructions and a separate address space, memory-mapped I/O integrates device registers directly into the system's main memory address space. This means you can access hardware just like you would access a variable in memory, using standard pointer operations. In Linux device drivers, memory-mapped I/O is often preferred for modern hardware since it allows for more efficient and flexible access, especially for devices with many registers or high throughput requirements.
To use memory-mapped I/O, you typically use the ioremap function to map the physical address of the device's registers into the kernel's virtual address space. Then, you can use functions like readl and writel to read and write 32-bit values to these registers. This approach is different from the I/O port example, where you used inb and outb with a port number. With memory-mapped I/O, you interact with hardware using memory addresses and standard memory access functions.
mmio_example.c
123456789101112131415161718192021222324252627282930313233343536373839404142434445#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/io.h> #define DEVICE_PHYS_ADDR 0xFE200000 // Example physical address #define DEVICE_REG_SIZE 0x1000 // Size of mapped region static void __iomem *device_base; static int __init mmio_init(void) { u32 value; // Map the physical device address to kernel virtual address space device_base = ioremap(DEVICE_PHYS_ADDR, DEVICE_REG_SIZE); if (!device_base) { printk(KERN_ERR "Failed to ioremap device memory\n"); return -ENOMEM; } // Read a 32-bit value from offset 0x00 value = readl(device_base); printk(KERN_INFO "Read value 0x%08x from device register\n", value); // Write a 32-bit value to offset 0x00 writel(0xAABBCCDD, device_base); printk(KERN_INFO "Wrote value 0xAABBCCDD to device register\n"); return 0; } static void __exit mmio_exit(void) { if (device_base) iounmap(device_base); printk(KERN_INFO "MMIO example exiting\n"); } module_init(mmio_init); module_exit(mmio_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Device Driver Course"); MODULE_DESCRIPTION("Example of memory-mapped I/O using ioremap, readl, and writel");
Thanks for your feedback!
Ask AI
Ask AI
Ask anything or try one of the suggested questions to begin our chat
Can you explain the advantages and disadvantages of I/O ports versus memory-mapped I/O?
How do I choose which method to use for my device driver?
Can you provide an example of using ioremap and writel in a Linux device driver?
Awesome!
Completion rate improved to 3.85
I/O Ports vs Memory-Mapped I/O
Swipe to show menu
When writing device drivers in C, you need to communicate directly with hardware components. Two primary techniques exist for this: using traditional I/O ports and using memory-mapped I/O. Each method provides a way to read from and write to device registers, but they differ in how the CPU accesses these registers.
I/O ports are a legacy mechanism where special CPU instructions are used to interact with specific hardware addresses outside the normal memory space. These instructions, such as inb and outb, allow you to send or receive data directly to and from a hardware device using port numbers. This approach is common on x86 systems and is often used for simple devices or legacy hardware.
io_port_example.c
123456789101112131415161718192021222324252627282930313233#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <asm/io.h> #define PORT 0x60 // Example port (keyboard data port) static int __init io_port_init(void) { unsigned char value; // Read a byte from the port value = inb(PORT); printk(KERN_INFO "Read value 0x%02x from port 0x%02x\n", value, PORT); // Write a byte to the port outb(0x55, PORT); printk(KERN_INFO "Wrote value 0x55 to port 0x%02x\n", PORT); return 0; } static void __exit io_port_exit(void) { printk(KERN_INFO "I/O Port example exiting\n"); } module_init(io_port_init); module_exit(io_port_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Device Driver Course"); MODULE_DESCRIPTION("Example of I/O port access using inb and outb");
While I/O ports use specialized instructions and a separate address space, memory-mapped I/O integrates device registers directly into the system's main memory address space. This means you can access hardware just like you would access a variable in memory, using standard pointer operations. In Linux device drivers, memory-mapped I/O is often preferred for modern hardware since it allows for more efficient and flexible access, especially for devices with many registers or high throughput requirements.
To use memory-mapped I/O, you typically use the ioremap function to map the physical address of the device's registers into the kernel's virtual address space. Then, you can use functions like readl and writel to read and write 32-bit values to these registers. This approach is different from the I/O port example, where you used inb and outb with a port number. With memory-mapped I/O, you interact with hardware using memory addresses and standard memory access functions.
mmio_example.c
123456789101112131415161718192021222324252627282930313233343536373839404142434445#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/io.h> #define DEVICE_PHYS_ADDR 0xFE200000 // Example physical address #define DEVICE_REG_SIZE 0x1000 // Size of mapped region static void __iomem *device_base; static int __init mmio_init(void) { u32 value; // Map the physical device address to kernel virtual address space device_base = ioremap(DEVICE_PHYS_ADDR, DEVICE_REG_SIZE); if (!device_base) { printk(KERN_ERR "Failed to ioremap device memory\n"); return -ENOMEM; } // Read a 32-bit value from offset 0x00 value = readl(device_base); printk(KERN_INFO "Read value 0x%08x from device register\n", value); // Write a 32-bit value to offset 0x00 writel(0xAABBCCDD, device_base); printk(KERN_INFO "Wrote value 0xAABBCCDD to device register\n"); return 0; } static void __exit mmio_exit(void) { if (device_base) iounmap(device_base); printk(KERN_INFO "MMIO example exiting\n"); } module_init(mmio_init); module_exit(mmio_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Device Driver Course"); MODULE_DESCRIPTION("Example of memory-mapped I/O using ioremap, readl, and writel");
Thanks for your feedback!