Linux设备驱动之I/O端口与I/O内存

本文探讨了统一编址与独立编址两种I/O编址方式,详细介绍了物理地址、总线地址及虚拟地址的概念,并针对Linux系统下I/O端口与I/O内存的操作方法进行了说明。

在 Linux 设备驱动开发中,​I/O 端口I/O 内存是两种与硬件设备通信的核心机制。它们对应不同的硬件访问方式,适用于不同的设备类型(如传统 ISA 设备或现代 PCI 设备)。以下是两者的详细对比及使用方法。


1. I/O 端口(Port I/O)​

基本概念
  • 适用场景:传统 ISA 设备或需要直接通过端口号访问的硬件(如串口、PS/2 控制器)。
  • 特点
    • 通过独立的 I/O 地址空间访问(x86 架构的 in/out 指令)。
    • 地址范围通常较小(0x0000 ~ 0xFFFF)。
    • 需要申请端口资源,防止冲突。
操作流程
  1. 申请端口资源

    #include <linux/ioport.h>
    
    struct resource *request_region(unsigned long start, unsigned long len, const char *name);
    • 示例
      if (!request_region(0x3F8, 8, "my_serial")) {
          printk(KERN_ERR "Failed to request I/O ports\n");
          return -EBUSY;
      }
  2. 端口读写函数

    • 8 位操作
      unsigned char inb(unsigned int port);
      void outb(unsigned char value, unsigned int port);
    • 16 位操作
      unsigned short inw(unsigned int port);
      void outw(unsigned short value, unsigned int port);
    • 32 位操作
      unsigned int inl(unsigned int port);
      void outl(unsigned int value, unsigned int port);
  3. 释放端口资源

    void release_region(unsigned long start, unsigned long len);
代码示例
// 向串口端口 0x3F8 写入数据
outb('A', 0x3F8);

// 从端口 0x3F8 读取数据
unsigned char data = inb(0x3F8);

2. I/O 内存(Memory-Mapped I/O)​

基本概念
  • 适用场景:现代 PCI 设备、SoC 外设等,通过将硬件寄存器映射到内存地址空间访问。
  • 特点
    • 硬件寄存器映射到系统内存地址空间。
    • 支持大范围地址访问。
    • 需要映射到内核虚拟地址才能访问。
操作流程
  1. 申请内存资源

    struct resource *request_mem_region(resource_size_t start, resource_size_t len, const char *name);
  2. 映射到内核虚拟地址

    void __iomem *ioremap(phys_addr_t offset, size_t size);
  3. 访问内存映射寄存器

    • 使用内核提供的访问函数(避免直接指针操作):
      • 8 位操作
        unsigned char ioread8(const void __iomem *addr);
        void iowrite8(u8 value, void __iomem *addr);
      • 16 位操作
        unsigned short ioread16(const void __iomem *addr);
        void iowrite16(u16 value, void __iomem *addr);
      • 32 位操作
        unsigned int ioread32(const void __iomem *addr);
        void iowrite32(u32 value, void __iomem *addr);
  4. 解除映射并释放资源

    void iounmap(void __iomem *addr);
    void release_mem_region(resource_size_t start, resource_size_t len);
代码示例
// 1. 申请内存资源(假设设备寄存器物理地址为 0xFED00000)
if (!request_mem_region(0xFED00000, 0x100, "my_device")) {
    return -EBUSY;
}

// 2. 映射到内核虚拟地址
void __iomem *regs = ioremap(0xFED00000, 0x100);
if (!regs) {
    release_mem_region(0xFED00000, 0x100);
    return -ENOMEM;
}

// 3. 读写寄存器
u32 value = ioread32(regs + 0x10);  // 读取偏移 0x10 处的 32 位寄存器
iowrite32(0x12345678, regs + 0x10); // 写入数据

// 4. 清理
iounmap(regs);
release_mem_region(0xFED00000, 0x100);

3. 对比与选择

特性I/O 端口I/O 内存
地址空间独立的 I/O 地址空间系统内存地址空间
适用设备传统 ISA 设备现代 PCI/PCIe、SoC 外设
操作函数inb()/outb() 等ioread8()/iowrite8() 等
性能较低(需通过端口指令)较高(直接内存访问)
跨平台兼容性x86 架构为主通用(ARM、x86 等均支持)

选择原则

  • 优先使用 ​I/O 内存​(现代设备普遍支持)。
  • 仅在传统设备(如旧 ISA 设备)必须时使用 ​I/O 端口

4. 高级用法

​(1) 内存屏障(Memory Barriers)​

确保硬件寄存器访问顺序:

iowrite32(0x55AA, regs + CTRL_REG);  // 写入控制寄存器
mb();  // 内存屏障,确保写入完成
​(2) 直接指针访问(慎用)​

仅在内核明确允许时使用:

u32 __iomem *reg = (u32 __iomem *)regs;
writel(0x12345678, reg + 0x10);  // 等效于 iowrite32()
​(3) 用户空间映射(mmap)​

允许用户空间直接访问设备内存:

// 驱动中的 mmap 实现
static int mydev_mmap(struct file *filp, struct vm_area_struct *vma) {
    unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
    unsigned long pfn = virt_to_phys(regs_base + offset) >> PAGE_SHIFT;
    return remap_pfn_range(vma, vma->vm_start, pfn, vma->vm_end - vma->vm_start, vma->vm_page_prot);
}

5. 调试与工具

​(1) 查看资源分配
  • I/O 端口
    cat /proc/ioports
  • I/O 内存
    cat /proc/iomem
​(2) 直接读写工具
  • I/O 端口
    sudo setpci -s 00:02.0 0x10.L=0x12345678  # 修改 PCI 配置空间
  • I/O 内存
    sudo busybox devmem 0xFED00000 32  # 读取物理地址 0xFED00000 的 32 位值

6. 常见问题

​(1) 资源冲突
  • 表现request_region 或 request_mem_region 失败(返回 EBUSY)。
  • 解决:检查 /proc/ioports 和 /proc/iomem,确保地址未被占用。
​(2) 地址映射失败
  • 原因:物理地址未正确映射或超出范围。
  • 验证:使用 iounmap 前检查 ioremap 返回值是否为 NULL
​(3) 字节序问题
  • 注意:某些设备使用大端字节序,需使用 __raw_readl() 或字节序转换函数:
    u32 value = __raw_readl(regs);  // 直接读取,不进行字节序转换

总结

  • I/O 端口:通过 in/out 指令操作独立地址空间,适用于传统设备。
  • I/O 内存:将硬件寄存器映射到内存地址空间,通过 ioread/iowrite 访问,适用于现代设备。
  • 核心步骤:资源申请 → 地址映射 → 安全访问 → 清理释放。
  • 调试工具/proc/ioports/proc/iomemdevmem

正确使用 I/O 端口和 I/O 内存,是驱动与硬件交互的基础能力。

参考:

  1. linux 设备驱动编程 i o,Linux设备驱动之I/O端口与I/O内存-优快云博客 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浩瀚之水_csdn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值