关于PCI的配置
#
# Bus options (PCI etc.)
#
CONFIG_PCI=y
CONFIG_PCI_DIRECT=y
CONFIG_PCI_MMCONFIG=y
CONFIG_PCI_XEN=y
CONFIG_PCI_DOMAINS=y
# CONFIG_PCI_CNB20LE_QUIRK is not set
CONFIG_PCIEPORTBUS=y
CONFIG_HOTPLUG_PCI_PCIE=y
CONFIG_PCIEAER=y
# CONFIG_PCIE_ECRC is not set
# CONFIG_PCIEAER_INJECT is not set
CONFIG_PCIEASPM=y
CONFIG_PCIEASPM_DEBUG=y
CONFIG_PCIEASPM_DEFAULT=y
# CONFIG_PCIEASPM_POWERSAVE is not set
# CONFIG_PCIEASPM_POWER_SUPERSAVE is not set
# CONFIG_PCIEASPM_PERFORMANCE is not set
CONFIG_PCIE_PME=y
CONFIG_PCIE_DPC=y
CONFIG_PCIE_PTM=y
CONFIG_PCI_BUS_ADDR_T_64BIT=y
CONFIG_PCI_MSI=y
CONFIG_PCI_MSI_IRQ_DOMAIN=y
CONFIG_PCI_QUIRKS=y
# CONFIG_PCI_DEBUG is not set
CONFIG_PCI_REALLOC_ENABLE_AUTO=y
CONFIG_PCI_STUB=m
CONFIG_XEN_PCIDEV_FRONTEND=m
CONFIG_PCI_ATS=y
CONFIG_PCI_LOCKLESS_CONFIG=y
CONFIG_PCI_IOV=y
CONFIG_PCI_PRI=y
CONFIG_PCI_PASID=y
CONFIG_PCI_LABEL=y
CONFIG_PCI_HYPERV=m
CONFIG_HOTPLUG_PCI=y
CONFIG_HOTPLUG_PCI_ACPI=y
CONFIG_HOTPLUG_PCI_ACPI_IBM=m
CONFIG_HOTPLUG_PCI_CPCI=y
CONFIG_HOTPLUG_PCI_CPCI_ZT5550=m
CONFIG_HOTPLUG_PCI_CPCI_GENERIC=m
CONFIG_HOTPLUG_PCI_SHPC=m
#
# DesignWare PCI Core Support
#
CONFIG_PCIE_DW=y
CONFIG_PCIE_DW_HOST=y
CONFIG_PCIE_DW_PLAT=y
#
# PCI host controller drivers
#
CONFIG_VMD=m
#
# PCI Endpoint
#
CONFIG_PCI_ENDPOINT=y
CONFIG_PCI_ENDPOINT_CONFIGFS=y
# CONFIG_PCI_EPF_TEST is not set
第一阶段初始化
pure_initcall系列初始化
入口参数-early_param(复习)
pci.c:6564:pure_initcall(pci_realloc_setup_params);
pci_realloc_setup_params 这个函数是GRUB引导时可以传入一些PCI的配置参数,
这里先不深入研究,可以参考《Linux那些事之PCI》P23
early_param("pci", pci_setup);
pci_setup(char *str)
ubuntu 修改命令行参数方式:
/etc/default/grub
中
GRUB_CMDLINE_LINUX=
修改完后,sudo update-grub && sudo update-grub2
最后在 /boot/grub/grub.cfg中表现出来: linux /boot/vmlinuz-…… root=xxxxx
参考:Linux内核引导参数简介
与PCI相关的命令行参数:
如果在grub文件kernel那一行添加有“pci=”这样的东东,在调用那些入口函数之前,就必须得先调用一个pci_setup函数来解析这部分内核参数。pci_setup函数在drivers/pci/pci.c里有定义
[PCI]
pci=option[,option...]
off
[IA-32]不检测PCI总线,也就是关闭所有PCI设备。
bios
[IA-32]强制使用PCI BIOS而不是直接访问硬件,这表示内核完全信任BIOS(大多数情况下它并不可信)。仅在你的机器有一个不标准的PCI host bridge的时候才用。
nobios
[IA-32]强制直接访问硬件而不使用PCI BIOS,2.6.13之后这是默认值。如果你确定在内核引导时的崩溃是由BIOS所致就可以使用它。
conf1
[IA-32]强制硬件设备使用PCI Configuration Mechanism 1访问PCI Memory以与内核中的驱动程序进行通信。
conf2
[IA-32]强制硬件设备使用PCI Configuration Mechanism 2访问PCI Memory以与内核中的驱动程序进行通信。
nommconf
[IA-32,X86_64]禁止为 PCI Configuration 使用 MMCONFIG 表。
nomsi
[MSI]如果启用了PCI_MSI内核配置选项,那么可以使用这个参数在系统范围内禁用MSI中断。
nosort
[IA-32]不在检测阶段根据PCI BIOS给出的顺序对PCI设备进行排序。进行这样的排序是为了以与早期内核兼容的方式获取设备序号。
biosirq
[IA-32]使用PCI BIOS调用来获取中断路由表。这些调用在不少机器上都有缺陷,会导致系统在使用过程中挂起。但是在某些机器上却是唯一获取中断路由表的手段。如果内核无法分配IRQ或者发现了第二个PCI总线,就可以尝试使用这个选项解决问题。
rom
[IA-32]为扩展ROM分配地址空间。使用此选项要小心,因为某些设备在ROM与其它资源之间共享地址×××。
pirqaddr=0xAAAAA
[IA-32]指定物理地址位于F0000h-100000h范围之外的PIRQ表(通常由BIOS产生)的物理地址。
lastbus=N
[IA-32]扫描所有总线,直到第N个总线。如果内核找不到第二条总线的时候,你就需要使用这个选项明确告诉它。
assign-busses
[IA-32]总是使用你自己指定的PCI总线号(而不是firmware提供的)。
usepirqmask
[IA-32]优先使用可能存在于BIOS $PIR表中的IRQ掩码。某些有缺陷的BIOS需要这个选项,特别是在HP Pavilion N5400和Omnibook XE3笔记本上。如果启用了ACPI IRQ路由的话,将不会考虑这个选项的设置。
noacpi
[IA-32]不为IRQ路由或者PCI扫描使用ACPI。
routeirq
为所有PCI设备执行IRQ路由。这个通常在pci_enable_device()中执行,所有这是一个解决不调用此函数的bug驱动程序的临时解决方法。
bfsort
按照宽度优先的顺序对PCI设备进行排序。进行这样的排序是为了以与2.4内核兼容的方式获取设备序号。
nobfsort
不按照宽度优先的顺序对PCI设备进行排序。
cbiosize=nn[KMG]
从CardBus bridge 的 IO 窗口接受的固定长度的总线空间(bus space),默认值是256字节。
cbmemsize=nn[KMG]
从CardBus bridge 的 memory 窗口接受的固定长度的总线空间(bus space),默认值是64MB。
# max payload配置选择,默认是PCIE_BUS_DEFAULT
pcie_bus_config = PCIE_BUS_TUNE_OFF;
pcie_bus_config = PCIE_BUS_SAFE;
pcie_bus_config = PCIE_BUS_PERFORMANCE;
pcie_bus_config = PCIE_BUS_PEER2PEER;
postcore_initcall系列初始化
sys/class/pci注册
// probe.c:108:postcore_initcall(pcibus_class_init);
// driver/pci/probe.c
// 注册 /sys/class/pci_bus接口
static struct class pcibus_class = {
.name = "pci_bus",
.dev_release = &release_pcibus_dev,
.dev_groups = pcibus_groups,
};
static int __init pcibus_class_init(void)
{
return class_register(&pcibus_class); // /sys/class/pci_bus
}
postcore_initcall(pcibus_class_init);
pci_bus注册和/sys/bus/pci/初始化
// pci-driver.c:1681:postcore_initcall(pci_driver_init);
// driver/pci/pci-driver.c
// 注册PCI BUS,所以/sys/bus/pci目录存在,且子目录device和driver目录存在
struct bus_type pci_bus_type = {
.name = "pci",
.match = pci_bus_match,
.uevent = pci_uevent,
.probe = pci_device_probe,
.remove = pci_device_remove,
.shutdown = pci_device_shutdown,
.dev_groups = pci_dev_groups,
.bus_groups = pci_bus_groups,
.drv_groups = pci_drv_groups,
.pm = PCI_PM_OPS_PTR,
.num_vf = pci_bus_num_vf,
.dma_configure = pci_dma_configure,
};
pci_driver_init
bus_register(&pci_bus_type); // /sys/bus/pci/
arch_initcall系列(重点)
注:DMI信息参考《Linux那些事之PCI》P42中详解,不考虑这些相关函数
pci_arch_init(重点)
X86开始扫描检测PCI设备
// init.c:45:arch_initcall(pci_arch_init);
pci_arch_init
if (!(pci_probe & PCI_PROBE_NOEARLY))
pci_mmcfg_early_init(); // 这里会初始化ECAM,参考下节配置空间访问 详解
// 《Linux那些事之PCI》P5中描述了三种PCI access mode,
// 内核中CONFIG_PCI_DIRECT这个宏有配直接Direct去访问
pci_direct_probe(); // 判断是否为PCI桥设备,下边详解
if (x86_init.pci.arch_init && !x86_init.pci.arch_init()) // 函数没实现,哈哈
return 0;
// pci_pcbios_init(); // CONFIG_PCI_BIOS--不配置,不用看
pci_direct_init(type); // raw_pci_ops = raw_pci_ext_ops 预留读写pci配置空间的接口
pci_direct_probe 详解
pci_probe & PCI_PROBE_CONF1 # 判断,什么是CONF1和CONF2
request_region(0xCF8, 8, "PCI conf1") # 为什么使用0xCF8
pci_check_type1() # 检测type1
raw_pci_ops = &pci_direct_conf1; # 初始化pci配置空间操作接口
CONF1和CONF2
《Linux那些事之PCI》P36-P43 这部分讲解很详细了
// 相关宏配置:arch/x86/include/asm/pci_x86.h
unsigned int pci_probe = PCI_PROBE_BIOS | PCI_PROBE_CONF1 | PCI_PROBE_CONF2 |
PCI_PROBE_MMCONF; // 其中PCI_PROBE_CONF1和PCI_PROBE_CONF2
// 这里不考虑主动传入命令行参数情况
这些配置在《Linux那些事之PCI》P27说过,其他都
PCI设备的访问方式有BIOS、Direct的conf1和conf2(兼容老主板)、MMConfig PCI_PROBE_BIOS对应了BIOS方式, PCI_PROBE_MMCONF对应了MMConfig方式,这好理解,看名字就知道了, 不好理解的是PCI_PROBE_CONF1和PCI_PROBE_CONF2都对应了Direct方式:
因为曾经有过两种PCI Configuration Mechanism, 内核要想不通过BIOS直接去访问设备的话,也必须得对应有两种访问方式,即这里的conf1和conf2(也是TYPE1和TYPE2)。
Type2主要是在PCI发展的少年时期,某些主桥用过,现在一般都不会再用了,但是为了兼容一些老的主板,conf2还是保留了下来。
PCIE的IO端口和配置空间访问
request_region(0xCF8, 8, "PCI conf1")
X86使用0xCF8(bus+device+),0xCFC作为IO端口来访问PCI,对PCI主桥校验
baiy@ubuntu:output$ sudo cat /proc/ioports | grep PCI
0000-0cf7 : PCI Bus 0000:00
0cf8-0cff : PCI conf1 # 访问基础配置空间
0d00-feff : PCI Bus 0000:00
2000-3fff : PCI Bus 0000:02
4000-4fff : PCI Bus 0000:03
5000-5fff : PCI Bus 0000:0b
6000-6fff : PCI Bus 0000:13
7000-7fff : PCI Bus 0000:1b
8000-8fff : PCI Bus 0000:04
9000-9fff : PCI Bus 0000:0c
a000-afff : PCI Bus 0000:14
b000-bfff : PCI Bus 0000:1c
c000-cfff : PCI Bus 0000:05
d000-dfff : PCI Bus 0000:0d
e000-efff : PCI Bus 0000:15
baiy@ubuntu:output$
这就是PCI基础配置空间读写的真实方法:
inno@DEV-005:~$ sudo cat /proc/ioports | grep "PCI conf"
0cf8-0cff : PCI conf1
// arch/x86/direct.c中
#define PCI_CONF1_ADDRESS(bus, devfn, reg) \
(0x80000000 | ((reg & 0xF00) << 16) | (bus << 16) \
| (devfn << 8) | (reg & 0xFC))
outl(PCI_CONF1_ADDRESS(bus, devfn, reg), 0xCF8); // 配置地址
u32 value = inl(0xCFC); // 读取配置
深入PCI与PCIe之二:软件篇 中关于CONFIG_ADDRESS描述:
CONFIG_ADDRESS寄存器格式:
31 位:Enabled位。
23:16 位:总线编号。 // bus
15:11 位:设备编号。 // devfn[7:3]
10: 8 位:功能编号。 // devfn[2:0]
7: 2 位:配置空间寄存器编号。 // 配置空间偏移地址, 注:因为是32位端口,所以4字节访问。
1: 0 位:恒为“00”。这是因为CF8h、CFCh端口是32位端口。
看到这里有个疑问:配置空间寄存器编号 只有8bit,所以只能访问到 0-255的配置空间,如何访问PCI设备的拓展配置空间?下一章会描述ECAM机制。
校验主桥过程
// IO空间中0xCF8和0xCFC留给PCI使用,0xCF8作为地址信息,0xCFC数据交互
request_region(0xCF8, 8, "PCI conf1") // 申请8字节的IO端口资源
// 这里肯定是CONF1,所以不考虑其他情况
pci_check_type1() // 读取HOST主桥的配置空间,看是否可以读取出来 -书上详解,这里不再重复
//??? 其实这段代码很有悬念,感觉就像检查下端口是否好用,和检测下北桥
// 对这句话的官方解释如下:
// 问题:https://www.cs.helsinki.fi/linux/linux-kernel/2003-01/0553.html
// 回复:https://www.cs.helsinki.fi/linux/linux-kernel/2003-01/1060.html
// "It is trying to verify that the PCI northbridge does *NOT* respond to this (byte-wide) reference".
outb(0x01, 0xCFB); // 前边说过, CONFIG_ADDRESS[1:0]永远都为2b00,瞬间打脸,这里只测试下北桥,没有意义
tmp = inl(0xCF8); // 保存0xCF8的状态
// 同上,也是迷之代码
outl(0x80000000, 0xCF8);
判断inl(0xCF8) == 0x80000000
// ******重点:测试HOST-bridge是否存在*******
/*
* 在决定使用直接硬件访问机制之前,我们尝试进行一些琐碎的检查,以确保它至少_似乎正常运行
* 我们只测试总线00是否包含主桥
*/
pci_sanity_check // 用pci_direct_conf1接口尝试读取总线0上所有设备的 device ID和Vendor ID
for (devfn = 0; devfn < 0x100; devfn++) {
pci_direct_conf1->read(); // 遍历bus0下所有设备,寻找VGA/HOST_BRIDGE ,肯定是要能找到的。
raw_pci_ops = &pci_direct_conf1;
port_cf9_safe = true;
return 1; // 从这里返回
release_region(0xCF8, 8); // 释放8字节IO端口资源, 基本上不会走到这里,因为肯定有主桥,且永久占用
- inb 从I/O端口读取一个字节(BYTE, HALF-WORD) ;
- outb 向I/O端口写入一个字节(BYTE, HALF-WORD) ;
- inw 从I/O端口读取一个字(WORD,即两个字节) ;
- outw 向I/O端口写入一个字(WORD,即两个字节) ;
- inl 从I/O端口读取双字(即四个字节) ;
- outl 向I/O端口写入双字(即四个字节) ;
这里细节太多了,《Linux那些事之PCI》P36-P43 讲解很详细
总结:读取了下X86的IO空间,然后检测可以读取HOST主桥的设备信息, 这里也留了操作PCI的接口函数。
SMBIOS(System Management BIOS)主板厂商使用 DMI(Desktop Management Interface)进行同步,这里不看这部分
arch_init详解
if (x86_init.pci.arch_init && !x86_init.pci.arch_init())
return 0;
竟然有没实现相关方法,开不开心
// arch/x86/kernel/x86_init.c
struct x86_init_ops x86_init __initdata = {
.pci = {
.init = x86_default_pci_init,
.init_irq = x86_default_pci_init_irq,
.fixup_irqs = x86_default_pci_fixup_irqs,
},
}
pci_direct_init详解
正常情况下:type = pci_direct_probe();
返回的配置WORK=1,type=1
pci_direct_init(int type)
raw_pci_ops = &pci_direct_conf1;
port_cf9_safe = true;
raw_pci_ops = &pci_direct_conf1; // 给全局变量OPS赋值
raw_pci_ext_ops = &pci_direct_conf1; // TBD,这里ext指的是0x40-0xff还是0x100-0xfff ???
const struct pci_raw_ops pci_direct_conf1 = { // 以后配置空间访问就看这哥们的了
.read = pci_conf1_read,
.write = pci_conf1_write,
};
subsys_initcall 系列
pci_slot_init 创建了/sys/bus/pci/slots/ 目录
代码也就创建了/sys/bus/pci/slots/目录和初始化相关方法
[baiy@server_202 pci]$ tree /sys/bus/pci/slots/
/sys/bus/pci/slots/
pci_bus_kset = bus_get_kset(&pci_bus_type);
pci_slots_kset = kset_create_and_add("slots", NULL,
pci_subsys_init(重点-第三部分详解)
x86_init.pci.init() // ACPI PCI初始化-后边详解
// 注:旧版本调用pci_legacy_init(),由于X86都使用了ACPI,所以不研究
pcibios_fixup_peer_bridges();
x86_init.pci.init_irq(); // ACPI PCI INIT初始化
pcibios_init();
pci_legacy_init其实是以前旧版本的PCI检测程序,资料较多,可惜现在都用了ACPI部分
x86默认初始化接口
x86_init.pci.init() // 前边掉的是x86_init.pci.arch_init(),现在有这个初始化
// arch/x86/kernel/x86_init.c
struct x86_init_ops x86_init __initdata = {
.pci = {
.init = x86_default_pci_init,
.init_irq = x86_default_pci_init_irq,
.fixup_irqs = x86_default_pci_fixup_irqs,
},
}
#ifdef CONFIG_PCI
# ifdef CONFIG_ACPI
# define x86_default_pci_init pci_acpi_init
# else
# define x86_default_pci_init pci_legacy_init
# endif
# define x86_default_pci_init_irq pcibios_irq_init
# define x86_default_pci_fixup_irqs pcibios_fixup_irqs
#endif
第一阶段初始化内核打印
其实就在 linux-git\arch\x86\pci\direct.c : pci_direct_init 打印了一句
[ 0.149685] PCI: Using configuration type 1 for base access