Windows 7 : Programming KMDF Hardware Driver – Mapping Resources – Code to Map Resources

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 PciDrvEvtDevicePrepareHardwareNICMapHwResources 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 EvtDeviceReleaseHardwareNICUnmapHwResources 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;
}