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.
To read or write a register in I/O space
by using the READ_PORT_Xxx and
WRITE_PORT_Xxx macros, a driver
should cast the port address to a pointer of type PVOID or PUCHAR (if pointer
addition is required). For example:
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.
In the sample, the driver parses the
resource list to determine whether its control and status registers are mapped
to I/O space (CmResourceTypePort) or
memory (CmResourceTypeMemory). It then
sets pointers to the macros it will use to access the registers in its device
extension at FdoData‑>ReadPort and FdoData‑>WritePort.
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