--- /dev/null
+/**@file\r
+ This file contains the form processing code to the HII database.\r
+\r
+Copyright (c) 2006 - 2008 Intel Corporation. <BR>\r
+All rights reserved. This program and the accompanying materials\r
+are licensed and made available under the terms and conditions of the BSD License\r
+which accompanies this distribution. The full text of the license may be found at\r
+http://opensource.org/licenses/bsd-license.php\r
+\r
+THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,\r
+WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
+\r
+**/\r
+\r
+\r
+#include "HiiDatabase.h"\r
+#include "UefiIfrDefault.h"\r
+\r
+//\r
+// This structure is only intended to be used in this file.\r
+//\r
+#pragma pack(1)\r
+typedef struct {\r
+ EFI_HII_PACK_HEADER PackageHeader;\r
+ FRAMEWORK_EFI_IFR_FORM_SET FormSet;\r
+ FRAMEWORK_EFI_IFR_END_FORM_SET EndFormSet;\r
+} FW_HII_FORMSET_TEMPLATE;\r
+#pragma pack()\r
+\r
+FW_HII_FORMSET_TEMPLATE FormSetTemplate = {\r
+ {\r
+ sizeof (FW_HII_FORMSET_TEMPLATE),\r
+ EFI_HII_IFR\r
+ },\r
+ {\r
+ {\r
+ FRAMEWORK_EFI_IFR_FORM_SET_OP,\r
+ sizeof (FRAMEWORK_EFI_IFR_FORM_SET)\r
+ },\r
+ {0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}}, //Guid\r
+ 0,\r
+ 0,\r
+ 0,\r
+ 0,\r
+ 0,\r
+ 0\r
+ },\r
+ {\r
+ {\r
+ FRAMEWORK_EFI_IFR_END_FORM_SET_OP,\r
+ sizeof (FRAMEWORK_EFI_IFR_END_FORM_SET)\r
+ }\r
+ }\r
+};\r
+\r
+\r
+EFI_GUID mTianoHiiIfrGuid = EFI_IFR_TIANO_GUID;\r
+\r
+/**\r
+\r
+ This thunk module only handles UEFI HII packages. The caller of this function \r
+ won¡¯t be able to parse the content. Therefore, it is not supported.\r
+ \r
+ This function will ASSERT and return EFI_UNSUPPORTED.\r
+\r
+ @param This N.A.\r
+ @param Handle N.A.\r
+ @param BufferSize N.A.\r
+ @param Buffer N.A.\r
+\r
+ @retval EFI_UNSUPPORTED\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+HiiExportDatabase (\r
+ IN EFI_HII_PROTOCOL *This,\r
+ IN FRAMEWORK_EFI_HII_HANDLE Handle,\r
+ IN OUT UINTN *BufferSize,\r
+ OUT VOID *Buffer\r
+ )\r
+{\r
+ ASSERT (FALSE);\r
+ return EFI_UNSUPPORTED;\r
+}\r
+\r
+\r
+/**\r
+ This function allows a program to extract a form or form package that has\r
+ previously been registered with the EFI HII database.\r
+\r
+ In this thunk module, this function will create a IFR Package with only \r
+ one Formset. Effectively, only the GUID of the Formset is updated and return\r
+ in this IFR package to caller. This is enable the Framework modules which call \r
+ a API named GetStringFromToken. GetStringFromToken retieves a String based on\r
+ a String Token from a Package List known only by the Formset GUID.\r
+ \r
+\r
+\r
+ @param This A pointer to the EFI_HII_PROTOCOL instance.\r
+ @param Handle Handle on which the form resides. Type FRAMEWORK_EFI_HII_HANDLE is defined in\r
+ EFI_HII_PROTOCOL.NewPack() in the Packages section.\r
+ @param FormId Ignored by this implementation.\r
+ @param BufferLengthTemp On input, the size of input buffer. On output, it\r
+ is the size of FW_HII_FORMSET_TEMPLATE.\r
+ @param Buffer The buffer designed to receive the form(s).\r
+\r
+ @retval EFI_SUCCESS Buffer filled with the requested forms. BufferLength\r
+ was updated.\r
+ @retval EFI_INVALID_PARAMETER The handle is unknown.\r
+ @retval EFI_NOT_FOUND A form on the requested handle cannot be found with the\r
+ requested FormId.\r
+ @retval EFI_BUFFER_TOO_SMALL The buffer provided was not large enough to allow the form to be stored.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+HiiGetForms (\r
+ IN EFI_HII_PROTOCOL *This,\r
+ IN FRAMEWORK_EFI_HII_HANDLE Handle,\r
+ IN EFI_FORM_ID FormId,\r
+ IN OUT UINTN *BufferLengthTemp,\r
+ OUT UINT8 *Buffer\r
+ )\r
+{\r
+ HII_THUNK_PRIVATE_DATA *Private;\r
+ HII_THUNK_CONTEXT *ThunkContext;\r
+ FW_HII_FORMSET_TEMPLATE *OutputFormSet;\r
+\r
+ if (*BufferLengthTemp < sizeof(FW_HII_FORMSET_TEMPLATE)) {\r
+ *BufferLengthTemp = sizeof(FW_HII_FORMSET_TEMPLATE);\r
+ return EFI_BUFFER_TOO_SMALL;\r
+ }\r
+ \r
+ Private = HII_THUNK_PRIVATE_DATA_FROM_THIS(This);\r
+\r
+ ThunkContext = FwHiiHandleToThunkContext (Private, Handle);\r
+\r
+ if (ThunkContext == NULL) {\r
+ return EFI_NOT_FOUND;\r
+ }\r
+\r
+ OutputFormSet = (FW_HII_FORMSET_TEMPLATE *) Buffer;\r
+ \r
+ CopyMem (OutputFormSet, &FormSetTemplate, sizeof (FW_HII_FORMSET_TEMPLATE));\r
+ CopyMem (&OutputFormSet->FormSet.Guid, &ThunkContext->TagGuid, sizeof (EFI_GUID)); \r
+\r
+ if (ThunkContext->FormSet != NULL) {\r
+ OutputFormSet->FormSet.Class = ThunkContext->FormSet->Class;\r
+ OutputFormSet->FormSet.SubClass = ThunkContext->FormSet->SubClass;\r
+ OutputFormSet->FormSet.Help = ThunkContext->FormSet->Help;\r
+ OutputFormSet->FormSet.FormSetTitle = ThunkContext->FormSet->FormSetTitle;\r
+ }\r
+\r
+ return EFI_SUCCESS;\r
+}\r
+\r
+\r
+/**\r
+\r
+ This function allows a program to extract the NV Image\r
+ that represents the default storage image\r
+\r
+\r
+ @param This A pointer to the EFI_HII_PROTOCOL instance.\r
+ @param Handle The HII handle from which will have default data retrieved.\r
+ UINTN - Mask used to retrieve the default image.\r
+ @param DefaultMask EDES_TODO: Add parameter description\r
+ @param VariablePackList Callee allocated, tightly-packed, link list data\r
+ structure that contain all default varaible packs\r
+ from the Hii Database.\r
+\r
+ @retval EFI_NOT_FOUND If Hii database does not contain any default images.\r
+ @retval EFI_INVALID_PARAMETER Invalid input parameter.\r
+ @retval EFI_SUCCESS Operation successful.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+HiiGetDefaultImage (\r
+ IN EFI_HII_PROTOCOL *This,\r
+ IN FRAMEWORK_EFI_HII_HANDLE Handle,\r
+ IN UINTN DefaultMask,\r
+ OUT EFI_HII_VARIABLE_PACK_LIST **VariablePackList\r
+ )\r
+{\r
+ LIST_ENTRY *UefiDefaults;\r
+ EFI_STATUS Status;\r
+ HII_THUNK_PRIVATE_DATA *Private;\r
+ HII_THUNK_CONTEXT *ThunkContext;\r
+\r
+ Private = HII_THUNK_PRIVATE_DATA_FROM_THIS(This);\r
+\r
+ ThunkContext = FwHiiHandleToThunkContext (Private, Handle);\r
+ if (ThunkContext == NULL) {\r
+ ASSERT (FALSE);\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+\r
+ UefiDefaults = NULL;\r
+ Status = UefiIfrGetBufferTypeDefaults (ThunkContext, &UefiDefaults);\r
+ if (EFI_ERROR (Status)) {\r
+ goto Done;\r
+ }\r
+\r
+ Status = UefiDefaultsToFwDefaults (UefiDefaults, DefaultMask, ThunkContext->FormSet->DefaultVarStoreId, VariablePackList);\r
+\r
+Done:\r
+ FreeDefaultList (UefiDefaults);\r
+ \r
+ return Status;\r
+}\r
+\r
+/**\r
+ This function update the FormCallbackProtocol cached in Config Access\r
+ private context data.\r
+\r
+ @param CallbackHandle The EFI Handle on which the Framework FormCallbackProtocol is \r
+ installed.\r
+ @param ThunkContext The Thunk Context.\r
+\r
+ @retval EFI_SUCCESS The update is successful.\r
+ @retval EFI_INVALID_PARAMETER If no Framework FormCallbackProtocol is located on CallbackHandle.\r
+\r
+**/\r
+EFI_STATUS\r
+UpdateFormCallBack (\r
+ IN EFI_HANDLE CallbackHandle,\r
+ IN CONST HII_THUNK_CONTEXT *ThunkContext\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ EFI_FORM_CALLBACK_PROTOCOL *FormCallbackProtocol;\r
+ EFI_HII_CONFIG_ACCESS_PROTOCOL *ConfigAccessProtocol;\r
+ EFI_HANDLE UefiDriverHandle;\r
+ CONFIG_ACCESS_PRIVATE *ConfigAccessPrivate;\r
+ \r
+ Status = gBS->HandleProtocol (\r
+ CallbackHandle,\r
+ &gEfiFormCallbackProtocolGuid,\r
+ (VOID **) &FormCallbackProtocol\r
+ );\r
+ if (EFI_ERROR (Status)) {\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+ \r
+ Status = mHiiDatabase->GetPackageListHandle (\r
+ mHiiDatabase,\r
+ ThunkContext->UefiHiiHandle,\r
+ &UefiDriverHandle\r
+ );\r
+ ASSERT_EFI_ERROR (Status);\r
+ Status = gBS->HandleProtocol (\r
+ UefiDriverHandle,\r
+ &gEfiHiiConfigAccessProtocolGuid,\r
+ (VOID **) &ConfigAccessProtocol\r
+ );\r
+ ASSERT_EFI_ERROR (Status);\r
+ \r
+ ConfigAccessPrivate = CONFIG_ACCESS_PRIVATE_FROM_PROTOCOL (ConfigAccessProtocol);\r
+ \r
+ ConfigAccessPrivate->FormCallbackProtocol = FormCallbackProtocol;\r
+\r
+ return EFI_SUCCESS;\r
+}\r
+\r
+\r
+/**\r
+ Get the package data from the Package List.\r
+\r
+ @param HiiPackageList Package List.\r
+ @param PackageIndex The index of the Package in the Package List.\r
+ @param BufferLen The Length of the Pacage data.\r
+ @param Buffer On output, the Package data.\r
+\r
+ @return EFI_NOT_FOUND No Package is found for PackageIndex.\r
+ @return EFI_SUCCESS The package data is returned.\r
+\r
+**/\r
+EFI_STATUS\r
+GetPackageData (\r
+ IN EFI_HII_PACKAGE_LIST_HEADER *HiiPackageList,\r
+ IN UINT32 PackageIndex,\r
+ OUT UINT32 *BufferLen,\r
+ OUT EFI_HII_PACKAGE_HEADER **Buffer\r
+ )\r
+{\r
+ UINT32 Index;\r
+ EFI_HII_PACKAGE_HEADER *Package;\r
+ UINT32 Offset;\r
+ UINT32 PackageListLength;\r
+ EFI_HII_PACKAGE_HEADER PackageHeader = {0, 0};\r
+\r
+ ASSERT(HiiPackageList != NULL);\r
+\r
+ if ((BufferLen == NULL) || (Buffer == NULL)) {\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+\r
+ Package = NULL;\r
+ Index = 0;\r
+ Offset = sizeof (EFI_HII_PACKAGE_LIST_HEADER);\r
+ CopyMem (&PackageListLength, &HiiPackageList->PackageLength, sizeof (UINT32));\r
+ while (Offset < PackageListLength) {\r
+ Package = (EFI_HII_PACKAGE_HEADER *) (((UINT8 *) HiiPackageList) + Offset);\r
+ CopyMem (&PackageHeader, Package, sizeof (EFI_HII_PACKAGE_HEADER));\r
+ if (Index == PackageIndex) {\r
+ break;\r
+ }\r
+ Offset += PackageHeader.Length;\r
+ Index++;\r
+ }\r
+ if (Offset >= PackageListLength) {\r
+ //\r
+ // no package found in this Package List\r
+ //\r
+ return EFI_NOT_FOUND;\r
+ }\r
+\r
+ *BufferLen = PackageHeader.Length;\r
+ *Buffer = Package;\r
+ return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+ Check if Label exist in the IFR form package and return the FormSet GUID\r
+ and Form ID.\r
+\r
+ @param Package The Package Header.\r
+ @param Label The Label ID.\r
+ @param FormsetGuid Returns the FormSet GUID.\r
+ @param FormId Returns the Form ID.\r
+\r
+ @retval EFI_SUCCESS The FORM ID is found.\r
+ @retval EFI_NOT_FOUND The FORM ID is not found.\r
+**/\r
+EFI_STATUS\r
+LocateLabel (\r
+ IN CONST EFI_HII_PACKAGE_HEADER *Package,\r
+ IN EFI_FORM_LABEL Label,\r
+ OUT EFI_GUID *FormsetGuid,\r
+ OUT EFI_FORM_ID *FormId\r
+ )\r
+{\r
+ UINTN Offset;\r
+ EFI_IFR_OP_HEADER *IfrOpHdr;\r
+ EFI_GUID InternalFormSetGuid;\r
+ EFI_FORM_ID InternalFormId;\r
+ BOOLEAN GetFormSet;\r
+ BOOLEAN GetForm;\r
+ EFI_IFR_GUID_LABEL *LabelOpcode;\r
+\r
+ IfrOpHdr = (EFI_IFR_OP_HEADER *)((UINT8 *) Package + sizeof (EFI_HII_PACKAGE_HEADER));\r
+ Offset = sizeof (EFI_HII_PACKAGE_HEADER);\r
+\r
+ InternalFormId= 0;\r
+ ZeroMem (&InternalFormSetGuid, sizeof (EFI_GUID));\r
+ GetFormSet = FALSE;\r
+ GetForm = FALSE;\r
+\r
+ while (Offset < Package->Length) {\r
+ switch (IfrOpHdr->OpCode) {\r
+ case EFI_IFR_FORM_SET_OP :\r
+ CopyMem (&InternalFormSetGuid, &((EFI_IFR_FORM_SET *) IfrOpHdr)->Guid, sizeof (EFI_GUID));\r
+ GetFormSet = TRUE;\r
+ break;\r
+\r
+ case EFI_IFR_FORM_OP:\r
+ CopyMem (&InternalFormId, &((EFI_IFR_FORM *) IfrOpHdr)->FormId, sizeof (EFI_FORM_ID));\r
+ GetForm = TRUE;\r
+ break;\r
+\r
+ case EFI_IFR_GUID_OP :\r
+ LabelOpcode = (EFI_IFR_GUID_LABEL *) IfrOpHdr;\r
+ //\r
+ // If it is an Label opcode.\r
+ //\r
+ if ((LabelOpcode->ExtendOpCode == EFI_IFR_EXTEND_OP_LABEL) && (CompareMem (&LabelOpcode->Guid, &mTianoHiiIfrGuid, sizeof (EFI_GUID)) == 0)) {\r
+ if (CompareMem (&Label, &LabelOpcode->Number, sizeof (UINT16)) == 0) {\r
+ ASSERT (GetForm && GetFormSet);\r
+ CopyGuid (FormsetGuid, &InternalFormSetGuid);\r
+ *FormId = InternalFormId;\r
+ return EFI_SUCCESS;\r
+ }\r
+ }\r
+ break;\r
+ default :\r
+ break;\r
+ }\r
+\r
+ //\r
+ // Go to the next Op-Code\r
+ //\r
+ Offset += IfrOpHdr->Length;\r
+ IfrOpHdr = (EFI_IFR_OP_HEADER *) ((CHAR8 *) (IfrOpHdr) + IfrOpHdr->Length);\r
+ }\r
+\r
+ return EFI_NOT_FOUND;\r
+}\r
+\r
+/**\r
+ Find the first EFI_FORM_LABEL in FormSets for a given EFI_HII_HANLDE defined.\r
+ \r
+ EFI_FORM_LABEL is a specific to Tiano implementation. The current implementation\r
+ does not restrict labels with same label value to be duplicated in either FormSet \r
+ scope or Form scope. This function will only locate the FIRST EFI_FORM_LABEL\r
+ with value as the same as the input Label in the Formset registered with UefiHiiHandle. The FormSet GUID \r
+ and Form ID is returned if such Label is found.\r
+\r
+ \r
+ @retval EFI_INVALID_PARAMETER If UefiHiiHandle is not a valid handle.\r
+ @retval EFI_NOT_FOUND The package list identified by UefiHiiHandle deos not contain FormSet or\r
+ There is no Form ID with value Label found in all Form Sets in the pacakge\r
+ list.\r
+ \r
+ @retval EFI_SUCCESS The first found Form ID is returned in FormId.\r
+**/\r
+EFI_STATUS\r
+LocateFormId (\r
+ IN EFI_HII_HANDLE Handle,\r
+ IN EFI_FORM_LABEL Label,\r
+ OUT EFI_GUID *FormsetGuid,\r
+ OUT EFI_FORM_ID *FormId\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ EFI_HII_PACKAGE_LIST_HEADER *HiiPackageList;\r
+ UINT32 Index;\r
+ UINTN BufferSize;\r
+ EFI_HII_PACKAGE_HEADER PackageHeader;\r
+ EFI_HII_PACKAGE_HEADER *Package;\r
+ UINT32 PackageLength;\r
+\r
+ BufferSize = 0;\r
+ HiiPackageList = NULL;\r
+ Status = mHiiDatabase->ExportPackageLists (mHiiDatabase, Handle, &BufferSize, HiiPackageList);\r
+ if (Status == EFI_BUFFER_TOO_SMALL) {\r
+ HiiPackageList = AllocatePool (BufferSize);\r
+ ASSERT (HiiPackageList != NULL);\r
+\r
+ Status = mHiiDatabase->ExportPackageLists (mHiiDatabase, Handle, &BufferSize, HiiPackageList);\r
+ if (EFI_ERROR (Status)) {\r
+ goto Done;\r
+ }\r
+ }\r
+\r
+ for (Index = 0; ; Index++) {\r
+ Status = GetPackageData (HiiPackageList, Index, &PackageLength, &Package);\r
+ if (!EFI_ERROR (Status)) {\r
+ CopyMem (&PackageHeader, Package, sizeof (EFI_HII_PACKAGE_HEADER));\r
+ if (PackageHeader.Type == EFI_HII_PACKAGE_FORMS) {\r
+ Status = LocateLabel (Package, Label, FormsetGuid, FormId);\r
+ if (!EFI_ERROR(Status)) {\r
+ break;\r
+ }\r
+ }\r
+ } else {\r
+ break;\r
+ }\r
+ }\r
+\r
+ \r
+Done:\r
+ FreePool (HiiPackageList);\r
+ \r
+ return Status;\r
+}\r
+\r
+/**\r
+ This function allows the caller to update a form that has\r
+ previously been registered with the EFI HII database.\r
+\r
+\r
+ @param This EDES_TODO: Add parameter description\r
+ @param Handle Hii Handle associated with the Formset to modify\r
+ @param Label Update information starting immediately after this label in the IFR\r
+ @param AddData If TRUE, add data. If FALSE, remove data\r
+ @param Data If adding data, this is the pointer to the data to add\r
+\r
+ @retval EFI_SUCCESS Update success.\r
+ @retval Other Update fail.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+HiiUpdateForm (\r
+ IN EFI_HII_PROTOCOL *This,\r
+ IN FRAMEWORK_EFI_HII_HANDLE Handle,\r
+ IN EFI_FORM_LABEL Label,\r
+ IN BOOLEAN AddData,\r
+ IN FRAMEWORK_EFI_HII_UPDATE_DATA *Data\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ HII_THUNK_PRIVATE_DATA *Private;\r
+ HII_THUNK_CONTEXT *ThunkContext;\r
+ EFI_HII_UPDATE_DATA *UefiHiiUpdateData;\r
+ EFI_HII_HANDLE UefiHiiHandle;\r
+ EFI_GUID FormsetGuid;\r
+ EFI_FORM_ID FormId;\r
+ EFI_TPL OldTpl;\r
+\r
+ OldTpl = gBS->RaiseTPL (TPL_NOTIFY);\r
+ \r
+ mInFrameworkUpdatePakcage = TRUE;\r
+ Status = EFI_SUCCESS;\r
+ UefiHiiUpdateData = NULL;\r
+\r
+\r
+ Private = HII_THUNK_PRIVATE_DATA_FROM_THIS(This);\r
+\r
+ ThunkContext = FwHiiHandleToThunkContext (Private, Handle);\r
+\r
+ if (ThunkContext == NULL) {\r
+ Status = EFI_NOT_FOUND;\r
+ goto Done;\r
+ }\r
+ \r
+ if (Data->FormSetUpdate) {\r
+ Status = UpdateFormCallBack ((EFI_HANDLE) (UINTN) Data->FormCallbackHandle, ThunkContext);\r
+ if (EFI_ERROR (Status)) {\r
+ goto Done;\r
+ }\r
+ }\r
+\r
+ if (ThunkContext->IfrPackageCount == 0) {\r
+ ASSERT (FALSE);\r
+ Status = EFI_INVALID_PARAMETER;\r
+ goto Done;\r
+ } else {\r
+ UefiHiiHandle = ThunkContext->UefiHiiHandle;\r
+ }\r
+\r
+ Status = LocateFormId (UefiHiiHandle, Label, &FormsetGuid, &FormId);\r
+ if (EFI_ERROR (Status)) {\r
+ //\r
+ // Can't find the label.\r
+ //\r
+ goto Done;\r
+ }\r
+\r
+ if (AddData) {\r
+ if (Data->DataCount != 0) {\r
+\r
+ ThunkContext = UefiHiiHandleToThunkContext (Private, UefiHiiHandle);\r
+ Status = FwUpdateDataToUefiUpdateData (ThunkContext, Data, &UefiHiiUpdateData);\r
+ ASSERT_EFI_ERROR (Status);\r
+\r
+ Status = IfrLibUpdateForm (UefiHiiHandle, &FormsetGuid, FormId, Label, TRUE, UefiHiiUpdateData);\r
+ ASSERT_EFI_ERROR (Status);\r
+ \r
+ } \r
+ } else {\r
+ //\r
+ // Delete Opcode starting from Labe in FormId found\r
+ //\r
+ UefiHiiUpdateData = AllocateZeroPool (sizeof (*UefiHiiUpdateData));\r
+ \r
+ Status = IfrLibUpdateForm (UefiHiiHandle, &FormsetGuid, FormId, Label, FALSE, UefiHiiUpdateData);\r
+ ASSERT_EFI_ERROR (Status);\r
+ }\r
+\r
+Done:\r
+ if (UefiHiiUpdateData != NULL) {\r
+ if (UefiHiiUpdateData->Data != NULL) {\r
+ FreePool (UefiHiiUpdateData->Data);\r
+ }\r
+ FreePool (UefiHiiUpdateData);\r
+ }\r
+\r
+ mInFrameworkUpdatePakcage = FALSE; \r
+\r
+ gBS->RestoreTPL (OldTpl);\r
+\r
+ return Status;\r
+}\r