A KMDF driver maps resources for its hardware as part of its EvtDevicePrepareHardware callback and unmaps them in its EvtDeviceReleaseHardware
callback. These callbacks provide a way for a driver to prepare its
device hardware immediately before the device enters and after the
device leaves the operational (DO) state. These two routines are always called in pairs—that is, after KMDF calls EvtDevicePrepareHardware, it always calls EvtDeviceReleaseHardware before calling EvtDevicePrepareHardware again.
KMDF calls a driver’s EvtDevicePrepareHardware callback before calling EvtDeviceDOEntry
whenever resources are assigned to the device—specifically, during
initial device enumeration and during power-up after resource
rebalancing. EvtDevicePrepareHardware
should map device resources but should not load firmware or perform
other device initialization tasks. Drivers for USB devices might also
get device and configuration descriptors and select configurations in
this callback. Drivers should not attempt to access their device
hardware in this callback.
EvtDeviceReleaseHardware undoes any work that was done by EvtDevicePrepareHardware. For example, if EvtDevicePrepareHardware maps resources, EvtDeviceReleaseHardware releases them.
Within its EvtDevicePrepareHardware function, a driver calls WdfCmResourceListGetCount to get the number of resources that the system has assigned to its device and then calls WdfCmResourceListGetDescriptor to get details about a particular resource from the list.
The chipset on an individual machine can map
hardware resources into either I/O or memory space, regardless of how
the device itself is designed. To be platform independent, all drivers
should support both types of mapping, just as the PCIDRV sample does. For I/O and memory-mapped resources, a KMDF driver takes steps that are similar to those a WDM driver would take:
- For an I/O-mapped resource (CmResourceTypePort), the driver saves the base address and range at which the resource is mapped and saves a pointer to the HAL’s *_PORT_* functions that it will use to access the resource.
- For a memory-mapped resource (CmResourceTypeMemory),
the driver checks that the allocated size is adequate. If so, it maps
the returned physical address to a virtual address by calling
MmMapIoSpace and saves pointers to the HAL’s *_REGISTER_* functions that it will use to access the resource.
For interrupt resources, a KMDF driver simply creates an interrupt object as part of EvtDriverDeviceAdd
processing, as described in the previous discussion. The object itself
picks up its resources with no required driver intervention.
1. Code to Map Resources
The PCI device supported by the sample driver has three base address registers (BARs): BAR 0 is memory mapped, BAR 1 is I/O mapped, and BAR 3 is flash-memory mapped. The driver determines whether to use the I/O-mapped BAR or the memory-mapped BAR to access the control and status registers.
The sample driver checks for registers in both
memory and I/O space. On some platforms, the I/O registers can be
mapped into memory space; every driver should be coded to handle this.
In the PDIDRV sample, the code to map resources is isolated in the NICMapHwResources function, which is called by PciDrvEvtDevicePrepareHardware. NICMapHwResources has two parameters: a pointer to the device context area (FdoData) and a handle to the list of translated resources (ResourcesTranslated) that was passed to PciDrvEvtDevicePrepareHardware. The driver must use the translated resources to map device registers into port and memory space.
The following sample code is excerpted from the pcidrv\sys\Hw\Nic_init.c file. It shows how the PCIDRV sample maps hardware resources.
NTSTATUS
NICMapHwResources (
IN OUT PFDO_DATA FdoData,
WDFCMRESLIST ResourcesTranslated
)
{
PCM_PARTIAL_RESOURCE_DESCRIPTOR descriptor;
ULONG i;
NTSTATUS status = STATUS_SUCCESS;
BOOLEAN bResPort = FALSE;
BOOLEAN bResInterrupt = FALSE;
BOOLEAN bResMemory = FALSE;
ULONG numberOfBARSs = 0;
PAGED_CODE ();
for (i = 0; i<WdfCmResourceListGetCount
(ResourcesTranslated); i++)
{
descriptor =
WdfCmResourceListGetDescriptor
(RessourcesTranslated, i);
If (!descriptor)
{
return STATUS_DEVICE_CONFIGURATION_ERROR;
}
switch (descriptor->Type)
{
case CmResosurceTypePort:
//
// We will increment the BAR count only for valid
// resources. Do not count the private device types
// added by the PCI bus driver.
//
numberOfBars++;
//
// The resources are listed in the same order as the
// BARSs in the configuration space, so this should
// be the second one.
//
if (numberOfBars != 2)
{
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 functions 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)
{
//
// 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_LEGTH,
MmNonCached);
bResMemory = TRUE;
}
else if (numberOfBARs == 2)
{
//
// The port is in memory space on this machine.
// Call MmMapIoSpace to map the
// physical to virtual address, and 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)
{
//
// Our flash memory should be 1MB in size. We
// don't access it, so do not bother mapping it.
//
// ASSERT (descriptor->u.Memory.Length ==
// 0x100000);
}
else
{
status =
STATUS_DEVICE_CONFIGURATION_ERROR;
return status;
}
break;
case CmResourceTypeInterrupt:
ASSERT (!bResInterrupt);
bResInterrupt = TRUE;
break;
default:
//
// This could be a device-private type added by
// the PCI bus driver. We shouldn't filter this
// or change the information contained in it.
//
break;
}
}
// Make sure we got all the resources to work with.
//
if (! (bResPort && bResInterrupt && bResMemory))
{
status =
STATUS_DEVICE_CONFIGURATION_ERROR;
}
return status;
}
The
driver parses the list of translated resources in a loop that starts at
zero and ends when it has reached the last resource in the list. The
driver determines the number of resources in the list by calling the WdfCmResourceListGetCount function.
For each resource in the list, the driver calls WdfCmResourceListGetDescriptor
to get a pointer to the resource descriptor. The resource descriptor is
an enumerator that indicates the type of the resource. (If you are
familiar with WDM drivers, you will notice that the resource types are
the same as those for WDM.)
For the CmResourceTypePort
resources, the driver saves the starting address and range in the
device context area and then sets the addresses of the functions that
it uses to access the port resources.
For CmResourceTypeMemory resources, the driver also saves the starting address and range in the device context area, but then uses MmMapIoSpace to map the resources and get a virtual address through which it can access them.
For CmResourceTypeInterrupt
resources, the driver is not required to save the resource information
because KMDF handles this transparently for the driver when the driver
creates the WDFINTERRUPT object. The sample driver merely checks this resource for completeness.
2. Code to Unmap Resources
When the device is removed or when the system rebalances resources, the driver must release its mapped resources in an EvtDeviceReleaseHardware callback. KMDF calls this function after calling the driver EvtDeviceDOExit function.
The PCIDRV sample does so in the internal function NICUnmapHwResources, which is called by its EvtDeviceReleaseHardware. NICUnmapHwResources appears in the pcidrv\sys\hw\nic_init.c source file and releases the resources as follows:
if (FdoData->CSRAddress)
{
MmUnmapIoSpace (FdoData->CSRAddress,
NIC_MAP_IOSPACE_LENGTH);
FdoData->CSRAddress = NULL;
}
if (FdoData->MappedPorts)
{
MmUnMapIoSpace (FdoData->IoBaseAddress,
FdoData->IoRange);
FdoData->IoBaseAddress = NULL;
}