/** @file | |
This driver effectuates OVMF's platform configuration settings and exposes | |
them via HII. | |
Copyright (C) 2014, Red Hat, Inc. | |
Copyright (c) 2009 - 2014, Intel Corporation. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <Library/BaseLib.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/DevicePathLib.h> | |
#include <Library/HiiLib.h> | |
#include <Library/MemoryAllocationLib.h> | |
#include <Library/PrintLib.h> | |
#include <Library/UefiBootServicesTableLib.h> | |
#include <Library/UefiHiiServicesLib.h> | |
#include <Protocol/DevicePath.h> | |
#include <Protocol/GraphicsOutput.h> | |
#include <Protocol/HiiConfigAccess.h> | |
#include <Guid/MdeModuleHii.h> | |
#include <Guid/OvmfPlatformConfig.h> | |
#include "Platform.h" | |
#include "PlatformConfig.h" | |
// | |
// The HiiAddPackages() library function requires that any controller (or | |
// image) handle, to be associated with the HII packages under installation, be | |
// "decorated" with a device path. The tradition seems to be a vendor device | |
// path. | |
// | |
// We'd like to associate our HII packages with the driver's image handle. The | |
// first idea is to use the driver image's device path. Unfortunately, loaded | |
// images only come with an EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL (not the | |
// usual EFI_DEVICE_PATH_PROTOCOL), ie. a different GUID. In addition, even the | |
// EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL interface may be NULL, if the image | |
// has been loaded from an "unnamed" memory source buffer. | |
// | |
// Hence let's just stick with the tradition -- use a dedicated vendor device | |
// path, with the driver's FILE_GUID. | |
// | |
#pragma pack(1) | |
typedef struct { | |
VENDOR_DEVICE_PATH VendorDevicePath; | |
EFI_DEVICE_PATH_PROTOCOL End; | |
} PKG_DEVICE_PATH; | |
#pragma pack() | |
STATIC PKG_DEVICE_PATH mPkgDevicePath = { | |
{ | |
{ | |
HARDWARE_DEVICE_PATH, | |
HW_VENDOR_DP, | |
{ | |
(UINT8)(sizeof (VENDOR_DEVICE_PATH)), | |
(UINT8)(sizeof (VENDOR_DEVICE_PATH) >> 8) | |
} | |
}, | |
EFI_CALLER_ID_GUID | |
}, | |
{ | |
END_DEVICE_PATH_TYPE, | |
END_ENTIRE_DEVICE_PATH_SUBTYPE, | |
{ | |
(UINT8)(END_DEVICE_PATH_LENGTH), | |
(UINT8)(END_DEVICE_PATH_LENGTH >> 8) | |
} | |
} | |
}; | |
// | |
// The configuration interface between the HII engine (form display etc) and | |
// this driver. | |
// | |
STATIC EFI_HII_CONFIG_ACCESS_PROTOCOL mConfigAccess; | |
// | |
// The handle representing our list of packages after installation. | |
// | |
STATIC EFI_HII_HANDLE mInstalledPackages; | |
// | |
// The arrays below constitute our HII package list. They are auto-generated by | |
// the VFR compiler and linked into the driver image during the build. | |
// | |
// - The strings package receives its C identifier from the driver's BASE_NAME, | |
// plus "Strings". | |
// | |
// - The forms package receives its C identifier from the VFR file's basename, | |
// plus "Bin". | |
// | |
// | |
extern UINT8 PlatformDxeStrings[]; | |
extern UINT8 PlatformFormsBin[]; | |
// | |
// We want to be notified about GOP installations until we find one GOP | |
// interface that lets us populate the form. | |
// | |
STATIC EFI_EVENT mGopEvent; | |
// | |
// The registration record underneath this pointer allows us to iterate through | |
// the GOP instances one by one. | |
// | |
STATIC VOID *mGopTracker; | |
// | |
// The driver image handle, used to obtain the device path for <ConfigHdr>. | |
// | |
STATIC EFI_HANDLE mImageHandle; | |
// | |
// Cache the resolutions we get from the GOP. | |
// | |
typedef struct { | |
UINT32 X; | |
UINT32 Y; | |
} GOP_MODE; | |
STATIC UINTN mNumGopModes; | |
STATIC GOP_MODE *mGopModes; | |
/** | |
Load the persistent platform configuration and translate it to binary form | |
state. | |
If the platform configuration is missing, then the function fills in a | |
default state. | |
@param[out] MainFormState Binary form/widget state after translation. | |
@retval EFI_SUCCESS Form/widget state ready. | |
@return Error codes from underlying functions. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
PlatformConfigToFormState ( | |
OUT MAIN_FORM_STATE *MainFormState | |
) | |
{ | |
EFI_STATUS Status; | |
PLATFORM_CONFIG PlatformConfig; | |
UINT64 OptionalElements; | |
UINTN ModeNumber; | |
ZeroMem (MainFormState, sizeof *MainFormState); | |
Status = PlatformConfigLoad (&PlatformConfig, &OptionalElements); | |
switch (Status) { | |
case EFI_SUCCESS: | |
if (OptionalElements & PLATFORM_CONFIG_F_GRAPHICS_RESOLUTION) { | |
// | |
// Format the preferred resolution as text. | |
// | |
UnicodeSPrintAsciiFormat ( | |
(CHAR16 *)MainFormState->CurrentPreferredResolution, | |
sizeof MainFormState->CurrentPreferredResolution, | |
"%Ldx%Ld", | |
(INT64)PlatformConfig.HorizontalResolution, | |
(INT64)PlatformConfig.VerticalResolution | |
); | |
// | |
// Try to locate it in the drop-down list too. This may not succeed, but | |
// that's fine. | |
// | |
for (ModeNumber = 0; ModeNumber < mNumGopModes; ++ModeNumber) { | |
if ((mGopModes[ModeNumber].X == PlatformConfig.HorizontalResolution) && | |
(mGopModes[ModeNumber].Y == PlatformConfig.VerticalResolution)) | |
{ | |
MainFormState->NextPreferredResolution = (UINT32)ModeNumber; | |
break; | |
} | |
} | |
break; | |
} | |
// | |
// fall through otherwise | |
// | |
case EFI_NOT_FOUND: | |
UnicodeSPrintAsciiFormat ( | |
(CHAR16 *)MainFormState->CurrentPreferredResolution, | |
sizeof MainFormState->CurrentPreferredResolution, | |
"Unset" | |
); | |
break; | |
default: | |
return Status; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
This function is called by the HII machinery when it fetches the form state. | |
See the precise documentation in the UEFI spec. | |
@param[in] This The Config Access Protocol instance. | |
@param[in] Request A <ConfigRequest> format UCS-2 string describing the | |
query. | |
@param[out] Progress A pointer into Request on output, identifying the query | |
element where processing failed. | |
@param[out] Results A <MultiConfigAltResp> format UCS-2 string that has | |
all values filled in for the names in the Request | |
string. | |
@retval EFI_SUCCESS Extraction of form state in <MultiConfigAltResp> | |
encoding successful. | |
@return Status codes from underlying functions. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
ExtractConfig ( | |
IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This, | |
IN CONST EFI_STRING Request, | |
OUT EFI_STRING *Progress, | |
OUT EFI_STRING *Results | |
) | |
{ | |
MAIN_FORM_STATE MainFormState; | |
EFI_STATUS Status; | |
EFI_STRING ConfigRequestHdr; | |
EFI_STRING ConfigRequest; | |
UINTN Size; | |
BOOLEAN AllocatedRequest; | |
DEBUG ((DEBUG_VERBOSE, "%a: Request=\"%s\"\n", __func__, Request)); | |
if ((Progress == NULL) || (Results == NULL)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
ConfigRequestHdr = NULL; | |
ConfigRequest = NULL; | |
Size = 0; | |
AllocatedRequest = FALSE; | |
// | |
// Check if <ConfigHdr> matches the GUID and name | |
// | |
*Progress = Request; | |
if ((Request != NULL) && | |
!HiiIsConfigHdrMatch ( | |
Request, | |
&gOvmfPlatformConfigGuid, | |
mHiiFormName | |
) | |
) | |
{ | |
return EFI_NOT_FOUND; | |
} | |
Status = PlatformConfigToFormState (&MainFormState); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if ((Request == NULL) || (StrStr (Request, L"OFFSET") == NULL)) { | |
// | |
// Request has no <RequestElement>, so construct full request string. | |
// Allocate and fill a buffer large enough to hold <ConfigHdr> | |
// followed by "&OFFSET=0&WIDTH=WWWWWWWWWWWWWWWW" followed by a | |
// null terminator. | |
// | |
ConfigRequestHdr = HiiConstructConfigHdr ( | |
&gOvmfPlatformConfigGuid, | |
mHiiFormName, | |
mImageHandle | |
); | |
if (ConfigRequestHdr == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Size = (StrLen (ConfigRequestHdr) + 32 + 1) * sizeof (CHAR16); | |
ConfigRequest = AllocateZeroPool (Size); | |
AllocatedRequest = TRUE; | |
if (ConfigRequest == NULL) { | |
FreePool (ConfigRequestHdr); | |
return EFI_OUT_OF_RESOURCES; | |
} | |
UnicodeSPrint ( | |
ConfigRequest, | |
Size, | |
L"%s&OFFSET=0&WIDTH=%016LX", | |
ConfigRequestHdr, | |
sizeof MainFormState | |
); | |
FreePool (ConfigRequestHdr); | |
} else { | |
ConfigRequest = Request; | |
} | |
// | |
// Answer the textual request keying off the binary form state. | |
// | |
Status = gHiiConfigRouting->BlockToConfig ( | |
gHiiConfigRouting, | |
ConfigRequest, | |
(VOID *)&MainFormState, | |
sizeof MainFormState, | |
Results, | |
Progress | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"%a: BlockToConfig(): %r, Progress=\"%s\"\n", | |
__func__, | |
Status, | |
(Status == EFI_DEVICE_ERROR) ? NULL : *Progress | |
)); | |
} else { | |
DEBUG ((DEBUG_VERBOSE, "%a: Results=\"%s\"\n", __func__, *Results)); | |
} | |
// | |
// If we used a newly allocated ConfigRequest, update Progress to point to | |
// original Request instead of ConfigRequest. | |
// | |
if (Request == NULL) { | |
*Progress = NULL; | |
} else if (StrStr (Request, L"OFFSET") == NULL) { | |
if (EFI_ERROR (Status)) { | |
// | |
// Since we constructed ConfigRequest, failure can only occur if there | |
// is not enough memory. In this case, we point Progress to the first | |
// character of Request. | |
// | |
*Progress = Request; | |
} else { | |
// | |
// In case of success, we point Progress to the null terminator of | |
// Request. | |
// | |
*Progress = Request + StrLen (Request); | |
} | |
} | |
if (AllocatedRequest) { | |
FreePool (ConfigRequest); | |
} | |
return Status; | |
} | |
/** | |
Interpret the binary form state and save it as persistent platform | |
configuration. | |
@param[in] MainFormState Binary form/widget state to verify and save. | |
@retval EFI_SUCCESS Platform configuration saved. | |
@return Error codes from underlying functions. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
FormStateToPlatformConfig ( | |
IN CONST MAIN_FORM_STATE *MainFormState | |
) | |
{ | |
EFI_STATUS Status; | |
PLATFORM_CONFIG PlatformConfig; | |
CONST GOP_MODE *GopMode; | |
// | |
// There's nothing to do with the textual CurrentPreferredResolution field. | |
// We verify and translate the selection in the drop-down list. | |
// | |
if (MainFormState->NextPreferredResolution >= mNumGopModes) { | |
return EFI_INVALID_PARAMETER; | |
} | |
GopMode = mGopModes + MainFormState->NextPreferredResolution; | |
ZeroMem (&PlatformConfig, sizeof PlatformConfig); | |
PlatformConfig.HorizontalResolution = GopMode->X; | |
PlatformConfig.VerticalResolution = GopMode->Y; | |
Status = PlatformConfigSave (&PlatformConfig); | |
return Status; | |
} | |
/** | |
This function is called by the HII machinery when it wants the driver to | |
interpret and persist the form state. | |
See the precise documentation in the UEFI spec. | |
@param[in] This The Config Access Protocol instance. | |
@param[in] Configuration A <ConfigResp> format UCS-2 string describing the | |
form state. | |
@param[out] Progress A pointer into Configuration on output, | |
identifying the element where processing failed. | |
@retval EFI_SUCCESS Configuration verified, state permanent. | |
@return Status codes from underlying functions. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
RouteConfig ( | |
IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This, | |
IN CONST EFI_STRING Configuration, | |
OUT EFI_STRING *Progress | |
) | |
{ | |
MAIN_FORM_STATE MainFormState; | |
UINTN BlockSize; | |
EFI_STATUS Status; | |
DEBUG (( | |
DEBUG_VERBOSE, | |
"%a: Configuration=\"%s\"\n", | |
__func__, | |
Configuration | |
)); | |
if ((Progress == NULL) || (Configuration == NULL)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Check if <ConfigHdr> matches the GUID and name | |
// | |
*Progress = Configuration; | |
if ((Configuration != NULL) && | |
!HiiIsConfigHdrMatch ( | |
Configuration, | |
&gOvmfPlatformConfigGuid, | |
mHiiFormName | |
) | |
) | |
{ | |
return EFI_NOT_FOUND; | |
} | |
// | |
// the "read" step in RMW | |
// | |
Status = PlatformConfigToFormState (&MainFormState); | |
if (EFI_ERROR (Status)) { | |
*Progress = Configuration; | |
return Status; | |
} | |
// | |
// the "modify" step in RMW | |
// | |
// (Update the binary form state. This update may be partial, which is why in | |
// general we must pre-load the form state from the platform config.) | |
// | |
BlockSize = sizeof MainFormState; | |
Status = gHiiConfigRouting->ConfigToBlock ( | |
gHiiConfigRouting, | |
Configuration, | |
(VOID *)&MainFormState, | |
&BlockSize, | |
Progress | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"%a: ConfigToBlock(): %r, Progress=\"%s\"\n", | |
__func__, | |
Status, | |
(Status == EFI_BUFFER_TOO_SMALL) ? NULL : *Progress | |
)); | |
return Status; | |
} | |
// | |
// the "write" step in RMW | |
// | |
Status = FormStateToPlatformConfig (&MainFormState); | |
if (EFI_ERROR (Status)) { | |
*Progress = Configuration; | |
} | |
return Status; | |
} | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
Callback ( | |
IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This, | |
IN EFI_BROWSER_ACTION Action, | |
IN EFI_QUESTION_ID QuestionId, | |
IN UINT8 Type, | |
IN OUT EFI_IFR_TYPE_VALUE *Value, | |
OUT EFI_BROWSER_ACTION_REQUEST *ActionRequest | |
) | |
{ | |
DEBUG (( | |
DEBUG_VERBOSE, | |
"%a: Action=0x%Lx QuestionId=%d Type=%d\n", | |
__func__, | |
(UINT64)Action, | |
QuestionId, | |
Type | |
)); | |
if (Action != EFI_BROWSER_ACTION_CHANGED) { | |
return EFI_UNSUPPORTED; | |
} | |
switch (QuestionId) { | |
case QUESTION_SAVE_EXIT: | |
*ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_SUBMIT_EXIT; | |
break; | |
case QUESTION_DISCARD_EXIT: | |
*ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_DISCARD_EXIT; | |
break; | |
default: | |
break; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Query and save all resolutions supported by the GOP. | |
@param[in] Gop The Graphics Output Protocol instance to query. | |
@param[out] NumGopModes The number of modes supported by the GOP. On output, | |
this parameter will be positive. | |
@param[out] GopModes On output, a dynamically allocated array containing | |
the resolutions returned by the GOP. The caller is | |
responsible for freeing the array after use. | |
@retval EFI_UNSUPPORTED No modes found. | |
@retval EFI_OUT_OF_RESOURCES Failed to allocate GopModes. | |
@return Error codes from Gop->QueryMode(). | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
QueryGopModes ( | |
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop, | |
OUT UINTN *NumGopModes, | |
OUT GOP_MODE **GopModes | |
) | |
{ | |
EFI_STATUS Status; | |
UINT32 ModeNumber; | |
if (Gop->Mode->MaxMode == 0) { | |
return EFI_UNSUPPORTED; | |
} | |
*NumGopModes = Gop->Mode->MaxMode; | |
*GopModes = AllocatePool (Gop->Mode->MaxMode * sizeof **GopModes); | |
if (*GopModes == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
for (ModeNumber = 0; ModeNumber < Gop->Mode->MaxMode; ++ModeNumber) { | |
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info; | |
UINTN SizeOfInfo; | |
Status = Gop->QueryMode (Gop, ModeNumber, &SizeOfInfo, &Info); | |
if (EFI_ERROR (Status)) { | |
goto FreeGopModes; | |
} | |
(*GopModes)[ModeNumber].X = Info->HorizontalResolution; | |
(*GopModes)[ModeNumber].Y = Info->VerticalResolution; | |
FreePool (Info); | |
} | |
return EFI_SUCCESS; | |
FreeGopModes: | |
FreePool (*GopModes); | |
return Status; | |
} | |
/** | |
Create a set of "one-of-many" (ie. "drop down list") option IFR opcodes, | |
based on available GOP resolutions, to be placed under a "one-of-many" (ie. | |
"drop down list") opcode. | |
@param[in] PackageList The package list with the formset and form for | |
which the drop down options are produced. Option | |
names are added as new strings to PackageList. | |
@param[out] OpCodeBuffer On output, a dynamically allocated opcode buffer | |
with drop down list options corresponding to GOP | |
resolutions. The caller is responsible for freeing | |
OpCodeBuffer with HiiFreeOpCodeHandle() after use. | |
@param[in] NumGopModes Number of entries in GopModes. | |
@param[in] GopModes Array of resolutions retrieved from the GOP. | |
@retval EFI_SUCESS Opcodes have been successfully produced. | |
@return Status codes from underlying functions. PackageList may | |
have been extended with new strings. OpCodeBuffer is | |
unchanged. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
CreateResolutionOptions ( | |
IN EFI_HII_HANDLE PackageList, | |
OUT VOID **OpCodeBuffer, | |
IN UINTN NumGopModes, | |
IN GOP_MODE *GopModes | |
) | |
{ | |
EFI_STATUS Status; | |
VOID *OutputBuffer; | |
UINTN ModeNumber; | |
OutputBuffer = HiiAllocateOpCodeHandle (); | |
if (OutputBuffer == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
for (ModeNumber = 0; ModeNumber < NumGopModes; ++ModeNumber) { | |
CHAR16 Desc[MAXSIZE_RES_CUR]; | |
EFI_STRING_ID NewString; | |
VOID *OpCode; | |
UnicodeSPrintAsciiFormat ( | |
Desc, | |
sizeof Desc, | |
"%Ldx%Ld", | |
(INT64)GopModes[ModeNumber].X, | |
(INT64)GopModes[ModeNumber].Y | |
); | |
NewString = HiiSetString ( | |
PackageList, | |
0 /* new string */, | |
Desc, | |
NULL /* for all languages */ | |
); | |
if (NewString == 0) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto FreeOutputBuffer; | |
} | |
OpCode = HiiCreateOneOfOptionOpCode ( | |
OutputBuffer, | |
NewString, | |
0 /* Flags */, | |
EFI_IFR_NUMERIC_SIZE_4, | |
ModeNumber | |
); | |
if (OpCode == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto FreeOutputBuffer; | |
} | |
} | |
*OpCodeBuffer = OutputBuffer; | |
return EFI_SUCCESS; | |
FreeOutputBuffer: | |
HiiFreeOpCodeHandle (OutputBuffer); | |
return Status; | |
} | |
/** | |
Populate the form identified by the (PackageList, FormSetGuid, FormId) | |
triplet. | |
The drop down list of video resolutions is generated from (NumGopModes, | |
GopModes). | |
@retval EFI_SUCESS Form successfully updated. | |
@return Status codes from underlying functions. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
PopulateForm ( | |
IN EFI_HII_HANDLE PackageList, | |
IN EFI_GUID *FormSetGuid, | |
IN EFI_FORM_ID FormId, | |
IN UINTN NumGopModes, | |
IN GOP_MODE *GopModes | |
) | |
{ | |
EFI_STATUS Status; | |
VOID *OpCodeBuffer; | |
VOID *OpCode; | |
EFI_IFR_GUID_LABEL *Anchor; | |
VOID *OpCodeBuffer2; | |
OpCodeBuffer2 = NULL; | |
// | |
// 1. Allocate an empty opcode buffer. | |
// | |
OpCodeBuffer = HiiAllocateOpCodeHandle (); | |
if (OpCodeBuffer == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
// | |
// 2. Create a label opcode (which is a Tiano extension) inside the buffer. | |
// The label's number must match the "anchor" label in the form. | |
// | |
OpCode = HiiCreateGuidOpCode ( | |
OpCodeBuffer, | |
&gEfiIfrTianoGuid, | |
NULL /* optional copy origin */, | |
sizeof *Anchor | |
); | |
if (OpCode == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto FreeOpCodeBuffer; | |
} | |
Anchor = OpCode; | |
Anchor->ExtendOpCode = EFI_IFR_EXTEND_OP_LABEL; | |
Anchor->Number = LABEL_RES_NEXT; | |
// | |
// 3. Create the opcodes inside the buffer that are to be inserted into the | |
// form. | |
// | |
// 3.1. Get a list of resolutions. | |
// | |
Status = CreateResolutionOptions ( | |
PackageList, | |
&OpCodeBuffer2, | |
NumGopModes, | |
GopModes | |
); | |
if (EFI_ERROR (Status)) { | |
goto FreeOpCodeBuffer; | |
} | |
// | |
// 3.2. Create a one-of-many question with the above options. | |
// | |
OpCode = HiiCreateOneOfOpCode ( | |
OpCodeBuffer, // create opcode inside this | |
// opcode buffer, | |
QUESTION_RES_NEXT, // ID of question, | |
FORMSTATEID_MAIN_FORM, // identifies form state | |
// storage, | |
(UINT16)OFFSET_OF ( | |
MAIN_FORM_STATE, // value of question stored | |
NextPreferredResolution | |
), // at this offset, | |
STRING_TOKEN (STR_RES_NEXT), // Prompt, | |
STRING_TOKEN (STR_RES_NEXT_HELP), // Help, | |
0, // QuestionFlags, | |
EFI_IFR_NUMERIC_SIZE_4, // see sizeof | |
// NextPreferredResolution, | |
OpCodeBuffer2, // buffer with possible | |
// choices, | |
NULL // DEFAULT opcodes | |
); | |
if (OpCode == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto FreeOpCodeBuffer2; | |
} | |
// | |
// 4. Update the form with the opcode buffer. | |
// | |
Status = HiiUpdateForm ( | |
PackageList, | |
FormSetGuid, | |
FormId, | |
OpCodeBuffer, // buffer with head anchor, and new contents to be | |
// inserted at it | |
NULL // buffer with tail anchor, for deleting old | |
// contents up to it | |
); | |
FreeOpCodeBuffer2: | |
HiiFreeOpCodeHandle (OpCodeBuffer2); | |
FreeOpCodeBuffer: | |
HiiFreeOpCodeHandle (OpCodeBuffer); | |
return Status; | |
} | |
/** | |
Load and execute the platform configuration. | |
@retval EFI_SUCCESS Configuration loaded and executed. | |
@return Status codes from PlatformConfigLoad(). | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
ExecutePlatformConfig ( | |
VOID | |
) | |
{ | |
EFI_STATUS Status; | |
PLATFORM_CONFIG PlatformConfig; | |
UINT64 OptionalElements; | |
RETURN_STATUS PcdStatus; | |
Status = PlatformConfigLoad (&PlatformConfig, &OptionalElements); | |
if (EFI_ERROR (Status)) { | |
DEBUG (( | |
(Status == EFI_NOT_FOUND) ? DEBUG_VERBOSE : DEBUG_ERROR, | |
"%a: failed to load platform config: %r\n", | |
__func__, | |
Status | |
)); | |
return Status; | |
} | |
if (OptionalElements & PLATFORM_CONFIG_F_GRAPHICS_RESOLUTION) { | |
// | |
// Pass the preferred resolution to GraphicsConsoleDxe via dynamic PCDs. | |
// | |
PcdStatus = PcdSet32S ( | |
PcdVideoHorizontalResolution, | |
PlatformConfig.HorizontalResolution | |
); | |
ASSERT_RETURN_ERROR (PcdStatus); | |
PcdStatus = PcdSet32S ( | |
PcdVideoVerticalResolution, | |
PlatformConfig.VerticalResolution | |
); | |
ASSERT_RETURN_ERROR (PcdStatus); | |
PcdStatus = PcdSet8S (PcdVideoResolutionSource, 1); | |
ASSERT_RETURN_ERROR (PcdStatus); | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Notification callback for GOP interface installation. | |
@param[in] Event Event whose notification function is being invoked. | |
@param[in] Context The pointer to the notification function's context, which | |
is implementation-dependent. | |
**/ | |
STATIC | |
VOID | |
EFIAPI | |
GopInstalled ( | |
IN EFI_EVENT Event, | |
IN VOID *Context | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop; | |
ASSERT (Event == mGopEvent); | |
// | |
// Check further GOPs. | |
// | |
for ( ; ;) { | |
mNumGopModes = 0; | |
mGopModes = NULL; | |
Status = gBS->LocateProtocol ( | |
&gEfiGraphicsOutputProtocolGuid, | |
mGopTracker, | |
(VOID **)&Gop | |
); | |
if (EFI_ERROR (Status)) { | |
return; | |
} | |
Status = QueryGopModes (Gop, &mNumGopModes, &mGopModes); | |
if (EFI_ERROR (Status)) { | |
continue; | |
} | |
Status = PopulateForm ( | |
mInstalledPackages, | |
&gOvmfPlatformConfigGuid, | |
FORMID_MAIN_FORM, | |
mNumGopModes, | |
mGopModes | |
); | |
if (EFI_ERROR (Status)) { | |
FreePool (mGopModes); | |
continue; | |
} | |
break; | |
} | |
// | |
// Success -- so uninstall this callback. Closing the event removes all | |
// pending notifications and all protocol registrations. | |
// | |
Status = gBS->CloseEvent (mGopEvent); | |
ASSERT_EFI_ERROR (Status); | |
mGopEvent = NULL; | |
mGopTracker = NULL; | |
} | |
/** | |
Entry point for this driver. | |
@param[in] ImageHandle Image handle of this driver. | |
@param[in] SystemTable Pointer to SystemTable. | |
@retval EFI_SUCESS Driver has loaded successfully. | |
@retval EFI_OUT_OF_RESOURCES Failed to install HII packages. | |
@return Error codes from lower level functions. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
PlatformInit ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
EFI_STATUS Status; | |
ExecutePlatformConfig (); | |
mConfigAccess.ExtractConfig = &ExtractConfig; | |
mConfigAccess.RouteConfig = &RouteConfig; | |
mConfigAccess.Callback = &Callback; | |
// | |
// Declare ourselves suitable for HII communication. | |
// | |
Status = gBS->InstallMultipleProtocolInterfaces ( | |
&ImageHandle, | |
&gEfiDevicePathProtocolGuid, | |
&mPkgDevicePath, | |
&gEfiHiiConfigAccessProtocolGuid, | |
&mConfigAccess, | |
NULL | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// Save the driver image handle. | |
// | |
mImageHandle = ImageHandle; | |
// | |
// Publish the HII package list to HII Database. | |
// | |
mInstalledPackages = HiiAddPackages ( | |
&gEfiCallerIdGuid, // PackageListGuid | |
ImageHandle, // associated DeviceHandle | |
PlatformDxeStrings, // 1st package | |
PlatformFormsBin, // 2nd package | |
NULL // terminator | |
); | |
if (mInstalledPackages == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto UninstallProtocols; | |
} | |
Status = gBS->CreateEvent ( | |
EVT_NOTIFY_SIGNAL, | |
TPL_CALLBACK, | |
&GopInstalled, | |
NULL /* Context */, | |
&mGopEvent | |
); | |
if (EFI_ERROR (Status)) { | |
goto RemovePackages; | |
} | |
Status = gBS->RegisterProtocolNotify ( | |
&gEfiGraphicsOutputProtocolGuid, | |
mGopEvent, | |
&mGopTracker | |
); | |
if (EFI_ERROR (Status)) { | |
goto CloseGopEvent; | |
} | |
// | |
// Check already installed GOPs. | |
// | |
Status = gBS->SignalEvent (mGopEvent); | |
ASSERT_EFI_ERROR (Status); | |
return EFI_SUCCESS; | |
CloseGopEvent: | |
gBS->CloseEvent (mGopEvent); | |
RemovePackages: | |
HiiRemovePackages (mInstalledPackages); | |
UninstallProtocols: | |
gBS->UninstallMultipleProtocolInterfaces ( | |
ImageHandle, | |
&gEfiDevicePathProtocolGuid, | |
&mPkgDevicePath, | |
&gEfiHiiConfigAccessProtocolGuid, | |
&mConfigAccess, | |
NULL | |
); | |
return Status; | |
} | |
/** | |
Unload the driver. | |
@param[in] ImageHandle Handle that identifies the image to evict. | |
@retval EFI_SUCCESS The image has been unloaded. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
PlatformUnload ( | |
IN EFI_HANDLE ImageHandle | |
) | |
{ | |
if (mGopEvent == NULL) { | |
// | |
// The GOP callback ran successfully and unregistered itself. Release the | |
// resources allocated there. | |
// | |
ASSERT (mGopModes != NULL); | |
FreePool (mGopModes); | |
} else { | |
// | |
// Otherwise we need to unregister the callback. | |
// | |
ASSERT (mGopModes == NULL); | |
gBS->CloseEvent (mGopEvent); | |
} | |
// | |
// Release resources allocated by the entry point. | |
// | |
HiiRemovePackages (mInstalledPackages); | |
gBS->UninstallMultipleProtocolInterfaces ( | |
ImageHandle, | |
&gEfiDevicePathProtocolGuid, | |
&mPkgDevicePath, | |
&gEfiHiiConfigAccessProtocolGuid, | |
&mConfigAccess, | |
NULL | |
); | |
return EFI_SUCCESS; | |
} |