pci枚举初始化部分

Posted weiyoutongxing

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了pci枚举初始化部分相关的知识,希望对你有一定的参考价值。

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)

等宏进行挂载。

  • 举个例子
    archx86pci
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
");
   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, &region);
 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, &region);
    pci_info(dev, "legacy IDE quirk: reg 0x10: %pR
",
      res);
    region.start = 0x3F6;
    region.end = 0x3F6;
    res = &dev->resource[1];
    res->flags = LEGACY_IO_RESOURCE;
    pcibios_bus_to_resource(dev->bus, res, &region);
    pci_info(dev, "legacy IDE quirk: reg 0x14: %pR
",
      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, &region);
    pci_info(dev, "legacy IDE quirk: reg 0x18: %pR
",
      res);
    region.start = 0x376;
    region.end = 0x376;
    res = &dev->resource[3];
    res->flags = LEGACY_IO_RESOURCE;
    pcibios_bus_to_resource(dev->bus, res, &region);
    pci_info(dev, "legacy IDE quirk: reg 0x1c: %pR
",
      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, &reg32);
 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的寄存器中有关于是否支持热插拔的状态位。
技术分享图片

bit6 = 1时,表示设备支持热插拔。
技术分享图片

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()函数完成了对单个设备的设备和检测,并将获取的信息存取设备结构体中,用于后期具体设备的使用。


































以上是关于pci枚举初始化部分的主要内容,如果未能解决你的问题,请参考以下文章

RK3399平台开发系列讲解(PCI/PCI-E)5.55PCIE RC枚举EP过程

linux pci 驱动小结

Linux内核(13) - 子系统的初始化之以PCI子系统为例

)

Linux的PCI驱动分析

Linux驱动之PCI