菜单

kaoqi
kaoqi
发布于 2026-07-30 / 4 阅读
0
0

PCIE配置空间struct——pci.h

本文主要解析PCIE在配置地址空间、LTP包转发、MSI中断描述三个方面中配置空间的struct。

具体来说就是,解析如下几个头文件中的源代码:

/include/linux/pci.h——核心PCI设备结构体

/include/linux/pci_regs.h——配置空间寄存器

/include/linux/pci-ep.h——PCI端点设备结构

PCIE协议对应源文件:Specifications | PCI-SIG(这里是官网下载,本文讲的是配置空间的事情,可以看这个文件:https://members.pcisig.com/wg/PCI-SIG/document/23941,或者这个盗版的:NCB-PCI_Express_Base_5.0r1.0-2019-05-22.pdf)

总线的struct在pci初始化部分单独讨论,这里重点讲的是pci_dev。

pci.h文件

从抽象层的逻辑实现角度来说,这个文件有如下3个主要功能要实现:

1、以struct pci_dev来实现PCIE设备的结构体描述,在这个struct中规定对应的设备标识、中断号、BAR资源数组

2、以struct resource来定义BAR空间在内核中的实现,包括BAR的起始物理地址、结束物理地址、资源名称、资源类型标志等信息(注意,resoruce来源于/include/linux/ioport.h文件)

3、配置空间访问API,config区域的读写方法(8位、16位、32位的共6种方法)

注意,这里起始是在枚举的时候,对PCIE寄存器地址空间进行解析的一个struct,所以需要遵循PCIE协议所描述的寄存器中对应的数据结构:

对于pci_dev设备来说,首先是在枚举的时候,会从BAR空间中按标准的结构解析配置参数出来,这里有关BAR空间这种标准通用配置空间的结构,是在pci_regs.h文件中进行描述与解析的,而对应的Base Address Register映射过去的空间中,各寄存器的含义,则需要由各PCIE设备的私有化驱动内部struct再进一步解析。

如下是pce_dev结构体的主要结构:(对于各种不同的设备结构,其由驱动初始化,产生对应实例化pci_dev的时候)

大功能

条目

含义

实现总线链表

struct list_head bus_list

将设备挂入其所在总线的设备链表,用于遍历同一总线上的所有设备。

struct pci_bus *bus

指向设备当前所在的总线结构体,用于获取总线资源、配置空间访问接口等。

struct pci_bus *subordinate

指向该设备桥接的下级总线,仅对 PCI-PCI 桥设备有效,用于枚举下游设备。

架构扩展用户接口

void *sysdata

架构特定数据的钩子,ARMv8 等平台用它存储 Host 桥私有数据(BAR映射协议相关)

struct proc_dir_entry *procent

设备在 /proc/bus/pci 目录下的入口,用于用户空间通过 proc 文件系统访问设备配置空间。

struct pci_slot *slot

指向设备所在的物理插槽结构体,用于管理热插拔、电源控制等插槽级功能。

设备标识

unsigned int devfn

设备号与功能号,用于在总线上唯一标识一个设备功能

unsigned short vendor;
unsigned short device;

厂商ID和设备ID,用于匹配对应驱动;注意,这里的驱动由对应设备放在对应文件中,比如USB控制器在进行PCI设备实例化时,实际上是在/drivers/usb/host这个目录下去实例化一个pci_dev

注意,也正是从这里开始,启用了与

unsigned short subsystem_vendor;
unsigned short subsystem_device;

子系统厂商和设备 ID,用于区分不同板卡上的同一芯片。

unsigned int class;

3 字节的类代码(基本类、子类、编程接口),标识设备类型(如网卡、显卡)。

u8 revision;

标识PCIE版本

u8 hdr_type;

PCI 头类型,区分普通设备、PCI-PCI 桥或 CardBus 桥。

PCIE扩展能力

u32 devcap;

缓存设备能力寄存器,记录 Max Payload Size 等设备特性。

u8 pcie_cap;

PCIe 能力结构在配置空间中的偏移量,是访问所有 PCIe 扩展功能的总入口。

u8 msi_cap;
u8 msix_cap;

MSI 和 MSI-X 能力结构偏移,用于配置中断。

u8 pcie_mpss:3;

这是一个位域,占用3个比特位。它缓存了PCIe设备“设备功能寄存器”中的 Max_Payload_Size_Supported 字段的值。这个值表示该设备硬件上支持的最大数据有效载荷(TLP)大小,例如128字节、256字节、512字节等。

‌u8 rom_base_reg;‌

一个8位无符号整数,记录了PCI配置空间中ROM基地址寄存器(Expansion ROM Base Address Register)的位置。对于不同类型的PCI配置空间头部(如Type 0普通设备,Type 1 PCI桥),ROM基地址寄存器的偏移位置是不同的,这个字段就用于指明具体位置。

‌u8 pin;‌

一个8位无符号整数,表示该PCI设备使用的是哪条中断引脚。PCI规范定义了四条中断引脚:INTA#、INTB#、INTC#、INTD#。这个字段的值就对应着设备连接的是哪一条,驱动程序可以据此申请中断号。

‌u16 pcie_flags_reg;‌

一个16位无符号整数,是PCIe功能寄存器(PCI Express Capabilities Register)的缓存副本。这个寄存器包含了PCIe功能结构的版本号、设备/端口类型(如端点、根端口、交换端口)等信息。系统在初始化时读取该寄存器并存到这里,方便后续快速查询,避免重复访问配置空间。

unsigned long *dma_alias_mask;‌

一个指针,指向一个位掩码。这个掩码用于记录启用了DMA别名功能的设备功能号(devfn)。在某些复杂的PCIe拓扑中,一个设备可能通过不同的设备功能号(devfn)来发起DMA,这个掩码就是用来追踪这些别名的。

struct pci_driver *driver;‌

一个指针,指向绑定到这个PCI设备的驱动程序所对应的 pci_driver 结构体。当设备驱动被加载并与设备匹配成功后,这个指针就会指向该驱动的实例,是内核将设备与驱动关联起来的核心纽带。

u64 dma_mask;‌

一个64位无符号整数,是设备DMA(直接内存访问)的总线地址掩码。它定义了设备能够寻址的总线地址范围。通常默认值是 0xffffffff,表示设备只支持32位DMA寻址。如果设备支持64位DMA,或者其DMA寻址能力有缺陷,就需要修改这个掩码。它告诉内核在分配DMA缓冲区时,必须确保物理地址在设备可寻址的范围内。

‌struct device_dma_parameters dma_parms;‌

一个结构体,包含了设备的一些DMA相关参数,例如用于一致性DMA映射的边界限制、最大分段大小等。这些参数为DMA映射操作提供了更精细的控制,确保DMA操作符合硬件和平台的要求。

电源状态

‌pci_power_t current_state

记录设备当前所处的电源状态。PCI电源状态从D0(全功能运行)到D3(关闭),功耗依次降低。

unsigned int imm_ready:1

一个位域标志,表示设备支持“立即就绪”机制。当此位为1时,设备从低功耗状态恢复到D0后,无需等待常规的延迟,可以立即被访问。

u8 pm_cap

记录PCI电源管理能力寄存器在配置空间中的偏移地址。这是访问所有电源管理功能的基础,如果设备不支持电源管理,此值通常为0。

unsigned int pme_support:5

一个位掩码,标识设备能从哪些低功耗状态(D0、D1、D2、D3hot)产生电源管理事件(PME)唤醒信号。

unsigned int pme_poll:1

一个轮询标志。对于某些无法主动上报PME状态的设备,需要内核定期轮询其PME状态位,此标志用于标识这类设备。

unsigned int d1_support:1
unsigned int d2_support:1

分别表示设备硬件是否支持D1和D2这两个可选的中间低功耗状态。

unsigned int no_d1d2:1

一个强制禁止标志。即使硬件支持D1或D2,如果此位被置1,系统也会禁止设备进入这两种状态。

unsigned int no_d3cold:1

禁止设备进入D3cold状态。因为从D3cold恢复需要重新初始化硬件,唤醒延迟较长,且需要平台固件支持。

unsigned int bridge_d3:1‌

一个针对PCI桥设备的特殊标志。置1表示允许该桥设备进入D3状态,这通常用于优化整体功耗,但需要桥设备本身支持。

unsigned int d3cold_allowed:1

一个由用户通过sysfs控制的标志,表示用户明确允许设备进入D3cold状态。这是进入D3cold的必要条件之一。

‌unsigned int mmio_always_on:1

一个特殊标志,用于禁止在探测设备BAR空间大小时关闭其内存或I/O解码。这通常用于规避某些设备的硬件缺陷。

unsigned int wakeup_prepared:1

一个流程控制标志,表示设备已经为唤醒事件做好了准备。这通常是在系统进入休眠前,由驱动调用相关接口设置的。

unsigned int runtime_d3cold:1

及其他

标识设备在运行时电源管理(Runtime PM)中是否经历了D3cold状态。这有助于区分设备是由其上级桥设备直接控制供电,还是通过自身寄存器进入D3cold。

事务层

unsigned int eetlp_prefix_path:1;

标记PCIE设备是否启用端到端事务TLP

pci_channel_state_t error_state;

枚举当前设备通路的连通状态

struct device dev;

PCI设备struct

unsigned int is_busmaster:1;

设备是否启用总线主控能力

unsigned int block_cfg_access:1;

是否阻止对配置空间的访问

struct resource resource[DEVICE_COUNT_RESOURCE];

描述resource具体BAR空间的变量

struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE];

资源属性

struct bin_attribute *rom_attr;

指向 sysfs 二进制属性描述符,用于在 /sys/bus/pci/devices/.../rom 中导出设备 ROM

u32 saved_config_space[16];

配置空间保存

pci_dev结构:(这个struct更多的是kernel内部用的,跟BAR空间其实没有直接对应关系)


/* The pci_dev structure describes PCI devices */
struct pci_dev {
	struct list_head bus_list;	/* Node in per-bus list */
	struct pci_bus	*bus;		/* Bus this device is on */
	struct pci_bus	*subordinate;	/* Bus this device bridges to */

	void		*sysdata;	/* Hook for sys-specific extension */
	struct proc_dir_entry *procent;	/* Device entry in /proc/bus/pci */
	struct pci_slot	*slot;		/* Physical slot this device is in */

	unsigned int	devfn;		/* Encoded device & function index */
	unsigned short	vendor;
	unsigned short	device;
	unsigned short	subsystem_vendor;
	unsigned short	subsystem_device;
	unsigned int	class;		/* 3 bytes: (base,sub,prog-if) */
	u8		revision;	/* PCI revision, low byte of class word */
	u8		hdr_type;	/* PCI header type (`multi' flag masked out) */
#ifdef CONFIG_PCIEAER
	u16		aer_cap;	/* AER capability offset */
	struct aer_stats *aer_stats;	/* AER stats for this device */
#endif
#ifdef CONFIG_PCIEPORTBUS
	struct rcec_ea	*rcec_ea;	/* RCEC cached endpoint association */
#endif
	u32		devcap;		/* PCIe Device Capabilities */
	u8		pcie_cap;	/* PCIe capability offset */
	u8		msi_cap;	/* MSI capability offset */
	u8		msix_cap;	/* MSI-X capability offset */
	u8		pcie_mpss:3;	/* PCIe Max Payload Size Supported */
	u8		rom_base_reg;	/* Config register controlling ROM */
	u8		pin;		/* Interrupt pin this device uses */
	u16		pcie_flags_reg;	/* Cached PCIe Capabilities Register */
	unsigned long	*dma_alias_mask;/* Mask of enabled devfn aliases */

	struct pci_driver *driver;	/* Driver bound to this device */
	u64		dma_mask;	/* Mask of the bits of bus address this
					   device implements.  Normally this is
					   0xffffffff.  You only need to change
					   this if your device has broken DMA
					   or supports 64-bit transfers.  */

	struct device_dma_parameters dma_parms;

	pci_power_t	current_state;	/* Current operating state. In ACPI,
					   this is D0-D3, D0 being fully
					   functional, and D3 being off. */
	unsigned int	imm_ready:1;	/* Supports Immediate Readiness */
	u8		pm_cap;		/* PM capability offset */
	unsigned int	pme_support:5;	/* Bitmask of states from which PME#
					   can be generated */
	unsigned int	pme_poll:1;	/* Poll device's PME status bit */
	unsigned int	d1_support:1;	/* Low power state D1 is supported */
	unsigned int	d2_support:1;	/* Low power state D2 is supported */
	unsigned int	no_d1d2:1;	/* D1 and D2 are forbidden */
	unsigned int	no_d3cold:1;	/* D3cold is forbidden */
	unsigned int	bridge_d3:1;	/* Allow D3 for bridge */
	unsigned int	d3cold_allowed:1;	/* D3cold is allowed by user */
	unsigned int	mmio_always_on:1;	/* Disallow turning off io/mem
						   decoding during BAR sizing */
	unsigned int	wakeup_prepared:1;
	unsigned int	runtime_d3cold:1;	/* Whether go through runtime
						   D3cold, not set for devices
						   powered on/off by the
						   corresponding bridge */
	unsigned int	skip_bus_pm:1;	/* Internal: Skip bus-level PM */
	unsigned int	ignore_hotplug:1;	/* Ignore hotplug events */
	unsigned int	hotplug_user_indicators:1; /* SlotCtl indicators
						      controlled exclusively by
						      user sysfs */
	unsigned int	clear_retrain_link:1;	/* Need to clear Retrain Link
						   bit manually */
	unsigned int	d3hot_delay;	/* D3hot->D0 transition time in ms */
	unsigned int	d3cold_delay;	/* D3cold->D0 transition time in ms */

#ifdef CONFIG_PCIEASPM
	struct pcie_link_state	*link_state;	/* ASPM link state */
	unsigned int	ltr_path:1;	/* Latency Tolerance Reporting
					   supported from root to here */
	int		l1ss;		/* L1SS Capability pointer */
#endif
	unsigned int	eetlp_prefix_path:1;	/* End-to-End TLP Prefix */

	pci_channel_state_t error_state;	/* Current connectivity state */
	struct device	dev;			/* Generic device interface */

	int		cfg_size;		/* Size of config space */

	/*
	 * Instead of touching interrupt line and base address registers
	 * directly, use the values stored here. They might be different!
	 */
	unsigned int	irq;
	struct resource resource[DEVICE_COUNT_RESOURCE]; /* I/O and memory regions + expansion ROMs */

	bool		match_driver;		/* Skip attaching driver */

	unsigned int	transparent:1;		/* Subtractive decode bridge */
	unsigned int	io_window:1;		/* Bridge has I/O window */
	unsigned int	pref_window:1;		/* Bridge has pref mem window */
	unsigned int	pref_64_window:1;	/* Pref mem window is 64-bit */
	unsigned int	multifunction:1;	/* Multi-function device */

	unsigned int	is_busmaster:1;		/* Is busmaster */
	unsigned int	no_msi:1;		/* May not use MSI */
	unsigned int	no_64bit_msi:1;		/* May only use 32-bit MSIs */
	unsigned int	block_cfg_access:1;	/* Config space access blocked */
	unsigned int	broken_parity_status:1;	/* Generates false positive parity */
	unsigned int	irq_reroute_variant:2;	/* Needs IRQ rerouting variant */
	unsigned int	msi_enabled:1;
	unsigned int	msix_enabled:1;
	unsigned int	ari_enabled:1;		/* ARI forwarding */
	unsigned int	ats_enabled:1;		/* Address Translation Svc */
	unsigned int	pasid_enabled:1;	/* Process Address Space ID */
	unsigned int	pri_enabled:1;		/* Page Request Interface */
	unsigned int	is_managed:1;
	unsigned int	needs_freset:1;		/* Requires fundamental reset */
	unsigned int	state_saved:1;
	unsigned int	is_physfn:1;
	unsigned int	is_virtfn:1;
	unsigned int	reset_fn:1;
	unsigned int	is_hotplug_bridge:1;
	unsigned int	is_pciehp:1;
	unsigned int	shpc_managed:1;		/* SHPC owned by shpchp */
	unsigned int	is_thunderbolt:1;	/* Thunderbolt controller */
	/*
	 * Devices marked being untrusted are the ones that can potentially
	 * execute DMA attacks and similar. They are typically connected
	 * through external ports such as Thunderbolt but not limited to
	 * that. When an IOMMU is enabled they should be getting full
	 * mappings to make sure they cannot access arbitrary memory.
	 */
	unsigned int	untrusted:1;
	/*
	 * Info from the platform, e.g., ACPI or device tree, may mark a
	 * device as "external-facing".  An external-facing device is
	 * itself internal but devices downstream from it are external.
	 */
	unsigned int	external_facing:1;
	unsigned int	broken_intx_masking:1;	/* INTx masking can't be used */
	unsigned int	io_window_1k:1;		/* Intel bridge 1K I/O windows */
	unsigned int	irq_managed:1;
	unsigned int	non_compliant_bars:1;	/* Broken BARs; ignore them */
	unsigned int	is_probed:1;		/* Device probing in progress */
	unsigned int	link_active_reporting:1;/* Device capable of reporting link active */
	unsigned int	no_vf_scan:1;		/* Don't scan for VFs after IOV enablement */
	unsigned int	no_command_memory:1;	/* No PCI_COMMAND_MEMORY */
	unsigned int	rom_bar_overlap:1;	/* ROM BAR disable broken */
	pci_dev_flags_t dev_flags;
	atomic_t	enable_cnt;	/* pci_enable_device has been called */

	u32		saved_config_space[16]; /* Config space saved at suspend time */
	struct hlist_head saved_cap_space;
	struct bin_attribute *rom_attr;		/* Attribute descriptor for sysfs ROM entry */
	int		rom_attr_enabled;	/* Display of ROM attribute enabled? */
	struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE]; /* sysfs file for resources */
	struct bin_attribute *res_attr_wc[DEVICE_COUNT_RESOURCE]; /* sysfs file for WC mapping of resources */

#ifdef CONFIG_HOTPLUG_PCI_PCIE
	unsigned int	broken_cmd_compl:1;	/* No compl for some cmds */
#endif
#ifdef CONFIG_PCIE_PTM
	unsigned int	ptm_root:1;
	unsigned int	ptm_enabled:1;
	u8		ptm_granularity;
#endif
#ifdef CONFIG_PCI_MSI
	const struct attribute_group **msi_irq_groups;
#endif
	struct pci_vpd *vpd;
#ifdef CONFIG_PCIE_DPC
	u16		dpc_cap;
	unsigned int	dpc_rp_extensions:1;
	u8		dpc_rp_log_size;
#endif
#ifdef CONFIG_PCI_ATS
	union {
		struct pci_sriov	*sriov;		/* PF: SR-IOV info */
		struct pci_dev		*physfn;	/* VF: related PF */
	};
	u16		ats_cap;	/* ATS Capability offset */
	u8		ats_stu;	/* ATS Smallest Translation Unit */
#endif
#ifdef CONFIG_PCI_PRI
	u16		pri_cap;	/* PRI Capability offset */
	u32		pri_reqs_alloc; /* Number of PRI requests allocated */
	unsigned int	pasid_required:1; /* PRG Response PASID Required */
#endif
#ifdef CONFIG_PCI_PASID
	u16		pasid_cap;	/* PASID Capability offset */
	u16		pasid_features;
#endif
#ifdef CONFIG_PCI_P2PDMA
	struct pci_p2pdma *p2pdma;
#endif
	u16		acs_cap;	/* ACS Capability offset */
	phys_addr_t	rom;		/* Physical address if not from BAR */
	size_t		romlen;		/* Length if not from BAR */
	char		*driver_override; /* Driver name to force a match */

	unsigned long	priv_flags;	/* Private flags for the PCI driver */
};

resource结构:(本体源自/include/linux/opport.h,此处被实例化为具体的device)

struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	unsigned long desc;
	struct resource *parent, *sibling, *child;
};

API接口函数:

int pci_read_config_byte(const struct pci_dev *dev, int where, u8 *val);
int pci_read_config_word(const struct pci_dev *dev, int where, u16 *val);
int pci_read_config_dword(const struct pci_dev *dev, int where, u32 *val);
int pci_write_config_byte(const struct pci_dev *dev, int where, u8 val);
int pci_write_config_word(const struct pci_dev *dev, int where, u16 val);
int pci_write_config_dword(const struct pci_dev *dev, int where, u32 val);

// 具体实现来自/drivers/pci/access.c文件:
int pci_read_config_byte(const struct pci_dev *dev, int where, u8 *val)
{
	if (pci_dev_is_disconnected(dev)) {
		*val = ~0;
		return PCIBIOS_DEVICE_NOT_FOUND;
	}
	return pci_bus_read_config_byte(dev->bus, dev->devfn, where, val);
}


评论