《Linux设备驱动程序》第十二章 PCI驱动程序读书笔记

June 29, 2012 / Driver, Linux, PCI, Reading Note

* PCI寻址
    * 每个PCI外设由一个总线编号、一个设备编号及一个功能编号来标识
    * PCI规范允许单个系统拥有高达256个总线,但是256个总线对于许多大型系统而言是不够的,因此,Linux目前支持PCI域
    * 每个PCI域可以拥有最多256个总线
    * 每个总线上可支持32个设备,每个设备可以是多功能板,最多可有八种功能
    * 每种功能都可以在硬件级由一个16位的地址来标识
    * 为Linux编写的设备驱动程序可以使用一种特殊的数据结构(pci_dev)来访问设备
    * 当前的工作站一般配置有至少两个PCI总线,在单个系统中插入多个总线,可通过桥(bridge)来完成,它是用来连接两个总线的特殊PCI外设
    * PCI系统的整体布局组织为树型,其中每个总线连接到上一线总线,直到树根的0号总线
    * lspci
    * proc/pci
    * /proc/bus/pci/
        * 查看PCI设备清单和设备的配置寄存器
    * /sys/bus/pci/devices
    * 每个外设板的硬件电路对如下三种地址空间的查询进行应答
        * 内存位置
        * I/O端口
        * 配置寄存器
    * 前两种地址空间由同一PCI总线上的所有设备共享
    * 配置空间利用了地理寻址
    * 配置查询每次只对一个槽寻址
    * 每个PCI槽有四个中断引脚,每个设备功能可使用其中的一个
    * PCI总线中的I/O空间使用32位地址总线,而内存空间可通过32位或64位地址来访问
* 引导阶段
    * 当PCI设备上电时,硬件保持未激活状态
        * 不会有内存和I/O端口映射到计算机的地址空间
        * 禁止中断报告
    * 每个PCI主板均配备有能够处理PCI的固件,称为BIOS、NVRAM或PROM,固件通过读写PCI控制器中的寄存器,提供了对设备配置地址空间的访问
    * 系统引导时,固件在每个PCI外设上执行配置事务,以便为它提供的每个地址区域分配一个安全的位置
* 配置寄存器和初始化
    * 所有的PCI设备都有至少256字节的地址空间
        * 前64字节是标准化的,而其余的是设备相关的
    * PCI寄存器始终是小端的
    * 驱动程序编写者在访问多字节的配置寄存器时,要十分注意字节序,因为能够在PC上工作队的代码到其他平台上可能就无法工作
    * vendorID、deviceID和class是常用的三个寄存器
        * vendorID
            * 16位寄存器,用于标识硬件制造商
            * PCI Special Interest Group维护有一个全球的厂商编号注册表,制造商必须申请一个唯一编号并赋于它们的寄存器
        * deviceID
            * 16位寄存器,由制造商选择,该ID通常和厂商ID配对生成生成一个唯一的32位硬件设备标识符
        * class
            * 每个外部设备属于某个类
            * 16位寄存器,高8位标识了“基类(base class)”或者组
        * subsystem vendorID、subsystem deviceID
            * 这两个字段可用来进一步识别设备
    * struct pci_device_id用于定义驱动程序支持的不同类型的PCI设备列表
        * __u32 vendor;
        * __u32 device;
            * 以上两字段指定了设备的PCI厂商和设备ID,如果驱动程序可以处理任何厂商或者设备ID,这些字段应该使用值PCI_ANY_ID
        * __u32 subvendor;
        * __u32 subdevice;
            * 以上两字段指定设备的PCI子系统厂商和子系统设备ID,如果驱动程序可以处理任何类型的子系统ID,这些字段应该使用值PCI_ANY_ID
        * __u32 class;
        * __u32 class_mask;
            * 这两个值使驱动程序可以指定它支持一种PCI类(class)设备,如果驱动程序可以处理任何类型的子系统ID,这些字段应该使用值PCI_ANY_ID
        * kernel_ulong_t driver_data
            * 用来保存PCI驱动程序用于区分不同设备的信息
    * 初始化
        * PCI_DEVICE(vendor, device)
        * PCI_DEVICE_CLASS(device_class, device_class_mask)
* MODULE_DEVICE_TABLE
    * MODULE_DEVICE_TABLE(pci, i810_ids);
        * 创建一个名为__mod_pci_device_table的局部变量,指向struct pci_device_id数组
        * 在内核构建过程中,depmod程序在所有的模块中搜索符号__mod_pci_device_table
        * 如果找到了该符号,它把数据从该模块中抽出,添加到文件/lib/modules/KERNEL_VERSION/modules.pcimap中
        * 当内核告知热插拔系统一个新的PCI设备已经被发现时,热插拔系统使用modules.pcimap文件来寻找要装载的恰当的驱动程序
* 注册PCI驱动程序
    * 所以的PCI驱动程序都必须创建的主要结构休是struct pci_driver
        * const char *name;
            * 驱动程序的名字
            * 当驱动程序运行在内核中时,它会出现在sysfs的/sys/bus/pci/drivers/下面
        * const struct pci_device_id *id_table
        * int (*probe) (struct pci_dev *dev, const struct pci_device_id *id);
            * 指向PCI驱动程序中的探测函数的指针同,当PCI核心有一个它认为驱动程序需要控制的struct pci_dev时,就会调用该函数
        * void (*remove) (struct pci_dev *dev);
            * 指向一个移除函数的指针,当struct pci_dev被从系统中移除,或者PCI驱动程序正在从内核中卸载时,PCI核心调用该函数
        * void (*suspend) (struct pci_dev *dev, u32 state);
            * 指向一个恢复函数的指针,当struct pci_dev被恢复时PCI核心调用该函数
    * int pci_register_driver(struct pci_driver *drv);
        * 注册成功返回0,否则,返回一个负的错误编号
    * void pci_unregister_driver(struct pci_driver *drv);
    * 在支持PCI热插拔的系统或者CardBus系统上,PCI设备可以在任何时刻出现或者消失
    * 2.6内核允许在驱动程序被装载之后动态地分配新的PCI ID给它
* 老式PCI探测
    * struct pci_dev *pci_get_device(unsigned int vendor, unsigned int device, struct pci_dev *from);
    * struct pci_dev *pci_get_subsys(unsigned int vendor, unsigned int device, unsigned int ss_vendor, unsigned int ss_device, struct pci_dev *from);
    * struct pci_dev *pci_get_slot(struct pci_bus *bus, unsigned int devfn);
* 激活PCI设备
    * int pci_enable_device(struct pci_dev *dev);
* 访问配置空间
    * 在驱动程序检测到设备之后,通常需要读取或写入三个地址空间
        * 内存
        * 端口
        * 配置
    * <linux/pci.h>
        * int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);
        * int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);
        * int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);
        * int pci_write_config_byte(struct pci_dev *dev, int where, u8 *val);
        * int pci_write_config_word(struct pci_dev *dev, int where, u16 *val);
        * int pci_write_config_dword(struct pci_dev *dev, int where, u32 *val);
        * int pci_bus_read_config_byte (struct pci_bus *bus, unsigned int devfn, int where, u8 *val);
        * int pci_bus_read_config_word (struct pci_bus *bus, unsigned int devfn, int where, u16 *val);
        * int pci_bus_read_config_dword (struct pci_bus *bus, unsigned int devfn, int where, u32 *val);
        * int pci_bus_write_config_byte (struct pci_bus *bus, unsigned int devfn, int where, u8 *val);
        * int pci_bus_write_config_word (struct pci_bus *bus, unsigned int devfn, int where, u16 *val);
        * int pci_bus_write_config_dword (struct pci_bus *bus, unsigned int devfn, int where, u32 *val);
* 访问I/O和内存空间
    * 一个PCI设备可实现多达6个I/O地址区域
    * 一个接口板通过配置寄存器报告其区域的大小和当前位置,它们的符号名称为PCI_BASE_ADDRESS_0到PCI_BASE_ADDRESS_5
    * 在内核中,PCI设备的I/O区域已经被集成到通用资源管理,我们无需访问配置变量来了解设备被映射到内存或I/O空间的何处
    * unsigned long pci_resource_start(struct pci_dev *dev, int bar);
    * unsigned long pci_resource_end(struct pci_dev *dev, int bar);
    * unsinged long pci_resource_flags(struct pci_dev *dev, int bar);
    * <linux/ioport.h>
        * IORESOURCE_IO
        * IORESOURCE_MEM
        * IORESOURCE_PREFETCH
        * IORESOURCE_READONLY
    * 中断号保存在配置寄存器60(PCI_INTERRUPT_LINE)中,该寄存器为一个字节宽
    * 如果设备不支持中断,寄存器61(PCI_INTERRUPT_PIN)是0
* 硬件抽象
    * 在PCI管理中,唯一依赖于硬件的操作是读取和写入配置寄存器
    * 用于配置寄存器访问的相关结构仅包含2个字段
        * struct pci_ops
            * int (*read) (struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val);
            * int (*write) (struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val);
    * 该结构在<linux/pci.h>中定义,并由drivers/pci/pci.c使用,后者定义了实际的公共函数