/** @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, | |
__func__, | |
(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, | |
__func__, | |
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 | |
); | |
} |