(草稿)从设备到操作系统: 如何编写驱动

组件介绍

  • cpu 内存, 系统总线, pci总线
  • 早期简单设备GPIO直接控制
  • PCI/USB总线

I/O 相关概念

I/O传输方式:

  • PIO(PMIO/MMIO轮询), Interrupt, DMA (中断不一定一定比PIO好)

I/O传输内容:

  • 操作系统组件与驱动交互, 驱动与设备交互
  • 驱动传递给设备命令和数据
  • 文件/流/virtio-mmio的共享内存(1, 2略)

I/O模型:

  • 阻塞
  • 非阻塞
  • 多路复用
  • 信号驱动
  • 异步

驱动程序设计:

  1. 数据结构初始化(VirtIoHeader, 其中包含VirtQueue, 其中包含设备描述符/Avail/Used)
  2. 初始化设备, 设定设备状态
  3. 中断处理例程
  4. 和操作系统组件交互

设备的呈现模式与 probe

  1. x86_64, 个人计算机通常采用 PCI 总线的发现方式
  2. arm/riscv 因为设备多种多样, 采用 device tree, Bootloader(如SBI)负责将dtb放入寄存器交给操作系统
  3. 设备树的 reg 是”总线地址”(对于在 PCI 总线上的设备) 或 直接的物理地址(对于 virtio-mmio 设备), 前者需要 IOMMU 翻译
  4. 解析设备树主要是根据不同设备类型分派到不同处理过程, 主要是拿一下地址, 还有相关信息等

PLIC:

  • 平台级别中断控制器的意义
  • 传来中断时并不清楚是不是给自己的中断, 所以要检查一下www

virtio设备规范:

virtio 之设备描述: 设备特征位与地址空间 virtio 之状态表示: 设备状态域 virtio 只交互机制: 通知+虚拟队列

通知(门铃 doorbell):

虚拟队列 VirtQueue:

  • 描述符表: 存放每个描述符的位置和 next 指针; 很多驱动传达给设备的信息需要多个描述符组成一个链表(命令+数据+结果)
  • 可用环(Avail vring): 存放驱动程序放进去描述符链表的索引, 表示传递的命令, 通过 kick 机制 (写入特定寄存器) 通知设备有新任务
  • 已用环(Used vring): 设备从可用环中取出描述符链表解析并处理, 然后把填好后的描述符索引放入已用环, 中断一下让驱动程序拿结果

在驱动程序操作描述符表, 可用环和通知设备时, 需要设置内存屏障强制控制指令执行顺序, 防止乱序执行导致设备看到错误的信息

VirtIOHeader:

各种寄存器区域, 除了status 和 features 相关的寄存器, 还有存放虚拟队列索引的寄存器, 以及存放描述符表/可用环/已用环的内存位置的寄存器(分为低32和高32位存,为了兼容性和内存对齐), 以及中断相关的寄存器

初始化设备:

读取信息, 按照规范设定设备状态, 开辟所需数据结构的dma内存

Virtio-GPU:

  • header, rect, 实际的显示内存载荷, 总体命令的虚拟队列, 光标命令的虚拟队列, 队列本身的内存载荷, 为了方便处理的 send/recv queue 切片(注意生命周期标注, 必须至少和 VirIOGPU 活的一样长)
  • new 里初始化设备, 分配各种dma内存, 读配置空间
  • 还需要按照规范把实际显存关联到 virtio-gpu 设备上; 主要是发送 ResourceCreate2D 和 ResourceAttackBacking, 然后 SetScanout 把显示资源链接到显示扫描输出上

操作系统的任务:

包装 VirtIOGPU 对象, 加引用计数和锁, 操作显存, flush, 暴露系统调用给用户态程序