PCI总线是目前应用最广泛的计算机总线标准,而且是一种兼容性最强,功能最全的计算机总线。 而linux作为一种开源的操作系统,同时也为PCI总线与各种新型设备互联成为可能。尤其被现在的异构计算GPU/FPGA、软硬结合新的方向广泛运用。

一、PCI设备和驱动概述

应用程序位于用户空间,驱动程序位于内核空间。linux系统规定,用户空间不可以直接调用内核函数,所以必须经过系统调用,应用程序才可以调用驱动程序的函数。另外应用程序通过系统调用去调用驱动程序的函数,还有一个前提就是驱动程序必须留有接口,这里的接口就是ops函数的操作集合。

::: hljs-center

                 1702014908899.png

:::

  驱动最终通过与文件系统相关的系统调用或C库函数(本质也是基于系统调用)被访问,而设备驱动的结构也是为了迎合应用程序,提供给应用程序的SDK API。

sys:linux设备驱动模型中的总线、驱动和设备都可以在sysfs文件系统中找到相应的节点。当内核检测到系统中出现新的设备后,内核会在sysfs文件系统中为设备生成一项新的记录。

sysfs是一个虚拟的文件系统,它可以产生一个包括<font color="red">所有系统硬件</font>的层级视图,与<font color=red>提供进程和状态的proc文件系统</font>十分类似。可以让用户空间存取,向<font color=red>用户空间导出内核数据结构以及它的属性</font>。

在linux内核中,设备和驱动是分开注册的,注册1个设备的时候,并不需要驱动已经存在,而1个驱动被注册的时候,也不需要对应的设备被注册。设备和驱动各自涌入内核,而每个设备和驱动涌入内核的时候,都会寻找另外一半^_^。而正是bus_type的match()成员函数将两者绑定在一起。

简单来说,设备和驱动就是红尘的男女,而bus_type的match()则是牵引红线的月老,它可以识别什么设备与什么驱动,是配对的。一旦成功,xxx_driver的<font color=red>probe</font>就被执行。

二、PCI总线描述

::: hljs-center

          1702019935166.png

:::

PCI是<font color=red>CPU和外围设备通信的高速传输总线</font>。普通PCI总线带宽一般为132MB/s或者264MB/s。

PCI总线体系结构是一种层次式的体系结构,PCI桥设备占据重要的地位,它将父总线与子总线连接在一起,从而使整个系统看起来像一颗倒置的树形结构。

三、PCI配置空间

PCI有3种地址空间:PCI配置空间、PCI/IO空间、PCI内存地址空间。

1. PCI配置空间

::: hljs-center

          1702020773321.png

:::

deviceID和vendorID寄存器:由pcisig分配,只读,vendorID代表pci设备的厂商,deviceID代表厂商的具体设备; status:设备状态字; command:设备状态字; base address register:决定pci/pcie设备空间映射到系统具体位置的寄存器,IO和memory映射两种;

2. PCI/IO空间(PIO)

pio端口的编址是<font color=red>独立于系统的地址空间,其实是一段地址区域,所有外设的地址都映射到这段区域中</font>。不同外设的IO端口不同,访问IO端口需要特殊的IO指令,OUT/IN,out用于write操作,in用于read操作; IO地址空间有限;

3. PCI内存地址空间(MMIO)

io内存就是把寄存器的地址空间直接映射到系统的地址空间,系统地址空间保留一段内存用于MMIO的映射。

上述的方案只适用于外设和内存进行小数据量的传输时,假如进行大数据量的传输,PIO以字节为单位的传输不用说了,MMIO虽然进行了内存映射,但是范围相对于大量的数据,不值得一提,所以即使采用了MMIO仍然满足不了需要,会让吧CPU大部分时间处理繁琐的映射,极大浪费CPU资源。在这种情况下,引入了DMA,由DMA控制器控制,完成后中断通知CPU,极大解放了CPU。后面的文章会接收DMA。

四、PCI设备驱动组成

PCI本质上就是一种总线,具体的PCI设备可以是字符设备、网络设备、USB等,所以PCI设备驱动应该包括两个部分:

  1. PCI通用驱动
  2. 根据实际需要的设备驱动 根据需求的设备驱动是最终目的,PCI驱动只是手段帮助设备驱动达到最终目的而已。换句话,PCI设备驱动不仅实现PCI驱动还要包括具体需求的设备驱动。 ::: hljs-center

          1702024393312.png

:::

PCI驱动注册与注销:

int pci_register_driver(struct pci_driver *driver);
int pci_unregister_driver(struct pci_driver *driver);

PCI_driver结构体:

struct pci_driver {
    struct list_head node;
    char *name; /* 驱动程序的名称 */
    struct module *owner;
    /* 指向设备驱动程序感兴趣的设备ID的一个列表,包括:
     * 厂商ID、设备ID、子厂商ID、子设备ID、类别、类别掩码、私有数据
     */
    const struct pci_device_id *id_table;
    /* 指向一个函数对于每一个id_table中的项匹配的且未被其他驱动程序处理的设备,
     * 在执行pci_register_driver时候调用此函数或者如果是以后插入的一个新设备的话,只要满足上述条件也会调* 用此函数
     */
    int (*probe) (struct pci_dev *dev, const struct pci_device_id *id);
    /* 指向一个函数当驱动函数程序卸载或者被该驱动程序管理的设备被卸下的时候,调用此函数
     */
    int (*remove) (struct pci_dev *dev);
    int (*save_state) (struct pci_dev *dev, u32 state); /* 设备被挂起之前保存的相关状态 */
    int (*suspend) (struct pci_dev *dev, u32 state); /* 挂起设备使之处于节能状态 */
    int (*resume) (struct pci_dev *dev); /* 唤醒挂起的设备 */
    /* 使设备能够从挂起态产生唤醒事件*/
    int   (*enable_wake) (struct pci_dev *dev, u32 state, int enable);
};

五、未完待续

Linux下PCI设备驱动开发详解(二),将介绍具体的函数实现。