What Is Accessing Device Registers?

Device registers are among a device’s hardware resources, which are assigned to the device during Plug and Play enumeration. Registers can be mapped into memory or into the 64‑KB I/O space, depending on the type of device, the bus to which it is attached, and the underlying hardware platform. The x86, x64, and Itanium processors, along with most common buses (including PCI, PCI Express, ISA, and EISA), support both memory mapping and I/O mapping. Some other hardware architectures, however, support only memory mapping.

As described earlier in “Physical Address Space,” I/O mapping is a holdover from early microprocessor designs. Today, however, most machines have more than enough physical address space to hold all the device registers. As a result, newer devices are typically memory-mapped. In a memory-mapped device, the device registers are mapped to addresses in the physical memory space. The driver then maps those addresses into the virtual address space before it uses them. 

Device registers that are mapped into I/O space are read and written with the READ_PORT_Xxx and WRITE_PORT_Xxx macros. Device registers that are mapped into memory are read and written with the READ_REGISTER_Xxx and WRITE_REGISTER_Xxx macros. Thus, I/O mapping is sometimes called PORT mapping and the mapped resources are called PORT resources (with or without the capital letters). Similarly, memory mapping is sometimes called REGISTER mapping and the corresponding resources are called REGISTER resources.

At device enumeration, the bus driver requests I/O or memory mapping for each device resource, in response to queries from the Plug and Play manager. The Plug and Play manager assigns raw and translated resources to the device and passes a list of the assigned resources to the device’s function driver in the IRP_MN_START_DEVICE request.

The raw resource type reflects how the hardware is wired. The translated resource type indicates where the resource is mapped on the current system and thus whether the driver should use PORT or REGISTER macros to read and write the resource.

Knowing how your device hardware is wired does not necessarily tell you how its resources will be mapped because some chipsets change the mapping. Therefore, drivers must be prepared to support both types of mapping for each register. Defining wrappers for the macros is a common strategy for supporting both types of mappings.

To determine the mapping for each individual hardware resource, a driver inspects the resource lists passed in the IRP_MN_START_DEVICE request. Resources of type CmResourceTypeMemory are memory-mapped, and resources of type CmResourceTypePort are I/O-mapped. Each resource has a starting address and a length. Drivers should parse and save the translated resource lists in the device extension, and then use the translated resources to read and write device registers. Most drivers do not need the raw resources.

The resource list also contains a set of flags that provide additional information about the assigned resources. For I/O-mapped resources, the flags indicate the type of decoding that the device performs and whether the device decodes 10, 12, or 16 bits of the port address. For memory-mapped resources, the flags indicate whether the memory can be read, written, or both; whether it is cached or write-combined; whether it can be prefetched; and whether the device uses 24-bit addressing.  

PortPointer =
     (PVOID)(ULONG_PTR)Resource->u.Port.Start.QuadPart

Before reading or writing a resource in memory space, the driver must call MmMapIoSpace to get a virtual address for the translated physical address. The driver then must use the returned virtual address in calls to the READ_REGISTER_Xxx and WRITE_REGISTER_Xxx macros.

Remember that the driver should use the correct type of pointer for the data to be read or written with the PORT and REGISTER macros. For example, a driver should use PUCHAR to read or write an unsigned character, PUSHORT for a short integer, and so forth.

The following example shows how a driver gets the resources that have been assigned to its device. The driver receives the translated resource list in the start-device IRP at Parameters.StartDevice.AllocatedResourcesTranslated. The value at this location points to a CM_RESOURCE_LIST that describes the hardware resources that the Plug and Play manager assigned to the device. The base address registers (BARs) are always returned in order.


partialResourceListTranslated =
       &stack->Parameters.StartDevice.
       AllocatedResourcesTranslated->List[0].
       PartialResourceList;

resourceTrans =
       &partialResourceListTranslated->PartialDescriptors[0];

for (i = 0;
     i < partialResourceListTranslated->Count;
     i++, resourceTrans++) {

     switch (resourceTrans->Type) {
           
       case CmResourceTypePort:
          // The control and status (CSR) registers
          // are in port space.

          // Save the base address and length.
          FdoData->IoBaseAddress =
             (PVOID)(ULONG_PTR)(
                 resourceTrans->u.Port.Start.QuadPart);

          FdoData->IoRange = resourceTrans->u.Port.Length;

          // All accesses are USHORT wide, so
          // create an accessor table to read and write
          // the ports.
          FdoData->ReadPort = READ_PORT_USHORT;
          FdoData->WritePort = WRITE_PORT_USHORT;
         
        break;

       case CmResourceTypeMemory:

          // The CSR registers are in memory space.
          // This device requires a CSR memory space that
          // is 0x1000 in size. Assert if it’s not.
          //
          ASSERT(resourceTrans->u.Memory.Length == 0x1000);

          // The driver can read and write registers in
          // memory space by using the READ_REGISTER_*
          // macros.

          FdoData->ReadPort = READ_REGISTER_USHORT;
          FdoData->WritePort = WRITE_REGISTER_USHORT;

          // Save the starting address.
          FdoData->MemPhysAddress =
                   resourceTrans->u.Memory.Start;

          // Map the returned physical address to a
          // virtual address. We can use the VA to call
          // call READ_REGISTER* and WRITE_REGISTER* macros.
          // We can also define the CSRAddress structure that
          // maps on top of the registers. We can then
          // read and write registers directly by name.

           FdoData->CSRAddress = MmMapIoSpace(
                    resourceTrans->u.Memory.Start,
                    resourceTrans->u.Memory.Length,
                    MmNonCached);
           if (FdoData->CSRAddress == NULL) {
              DebugPrint(ERROR, DBG_INIT,
              "MmMapIoSpace failed\n");
           status = STATUS_INSUFFICIENT_RESOURCES;
           }
               
      break;
}


If the registers are in physical memory space, the driver calls MmMapIoSpace to map them into system-space virtual memory. Specifying noncached memory is important to ensure that new values are immediately written to the device, without being held in the processor cache. Control and status registers should always be mapped uncached. On-board memory can be mapped cached, uncached, or uncached/write-combined, depending on how it will be used.

As an alternative to using the READ_REGISTER_Xxx and WRITE_REGISTER_Xxx macros to read and write the registers, you can define a structure that matches the register layout and then read and write the fields of that structure. In the preceding sample, the structure at FdoData‑>CSRAddress is defined for this purpose. If the order in which you set bits in the registers is important, you should declare the register fields of the structure as volatile to prevent compiler reordering of read and write instructions. Depending on exactly what a particular code sequence does, you might also need to use memory barriers to prevent some hardware architectures from reordering memory access. How to use both the volatile keyword and memory barriers is described in “Multiprocessor Considerations for Kernel-mode Drivers,” which is listed in the Resources section at the end of this paper.


The sample code saves pointers to the macros it will use to read and write the registers in its device extension. In most cases, this technique works because the PORT and REGISTER macros have the same semantics. For example, READ_PORT_USHORT and READ_REGISTER_USHORT operate in the same way on all platforms. However, READ_PORT_BUFFER_UCHAR and READ_REGISTER_BUFFER_UCHAR are exceptions. On some platforms, READ_PORT_BUFFER_UCHAR updates the port address after it reads a byte, but on others it does not. To work around this issue, avoid using both of these macros. Instead, call READ_PORT_UCHAR or READ_REGISTER_UCHAR in a loop until the processor has read the correct number of bytes.

0 komentar:

Posting Komentar

 

Serba Ada Blog Copyright © 2011-2012 | Powered by Blogger