Aros/Developer/PCIDriversDev
Introduction
[edit | edit source]sources in rom/hidds/hidd.pci
pci.hidd IS initialized as part of bootstrap of resident modules. When the pci.hidd inits, it probes the PCI bus for devices. pci.hidd is not a device driver maintainer. It maintains PCI bus only. Besides, pci.hidd is started with very high priority and thus it would be able to initialize only this drivers which has higher priority then this hidd itself.
The pci.hidd is a collection of classes used to maintain all PCI devices available in the system. All device properties are available through appropriate OOP_Object properties and should not be changed per hand (although it is still available through six public methods of pcidriver class).
How to ask for a PCI device?
In order to query for specified PCI device, the moHidd_PCI_EnumDevices method of the main PCI class (IID_Hidd_PCI) is to be used. It takes two parameters. The first one is the pointer to struct Hook, defining the call-back function called for every PCI device that match given requirements. Second parameter (may be NULL) is pointer to struct TagItem[] defining the requirements that has to be met. Any combination of VendorID, ProductID, RevisionID, Interface, Class, Subclass, SubsystemVendorID, SubsystemID may be used (see the include/hidd/pci.h for details). If NULL is given, the call-back function will be called for every single PCI device seen by the pci class. The following code may be used to find the PCI device:
/* 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); } }
Simple, efficient, nice :)
What to do with PCI device object?
Once the pointer to pci device object is known, the PCI device may be asked for its properties, as well as some of the device properties may be changed. The Bus, Dev and Sub properties define the physical address of PCI device, as seen by the bus driver handling this device (available as Driver property). In case of PCI-to-PCI bridges (see isBridge property) , there are some additional properties available (some others, like base addresses 2 to 5 are unavailable on the other hand). Most commonly used were probably:
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.
If bit ADDRB_IO in Type property is set, the region is an IO region. Otherwise it is a memory region, which may be of prefetchable memory (bit ADDRB_PREFETCH set).
Additionally, the driver may check, whether I/O or MEM is decoded by given PCI device at all (isIO, isMEM properties), whether BusMaster has been enabled (isMaster property), whether the device does snoop PCI bus for VGA palette changes (paletteSnoop property). Finally, it is possible to check whether the device does support 66 MHz PCI bus (is66MHz property).
Note, that depending on driver requirements, isIO, isMEM, isMaster and paletteSnoop properties may be also set.
All properties are obtainable through OOP_GetAttr call (sigh, we are really missing the OOP_GetAttrs(obj, struct TagItem **attributes_to_get_with_one_call)!!!) and some of them are setable through OOP_SetAttrs call (see hidd/pci.h include for details). Please also remember, that before work with attributes is done, the IID_Hidd_PCIDevice AttrBase has to be obtained (please don't forget to release it when it's not needed anymore).
PCIDriver class (user side)
One of the read-only attributes of PCIDevice class, is the PCIDriver class pointer. It points to the hardware driver which handles given PCI device object. As it will be seen later, there may be more than one driver working at the same time in the system.
The driver class has one important attribute - aHidd_PCIDriver_DirectBus. It is read-only and if it is set to TRUE, the driver handles PCI bus which is directly mapped within the CPU space. DirectBus device may be for example typical PCI bus in PC handled by native AROS. Typical indirect PCI bus will be then PCI bus handled under Linux (there is no physical direct access to the PCI devices on hosted AROS on Linux). Depending on the DirectBus property, some methods may or should be used.
While working with not DirectBus PCI driver, the HIDD_PCIDriver_MapPCI and HIDD_PCIDriver_UnmapPCI methods may be used to access memory ranges of PCI device. The first method tries to map PCI memory space to CPU memory space (using for example mmap on /dev/mem in case of Linux) so that the given PCI memory range may be accessed. UnmapPCI method frees mapping created previously with this method.
Additionally, in case of not DirectBus PCI driver, AllocPCIMem and FreePCIMem can be used in order to reserve/free memory accessible by PCI devices and aligned to the page boundary. If these methods are not implemented or there is no memory available for PCI devices, AllocPCIMem will return (APTR)-1.
In case of DirectBus devices, the above called methods are still usable. The MapPCI is then equivalent to HIDD_PCIDriver_PCItoCPU call and simply translates the address seen by PCI device to address seen by CPU. The CPUtoPCI works in other direction.
Driver creation
In order to write PCI hardware driver, one has to create class deriving from the CLID_Hidd_PCIDriver class. That simplifies the work on driver, as only few methods have to be implemented: PCIDriver::New()
This method should add some attributes to the msg->attrList and pass the ::New message to the superclass. The aHidd_Name and aHidd_HardwareName are welcomed here. Additionally, if driver doesn't work on direct access bus, it should reset the aHidd_PCIDriver_DirectBus to FALSE (otherwise it is set to TRUE by the superclass).
Please note that in worst case (author don't want to provide aHidd_Name and aHidd_HardwareName), the implementation of ::New may be skipped. PCIDriver::ReadConfigLong() and PCIDriver::WriteConfigLong()
These two methods HAVE TO be defined in driver class. Otherwise superclass will complain with error messages. All other methods used to access the PCI config space (Read/Write of Word/Byte) may be implemented by driver class but doesn't have to. As all methods are virtual, superclass will do the magic (it will use ReadConfigLong and WriteConfigLong methods to access words and bytes in both read and write mode).
Additionally , the MapPCI/UnmapPCI and CPUtoPCI/PCItoCPU may require rewriting (default is that in case of indirect bus they return 0xffffffff always and in case of direct bus they return the same address as given). Adding driver class to the system
When the driver class is successfully created, it's pointer may be passed to the main pci class. It may be done in following way (assume, that cl is the pointer to freshly created driver class):
[...] 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); } [...]
Done. The pci subsystem will then use the passed class pointer (note: thanks to passing class pointer directly, driver classes do not have to be public) to scan PCI bus handled with this hardware driver. From this point, PCI devices handled with newly added driver are available for any use. Removing driver class from the system
The driver may ask PCI subsystem to be removed, using the RemHardwareDriver call. It's query may be, but doesn't have to be fulfilled. The driver will not be removed if there are any other users of PCI subsystem expect the driver wishing to be removed itself. When the RemHardwareDriver call success, the driver class may be deleted.
Why do I need this pluggable driver?
Imagine a PCI device (of any kind) which has it's own PCI bus. The device driver does know about this bus and wants to share this with other drivers (system user). Unfortunately only this specific device driver knows how to handle this additional PCI bus. When it creates a driver class which know how to talk to it and adds this driver class to pci subsystem, this PCI bus becomes part of whole system and from now on it is accessible for anyone.
pciclass.c pcideviceclass.c pciutil.c
pciclass.c
[edit | edit source]isPCIDeviceAvailable PCI__Hidd_PCI__AddHardwareDriver PCI__Hidd_PCI__EnumDevices PCI__Hidd_PCI__RemHardwareDriver PCI__Root__New PCI__Root__Dispose PCI_ExpungeClass PCI_InitClass
pcideviceclass.c
[edit | edit source]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
pciutil.c
[edit | edit source]getPCIClassDesc sizePCIBaseReg
Detecting the device
[edit | edit source]Enabling the device
[edit | edit source]Understanding the device
[edit | edit source]Bus-independent device access
[edit | edit source]Understanding the PCI configuration space
[edit | edit source]Misc
[edit | edit source]First of all it uses 64bit BAR addresses and those aren't supported in PCI.hidd(or am i wrong?), if i got it right reading those PCI specs a 64bit BAR takes the next BAR's space and uses it as upper bits in 64bit address, but Aros isn't aware of that and treats the next BAR as a new BAR.
Secondly in PCI.hidd BAR size(32bit or 64bit) is checked AFTER the BAR-address is taken, so in a sense it is done backwards... as the size(32bit or 64bit) could only be checked in sizePCIBaseReg function, which should be broken up to detect the size(32bit or 64bit) and if it's IO or MEM and the used IO/MEM size.
AFAIK Amiga PCI hardware is single big (256M+ memory space used for PCI) io autoconfig device from Amiga software point of view. Zorro boards can only use interrupt levels 2 or 6 so I assume all PCI interrupts are always routed to same physical 680x0 interrupt. netbsd amiga pci driver seems to explain most of this stuff (src/sys/arch/amiga/pci)