Thursday 19 August 2010

Ariadne's thread in the labyrinth of PCI

Do you ever setup a BSP with PCI support? It should be easy enough because all you need is there, however it is spread across your entire C:\WinCE600 folder. But what are the parts you may use without changes, what libraries must be included and what is hardware-specific and must be adapted?

You may start with the PciBus.dll. But there is nothing hardware-specific, that’s the wrong end of the thread.
There is a PCI library in the production-quality OAL (PQOAL) model, maybe that’s the right point to start. But the online help says this:

“Implements PCI bus support in the kernel. It also implements simple PCI bus configuration functions intended to be used for boot loader and KITL.”

No, it’s no KITL we want to setup. But anywhere between PciBus.dll and PQOAL must be a hardware-specific part, which must be ported to our PCI host controller.


Let’s start with some basics.
The PCI bus provides three address spaces: Config-Space, Memory-Space and IO-Space. Each PCI device “shows” its internal registers or memory in up to six so-called BARs. These BARs are in Memory or IO Space. It is common usage to iterate all buses and devices to find a specific controller on the PCI bus. Under Windows CE it’s better to implement a bus-agnostic driver calling DDKReg_GetWindowInfo(), but that’s another story.
Memory-Space and IO-Space are ordinary address spaces you may access with e.g. VirtualCopy(). The access to the Config-Space could be different: memory mapped or via special registers. As a result we need some special routines to read or write the Config-Space.

Let’s go back to the PciBus.dll:
During initialization of the PCI bus the driver reads the config space of each device and each function by calling HalGetBusData():

PCIBus.c:
Init()
Enumerate()

PCIcfg.c
PCICfg()
PCICfgBus()
/*...*/
// Loop through all device numbers
for (Device = 0; Device < PCI_MAX_DEVICES; Device++) {
SlotNumber.u.bits.DeviceNumber = Device;

// Loop through all functions
for (Function = 0; Function < PCI_MAX_FUNCTION; Function++) {
Cfg.VendorID = PCI_INVALID_VENDORID;
Cfg.DeviceID = PCI_INVALID_DEVICEID;
Cfg.HeaderType = PCI_DEVICE_TYPE;

SlotNumber.u.bits.FunctionNumber = Function;

// Read device's configuration header (except for device-specific information)
Length = HalGetBusData(PCIConfiguration, Bus, SlotNumber.u.AsULONG, &Cfg, sizeof(Cfg) - sizeof(Cfg.DeviceSpecific));

HalGetBusData() is implemented in the CeDDK:


CeDDK.h:
HalGetBusData()
CeDDK/DDK_Bus/Data.c:
HalGetBusDataByOffset()
KernelIoControl(IOCTL_HAL_DDK_CALL, ¶ms, sizeof(params), NULL, 0, &outSize);

The KernelIOCtrl takes us to the kernel:

ioctl.c from the OAL_IO_PCI.lib (Platform/Common/Src/Common/IO)
OALIoCtlHalDdkCall() implements the IOCTL_HAL_DDK_CALL
/*...*/
case IOCTL_OAL_READBUSDATA:
pParams = (OAL_DDK_PARAMS*)pInpBuffer;
pParams->rc = OALIoReadBusData(
&pParams->busData.devLoc, pParams->busData.offset,
pParams->busData.length , pParams->busData.pBuffer
);

data.c from the OAL_IO_PCI.lib (Platform/Common/Src/Common/IO)
OALIoReadBusData()
/*...*/
case PCIBus:
rc = OALPCICfgRead(
pDevLoc->BusNumber, *(OAL_PCI_LOCATION*)&pDevLoc->LogicalLoc,
address, size, pData
);

And here is the call to OALPCICfgRead():

PCI.c in your OAL:
UINT32 OALPCICfgRead(UINT32 busId, OAL_PCI_LOCATION pciLoc, UINT32 offset, UINT32 size, VOID *pData)

Well, this was the implementation for a “normal” CPU. As usual x86 is different:

ioctl.c from the OAL_IO_x86.lib (Platform/Common/Src/x86/Common/IO)
x86IoCtlHalDdkCall() implements the IOCTL_HAL_DDK_CALL
/*...*/
else if (nInBufSize == sizeof(BUSDATA_PARMS)) {
/*...*/
case IOCTL_HAL_GETBUSDATA:
pbd->ReturnCode = OEMGetBusDataByOffset(
((PBUSDATA_PARMS)lpInBuf)->BusDataType,
((PBUSDATA_PARMS)lpInBuf)->BusNumber,
((PBUSDATA_PARMS)lpInBuf)->SlotNumber,
((PBUSDATA_PARMS)lpInBuf)->Buffer,
((PBUSDATA_PARMS)lpInBuf)->Offset,
((PBUSDATA_PARMS)lpInBuf)->Length
);
break;
/*...*/
}
} else if (nInBufSize == sizeof(OAL_DDK_PARAMS)) {
/*...*/
case IOCTL_OAL_READBUSDATA:
pParams->rc = PCIReadBusData (bus, device, function, pParams->busData.pBuffer, offset, size);
break;


OEMGetBusDataByOffset()
/*...*/
case PCIConfiguration:
return(PCIGetBusDataByOffset(BusNumber, SlotNumber, Buffer, Offset, Length));

PCI.c (Platform/Common/Src/x86/Common/IO)
PCIGetBusDataByOffset()
PCIReadBusData()



Summery:
The key feature to access the PCI bus in your BSP is a good implementation of OALPCICfgRead() and OALPCICfgWrite().
And what about the KITL and PCI?
Well, KITL is an extra DLL and has no direct access to the OAL. That’s why KITL implements it’s own versions of OALPCICfgRead() and OALPCICfgWrite(). These implementations call the KernelIOCtl(IOCTL_HAL_DDK_CALL).

That’s a lot of grepping work to find out how PCI works. I hope to be your Ariadne's thread if you ever have to fix a PCI problem below PciBus.dll.

Tschüß Holger

1 comment:

Anonymous said...

Hi Holger, thx a lot for this summary!
What will come next?