- PCIE简介
PCIe作为串行总线的一种,它的发展必然和另一种总线架构密不可分:并行总线。
像PCIe接口的显卡、声卡、网卡,都属于功能设备,在PCIe规范中,我们统称为Endpoint(简称EP)。还有其他两类设备, 一个是Root Complex,简称RC,另一个是switch(或者PCI中称为bridge),RC是根设备,通过switch和下游设备(可以是EP也可以是switch)进行桥接,从而各个PCIe设备组成了一个PCIe设备网络,信息就以数据包(TLP、DLLP)的形式在网络中传递。
一、PCI设备编号
PCI设备的ID号由总线号(BUS NUMBER)、设备号(DEVICE NUMBER)和功能号(FUNCTION NUMBER)组成。
一条PCI总线的设备号由PCI设备的IDSEL信号与PCI总线地址线的连接关系确定,即每一个PCI插槽的总线号和设备号都是固定的,这是硬件工程师决定的。
PCI功能号与PCI设备的具体设计相关。在一个PCI设备中最多有8个功能设备,而且每一个功能设备都有各自的PCI配置空间,而在绝大数PCI设备中只有一个功能设备。
lspci --- 枚举PCI设备
01:00.0 VGA compatible controller: NVIDIA Corporation GM107 [GeForce GTX 745] (rev a2)
(bus number 0~0xff[2^8]):(device number 0~0x1f[2^5]).(function number 0~0x07[2^3]) Class:Vendor Device (revision)
二、PCI配置空间
每个PCI逻辑设备都有自己的配置空间,里面存储了一些基本信息,生产商,IRQ中断号,还有就是定义了mem空间和io空间的起始地址和大小。
HOST主桥使用寄存器号,访问PCI设备配置空间的某个寄存器。
三、PCI驱动函数接口
1. pci驱动注册
pci_register_driver(struct pci_driver *drv)
pci_unregister_driver(struct pci_driver *drv)
static struct pci_device_id vt8623_devices[] = {
{PCI_DEVICE(PCI_VENDOR_ID_VIA, 0x3122)},
{0, 0, 0, 0, 0, 0, 0}
};
static struct pci_driver vt8623fb_pci_driver = {
.name = "vt8623fb",
.id_table = vt8623_devices,
.probe = vt8623_pci_probe,
.remove = vt8623_pci_remove,
.suspend = vt8623_pci_suspend,
.resume = vt8623_pci_resume,
};
pci_register_driver(&vt8623fb_pci_driver);
2. 激活PCI设备
在驱动程序可以访问PCI设备的任何设备资源之前(I/O区域或者中断),驱动程序必须调用该函数:
int pci_enable_device(struct pci_dev *dev); /*driver/pci/pci.c*/
这个函数主要就是把PCI配置空间的Command域的0位和1 位置成了1,从而达到了开启设备的目的。
3. 访问PCI配置空间
int pci_read_config_byte(conststruct pci_dev *dev,int where, u8 *val);/*8位,读入一个字节*/
int pci_read_config_word(conststruct pci_dev *dev,int where, u16 *val);/*16位,读入两个字节*/
int pci_read_config_dword(conststruct pci_dev *dev,int where, u32 *val);/*32位,读入四个字节*/
int pci_write_config_byte(conststruct pci_dev *dev,int where, u8 *val);/*8位,写入一个字节*/
int pci_write_config_word(conststruct pci_dev *dev,int where, u16 *val);/*16位,写入两个字节*/
int pci_write_config_dword(conststruct pci_dev *dev,int where, u32 *val);/*32位,写入四个字节*/
4. 申请IO端口和内存资源
通知内核该设备对应的IO端口和内存资源已经使用,其他的PCI设备不要再使用这个区域
int pci_request_regions(struct pci_dev *dev, const char *res_name)
int pci_release_regions(struct pci_dev *pdev)
int pci_request_mem_regions(struct pci_dev *pdev, const char *name)
int pci_release_mem_regions(struct pci_dev *pdev)
int pci_request_io_regions(struct pci_dev *pdev, const char *name)
int pci_release_io_regions(struct pci_dev *pdev)
5. 访问PCI的I/O和内存空间
int pci_resource_[start|end|flags|len](struct pci_dev *dev, int bar) //Bar值的范围为0-5
在硬件加电初始化时,BIOS固件统一检查了所有的PCI设备,并统一为他们分配了一个和其他互不冲突的地址,让他们的驱动程序可以向这些地址映射他们的寄存器,这些地址被BIOS写进了各个设备的配置空间,因为这个活动是一个PCI的标准的活动,所以自然写到各个设备的配置空间里而不是他们风格各异的控制寄存器空间里。当操作系统初始化时,他为每个PCI设备分配了pci_dev结构,并且把BIOS获得的并写到了配置空间中的地址读出来写到了pci_dev中的resource字段中。这样以后我们在读这些地址就不需要在访问配置空间了,直接跟pci_dev要就可以了,我们这里的四个函数就是直接从pci_dev读出了相关数据。
Linux认为这个地址是IO地址,如果要访问的话可以通过ioremap映射到内核空间,然后通过readl/writel等IO接口进行操作。
6. 私有数据
pci_set_drvdata 设置驱动私有数据
pci_get_drvdata 获取驱动私有数据
7. 主设备模式
pci_set_master 设定设备工作在总线主设备模式