/** @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.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; // // 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 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 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 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; DEBUG ((EFI_D_VERBOSE, "%a: Request=\"%s\"\n", __FUNCTION__, Request)); Status = PlatformConfigToFormState (&MainFormState); if (EFI_ERROR (Status)) { *Progress = Request; return Status; } // // Answer the textual request keying off the binary form state. // Status = gHiiConfigRouting->BlockToConfig (gHiiConfigRouting, Request, (VOID *) &MainFormState, sizeof MainFormState, Results, Progress); if (EFI_ERROR (Status)) { DEBUG ((EFI_D_ERROR, "%a: BlockToConfig(): %r, Progress=\"%s\"\n", __FUNCTION__, Status, (Status == EFI_DEVICE_ERROR) ? NULL : *Progress)); } else { DEBUG ((EFI_D_VERBOSE, "%a: Results=\"%s\"\n", __FUNCTION__, *Results)); } 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 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 ((EFI_D_VERBOSE, "%a: Configuration=\"%s\"\n", __FUNCTION__, Configuration)); // // 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 ((EFI_D_ERROR, "%a: ConfigToBlock(): %r, Progress=\"%s\"\n", __FUNCTION__, 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 ((EFI_D_VERBOSE, "%a: Action=0x%Lx QuestionId=%d Type=%d\n", __FUNCTION__, (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) ? EFI_D_VERBOSE : EFI_D_ERROR, "%a: failed to load platform config: %r\n", __FUNCTION__, 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); } 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; } // // 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; }