18.4.1 Memory-mapped I/O ordering issues

PCI transactions follow the ordering rules defined in the PCI Specification. The ordering rules vary for I/O, memory-mapped I/O, and PCI configuration cycles.

The PCI I/O Protocol Mem.Read() service generates PCI memory read cycles guaranteed to complete before control is returned to the PCI driver. However, the PCI I/O Protocol Mem.Write() service does not guarantee that PCI memory cycles produced by this service are completed before control is returned to the PCI driver. This distinction means that memory write transactions may be sitting in write buffers when this service returns. If the PCI driver requires a Mem.Write() transaction to complete, then the Mem.Write() transaction must be followed by a Mem.Read() transaction to the same PCI controller. Some chipsets and PCI-to-PCI bridges are more sensitive to this issue than others.

The following example shows a Mem.Write() call to a memory-mapped I/O register at offset 0x20 into BAR #1 of a PCI controller. This write transaction is followed by a Mem.Read() call from the same memory-mapped I/O register. This combination guarantees that the write transaction is completed by the time the Mem.Read() call returns.

In general, this mechanism is not required because a PCI driver typically reads a status register and this read transaction forces all posted write transactions to complete on the PCI controller. The only time to use this mechanism is when a PCI driver performs a write transaction not immediately followed by a read transaction and the PCI driver needs to guarantee that the write transaction is completed immediately.

Example 173-Completing a memory write transaction
#include <Uefi.h>
#include <Protocol/PciIo.h>

EFI_STATUS Status;
EFI_PCI_IO_PROTOCOL *PciIo;
UINT32 DmaStartAddress;

//
// Write the value in DmaStartAddress to offset 0x20 of BAR #1
//
Status = PciIo->Mem.Write (
                      PciIo,                // This
                      EfiPciIoWidthUint32,  // Width
                      1,                    // BarIndex
                      0x20,                 // Offset
                      1,                    // Count
                      &DmaStartAddress      // Buffer
                      );
if (EFI_ERROR (Status)) {
  return Status;
}

//
// Read offset 0x20 of BAR #1 This guarantees that the previous write
// transaction is posted to the PCI controller.
//
Status = PciIo->Mem.Read (
                      PciIo, // This
                      EfiPciIoWidthUint32,  // Width
                      1,                    // BarIndex
                      0x20,                 // Offset
                      1,                    // Count
                      &DmaStartAddress      // Buffer
                      );
if (EFI_ERROR (Status)) {
  return Status;
}