PCI的配置空间为4KB,具体格式如下表。
0x03 | 0x02 | 0x01 | 0x00 | Address |
Device ID | Vendor ID | 0x00 | ||
0x07 | 0x06 | 0x05 | 0x04 | |
Cmd Reg | Status Reg | 0x04 | ||
0x0B | 0x0A | 0x09 | 0x08 | |
Class Code | 0x08 | |||
0x0F | 0x0E | 0x0D | 0x0C | |
BIST | Latency | 0x0C | ||
0x13 | 0x12 | 0x11 | 0x10 | |
Base0 | 0x10 | |||
0x17 | 0x16 | 0x15 | 0x14 | |
Base1 | 0x14 | |||
0x1B | 0x1A | 0x19 | 0x18 | |
Base2 | 0x18 | |||
0x1F | 0x1E | 0x1D | 0x1C | |
Base3 | 0x1C | |||
0x23 | 0x22 | 0x21 | 0x20 | |
Base4 | 0x20 | |||
0x27 | 0x26 | 0x25 | 0x24 | |
Base5 | 0x24 | |||
0x2B | 0x2A | 0x29 | 0x28 | |
Card Bus CIS Pointer | 0x28 | |||
0x2F | 0x2E | 0x2D | 0x2C | |
Sub Device ID | Sub VID | 0x2C | ||
0x33 | 0x32 | 0x31 | 0x30 | |
Expansion ROM Base Address | 0x30 | |||
0x37 | 0x36 | 0x35 | 0x34 | |
Reserved | 0x34 | |||
0x3B | 0x3A | 0x39 | 0x38 | |
Reserved | 0x38 | |||
0x3F | 0x3E | 0x3D | 0x3C | |
Max_Lat | IRQ Pin | 0x3C |
PCI配置空间的前256个Byte是兼容PCI的配置寄存器,可采用PCI或PCIe配置访问机制 ;后3840Byte是PCIe的扩展配置空间,用于实现可选的PCIe扩展功能寄存器。
在x86体系中,要读取设备配置空间,需要访问2个32bit的register,首先在0xCF8中,写入Bus Number,DeviceNumver,Function Number, Register Number;再从0xCFC中读取设备信息。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 0xCF8 |
Register Number (0 ~ 255) | ||||||||
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | |
Device Numver (0 ~ 31) | ||||||||
23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | |
Bus Number | ||||||||
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | |
Effect | Reserved |
==================================================================================================================
KMDF驱动程序将硬件资源映射成为EvtDevicePrepareHardware回调函数的一部分;最后在EvtDeviceReleaseHardware函数中取消资源映射。在设备枚举时和资源均衡后,KMDF会在调用EvtDeviceDOEntry之前,调用EvtDevicePrepareHardware函数来映射设备资源。在这个函数中,驱动程序不要访问硬件设备。在EvtDevicePrepareHardware函数内,驱动程序调用WdfCmResourceListGetCount来获得设备资源的数量,然后在调用WdfCmResourceListGetDesctiptor获得指定资源的细节。
硬件资源可以映射到IO或者Memory,要实现与平台无关,需要对IO和Memory两种映射都支持。
映射IO:CmResourceTypePort,保存Base地址和资源的映射范围;保存*_PORT_*函数的指针,用来访问资源。
映射Memory:CmResourceTypeMemory,需要确保分配的大小足够,然后调用MmMapIoSpace,把返回的物理地址映射到虚拟地址,保存*_PORT_*函数的指针,用来访问资源。
NTSTATUS NICMapHWResources(IN OUT PFDO_DATA FdoData,IN WDFCMRESLIST ResourcesRaw,IN WDFCMRESLIST ResourcesTranslated )
Routine Description:
Gets the HW resources assigned by the bus driver and:
1) Maps them to system address space.
2) If PCIDRV_CREATE_INTERRUPT_IN_PREPARE_HARDWARE is defined,
it creates a WDFINTERRUPT object.
Called during EvtDevicePrepareHardware callback.
Three base address registers are supported by the 8255x:
1) CSR Memory Mapped Base Address Register (BAR 0 at offset 10)
2) CSR I/O Mapped Base Address Register (BAR 1 at offset 14)
3) Flash Memory Mapped Base Address Register (BAR 2 at offset 18)
The 8255x requires one BAR for I/O mapping and one BAR for memory
mapping of these registers anywhere within the 32-bit memory address space.
The driver determines which BAR (I/O or Memory) is used to access the
Control/Status Registers.
Just for illustration, this driver maps both memory and I/O registers and
shows how to use READ_PORT_xxx or READ_REGISTER_xxx functions to perform
I/O in a platform independent basis. On some platforms, the I/O registers
can get mapped into memory space and your driver should be able to handle
this transparently.
One BAR is also required to map the accesses to an optional Flash memory.
The 82557 implements this register regardless of the presence or absence
of a Flash chip on the adapter. The 82558 and 82559 implement this
register only if a bit is set in the EEPROM. The size of the space requested
by this register is 1Mbyte, and it is always mapped anywhere in the 32-bit
memory address space.
Note: Although the 82558 only supports up to 64 Kbytes of Flash memory
and the 82559 only supports 128 Kbytes of Flash memory, the driver
requests 1 Mbyte of address space. Software should not access Flash
addresses above 64 Kbytes for the 82558 or 128 Kbytes for the 82559
because Flash accesses above the limits are aliased to lower addresses.
Arguments:
FdoData Pointer to our FdoData
ResourcesRaw - Pointer to list of raw resources passed to
EvtDevicePrepareHardware callback
ResourcesTranslated - Pointer to list of translated resources passed to
EvtDevicePrepareHardware callback
Return Value:
NTSTATUS
--*/
{
PCM_PARTIAL_RESOURCE_DESCRIPTOR descriptor;
ULONG i;
NTSTATUS status = STATUS_SUCCESS;
BOOLEAN bResPort = FALSE;
BOOLEAN bResInterrupt = FALSE;
BOOLEAN bResMemory = FALSE;
ULONG numberOfBARs = 0;
UNREFERENCED_PARAMETER(ResourcesRaw);
PAGED_CODE();
for (i=0; i<WdfCmResourceListGetCount(ResourcesTranslated); i++)
{
descriptor = WdfCmResourceListGetDescriptor(ResourcesTranslated, i);
if(!descriptor)
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT, "WdfResourceCmGetDescriptor");
return STATUS_DEVICE_CONFIGURATION_ERROR;
}
switch (descriptor->Type) {
case CmResourceTypePort:
//
// We will increment the BAR count only for valid resources. We will
// not count the private device types added by the PCI bus driver.
//
numberOfBARs++;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_INIT,
"I/O mapped CSR: (%x) Length: (%d)\n",
descriptor->u.Port.Start.LowPart,
descriptor->u.Port.Length);
//
// The resources are listed in the same order the as
// BARs in the config space, so this should be the second one.
//
if(numberOfBARs != 2) {
TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT, "I/O mapped CSR is not in the right order\n");
status = STATUS_DEVICE_CONFIGURATION_ERROR;
return status;
}
//
// The port is in I/O space on this machine.
// We should use READ_PORT_Xxx, and WRITE_PORT_Xxx routines
// to read or write to the port.
//
FdoData->IoBaseAddress = ULongToPtr(descriptor->u.Port.Start.LowPart);
FdoData->IoRange = descriptor->u.Port.Length;
//
// Since all our accesses are USHORT wide, we will create an accessor
// table just for these two functions.
//
FdoData->ReadPort = NICReadPortUShort;
FdoData->WritePort = NICWritePortUShort;
bResPort = TRUE;
FdoData->MappedPorts = FALSE;
break;
case CmResourceTypeMemory:
numberOfBARs++;
if(numberOfBARs == 1)
{
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_INIT, "Memory mapped CSR:(%x:%x) Length:(%d)\n",
descriptor->u.Memory.Start.LowPart,
descriptor->u.Memory.Start.HighPart,
descriptor->u.Memory.Length);
//
// Our CSR memory space should be 0x1000 in size.
//
ASSERT(descriptor->u.Memory.Length == 0x1000);
FdoData->MemPhysAddress = descriptor->u.Memory.Start;
FdoData->CSRAddress = MmMapIoSpace(
descriptor->u.Memory.Start,
NIC_MAP_IOSPACE_LENGTH,
MmNonCached);
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_INIT, "CSRAddress=%p\n", FdoData->CSRAddress);
bResMemory = TRUE;
}
else if(numberOfBARs == 2)
{
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_INIT,
"I/O mapped CSR in Memory Space: (%x) Length: (%d)\n",
descriptor->u.Memory.Start.LowPart,
descriptor->u.Memory.Length);
//
// The port is in memory space on this machine.
// We should call MmMapIoSpace to map the physical to virtual
// address, and also use the READ/WRITE_REGISTER_xxx function
// to read or write to the port.
//
FdoData->IoBaseAddress = MmMapIoSpace(
descriptor->u.Memory.Start,
descriptor->u.Memory.Length,
MmNonCached);
FdoData->ReadPort = NICReadRegisterUShort;
FdoData->WritePort = NICWriteRegisterUShort;
FdoData->MappedPorts = TRUE;
bResPort = TRUE;
}
else if(numberOfBARs == 3)
{
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_INIT, "Flash memory:(%x:%x) Length:(%d)\n",
descriptor->u.Memory.Start.LowPart,
descriptor->u.Memory.Start.HighPart,
descriptor->u.Memory.Length);
//
// Our flash memory should be 1MB in size. Since we don't
// access the memory, let us not bother mapping it.
//
//ASSERT(descriptor->u.Memory.Length == 0x100000);
}
else
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT,
"Memory Resources are not in the right order\n");
status = STATUS_DEVICE_CONFIGURATION_ERROR;
return status;
}
break;
case CmResourceTypeInterrupt:
ASSERT(!bResInterrupt);
#ifdef PCIDRV_CREATE_INTERRUPT_IN_PREPARE_HARDWARE
{
WDF_INTERRUPT_CONFIG interruptConfig;
//
// Create WDFINTERRUPT object.
//
WDF_INTERRUPT_CONFIG_INIT(&interruptConfig,
NICEvtInterruptIsr,
NICEvtInterruptDpc);
//
// These first two callbacks will be called at DIRQL. Their job is to
// enable and disable interrupts.
//
interruptConfig.EvtInterruptEnable = NICEvtInterruptEnable;
interruptConfig.EvtInterruptDisable = NICEvtInterruptDisable;
interruptConfig.InterruptTranslated = descriptor;
interruptConfig.InterruptRaw =
WdfCmResourceListGetDescriptor(ResourcesRaw, i);
status = WdfInterruptCreate(FdoData->WdfDevice,
&interruptConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&FdoData->WdfInterrupt);
if (!NT_SUCCESS (status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT,
"WdfInterruptCreate failed: %!STATUS!\n", status);
return status;
}
}
#endif
bResInterrupt = TRUE;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_INIT,
"Interrupt level: 0x%0x, Vector: 0x%0x\n",
descriptor->u.Interrupt.Level,
descriptor->u.Interrupt.Vector);
break;
default:
//
// This could be device-private type added by the PCI bus driver. We
// shouldn't filter this or change the information contained in it.
//
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_INIT, "Unhandled resource type (0x%x)\n",
descriptor->Type);
break;
}
}
//
// Make sure we got all the 3 resources to work with.
//
if (!(bResPort && bResInterrupt && bResMemory)) {
status = STATUS_DEVICE_CONFIGURATION_ERROR;
return status;
}
//
// Read additional info from NIC such as MAC address
//
status = NICReadAdapterInfo(FdoData);
if (status != STATUS_SUCCESS)
{
return status;
}
//
// Test our adapter hardware
//
status = NICSelfTest(FdoData);
if (status != STATUS_SUCCESS)
{
return status;
}
return status;
}
NTSTATUS
NICUnmapHWResources(
IN OUT PFDO_DATA FdoData
)
/*++
Routine Description:
Disconnect the interrupt and unmap all the memory and I/O resources.
Arguments:
FdoData Pointer to our FdoData
Return Value:
None
--*/
{
PAGED_CODE();
//
// Free hardware resources
//
if (FdoData->CSRAddress)
{
MmUnmapIoSpace(FdoData->CSRAddress, NIC_MAP_IOSPACE_LENGTH);
FdoData->CSRAddress = NULL;
}
if(FdoData->MappedPorts){
MmUnmapIoSpace(FdoData->IoBaseAddress, FdoData->IoRange);
FdoData->IoBaseAddress = NULL;
}
return STATUS_SUCCESS;
}