PCIe 总线基础 驱动接口 和 BAR空间详解

这篇具有很好参考价值的文章主要介绍了PCIe 总线基础 驱动接口 和 BAR空间详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

PCIe 总线基础

PCIe扫盲系列
原版PDF

  1. PCI总线是一种树型结构,并且独立于CPU总线,可以和CPU总线并行操作。PCI总线上可以挂接PCI设备和PCI桥,PCI总线上只允许有一个PCI主设备(同一时刻),其他的均为PCI 从设备,而且读写操作只能在主从设备之间进行,从设备之间的数据交换需要通过主设备中转。
    注:这并不意味着所有的读写操作都需要通过北桥中转,因为PCI总线上的主设备和从设备属性是可以变化的。

  2. PCI总线是一种地址和数据复用的总线,即地址和数据占用同一组信号线AD。PCI总线的所有信号都与时钟信号同步,及所有的信号的变化都发生在时钟的上升沿,或者在时钟上升沿进行采样。

  3. 如下图所示,最左边的即为Memory Address Space,其中包括了多个PCI Memory、AGP Video(显卡)Memory以及Extended Memory、Boot ROM等。中间的为I/O Address Space,需要注意的是,虽然PCI支持32位的地址,但是由于x86的CPU只支持16位的I/O空间,这就限制了PCI的I/O Address Space最大只有64KB。最右边的则为Configuration Address Space,由于每一个PCI设备最多支持8种功能(Function),每一条PCI总线最多支持32个设备,而每一个PCI总线系统最多又支持256个子总线(通过PCI桥)。因此,总的Configuration Address Space的大小为:256 Bytes/function x 8 functions/device x 32 devices/bus x 256 buses/system = 16MB
    PCIe 总线基础 驱动接口 和 BAR空间详解
    PCIe 总线基础 驱动接口 和 BAR空间详解

  4. 和很多的串行传输协议一样,一个完整的PCIe体系结构包括应用层、事务层(Transaction Layer)、数据链路层(Data Link Layer)和物理层(Physical Layer)
    PCIe 总线基础 驱动接口 和 BAR空间详解

  5. 8B / 10B Encode/Decode详解

  6. 一个Memory Read操作的例子

  7. PCIE BAR空间理解

  8. 【精讲】PCIe基础篇——BAR(Base Address Register)详解

Base Address Registers (BARs)

概述

在一个系统中的每一个设备(device)在地址空间的大小和类型是不同的,对应不同的访问需求。例如,某一个设备在内部寄存器或者存储空间有256Byte,可以通过IO地址空间访问,另个一设备有16K大小的空间则需要通过MMIO的方式进行访问;

哪些地址应该使用哪种方式(IO或Memory)来访问设备的内部位置,这是系统软件(即BIOS和OS内核)的工作,PCI设备无法自己决定,但是需要提供一个方法让系统软件去决定以何种方式去访问设备内部地址空间。一旦软件确认了设备在地址空间的需求,就会分配合适的地址范围,以合适的方式(IO, NP‐MMIO or P‐MMIO)去完成这个任务;
设备提供的方法就是 在configuration space的header 即 Base Address Registers (BARs)。如下图所示,Type0的header有6个可用BARs,每一个32bit,而type1只有两个可用BARs。Type 1报头在所有网桥设备中都可以找到,这意味着每个switch端口都有Type 1报头。Type 0报头在非网桥设备中,比如end point。
PCIe 总线基础 驱动接口 和 BAR空间详解
系统软件必须要先确认设备地址空间的大小(size)和类型(type,决定通过何种方式映射 IO, NP‐MMIO or P‐MMIO ),这些信息只有硬件设计者才清楚的,所以size和type信息都是通过hard-codes写死在BARs的低bits中,系统软件可以通过读到这些信息,构建出访问硬件地址空间的方法,实现按照device要求的方式去访问到设备内部的地址空间中的数据。

而BARs的高位bits是由软件去填写的,内容是系统软件为此设备分配的地址空间的基地址。
单个EP(EndPoint - type0 header)有6个BARs,最多可以有六个不同的地址空间,但是实际上大多数设备只用到1~3个。如果设备不需要通过BARs去映射内部寄存器,则会通过hard-code在BARs中bit全部填写0,用于告知系统软件这个BAR为不可用。对于被使用的BAR来说,其部分低比特位是不可以被软件操作的,只有其高比特位才可以被软件操作。而这些不可操作的低比特决定了当前BAR支持的操作类型和可申请的地址空间的大小

一旦BAR的值确定了(Have been programmed),其指定范围内的当前设备中的内部寄存器(或内部存储空间)就可以被访问了。当该设备确认某一个请求(Request)中的地址在自己的BAR的范围内,便会接受这请求。

example

下面用几个简单的例子来熟悉BAR的机制:

例1. 32-bit Memory Address Space Request
如下图所示,请求一个4KB的NP-MMIO一般需要以下三个步骤:
PCIe 总线基础 驱动接口 和 BAR空间详解

  1. 如图所示,未初始化的BAR的低比特(11~ 4 )都是0,高比特(31~12)都是不确定的值(用 X 表示)。所谓初始化,就是系统(软件)向整个BAR都写1,来确定BAR的可操作的最低位是哪一位。当前可操作的最低位为12,因此当前BAR可申请的(最小)地址空间大小为4KB(2 ^ 12 )如果可操作的最低位为20,则该BAR可申请的(最小)地址空间大小为1MB( 2^20)。

  2. 完成初始化(写1操作)之后,软件便开始读取BAR的值,来确定每一个BAR对应的地址空间大小和类型。其中操作的类型一般由最低四位所决定,具体如上图右侧部分所示。

  3. 最后一步是,系统软件在读到 size和type信息后,分配了对应的地址空间,并向BAR的高比特写入地址空间的起始地址(Start Address)。如图中所示,为0xF9000000。

例2. 64-bit Memory Address Space Request

下面是一个申请64MB P-MMIO地址空间的例子,由于采用的是64-bit的地址,因此需要两个BAR。具体如下图所示:
PCIe 总线基础 驱动接口 和 BAR空间详解
例3. IO Address Space Request
下面是一个申请IO地址空间的例子,如下图所示:
PCIe 总线基础 驱动接口 和 BAR空间详解

  1. 首先是没有初始化的BAR空间,系统软件开始从BAR0开始逐个向整个BAR空间所有bit位写1,然后系统软件去确认设备是否通过BAR3请求额外的地址空间;
  2. 系统软件回读BAR3的内容,根据回读数据,判断设备申请的地址空间大小(能操作的最低位 8,256byte)和类型(IO);
  3. 系统软件得到设备请求内容后,申请对应的地址空间,并将基地址写入到BAR寄存器;

补充

注:需要特别注意的是,软件对BAR的检测与操作(Evaluating)必须是顺序执行的,即先BAR0,然后BAR1,……,直到BAR5。当软件检测到那些被硬件设置为全0的BAR,则认为这个BAR没有被使用。
无论是PCI还是PCIe,都没有明确规定,第一个使用的BAR必须是BAR0。事实上,只要设计者原意,完全可以将BAR4作为第一个BAR,并将BAR0~BAR3,BAR5都设置为不使用。
但是系统软件提前无法知道每个BARs的使用情况,所以必须要从头开始遍历所有的BARs。

linux PCIe driver

一个总线由电气接口和程序接口组成;

kernel interface

下面我们重点关心一个PCI驱动如何完成匹配到对应的硬件设备并执行访问:
PCI设备有一个8bit的总线号,一个5bit的设备编号以及一个3bit的功能编号,即一个主桥最多有256个总线,总线上最多挂32个设备,每个设备有最多8个功能;
命令行执行lspci或者cat /proc/bus/pci/devices 和 tree /sys/bus/pci/devices/中信息排布。
PCIe 总线基础 驱动接口 和 BAR空间详解
上图显示PCI设备00:0f:2为例,分隔符将设备地址分为三个区间,首地址00表示总线号,中间1f表示设备编号,末位区间2是功能编号;
首先看 结构体 pci_device_id

static struct pci_device_id plda_pcie_id_table[] =
{
	{
		vendor : PLDA_VENDOR_ID,  // 16bit 标识一个硬件制造商
		device : PCI_ANY_ID,      //16bit 供应商决定,用来唯一标识一个设备
		subvendor : PCI_ANY_ID,   //子系统供应商
		subdevice : PCI_ANY_ID,   
		class : 0 ,                //每个外设都属于一个类. 类寄存器16-位, 它的高8位标识"基类".
		class_mask : 0,            //驱动指定支持一类PCI设备,如果可以适用任意ID,则指明为 PCI_ANY_ID
		driver_data : 0,   //这个值不用来匹配一个设备, 但是用来持有信息, PCI 驱动可用来区分不同的设备
	},
	{0,0,0,0,0,0,0 },
};
MODULEDEVICETABLE宏

这个 pci_device_id 结构需要被输出到用户空间, 当热插拔或者模块加载时系统才能知道什么驱动模块匹配什么硬件设备. 宏 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. 在 depmod 完成后, 所有的被内核中的模块支持的 PCI 设备以及它们的名字被列出在文件中. 当内核告知热插拔系统有新的 PCI 设备已找到, 热插拔系统使用 moudles.pcimap 文件来找到正确的驱动来加载.

注册一个PCI驱动
static struct pci_driver plda_pci_driver =
{
	name:		    DRIVER_NAME,
	id_table:		plda_pcie_id_table,
	probe:		    plda_pcie_probe,
	remove:		    plda_pcie_remove,
	suspend:		plda_pcie_suspend,
	resume:		    plda_pcie_resume
};
int __init plda_pcie_module_init(void)
{
	int err;
	...
	if ( 0 != (err = pci_register_driver(&plda_pci_driver)))
	{
		plda_printk(KERN_ERR DRIVER_NAME ": Init Error, failed to call pci_register_driver.\n");
		class_destroy(chardev_class);
		plda_pcie_unregister_chrdev(char_major, DRIVER_NAME);
#if SUPPORT_NETLINK_INTERFACE
		goto err_out2;
#else
		return err;
#endif
	}
	...
}
使能PCI设备 pci_enable_device

在PCI driver的probe函数中,驱动访问PCI设备资源( I/O 区域 或者 中断)之前需要先调用 pci_enable_device函数:
int pci_enable_device(struct pci_dev *dev);
此函数真正使能设备,并且会分配中断线和I/O空间;

访问配置空间

在驱动匹配到设备后,通常会读写三个地址空间 :内存、端口和配置。而访问配置空间是唯一一个找到设备映射到内存和I/O空间的方法;
linux提供了标准接口去读写设备配置空间:

<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);
存取I/O和内存空间

首先明确一下可预取和不可预取的概念:

  1. 可预取指的是CPU 可缓存它的内容并且对它做所有类型的优化;可预取性内存是指存储器空间的可预取能力。例如如果读操作没有副作用(即如同从 RAM 中读数据一样不会破坏数据),则称存储器空间可预取。必要时可将字节写操作合并成一个双字写操作。可预取是读取一次以后不会改变读取地址和存储状态的任何改变,因为CPU 可缓存它的内容并且对它做所有类型的优化;
  2. 非预取的内存就象FIFO地址影射到内存地址,读取数据以后会引起FIFO指针的改变.另外还象一些中断状态I/O影射到内存,读取这个内存后,可能会清除中断标志等等,所以CPU不可缓存这个内存地址;
    如果满足以下全部条件,则应该设置为可预取的内存状态:
  3. a. 多次读(写)一个长字节产生相同的数据;
    b.如果主PCI丢弃读数据,将不会发生负的边际效应;
    c.地址空间并没有映射为I/O;
    d.允许在转发写缓冲器中进行字节合并;
    一个PCI设备最多实现6个I/O地址区域。设备接口会输出对应地址区域的大小和当前位置,6个32bit的配置寄存器,PCI_BASE_ADDRESS_0到PCI_BASE_ADDRESS_5用于表示对应的区域。两个连续的PCI_BASE_ADDRESS寄存器可以用于表示一个64bit的地址空间;
    读取设备的输出信息并分配相应的地址空间,内核已经完成这部分工作,并提供接口去获取这部分地址空间的信息:
unsigned long pci_resource_start(struct pci_dev *dev, int bar);
/*The function returns the first address (memory address or I/O port number)
associated with one of the six PCI I/O regions. The region is selected by the integer
bar (the base address register), ranging from 0–5 (inclusive).*/
unsigned long pci_resource_end(struct pci_dev *dev, int bar);
/*The function returns the last address that is part of the I/O region number bar.
Note that this is the last usable address, not the first address after the region.*/
unsigned long pci_resource_flags(struct pci_dev *dev, int bar);
//This function returns the flags associated with this resource.

<linux/ioport.h>;中定义了所有的资源标识文章来源地址https://www.toymoban.com/news/detail-406458.html

IORESOURCE_IO
IORESOURCE_MEM
IORESOURCE_PREFETCH
IORESOURCE_READONLY

到了这里,关于PCIe 总线基础 驱动接口 和 BAR空间详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux 操作系统原理 — PCIe 总线标准

    2023年04月24日
    浏览(82)
  • Linux字符设备驱动(设备文件,用户空间与内核空间进行数据交互,ioctl接口)

    在Linux系统中“一切皆文件”,上一篇讲述了cdev结构体就描述了一个字符设备驱动,主要包括设备号和操作函数集合。但是要怎么操作这个驱动呢?例如,使用open()该打开谁,read()该从哪读取数据等等。所以就需要创建一个设备文件来代表设备驱动。 应用程序要操纵外部硬件

    2024年02月12日
    浏览(41)
  • 韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型)

    本人学习完韦老师的视频,因此来复习巩固,写以笔记记之。 韦老师的课比较难,第一遍不知道在说什么,但是坚持看完一遍,再来复习,基本上就水到渠成了。 看完视频复习的同学观看最佳! 基于 IMX6ULL-PRO 参考视频 Linux快速入门到精通视频 参考资料 :01_嵌入式Linux应用

    2024年04月25日
    浏览(81)
  • 【PCIE体系结构五】PCIE配置和地址空间

    👉个人主页:highman110 👉作者简介:一名硬件工程师,持续学习,不断记录,保持思考,输出干货内容  参考书籍: PCI_Express体系结构导读、 深入浅出SSD:固态存储核心技术、原理与实战 目录 概述 EP的配置空间 switch的配置空间         每个PCIe设备都有这样一段空间,

    2023年04月14日
    浏览(95)
  • QEMU pcie config空间访问机制

    pci设备的config空间只有256字节,X86架构下是通过两个IO端口访问的,0xCF8/0xCFC端口,分别用于选通地址和传输数据。当前大部分设备都是pcie设备,config空间扩展到了4KB,而对于[256-4096)的扩展config空间,X86是通过memory映射的方式访问,并非IO端口的形式。也就是X86会把pcie的con

    2024年02月04日
    浏览(29)
  • 【技术分享】Altera FPGA EP4CGX22CF19C8详解:原理图、PCB图纸、源代码及PCIe二次开发驱动和代码全解析

    altera fpga ep4cgx22cf19c8,有原理图,PCB图纸,源代码,PCIe二次开发驱动和代码等。 ID:313000 681436451614 小明子555 《基于Altera FPGA EP4CGX22CF19C8的硬件开发与PCIe驱动开发》 摘要:本文基于Altera FPGA EP4CGX22CF19C8芯片,探讨了硬件开发和PCIe驱动开发的相关技术。首先介绍了EP4CGX22CF19C8芯片

    2024年04月25日
    浏览(35)
  • CAN总线基础详解以及stm32的CAN控制器

    目录 CAN简介 CAN总线拓扑图 CAN总线特定 CAN应用场景 CAN的物理层 CAN的协议层 CAN数据帧介绍 CAN位时序介绍 数据同步过程 硬件同步 再同步 CAN总线仲裁 stm32的CAN控制器 CAN控制器介绍 CAN控制器模式 CAN控制器框图 接收过滤器 CAN控制器波特率计算 CAN相关寄存器 CAN主控制寄存器(

    2024年01月25日
    浏览(49)
  • PCIE学习系列 五(Linux之PCIe设备驱动开发框架)

    本文讲述一个开源的PCIe设备驱动,通过这个例子可以基本上理解所有的PCIe设备驱动。后续也会做关于Linux各类驱动的文章。 通过前面的学习,我们知道PCIe设备访问之前需要先做枚举。一般来说,PCI设备的枚举操作不需要我们来做,BIOS或者系统初始化时已经做好了,当系统枚

    2024年02月05日
    浏览(58)
  • 怎么编写PCIe设备驱动程序

    DocumentationPCIMSI-HOWTO.txt driversnvmehostpci.c PCI总线设备驱动模型: 右边是pci_dev,由PCIe控制器的驱动程序扫描PCIe总线,识别出设备,并构造、注册pci_dev pci_dev结构体含有丰富的信息,比如vid、pid、class、已经分配得到的mem/io资源、INTx中断资源 左边是PCIe设备驱动程序pci_driver,

    2023年04月17日
    浏览(45)
  • 海思芯片pcie启动——pcie_mcc驱动框架的booter程序分析

    (1)源码目录:pcie_mcc/multi_boot/example/boot_test.c; (2)调用命令:./booter start_device; (3)booter程序的作用:在主片将pcie启动相关的驱动加载完成后,调用booter来引导从片pcie启动; (1)调用pcie启动相关驱动,知道当前pcie接口连接了多少个从片; (2)先传输uboot的前80KB数据到36A的内部RAM中

    2024年02月06日
    浏览(77)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包