1.2.8判断pcie设备是否支持雷电技术
Intel具有一种基于Thunderbolt技术的PCIE变体,它结合了DisplayPort和PCIe协议,与Mini DisplayPort兼容。
Thunderbolt技术融合两种通信方法或者说协议,其中PCI Express用于数据传输,可以连接几乎任何类型的设备,DisplayPort用于显示,能同步传输1080p乃至超高清视频和最多八声道音频。
因此代码只在intel生产的设备中进行判别。
set_pcie_thunderbolt()
while ((vsec = pci_find_next_ext_capability(dev, vsec,
PCI_EXT_CAP_ID_VNDR))) {
pci_read_config_dword(dev, vsec + PCI_VNDR_HEADER, &header);
/* Is the device part of a Thunderbolt controller? */
//设备是否具有雷电控制器
if (dev->vendor == PCI_VENDOR_ID_INTEL &&
PCI_VNDR_HEADER_ID(header) == PCI_VSEC_ID_INTEL_TBT) {
dev->is_thunderbolt = 1;
return;
}
}
其中有
#define PCI_VNDR_HEADER_ID(x) ((x) & 0xffff)
#define PCI_VSEC_ID_INTEL_TBT 0x1234 //雷电接口
1.2.9修复某些特殊的bug
对于某些bug,只存在于特定体系或设备,无法在此处进行列举,因此提供一个hook用于修复特殊设备的bug。而hook通过内核配置情况进行挂载。
pci_fixup_device()
//查找是否存在厂商号设备号相同的情况
for (; f < end; f++)
if ((f->class == (u32) (dev->class >> f->class_shift) ||
f->class == (u32) PCI_ANY_ID) &&
(f->vendor == dev->vendor ||
f->vendor == (u16) PCI_ANY_ID) &&
(f->device == dev->device ||
f->device == (u16) PCI_ANY_ID)) {
void (*hook)(struct pci_dev *dev);
//获取hook函数指针,用于修复特定bug
#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
hook = offset_to_ptr(&f->hook_offset);
#else
hook = f->hook;
#endif
calltime = fixup_debug_start(dev, hook);
hook(dev);
fixup_debug_report(dev, calltime, hook);
}
其中hook可有由
#define DECLARE_PCI_FIXUP_CLASS_EARLY(vendor, device, class, \
class_shift, hook) \
DECLARE_PCI_FIXUP_SECTION(.pci_fixup_early, \
hook, vendor, device, class, class_shift, hook)
等宏进行挂载。
- 举个例子
arch\x86\pci
static void pci_early_fixup_cyrix_5530(struct pci_dev *dev)
{
u8 r;
/* clear 'F4 Video Configuration Trap' bit */
pci_read_config_byte(dev, 0x42, &r);
r &= 0xfd;
pci_write_config_byte(dev, 0x42, r);
}
//注册回调函数到pci_early_fixup段中,用于修复x86平台下该设备出现的bug。
DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_CYRIX, PCI_DEVICE_ID_CYRIX_5530_LEGACY,
pci_early_fixup_cyrix_5530);
DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_CYRIX, PCI_DEVICE_ID_CYRIX_5530_LEGACY,
pci_early_fixup_cyrix_5530);
通过DECLARE_PCI_FIXUP_EARLY宏将pci_early_fixup_cyrix_5530回调函数注册到pci_fixup_early段中,在通过hook函数指针调用并执行函数解决x86平台下5530设备特有的错误情况。
1.2.10 设置command寄存器
1.2.10.1 错误情况禁止IO空间和内存空间
//没有识别正确的
if (dev->non_compliant_bars) {
pci_read_config_word(dev, PCI_COMMAND, &cmd);
if (cmd & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) {
pci_info(dev, "device has non-compliant BARs; disabling IO/MEM decoding\n");
cmd &= ~PCI_COMMAND_IO;
cmd &= ~PCI_COMMAND_MEMORY;
pci_write_config_word(dev, PCI_COMMAND, cmd);
}
}
COMMAND[0] :IO SPACE位
该位表示PCI设备是否响应I/O请求,为1时响应,0时不响应。
COMMAND[1]: Memory Space位
该位表示PCI设备是否响应存储器请求,为1时响应,0时不响应。
1.2.10.2 判断command寄存器中断禁止位是否可写
pci_intx_mask_broken()
u16 orig, toggle, new;
pci_read_config_word(dev, PCI_COMMAND, &orig);
toggle = orig ^ PCI_COMMAND_INTX_DISABLE;
pci_write_config_word(dev, PCI_COMMAND, toggle);
pci_read_config_word(dev, PCI_COMMAND, &new);
pci_write_config_word(dev, PCI_COMMAND, orig);
//PCI_COMMAND_INTX_DISABLE是预留位并在PCIr2.3版本是只读的,因此严格输出
//如果他是不可写的,则这个设备没有损坏。
if (new != toggle)
return 1;
return 0;
COMMAND[10] :interrupt Disable位
复位值为0,该位为1时,PCI设备不能通过INTx信号向HOST主桥提交中断请求,为0时可以使用INTx信号提出请求。当PCI设备使用MSI中断方式提交中断请求时,该位将被置为1。
1.2.11 根据不同头类型做初始化
1.2.11.1标准头
1.2.11.1.1获取设备信息
获取设备中断信息和BAR空间信息。
switch (dev->hdr_type) { /* header type */
case PCI_HEADER_TYPE_NORMAL: /* standard header */
//错误情况
if (class == PCI_CLASS_BRIDGE_PCI)
goto bad;
//获取中断号和中断引脚
pci_read_irq(dev);
//获取基地址
pci_read_bases(dev, 6, PCI_ROM_ADDRESS);
//获取子厂商号设备号
pci_subsystem_ids(dev, &dev->subsystem_vendor, &dev->subsystem_device);
(1)中断信息
包括中断号,中断引脚。
pci_read_irq()
pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &irq);
dev->pin = irq;
if (irq)
pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &irq);
dev->irq = irq;
(2)BAR空间信息
包括BAR空间大小,空间基地址,空间类型(IO/内存),空间位数(32位,64位)等。
pci_read_bases函数调用__pci_read_base函数来获取这些信息。
__pci_read_base()
(2.1)默认为 32位PCI时,获取PCI空间大小
pci_read_config_dword(dev, pos, &l);
pci_write_config_dword(dev, pos, l | mask);
pci_read_config_dword(dev, pos, &sz);
pci_write_config_dword(dev, pos, l);
- (2.2) 通过decode_bar()函数判别PCIBAR空间类型*
- 判断是否IO空间类型。
//判断是否IO空间
if ((bar & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_IO) {
flags = bar & ~PCI_BASE_ADDRESS_IO_MASK;
flags |= IORESOURCE_IO;
return flags;
}
通过BAR基地址的bit[0]可判别BAR空间类型为IO空间还是内存空间,1表示IO空间,0表示内存空间。
bit[1,2]则表示了BAR空间为32位还是64位。
bit[3]表示BAR是否支持预取。
其中bit[1,2]=01在linux4.x代码中代表1M这种类型。
- 判断内存空间位数
//判断内存空间位数
mem_type = bar & PCI_BASE_ADDRESS_MEM_TYPE_MASK;
switch (mem_type) {
case PCI_BASE_ADDRESS_MEM_TYPE_32:
break;
case PCI_BASE_ADDRESS_MEM_TYPE_1M:
/* 1M mem BAR treated as 32-bit BAR */
break;
case PCI_BASE_ADDRESS_MEM_TYPE_64:
flags |= IORESOURCE_MEM_64;
break;
default:
/* mem unknown type treated as 32-bit BAR */
break;
}
- 64位的处理
//64位重新获取设备基地址和大小
pci_read_config_dword(dev, pos + 4, &l);
pci_write_config_dword(dev, pos + 4, ~0);
pci_read_config_dword(dev, pos + 4, &sz);
pci_write_config_dword(dev, pos + 4, l);
获取地址和大小后还需判断其是否超过体系所支持的位数,32位下是不能支持64位PCI设备的,PCI空间大小也是不能超过4G的。
- 测试BAR空间映射是否正确。
pcibios_bus_to_resource(dev->bus, res, ®ion);
pcibios_resource_to_bus(dev->bus, &inverted_region, res);
pcibios_bus_to_resource函数用于总线地址转换到资源地址(用于CPU的物理地址)。
pcibios_resource_to_bus执行相反操作,如果,相互转换的值不正确,则不能使用该设备。
1.2.11.1.2 对于ATA控制器的特殊设置
传统模式ATA控制器具有固定地址。且BAR0-3的数据在某些情况下是无效的。
classCode寄存器用于判断设备类别。
#define PCI_CLASS_STORAGE_IDE 0x0101
通过base class=01,subclass =01,可以确定为IDE类型存储器
对于IDE控制器类型的PCI设备,又通过interface寄存器字段进行了细分:
interface:
bit7:确定是否为主IDE设备
bit3:可编程指示器(次通道)
bit2:操作模式(次通道)
bit1:可编程指示器(主通道)
bit0:操作模式(主通道)
根据PCI IDE Controller Specification Revision 1.0文档可知
对于主通道来说,命令寄存器被固定为1f0h-1f7h,其控制块地址为3fh,
次通道命令寄存器地址170h-177h,控制寄存器376h.
if (class == PCI_CLASS_STORAGE_IDE) {
u8 progif;
pci_read_config_byte(dev,, &progif);
//主通道IDE控制器
if ((progif & 1) == 0) {
region.start = 0x1F0;
region.end = 0x1F7;
res = &dev->resource[0];
res->flags = LEGACY_IO_RESOURCE;
pcibios_bus_to_resource(dev->bus, res, ®ion);
pci_info(dev, "legacy IDE quirk: reg 0x10: %pR\n",
res);
region.start = 0x3F6;
region.end = 0x3F6;
res = &dev->resource[1];
res->flags = LEGACY_IO_RESOURCE;
pcibios_bus_to_resource(dev->bus, res, ®ion);
pci_info(dev, "legacy IDE quirk: reg 0x14: %pR\n",
res);
}
//次通道IDE控制器
if ((progif & 4) == 0) {
region.start = 0x170;
region.end = 0x177;
res = &dev->resource[2];
res->flags = LEGACY_IO_RESOURCE;
pcibios_bus_to_resource(dev->bus, res, ®ion);
pci_info(dev, "legacy IDE quirk: reg 0x18: %pR\n",
res);
region.start = 0x376;
region.end = 0x376;
res = &dev->resource[3];
res->flags = LEGACY_IO_RESOURCE;
pcibios_bus_to_resource(dev->bus, res, ®ion);
pci_info(dev, "legacy IDE quirk: reg 0x1c: %pR\n",
res);
}
}
break;
1.2.11.2桥头
PCI-PCI桥若要求译码(比如透明桥),桥的编程接口代码必须是0x01
case PCI_HEADER_TYPE_BRIDGE: /* bridge header */
if (class != PCI_CLASS_BRIDGE_PCI)
goto bad;
pci_read_irq(dev);
dev->transparent = ((dev->class & 0xff) == 1);
pci_read_bases(dev, 2, PCI_ROM_ADDRESS1);
//查询是否支持热插拔
set_pcie_hotplug_bridge(dev);
//查找capability中子系统的厂商号和设备号
pos = pci_find_capability(dev, PCI_CAP_ID_SSVID);
if (pos) {
pci_read_config_word(dev, pos + PCI_SSVID_VENDOR_ID, &dev->subsystem_vendor);
pci_read_config_word(dev, pos + PCI_SSVID_DEVICE_ID, &dev->subsystem_device);
}
break;
对于热插拔
set_pcie_hotplug_bridge()
pcie_capability_read_dword(pdev, PCI_EXP_SLTCAP, ®32);
if (reg32 & PCI_EXP_SLTCAP_HPC)
pdev->is_hotplug_bridge = 1;
其中
#define PCI_EXP_SLTCAP 20 /* Slot Capabilities */
#define PCI_EXP_SLTCAP_HPC 0x00000040 /* Hot-Plug Capable */
通过查询pcie_3.0总线规范,可以看到偏移为14h的寄存器中有关于是否支持热插拔的状态位。
1.2.11.2cardBus桥头
case PCI_HEADER_TYPE_CARDBUS: /* CardBus bridge header */
if (class != PCI_CLASS_BRIDGE_CARDBUS)
goto bad;
pci_read_irq(dev);
pci_read_bases(dev, 1, 0);
pci_read_config_word(dev, PCI_CB_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);
pci_read_config_word(dev, PCI_CB_SUBSYSTEM_ID, &dev->subsystem_device);
break;
结束
pci_setup_device()函数完成了对单个设备的设备和检测,并将获取的信息存取设备结构体中,用于后期具体设备的使用。