PCIE(RIFFA)驱动解读
+ -

PCIE(RIFFA)驱动解读(一)

2025-08-05 1 0

解读RIFFA驱动

  • 基于Linux

导言

去年便计划给大家出个关于PCIe驱动的文章,因为种种原因没能继续写,本次发一个关于RIFFA驱动的系列文章,文章很长,本系列一共23259个字,文章来源西安电子科技大学锤王。

感谢西安电子科技大学锤王投稿,文章版权归原作者

**目录** 

第一章 RIFFA仓库地址 

第二章 体系结构概述 

2.1 冯·诺依曼结构和哈佛结构

2.2 现代计算机执行过程概述 

2.3 Linux启动过程概述

2.4 用户态和内核态

2.5 CPU访存过程概述 

2.6 高速缓存概述 

2.7 中断概述 [9]

2.7.1 单片机中断系统

2.7.2 MPU中断系统 

2.7.3 Linux中断映射简述

2.8 指令集概述

第三章 字符设备驱动框架 

3.1 Linux设备分类

3.2 设备号

3.3 字符设备操作函数结构体

3.5 注册字符设备

3.6 驱动在Linux中的表现形式 

3.7 自动创建设备节点

3.8 模块插入/卸载函数的关联

第四章 PCI设备驱动框架

4.1 pci_dev结构体 

4.2 PCI设备驱动的ID号设置

4.3 probe函数和它的执行条件

4.4 pci_driver结构体

4.5 PCI驱动在init函数中要做的事

4.6 PCI驱动初始化流程

第五章 Linux并发与竞态概述

5.1 并发与竞争

5.2 原子操作

5.3 自旋锁

5.3.1 自旋锁的优缺点

5.3.2 自旋锁的死锁

5.4 信号量

5.4.1 信号量

5.4.2 读写信号量

5.5 互斥体

第六章 Linux中断处理与阻塞I/O概述

6.1 Linux中断处理

6.1.1 中断服务函数的主旨

6.1.2 中断上下文

6.2 阻塞I/O

6.2.1 阻塞I/O与非阻塞I/O 

6.2.2 等待队列简介 

6.2.3 等待队列头和等待队列项 

6.2.4 将队列项添加/移除等待队列头

6.2.5 休眠与唤醒

第七章 Linux内存管理与DMA映射概述

7.1 进程地址空间布局

7.2 页表概述

7.3 kmalloc和vmalloc概述 

7.4 缺页中断概述

7.5 DMA的类型

7.6 一致性DMA和流式DMA

7.7 Linux中建立一致性DMA

7.8 Linux中建立SGDMA 

7.8.1 SG缓冲区的组织形式

7.8.2 SG缓冲区的建立流程

第八章 RIFFA驱动分析

8.1 RIFFA驱动文件构成

8.2 RIFFA驱动的消息队列

8.3 RIFFA驱动创建字符设备

8.4 RIFFA驱动初始化收发通道

8.5 RIFFA驱动的probe函数

8.6 RIFFA驱动的中断回调函数

8.7 RIFFA驱动建立SG缓冲区

8.8 RIFFA驱动读通道函数

8.9 RIFFA驱动写通道函数 

8.10 RIFFA驱动ioctl函数

Part1RIFFA仓库地址

RIFFA是一个用于PCIe设备的可重用集成框架,它的Github仓库如下:

https://github.com/KastnerRG/riffa.git

本文档旨在从基本概念开始逐步剖析RIFFA项目中基于Linux的驱动程序。

Part2体系结构概述

12.1 冯·诺依曼结构和哈佛结构

计算机体系中有两种耳熟能详的基本结构,冯·诺依曼结构和哈佛结构。

冯·诺依曼结构的基本思想是将数据存储和程序存储都使用一块存储器来完成,不过缺点是可能会产生结构冒险,即CPU执行一些程序时需要从程序存储器中获取指令,同时也需要从数据存储器中获取数据,但存储器是共用的,每次只能取到一种数据,这就产生了结构冒险。

哈佛结构将数据和程序分别用两个存储器来存储,这样就解决了结构冒险,但缺点是需要两块存储器,成本提高了。

22.2 现代计算机执行过程概述

现代处理器一般使用混合结构,外部使用同一个存储器存储数据段和代码段(内存条),内部将数据和代码分开成两个存储器存储(I-Cache和D-Cache)。

现代计算机一般都有内存、硬盘这两种存储器。硬盘主要存储文件,当需要执行某个程序时,CPU会把该文件读到内存中运行,此时内存就是那个共用的主存储器,CPU访存时若Cache未命中,就会将代码和数据分别加载到I-Cache和D-Cache上然后运行。

32.3 Linux启动过程概述

以ARM为例,在一个运行着Linux的设备中,硬盘上主要存储着Linux内核镜像、根文件系统和设备树文件。在设备上电时首先会从硬盘读取u-boot运行,u-boot是一种bootloader程序,负责完成必要设备的初始化(比如DDR、PLL等)、堆栈的初始化、读取设备树的dtb文件并存入内存,并将启动参数传递给Linux内核(其中就包括dtb文件在内存中的地址,后面Linux可以通过这个地址找到设备树),引导Linux启动,u-boot寿命结束后就会从硬盘中读取内核镜像并依据u-boot传递的参数启动。

而X86的这一启动过程由主板上的BIOS芯片完成,X86没有设备树文件,设备的枚举也由BIOS完成(包括PCI设备)。

42.4 用户态和内核态

现代大型操作系统一般都分为内核态和用户态,用户态是指用户可以操作到的空间,比如在Ubuntu中使用终端、编写C语言应用程序等我们看得到的操作均是在用户态下运行的。内核态是对用户不透明的,用户无法直接操作内核空间的事物。

之所以将系统分为用户态和内核态,是因为内核态可以直接操作外设及内存,而些操作对于用户来说是非常危险的操作。在大部分CPU体系中,均将指令集划分了一些特权级别,比如Cortex-A9将工作模式分为如下几种:

170530879746

可以看到,大部分用户操作均在用户模式下进行,而复位、软中断等操作需要在特权模式下进行,这种模式供操作系统使用。如果允许用户随意使用特权指令,可能会带来意想不到的灾难性后果,比如用户可以使用内存清除指令清除内存空间进而导致系统崩溃。

所以需要将用户和内核划分开来,当然用户态也需要能够切换到内核态,毕竟用户想要操作什么东西都需要执行驱动程序或是调用内核来完成。从用户态切换到内核态一般通过系统调用和中断,系统调用就是指在用户态应用程序中调用操作系统提供的接口(比如open、close函数),系统调用属于一种软中断,软中断会导致CPU陷入内核;同样,中断也会让CPU陷入内核来执行中断回调函数。

这些特权级别切换在CPU级别都是有对应的汇编指令的,执行汇编指令即可完成切换,当然这些汇编程序都是已经内嵌到Linux内核源码中的,切换过程用户不必关心。

52.5 CPU访存过程概述

CPU访存过程的概念图如下所示:

170537227507

MMU(内存管理单元)的作用是进行虚拟地址和物理地址间的映射,记录映射结果的组织结构叫做页表(Page),这个将在后面Linux内存管理中讲述。

TLB(转址旁路缓存)的作用类似于Cache,但是配合MMU使用的,其作用是当CPU访问MMU前首先会访问TLB,如果TLB中没有存储对应的页表项,则再访问MMU,加这样一个TLB的目的就是因为访问页表太慢了,尤其现代操作系统中都是多级页表(Linux页表为3~5级)。

所以当CPU想要访问内存时,首先将虚拟地址(由页号和页内偏移构成)传递给TLB,若TLB命中,则将其中存储的对应的物理页号传递给Cache;若TLB未命中,则访问该进程的页表,获取到对应的物理页号再传递给Cache。

关于虚拟地址的构成将在Linux内存管理中描述。

62.6 高速缓存概述

Cache(高速缓存)在多核处理器中的组织形式如下图所示:
17054431766

现代处理器一般分为三级缓存,L1~L3缓存容量逐步变大,L1和L2都是和Core绑定的,L3是多核共享的。缓存越小,地址解码越快,读写速度也就越快,但容量低也意味着容易未命中,当L1未命中时会读取L2,L2失效会读取L3,若所有Cache均失效则访存。这种多层次存储结构的好处是能够减小Cache未命中而访问内存所带来的较大代价。

Cache的组织形式分为直接映射、组相联映射和全相联映射,读写的单元是Cache line;

Cache的写策略分为写直通、写缓存、写返回等,广泛使用的是写返回策略,这里就不展开讲了。

72.7 中断概述

2.7.1 单片机中断系统

单片机的中断较为简易,以Cortex-M系列为例,其中断控制器名为NVIC,负责使能中断、设置中断优先级、中断仲裁等。

单片机在汇编启动文件中维护了一张中断向量表,中断向量表在链接的起始地址处,也就是单片机执行代码首先要执行中断向量表。需要注意的是,中断向量表不是一个像表格似的文件,它就是一段汇编程序,映射了中断号和中断服务函数的入口地址而已。

下面看一下恩智浦公司的LPC1114芯片的中断向量表,它是Cortex-M0内核的:

单片机执行代码首先就执行中断向量表的第一句,这里是设置sp指针(堆栈),然后跳转到复位中断的服务函数Reset_Handler,在里面执行一些初始化操作然后调用C语言的main( )函数,从此进入C语言执行环境。

中断向量表是按顺序排的,这个大体顺序是ARM公司定好的,外设中断部分的顺序是SoC公司定好的,规则就是按中断号由小到大排列,当发生一个外设中断时,可通过中断号找到中断向量的地址,而中断向量本身又是中断服务函数的入口地址,进而执行中断服务函数。

可以看到,在单片机里,每个外设都是有中断向量的,因为单片机的中断比较少。也就是每个外设中断都对应一个中断服务函数,而不需要查看是什么外设的中断。
170551867752

2.7.2 MPU中断系统

而在MPU(微处理器)中就与上面不太一样了,以Cortex-A7为例,先介绍一下GIC(通用中断控制器)。

GIC结构框图如下所示:左侧是中断源输入,右侧是给Core发送的中断信号。

  1. SPI(共享中断),共享是指所有Core共享的中断,外部中断都属于共享中断,例如GPIO、UART中断等。

  2. PPI(私有中断),私有中断就是Core独有的,因为每个Core肯定有自己的中断。

  3. SGI(软件中断),由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。

170557230279

GIC分为两个逻辑块,Distributor和CPU Interface。

Distributor负责处理各个中断事件的分发问题,也就是中断事件应该发送到哪个 CPU Interface 上去。分发器收集所有的中断源,可以控制每个中断的优先级,它总是将优先级最高的中断事件发送到 CPU 接口端,并将其他中断信号挂起并缓存起来等待下次发送。

CPU Interface和Core连接,主要负责发送优先级最高的那个中断通知CPU、CPU通知GIC中断处理完成、和CPU进行一些交互等。最终GIC通过4根线通知CPU有中断:VFIQ、VIRQ、FIQ、IRQ;带V的都是和虚拟化相关的,FIQ是快速中断,IRQ才是平时用的最多的中断线。

Cortex-A7也有中断向量表,不过可不像单片机一样好几十行,Cortex-A7只有8个中断向量,如下表所示:

170605838598

因为MPU的中断太多了,Cortex-A7的GIC-v2可是支持1020个中断。介绍一下常用的:

  1. 软中断:由SWI指令引起的中断,Linux的系统调用会用SWI指令来引起软中断,通过软中断来陷入到内核空间。

  2. 复位中断:CPU复位以后就会进入复位中断,可以在复位中断服务函数里面做一些初始化工作,比如初始化SP 指针、DDR等。

  3. IRQ:外部中断,外设的中断经过GIC仲裁后都走这一条线。

MPU的中断向量表依然是放在链接地址开头处,上电执行复位中断,然后进入main( )函数。如果此时有一个外设中断通过GIC的仲裁由IRQ线通知CPU,那么CPU会跳转到链接地址+0x18处访问IRQ中断向量,然后由中断向量跳转到IRQ中断服务函数中,因为外设都走的是共享中断,通知CPU只有一条IRQ线,所以进入中断服务函数首先要做的就是判断是什么中断,这就需要CPU去读取GIC的一个寄存器,这个寄存器里存这当前从IRQ送出去的中断的中断号,根据中断号就知道是什么中断了。

2.7.3 Linux中断映射简述

上面说的中断号都是hwirq(硬件中断号),这些中断号是和硬件相关的,是不能改变的,而Linux中的所谓中断号(irq)是虚拟的,是和硬件没关系的。这么做主要是因为现在的大型SoC里面可能不止一个中断控制器,多个中断控制器间可能有中断号的冲突,Linux为了区分是哪个中断控制器的哪个中断,就将它们映射为虚拟中断号了。

82.8 指令集概述

指令集常见的包括ARM、RISC-V、MIPS、8051、X86等。

指令集再细分出指令集架构(ISA),比如X86中的i386、i686;ARM中的ARMv7、Thumb;RISC-V中的RV32-I、RV64-IA等,ISA是具体的指令、ABI(二进制接口)、内存划分等细节性规定的集合。

本文档将使用ARM和X86作为例子讲述。

本文转自公众号《 亦安的数字小站》,版权归西安电子科技大学锤王。

0 篇笔记 写笔记

PCIE(RIFFA)驱动解读(一)
解读RIFFA驱动基于Linux导言去年便计划给大家出个关于PCIe驱动的文章,因为种种原因没能继续写,本次发一个关于RIFFA驱动的系列文章,文章很长,本系列一共23259个字,文章来源西安电子科技大学锤王。感谢西安电子科技大学锤王投稿,文章版权归原作者**目录** 第一章 RIF......
PCIE(RIFFA)驱动解读(二)
解读RIFFA驱动基于Linux感谢西安电子科技大学锤王投稿。Part3字符设备驱动框架13.1 Linux设备分类Linux将设备主要分为三种:字符设备(char device)、块设备(block device)和网络设备(net device)。字符设备一般指串并行I/O设备,比如串......
PCIE(RIFFA)驱动解读(三)
解读RIFFA驱动基于Linux感谢西安电子科技大学锤王投稿。Part4PCI设备驱动框架14.1 pci_dev结构体对X86来说,上电时由BIOS枚举PCI设备,此时PCI设备处于未激活状态,仅能访问其配置空间,内存和I/O空间还没被映射。BIOS从Host主桥/Root Complex......
PCIE(RIFFA)驱动解读(四)
Part5Linux并发与竞态概述15.1 并发与竞争Linux是个多任务操作系统,肯定会发生同一时间内两个任务操作同一内存或同一设备的情况,这就是竞争,在大部分情况下这种竞争是不被允许的。能够被多个任务和中断访问到的资源称为共享资源, 对于共享资源的访问需要保护。Linux中的并发主要有以下几个......
PCIE(RIFFA)驱动解读(五)
Part6Linux中断处理与阻塞I/O概述16.1 Linux中断处理6.1.1 中断服务函数的主旨在单片机中经常用到中断,编写中断服务函数的主旨就是”执行越快越好”因为中断的优先级比普通任务优先级高,如果中断长时间占用CPU,其他任务就得不到调度;或者本次中断还没执行完下一个中断又来了,那CPU......
PCIE(RIFFA)驱动解读(六)
Part7Linux内存管理与DMA映射概述17.1 进程地址空间布局在Linux中每个进程都拥有独立的虚拟地址空间,每个虚拟地址空间都可以认为自己拥有全部内存,比如32位系统中的进程就认为自己拥有4GB的内存。Linux把一个进程空间中划分为用户空间和内核空间两段。在32位系统中给用户空间划分3G......
PCIE(RIFFA)驱动解读(七)
本系列到此结束,一共7篇,8个小章节,再次感谢西安电子科技大学锤王的投稿。Part8RIFFA驱动分析18.1 RIFFA驱动文件构成RIFFA的Linux驱动文件夹下有6个C源码文件,riffa_driver.c、riffa_driver.h、circ_queue.c、circ_queue.h、......
关注公众号
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

您的支持,是我们前进的动力!