mirror of
https://github.com/CloverHackyColor/CloverBootloader.git
synced 2024-12-11 14:28:08 +01:00
461 lines
15 KiB
C
461 lines
15 KiB
C
|
/* driver.c - Wrapper for standalone EFI filesystem drivers */
|
||
|
/*
|
||
|
* Copyright © 2014 Pete Batard <pete@akeo.ie>
|
||
|
* Based on iPXE's efi_driver.c and efi_file.c:
|
||
|
* Copyright © 2011,2013 Michael Brown <mbrown@fensystems.co.uk>.
|
||
|
*
|
||
|
* 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 <Protocol/DriverBinding.h>
|
||
|
#include <Protocol/ComponentName.h>
|
||
|
#include <Protocol/ComponentName2.h>
|
||
|
|
||
|
#include "driver.h"
|
||
|
|
||
|
/* We'll try to instantiate a custom protocol as a mutex, so we need a GUID */
|
||
|
EFI_GUID *MutexGUID = NULL;
|
||
|
|
||
|
/* Keep a global copy of our ImageHanle */
|
||
|
EFI_HANDLE EfiImageHandle = NULL;
|
||
|
|
||
|
/* Handle for our custom protocol/mutex instance */
|
||
|
static EFI_HANDLE MutexHandle = NULL;
|
||
|
|
||
|
/* Custom protocol/mutex definition */
|
||
|
typedef struct {
|
||
|
INTN Unused;
|
||
|
} EFI_MUTEX_PROTOCOL;
|
||
|
static EFI_MUTEX_PROTOCOL MutexProtocol = { 0 };
|
||
|
|
||
|
|
||
|
/* Return the driver name */
|
||
|
static EFI_STATUS EFIAPI
|
||
|
FSGetDriverName(EFI_COMPONENT_NAME_PROTOCOL *This,
|
||
|
CHAR8 *Language, CHAR16 **DriverName)
|
||
|
{
|
||
|
*DriverName = DriverNameString;
|
||
|
return EFI_SUCCESS;
|
||
|
}
|
||
|
|
||
|
static EFI_STATUS EFIAPI
|
||
|
FSGetDriverName2(EFI_COMPONENT_NAME2_PROTOCOL *This,
|
||
|
CHAR8 *Language, CHAR16 **DriverName)
|
||
|
{
|
||
|
*DriverName = DriverNameString;
|
||
|
return EFI_SUCCESS;
|
||
|
}
|
||
|
|
||
|
/* Return the controller name (unsupported for a filesystem) */
|
||
|
static EFI_STATUS EFIAPI
|
||
|
FSGetControllerName(EFI_COMPONENT_NAME_PROTOCOL *This,
|
||
|
EFI_HANDLE ControllerHandle, EFI_HANDLE ChildHandle,
|
||
|
CHAR8 *Language, CHAR16 **ControllerName)
|
||
|
{
|
||
|
return EFI_UNSUPPORTED;
|
||
|
}
|
||
|
|
||
|
static EFI_STATUS EFIAPI
|
||
|
FSGetControllerName2(EFI_COMPONENT_NAME2_PROTOCOL *This,
|
||
|
EFI_HANDLE ControllerHandle, EFI_HANDLE ChildHandle,
|
||
|
CHAR8 *Language, CHAR16 **ControllerName)
|
||
|
{
|
||
|
return EFI_UNSUPPORTED;
|
||
|
}
|
||
|
|
||
|
static VOID
|
||
|
FreeFsInstance(EFI_FS *Instance) {
|
||
|
if (Instance->DevicePathString != NULL)
|
||
|
{
|
||
|
FreePool(Instance->DevicePathString);
|
||
|
Instance->DevicePathString = NULL;
|
||
|
}
|
||
|
|
||
|
if (Instance->RootFile != NULL)
|
||
|
{
|
||
|
FreePool(Instance->RootFile);
|
||
|
Instance->RootFile = NULL;
|
||
|
}
|
||
|
|
||
|
if (Instance != NULL)
|
||
|
{
|
||
|
FreePool(Instance);
|
||
|
Instance = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* http://sourceforge.net/p/tianocore/edk2-MdeModulePkg/ci/master/tree/Universal/Disk/DiskIoDxe/DiskIo.c
|
||
|
* To check if your driver has a chance to apply to the controllers sent during
|
||
|
* the supported detection phase, try to open the child protocols they are meant
|
||
|
* to consume in exclusive access (here EFI_DISK_IO).
|
||
|
*/
|
||
|
static EFI_STATUS EFIAPI
|
||
|
FSBindingSupported(EFI_DRIVER_BINDING_PROTOCOL *This,
|
||
|
EFI_HANDLE ControllerHandle,
|
||
|
EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath)
|
||
|
{
|
||
|
EFI_STATUS Status;
|
||
|
EFI_DISK_IO_PROTOCOL *DiskIo;
|
||
|
EFI_DISK_IO2_PROTOCOL *DiskIo2;
|
||
|
|
||
|
/* Don't handle this unless we can get exclusive access to DiskIO through it */
|
||
|
Status = BS->OpenProtocol(ControllerHandle,
|
||
|
&gEfiDiskIo2ProtocolGuid, (VOID **) &DiskIo2,
|
||
|
This->DriverBindingHandle, ControllerHandle,
|
||
|
EFI_OPEN_PROTOCOL_BY_DRIVER);
|
||
|
if (EFI_ERROR(Status))
|
||
|
{
|
||
|
DiskIo2 = NULL;
|
||
|
}
|
||
|
|
||
|
Status = BS->OpenProtocol(ControllerHandle,
|
||
|
&gEfiDiskIoProtocolGuid, (VOID **) &DiskIo,
|
||
|
This->DriverBindingHandle, ControllerHandle,
|
||
|
EFI_OPEN_PROTOCOL_BY_DRIVER);
|
||
|
if (EFI_ERROR(Status))
|
||
|
return Status;
|
||
|
|
||
|
PrintDebug(L"FSBindingSupported\n");
|
||
|
|
||
|
/* The whole concept of BindingSupported is to hint at what we may
|
||
|
* actually support, but not check if the target is valid or
|
||
|
* initialize anything, so we must close all protocols we opened.
|
||
|
*/
|
||
|
BS->CloseProtocol(ControllerHandle, &gEfiDiskIo2ProtocolGuid,
|
||
|
This->DriverBindingHandle, ControllerHandle);
|
||
|
|
||
|
BS->CloseProtocol(ControllerHandle, &gEfiDiskIoProtocolGuid,
|
||
|
This->DriverBindingHandle, ControllerHandle);
|
||
|
|
||
|
return EFI_SUCCESS;
|
||
|
}
|
||
|
|
||
|
static EFI_STATUS EFIAPI
|
||
|
FSBindingStart(EFI_DRIVER_BINDING_PROTOCOL *This,
|
||
|
EFI_HANDLE ControllerHandle,
|
||
|
EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath)
|
||
|
{
|
||
|
EFI_STATUS Status;
|
||
|
EFI_FS *Instance;
|
||
|
EFI_DEVICE_PATH *DevicePath;
|
||
|
|
||
|
PrintDebug(L"FSBindingStart\n");
|
||
|
|
||
|
/* Allocate a new instance of a filesystem */
|
||
|
Instance = AllocateZeroPool(sizeof(EFI_FS));
|
||
|
if (Instance == NULL) {
|
||
|
Status = EFI_OUT_OF_RESOURCES;
|
||
|
PrintStatusError(Status, L"Could not allocate a new file system instance");
|
||
|
return Status;
|
||
|
}
|
||
|
Instance->FileIoInterface.Revision = EFI_FILE_IO_INTERFACE_REVISION;
|
||
|
Instance->FileIoInterface.OpenVolume = FileOpenVolume,
|
||
|
|
||
|
/* Fill the device path for our instance */
|
||
|
DevicePath = DevicePathFromHandle(ControllerHandle);
|
||
|
if (DevicePath == NULL) {
|
||
|
Status = EFI_NO_MAPPING;
|
||
|
PrintStatusError(Status, L"Could not get Device Path");
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
Instance->DevicePathString = ConvertDevicePathToText(DevicePath, FALSE, FALSE);
|
||
|
if (Instance->DevicePathString == NULL) {
|
||
|
Status = EFI_OUT_OF_RESOURCES;
|
||
|
PrintStatusError(Status, L"Could not allocate Device Path string");
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
/* Get access to the Block IO protocol for this controller */
|
||
|
Status = BS->OpenProtocol(ControllerHandle,
|
||
|
&gEfiBlockIo2ProtocolGuid, (VOID **) &Instance->BlockIo2,
|
||
|
This->DriverBindingHandle, ControllerHandle,
|
||
|
/* http://wiki.phoenix.com/wiki/index.php/EFI_BOOT_SERVICES#OpenProtocol.28.29
|
||
|
* EFI_OPEN_PROTOCOL_BY_DRIVER returns Access Denied here, most likely
|
||
|
* because the disk driver has that protocol already open. So we use
|
||
|
* EFI_OPEN_PROTOCOL_GET_PROTOCOL (which doesn't require us to close it)
|
||
|
*/
|
||
|
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
|
||
|
if (EFI_ERROR(Status))
|
||
|
{
|
||
|
Instance->BlockIo2 = NULL;
|
||
|
}
|
||
|
|
||
|
Status = BS->OpenProtocol(ControllerHandle,
|
||
|
&gEfiBlockIoProtocolGuid, (VOID **) &Instance->BlockIo,
|
||
|
This->DriverBindingHandle, ControllerHandle,
|
||
|
/* http://wiki.phoenix.com/wiki/index.php/EFI_BOOT_SERVICES#OpenProtocol.28.29
|
||
|
* EFI_OPEN_PROTOCOL_BY_DRIVER returns Access Denied here, most likely
|
||
|
* because the disk driver has that protocol already open. So we use
|
||
|
* EFI_OPEN_PROTOCOL_GET_PROTOCOL (which doesn't require us to close it)
|
||
|
*/
|
||
|
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
|
||
|
if (EFI_ERROR(Status)) {
|
||
|
PrintStatusError(Status, L"Could not access BlockIO protocol");
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
/* Get exclusive access to the Disk IO protocol */
|
||
|
Status = BS->OpenProtocol(ControllerHandle,
|
||
|
&gEfiDiskIo2ProtocolGuid, (VOID**) &Instance->DiskIo2,
|
||
|
This->DriverBindingHandle, ControllerHandle,
|
||
|
EFI_OPEN_PROTOCOL_BY_DRIVER);
|
||
|
if (EFI_ERROR(Status))
|
||
|
{
|
||
|
Instance->DiskIo2 = NULL;
|
||
|
}
|
||
|
|
||
|
Status = BS->OpenProtocol(ControllerHandle,
|
||
|
&gEfiDiskIoProtocolGuid, (VOID**) &Instance->DiskIo,
|
||
|
This->DriverBindingHandle, ControllerHandle,
|
||
|
EFI_OPEN_PROTOCOL_BY_DRIVER);
|
||
|
if (EFI_ERROR(Status)) {
|
||
|
PrintStatusError(Status, L"Could not access the DiskIo protocol");
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
/* Go through GRUB target init */
|
||
|
Status = GrubDeviceInit(Instance);
|
||
|
if (EFI_ERROR(Status)) {
|
||
|
PrintStatusError(Status, L"Could not init grub device");
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
Status = FSInstall(Instance, ControllerHandle);
|
||
|
/* Unless we close the DiskIO protocol in case of error, no other
|
||
|
* FS driver will be able to access this partition.
|
||
|
*/
|
||
|
if (EFI_ERROR(Status)) {
|
||
|
GrubDeviceExit(Instance);
|
||
|
BS->CloseProtocol(ControllerHandle, &gEfiDiskIo2ProtocolGuid,
|
||
|
This->DriverBindingHandle, ControllerHandle);
|
||
|
BS->CloseProtocol(ControllerHandle, &gEfiDiskIoProtocolGuid,
|
||
|
This->DriverBindingHandle, ControllerHandle);
|
||
|
}
|
||
|
|
||
|
error:
|
||
|
if (EFI_ERROR(Status))
|
||
|
FreeFsInstance(Instance);
|
||
|
return Status;
|
||
|
}
|
||
|
|
||
|
static EFI_STATUS EFIAPI
|
||
|
FSBindingStop(EFI_DRIVER_BINDING_PROTOCOL *This,
|
||
|
EFI_HANDLE ControllerHandle, UINTN NumberOfChildren,
|
||
|
EFI_HANDLE *ChildHandleBuffer)
|
||
|
{
|
||
|
EFI_STATUS Status;
|
||
|
EFI_FS *Instance;
|
||
|
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FileIoInterface;
|
||
|
|
||
|
PrintDebug(L"FSBindingStop\n");
|
||
|
|
||
|
/* Get a pointer back to our FS instance through its installed protocol */
|
||
|
Status = BS->OpenProtocol(ControllerHandle,
|
||
|
&gEfiSimpleFileSystemProtocolGuid, (VOID **) &FileIoInterface,
|
||
|
This->DriverBindingHandle, ControllerHandle,
|
||
|
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
|
||
|
if (EFI_ERROR(Status)) {
|
||
|
PrintStatusError(Status, L"Could not locate our instance");
|
||
|
return Status;
|
||
|
}
|
||
|
|
||
|
Instance = _CR(FileIoInterface, EFI_FS, FileIoInterface);
|
||
|
FSUninstall(Instance, ControllerHandle);
|
||
|
|
||
|
Status = GrubDeviceExit(Instance);
|
||
|
if (EFI_ERROR(Status)) {
|
||
|
PrintStatusError(Status, L"Could not destroy grub device");
|
||
|
}
|
||
|
|
||
|
BS->CloseProtocol(ControllerHandle, &gEfiDiskIo2ProtocolGuid,
|
||
|
This->DriverBindingHandle, ControllerHandle);
|
||
|
|
||
|
BS->CloseProtocol(ControllerHandle, &gEfiDiskIoProtocolGuid,
|
||
|
This->DriverBindingHandle, ControllerHandle);
|
||
|
|
||
|
FreeFsInstance(Instance);
|
||
|
|
||
|
return EFI_SUCCESS;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* The platform determines whether it will support the older Component
|
||
|
* Name Protocol or the current Component Name2 Protocol, or both.
|
||
|
* Because of this, it is strongly recommended that you implement both
|
||
|
* protocols in your driver.
|
||
|
*
|
||
|
* NB: From what I could see, the only difference between Name and Name2
|
||
|
* is that Name uses ISO-639-2 ("eng") whereas Name2 uses RFC 4646 ("en")
|
||
|
* See: http://www.loc.gov/standards/iso639-2/faq.html#6
|
||
|
*/
|
||
|
static EFI_COMPONENT_NAME_PROTOCOL FSComponentName = {
|
||
|
.GetDriverName = FSGetDriverName,
|
||
|
.GetControllerName = FSGetControllerName,
|
||
|
.SupportedLanguages = (CHAR8 *) "eng"
|
||
|
};
|
||
|
|
||
|
static EFI_COMPONENT_NAME2_PROTOCOL FSComponentName2 = {
|
||
|
.GetDriverName = FSGetDriverName2,
|
||
|
.GetControllerName = FSGetControllerName2,
|
||
|
.SupportedLanguages = (CHAR8 *) "en"
|
||
|
};
|
||
|
|
||
|
static EFI_DRIVER_BINDING_PROTOCOL FSDriverBinding = {
|
||
|
.Supported = FSBindingSupported,
|
||
|
.Start = FSBindingStart,
|
||
|
.Stop = FSBindingStop,
|
||
|
/* This field is used by the EFI boot service ConnectController() to determine the order
|
||
|
* that driver's Supported() service will be used when a controller needs to be started.
|
||
|
* EFI Driver Binding Protocol instances with higher Version values will be used before
|
||
|
* ones with lower Version values. The Version values of 0x0-0x0f and
|
||
|
* 0xfffffff0-0xffffffff are reserved for platform/OEM specific drivers. The Version
|
||
|
* values of 0x10-0xffffffef are reserved for IHV-developed drivers.
|
||
|
*/
|
||
|
.Version = 0x10,
|
||
|
.ImageHandle = NULL,
|
||
|
.DriverBindingHandle = NULL
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Uninstall EFI driver
|
||
|
*
|
||
|
* @v ImageHandle Handle identifying the loaded image
|
||
|
* @ret Status EFI status code to return on exit
|
||
|
*/
|
||
|
EFI_STATUS EFIAPI
|
||
|
FSDriverUninstall(EFI_HANDLE ImageHandle)
|
||
|
{
|
||
|
EFI_STATUS Status;
|
||
|
UINTN NumHandles;
|
||
|
EFI_HANDLE *Handles;
|
||
|
UINTN i;
|
||
|
|
||
|
/* Enumerate all handles */
|
||
|
Status = BS->LocateHandleBuffer(AllHandles, NULL, NULL, &NumHandles, &Handles);
|
||
|
|
||
|
/* Disconnect controllers linked to our driver. This action will trigger a call to BindingStop */
|
||
|
if (Status == EFI_SUCCESS) {
|
||
|
for (i=0; i<NumHandles; i++) {
|
||
|
/* Make sure to filter on DriverBindingHandle, else EVERYTHING gets disconnected! */
|
||
|
Status = BS->DisconnectController(Handles[i], FSDriverBinding.DriverBindingHandle, NULL);
|
||
|
if (Status == EFI_SUCCESS)
|
||
|
PrintDebug(L"DisconnectController[%d]\n", i);
|
||
|
}
|
||
|
} else {
|
||
|
PrintStatusError(Status, L"Unable to enumerate handles");
|
||
|
}
|
||
|
|
||
|
if (Handles != NULL)
|
||
|
{
|
||
|
BS->FreePool(Handles);
|
||
|
Handles = NULL;
|
||
|
}
|
||
|
|
||
|
/* Now that all controllers are disconnected, we can safely remove our protocols */
|
||
|
BS->UninstallMultipleProtocolInterfaces(ImageHandle,
|
||
|
&gEfiDriverBindingProtocolGuid, &FSDriverBinding,
|
||
|
&gEfiComponentNameProtocolGuid, &FSComponentName,
|
||
|
&gEfiComponentName2ProtocolGuid, &FSComponentName2,
|
||
|
NULL);
|
||
|
|
||
|
/* Release the relevant GRUB fs module(s) */
|
||
|
GrubDriverExit();
|
||
|
|
||
|
/* Uninstall our mutex (we're the only instance that can run this code) */
|
||
|
BS->UninstallMultipleProtocolInterfaces(MutexHandle,
|
||
|
MutexGUID, &MutexProtocol,
|
||
|
NULL);
|
||
|
|
||
|
PrintDebug(L"FS driver uninstalled.\n");
|
||
|
return EFI_SUCCESS;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Install EFI driver - Will be the entrypoint for our driver executable
|
||
|
* http://wiki.phoenix.com/wiki/index.php/EFI_IMAGE_ENTRY_POINT
|
||
|
*
|
||
|
* @v ImageHandle Handle identifying the loaded image
|
||
|
* @v SystemTable Pointers to EFI system calls
|
||
|
* @ret Status EFI status code to return on exit
|
||
|
*/
|
||
|
EFI_STATUS EFIAPI
|
||
|
FSDriverInstall(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable)
|
||
|
{
|
||
|
EFI_STATUS Status;
|
||
|
EFI_LOADED_IMAGE_PROTOCOL *LoadedImage = NULL;
|
||
|
VOID *Interface;
|
||
|
|
||
|
//InitializeLib(ImageHandle, SystemTable);
|
||
|
SetLogging();
|
||
|
EfiImageHandle = ImageHandle;
|
||
|
|
||
|
/* Prevent the driver from being loaded twice by detecting and trying to
|
||
|
* instantiate a custom protocol, which we use as a global mutex.
|
||
|
*/
|
||
|
MutexGUID = GetFSGuid();
|
||
|
|
||
|
Status = BS->LocateProtocol(MutexGUID, NULL, &Interface);
|
||
|
if (Status == EFI_SUCCESS) {
|
||
|
PrintError(L"This driver has already been installed\n");
|
||
|
return EFI_LOAD_ERROR;
|
||
|
}
|
||
|
/* The only valid status we expect is NOT FOUND here */
|
||
|
if (Status != EFI_NOT_FOUND) {
|
||
|
PrintStatusError(Status, L"Could not locate global mutex");
|
||
|
return Status;
|
||
|
}
|
||
|
Status = BS->InstallMultipleProtocolInterfaces(&MutexHandle,
|
||
|
MutexGUID, &MutexProtocol,
|
||
|
NULL);
|
||
|
if (EFI_ERROR(Status)) {
|
||
|
PrintStatusError(Status, L"Could not install global mutex");
|
||
|
return Status;
|
||
|
}
|
||
|
|
||
|
/* Grab a handle to this image, so that we can add an unload to our driver */
|
||
|
Status = BS->OpenProtocol(ImageHandle, &gEfiLoadedImageProtocolGuid,
|
||
|
(VOID **) &LoadedImage, ImageHandle,
|
||
|
NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
|
||
|
if (EFI_ERROR(Status)) {
|
||
|
PrintStatusError(Status, L"Could not open loaded image protocol");
|
||
|
return Status;
|
||
|
}
|
||
|
|
||
|
/* Configure driver binding protocol */
|
||
|
FSDriverBinding.ImageHandle = ImageHandle;
|
||
|
FSDriverBinding.DriverBindingHandle = ImageHandle;
|
||
|
|
||
|
/* Install driver */
|
||
|
Status = BS->InstallMultipleProtocolInterfaces(&FSDriverBinding.DriverBindingHandle,
|
||
|
&gEfiDriverBindingProtocolGuid, &FSDriverBinding,
|
||
|
&gEfiComponentNameProtocolGuid, &FSComponentName,
|
||
|
&gEfiComponentName2ProtocolGuid, &FSComponentName2,
|
||
|
NULL);
|
||
|
if (EFI_ERROR(Status)) {
|
||
|
PrintStatusError(Status, L"Could not bind driver");
|
||
|
return Status;
|
||
|
}
|
||
|
|
||
|
/* Register the uninstall callback */
|
||
|
LoadedImage->Unload = FSDriverUninstall;
|
||
|
|
||
|
/* Initialize the relevant GRUB fs module(s) */
|
||
|
GrubDriverInit();
|
||
|
|
||
|
PrintDebug(L"FS driver installed.\n");
|
||
|
return EFI_SUCCESS;
|
||
|
}
|