/** @file PCI Rom supporting funtions implementation for PCI Bus module. Copyright (c) 2006 - 2019, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "PciBus.h" /** Load the EFI Image from Option ROM @param PciIoDevice PCI IO device instance. @param FilePath The file path of the EFI Image @param BufferSize On input the size of Buffer in bytes. On output with a return code of EFI_SUCCESS, the amount of data transferred to Buffer. On output with a return code of EFI_BUFFER_TOO_SMALL, the size of Buffer required to retrieve the requested file. @param Buffer The memory buffer to transfer the file to. If Buffer is NULL, then no the size of the requested file is returned in BufferSize. @retval EFI_SUCCESS The file was loaded. @retval EFI_INVALID_PARAMETER FilePath is not a valid device path, or BufferSize is NULL. @retval EFI_NOT_FOUND Not found PCI Option Rom on PCI device. @retval EFI_DEVICE_ERROR Failed to decompress PCI Option Rom image. @retval EFI_BUFFER_TOO_SMALL The BufferSize is too small to read the current directory entry. BufferSize has been updated with the size needed to complete the request. **/ EFI_STATUS LocalLoadFile2 ( IN PCI_IO_DEVICE *PciIoDevice, IN EFI_DEVICE_PATH_PROTOCOL *FilePath, IN OUT UINTN *BufferSize, IN VOID *Buffer OPTIONAL ) { EFI_STATUS Status; MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH *EfiOpRomImageNode; EFI_PCI_EXPANSION_ROM_HEADER *EfiRomHeader; PCI_DATA_STRUCTURE *Pcir; UINT32 ImageSize; UINT8 *ImageBuffer; UINT32 ImageLength; UINT32 DestinationSize; UINT32 ScratchSize; VOID *Scratch; EFI_DECOMPRESS_PROTOCOL *Decompress; UINT32 InitializationSize; EfiOpRomImageNode = (MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH *)FilePath; if ((EfiOpRomImageNode == NULL) || (DevicePathType (FilePath) != MEDIA_DEVICE_PATH) || (DevicePathSubType (FilePath) != MEDIA_RELATIVE_OFFSET_RANGE_DP) || (DevicePathNodeLength (FilePath) != sizeof (MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH)) || (!IsDevicePathEnd (NextDevicePathNode (FilePath))) || (EfiOpRomImageNode->StartingOffset > EfiOpRomImageNode->EndingOffset) || (EfiOpRomImageNode->EndingOffset >= PciIoDevice->RomSize) || (BufferSize == NULL) ) { return EFI_INVALID_PARAMETER; } EfiRomHeader = (EFI_PCI_EXPANSION_ROM_HEADER *)( (UINT8 *)PciIoDevice->PciIo.RomImage + EfiOpRomImageNode->StartingOffset ); if (EfiRomHeader->Signature != PCI_EXPANSION_ROM_HEADER_SIGNATURE) { return EFI_NOT_FOUND; } Pcir = (PCI_DATA_STRUCTURE *)((UINT8 *)EfiRomHeader + EfiRomHeader->PcirOffset); ASSERT (Pcir->Signature == PCI_DATA_STRUCTURE_SIGNATURE); if ((Pcir->CodeType == PCI_CODE_TYPE_EFI_IMAGE) && (EfiRomHeader->EfiSignature == EFI_PCI_EXPANSION_ROM_HEADER_EFISIGNATURE) && ((EfiRomHeader->EfiSubsystem == EFI_IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER) || (EfiRomHeader->EfiSubsystem == EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER)) && (EfiRomHeader->CompressionType <= EFI_PCI_EXPANSION_ROM_HEADER_COMPRESSED) ) { ImageSize = Pcir->ImageLength * 512; InitializationSize = (UINT32)EfiRomHeader->InitializationSize * 512; if ((InitializationSize > ImageSize) || (EfiRomHeader->EfiImageHeaderOffset >= InitializationSize)) { return EFI_NOT_FOUND; } ImageBuffer = (UINT8 *)EfiRomHeader + EfiRomHeader->EfiImageHeaderOffset; ImageLength = InitializationSize - EfiRomHeader->EfiImageHeaderOffset; if (EfiRomHeader->CompressionType != EFI_PCI_EXPANSION_ROM_HEADER_COMPRESSED) { // // Uncompressed: Copy the EFI Image directly to user's buffer // if ((Buffer == NULL) || (*BufferSize < ImageLength)) { *BufferSize = ImageLength; return EFI_BUFFER_TOO_SMALL; } *BufferSize = ImageLength; CopyMem (Buffer, ImageBuffer, ImageLength); return EFI_SUCCESS; } else { // // Compressed: Uncompress before copying // Status = gBS->LocateProtocol (&gEfiDecompressProtocolGuid, NULL, (VOID **)&Decompress); if (EFI_ERROR (Status)) { return EFI_DEVICE_ERROR; } Status = Decompress->GetInfo ( Decompress, ImageBuffer, ImageLength, &DestinationSize, &ScratchSize ); if (EFI_ERROR (Status)) { return EFI_DEVICE_ERROR; } if ((Buffer == NULL) || (*BufferSize < DestinationSize)) { *BufferSize = DestinationSize; return EFI_BUFFER_TOO_SMALL; } *BufferSize = DestinationSize; Scratch = AllocatePool (ScratchSize); if (Scratch == NULL) { return EFI_DEVICE_ERROR; } Status = Decompress->Decompress ( Decompress, ImageBuffer, ImageLength, Buffer, DestinationSize, Scratch, ScratchSize ); FreePool (Scratch); if (EFI_ERROR (Status)) { return EFI_DEVICE_ERROR; } return EFI_SUCCESS; } } return EFI_NOT_FOUND; } /** Initialize a PCI LoadFile2 instance. @param PciIoDevice PCI IO Device. **/ VOID InitializePciLoadFile2 ( IN PCI_IO_DEVICE *PciIoDevice ) { PciIoDevice->LoadFile2.LoadFile = LoadFile2; } /** Causes the driver to load a specified file. @param This Indicates a pointer to the calling context. @param FilePath The device specific path of the file to load. @param BootPolicy Should always be FALSE. @param BufferSize On input the size of Buffer in bytes. On output with a return code of EFI_SUCCESS, the amount of data transferred to Buffer. On output with a return code of EFI_BUFFER_TOO_SMALL, the size of Buffer required to retrieve the requested file. @param Buffer The memory buffer to transfer the file to. If Buffer is NULL, then no the size of the requested file is returned in BufferSize. @retval EFI_SUCCESS The file was loaded. @retval EFI_UNSUPPORTED BootPolicy is TRUE. @retval EFI_INVALID_PARAMETER FilePath is not a valid device path, or BufferSize is NULL. @retval EFI_NOT_FOUND Not found PCI Option Rom on PCI device. @retval EFI_DEVICE_ERROR Failed to decompress PCI Option Rom image. @retval EFI_BUFFER_TOO_SMALL The BufferSize is too small to read the current directory entry. BufferSize has been updated with the size needed to complete the request. **/ EFI_STATUS EFIAPI LoadFile2 ( IN EFI_LOAD_FILE2_PROTOCOL *This, IN EFI_DEVICE_PATH_PROTOCOL *FilePath, IN BOOLEAN BootPolicy, IN OUT UINTN *BufferSize, IN VOID *Buffer OPTIONAL ) { PCI_IO_DEVICE *PciIoDevice; if (BootPolicy) { return EFI_UNSUPPORTED; } PciIoDevice = PCI_IO_DEVICE_FROM_LOAD_FILE2_THIS (This); return LocalLoadFile2 ( PciIoDevice, FilePath, BufferSize, Buffer ); } /** Get Pci device's oprom information. @param PciIoDevice Input Pci device instance. Output Pci device instance with updated OptionRom size. @retval EFI_NOT_FOUND Pci device has not Option Rom. @retval EFI_SUCCESS Pci device has Option Rom. **/ EFI_STATUS GetOpRomInfo ( IN OUT PCI_IO_DEVICE *PciIoDevice ) { UINT8 RomBarIndex; UINT32 AllOnes; UINT64 Address; EFI_STATUS Status; UINT8 Bus; UINT8 Device; UINT8 Function; EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *PciRootBridgeIo; Bus = PciIoDevice->BusNumber; Device = PciIoDevice->DeviceNumber; Function = PciIoDevice->FunctionNumber; PciRootBridgeIo = PciIoDevice->PciRootBridgeIo; // // Offset is 0x30 if is not ppb // // // 0x30 // RomBarIndex = PCI_EXPANSION_ROM_BASE; if (IS_PCI_BRIDGE (&PciIoDevice->Pci)) { // // If is ppb, 0x38 // RomBarIndex = PCI_BRIDGE_ROMBAR; } // // The bit0 is 0 to prevent the enabling of the Rom address decoder // AllOnes = 0xfffffffe; Address = EFI_PCI_ADDRESS (Bus, Device, Function, RomBarIndex); Status = PciRootBridgeIo->Pci.Write ( PciRootBridgeIo, EfiPciWidthUint32, Address, 1, &AllOnes ); if (EFI_ERROR (Status)) { return EFI_NOT_FOUND; } // // Read back // Status = PciRootBridgeIo->Pci.Read ( PciRootBridgeIo, EfiPciWidthUint32, Address, 1, &AllOnes ); if (EFI_ERROR (Status)) { return EFI_NOT_FOUND; } // // Bits [1, 10] are reserved // AllOnes &= 0xFFFFF800; if ((AllOnes == 0) || (AllOnes == 0xFFFFF800)) { return EFI_NOT_FOUND; } PciIoDevice->RomSize = (~AllOnes) + 1; return EFI_SUCCESS; } /** Check if the RomImage contains EFI Images. @param RomImage The ROM address of Image for check. @param RomSize Size of ROM for check. @retval TRUE ROM contain EFI Image. @retval FALSE ROM not contain EFI Image. **/ BOOLEAN ContainEfiImage ( IN VOID *RomImage, IN UINT64 RomSize ) { PCI_EXPANSION_ROM_HEADER *RomHeader; PCI_DATA_STRUCTURE *RomPcir; UINT8 Indicator; Indicator = 0; RomHeader = RomImage; if (RomHeader == NULL) { return FALSE; } do { if (RomHeader->Signature != PCI_EXPANSION_ROM_HEADER_SIGNATURE) { RomHeader = (PCI_EXPANSION_ROM_HEADER *)((UINT8 *)RomHeader + 512); continue; } // // The PCI Data Structure must be DWORD aligned. // if ((RomHeader->PcirOffset == 0) || ((RomHeader->PcirOffset & 3) != 0) || ((UINT8 *)RomHeader + RomHeader->PcirOffset + sizeof (PCI_DATA_STRUCTURE) > (UINT8 *)RomImage + RomSize)) { break; } RomPcir = (PCI_DATA_STRUCTURE *)((UINT8 *)RomHeader + RomHeader->PcirOffset); if (RomPcir->Signature != PCI_DATA_STRUCTURE_SIGNATURE) { break; } if (RomPcir->CodeType == PCI_CODE_TYPE_EFI_IMAGE) { return TRUE; } Indicator = RomPcir->Indicator; RomHeader = (PCI_EXPANSION_ROM_HEADER *)((UINT8 *)RomHeader + RomPcir->ImageLength * 512); } while (((UINT8 *)RomHeader < (UINT8 *)RomImage + RomSize) && ((Indicator & 0x80) == 0x00)); return FALSE; } /** Load Option Rom image for specified PCI device. @param PciDevice Pci device instance. @param RomBase Base address of Option Rom. @retval EFI_OUT_OF_RESOURCES No enough memory to hold image. @retval EFI_SUCESS Successfully loaded Option Rom. **/ EFI_STATUS LoadOpRomImage ( IN PCI_IO_DEVICE *PciDevice, IN UINT64 RomBase ) { UINT8 RomBarIndex; UINT8 Indicator; UINT16 OffsetPcir; UINT32 RomBarOffset; UINT32 RomBar; EFI_STATUS RetStatus; BOOLEAN FirstCheck; UINT8 *Image; PCI_EXPANSION_ROM_HEADER *RomHeader; PCI_DATA_STRUCTURE *RomPcir; UINT64 RomSize; UINT64 RomImageSize; UINT32 LegacyImageLength; UINT8 *RomInMemory; UINT8 CodeType; RomSize = PciDevice->RomSize; Indicator = 0; RomImageSize = 0; RomInMemory = NULL; CodeType = 0xFF; // // Get the RomBarIndex // // // 0x30 // RomBarIndex = PCI_EXPANSION_ROM_BASE; if (IS_PCI_BRIDGE (&(PciDevice->Pci))) { // // if is ppb // // // 0x38 // RomBarIndex = PCI_BRIDGE_ROMBAR; } // // Allocate memory for Rom header and PCIR // RomHeader = AllocatePool (sizeof (PCI_EXPANSION_ROM_HEADER)); if (RomHeader == NULL) { return EFI_OUT_OF_RESOURCES; } RomPcir = AllocatePool (sizeof (PCI_DATA_STRUCTURE)); if (RomPcir == NULL) { FreePool (RomHeader); return EFI_OUT_OF_RESOURCES; } RomBar = (UINT32)RomBase; // // Enable RomBar // RomDecode (PciDevice, RomBarIndex, RomBar, TRUE); RomBarOffset = RomBar; RetStatus = EFI_NOT_FOUND; FirstCheck = TRUE; LegacyImageLength = 0; do { PciDevice->PciRootBridgeIo->Mem.Read ( PciDevice->PciRootBridgeIo, EfiPciWidthUint8, RomBarOffset, sizeof (PCI_EXPANSION_ROM_HEADER), (UINT8 *)RomHeader ); if (RomHeader->Signature != PCI_EXPANSION_ROM_HEADER_SIGNATURE) { RomBarOffset = RomBarOffset + 512; if (FirstCheck) { break; } else { RomImageSize = RomImageSize + 512; continue; } } FirstCheck = FALSE; OffsetPcir = RomHeader->PcirOffset; // // If the pointer to the PCI Data Structure is invalid, no further images can be located. // The PCI Data Structure must be DWORD aligned. // if ((OffsetPcir == 0) || ((OffsetPcir & 3) != 0) || (RomImageSize + OffsetPcir + sizeof (PCI_DATA_STRUCTURE) > RomSize)) { break; } PciDevice->PciRootBridgeIo->Mem.Read ( PciDevice->PciRootBridgeIo, EfiPciWidthUint8, RomBarOffset + OffsetPcir, sizeof (PCI_DATA_STRUCTURE), (UINT8 *)RomPcir ); // // If a valid signature is not present in the PCI Data Structure, no further images can be located. // if (RomPcir->Signature != PCI_DATA_STRUCTURE_SIGNATURE) { break; } if (RomImageSize + RomPcir->ImageLength * 512 > RomSize) { break; } if (RomPcir->CodeType == PCI_CODE_TYPE_PCAT_IMAGE) { CodeType = PCI_CODE_TYPE_PCAT_IMAGE; LegacyImageLength = ((UINT32)((EFI_LEGACY_EXPANSION_ROM_HEADER *)RomHeader)->Size512) * 512; } Indicator = RomPcir->Indicator; RomImageSize = RomImageSize + RomPcir->ImageLength * 512; RomBarOffset = RomBarOffset + RomPcir->ImageLength * 512; } while (((Indicator & 0x80) == 0x00) && ((RomBarOffset - RomBar) < RomSize) && (RomImageSize > 0)); // // Some Legacy Cards do not report the correct ImageLength so used the maximum // of the legacy length and the PCIR Image Length // if ((RomImageSize > 0) && (CodeType == PCI_CODE_TYPE_PCAT_IMAGE)) { RomImageSize = MAX (RomImageSize, LegacyImageLength); } if (RomImageSize > 0) { RetStatus = EFI_SUCCESS; Image = AllocatePool ((UINT32)RomImageSize); if (Image == NULL) { RomDecode (PciDevice, RomBarIndex, RomBar, FALSE); FreePool (RomHeader); FreePool (RomPcir); return EFI_OUT_OF_RESOURCES; } // // Copy Rom image into memory // PciDevice->PciRootBridgeIo->Mem.Read ( PciDevice->PciRootBridgeIo, EfiPciWidthUint32, RomBar, (UINT32)RomImageSize/sizeof (UINT32), Image ); RomInMemory = Image; } RomDecode (PciDevice, RomBarIndex, RomBar, FALSE); PciDevice->EmbeddedRom = TRUE; PciDevice->PciIo.RomSize = RomImageSize; PciDevice->PciIo.RomImage = RomInMemory; // // For OpROM read from PCI device: // Add the Rom Image to internal database for later PCI light enumeration // PciRomAddImageMapping ( NULL, PciDevice->PciRootBridgeIo->SegmentNumber, PciDevice->BusNumber, PciDevice->DeviceNumber, PciDevice->FunctionNumber, PciDevice->PciIo.RomImage, PciDevice->PciIo.RomSize ); // // Free allocated memory // FreePool (RomHeader); FreePool (RomPcir); return RetStatus; } /** Enable/Disable Option Rom decode. @param PciDevice Pci device instance. @param RomBarIndex The BAR index of the standard PCI Configuration header to use as the base address for resource range. The legal range for this field is 0..5. @param RomBar Base address of Option Rom. @param Enable Flag for enable/disable decode. **/ VOID RomDecode ( IN PCI_IO_DEVICE *PciDevice, IN UINT8 RomBarIndex, IN UINT32 RomBar, IN BOOLEAN Enable ) { UINT32 Value32; EFI_PCI_IO_PROTOCOL *PciIo; PciIo = &PciDevice->PciIo; if (Enable) { // // set the Rom base address: now is hardcode // enable its decoder // Value32 = RomBar | 0x1; PciIo->Pci.Write ( PciIo, (EFI_PCI_IO_PROTOCOL_WIDTH)EfiPciWidthUint32, RomBarIndex, 1, &Value32 ); // // Programe all upstream bridge // ProgramUpstreamBridgeForRom (PciDevice, RomBar, TRUE); // // Setting the memory space bit in the function's command register // PCI_ENABLE_COMMAND_REGISTER (PciDevice, EFI_PCI_COMMAND_MEMORY_SPACE); } else { // // disable command register decode to memory // PCI_DISABLE_COMMAND_REGISTER (PciDevice, EFI_PCI_COMMAND_MEMORY_SPACE); // // Destroy the programmed bar in all the upstream bridge. // ProgramUpstreamBridgeForRom (PciDevice, RomBar, FALSE); // // disable rom decode // Value32 = 0xFFFFFFFE; PciIo->Pci.Write ( PciIo, (EFI_PCI_IO_PROTOCOL_WIDTH)EfiPciWidthUint32, RomBarIndex, 1, &Value32 ); } } /** Load and start the Option Rom image. @param PciDevice Pci device instance. @retval EFI_SUCCESS Successfully loaded and started PCI Option Rom image. @retval EFI_NOT_FOUND Failed to process PCI Option Rom image. **/ EFI_STATUS ProcessOpRomImage ( IN PCI_IO_DEVICE *PciDevice ) { UINT8 Indicator; UINT32 ImageSize; VOID *RomBar; UINT8 *RomBarOffset; EFI_HANDLE ImageHandle; EFI_STATUS Status; EFI_STATUS RetStatus; EFI_PCI_EXPANSION_ROM_HEADER *EfiRomHeader; PCI_DATA_STRUCTURE *Pcir; EFI_DEVICE_PATH_PROTOCOL *PciOptionRomImageDevicePath; MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH EfiOpRomImageNode; VOID *Buffer; UINTN BufferSize; Indicator = 0; // // Get the Address of the Option Rom image // RomBar = PciDevice->PciIo.RomImage; RomBarOffset = (UINT8 *)RomBar; RetStatus = EFI_NOT_FOUND; if (RomBar == NULL) { return RetStatus; } ASSERT (((EFI_PCI_EXPANSION_ROM_HEADER *)RomBarOffset)->Signature == PCI_EXPANSION_ROM_HEADER_SIGNATURE); do { EfiRomHeader = (EFI_PCI_EXPANSION_ROM_HEADER *)RomBarOffset; if (EfiRomHeader->Signature != PCI_EXPANSION_ROM_HEADER_SIGNATURE) { RomBarOffset += 512; continue; } Pcir = (PCI_DATA_STRUCTURE *)(RomBarOffset + EfiRomHeader->PcirOffset); ASSERT (Pcir->Signature == PCI_DATA_STRUCTURE_SIGNATURE); ImageSize = (UINT32)(Pcir->ImageLength * 512); Indicator = Pcir->Indicator; // // Skip the image if it is not an EFI PCI Option ROM image // if (Pcir->CodeType != PCI_CODE_TYPE_EFI_IMAGE) { goto NextImage; } // // Ignore the EFI PCI Option ROM image if it is an EFI application // if (EfiRomHeader->EfiSubsystem == EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION) { goto NextImage; } // // Create Pci Option Rom Image device path header // EfiOpRomImageNode.Header.Type = MEDIA_DEVICE_PATH; EfiOpRomImageNode.Header.SubType = MEDIA_RELATIVE_OFFSET_RANGE_DP; SetDevicePathNodeLength (&EfiOpRomImageNode.Header, sizeof (EfiOpRomImageNode)); EfiOpRomImageNode.StartingOffset = (UINTN)RomBarOffset - (UINTN)RomBar; EfiOpRomImageNode.EndingOffset = (UINTN)RomBarOffset + ImageSize - 1 - (UINTN)RomBar; PciOptionRomImageDevicePath = AppendDevicePathNode (PciDevice->DevicePath, &EfiOpRomImageNode.Header); ASSERT (PciOptionRomImageDevicePath != NULL); // // load image and start image // BufferSize = 0; Buffer = NULL; ImageHandle = NULL; Status = gBS->LoadImage ( FALSE, gPciBusDriverBinding.DriverBindingHandle, PciOptionRomImageDevicePath, Buffer, BufferSize, &ImageHandle ); if (EFI_ERROR (Status)) { // // Record the Option ROM Image device path when LoadImage fails. // PciOverride.GetDriver() will try to look for the Image Handle using the device path later. // AddDriver (PciDevice, NULL, PciOptionRomImageDevicePath); } else { Status = gBS->StartImage (ImageHandle, NULL, NULL); if (!EFI_ERROR (Status)) { // // Record the Option ROM Image Handle // AddDriver (PciDevice, ImageHandle, NULL); PciRomAddImageMapping ( ImageHandle, PciDevice->PciRootBridgeIo->SegmentNumber, PciDevice->BusNumber, PciDevice->DeviceNumber, PciDevice->FunctionNumber, PciDevice->PciIo.RomImage, PciDevice->PciIo.RomSize ); RetStatus = EFI_SUCCESS; } } FreePool (PciOptionRomImageDevicePath); NextImage: RomBarOffset += ImageSize; } while (((Indicator & 0x80) == 0x00) && (((UINTN)RomBarOffset - (UINTN)RomBar) < PciDevice->RomSize)); return RetStatus; }