Search code examples
cuefignu-efi

UEFI Resolve full path


I'm working on a bootloader of sorts using GNU-EFI. So far I've been able to read the Boot#### NVRAM variable, so I have a semi-populated FilePathList[], which looks like this (printed with DevicePathToStr):

HD(Part2, SigCD0400E6-54F3-49F4-81F2-65B21E8278A8)/\EFI\Microsoft\Boot\bootmgfw.efi

When passed to LoadImage it fails with EFI_NOT_FOUND. As I understand it (UEFI Doc Section 3.1.2), I need to add the full path before what I already have. I've found out that the correct path is PciRoot(0x0)/Pci(0x1, 0x1)/Ata(0x0), but I am unsure how I programmatically find this path based on what I have so it can be prepended.

The code I have so far is as follows, please excuse the low quality, I've just been trying to get something working so far.

   EFI_STATUS status;
   EFI_GUID vendor = EFI_GLOBAL_VARIABLE;
   UINT32 Attr;

   UINTN size = 256;
   UINT16 *buf = AllocateZeroPool(size);
   if (buf == NULL)
      Print(L"Failed to allocate buffer\n");

   status = uefi_call_wrapper(RT->GetVariable, 5,
         L"BootOrder", /*VariableName*/
         &vendor, /*VendorGuid*/
         &Attr, /*Attributes*/
         &size, /*DataSize*/
         buf /*Data*/
         );
   if (status != EFI_SUCCESS)
      Print(L"Failed to read BootOrder (%d)\n", status);

   // should contain an int for the correct boot option
   UINT16 bootopt = buf[0];
   FreePool(buf);

   CHAR16 *name = AllocateZeroPool(18); // Bootxxxx\0 unicode
   SPrint(name, 18, L"Boot%04x", bootopt);

   Print(L"Next boot: %s\n", name);

   size = 0;
   do {
      buf = AllocateZeroPool(size);
      if (buf == NULL)
         Print(L"Failed to allocate buffer\n");

      status = uefi_call_wrapper(RT->GetVariable, 5,
            name,
            &vendor,
            &Attr,
            &size,
            buf
            );
      if (status == EFI_SUCCESS) break;

      FreePool(buf);
      // if it fails, size is set to what it needs to be
      // handy that
   } while(status == EFI_BUFFER_TOO_SMALL);

   if (!(buf[0]&LOAD_OPTION_ACTIVE)) Print(L"BootOption not active\n");
   Print(L"%s: 0x%r\n\n", name, buf);

   UINT8 *OrigFilePathList = ((UINT8*)buf) + (sizeof(UINT32) + sizeof(UINT16) + StrSize(buf+3));
   UINT16 *FilePathListLength = ((UINT16*)OrigFilePathList)+2;

   Print(L"&OrigFilePathList = 0x%r\n", OrigFilePathList);
   Print(L"sizeof(_EFI_LOAD_OPTION) = %d\n", size);
   Print(L"struct _EFI_LOAD_OPTION {\n");
   Print(L"    Attributes = %d\n", *(UINT32 *)(buf));
   Print(L"    FilePathListLength = %d,\n", *FilePathListLength);
   Print(L"    Description = %s,\n", buf+3);
   Print(L"    FilePathList[] = {\n");

   UINT16 totallength = 0;

   UINT8 *FilePathList = OrigFilePathList;
   for (UINT8 i = 0; i < *FilePathListLength+1; i++) {
      Print(L"        &FilePathList[%d] = 0x%r\n", i, OrigFilePathList);
      Print(L"        FilePathList[%d].Type = %d ", i, *OrigFilePathList);
      switch (*OrigFilePathList) {
         case 0x01:
            Print(L"(Hardware Device Path)\n");
            break;
         case 0x02:
            Print(L"(ACPI Device Path)\n");
            break;
         case 0x03:
            Print(L"(Messaging Device Path)\n");
            break;
         case 0x04:
            Print(L"(Media Device Path)\n");
            break;
         case 0x05:
            Print(L"(BIOS Boot Specification Device Path)\n");
            break;
         case 0x7f:
            Print(L"(End Of Hardware Device Path)\n");
            break;
         default:
            Print(L"(Unknown Device Path)\n");
            break;
      }
      Print(L"        FilePathList[%d].SubType = %d\n", i, *(OrigFilePathList+1));
      Print(L"        FilePathList[%d].Length = %d\n", i, *(UINT16*)(OrigFilePathList+2));
      totallength += *(UINT16*)(OrigFilePathList+2);

      OrigFilePathList += *(UINT16*)(OrigFilePathList+2);
   }
   Print(L"    }\n");
   Print(L"    &OptionalData = 0x%r\n", OrigFilePathList);
   Print(L"    OptionalDataLength = %d\n", size-totallength);
   Print(L"}\n");

   // The hard drive device path can be appended to the matching hardware
   // device path and normal boot behavior can then be used.

   // We need to locate the Type 1 FilePathList and prepend it to what we've already got

   // Need to prefix PciRoot(0x0)/Pci(0x1, 0x1)/Ata(0x0)
   // but automatically find it
   // in theory we should be able to use the list of handles to devices that support SIMPLE_FILE_SYSTEM_PROTOCOL
   // to find the right device

   Print(L"%s\n", DevicePathToStr((EFI_DEVICE_PATH *)FilePathList));

   /* EFI_STATUS (EFIAPI *EFI_IMAGE_LOAD) (
      IN BOOLEAN BootPolicy,
      IN EFI_HANDLE ParentImageHandle,
      IN EFI_DEVICE_PATH_PROTOCOL *DevicePath,
      IN VOID *SourceBuffer OPTIONAL,
      IN UINTN SourceSize,
      OUT EFI_HANDLE *ImageHandle
      ); */

   EFI_HANDLE *NextHandle = AllocateZeroPool(sizeof(EFI_HANDLE));

   status = uefi_call_wrapper(BS->LoadImage, 6,
   /* status = BS->LoadImage( */
         TRUE, /* BootPolicy */
         ImageHandle, /* ParentImageHandle */
         (EFI_DEVICE_PATH *)FilePathList, /* DevicePath */
         NULL, /* SourceBuffer */
         0, /* SourceSize */
         NextHandle /* ImageHandle */
         );

   if (status != EFI_SUCCESS)
      Print(L"Failed to LoadImage (%d)\n", status);
   else
      Print(L"LoadImage OK\n");

What functions and flows are required for me to fully qualify the FilePathList so it can be used with LoadImage?


Solution

  • Trust that when I've asked for help I work it out.

    The general idea is to use LocateHandleBuffer to find all handles for SIMPLE_FILE_SYSTEM_PROTOCOL. With these handles, compare the path (using DevicePathFromHandle) to what we already have to find the appropriate device. LoadImage now works for me.

    Sample code below (buf is the value of the Boot#### variable from GetVariable):

       Print(L"Description = %s\n", (CHAR16*)buf + 3);
       EFI_DEVICE_PATH *BootX = (EFI_DEVICE_PATH*) (((UINT8*)buf) + (sizeof(UINT32) + sizeof(UINT16) + StrSize(buf+3)));
    
       UINTN NoHandles = 0;
       EFI_HANDLE *handles = NULL;
       EFI_GUID SimpleFileSystemGUID = SIMPLE_FILE_SYSTEM_PROTOCOL;
       status = uefi_call_wrapper(BS->LocateHandleBuffer,
             5,
             ByProtocol,
             &SimpleFileSystemGUID,
             NULL,
             &NoHandles,
             &handles
             );
       if (status != EFI_SUCCESS)
          Print(L"Failed to LocateHandleBuffer (%d)\n", status);
       else
          Print(L"LocateHandleBuffer OK (%d handles)\n", NoHandles);
    
       EFI_DEVICE_PATH *prefix;
       UINTN index;
       for (index = 0; index < NoHandles; index++) {
          prefix = DevicePathFromHandle(handles[index]);
          while(!IsDevicePathEnd(NextDevicePathNode(prefix))) prefix = NextDevicePathNode(prefix);
          if(LibMatchDevicePaths(prefix, BootX)) {
             break;
          } else {
             FreePool(prefix);
          }
       }
    
       prefix = DevicePathFromHandle(handles[index]);
       // prefix ends with the same node that BootX starts with
       // so skip forward BootX so we can prepend prefix
       BootX = NextDevicePathNode(BootX);
       EFI_DEVICE_PATH *fullpath = AppendDevicePath(prefix, BootX);
       Print(L"Booting: %s\n", DevicePathToStr(fullpath));
    
       /* EFI_STATUS (EFIAPI *EFI_IMAGE_LOAD) (
          IN BOOLEAN BootPolicy,
          IN EFI_HANDLE ParentImageHandle,
          IN EFI_DEVICE_PATH_PROTOCOL *DevicePath,
          IN VOID *SourceBuffer OPTIONAL,
          IN UINTN SourceSize,
          OUT EFI_HANDLE *ImageHandle
          ); */
    
       EFI_HANDLE *NextHandle = AllocateZeroPool(sizeof(EFI_HANDLE));
    
       status = uefi_call_wrapper(BS->LoadImage, 6,
       /* status = BS->LoadImage( */
             TRUE, /* BootPolicy */
             ImageHandle, /* ParentImageHandle */
             fullpath, /* DevicePath */
             NULL, /* SourceBuffer */
             0, /* SourceSize */
             NextHandle /* ImageHandle */
             );
    
       if (status != EFI_SUCCESS)
          Print(L"Failed to LoadImage (%d)\n", status);
       else
          Print(L"LoadImage OK\n");