Skip to content

Instantly share code, notes, and snippets.

@kwaters
Created December 20, 2013 19:05
Show Gist options
  • Save kwaters/8059752 to your computer and use it in GitHub Desktop.
Save kwaters/8059752 to your computer and use it in GitHub Desktop.
/** @file
BootSwitch boot menu application
Copyright (c) 2013, Kenneth Waters
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
**/
#include "BootSwitch.h"
#include <Guid/FileInfo.h>
#include <Library/DevicePathLib.h>
#include <Library/FileHandleLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Protocol/LoadedImage.h>
#include <Protocol/SimpleFileSystem.h>
#include <Protocol/UsbIo.h>
#include <Uefi.h>
/**
Find the first attached BootSwitch device
@param[out] Usb The UsbIo protocol of the first BootSwitch.
@retval EFI_SUCCESS Found device.
**/
STATIC
EFI_STATUS
FirstBootSwitch (
OUT EFI_USB_IO_PROTOCOL **Usb
)
{
EFI_STATUS Status;
EFI_HANDLE *Handles = NULL;
UINTN HandleCount;
UINTN Index;
EFI_USB_IO_PROTOCOL *UsbIo;
EFI_USB_DEVICE_DESCRIPTOR DeviceDescriptor;
Status = gBS->LocateHandleBuffer (
ByProtocol,
&gEfiUsbIoProtocolGuid,
NULL,
&HandleCount,
&Handles
);
if (EFI_ERROR (Status)) {
goto Done;
}
// Iterate through all usb devices looking for a bootswitch pid/vid pair.
for (Index = 0; Index < HandleCount; Index++) {
Status = gBS->OpenProtocol (
Handles[Index],
&gEfiUsbIoProtocolGuid,
(VOID **) &UsbIo,
gImageHandle,
NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (EFI_ERROR (Status)) {
continue;
}
Status = UsbIo->UsbGetDeviceDescriptor (UsbIo, &DeviceDescriptor);
if (EFI_ERROR (Status)) {
continue;
}
if (DeviceDescriptor.IdVendor == BOOTSWITCH_VID &&
DeviceDescriptor.IdProduct == BOOTSWITCH_PID) {
Status = EFI_SUCCESS;
*Usb = UsbIo;
goto Done;
}
}
Status = EFI_NOT_FOUND;
Done:
if (Handles != NULL) {
FreePool (Handles);
}
return Status;
}
/**
Determine the position of the BootSwitch device
@param[in] Usb UsbIo protocol for the boot switch
@param[out] Position The position of the switch
@retval EFI_SUCESS Successfully read position.
**/
STATIC
EFI_STATUS
ReadBootSwitchPosition (
IN EFI_USB_IO_PROTOCOL *Usb,
OUT UINTN *Position
)
{
EFI_STATUS Status;
EFI_USB_DEVICE_REQUEST Request;
UINT32 Data;
UINT32 UsbStatus;
// Current status vendor command
Request.RequestType = 0xC0;
Request.Request = 1;
Request.Value = 0;
Request.Index = 0;
Request.Length = sizeof (Data);
Data = 0;
Status = Usb->UsbControlTransfer (
Usb,
&Request,
EfiUsbDataIn,
BOOTSWITCH_USB_TIMEOUT,
&Data,
sizeof (Data),
&UsbStatus
);
if (EFI_ERROR (Status)) {
return Status;
}
*Position = Data & 1;
return EFI_SUCCESS;
}
/**
Cleanup and remove the last part of a path.
@param[in,out] Path The path to modify in-place.
**/
STATIC
VOID
GetPathBaseDir (
IN OUT CHAR16 *Path
)
{
CHAR16 *Src;
CHAR16 *Dst;
CHAR16 *LastBackslash = NULL;
// Handle the empty path case.
if (*Path == L'\0') {
return;
}
// Start with the second character, that way we can always look back one
// character.
for (Src = Dst = Path + 1; *Src != L'\0'; Src++, Dst++) {
// Replace forward-slash with backslash.
if (*Src == L'/') {
*Dst = L'\\';
} else {
*Dst = *Src;
}
// Keep track of the last backslash.
if (*Dst == L'\\') {
if (Dst[-1] == L'\\') {
// Drop double backslashes.
Dst--;
}
LastBackslash = Dst;
}
}
if (LastBackslash != NULL) {
*LastBackslash = L'\0';
}
}
/**
Read the configuration file for the path to load.
@param[in] Index Which path to read.
@param[out] Path The path read from the configuration file. Must
be freed by the caller.
@retval EFI_SUCCESS Successfully read configuration file.
@retval other Failed to read configuration file.
**/
STATIC
EFI_STATUS
ReadConfiguration (
IN UINTN Index,
OUT EFI_DEVICE_PATH_PROTOCOL **Path
)
{
CONST STATIC CHAR16 ConfigurationFileName[] = L"BootSwitch.conf";
EFI_STATUS Status;
EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
CHAR16 *ImagePath = NULL;
CHAR16 *ConfigurationPath = NULL;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;
EFI_FILE_HANDLE Root = NULL;
EFI_FILE_HANDLE ConfigurationFile = NULL;
UINTN I;
BOOLEAN Ascii;
CHAR16 *Line;
EFI_DEVICE_PATH_PROTOCOL *DevPath;
Status = gBS->HandleProtocol (
gImageHandle,
&gEfiLoadedImageProtocolGuid,
(VOID **) &LoadedImage
);
if (EFI_ERROR (Status)) {
Print (L"ERROR: LoadedImageProtocol on gImageHandle unavailable.\n");
goto Done;
}
ImagePath = ConvertDevicePathToText (LoadedImage->FilePath, FALSE, FALSE);
if (ImagePath == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto Done;
}
GetPathBaseDir (ImagePath);
ConfigurationPath = AllocatePool (
StrLen (ImagePath) + StrLen (ConfigurationFileName) + 2
);
if (ConfigurationPath == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto Done;
}
StrCpy (ConfigurationPath, ImagePath);
StrCat (ConfigurationPath, L"\\");
StrCat (ConfigurationPath, ConfigurationFileName);
Status = gBS->HandleProtocol (
LoadedImage->DeviceHandle,
&gEfiSimpleFileSystemProtocolGuid,
(VOID **) &SimpleFileSystem
);
if (EFI_ERROR (Status)) {
Print (L"ERROR: SimpleFileSystem unavailable.\n");
goto Done;
}
Status = SimpleFileSystem->OpenVolume (SimpleFileSystem, &Root);
if (EFI_ERROR (Status)) {
Print (L"ERROR: OpenVolume failed.\n");
goto Done;
}
Status = Root->Open (
Root,
&ConfigurationFile,
ConfigurationPath,
EFI_FILE_MODE_READ,
0
);
if (EFI_ERROR (Status)) {
Print (L"ERROR: Open of '%s' failed.\n", ConfigurationPath);
goto Done;
}
// Read the |Index| line of the configuration file.
for (I = 0;; I++) {
if (FileHandleEof (ConfigurationFile)) {
Print (L"ERROR: Configuration file ends on line %d.\n", I);
Status = EFI_END_OF_FILE;
goto Done;
}
Line = FileHandleReturnLine (ConfigurationFile, &Ascii);
if (Line == NULL) {
Print (L"ERROR: FileHandleReturnLine failed on line %d.\n", I);
Status = EFI_DEVICE_ERROR;
goto Done;
}
if (I >= Index) {
break;
}
FreePool (Line);
}
DevPath = FileDevicePath (LoadedImage->DeviceHandle, Line);
FreePool (Line);
if (DevPath == NULL) {
Print (L"ERROR: FileDevicePath failed.\n");
Status = EFI_OUT_OF_RESOURCES;
goto Done;
}
*Path = DevPath;
Status = EFI_SUCCESS;
Done:
if (ConfigurationFile != NULL) {
FileHandleClose (ConfigurationFile);
}
if (Root != NULL) {
FileHandleClose (Root);
}
if (ConfigurationPath != NULL) {
FreePool (ConfigurationPath);
}
if (ImagePath != NULL) {
FreePool (ImagePath);
}
return Status;
}
/**
Application entry point
@param[in] ImageHandle Handle for the application image.
@param[in] SystemTable Pointer to the EFI system table.
@retval EFI_SUCCESS Successfully ran target application.
@retval other Failed to start target application.
**/
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHanle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_USB_IO_PROTOCOL *Usb;
UINTN Position = 0;
EFI_DEVICE_PATH_PROTOCOL *TargetPath;
EFI_HANDLE TargetImage;
CHAR16 *Text;
Print (L"BootSwitch\n");
Status = FirstBootSwitch (&Usb);
if (EFI_ERROR (Status)) {
Print (L"WARNING: Switch not found.\n");
} else {
Status = ReadBootSwitchPosition (Usb, &Position);
if (EFI_ERROR (Status)) {
Print (L"WARNING: Unable to read from switch.\n");
}
}
Status = ReadConfiguration (Position, &TargetPath);
if (EFI_ERROR (Status)) {
return Status;
}
Status = gBS->LoadImage (
TRUE,
gImageHandle,
TargetPath,
NULL,
0,
&TargetImage
);
if (EFI_ERROR (Status)) {
Text = ConvertDevicePathToText (TargetPath, TRUE, TRUE);
if (Text != NULL) {
Print (L"ERROR: Cannot load \"%s\"\n", Text);
FreePool (Text);
} else {
Print (L"ERROR: Cannot load target image.\n");
}
}
FreePool (TargetPath);
Status = gBS->StartImage (TargetImage, NULL, NULL);
if (EFI_ERROR (Status)) {
Print (L"ERROR: Cannot run target image.\n");
} else {
Status = EFI_SUCCESS;
}
(VOID) gBS->UnloadImage (TargetImage);
return Status;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment