OpenCorePkg/Library/OcDevicePathLib/OcDevicePathLib.c
Vitaly Cheptsov 4b3b0cb1d7
OcBootManagementLib: Add Apple DP expansion for VirtIO BLK (#561)
Apparently macOS strips down HD nodes from VirtIO BLK devices
leaving them "infix" shortened, i.e. PCI/APFS instead of
PCI/HD/APFS.
2024-10-07 17:44:08 +03:00

1544 lines
44 KiB
C

/** @file
Copyright (C) 2016 - 2018, The HermitCrabs Lab. All rights reserved.
All rights reserved.
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution. The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include <Uefi.h>
#include <Protocol/DevicePathToText.h>
#include <Protocol/SimpleFileSystem.h>
#include <Library/DebugLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/BaseOverflowLib.h>
#include <Library/DevicePathLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/OcStringLib.h>
#include <Library/OcDevicePathLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/OcDebugLogLib.h>
EFI_DEVICE_PATH_PROTOCOL *
AppendFileNameDevicePath (
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath,
IN CHAR16 *FileName
)
{
EFI_DEVICE_PATH_PROTOCOL *AppendedDevicePath;
FILEPATH_DEVICE_PATH *FilePathNode;
EFI_DEVICE_PATH_PROTOCOL *DevicePathEndNode;
UINTN FileNameSize;
UINTN FileDevicePathNodeSize;
AppendedDevicePath = NULL;
if ((DevicePath != NULL) && (FileName != NULL)) {
FileNameSize = StrSize (FileName);
FileDevicePathNodeSize = (FileNameSize + SIZE_OF_FILEPATH_DEVICE_PATH + END_DEVICE_PATH_LENGTH);
FilePathNode = AllocateZeroPool (FileDevicePathNodeSize);
if (FilePathNode != NULL) {
FilePathNode->Header.Type = MEDIA_DEVICE_PATH;
FilePathNode->Header.SubType = MEDIA_FILEPATH_DP;
SetDevicePathNodeLength (&FilePathNode->Header, FileNameSize + SIZE_OF_FILEPATH_DEVICE_PATH);
CopyMem (FilePathNode->PathName, FileName, FileNameSize);
DevicePathEndNode = NextDevicePathNode (&FilePathNode->Header);
SetDevicePathEndNode (DevicePathEndNode);
AppendedDevicePath = AppendDevicePath (DevicePath, (EFI_DEVICE_PATH_PROTOCOL *)FilePathNode);
FreePool (FilePathNode);
}
}
return AppendedDevicePath;
}
EFI_DEVICE_PATH_PROTOCOL *
FindDevicePathEndNode (
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath
)
{
while (!IsDevicePathEnd (DevicePath)) {
DevicePath = NextDevicePathNode (DevicePath);
}
return DevicePath;
}
EFI_DEVICE_PATH_PROTOCOL *
FindDevicePathNodeWithType (
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath,
IN UINT8 Type,
IN UINT8 SubType OPTIONAL
)
{
EFI_DEVICE_PATH_PROTOCOL *DevicePathNode;
DevicePathNode = NULL;
while (!IsDevicePathEnd (DevicePath)) {
if ( (DevicePathType (DevicePath) == Type)
&& ((SubType == 0) || (DevicePathSubType (DevicePath) == SubType)))
{
DevicePathNode = DevicePath;
break;
}
DevicePath = NextDevicePathNode (DevicePath);
}
return DevicePathNode;
}
EFI_DEVICE_PATH_PROTOCOL *
AbsoluteDevicePath (
IN EFI_HANDLE Handle,
IN EFI_DEVICE_PATH_PROTOCOL *RelativePath OPTIONAL
)
{
EFI_DEVICE_PATH_PROTOCOL *HandlePath;
EFI_DEVICE_PATH_PROTOCOL *NewPath;
HandlePath = DevicePathFromHandle (Handle);
if (HandlePath == NULL) {
return NULL;
}
if (RelativePath == NULL) {
return DuplicateDevicePath (HandlePath);
}
NewPath = AppendDevicePath (HandlePath, RelativePath);
if (NewPath == NULL) {
return DuplicateDevicePath (HandlePath);
}
return NewPath;
}
EFI_DEVICE_PATH_PROTOCOL *
TrailedBooterDevicePath (
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath
)
{
EFI_DEVICE_PATH_PROTOCOL *DevicePathWalker;
EFI_DEVICE_PATH_PROTOCOL *NewDevicePath;
FILEPATH_DEVICE_PATH *FilePath;
FILEPATH_DEVICE_PATH *NewFilePath;
UINTN Length;
UINTN Size;
DevicePathWalker = DevicePath;
while (!IsDevicePathEnd (DevicePathWalker)) {
if ( (DevicePathType (DevicePathWalker) == MEDIA_DEVICE_PATH)
&& (DevicePathSubType (DevicePathWalker) == MEDIA_FILEPATH_DP)
&& IsDevicePathEnd (NextDevicePathNode (DevicePathWalker)))
{
FilePath = (FILEPATH_DEVICE_PATH *)DevicePathWalker;
Length = OcFileDevicePathNameLen (FilePath);
if (Length > 0) {
if (FilePath->PathName[Length - 1] == L'\\') {
//
// Already appended, good. It should never be true with Apple entries though.
//
return NULL;
}
if ((Length > 4) && ( (FilePath->PathName[Length - 4] != '.')
|| ((FilePath->PathName[Length - 3] != 'e') && (FilePath->PathName[Length - 3] != 'E'))
|| ((FilePath->PathName[Length - 2] != 'f') && (FilePath->PathName[Length - 2] != 'F'))
|| ((FilePath->PathName[Length - 1] != 'i') && (FilePath->PathName[Length - 1] != 'I'))))
{
//
// Found! We should have gotten something like:
// PciRoot(0x0)/Pci(...)/Pci(...)/Sata(...)/HD(...)/\com.apple.recovery.boot
//
Size = GetDevicePathSize (DevicePath);
NewDevicePath = (EFI_DEVICE_PATH_PROTOCOL *)AllocatePool (Size + sizeof (CHAR16));
if (NewDevicePath == NULL) {
//
// Allocation failure, just ignore.
//
return NULL;
}
//
// Strip the string termination and DP end node, which will get re-set
//
CopyMem (NewDevicePath, DevicePath, Size - sizeof (CHAR16) - END_DEVICE_PATH_LENGTH);
NewFilePath = (FILEPATH_DEVICE_PATH *)((UINT8 *)DevicePathWalker - (UINT8 *)DevicePath + (UINT8 *)NewDevicePath);
Size = DevicePathNodeLength (DevicePathWalker) + sizeof (CHAR16);
SetDevicePathNodeLength (NewFilePath, Size);
NewFilePath->PathName[Length] = L'\\';
NewFilePath->PathName[Length+1] = L'\0';
SetDevicePathEndNode ((UINT8 *)NewFilePath + Size);
return NewDevicePath;
}
}
}
DevicePathWalker = NextDevicePathNode (DevicePathWalker);
}
//
// Has .efi suffix or unsupported format.
//
return NULL;
}
VOID
OcFixAppleBootDevicePathNodeRestore (
IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath,
IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePathNode,
IN CONST APPLE_BOOT_DP_PATCH_CONTEXT *RestoreContext
)
{
EFI_DEV_PATH_PTR Node;
UINT8 NodeType;
UINT8 NodeSubType;
//
// ATTENTION: This function must be carefully sync'd with changes to
// OcFixAppleBootDevicePathNode().
//
Node.DevPath = *DevicePathNode;
NodeType = DevicePathType (Node.DevPath);
NodeSubType = DevicePathSubType (Node.DevPath);
if (RestoreContext->OldPath != NULL) {
//
// Restore the previously stored Device Path prior to patching.
//
*DevicePathNode = (EFI_DEVICE_PATH_PROTOCOL *)(
(UINTN)RestoreContext->OldPath +
((UINTN)*DevicePathNode - (UINTN)*DevicePath)
);
FreePool (*DevicePath);
*DevicePath = RestoreContext->OldPath;
return;
}
if (NodeType == MESSAGING_DEVICE_PATH) {
switch (NodeSubType) {
case MSG_SATA_DP:
Node.Sata->PortMultiplierPortNumber = RestoreContext->Types.Sata.PortMultiplierPortNumber;
break;
//
// The related patches in OcFixAppleBootDevicePathNode() are performed
// from MSG_SASEX_DP and MSG_NVME_NAMESPACE_DP but change the SubType.
// Please refer to the destination rather than the source SubType when
// matching the logic.
//
case MSG_NVME_NAMESPACE_DP:
case 0x22:
Node.NvmeNamespace->Header.SubType = RestoreContext->Types.SasExNvme.SubType;
break;
default:
break;
}
} else if (NodeType == ACPI_DEVICE_PATH) {
switch (NodeSubType) {
case ACPI_DP:
Node.Acpi->HID = RestoreContext->Types.Acpi.HID;
Node.Acpi->UID = RestoreContext->Types.Acpi.UID;
break;
case ACPI_EXTENDED_DP:
Node.ExtendedAcpi->HID = RestoreContext->Types.ExtendedAcpi.HID;
Node.ExtendedAcpi->CID = RestoreContext->Types.ExtendedAcpi.CID;
break;
default:
break;
}
}
}
VOID
OcFixAppleBootDevicePathNodeRestoreFree (
IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePathNode,
IN OUT APPLE_BOOT_DP_PATCH_CONTEXT *RestoreContext
)
{
//
// ATTENTION: This function must be carefully sync'd with changes to
// OcFixAppleBootDevicePathNode().
//
//
// Free the previously stored original Device Path for expansion patches.
//
if (RestoreContext->OldPath != NULL) {
FreePool (RestoreContext->OldPath);
}
}
/*
Constructs a new device path for an unrecognised device path with a hard drive
node.
@param[in,out] DevicePath A pointer to the device path to fix the node
of. It must be a pool memory buffer.
On success, may be updated with a reallocated
pool memory buffer.
@param[in,out] DevicePathNode A pointer to the device path node to fix. It
must be a node of *DevicePath.
On success, may be updated with the
corresponding node of *DevicePath.
@param[out] RestoreContext A pointer to a context that can be used to
restore DevicePathNode's original content in
the case of failure.
On success, data may need to be freed.
@retval -1 DevicePathNode could not be fixed.
@retval 1 DevicePathNode and is valid.
*/
STATIC
INTN
InternalExpandNewPath (
IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath,
IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePathNode
)
{
EFI_DEVICE_PATH_PROTOCOL *HdNode;
UINTN PrefixSize;
EFI_DEVICE_PATH_PROTOCOL *ExpandedPath;
EFI_DEVICE_PATH_PROTOCOL *ExpandedNode;
DebugPrintDevicePath (DEBUG_VERBOSE, "Expanding new DP from", *DevicePath);
DebugPrintDevicePath (DEBUG_VERBOSE, "at node", *DevicePathNode);
//
// Find HD node to locate a valid Device Path of. We require the prefix
// till the offending node (e.g. a SATA node, which should be
// preceeded by a PCI chain) as well as the suffix match (though latter
// may be expanded). For example SATA should be an arbitrary VendorBlockIo
// node.
//
HdNode = FindDevicePathNodeWithType (
*DevicePath,
MEDIA_DEVICE_PATH,
MEDIA_HARDDRIVE_DP
);
if (HdNode == NULL) {
DEBUG ((DEBUG_VERBOSE, "Failed to find HD node\n"));
//
// Expansion makes little sense when we don't have a HD node.
//
return -1;
}
PrefixSize = (UINTN)*DevicePathNode - (UINTN)*DevicePath;
//
// Expand the Device Path based of the HD node and sanity-check it
// likely describes the intended path.
//
for (
ExpandedPath = OcGetNextLoadOptionDevicePath (HdNode, NULL);
ExpandedPath != NULL;
ExpandedPath = OcGetNextLoadOptionDevicePath (HdNode, ExpandedPath)
)
{
DebugPrintDevicePath (DEBUG_VERBOSE, "DP candidate", ExpandedPath);
//
// Skip this expansion if the prefix does not match.
//
if ( (GetDevicePathSize (ExpandedPath) < PrefixSize)
|| (CompareMem (ExpandedPath, *DevicePath, PrefixSize) != 0))
{
DEBUG ((DEBUG_VERBOSE, "Prefix does not match\n"));
continue;
}
//
// The suffix is handled implicitly (by OcGetNextLoadOptionDevicePath).
// Keep in mind that with this logic our broken node may expand to an
// arbitrary number of nodes now.
//
ExpandedNode = (EFI_DEVICE_PATH_PROTOCOL *)(
(UINTN)ExpandedPath + PrefixSize
);
DebugPrintDevicePath (DEBUG_VERBOSE, "accepted DP", ExpandedPath);
DebugPrintDevicePath (DEBUG_VERBOSE, "fix starting at", ExpandedNode);
DEBUG_CODE (
EFI_DEVICE_PATH_PROTOCOL *RemDevPath = ExpandedPath;
EFI_HANDLE Dev;
EFI_STATUS Status = gBS->LocateDevicePath (&gEfiDevicePathProtocolGuid, &RemDevPath, &Dev);
if (EFI_ERROR (Status) || (RemDevPath->Type != END_DEVICE_PATH_TYPE) || (RemDevPath->SubType != END_ENTIRE_DEVICE_PATH_SUBTYPE)) {
DEBUG ((DEBUG_VERBOSE, "borked piece of crap\n"));
}
);
*DevicePath = ExpandedPath;
*DevicePathNode = ExpandedNode;
return 1;
}
//
// No adequate expansion could be found.
//
return -1;
}
/**
Fix Apple Boot Device Path VirtIO node to be compatible with conventional UEFI
implementations. Currently only APFS file system is supported as VirtIO support
landed in macOS in 10.14, which was APFS-only.
@param[in,out] DevicePath A pointer to the device path to fix a node of.
It must be a pool memory buffer.
On success, may be updated with a reallocated
pool memory buffer.
@param[in,out] DevicePathNode A pointer to the device path node to fix. It
must be a node of *DevicePath.
On success, may be updated with the
corresponding node of *DevicePath.
@param[out] RestoreContext A pointer to a context that can be used to
restore DevicePathNode's original content in
the case of failure.
On success, data may need to be freed.
@retval 0 DevicePathNode was not modified and may be valid.
@retval 1 DevicePathNode was fixed and may be valid.
**/
STATIC
INTN
InternalFixAppleBootDevicePathVirtioNode (
IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath,
IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePathNode,
OUT APPLE_BOOT_DP_PATCH_CONTEXT *RestoreContext OPTIONAL
)
{
EFI_STATUS Status;
UINTN HandleCount;
EFI_HANDLE Device;
EFI_HANDLE *HandleBuffer;
UINTN Index;
UINTN DevicePathSize;
UINTN ValidPrefixSize;
EFI_DEVICE_PATH_PROTOCOL *TestedDevicePath;
EFI_DEVICE_PATH_PROTOCOL *TestedDeviceNode;
EFI_DEVICE_PATH_PROTOCOL *NewDeviceNode;
EFI_DEVICE_PATH_PROTOCOL *FixedDevicePath;
UINTN TestedSize;
UINT8 TestedNodeType;
UINT8 TestedNodeSubType;
//
// Apple may skip HD node for VirtIO-BLK devices giving APFS Vendor DP
// right away in their device paths. Try to locate and fix them up.
//
//
// 1. Get all Block Io protocol handles.
//
HandleCount = 0;
Status = gBS->LocateHandleBuffer (
ByProtocol,
&gEfiBlockIoProtocolGuid,
NULL,
&HandleCount,
&HandleBuffer
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_VERBOSE, "Failed to locate any block i/o to fixup DP\n"));
return 0;
}
//
// 2. Calculate prefix size.
//
DevicePathSize = GetDevicePathSize (*DevicePath);
ValidPrefixSize = DevicePathSize - GetDevicePathSize (*DevicePathNode);
for (Index = 0; Index < HandleCount; ++Index) {
//
// 2. Get Device Path protocol from each handle.
//
Status = gBS->HandleProtocol (
HandleBuffer[Index],
&gEfiDevicePathProtocolGuid,
(VOID **)&TestedDevicePath
);
if (EFI_ERROR (Status)) {
continue;
}
//
// 3. Calculate found device path size.
//
TestedSize = GetDevicePathSize (TestedDevicePath);
//
// 4.1 Skip tested device paths that do not match HD paths.
//
if (TestedSize != ValidPrefixSize + sizeof (HARDDRIVE_DEVICE_PATH) + END_DEVICE_PATH_LENGTH) {
continue;
}
//
// 4.2 Skip tested device paths that do not share a common prefix.
//
if (CompareMem (TestedDevicePath, *DevicePath, ValidPrefixSize) != 0) {
continue;
}
//
// 4.3 Skip tested device paths that are not adding HD nodes.
//
TestedDeviceNode = (VOID *)((UINTN)TestedDevicePath + ValidPrefixSize);
TestedNodeType = DevicePathType (TestedDeviceNode);
TestedNodeSubType = DevicePathSubType (TestedDeviceNode);
if ( (TestedNodeType != MEDIA_DEVICE_PATH)
|| (TestedNodeSubType != MEDIA_HARDDRIVE_DP))
{
continue;
}
//
// 5. Initial checks matched. Build fixed device path.
//
FixedDevicePath = AllocatePool (DevicePathSize + sizeof (HARDDRIVE_DEVICE_PATH));
if (FixedDevicePath == NULL) {
FreePool (HandleBuffer);
return 0;
}
CopyMem (
FixedDevicePath,
TestedDevicePath,
ValidPrefixSize + sizeof (HARDDRIVE_DEVICE_PATH)
);
TestedDeviceNode = (VOID *)((UINTN)FixedDevicePath + ValidPrefixSize + sizeof (HARDDRIVE_DEVICE_PATH));
CopyMem (
TestedDeviceNode,
(VOID *)((UINTN)*DevicePath + ValidPrefixSize),
DevicePathSize - ValidPrefixSize
);
//
// 6. Check this is the right partition.
//
NewDeviceNode = FixedDevicePath;
Status = gBS->LocateDevicePath (
&gEfiDevicePathProtocolGuid,
&NewDeviceNode,
&Device
);
if (EFI_ERROR (Status)) {
FreePool (FixedDevicePath);
continue;
}
//
// If the discovered device path is past the HD path, then it is ours.
// Otherwise we picked up wrong partition and need to retry.
//
if ((UINTN)NewDeviceNode > (UINTN)TestedDeviceNode) {
RestoreContext->OldPath = *DevicePath;
*DevicePath = FixedDevicePath;
*DevicePathNode = TestedDeviceNode;
FreePool (HandleBuffer);
return 1;
}
FreePool (FixedDevicePath);
}
FreePool (HandleBuffer);
//
// We found nothing.
//
return 0;
}
INTN
OcFixAppleBootDevicePathNode (
IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath,
IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePathNode,
OUT APPLE_BOOT_DP_PATCH_CONTEXT *RestoreContext OPTIONAL,
IN EFI_HANDLE ValidDevice OPTIONAL
)
{
INTN Result;
EFI_DEVICE_PATH_PROTOCOL *OldPath;
EFI_DEV_PATH_PTR Node;
UINT8 NodeType;
UINT8 NodeSubType;
UINTN NodeSize;
ASSERT (DevicePathNode != NULL);
//
// ATTENTION: Changes to this function must be carefully sync'd with
// OcFixAppleBootDevicePathNodeRestore().
//
if (RestoreContext != NULL) {
RestoreContext->OldPath = NULL;
}
Node.DevPath = *DevicePathNode;
NodeType = DevicePathType (Node.DevPath);
NodeSubType = DevicePathSubType (Node.DevPath);
if (NodeType == MESSAGING_DEVICE_PATH) {
switch (NodeSubType) {
case MSG_SATA_DP:
if (Node.Sata->PortMultiplierPortNumber != 0xFFFF) {
if (RestoreContext != NULL) {
RestoreContext->Types.Sata.PortMultiplierPortNumber = Node.Sata->PortMultiplierPortNumber;
}
//
// Must be set to 0xFFFF if the device is directly connected to the
// HBA. This rule has been established by UEFI 2.5 via an Erratum
// and has not been followed by Apple thus far.
// Reference: AppleACPIPlatform.kext,
// appendSATADevicePathNodeForIOMedia
//
Node.Sata->PortMultiplierPortNumber = 0xFFFF;
return 1;
}
//
// Some vendors use proprietary node types over standardised ones. Find
// a new, valid DevicePath that matches in prefix and suffix to the
// given one but ignore the data of the node that matches the malformed
// one.
// REF: https://github.com/acidanthera/bugtracker/issues/991#issue-643248184
//
OldPath = *DevicePath;
Result = InternalExpandNewPath (
DevicePath,
DevicePathNode
);
if (Result > 0) {
//
// On success, handle restoring and resources.
//
if (RestoreContext != NULL) {
RestoreContext->OldPath = OldPath;
} else {
FreePool (OldPath);
}
}
return Result;
case MSG_SASEX_DP:
//
// Apple uses SubType 0x16 (SasEx) for NVMe, while the UEFI
// Specification defines it as SubType 0x17. The structures are
// identical.
// Reference: AppleACPIPlatform.kext,
// appendNVMeDevicePathNodeForIOMedia
//
if (RestoreContext != NULL) {
RestoreContext->Types.SasExNvme.SubType = Node.DevPath->SubType;
}
STATIC_ASSERT (
sizeof (SASEX_DEVICE_PATH) != sizeof (NVME_NAMESPACE_DEVICE_PATH),
"SasEx and NVMe DPs must differ in size for fixing to be accurate."
);
NodeSize = DevicePathNodeLength (Node.DevPath);
if (NodeSize == sizeof (NVME_NAMESPACE_DEVICE_PATH)) {
Node.SasEx->Header.SubType = MSG_NVME_NAMESPACE_DP;
return 1;
}
return -1;
case MSG_NVME_NAMESPACE_DP:
//
// Workaround for MacPro5,1 using custom NVMe type.
//
if (RestoreContext != NULL) {
RestoreContext->Types.SasExNvme.SubType = Node.DevPath->SubType;
}
Node.NvmeNamespace->Header.SubType = MSG_APPLE_NVME_NAMESPACE_DP;
return 1;
default:
break;
}
} else if (NodeType == ACPI_DEVICE_PATH) {
switch (NodeSubType) {
case ACPI_DP:
if (RestoreContext != NULL) {
RestoreContext->Types.Acpi.HID = Node.Acpi->HID;
RestoreContext->Types.Acpi.UID = Node.Acpi->UID;
}
if (EISA_ID_TO_NUM (Node.Acpi->HID) == 0x0A03) {
//
// In some types of firmware, UIDs for PciRoot do not match between ACPI tables and UEFI
// UEFI Device Paths. The former contain 0x00, 0x40, 0x80, 0xC0 values, while
// the latter have ascending numbers.
// Reference: https://github.com/acidanthera/bugtracker/issues/664.
// On other boards it is be even more erratic, refer to:
// https://github.com/acidanthera/bugtracker/issues/664#issuecomment-647526506
// On older boards there also is a PCI0/_UID1 issue, refer to:
// https://github.com/acidanthera/bugtracker/issues/664#issuecomment-663873846
//
switch (Node.Acpi->UID) {
case 0x1:
Node.Acpi->UID = 0;
return 1;
case 0x10:
case 0x40:
Node.Acpi->UID = 1;
return 1;
case 0x20:
case 0x80:
Node.Acpi->UID = 2;
return 1;
case 0x28:
case 0xC0:
Node.Acpi->UID = 3;
return 1;
case 0x30:
Node.Acpi->UID = 4;
return 1;
case 0x38:
Node.Acpi->UID = 5;
return 1;
default:
break;
}
//
// Apple uses PciRoot (EISA 0x0A03) nodes while some types of firmware might use
// PcieRoot (EISA 0x0A08).
//
Node.Acpi->HID = BitFieldWrite32 (
Node.Acpi->HID,
16,
31,
0x0A08
);
return 1;
}
return -1;
case ACPI_EXTENDED_DP:
if (RestoreContext != NULL) {
RestoreContext->Types.ExtendedAcpi.HID = Node.ExtendedAcpi->HID;
RestoreContext->Types.ExtendedAcpi.CID = Node.ExtendedAcpi->CID;
}
//
// Apple uses PciRoot (EISA 0x0A03) nodes while some types of firmware might use
// PcieRoot (EISA 0x0A08).
//
if (EISA_ID_TO_NUM (Node.ExtendedAcpi->HID) == 0x0A03) {
Node.ExtendedAcpi->HID = BitFieldWrite32 (
Node.ExtendedAcpi->HID,
16,
31,
0x0A08
);
return 1;
}
if ( (EISA_ID_TO_NUM (Node.ExtendedAcpi->CID) == 0x0A03)
&& (EISA_ID_TO_NUM (Node.ExtendedAcpi->HID) != 0x0A08))
{
Node.ExtendedAcpi->CID = BitFieldWrite32 (
Node.ExtendedAcpi->CID,
16,
31,
0x0A08
);
return 1;
}
return -1;
default:
break;
}
} else if ((NodeType == MEDIA_DEVICE_PATH) && (NodeSubType == MEDIA_VENDOR_DP)) {
if (ValidDevice == NULL) {
return 0;
}
return InternalFixAppleBootDevicePathVirtioNode (
DevicePath,
DevicePathNode,
RestoreContext
);
}
return 0;
}
INTN
OcFixAppleBootDevicePath (
IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath,
OUT EFI_DEVICE_PATH_PROTOCOL **RemainingDevicePath
)
{
INTN Result;
INTN NodePatched;
UINTN DevicePathSize;
APPLE_BOOT_DP_PATCH_CONTEXT FirstNodeRestoreContext;
APPLE_BOOT_DP_PATCH_CONTEXT *RestoreContextPtr;
EFI_HANDLE Device;
ASSERT (DevicePath != NULL);
ASSERT (*DevicePath != NULL);
ASSERT (IsDevicePathValid (*DevicePath, 0));
//
// Restoring is only required for the first Device Path node. Please refer
// to the loop for an explanation.
//
RestoreContextPtr = &FirstNodeRestoreContext;
NodePatched = 0;
do {
//
// Retrieve the first Device Path node that cannot be located.
//
*RemainingDevicePath = *DevicePath;
gBS->LocateDevicePath (
&gEfiDevicePathProtocolGuid,
RemainingDevicePath,
&Device
);
//
// Patch the potentially invalid node.
//
Result = OcFixAppleBootDevicePathNode (
DevicePath,
RemainingDevicePath,
RestoreContextPtr,
Device
);
//
// Save a restore context only for the first processing of the first node.
// The reason for this is when the first node cannot be located with any
// patch applied, the Device Path may be of a prefix short-form and may
// possibly be expanded successfully unmodified.
//
RestoreContextPtr = NULL;
//
// Continue as long as nodes are being patched. Remember patch status.
//
NodePatched |= Result > 0;
} while (Result > 0);
if (Result < 0) {
if (NodePatched != 0) {
if (*RemainingDevicePath == *DevicePath) {
//
// If the path could not be fixed, restore the first node if it's the
// one failing to be located. Please refer to the loop for an
// explanation.
// If advancing by at least one node happened, the Device Path cannot
// be of a prefix short-form and hence restoring is not beneficial (and
// most especially would require tracking every node individually).
//
OcFixAppleBootDevicePathNodeRestore (
DevicePath,
RemainingDevicePath,
&FirstNodeRestoreContext
);
} else {
//
// If patching was successful, explicitly free the used resources.
//
OcFixAppleBootDevicePathNodeRestoreFree (
*DevicePath,
&FirstNodeRestoreContext
);
}
}
return -1;
}
//
// Double-check that *RemainingDevicePath still points into *DevicePath.
//
DEBUG_CODE_BEGIN ();
DevicePathSize = GetDevicePathSize (*DevicePath);
ASSERT ((UINTN)*RemainingDevicePath >= (UINTN)*DevicePath);
ASSERT ((UINTN)*RemainingDevicePath < ((UINTN)*DevicePath) + DevicePathSize);
DEBUG_CODE_END ();
return NodePatched;
}
STATIC
BOOLEAN
InternalFileDevicePathsEqualClipBottom (
IN CONST FILEPATH_DEVICE_PATH *FilePath,
IN OUT UINTN *FilePathLength,
IN OUT UINTN *ClipIndex
)
{
UINTN Index;
ASSERT (FilePathLength != NULL);
ASSERT (*FilePathLength != 0);
ASSERT (FilePath != NULL);
ASSERT (ClipIndex != NULL);
Index = *ClipIndex;
if (FilePath->PathName[Index] == L'\\') {
*ClipIndex = Index + 1;
--(*FilePathLength);
return TRUE;
}
return FALSE;
}
STATIC
UINTN
InternalFileDevicePathsEqualClipNode (
IN FILEPATH_DEVICE_PATH **FilePath,
OUT UINTN *ClipIndex
)
{
EFI_DEV_PATH_PTR DevPath;
UINTN Index;
UINTN Length;
EFI_DEVICE_PATH_PROTOCOL *NextNode;
ASSERT (FilePath != NULL);
ASSERT (ClipIndex != NULL);
//
// It is unlikely to be encountered, but empty nodes are not forbidden.
//
for (
Length = 0, NextNode = &(*FilePath)->Header;
Length == 0;
NextNode = NextDevicePathNode (DevPath.DevPath)
)
{
DevPath.DevPath = NextNode;
if ( (DevicePathType (DevPath.DevPath) != MEDIA_DEVICE_PATH)
|| (DevicePathSubType (DevPath.DevPath) != MEDIA_FILEPATH_DP))
{
Length = 0;
break;
}
Length = OcFileDevicePathNameLen (DevPath.FilePath);
if (Length > 0) {
Index = 0;
InternalFileDevicePathsEqualClipBottom (DevPath.FilePath, &Length, &Index);
if ( (Length > 0)
&& (DevPath.FilePath->PathName[Index + Length - 1] == L'\\'))
{
--Length;
}
*ClipIndex = Index;
}
}
*FilePath = DevPath.FilePath;
return Length;
}
STATIC
UINTN
InternalFileDevicePathsEqualClipNextNode (
IN FILEPATH_DEVICE_PATH **FilePath,
OUT UINTN *ClipIndex
)
{
ASSERT (FilePath != NULL);
ASSERT (ClipIndex != NULL);
*FilePath = (FILEPATH_DEVICE_PATH *)NextDevicePathNode (*FilePath);
return InternalFileDevicePathsEqualClipNode (FilePath, ClipIndex);
}
BOOLEAN
InternalFileDevicePathsEqualWorker (
IN FILEPATH_DEVICE_PATH **FilePath1,
IN FILEPATH_DEVICE_PATH **FilePath2
)
{
UINTN Clip1Index;
UINTN Clip2Index;
UINTN Len1;
UINTN Len2;
UINTN CurrentLen;
UINTN Index;
CHAR16 Char1;
CHAR16 Char2;
BOOLEAN Result;
ASSERT (FilePath1 != NULL);
ASSERT (*FilePath1 != NULL);
ASSERT (FilePath2 != NULL);
ASSERT (*FilePath2 != NULL);
ASSERT (IsDevicePathValid (&(*FilePath1)->Header, 0));
ASSERT (IsDevicePathValid (&(*FilePath2)->Header, 0));
Len1 = InternalFileDevicePathsEqualClipNode (FilePath1, &Clip1Index);
Len2 = InternalFileDevicePathsEqualClipNode (FilePath2, &Clip2Index);
do {
if ((Len1 == 0) && (Len2 == 0)) {
return TRUE;
}
CurrentLen = MIN (Len1, Len2);
if (CurrentLen == 0) {
return FALSE;
}
//
// FIXME: Discuss case sensitivity. For UEFI FAT, case insensitivity is
// guaranteed.
//
for (Index = 0; Index < CurrentLen; ++Index) {
Char1 = CharToUpper ((*FilePath1)->PathName[Clip1Index + Index]);
Char2 = CharToUpper ((*FilePath2)->PathName[Clip2Index + Index]);
if (Char1 != Char2) {
return FALSE;
}
}
if (Len1 == Len2) {
Len1 = InternalFileDevicePathsEqualClipNextNode (FilePath1, &Clip1Index);
Len2 = InternalFileDevicePathsEqualClipNextNode (FilePath2, &Clip2Index);
} else if (Len1 < Len2) {
Len1 = InternalFileDevicePathsEqualClipNextNode (FilePath1, &Clip1Index);
if (Len1 == 0) {
return FALSE;
}
Len2 -= CurrentLen;
Clip2Index += CurrentLen;
//
// Switching to the next node for the other Device Path implies a path
// separator. Verify we hit such in the currently walked path too.
//
Result = InternalFileDevicePathsEqualClipBottom (*FilePath2, &Len2, &Clip2Index);
if (!Result) {
return FALSE;
}
} else {
Len2 = InternalFileDevicePathsEqualClipNextNode (FilePath2, &Clip2Index);
if (Len2 == 0) {
return FALSE;
}
Len1 -= CurrentLen;
Clip1Index += CurrentLen;
//
// Switching to the next node for the other Device Path implies a path
// separator. Verify we hit such in the currently walked path too.
//
Result = InternalFileDevicePathsEqualClipBottom (*FilePath1, &Len1, &Clip1Index);
if (!Result) {
return FALSE;
}
}
} while (TRUE);
}
/**
Check whether File Device Paths are equal.
@param[in] FilePath1 The first device path protocol to compare.
@param[in] FilePath2 The second device path protocol to compare.
@retval TRUE The device paths matched
@retval FALSE The device paths were different
**/
BOOLEAN
FileDevicePathsEqual (
IN FILEPATH_DEVICE_PATH *FilePath1,
IN FILEPATH_DEVICE_PATH *FilePath2
)
{
ASSERT (FilePath1 != NULL);
ASSERT (FilePath2 != NULL);
return InternalFileDevicePathsEqualWorker (&FilePath1, &FilePath2);
}
STATIC
BOOLEAN
InternalDevicePathCmpWorker (
IN EFI_DEVICE_PATH_PROTOCOL *ParentPath,
IN EFI_DEVICE_PATH_PROTOCOL *ChildPath,
IN BOOLEAN CheckChild
)
{
BOOLEAN Result;
INTN CmpResult;
EFI_DEV_PATH_PTR ChildPathPtr;
EFI_DEV_PATH_PTR ParentPathPtr;
UINT8 NodeType;
UINT8 NodeSubType;
UINTN NodeSize;
ASSERT (ParentPath != NULL);
ASSERT (IsDevicePathValid (ParentPath, 0));
ASSERT (ChildPath != NULL);
ASSERT (IsDevicePathValid (ChildPath, 0));
ParentPathPtr.DevPath = ParentPath;
ChildPathPtr.DevPath = ChildPath;
while (TRUE) {
NodeType = DevicePathType (ChildPathPtr.DevPath);
NodeSubType = DevicePathSubType (ChildPathPtr.DevPath);
if (NodeType == END_DEVICE_PATH_TYPE) {
//
// We only support single-instance Device Paths.
//
ASSERT (NodeSubType == END_ENTIRE_DEVICE_PATH_SUBTYPE);
return ( CheckChild
|| (DevicePathType (ParentPathPtr.DevPath) == END_DEVICE_PATH_TYPE));
}
if ( (DevicePathType (ParentPathPtr.DevPath) != NodeType)
|| (DevicePathSubType (ParentPathPtr.DevPath) != NodeSubType))
{
return FALSE;
}
if ( (NodeType == MEDIA_DEVICE_PATH)
&& (NodeSubType == MEDIA_FILEPATH_DP))
{
//
// File Paths need special consideration for prepended and appended
// terminators, as well as multiple nodes.
//
Result = InternalFileDevicePathsEqualWorker (
&ParentPathPtr.FilePath,
&ChildPathPtr.FilePath
);
if (!Result) {
return FALSE;
}
//
// InternalFileDevicePathsEqualWorker advances the nodes.
//
} else {
NodeSize = DevicePathNodeLength (ChildPathPtr.DevPath);
if (DevicePathNodeLength (ParentPathPtr.DevPath) != NodeSize) {
return FALSE;
}
STATIC_ASSERT (
(sizeof (*ChildPathPtr.DevPath) == 4),
"The Device Path comparison logic depends on the entire header being checked"
);
CmpResult = CompareMem (
(ChildPathPtr.DevPath + 1),
(ParentPathPtr.DevPath + 1),
(NodeSize - sizeof (*ChildPathPtr.DevPath))
);
if (CmpResult != 0) {
return FALSE;
}
ParentPathPtr.DevPath = NextDevicePathNode (ParentPathPtr.DevPath);
ChildPathPtr.DevPath = NextDevicePathNode (ChildPathPtr.DevPath);
}
}
}
BOOLEAN
EFIAPI
IsDevicePathEqual (
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath1,
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath2
)
{
return InternalDevicePathCmpWorker (DevicePath1, DevicePath2, FALSE);
}
BOOLEAN
EFIAPI
IsDevicePathChild (
IN EFI_DEVICE_PATH_PROTOCOL *ParentPath,
IN EFI_DEVICE_PATH_PROTOCOL *ChildPath
)
{
return InternalDevicePathCmpWorker (ParentPath, ChildPath, TRUE);
}
UINTN
OcFileDevicePathNameSize (
IN CONST FILEPATH_DEVICE_PATH *FilePath
)
{
ASSERT (FilePath != NULL);
ASSERT (IsDevicePathValid (&FilePath->Header, 0));
return (OcFileDevicePathNameLen (FilePath) + 1) * sizeof (*FilePath->PathName);
}
UINTN
OcFileDevicePathNameLen (
IN CONST FILEPATH_DEVICE_PATH *FilePath
)
{
UINTN Size;
UINTN Len;
ASSERT (FilePath != NULL);
ASSERT (IsDevicePathValid (&FilePath->Header, 0));
Size = DevicePathNodeLength (FilePath) - SIZE_OF_FILEPATH_DEVICE_PATH;
//
// Account for more than one termination character.
//
Len = (Size / sizeof (*FilePath->PathName)) - 1;
while (Len > 0 && FilePath->PathName[Len - 1] == L'\0') {
--Len;
}
return Len;
}
/**
Retrieve the length of the full file path described by DevicePath.
@param[in] DevicePath The Device Path to inspect.
@returns The length of the full file path.
@retval 0 DevicePath does not start with a File Path node or contains
non-terminating nodes that are not File Path nodes.
**/
UINTN
OcFileDevicePathFullNameLen (
IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath
)
{
UINTN PathLength;
CONST FILEPATH_DEVICE_PATH *FilePath;
ASSERT (DevicePath != NULL);
ASSERT (IsDevicePathValid (DevicePath, 0));
PathLength = 0;
do {
//
// On the first iteration, this ensures the path is not immediately
// terminated.
//
if ( (DevicePath->Type != MEDIA_DEVICE_PATH)
|| (DevicePath->SubType != MEDIA_FILEPATH_DP))
{
return 0;
}
FilePath = (FILEPATH_DEVICE_PATH *)DevicePath;
//
// Each node requires separator or CHAR_NULL
//
PathLength += OcFileDevicePathNameLen (FilePath) + 1;
DevicePath = NextDevicePathNode (DevicePath);
} while (!IsDevicePathEnd (DevicePath));
return PathLength - 1;
}
/**
Retrieve the size of the full file path described by DevicePath.
@param[in] DevicePath The Device Path to inspect.
@returns The size of the full file path.
@retval 0 DevicePath does not start with a File Path node or contains
non-terminating nodes that are not File Path nodes.
**/
UINTN
OcFileDevicePathFullNameSize (
IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath
)
{
UINTN Len;
Len = OcFileDevicePathFullNameLen (DevicePath);
if (Len == 0) {
return 0;
}
return (Len + 1) * sizeof (CHAR16);
}
/**
Retrieve the full file path described by FilePath.
The caller is expected to call OcFileDevicePathFullNameSize() or ensure its
guarantees are met.
@param[out] PathName On output, the full file path of FilePath.
@param[in] FilePath The File Device Path to inspect.
@param[in] PathNameSize The size, in bytes, of PathName. Must equal the
actual full file path size.
**/
VOID
OcFileDevicePathFullName (
OUT CHAR16 *PathName,
IN CONST FILEPATH_DEVICE_PATH *FilePath,
IN UINTN PathNameSize
)
{
UINTN PathLen;
ASSERT (PathName != NULL);
ASSERT (FilePath != NULL);
ASSERT (IsDevicePathValid (&FilePath->Header, 0));
ASSERT (PathNameSize == OcFileDevicePathFullNameSize (&FilePath->Header));
//
// Note: The UEFI spec declares that a path separator may optionally be
// present at the beginning or end of any node's PathName. This is not
// currently supported here. Any fix would need to be applied here and
// in OcFileDevicePathNameLen.
//
do {
PathLen = OcFileDevicePathNameLen (FilePath);
CopyMem (
PathName,
FilePath->PathName,
PathLen * sizeof (*FilePath->PathName)
);
PathName += PathLen;
*PathName++ = L'\\';
FilePath = (CONST FILEPATH_DEVICE_PATH *)NextDevicePathNode (FilePath);
} while (!IsDevicePathEnd (FilePath));
*(PathName - 1) = CHAR_NULL;
}
CHAR16 *
OcCopyDevicePathFullName (
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath,
OUT EFI_DEVICE_PATH_PROTOCOL **FileDevicePath OPTIONAL
)
{
CHAR16 *Path;
EFI_DEVICE_PATH_PROTOCOL *CurrNode;
UINTN PathSize;
Path = NULL;
if (FileDevicePath != NULL) {
*FileDevicePath = NULL;
}
for (CurrNode = DevicePath; !IsDevicePathEnd (CurrNode); CurrNode = NextDevicePathNode (CurrNode)) {
if ( (DevicePathType (CurrNode) == MEDIA_DEVICE_PATH)
&& (DevicePathSubType (CurrNode) == MEDIA_FILEPATH_DP))
{
if (FileDevicePath != NULL) {
*FileDevicePath = CurrNode;
}
//
// Perform copying of all the underlying nodes due to potential unaligned access.
//
PathSize = OcFileDevicePathFullNameSize (CurrNode);
if (PathSize == 0) {
return NULL;
}
Path = AllocatePool (PathSize);
if (Path == NULL) {
return NULL;
}
OcFileDevicePathFullName (Path, (FILEPATH_DEVICE_PATH *)CurrNode, PathSize);
break;
}
}
return Path;
}
EFI_DEVICE_PATH_PROTOCOL *
OcAppendDevicePathInstanceDedupe (
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath OPTIONAL,
IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePathInstance
)
{
INTN CmpResult;
EFI_DEVICE_PATH_PROTOCOL *DevPathWalker;
EFI_DEVICE_PATH_PROTOCOL *CurrentInstance;
UINTN AppendInstanceSize;
UINTN CurrentInstanceSize;
ASSERT (DevicePathInstance != NULL);
if (DevicePath != NULL) {
AppendInstanceSize = GetDevicePathSize (DevicePathInstance);
DevPathWalker = DevicePath;
while (TRUE) {
CurrentInstance = GetNextDevicePathInstance (
&DevPathWalker,
&CurrentInstanceSize
);
if (CurrentInstance == NULL) {
break;
}
if (CurrentInstanceSize != AppendInstanceSize) {
FreePool (CurrentInstance);
continue;
}
CmpResult = CompareMem (
CurrentInstance,
DevicePathInstance,
CurrentInstanceSize
);
FreePool (CurrentInstance);
if (CmpResult == 0) {
return DuplicateDevicePath (DevicePath);
}
}
}
return AppendDevicePathInstance (DevicePath, DevicePathInstance);
}
UINTN
OcGetNumDevicePathInstances (
IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath
)
{
UINTN NumInstances;
NumInstances = 1;
while (!IsDevicePathEnd (DevicePath)) {
if (IsDevicePathEndInstance (DevicePath)) {
++NumInstances;
}
DevicePath = NextDevicePathNode (DevicePath);
}
return NumInstances;
}
BOOLEAN
OcDevicePathHasFilePathSuffix (
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath,
IN CONST CHAR16 *Suffix,
IN UINTN SuffixLen
)
{
FILEPATH_DEVICE_PATH *FilePath;
UINTN PathNameLen;
UINTN PathNameSize;
CHAR16 *PathName;
INTN Result;
//
// OcStrinCmp will be needed in case of a mismatch.
//
ASSERT (SuffixLen == StrLen (Suffix));
FilePath = (FILEPATH_DEVICE_PATH *)FindDevicePathNodeWithType (
DevicePath,
MEDIA_DEVICE_PATH,
MEDIA_FILEPATH_DP
);
if (FilePath == NULL) {
return FALSE;
}
PathNameLen = OcFileDevicePathFullNameLen (&FilePath->Header);
if (PathNameLen < SuffixLen) {
return FALSE;
}
PathNameSize = (PathNameLen + 1) * sizeof (CHAR16);
PathName = AllocatePool (PathNameSize);
if (PathName == NULL) {
return FALSE;
}
OcFileDevicePathFullName (PathName, FilePath, PathNameSize);
Result = OcStriCmp (
&PathName[PathNameLen - SuffixLen],
Suffix
);
FreePool (PathName);
return Result == 0;
}