]> code.delx.au - refind/blobdiff - refind/driver_support.c
Worked around bug/quirk in some EFIs that prevented filesystem drivers
[refind] / refind / driver_support.c
index a547d654d25e6f2b9fc3f08f9234b3191a1cdfcd..3faf9846ee68f6b26baf51cbb13d4d4b432a6e40 100644 (file)
@@ -1,12 +1,7 @@
 /*
- * File to implement LibScanHandleDatabase(), which is used by rEFInd's
- * driver-loading code (inherited from rEFIt), but which has not been
- * implemented in GNU-EFI and seems to have been dropped from current
- * versions of the Tianocore library. This function was taken from a git
- * site with EFI code, but some of the constants it uses were taken from
- * a more recent EDK2 package (see below for details). The original files
- * bore the following copyright notice:
- *
+ * Functions related to drivers. Original copyright notices below....
+ */
+/*
  * Copyright (c) 2006 - 2011, Intel Corporation. All rights reserved.<BR>
  * This program and the accompanying materials are licensed and made available under
  * the terms and conditions of the BSD License that accompanies this distribution.
  * WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
  *
  */
+/*
+ * refit/main.c
+ * Main code for the boot menu
+ *
+ * Copyright (c) 2006-2010 Christoph Pfisterer
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the
+ *    distribution.
+ *
+ *  * Neither the name of Christoph Pfisterer nor the names of the
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Modifications copyright (c) 2012-2016 Roderick W. Smith
+ *
+ * Modifications distributed under the terms of the GNU General Public
+ * License (GPL) version 3 (GPLv3), or (at your option) any later version.
+ *
+ */
+/*
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
 
 #include "driver_support.h"
 #include "lib.h"
+#include "mystrings.h"
+#include "screen.h"
 #include "../include/refit_call_wrapper.h"
 
+#if defined (EFIX64)
+#define DRIVER_DIRS             L"drivers,drivers_x64"
+#elif defined (EFI32)
+#define DRIVER_DIRS             L"drivers,drivers_ia32"
+#elif defined (EFIAARCH64)
+#define DRIVER_DIRS             L"drivers,drivers_aa64"
+#endif
+
 #ifdef __MAKEWITH_GNUEFI
 // Following "global" constants are from EDK2's AutoGen.c....
 EFI_GUID gEfiLoadedImageProtocolGuid = { 0x5B1B31A1, 0x9562, 0x11D2, { 0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};
@@ -33,22 +94,64 @@ EFI_GUID gEfiDriverDiagnostics2ProtocolGuid = { 0x4D330321, 0x025F, 0x4AAC, { 0x
 EFI_GUID gEfiComponentNameProtocolGuid = { 0x107A772C, 0xD5E1, 0x11D4, { 0x9A, 0x46, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D }};
 EFI_GUID gEfiComponentName2ProtocolGuid = { 0x6A7A5CFF, 0xE8D9, 0x4F70, { 0xBA, 0xDA, 0x75, 0xAB, 0x30, 0x25, 0xCE, 0x14 }};
 EFI_GUID gEfiDevicePathProtocolGuid = { 0x09576E91, 0x6D3F, 0x11D2, { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};
-#endif
+EFI_GUID gEfiDiskIoProtocolGuid = { 0xCE345171, 0xBA0B, 0x11D2, { 0x8E, 0x4F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};
+EFI_GUID gEfiBlockIoProtocolGuid = { 0x964E5B21, 0x6459, 0x11D2, { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};
+EFI_GUID gEfiSimpleFileSystemProtocolGuid = { 0x964E5B22, 0x6459, 0x11D2, { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};
+
+struct EFI_SIMPLE_FILE_SYSTEM_PROTOCOL;
+struct EFI_FILE_PROTOCOL;
 
-// Below is from http://git.etherboot.org/?p=mirror/efi/shell/.git;a=commitdiff;h=b1b0c63423cac54dc964c2930e04aebb46a946ec;
-// Seems to have been replaced by ParseHandleDatabaseByRelationshipWithType(), but the latter isn't working for me....
+typedef
 EFI_STATUS
-LibScanHandleDatabase (
-  EFI_HANDLE  DriverBindingHandle, OPTIONAL
-  UINT32      *DriverBindingHandleIndex, OPTIONAL
-  EFI_HANDLE  ControllerHandle, OPTIONAL
-  UINT32      *ControllerHandleIndex, OPTIONAL
-  UINTN       *HandleCount,
-  EFI_HANDLE  **HandleBuffer,
-  UINT32      **HandleType
-  )
+(EFIAPI *EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_OPEN_VOLUME)(
+  IN struct EFI_SIMPLE_FILE_SYSTEM_PROTOCOL    *This,
+  OUT struct EFI_FILE_PROTOCOL                 **Root
+  );
 
-{
+typedef struct _EFI_SIMPLE_FILE_SYSTEM_PROTOCOL {
+  UINT64                                      Revision;
+  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_OPEN_VOLUME OpenVolume;
+} EFI_SIMPLE_FILE_SYSTEM_PROTOCOL;
+
+typedef struct _EFI_FILE_PROTOCOL {
+  UINT64                Revision;
+  EFI_FILE_OPEN         Open;
+  EFI_FILE_CLOSE        Close;
+  EFI_FILE_DELETE       Delete;
+  EFI_FILE_READ         Read;
+  EFI_FILE_WRITE        Write;
+  EFI_FILE_GET_POSITION GetPosition;
+  EFI_FILE_SET_POSITION SetPosition;
+  EFI_FILE_GET_INFO     GetInfo;
+  EFI_FILE_SET_INFO     SetInfo;
+  EFI_FILE_FLUSH        Flush;
+} EFI_FILE_PROTOCOL;
+
+typedef struct _EFI_BLOCK_IO_PROTOCOL {
+  UINT64             Revision;
+  EFI_BLOCK_IO_MEDIA *Media;
+  EFI_BLOCK_RESET    Reset;
+  EFI_BLOCK_READ     ReadBlocks;
+  EFI_BLOCK_WRITE    WriteBlocks;
+  EFI_BLOCK_FLUSH    FlushBlocks;
+} EFI_BLOCK_IO_PROTOCOL;
+#endif
+
+/* LibScanHandleDatabase() is used by rEFInd's driver-loading code (inherited
+ * from rEFIt), but has not been implemented in GNU-EFI and seems to have been
+ * dropped from current versions of the Tianocore library. This function was
+ * taken from http://git.etherboot.org/?p=mirror/efi/shell/.git;a=commitdiff;h=b1b0c63423cac54dc964c2930e04aebb46a946ec,
+ * The original files are copyright 2006-2011 Intel and BSD-licensed. Minor
+ * modifications by Roderick Smith are GPLv3.
+ */
+EFI_STATUS
+LibScanHandleDatabase (EFI_HANDLE  DriverBindingHandle, OPTIONAL
+                       UINT32      *DriverBindingHandleIndex, OPTIONAL
+                       EFI_HANDLE  ControllerHandle, OPTIONAL
+                       UINT32      *ControllerHandleIndex, OPTIONAL
+                       UINTN       *HandleCount,
+                       EFI_HANDLE  **HandleBuffer,
+                       UINT32      **HandleType) {
   EFI_STATUS                          Status;
   UINTN                               HandleIndex;
   EFI_GUID                            **ProtocolGuidArray;
@@ -59,14 +162,12 @@ LibScanHandleDatabase (
   UINTN                               OpenInfoIndex;
   UINTN                               ChildIndex;
   BOOLEAN                             DriverBindingHandleIndexValid;
-//  BOOLEAN                             ControllerHandleIndexValid;
 
   DriverBindingHandleIndexValid = FALSE;
   if (DriverBindingHandleIndex != NULL) {
     *DriverBindingHandleIndex = 0xffffffff;
   }
 
-//  ControllerHandleIndexValid = FALSE;
   if (ControllerHandleIndex != NULL) {
     *ControllerHandleIndex = 0xffffffff;
   }
@@ -111,7 +212,6 @@ LibScanHandleDatabase (
 
     if (ControllerHandle != NULL && ControllerHandleIndex != NULL && (*HandleBuffer)[HandleIndex] == ControllerHandle) {
       *ControllerHandleIndex      = (UINT32) HandleIndex;
-//      ControllerHandleIndexValid  = TRUE;
     }
 
   }
@@ -249,3 +349,249 @@ Error:
 
   return Status;
 } /* EFI_STATUS LibScanHandleDatabase() */
+
+#ifdef __MAKEWITH_GNUEFI
+/* Modified from EDK2 function of a similar name; original copyright Intel &
+ * BSD-licensed; modifications by Roderick Smith are GPLv3. */
+EFI_STATUS ConnectAllDriversToAllControllers(VOID)
+{
+    EFI_STATUS           Status;
+    UINTN                AllHandleCount;
+    EFI_HANDLE           *AllHandleBuffer;
+    UINTN                Index;
+    UINTN                HandleCount;
+    EFI_HANDLE           *HandleBuffer;
+    UINT32               *HandleType;
+    UINTN                HandleIndex;
+    BOOLEAN              Parent;
+    BOOLEAN              Device;
+
+    Status = LibLocateHandle(AllHandles,
+                             NULL,
+                             NULL,
+                             &AllHandleCount,
+                             &AllHandleBuffer);
+    if (EFI_ERROR(Status))
+        return Status;
+
+    for (Index = 0; Index < AllHandleCount; Index++) {
+        //
+        // Scan the handle database
+        //
+        Status = LibScanHandleDatabase(NULL,
+                                       NULL,
+                                       AllHandleBuffer[Index],
+                                       NULL,
+                                       &HandleCount,
+                                       &HandleBuffer,
+                                       &HandleType);
+        if (EFI_ERROR (Status))
+            goto Done;
+
+        Device = TRUE;
+        if (HandleType[Index] & EFI_HANDLE_TYPE_DRIVER_BINDING_HANDLE)
+            Device = FALSE;
+        if (HandleType[Index] & EFI_HANDLE_TYPE_IMAGE_HANDLE)
+            Device = FALSE;
+
+        if (Device) {
+            Parent = FALSE;
+            for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) {
+                if (HandleType[HandleIndex] & EFI_HANDLE_TYPE_PARENT_HANDLE)
+                    Parent = TRUE;
+            } // for
+
+            if (!Parent) {
+                if (HandleType[Index] & EFI_HANDLE_TYPE_DEVICE_HANDLE) {
+                   Status = refit_call4_wrapper(BS->ConnectController,
+                                                AllHandleBuffer[Index],
+                                                NULL,
+                                                NULL,
+                                                TRUE);
+                }
+            }
+        }
+
+        MyFreePool (HandleBuffer);
+        MyFreePool (HandleType);
+    }
+
+Done:
+    MyFreePool (AllHandleBuffer);
+    return Status;
+} /* EFI_STATUS ConnectAllDriversToAllControllers() */
+#else
+EFI_STATUS ConnectAllDriversToAllControllers(VOID) {
+    BdsLibConnectAllDriversToAllControllers();
+    return 0;
+}
+#endif
+
+/*
+ * ConnectFilesystemDriver() is modified from DisconnectInvalidDiskIoChildDrivers()
+ * in Clover (https://sourceforge.net/projects/cloverefiboot/), which is derived
+ * from rEFIt. The refit/main.c file from which this function was taken continues
+ * to bear rEFIt's original copyright/licence notice (BSD); modifications by
+ * Roderick Smith (2016) are GPLv3.
+ */
+/**
+ * Some UEFI's (like HPQ EFI from HP notebooks) have DiskIo protocols
+ * opened BY_DRIVER (by Partition driver in HP case) even when no file system
+ * is produced from this DiskIo. This then blocks our FS drivers from connecting
+ * and producing file systems.
+ * To fix it: we will disconnect drivers that connected to DiskIo BY_DRIVER
+ * if this is partition volume and if those drivers did not produce file system,
+ * then try to connect every unconnected device to the driver whose handle is
+ * passed to us.
+ */
+VOID ConnectFilesystemDriver(EFI_HANDLE DriverHandle) {
+    EFI_STATUS                            Status;
+    UINTN                                 HandleCount = 0;
+    UINTN                                 Index;
+    UINTN                                 OpenInfoIndex;
+    EFI_HANDLE                            *Handles = NULL;
+    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL       *Fs;
+    EFI_BLOCK_IO_PROTOCOL                 *BlockIo;
+    EFI_OPEN_PROTOCOL_INFORMATION_ENTRY   *OpenInfo;
+    UINTN                                 OpenInfoCount;
+    EFI_HANDLE                            DriverHandleList[2];
+
+    //
+    // Get all DiskIo handles
+    //
+    Status = refit_call5_wrapper(gBS->LocateHandleBuffer,
+                                 ByProtocol,
+                                 &gEfiDiskIoProtocolGuid,
+                                 NULL,
+                                 &HandleCount,
+                                 &Handles);
+    if (EFI_ERROR(Status) || HandleCount == 0)
+        return;
+
+    //
+    // Check every DiskIo handle
+    //
+    for (Index = 0; Index < HandleCount; Index++) {
+        //
+        // If this is not partition - skip it.
+        // This is then whole disk and DiskIo
+        // should be opened here BY_DRIVER by Partition driver
+        // to produce partition volumes.
+        //
+        Status = refit_call3_wrapper(gBS->HandleProtocol,
+                                     Handles[Index],
+                                     &gEfiBlockIoProtocolGuid,
+                                     (VOID **) &BlockIo);
+        if (EFI_ERROR (Status))
+            continue;
+        if (BlockIo->Media == NULL || !BlockIo->Media->LogicalPartition)
+            continue;
+
+        //
+        // If SimpleFileSystem is already produced - skip it, this is ok
+        //
+        Status = refit_call3_wrapper(gBS->HandleProtocol,
+                                    Handles[Index],
+                                    &gEfiSimpleFileSystemProtocolGuid,
+                                    (VOID **) &Fs);
+        if (Status == EFI_SUCCESS)
+            continue;
+
+        //
+        // If no SimpleFileSystem on this handle but DiskIo is opened BY_DRIVER
+        // then disconnect this connection and try to connect our driver to it
+        //
+        Status = refit_call4_wrapper(gBS->OpenProtocolInformation,
+                                     Handles[Index],
+                                     &gEfiDiskIoProtocolGuid,
+                                     &OpenInfo,
+                                     &OpenInfoCount);
+        if (EFI_ERROR (Status))
+            continue;
+        DriverHandleList[1] = NULL;
+        for (OpenInfoIndex = 0; OpenInfoIndex < OpenInfoCount; OpenInfoIndex++) {
+            if ((OpenInfo[OpenInfoIndex].Attributes & EFI_OPEN_PROTOCOL_BY_DRIVER) == EFI_OPEN_PROTOCOL_BY_DRIVER) {
+                Status = refit_call3_wrapper(gBS->DisconnectController,
+                                             Handles[Index],
+                                             OpenInfo[OpenInfoIndex].AgentHandle,
+                                             NULL);
+                if (!(EFI_ERROR(Status))) {
+                    DriverHandleList[0] = DriverHandle;
+                    refit_call4_wrapper(gBS->ConnectController,
+                                        Handles[Index],
+                                        DriverHandleList,
+                                        NULL,
+                                        FALSE);
+                } // if
+            } // if
+        } // for
+        FreePool (OpenInfo);
+    }
+    FreePool(Handles);
+} // VOID ConnectFilesystemDriver()
+
+// Scan a directory for drivers.
+// Originally from rEFIt's main.c (BSD), but modified since then (GPLv3).
+static UINTN ScanDriverDir(IN CHAR16 *Path)
+{
+    EFI_STATUS              Status;
+    REFIT_DIR_ITER          DirIter;
+    UINTN                   NumFound = 0;
+    EFI_FILE_INFO           *DirEntry;
+    CHAR16                  FileName[256];
+
+    CleanUpPathNameSlashes(Path);
+    // look through contents of the directory
+    DirIterOpen(SelfRootDir, Path, &DirIter);
+    while (DirIterNext(&DirIter, 2, LOADER_MATCH_PATTERNS, &DirEntry)) {
+        if (DirEntry->FileName[0] == '.')
+            continue;   // skip this
+
+        SPrint(FileName, 255, L"%s\\%s", Path, DirEntry->FileName);
+        NumFound++;
+        Status = StartEFIImage(FileDevicePath(SelfLoadedImage->DeviceHandle, FileName),
+                               L"", TYPE_EFI, DirEntry->FileName, 0, NULL, FALSE, TRUE);
+    }
+    Status = DirIterClose(&DirIter);
+    if ((Status != EFI_NOT_FOUND) && (Status != EFI_INVALID_PARAMETER)) {
+        SPrint(FileName, 255, L"while scanning the %s directory", Path);
+        CheckError(Status, FileName);
+    }
+    return (NumFound);
+} // static UINTN ScanDriverDir()
+
+
+// Load all EFI drivers from rEFInd's "drivers" subdirectory and from the
+// directories specified by the user in the "scan_driver_dirs" configuration
+// file line.
+// Originally from rEFIt's main.c (BSD), but modified since then (GPLv3).
+VOID LoadDrivers(VOID) {
+    CHAR16        *Directory, *SelfDirectory;
+    UINTN         i = 0, Length, NumFound = 0;
+
+    // load drivers from the subdirectories of rEFInd's home directory specified
+    // in the DRIVER_DIRS constant.
+    while ((Directory = FindCommaDelimited(DRIVER_DIRS, i++)) != NULL) {
+        SelfDirectory = SelfDirPath ? StrDuplicate(SelfDirPath) : NULL;
+        CleanUpPathNameSlashes(SelfDirectory);
+        MergeStrings(&SelfDirectory, Directory, L'\\');
+        NumFound += ScanDriverDir(SelfDirectory);
+        MyFreePool(Directory);
+        MyFreePool(SelfDirectory);
+    }
+
+    // Scan additional user-specified driver directories....
+    i = 0;
+    while ((Directory = FindCommaDelimited(GlobalConfig.DriverDirs, i++)) != NULL) {
+        CleanUpPathNameSlashes(Directory);
+        Length = StrLen(Directory);
+        if (Length > 0) {
+            NumFound += ScanDriverDir(Directory);
+        } // if
+        MyFreePool(Directory);
+    } // while
+
+    // connect all devices
+    if (NumFound > 0)
+        ConnectAllDriversToAllControllers();
+} /* VOID LoadDrivers() */