pci列舉初始化部分(1)
阿新 • • 發佈:2018-11-29
基於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, ®16);
pdev->pcie_flags_reg = reg16;
pci_read_config_word(pdev, pos + PCI_EXP_DEVCAP, ®16);
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