写外设驱动前,先分清 PIO、MMIO、DMA 和用户态访问边界
CPU 是电子器件,外设是机电/电磁器件,两边速度差几个量级,连数据格式都未必一致(串行/并行、数字/模拟)。中间那层负责同步速度、设备寻址、控制/状态/数据三类命令、数据缓冲、错误检测和串并转换的硬件,就是 I/O interface。它通常和 CPU 装在同一块板上,外设本体在外面。
现代设备内部往往是一颗小 CPU + 固件 + 一小块内存,对外暴露的接口收敛为一组寄存器:status(只读,查询状态)、command(写命令)、data(搬运数据)。程序与外设的对话,就是读写这三类寄存器。
三条通路:PIO、MMIO、DMA
PIO(端口 I/O):x86 上对应 inb/outb/inw/outw 这类专用指令。端口本质上是设备寄存器映射成的一个整数。通路简单,但 CPU 要参与逐字节搬运,只能忙等。in/out 是特权指令,用户态直接执行会得到 Operation not permitted。Linux 的放行口是 /dev/port,需要 root 或 CAP_SYS_RAWIO。
MMIO(内存映射 I/O):设备寄存器被映射进 CPU 地址空间,普通 load/store 就能读写。好处是可以用 C struct 描述寄存器布局,让编译器做偏移计算。RISC-V、ARM 没有独立端口空间和专用 in/out 指令,基本全走 MMIO;x86 也可以用。内核里标准做法是 ioremap 建立映射后,用 ioread32/iowrite32(或 readl/writel)访问。用户态想直接摸寄存器,常见手段是 mmap:/dev/mem、树莓派的 /dev/gpiomem、PCI resource mmap 都这么干。
DMA:PIO/MMIO 即便再方便,每次访问仍是 CPU 发一次请求、扛一次数据。网卡、磁盘、GPU 这种吞吐型设备不能接受 CPU 当中介。DMA 的工作模式是:CPU 告诉 DMA 控制器数据在内存哪里、要拷多少、发给哪个设备,然后转身去干别的;搬完由 DMA 控制器拉一次中断。内核常用 dma_alloc_coherent 获得一致性 DMA 缓冲。
用户态访问硬件寄存器的边界
- 直接跑 in/out 不现实:x86 的 in/out 是特权指令,open /dev/port 也要求 root / CAP_SYS_RAWIO,否则 EPERM。普通用户态程序读写寄存器,更常见的入口是 mmap 设备地址区间,或对 /dev/gpiomem、/dev/i2c-N 这类专用节点做 mmap/ioctl。
- mmap 前先看地址:映射要 4096 页对齐,长度按页向上取整,且不要越出设备 BAR。先 lspci -v、cat /proc/iomem 确认设备地址区间,别图省事把整个 /dev/mem 都映进来。
- volatile 不是可选:设备寄存器有副作用,普通 C 结构体成员访问可能被编译器优化或重排。Linux 驱动不鼓励裸指针解引用,ioread32/iowrite32/readl/writel 的内部就是 volatile 访问外加必要的内存屏障。
- 不想给地址就给 ioctl:驱动在 file_operations 里实现 ioctl/unlocked_ioctl,把用户请求翻译成寄存器或固件操作。用户态从头到尾只拿一个文件描述符,也能控制设备。
RISC-V 上的移植差异
RISC-V 没有 x86 那套 in/out 指令,也没有 I/O 端口空间,所有外设统一走 MMIO。为了兼容,Linux asm-generic 在多数架构上把 inb/outb 实现为 MMIO 访问。x86 驱动迁到 RISC-V 时,这类调用能编过,但语义已经从端口指令变成内存映射访问;只测过 x86 时,这是最容易被漏掉的隐性差异。
选通路:看数据量和延迟要求
小批量、低延迟的控制动作,轮询寄存器(PIO/MMIO 状态位)直接够用;大批量、重吞吐的传输交给 DMA + 中断;用户态不想碰特权,就通过 ioctl/sysfs 走驱动抽象。反面做法也明确:别为了“显得底层”把安全敏感硬件直接 mmap 给不可信进程,用户态裸映射没有内核的权限与隔离保护。具体某驱动场景要不要开放 mmap,需要按实际设备和安全边界来定。
description:CPU 与外设之间隔着 I/O interface,设备对外表现为 status/command/data 三类寄存器。访问这些寄存器有三条通路:PIO、MMIO、DMA,分别对应特权端口指令、内存映射和 DMA 控制器搬运。文章整理了用户态访问硬件寄存器时的 /dev/port EPERM、mmap 页对齐与越界风险、volatile/内存屏障问题,以及 RISC-V 全 MMIO 带来的 x86 驱动移植差异。
tags:Linux,设备驱动,MMIO,PIO,DMA,RISC-V
keywords:PIO,MMIO,DMA,iormap,inb/outb,readl/writel,/dev/port,/dev/mem,/proc/iomem,dma_alloc_coherent,RISC-V,设备驱动