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);
boot階段用於打印pci設備所有配置空間信息

1.2.7 獲取pci配置空間大小

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM