1010 lines
32 KiB
C
1010 lines
32 KiB
C
/** @file
|
|
Work with PCI capabilities in PCI config space.
|
|
|
|
Provides functions to parse capabilities lists, and to locate, describe, read
|
|
and write capabilities. PCI config space access is abstracted away.
|
|
|
|
Copyright (C) 2018, Red Hat, Inc.
|
|
|
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
**/
|
|
|
|
#include <IndustryStandard/PciExpress21.h>
|
|
|
|
#include <Library/BaseMemoryLib.h>
|
|
#include <Library/DebugLib.h>
|
|
#include <Library/MemoryAllocationLib.h>
|
|
|
|
#include "BasePciCapLib.h"
|
|
|
|
|
|
/**
|
|
Compare a standalone PCI_CAP_KEY against a PCI_CAP containing an embedded
|
|
PCI_CAP_KEY.
|
|
|
|
@param[in] PciCapKey Pointer to the bare PCI_CAP_KEY.
|
|
|
|
@param[in] PciCap Pointer to the PCI_CAP with the embedded PCI_CAP_KEY.
|
|
|
|
@retval <0 If PciCapKey compares less than PciCap->Key.
|
|
|
|
@retval 0 If PciCapKey compares equal to PciCap->Key.
|
|
|
|
@retval >0 If PciCapKey compares greater than PciCap->Key.
|
|
**/
|
|
STATIC
|
|
INTN
|
|
EFIAPI
|
|
ComparePciCapKey (
|
|
IN CONST VOID *PciCapKey,
|
|
IN CONST VOID *PciCap
|
|
)
|
|
{
|
|
CONST PCI_CAP_KEY *Key1;
|
|
CONST PCI_CAP_KEY *Key2;
|
|
|
|
Key1 = PciCapKey;
|
|
Key2 = &((CONST PCI_CAP *)PciCap)->Key;
|
|
|
|
if (Key1->Domain < Key2->Domain) {
|
|
return -1;
|
|
}
|
|
if (Key1->Domain > Key2->Domain) {
|
|
return 1;
|
|
}
|
|
if (Key1->CapId < Key2->CapId) {
|
|
return -1;
|
|
}
|
|
if (Key1->CapId > Key2->CapId) {
|
|
return 1;
|
|
}
|
|
if (Key1->Instance < Key2->Instance) {
|
|
return -1;
|
|
}
|
|
if (Key1->Instance > Key2->Instance) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
Compare two PCI_CAP objects based on PCI_CAP.Key.
|
|
|
|
@param[in] PciCap1 Pointer to the first PCI_CAP.
|
|
|
|
@param[in] PciCap2 Pointer to the second PCI_CAP.
|
|
|
|
@retval <0 If PciCap1 compares less than PciCap2.
|
|
|
|
@retval 0 If PciCap1 compares equal to PciCap2.
|
|
|
|
@retval >0 If PciCap1 compares greater than PciCap2.
|
|
**/
|
|
STATIC
|
|
INTN
|
|
EFIAPI
|
|
ComparePciCap (
|
|
IN CONST VOID *PciCap1,
|
|
IN CONST VOID *PciCap2
|
|
)
|
|
{
|
|
CONST PCI_CAP_KEY *PciCap1Key;
|
|
|
|
PciCap1Key = &((CONST PCI_CAP *)PciCap1)->Key;
|
|
return ComparePciCapKey (PciCap1Key, PciCap2);
|
|
}
|
|
|
|
|
|
/**
|
|
Compare the standalone UINT16 config space offset of a capability header
|
|
against a PCI_CAP containing an embedded Offset.
|
|
|
|
@param[in] CapHdrOffset Pointer to the bare UINT16 config space offset.
|
|
|
|
@param[in] PciCap Pointer to the PCI_CAP with the embedded Offset.
|
|
|
|
@retval <0 If CapHdrOffset compares less than PciCap->Offset.
|
|
|
|
@retval 0 If CapHdrOffset compares equal to PciCap->Offset.
|
|
|
|
@retval >0 If CapHdrOffset compares greater than PciCap->Offset.
|
|
**/
|
|
STATIC
|
|
INTN
|
|
EFIAPI
|
|
ComparePciCapOffsetKey (
|
|
IN CONST VOID *CapHdrOffset,
|
|
IN CONST VOID *PciCap
|
|
)
|
|
{
|
|
UINT16 Offset1;
|
|
UINT16 Offset2;
|
|
|
|
Offset1 = *(CONST UINT16 *)CapHdrOffset;
|
|
Offset2 = ((CONST PCI_CAP *)PciCap)->Offset;
|
|
//
|
|
// Note: both Offset1 and Offset2 are promoted to INT32 below, and the
|
|
// subtraction takes place between INT32 values.
|
|
//
|
|
return Offset1 - Offset2;
|
|
}
|
|
|
|
|
|
/**
|
|
Compare two PCI_CAP objects based on PCI_CAP.Offset.
|
|
|
|
@param[in] PciCap1 Pointer to the first PCI_CAP.
|
|
|
|
@param[in] PciCap2 Pointer to the second PCI_CAP.
|
|
|
|
@retval <0 If PciCap1 compares less than PciCap2.
|
|
|
|
@retval 0 If PciCap1 compares equal to PciCap2.
|
|
|
|
@retval >0 If PciCap1 compares greater than PciCap2.
|
|
**/
|
|
STATIC
|
|
INTN
|
|
EFIAPI
|
|
ComparePciCapOffset (
|
|
IN CONST VOID *PciCap1,
|
|
IN CONST VOID *PciCap2
|
|
)
|
|
{
|
|
UINT16 Offset1;
|
|
UINT16 Offset2;
|
|
|
|
Offset1 = ((CONST PCI_CAP *)PciCap1)->Offset;
|
|
Offset2 = ((CONST PCI_CAP *)PciCap2)->Offset;
|
|
//
|
|
// Note: both Offset1 and Offset2 are promoted to INT32 below, and the
|
|
// subtraction takes place between INT32 values.
|
|
//
|
|
return Offset1 - Offset2;
|
|
}
|
|
|
|
|
|
/**
|
|
Insert a new instance of the PCI capability given by (Domain, CapId) in
|
|
CapList.
|
|
|
|
@param[in,out] CapList The PCI_CAP_LIST into which the new PCI_CAP
|
|
should be inserted. CapList will own the new
|
|
PCI_CAP structure.
|
|
|
|
@param[in,out] CapHdrOffsets Link the new PCI_CAP structure into the
|
|
(non-owning) CapHdrOffsets collection as well.
|
|
CapHdrOffsets orders the PCI_CAP structures
|
|
based on the PCI_CAP.Offset member, and enables
|
|
the calculation of PCI_CAP.MaxSizeHint.
|
|
|
|
@param[in] Domain Whether the capability is normal or extended.
|
|
|
|
@param[in] CapId Capability ID (specific to Domain).
|
|
|
|
@param[in] Offset Config space offset at which the standard
|
|
header of the capability starts. The caller is
|
|
responsible for ensuring that Offset be DWORD
|
|
aligned. The caller is also responsible for
|
|
ensuring that Offset be within the config space
|
|
identified by Domain.
|
|
|
|
@param[in] Version The version number of the capability. The
|
|
caller is responsible for passing 0 as Version
|
|
if Domain is PciCapNormal.
|
|
|
|
@retval RETURN_SUCCESS Insertion successful.
|
|
|
|
@retval RETURN_OUT_OF_RESOURCES Memory allocation failed.
|
|
|
|
@retval RETURN_DEVICE_ERROR A PCI_CAP with Offset is already linked by
|
|
CapHdrOffsets. This indicates a loop in the
|
|
capabilities list being parsed.
|
|
**/
|
|
STATIC
|
|
RETURN_STATUS
|
|
InsertPciCap (
|
|
IN OUT PCI_CAP_LIST *CapList,
|
|
IN OUT ORDERED_COLLECTION *CapHdrOffsets,
|
|
IN PCI_CAP_DOMAIN Domain,
|
|
IN UINT16 CapId,
|
|
IN UINT16 Offset,
|
|
IN UINT8 Version
|
|
)
|
|
{
|
|
PCI_CAP *PciCap;
|
|
RETURN_STATUS Status;
|
|
ORDERED_COLLECTION_ENTRY *PciCapEntry;
|
|
PCI_CAP *InstanceZero;
|
|
|
|
ASSERT ((Offset & 0x3) == 0);
|
|
ASSERT (Offset < (Domain == PciCapNormal ?
|
|
PCI_MAX_CONFIG_OFFSET : PCI_EXP_MAX_CONFIG_OFFSET));
|
|
ASSERT (Domain == PciCapExtended || Version == 0);
|
|
|
|
//
|
|
// Set InstanceZero to suppress incorrect compiler/analyzer warnings.
|
|
//
|
|
InstanceZero = NULL;
|
|
|
|
//
|
|
// Allocate PciCap, and populate it assuming it is the first occurrence of
|
|
// (Domain, CapId). Note that PciCap->MaxSizeHint is not assigned the final
|
|
// value just yet.
|
|
//
|
|
PciCap = AllocatePool (sizeof *PciCap);
|
|
if (PciCap == NULL) {
|
|
return RETURN_OUT_OF_RESOURCES;
|
|
}
|
|
PciCap->Key.Domain = Domain;
|
|
PciCap->Key.CapId = CapId;
|
|
PciCap->Key.Instance = 0;
|
|
PciCap->NumInstancesUnion.NumInstances = 1;
|
|
PciCap->Offset = Offset;
|
|
PciCap->MaxSizeHint = 0;
|
|
PciCap->Version = Version;
|
|
|
|
//
|
|
// Add PciCap to CapList.
|
|
//
|
|
Status = OrderedCollectionInsert (CapList->Capabilities, &PciCapEntry,
|
|
PciCap);
|
|
if (RETURN_ERROR (Status)) {
|
|
if (Status == RETURN_OUT_OF_RESOURCES) {
|
|
goto FreePciCap;
|
|
}
|
|
ASSERT (Status == RETURN_ALREADY_STARTED);
|
|
//
|
|
// PciCap is not the first instance of (Domain, CapId). Add it as a new
|
|
// instance, taking the current instance count from Instance#0. Note that
|
|
// we don't bump the instance count maintained in Instance#0 just yet, to
|
|
// keep rollback on errors simple.
|
|
//
|
|
InstanceZero = OrderedCollectionUserStruct (PciCapEntry);
|
|
PciCap->Key.Instance = InstanceZero->NumInstancesUnion.NumInstances;
|
|
PciCap->NumInstancesUnion.InstanceZero = InstanceZero;
|
|
|
|
ASSERT (PciCap->Key.Instance > 0);
|
|
Status = OrderedCollectionInsert (CapList->Capabilities, &PciCapEntry,
|
|
PciCap);
|
|
if (Status == RETURN_OUT_OF_RESOURCES) {
|
|
goto FreePciCap;
|
|
}
|
|
}
|
|
//
|
|
// At this point, PciCap has been inserted in CapList->Capabilities, either
|
|
// with Instance==0 or with Instance>0. PciCapEntry is the iterator that
|
|
// links PciCap.
|
|
//
|
|
ASSERT_RETURN_ERROR (Status);
|
|
|
|
//
|
|
// Link PciCap into CapHdrOffsets too, to order it globally based on config
|
|
// space offset. Note that partial overlaps between capability headers is not
|
|
// possible: Offset is DWORD aligned, normal capability headers are 16-bit
|
|
// wide, and extended capability headers are 32-bit wide. Therefore any two
|
|
// capability headers either are distinct or start at the same offset
|
|
// (implying a loop in the respective capabilities list).
|
|
//
|
|
Status = OrderedCollectionInsert (CapHdrOffsets, NULL, PciCap);
|
|
if (RETURN_ERROR (Status)) {
|
|
if (Status == RETURN_ALREADY_STARTED) {
|
|
//
|
|
// Loop found; map return status accordingly.
|
|
//
|
|
Status = RETURN_DEVICE_ERROR;
|
|
}
|
|
goto DeletePciCapFromCapList;
|
|
}
|
|
|
|
//
|
|
// Now we can bump the instance count maintained in Instance#0, if PciCap is
|
|
// not the first instance of (Domain, CapId).
|
|
//
|
|
if (PciCap->Key.Instance > 0) {
|
|
//
|
|
// Suppress invalid "nullptr dereference" compiler/analyzer warnings: the
|
|
// only way for "PciCap->Key.Instance" to be positive here is for it to
|
|
// have been assigned *from* dereferencing "InstanceZero" above.
|
|
//
|
|
ASSERT (InstanceZero != NULL);
|
|
|
|
InstanceZero->NumInstancesUnion.NumInstances++;
|
|
}
|
|
return RETURN_SUCCESS;
|
|
|
|
DeletePciCapFromCapList:
|
|
OrderedCollectionDelete (CapList->Capabilities, PciCapEntry, NULL);
|
|
|
|
FreePciCap:
|
|
FreePool (PciCap);
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
Calculate the MaxSizeHint member for a PCI_CAP object.
|
|
|
|
CalculatePciCapMaxSizeHint() may only be called once all capability instances
|
|
have been successfully processed by InsertPciCap().
|
|
|
|
@param[in,out] PciCap The PCI_CAP object for which to calculate the
|
|
MaxSizeHint member. The caller is responsible for
|
|
passing a PCI_CAP object that has been created by a
|
|
successful invocation of InsertPciCap().
|
|
|
|
@param[in] NextPciCap If NextPciCap is NULL, then the caller is responsible
|
|
for PciCap to represent the capability instance with
|
|
the highest header offset in all config space. If
|
|
NextPciCap is not NULL, then the caller is responsible
|
|
for (a) having created NextPciCap with a successful
|
|
invocation of InsertPciCap(), and (b) NextPciCap being
|
|
the direct successor of PciCap in config space offset
|
|
order, as ordered by ComparePciCapOffset().
|
|
**/
|
|
STATIC
|
|
VOID
|
|
CalculatePciCapMaxSizeHint (
|
|
IN OUT PCI_CAP *PciCap,
|
|
IN PCI_CAP *NextPciCap OPTIONAL
|
|
)
|
|
{
|
|
UINT16 ConfigSpaceSize;
|
|
|
|
ConfigSpaceSize = (PciCap->Key.Domain == PciCapNormal ?
|
|
PCI_MAX_CONFIG_OFFSET : PCI_EXP_MAX_CONFIG_OFFSET);
|
|
//
|
|
// The following is guaranteed by the interface contract on
|
|
// CalculatePciCapMaxSizeHint().
|
|
//
|
|
ASSERT (NextPciCap == NULL || PciCap->Offset < NextPciCap->Offset);
|
|
//
|
|
// The following is guaranteed by the interface contract on InsertPciCap().
|
|
//
|
|
ASSERT (PciCap->Offset < ConfigSpaceSize);
|
|
//
|
|
// Thus we can safely subtract PciCap->Offset from either of
|
|
// - ConfigSpaceSize
|
|
// - and NextPciCap->Offset (if NextPciCap is not NULL).
|
|
//
|
|
// PciCap extends from PciCap->Offset to NextPciCap->Offset (if any), except
|
|
// it cannot cross config space boundary.
|
|
//
|
|
if (NextPciCap == NULL || NextPciCap->Offset >= ConfigSpaceSize) {
|
|
PciCap->MaxSizeHint = ConfigSpaceSize - PciCap->Offset;
|
|
return;
|
|
}
|
|
PciCap->MaxSizeHint = NextPciCap->Offset - PciCap->Offset;
|
|
}
|
|
|
|
|
|
/**
|
|
Debug dump a PCI_CAP_LIST object at the DEBUG_VERBOSE level.
|
|
|
|
@param[in] CapList The PCI_CAP_LIST object to dump.
|
|
**/
|
|
STATIC
|
|
VOID
|
|
EFIAPI
|
|
DebugDumpPciCapList (
|
|
IN PCI_CAP_LIST *CapList
|
|
)
|
|
{
|
|
DEBUG_CODE_BEGIN ();
|
|
ORDERED_COLLECTION_ENTRY *PciCapEntry;
|
|
|
|
for (PciCapEntry = OrderedCollectionMin (CapList->Capabilities);
|
|
PciCapEntry != NULL;
|
|
PciCapEntry = OrderedCollectionNext (PciCapEntry)) {
|
|
PCI_CAP *PciCap;
|
|
RETURN_STATUS Status;
|
|
PCI_CAP_INFO Info;
|
|
|
|
PciCap = OrderedCollectionUserStruct (PciCapEntry);
|
|
Status = PciCapGetInfo (PciCap, &Info);
|
|
//
|
|
// PciCapGetInfo() cannot fail in this library instance.
|
|
//
|
|
ASSERT_RETURN_ERROR (Status);
|
|
|
|
DEBUG ((DEBUG_VERBOSE,
|
|
"%a:%a: %a 0x%04x %03u/%03u v0x%x @0x%03x+0x%03x\n", gEfiCallerBaseName,
|
|
__FUNCTION__, (Info.Domain == PciCapNormal ? "Norm" : "Extd"),
|
|
Info.CapId, Info.Instance, Info.NumInstances, Info.Version, Info.Offset,
|
|
Info.MaxSizeHint));
|
|
}
|
|
DEBUG_CODE_END ();
|
|
}
|
|
|
|
|
|
/**
|
|
Empty a collection of PCI_CAP structures, optionally releasing the referenced
|
|
PCI_CAP structures themselves. Release the collection at last.
|
|
|
|
@param[in,out] PciCapCollection The collection to empty and release.
|
|
|
|
@param[in] FreePciCap TRUE if the PCI_CAP structures linked by
|
|
PciCapCollection should be released. When
|
|
FALSE, the caller is responsible for
|
|
retaining at least one reference to each
|
|
PCI_CAP structure originally linked by
|
|
PciCapCollection.
|
|
**/
|
|
STATIC
|
|
VOID
|
|
EmptyAndUninitPciCapCollection (
|
|
IN OUT ORDERED_COLLECTION *PciCapCollection,
|
|
IN BOOLEAN FreePciCap
|
|
)
|
|
{
|
|
ORDERED_COLLECTION_ENTRY *PciCapEntry;
|
|
ORDERED_COLLECTION_ENTRY *NextEntry;
|
|
|
|
for (PciCapEntry = OrderedCollectionMin (PciCapCollection);
|
|
PciCapEntry != NULL;
|
|
PciCapEntry = NextEntry) {
|
|
PCI_CAP *PciCap;
|
|
|
|
NextEntry = OrderedCollectionNext (PciCapEntry);
|
|
OrderedCollectionDelete (PciCapCollection, PciCapEntry, (VOID **)&PciCap);
|
|
if (FreePciCap) {
|
|
FreePool (PciCap);
|
|
}
|
|
}
|
|
OrderedCollectionUninit (PciCapCollection);
|
|
}
|
|
|
|
|
|
/**
|
|
Parse the capabilities lists (both normal and extended, as applicable) of a
|
|
PCI device.
|
|
|
|
If the PCI device has no capabilities, that per se will not fail
|
|
PciCapListInit(); an empty capabilities list will be represented.
|
|
|
|
If the PCI device is found to be PCI Express, then an attempt will be made to
|
|
parse the extended capabilities list as well. If the first extended config
|
|
space access -- via PciDevice->ReadConfig() with SourceOffset=0x100 and
|
|
Size=4 -- fails, that per se will not fail PciCapListInit(); the device will
|
|
be assumed to have no extended capabilities.
|
|
|
|
@param[in] PciDevice Implementation-specific unique representation of the
|
|
PCI device in the PCI hierarchy.
|
|
|
|
@param[out] CapList Opaque data structure that holds an in-memory
|
|
representation of the parsed capabilities lists of
|
|
PciDevice.
|
|
|
|
@retval RETURN_SUCCESS The capabilities lists have been parsed from
|
|
config space.
|
|
|
|
@retval RETURN_OUT_OF_RESOURCES Memory allocation failed.
|
|
|
|
@retval RETURN_DEVICE_ERROR A loop or some other kind of invalid pointer
|
|
was detected in the capabilities lists of
|
|
PciDevice.
|
|
|
|
@return Error codes propagated from
|
|
PciDevice->ReadConfig().
|
|
**/
|
|
RETURN_STATUS
|
|
EFIAPI
|
|
PciCapListInit (
|
|
IN PCI_CAP_DEV *PciDevice,
|
|
OUT PCI_CAP_LIST **CapList
|
|
)
|
|
{
|
|
PCI_CAP_LIST *OutCapList;
|
|
RETURN_STATUS Status;
|
|
ORDERED_COLLECTION *CapHdrOffsets;
|
|
UINT16 PciStatusReg;
|
|
BOOLEAN DeviceIsExpress;
|
|
ORDERED_COLLECTION_ENTRY *OffsetEntry;
|
|
|
|
//
|
|
// Allocate the output structure.
|
|
//
|
|
OutCapList = AllocatePool (sizeof *OutCapList);
|
|
if (OutCapList == NULL) {
|
|
return RETURN_OUT_OF_RESOURCES;
|
|
}
|
|
//
|
|
// The OutCapList->Capabilities collection owns the PCI_CAP structures and
|
|
// orders them based on PCI_CAP.Key.
|
|
//
|
|
OutCapList->Capabilities = OrderedCollectionInit (ComparePciCap,
|
|
ComparePciCapKey);
|
|
if (OutCapList->Capabilities == NULL) {
|
|
Status = RETURN_OUT_OF_RESOURCES;
|
|
goto FreeOutCapList;
|
|
}
|
|
|
|
//
|
|
// The (temporary) CapHdrOffsets collection only references PCI_CAP
|
|
// structures, and orders them based on PCI_CAP.Offset.
|
|
//
|
|
CapHdrOffsets = OrderedCollectionInit (ComparePciCapOffset,
|
|
ComparePciCapOffsetKey);
|
|
if (CapHdrOffsets == NULL) {
|
|
Status = RETURN_OUT_OF_RESOURCES;
|
|
goto FreeCapabilities;
|
|
}
|
|
|
|
//
|
|
// Whether the device is PCI Express depends on the normal capability with
|
|
// identifier EFI_PCI_CAPABILITY_ID_PCIEXP.
|
|
//
|
|
DeviceIsExpress = FALSE;
|
|
|
|
//
|
|
// Check whether a normal capabilities list is present. If there's none,
|
|
// that's not an error; we'll just return OutCapList->Capabilities empty.
|
|
//
|
|
Status = PciDevice->ReadConfig (PciDevice, PCI_PRIMARY_STATUS_OFFSET,
|
|
&PciStatusReg, sizeof PciStatusReg);
|
|
if (RETURN_ERROR (Status)) {
|
|
goto FreeCapHdrOffsets;
|
|
}
|
|
if ((PciStatusReg & EFI_PCI_STATUS_CAPABILITY) != 0) {
|
|
UINT8 NormalCapHdrOffset;
|
|
|
|
//
|
|
// Fetch the start offset of the normal capabilities list.
|
|
//
|
|
Status = PciDevice->ReadConfig (PciDevice, PCI_CAPBILITY_POINTER_OFFSET,
|
|
&NormalCapHdrOffset, sizeof NormalCapHdrOffset);
|
|
if (RETURN_ERROR (Status)) {
|
|
goto FreeCapHdrOffsets;
|
|
}
|
|
|
|
//
|
|
// Traverse the normal capabilities list.
|
|
//
|
|
NormalCapHdrOffset &= 0xFC;
|
|
while (NormalCapHdrOffset > 0) {
|
|
EFI_PCI_CAPABILITY_HDR NormalCapHdr;
|
|
|
|
Status = PciDevice->ReadConfig (PciDevice, NormalCapHdrOffset,
|
|
&NormalCapHdr, sizeof NormalCapHdr);
|
|
if (RETURN_ERROR (Status)) {
|
|
goto FreeCapHdrOffsets;
|
|
}
|
|
|
|
Status = InsertPciCap (OutCapList, CapHdrOffsets, PciCapNormal,
|
|
NormalCapHdr.CapabilityID, NormalCapHdrOffset, 0);
|
|
if (RETURN_ERROR (Status)) {
|
|
goto FreeCapHdrOffsets;
|
|
}
|
|
|
|
if (NormalCapHdr.CapabilityID == EFI_PCI_CAPABILITY_ID_PCIEXP) {
|
|
DeviceIsExpress = TRUE;
|
|
}
|
|
NormalCapHdrOffset = NormalCapHdr.NextItemPtr & 0xFC;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the device has been found PCI Express, attempt to traverse the extended
|
|
// capabilities list. It starts right after the normal config space.
|
|
//
|
|
if (DeviceIsExpress) {
|
|
UINT16 ExtendedCapHdrOffset;
|
|
|
|
ExtendedCapHdrOffset = PCI_MAX_CONFIG_OFFSET;
|
|
while (ExtendedCapHdrOffset > 0) {
|
|
PCI_EXPRESS_EXTENDED_CAPABILITIES_HEADER ExtendedCapHdr;
|
|
|
|
Status = PciDevice->ReadConfig (PciDevice, ExtendedCapHdrOffset,
|
|
&ExtendedCapHdr, sizeof ExtendedCapHdr);
|
|
//
|
|
// If the first extended config space access fails, assume the device has
|
|
// no extended capabilities. If the first extended config space access
|
|
// succeeds but we read an "all bits zero" extended capability header,
|
|
// that means (by spec) the device has no extended capabilities.
|
|
//
|
|
if (ExtendedCapHdrOffset == PCI_MAX_CONFIG_OFFSET &&
|
|
(RETURN_ERROR (Status) ||
|
|
IsZeroBuffer (&ExtendedCapHdr, sizeof ExtendedCapHdr))) {
|
|
break;
|
|
}
|
|
if (RETURN_ERROR (Status)) {
|
|
goto FreeCapHdrOffsets;
|
|
}
|
|
|
|
Status = InsertPciCap (OutCapList, CapHdrOffsets, PciCapExtended,
|
|
(UINT16)ExtendedCapHdr.CapabilityId, ExtendedCapHdrOffset,
|
|
(UINT8)ExtendedCapHdr.CapabilityVersion);
|
|
if (RETURN_ERROR (Status)) {
|
|
goto FreeCapHdrOffsets;
|
|
}
|
|
|
|
ExtendedCapHdrOffset = ExtendedCapHdr.NextCapabilityOffset & 0xFFC;
|
|
if (ExtendedCapHdrOffset > 0 &&
|
|
ExtendedCapHdrOffset < PCI_MAX_CONFIG_OFFSET) {
|
|
//
|
|
// Invalid capability pointer.
|
|
//
|
|
Status = RETURN_DEVICE_ERROR;
|
|
goto FreeCapHdrOffsets;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Both capabilities lists have been parsed; compute the PCI_CAP.MaxSizeHint
|
|
// members if at least one capability has been found. In parallel, evacuate
|
|
// the CapHdrOffsets collection.
|
|
//
|
|
// At first, set OffsetEntry to the iterator of the PCI_CAP object with the
|
|
// lowest Offset (if such exists).
|
|
//
|
|
OffsetEntry = OrderedCollectionMin (CapHdrOffsets);
|
|
if (OffsetEntry != NULL) {
|
|
ORDERED_COLLECTION_ENTRY *NextOffsetEntry;
|
|
PCI_CAP *PciCap;
|
|
|
|
//
|
|
// Initialize NextOffsetEntry to the iterator of the PCI_CAP object with
|
|
// the second lowest Offset (if such exists).
|
|
//
|
|
NextOffsetEntry = OrderedCollectionNext (OffsetEntry);
|
|
//
|
|
// Calculate MaxSizeHint for all PCI_CAP objects except the one with the
|
|
// highest Offset.
|
|
//
|
|
while (NextOffsetEntry != NULL) {
|
|
PCI_CAP *NextPciCap;
|
|
|
|
OrderedCollectionDelete (CapHdrOffsets, OffsetEntry, (VOID **)&PciCap);
|
|
NextPciCap = OrderedCollectionUserStruct (NextOffsetEntry);
|
|
CalculatePciCapMaxSizeHint (PciCap, NextPciCap);
|
|
|
|
OffsetEntry = NextOffsetEntry;
|
|
NextOffsetEntry = OrderedCollectionNext (OffsetEntry);
|
|
}
|
|
//
|
|
// Calculate MaxSizeHint for the PCI_CAP object with the highest Offset.
|
|
//
|
|
OrderedCollectionDelete (CapHdrOffsets, OffsetEntry, (VOID **)&PciCap);
|
|
CalculatePciCapMaxSizeHint (PciCap, NULL);
|
|
}
|
|
ASSERT (OrderedCollectionIsEmpty (CapHdrOffsets));
|
|
OrderedCollectionUninit (CapHdrOffsets);
|
|
|
|
DebugDumpPciCapList (OutCapList);
|
|
*CapList = OutCapList;
|
|
return RETURN_SUCCESS;
|
|
|
|
FreeCapHdrOffsets:
|
|
EmptyAndUninitPciCapCollection (CapHdrOffsets, FALSE);
|
|
|
|
FreeCapabilities:
|
|
EmptyAndUninitPciCapCollection (OutCapList->Capabilities, TRUE);
|
|
|
|
FreeOutCapList:
|
|
FreePool (OutCapList);
|
|
|
|
ASSERT (RETURN_ERROR (Status));
|
|
DEBUG ((DEBUG_ERROR, "%a:%a: %r\n", gEfiCallerBaseName, __FUNCTION__,
|
|
Status));
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
Free the resources used by CapList.
|
|
|
|
@param[in] CapList The PCI_CAP_LIST object to free, originally produced by
|
|
PciCapListInit().
|
|
**/
|
|
VOID
|
|
EFIAPI
|
|
PciCapListUninit (
|
|
IN PCI_CAP_LIST *CapList
|
|
)
|
|
{
|
|
EmptyAndUninitPciCapCollection (CapList->Capabilities, TRUE);
|
|
FreePool (CapList);
|
|
}
|
|
|
|
|
|
/**
|
|
Locate a capability instance in the parsed capabilities lists.
|
|
|
|
@param[in] CapList The PCI_CAP_LIST object produced by PciCapListInit().
|
|
|
|
@param[in] Domain Distinguishes whether CapId is 8-bit wide and
|
|
interpreted in normal config space, or 16-bit wide and
|
|
interpreted in extended config space. Capability ID
|
|
definitions are relative to domain.
|
|
|
|
@param[in] CapId Capability identifier to look up.
|
|
|
|
@param[in] Instance Domain and CapId may identify a multi-instance
|
|
capability. When Instance is zero, the first instance of
|
|
the capability is located (in list traversal order --
|
|
which may not mean increasing config space offset
|
|
order). Higher Instance values locate subsequent
|
|
instances of the same capability (in list traversal
|
|
order).
|
|
|
|
@param[out] Cap The capability instance that matches the search
|
|
criteria. Cap is owned by CapList and becomes invalid
|
|
when CapList is freed with PciCapListUninit().
|
|
PciCapListFindCap() may be called with Cap set to NULL,
|
|
in order to test the existence of a specific capability
|
|
instance.
|
|
|
|
@retval RETURN_SUCCESS The capability instance identified by (Domain,
|
|
CapId, Instance) has been found.
|
|
|
|
@retval RETURN_NOT_FOUND The requested (Domain, CapId, Instance) capability
|
|
instance does not exist.
|
|
**/
|
|
RETURN_STATUS
|
|
EFIAPI
|
|
PciCapListFindCap (
|
|
IN PCI_CAP_LIST *CapList,
|
|
IN PCI_CAP_DOMAIN Domain,
|
|
IN UINT16 CapId,
|
|
IN UINT16 Instance,
|
|
OUT PCI_CAP **Cap OPTIONAL
|
|
)
|
|
{
|
|
PCI_CAP_KEY Key;
|
|
ORDERED_COLLECTION_ENTRY *PciCapEntry;
|
|
|
|
Key.Domain = Domain;
|
|
Key.CapId = CapId;
|
|
Key.Instance = Instance;
|
|
|
|
PciCapEntry = OrderedCollectionFind (CapList->Capabilities, &Key);
|
|
if (PciCapEntry == NULL) {
|
|
return RETURN_NOT_FOUND;
|
|
}
|
|
if (Cap != NULL) {
|
|
*Cap = OrderedCollectionUserStruct (PciCapEntry);
|
|
}
|
|
return RETURN_SUCCESS;
|
|
}
|
|
|
|
|
|
/**
|
|
Locate the first instance of the capability given by (Domain, CapId) such
|
|
that the instance's Version is greater than or equal to MinVersion.
|
|
|
|
This is a convenience function that may save client code calls to
|
|
PciCapListFindCap() and PciCapGetInfo().
|
|
|
|
@param[in] CapList The PCI_CAP_LIST object produced by PciCapListInit().
|
|
|
|
@param[in] Domain Distinguishes whether CapId is 8-bit wide and
|
|
interpreted in normal config space, or 16-bit wide and
|
|
interpreted in extended config space. Capability ID
|
|
definitions are relative to domain.
|
|
|
|
@param[in] CapId Capability identifier to look up.
|
|
|
|
@param[in] MinVersion The minimum version that the capability instance is
|
|
required to have. Note that all capability instances
|
|
in Domain=PciCapNormal have Version=0.
|
|
|
|
@param[out] Cap The first capability instance that matches the search
|
|
criteria. Cap is owned by CapList and becomes invalid
|
|
when CapList is freed with PciCapListUninit().
|
|
PciCapListFindCapVersion() may be called with Cap set
|
|
to NULL, in order just to test whether the search
|
|
criteria are satisfiable.
|
|
|
|
@retval RETURN_SUCCESS The first capability instance matching (Domain,
|
|
CapId, MinVersion) has been located.
|
|
|
|
@retval RETURN_NOT_FOUND No capability instance matches (Domain, CapId,
|
|
MinVersion).
|
|
**/
|
|
RETURN_STATUS
|
|
EFIAPI
|
|
PciCapListFindCapVersion (
|
|
IN PCI_CAP_LIST *CapList,
|
|
IN PCI_CAP_DOMAIN Domain,
|
|
IN UINT16 CapId,
|
|
IN UINT8 MinVersion,
|
|
OUT PCI_CAP **Cap OPTIONAL
|
|
)
|
|
{
|
|
PCI_CAP_KEY Key;
|
|
ORDERED_COLLECTION_ENTRY *PciCapEntry;
|
|
|
|
//
|
|
// Start the version checks at Instance#0 of (Domain, CapId).
|
|
//
|
|
Key.Domain = Domain;
|
|
Key.CapId = CapId;
|
|
Key.Instance = 0;
|
|
|
|
for (PciCapEntry = OrderedCollectionFind (CapList->Capabilities, &Key);
|
|
PciCapEntry != NULL;
|
|
PciCapEntry = OrderedCollectionNext (PciCapEntry)) {
|
|
PCI_CAP *PciCap;
|
|
|
|
PciCap = OrderedCollectionUserStruct (PciCapEntry);
|
|
//
|
|
// PCI_CAP.Key ordering keeps instances of the same (Domain, CapId)
|
|
// adjacent to each other, so stop searching if either Domain or CapId
|
|
// changes.
|
|
//
|
|
if (PciCap->Key.Domain != Domain || PciCap->Key.CapId != CapId) {
|
|
break;
|
|
}
|
|
if (PciCap->Version >= MinVersion) {
|
|
//
|
|
// Match found.
|
|
//
|
|
if (Cap != NULL) {
|
|
*Cap = PciCap;
|
|
}
|
|
return RETURN_SUCCESS;
|
|
}
|
|
}
|
|
return RETURN_NOT_FOUND;
|
|
}
|
|
|
|
|
|
/**
|
|
Get information about a PCI Capability instance.
|
|
|
|
@param[in] Cap The capability instance to get info about, located with
|
|
PciCapListFindCap*().
|
|
|
|
@param[out] Info A PCI_CAP_INFO structure that describes the properties of
|
|
Cap.
|
|
|
|
@retval RETURN_SUCCESS Fields of Info have been set.
|
|
|
|
@return Unspecified error codes, if filling in Info failed
|
|
for some reason.
|
|
**/
|
|
RETURN_STATUS
|
|
EFIAPI
|
|
PciCapGetInfo (
|
|
IN PCI_CAP *Cap,
|
|
OUT PCI_CAP_INFO *Info
|
|
)
|
|
{
|
|
PCI_CAP *InstanceZero;
|
|
|
|
ASSERT (Info != NULL);
|
|
|
|
InstanceZero = (Cap->Key.Instance == 0 ? Cap :
|
|
Cap->NumInstancesUnion.InstanceZero);
|
|
|
|
Info->Domain = Cap->Key.Domain;
|
|
Info->CapId = Cap->Key.CapId;
|
|
Info->NumInstances = InstanceZero->NumInstancesUnion.NumInstances;
|
|
Info->Instance = Cap->Key.Instance;
|
|
Info->Offset = Cap->Offset;
|
|
Info->MaxSizeHint = Cap->MaxSizeHint;
|
|
Info->Version = Cap->Version;
|
|
|
|
return RETURN_SUCCESS;
|
|
}
|
|
|
|
|
|
/**
|
|
Read a slice of a capability instance.
|
|
|
|
The function performs as few config space accesses as possible (without
|
|
attempting 64-bit wide accesses). PciCapRead() performs bounds checking on
|
|
SourceOffsetInCap and Size, and only invokes PciDevice->ReadConfig() if the
|
|
requested transfer falls within Cap.
|
|
|
|
@param[in] PciDevice Implementation-specific unique representation
|
|
of the PCI device in the PCI hierarchy.
|
|
|
|
@param[in] Cap The capability instance to read, located with
|
|
PciCapListFindCap*().
|
|
|
|
@param[in] SourceOffsetInCap Source offset relative to the capability
|
|
header to start reading from. A zero value
|
|
refers to the first byte of the capability
|
|
header.
|
|
|
|
@param[out] DestinationBuffer Buffer to store the read data to.
|
|
|
|
@param[in] Size The number of bytes to transfer.
|
|
|
|
@retval RETURN_SUCCESS Size bytes have been transferred from Cap to
|
|
DestinationBuffer.
|
|
|
|
@retval RETURN_BAD_BUFFER_SIZE Reading Size bytes starting from
|
|
SourceOffsetInCap would not (entirely) be
|
|
contained within Cap, as suggested by
|
|
PCI_CAP_INFO.MaxSizeHint. No bytes have been
|
|
read.
|
|
|
|
@return Error codes propagated from
|
|
PciDevice->ReadConfig(). Fewer than Size
|
|
bytes may have been read.
|
|
**/
|
|
RETURN_STATUS
|
|
EFIAPI
|
|
PciCapRead (
|
|
IN PCI_CAP_DEV *PciDevice,
|
|
IN PCI_CAP *Cap,
|
|
IN UINT16 SourceOffsetInCap,
|
|
OUT VOID *DestinationBuffer,
|
|
IN UINT16 Size
|
|
)
|
|
{
|
|
//
|
|
// Note: all UINT16 values are promoted to INT32 below, and addition and
|
|
// comparison take place between INT32 values.
|
|
//
|
|
if (SourceOffsetInCap + Size > Cap->MaxSizeHint) {
|
|
return RETURN_BAD_BUFFER_SIZE;
|
|
}
|
|
return PciDevice->ReadConfig (PciDevice, Cap->Offset + SourceOffsetInCap,
|
|
DestinationBuffer, Size);
|
|
}
|
|
|
|
|
|
/**
|
|
Write a slice of a capability instance.
|
|
|
|
The function performs as few config space accesses as possible (without
|
|
attempting 64-bit wide accesses). PciCapWrite() performs bounds checking on
|
|
DestinationOffsetInCap and Size, and only invokes PciDevice->WriteConfig() if
|
|
the requested transfer falls within Cap.
|
|
|
|
@param[in] PciDevice Implementation-specific unique
|
|
representation of the PCI device in the
|
|
PCI hierarchy.
|
|
|
|
@param[in] Cap The capability instance to write, located
|
|
with PciCapListFindCap*().
|
|
|
|
@param[in] DestinationOffsetInCap Destination offset relative to the
|
|
capability header to start writing at. A
|
|
zero value refers to the first byte of the
|
|
capability header.
|
|
|
|
@param[in] SourceBuffer Buffer to read the data to be stored from.
|
|
|
|
@param[in] Size The number of bytes to transfer.
|
|
|
|
@retval RETURN_SUCCESS Size bytes have been transferred from
|
|
SourceBuffer to Cap.
|
|
|
|
@retval RETURN_BAD_BUFFER_SIZE Writing Size bytes starting at
|
|
DestinationOffsetInCap would not (entirely)
|
|
be contained within Cap, as suggested by
|
|
PCI_CAP_INFO.MaxSizeHint. No bytes have been
|
|
written.
|
|
|
|
@return Error codes propagated from
|
|
PciDevice->WriteConfig(). Fewer than Size
|
|
bytes may have been written.
|
|
**/
|
|
RETURN_STATUS
|
|
EFIAPI
|
|
PciCapWrite (
|
|
IN PCI_CAP_DEV *PciDevice,
|
|
IN PCI_CAP *Cap,
|
|
IN UINT16 DestinationOffsetInCap,
|
|
IN VOID *SourceBuffer,
|
|
IN UINT16 Size
|
|
)
|
|
{
|
|
//
|
|
// Note: all UINT16 values are promoted to INT32 below, and addition and
|
|
// comparison take place between INT32 values.
|
|
//
|
|
if (DestinationOffsetInCap + Size > Cap->MaxSizeHint) {
|
|
return RETURN_BAD_BUFFER_SIZE;
|
|
}
|
|
return PciDevice->WriteConfig (PciDevice,
|
|
Cap->Offset + DestinationOffsetInCap, SourceBuffer,
|
|
Size);
|
|
}
|