Aros/Developer/PCIDriversDev
rom/hidds/hidd.pci 中的原始碼
pci.hidd 在駐留模組的引導過程中被初始化。當 pci.hidd 初始化時,它會探測 PCI 匯流排以查詢裝置。pci.hidd 不是裝置驅動程式維護程式。它僅維護 PCI 匯流排。此外,pci.hidd 以非常高的優先順序啟動,因此它只能初始化優先順序高於自身驅動程式。
pci.hidd 是一個類集合,用於維護系統中可用的所有 PCI 裝置。所有裝置屬性都可透過適當的 OOP_Object 屬性獲得,不應手動更改(儘管仍然可以透過 pcidriver 類的六個公共方法獲得)。
如何請求 PCI 裝置?
為了查詢指定的 PCI 裝置,需要使用主 PCI 類(IID_Hidd_PCI)的 moHidd_PCI_EnumDevices 方法。它接受兩個引數。第一個是指向 struct Hook 的指標,定義了針對每個符合給定要求的 PCI 裝置呼叫的回撥函式。第二個引數(可以為 NULL)是指向 struct TagItem[] 的指標,定義了必須滿足的要求。可以組合使用 VendorID、ProductID、RevisionID、Interface、Class、Subclass、SubsystemVendorID、SubsystemID(有關詳細資訊,請參見 include/hidd/pci.h)。如果給出 NULL,則回撥函式將針對 pci 類看到的每個 PCI 裝置呼叫。以下程式碼可用於查詢 PCI 裝置
/* This hook will be called for every PCI device that matches the given requirements */
AROS_UFH3(void, Callback,
AROS_UFHA(struct Hook *, hook, A0),
AROS_UFHA(OOP_Object *, obj, A2),
AROS_UFHA(APTR, msg, A1))
{
AROS_USERFUNC_INIT
/* Do whatever here with the PCIDevice object stored in obj pointer */
AROS_USERFUNC_EXIT
}
/* Hook defining our callback */
static const struct Hook PCIHook = {
h_Entry : (APTR)Callback
};
void Query()
{
OOP_Object *o; /* Keep PCI class object */
/* Get only VGA compatible video cards */
struct TagItem tags[] = {
{ tHidd_PCI_Class, 3 },
{ tHidd_PCI_SubClass, 0 },
{ tHidd_PCI_Interface, 0 },
{ TAG_DONE, 0UL }
};
/* Create instance of pci class */
o = OOP_NewObject(NULL, CLID_Hidd_PCI, NULL);
if (o)
{
/* Enumerate through all PCI devices */
HIDD_PCI_EnumDevices(o, &PCIHook, NULL);
/* Enumerate through devices that met requirements only */
HIDD_PCI_EnumDevices(o, &PCIHook, &tags);
[do whatever you want with PCI devices]
/* Don't need PCI object anymore */
OOP_DisposeObject(o);
}
}
簡單、高效、美觀:)
如何處理 PCI 裝置物件?
一旦知道了指向 pci 裝置物件的指標,就可以詢問 PCI 裝置的屬性,以及更改一些裝置屬性。Bus、Dev 和 Sub 屬性定義了 PCI 裝置的物理地址,如處理此裝置的匯流排驅動程式(作為 Driver 屬性提供)所見。對於 PCI-to-PCI 橋接器(參見 isBridge 屬性),還有一些其他屬性可用(另一方面,一些其他屬性,例如基本地址 2 到 5 不可用)。最常用的可能是
aHidd_PCIDevice_Base[0..5] - PCI base addresses of given device aHidd_PCIDevice_Size[0..5] - sizes of PCI memory/IO areas aHidd_PCIDevice_Type[0..5] - type of given area.
如果 Type 屬性中的 ADDRB_IO 位被設定,則該區域為 IO 區域。否則,它是一個記憶體區域,可能是可預取記憶體(ADDRB_PREFETCH 位設定)。
此外,驅動程式可以檢查給定 PCI 裝置是否完全解碼了 I/O 或 MEM(isIO、isMEM 屬性),是否啟用了 BusMaster(isMaster 屬性),裝置是否為 VGA 調色盤更改嗅探 PCI 匯流排(paletteSnoop 屬性)。最後,可以檢查裝置是否支援 66 MHz PCI 匯流排(is66MHz 屬性)。
注意,根據驅動程式的要求,isIO、isMEM、isMaster 和 paletteSnoop 屬性也可以被設定。
所有屬性都可以透過 OOP_GetAttr 呼叫獲得(唉,我們確實缺少 OOP_GetAttrs(obj, struct TagItem **attributes_to_get_with_one_call)!)),其中一些屬性可以透過 OOP_SetAttrs 呼叫設定(有關詳細資訊,請參見 hidd/pci.h 包含檔案)。請記住,在開始處理屬性之前,必須獲得 IID_Hidd_PCIDevice AttrBase(請不要忘記在不再需要它時釋放它)。
PCIDriver 類(使用者端)
PCIDevice 類的一個只讀屬性是 PCIDriver 類指標。它指向處理給定 PCI 裝置物件的硬體驅動程式。如後文所述,系統中可能有多個驅動程式同時工作。
驅動程式類有一個重要的屬性 - aHidd_PCIDriver_DirectBus。它是隻讀的,如果它被設定為 TRUE,則驅動程式處理直接對映到 CPU 空間的 PCI 匯流排。DirectBus 裝置可能是由本機 AROS 處理的 PC 中典型的 PCI 匯流排。典型的間接 PCI 匯流排將是在 Linux 下處理的 PCI 匯流排(在 Linux 上託管的 AROS 上沒有對 PCI 裝置的物理直接訪問)。根據 DirectBus 屬性,可以使用或應該使用一些方法。
在處理非 DirectBus PCI 驅動程式時,可以使用 HIDD_PCIDriver_MapPCI 和 HIDD_PCIDriver_UnmapPCI 方法訪問 PCI 裝置的記憶體範圍。第一個方法嘗試將 PCI 記憶體空間對映到 CPU 記憶體空間(例如,在 Linux 的情況下使用 /dev/mem 上的 mmap),以便可以訪問給定的 PCI 記憶體範圍。UnmapPCI 方法釋放之前使用此方法建立的對映。
此外,對於非 DirectBus PCI 驅動程式,可以使用 AllocPCIMem 和 FreePCIMem 來保留/釋放 PCI 裝置可訪問的記憶體,並將其對齊到頁邊界。如果這些方法未實現或沒有可用於 PCI 裝置的記憶體,AllocPCIMem 將返回 (APTR)-1。
對於 DirectBus 裝置,仍然可以使用上述呼叫方法。然後,MapPCI 等效於 HIDD_PCIDriver_PCItoCPU 呼叫,它只是將 PCI 裝置看到的地址轉換為 CPU 看到的地址。CPUtoPCI 向另一個方向工作。
驅動程式建立
為了編寫 PCI 硬體驅動程式,必須建立一個從 CLID_Hidd_PCIDriver 類派生的類。這簡化了驅動程式的工作,因為只需要實現幾個方法:PCIDriver::New()
此方法應向 msg->attrList 新增一些屬性,並將 ::New 訊息傳遞給超類。aHidd_Name 和 aHidd_HardwareName 在這裡受到歡迎。此外,如果驅動程式不在直接訪問總線上工作,則應將 aHidd_PCIDriver_DirectBus 重置為 FALSE(否則,超類會將其設定為 TRUE)。
請注意,在最壞的情況下(作者不想提供 aHidd_Name 和 aHidd_HardwareName),可以跳過 ::New 的實現。PCIDriver::ReadConfigLong() 和 PCIDriver::WriteConfigLong()
這兩個方法必須在驅動程式類中定義。否則,超類將抱怨錯誤訊息。所有其他用於訪問 PCI 配置空間(讀/寫字/位元組)的方法都可以由驅動程式類實現,但不必實現。由於所有方法都是虛擬的,因此超類將完成魔法(它將使用 ReadConfigLong 和 WriteConfigLong 方法來以讀寫模式訪問字和位元組)。
此外,MapPCI/UnmapPCI 和 CPUtoPCI/PCItoCPU 可能需要重寫(預設情況下,如果為間接匯流排,則始終返回 0xffffffff,如果為直接匯流排,則返回與給定地址相同的地址)。將驅動程式類新增到系統
成功建立驅動程式類後,可以將其指標傳遞給主 pci 類。可以透過以下方式完成(假設 cl 是指向新建立的驅動程式類的指標)
[...]
struct pHidd_PCI_AddHardwareDriver msg;
OOP_Object *pci;
msg.driverClass = cl;
msg.mID = OOP_GetMethodID(IID_Hidd_PCI, moHidd_PCI_AddHardwareDriver);
pci = OOP_NewObject(NULL, CLID_Hidd_PCI, NULL);
if (pci)
{
OOP_DoMethod(pci, (OOP_Msg)&msg);
OOP_DisposeObject(pci);
}
[...]
完成。然後,pci 子系統將使用傳遞的類指標(注意:由於直接傳遞類指標,驅動程式類不必是公共的)來掃描使用此硬體驅動程式處理的 PCI 匯流排。從這一點起,由新新增的驅動程式處理的 PCI 裝置可用於任何用途。從系統中刪除驅動程式類
驅動程式可以使用 RemHardwareDriver 呼叫請求從 PCI 子系統中刪除。可以,但不必滿足其查詢。如果除希望被刪除的驅動程式本身之外,還有其他使用者使用 PCI 子系統,則驅動程式不會被刪除。當 RemHardwareDriver 呼叫成功時,可以刪除驅動程式類。
為什麼需要這個可插拔驅動程式?
想象一個 PCI 裝置(任何型別),它有自己的 PCI 匯流排。裝置驅動程式瞭解此匯流排,並希望與其他驅動程式(系統使用者)共享此匯流排。不幸的是,只有這個特定的裝置驅動程式知道如何處理這個額外的 PCI 匯流排。當它建立一個知道如何與之通訊的驅動程式類並將此驅動程式類新增到 pci 子系統時,此 PCI 匯流排將成為整個系統的一部分,從現在開始,任何人都可以訪問它。
pciclass.c pcideviceclass.c pciutil.c
isPCIDeviceAvailable PCI__Hidd_PCI__AddHardwareDriver PCI__Hidd_PCI__EnumDevices PCI__Hidd_PCI__RemHardwareDriver PCI__Root__New PCI__Root__Dispose PCI_ExpungeClass PCI_InitClass
setLong setWord setByte getLong getWord getByte findCapabilityOffset dispatch_generic dispatch_base dispatch_type dispatch_size dispatch_pci2pcibridge dispatch_capability PCIDev__Root__Get PCIDev__Root__Set
getPCIClassDesc sizePCIBaseReg
首先,它使用 64 位 BAR 地址,而 PCI 不支援這些地址(或者我錯了?)。如果我理解正確,根據 PCI 規範,一個 64 位 BAR 會佔用下一個 BAR 的空間,並將它用作 64 位地址中的較高位。但是,Aros 沒有意識到這一點,而是將下一個 BAR 視為一個新的 BAR。
其次,在 PCI.hidd 中,BAR 大小(32 位或 64 位)是在獲取 BAR 地址之後檢查的,因此從某種意義上說,它是反向進行的……因為大小(32 位或 64 位)只能在 sizePCIBaseReg 函式中檢查,而該函式應該被分解以檢測大小(32 位或 64 位)以及是 I/O 還是 MEM 以及使用的 I/O/MEM 大小。
據我所知,從 Amiga 軟體的角度來看,Amiga PCI 硬體是一個大的(用於 PCI 的 256M+ 記憶體空間)I/O 自動配置裝置。Zorro 板只能使用中斷級別 2 或 6,所以我假設所有 PCI 中斷始終路由到同一個物理 680x0 中斷。netbsd amiga pci 驅動似乎解釋了大多數這些內容(src/sys/arch/amiga/pci)。