1. 程式人生 > >pci列舉初始化部分(1)

pci列舉初始化部分(1)

基於linux-4.20-rc3原始碼分析

1 .掃描所有PCI裝置並檢測,填充裝置結構體

static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn)
{
 struct pci_dev *dev;
 u32 l;

 //查詢PCI裝置廠商號和裝置號,以判斷裝置是否發生異常
 if (!pci_bus_read_dev_vendor_id(bus, devfn, &l, 60*1000))
  return NULL;

//分配裝置結構體
 dev = pci_alloc_dev(bus);
 if (!dev)
  return NULL;

 dev->devfn = devfn;
 dev->vendor = l & 0xffff;                  //獲取廠商號
 dev->device = (l >> 16) & 0xffff;    //獲取裝置號

//設定連結串列節點
 pci_set_of_node(dev);

//掃描所有裝置並設定,和獲取資訊
 if (pci_setup_device(dev)) {
  pci_bus_put(dev->bus);
  kfree(dev);
  return NULL;
 }

 return dev;
}

其中pci_setup_device(dev)函式對掛載在該總線上所有的裝置進行檢測並獲取相關資料,並裝置資訊進行填充。對於有些需特殊處理的裝置也進行了特殊處理,達到儘量相容新老裝置的目的。

1.1查詢裝置廠商號和裝置號

pci_bus_read_dev_vendor_id()

#ifdef CONFIG_PCI_QUIRKS
 struct pci_dev *bridge = bus->self;

 /*
  * Certain IDT switches have an issue where they improperly trigger
  * ACS Source Validation errors on completions for config reads.
  */
  //某些IDT交換機有一個問題,即它們在完成配置讀取時錯誤地觸發ACS源驗證錯誤。
 if (bridge && bridge->vendor == PCI_VENDOR_ID_IDT &&
     bridge->device == 0x80b5)
  return pci_idt_bus_quirk(bus, devfn, l, timeout);
#endif

 return pci_bus_generic_read_dev_vendor_id(bus, devfn, l, timeout);

該函式主要讀取PCI裝置的廠商號和裝置號,如果讀取出來包含全0,全1之類資料,這判斷為PCI裝置異常不再進行下一步操作。
對於某些IDT交換機裝置,可能存在在讀取裝置資訊時觸發ACS驗證錯誤,此時需要進行特殊操作,如果程式碼允許在IDT交換機上請一定配置PCI_QUIRKS選項。
ACS:安全接入控制器,接入伺服器(Access Server)又稱網路接入伺服器NAS或遠端接入伺服器RAS,它是位於公用電話網(PSTN/ISDN)與IP網之間的一種遠端訪問接入裝置。

  • pci_bus_generic_read_dev_vendor_id()
    該函式用於獲取裝置廠商號,並對錯誤的狀態進行識別,對需重試獲取的裝置進行重試獲取資訊,並通過超時加以限制。
 if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, l))
  return false;

 /* Some broken boards return 0 or ~0 if a slot is empty: */
//如果槽為空時會返回0或者~0
 if (*l == 0xffffffff || *l == 0x00000000 ||
     *l == 0x0000ffff || *l == 0xffff0000)
  return false;

//具有配置重試機制的裝置進行重試讀取廠商號
 if (pci_bus_crs_vendor_id(*l))
  return pci_bus_wait_crs(bus, devfn, l, timeout);

 return true;

1.2獲取裝置資訊並設定

1.2.1獲取裝置頭資訊

pci_hdr_type()

 pci_read_config_byte(dev, PCI_HEADER_TYPE, &hdr_type);

1.2.2 判斷是否為pcie裝置

//判斷是否為pcie裝置
 pos = pci_find_capability(pdev, PCI_CAP_ID_EXP);
 if (!pos)
  return;

 pdev->pcie_cap = pos;
 pci_read_config_word(pdev, pos + PCI_EXP_FLAGS, &reg16);
 pdev->pcie_flags_reg = reg16;
 pci_read_config_word(pdev, pos + PCI_EXP_DEVCAP, &reg16);
 pdev->pcie_mpss = reg16 & PCI_EXP_DEVCAP_PAYLOAD;

通過state暫存器獲取capability的有效性,並通過capability首地址順勢查詢是否有PCIE capatility結構體存在,從而判斷該裝置是否時pcie裝置

1.2.2.1 查詢裝置的capability,判斷其是否為pcie裝置

pci_find_capability()

//判斷capability有效性,有效則獲取capability表起始地址
 pos = __pci_bus_find_cap_start(dev->bus, dev->devfn, dev->hdr_type);

 //獲取ID為cap的capability的偏移
 if (pos)
  pos = __pci_find_next_cap(dev->bus, dev->devfn, pos, cap);
  • __pci_bus_find_cap_start()
 //判斷capabilityPointer暫存器中的值是否有效
 pci_bus_read_config_word(bus, devfn, PCI_STATUS, &status);
 if (!(status & PCI_STATUS_CAP_LIST))
  return 0;

 switch (hdr_type) {
 case PCI_HEADER_TYPE_NORMAL:
 case PCI_HEADER_TYPE_BRIDGE:
  return PCI_CAPABILITY_LIST;                 //普通裝置偏移
 case PCI_HEADER_TYPE_CARDBUS:
  return PCI_CB_CAPABILITY_LIST;           //橋裝置偏移
 }
  • __pci_find_next_cap()
//pos為capatibility結構首地址
 pci_bus_read_config_byte(bus, devfn, pos, &pos);

//目前ttl = PCI_FIND_CAP_TTL = 48;
 while ((*ttl)--) {
  if (pos < 0x40)
   break;
  pos &= ~3;
  pci_bus_read_config_word(bus, devfn, pos, &ent);

  id = ent & 0xff;
  if (id == 0xff)
   break;
  if (id == cap)            //獲取ID為PCI_CAP_ID_EXP的capatility結構體偏移
   return pos;
  pos = (ent >> 8);
 }

1.2.3 新增槽結構體到連結串列

pci_dev_assign_slot()

 struct pci_slot *slot;

 mutex_lock(&pci_slot_mutex);
 list_for_each_entry(slot, &dev->bus->slots, list)
  if (PCI_SLOT(dev->devfn) == slot->number)
   dev->slot = slot;
 mutex_unlock(&pci_slot_mutex);

1.2.4設定驅動名稱

 dev_set_name(&dev->dev, "%04x:%02x:%02x.%d", pci_domain_nr(dev->bus),
       dev->bus->number, PCI_SLOT(dev->devfn),
       PCI_FUNC(dev->devfn));

1.2.5獲取裝置類別等資訊

//獲取裝置類別
 class = pci_class(dev);            //讀取PCI空間偏移0x8處資料。

 dev->revision = class & 0xff;                                //版本號
 dev->class = class >> 8;   /* upper 3 bytes */       //裝置型別

1.2.6 boot階段列印PCI資訊

if (pci_early_dump)
early_dump_pci_device(dev);

1.2.7 獲取pci配置空間大小

pci_cfg_space_size()
通過判斷裝置型別從而獲取配置空間大小。
pci和pxi模式1的裝置的配置空間大小為256byte,PXI模式2和pcie裝置的配置空間大小為4096byte