/*++
Copyright (c) 2006, Intel Corporation                                                         
All rights reserved. This program and the accompanying materials                          
are licensed and made available under the terms and conditions of the BSD License         
which accompanies this distribution.  The full text of the license may be found at        
http://opensource.org/licenses/bsd-license.php                                            
                                                                                          
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,                     
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.             

Module Name:
  DriverSample.c

Abstract:

  This is an example of how a driver might export data to the HII protocol to be 
  later utilized by the Setup Protocol

--*/

#include "DriverSample.h"

#define DISPLAY_ONLY_MY_ITEM  0x0001

#define STRING_PACK_GUID \
  { \
    0x8160a85f, 0x934d, 0x468b, { 0xa2, 0x35, 0x72, 0x89, 0x59, 0x14, 0xf6, 0xfc } \
  }

EFI_GUID  mFormSetGuid    = FORMSET_GUID;
EFI_GUID  mStringPackGuid = STRING_PACK_GUID; 

STATIC
EFI_STATUS
EFIAPI
DriverCallback (
  IN EFI_FORM_CALLBACK_PROTOCOL       *This,
  IN UINT16                           KeyValue,
  IN EFI_IFR_DATA_ARRAY               *Data,
  OUT EFI_HII_CALLBACK_PACKET         **Packet
  )
/*++

Routine Description:

  This is the function that is called to provide results data to the driver.  This data
  consists of a unique key which is used to identify what data is either being passed back
  or being asked for. 

Arguments:

  KeyValue -        A unique value which is sent to the original exporting driver so that it
                    can identify the type of data to expect.  The format of the data tends to
                    vary based on the op-code that geerated the callback.

  Data -            A pointer to the data being sent to the original exporting driver.

Returns: 

--*/
{
  EFI_CALLBACK_INFO       *Private;
  EFI_HII_UPDATE_DATA     *UpdateData;
  EFI_STATUS              Status;
  UINT8                   *Location;
  EFI_HII_CALLBACK_PACKET *DataPacket;
  UINT16                  Value;
  CHAR16                  VariableName[40];
  STATIC UINT16           QuestionId = 0;
  IFR_OPTION              *OptionList;
  UINTN                   Index;
  MyIfrNVData             NVStruc;

  Private     = EFI_CALLBACK_INFO_FROM_THIS (This);

  //
  // This should tell me the first offset AFTER the end of the compiled NV map
  // If op-code results are not going to be saved to NV locations ensure the QuestionId
  // is beyond the end of the NVRAM mapping.
  //
  if (QuestionId == 0) {
    QuestionId = sizeof (MyIfrNVData);
  }

  ZeroMem (VariableName, (sizeof (CHAR16) * 40));

  switch (KeyValue) {
  case 0x0001:
    //
    // Create a small boot order list
    //
    QuestionId = (UINT16) ((UINTN) (&NVStruc.BootOrder) - (UINTN) (&NVStruc));

    //
    // Need some memory for OptionList. Allow for up to 8 options.
    //
    OptionList = AllocateZeroPool (sizeof (IFR_OPTION) * 8);
    ASSERT (OptionList != NULL);

    //
    // Allocate space for creation of Buffer
    //
    UpdateData = AllocateZeroPool (0x1000);
    ASSERT (UpdateData != NULL);

    //
    // Remove all the op-codes starting with Label 0x2222 to next Label (second label is for convenience
    // so we don't have to keep track of how many op-codes we added or subtracted.  The rules for removal
    // of op-codes are simply that the removal will always stop as soon as a label or the end of a form is
    // encountered.  Therefore, giving a large obnoxious count such as below takes care of other complexities.
    //
    UpdateData->DataCount = 0xFF;

    //
    // Delete set of op-codes
    //
    Private->Hii->UpdateForm (
                    Private->Hii,
                    Private->RegisteredHandle,
                    (EFI_FORM_LABEL) 0x2222,
                    FALSE,  // If we aren't adding, we are deleting
                    UpdateData
                    );

    //
    // Create 3 options
    //
    for (Index = 0; Index < 3; Index++) {
      OptionList[Index].StringToken = (UINT16) (STR_BOOT_OPTION1 + Index);
      OptionList[Index].Value       = (UINT16) (Index + 1);
      OptionList[Index].Flags       = RESET_REQUIRED;
    }

    CreateOrderedListOpCode (
      QuestionId,                               // Question ID
      8,                                        // Max Entries
      (UINT16) STRING_TOKEN (STR_BOOT_OPTIONS), // Token value for the Prompt
      (UINT16) STRING_TOKEN (STR_NULL_STRING),  // Token value for the Help
      OptionList,
      3,
      &UpdateData->Data                         // Buffer location to place op-codes
      );

    //
    // For one-of/ordered lists commands, they really consist of 2 op-codes (a header and a footer)
    // Each option within a one-of/ordered list is also an op-code
    // So this example has 5 op-codes it is adding since we have a one-of header + 3 options + one-of footer
    //
    UpdateData->DataCount = 0x5;

    //
    // Add one op-code
    //
    Private->Hii->UpdateForm (
                    Private->Hii,
                    Private->RegisteredHandle,
                    (EFI_FORM_LABEL) 0x2222,
                    TRUE,
                    UpdateData
                    );

    gBS->FreePool (UpdateData);
    gBS->FreePool (OptionList);
    break;

  case 0x0002:
    //
    // Create a large boot order list
    //
    QuestionId = (UINT16) ((UINTN) (&NVStruc.BootOrder) - (UINTN) (&NVStruc));

    //
    // Need some memory for OptionList. Allow for up to 8 options.
    //
    OptionList = AllocateZeroPool (sizeof (IFR_OPTION) * 8);
    ASSERT (OptionList != NULL);

    //
    // Allocate space for creation of Buffer
    //
    UpdateData = AllocateZeroPool (0x1000);
    ASSERT (UpdateData != NULL);

    //
    // Remove all the op-codes starting with Label 0x2222 to next Label (second label is for convenience
    // so we don't have to keep track of how many op-codes we added or subtracted
    //
    UpdateData->DataCount = 0xFF;

    //
    // Delete one op-code
    //
    Private->Hii->UpdateForm (
                    Private->Hii,
                    Private->RegisteredHandle,
                    (EFI_FORM_LABEL) 0x2222,
                    FALSE,
                    UpdateData
                    );

    //
    // Create 4 options
    //
    for (Index = 0; Index < 4; Index++) {
      OptionList[Index].StringToken = (UINT16) (STR_BOOT_OPTION1 + Index);
      OptionList[Index].Value       = (UINT16) (Index + 1);
      OptionList[Index].Flags       = RESET_REQUIRED;
    }

    CreateOrderedListOpCode (
      QuestionId,                               // Question ID
      8,                                        // Max Entries
      (UINT16) STRING_TOKEN (STR_BOOT_OPTIONS), // Token value for the Prompt
      (UINT16) STRING_TOKEN (STR_NULL_STRING),  // Token value for the Help
      OptionList,
      4,
      &UpdateData->Data                         // Buffer location to place op-codes
      );

    //
    // For one-of commands, they really consist of 2 op-codes (a header and a footer)
    // Each option within a one-of is also an op-code
    // So this example has 6 op-codes it is adding since we have a one-of header + 4 options + one-of footer
    //
    UpdateData->DataCount = 0x6;

    //
    // Add one op-code
    //
    Private->Hii->UpdateForm (
                    Private->Hii,
                    Private->RegisteredHandle,
                    (EFI_FORM_LABEL) 0x2222,
                    TRUE,
                    UpdateData
                    );

    gBS->FreePool (UpdateData);
    gBS->FreePool (OptionList);
    break;

  case 0x1234:
    //
    // Allocate space for creation of Buffer
    //
    QuestionId = (UINT16) ((UINTN) (&NVStruc.DynamicCheck) - (UINTN) (&NVStruc));
    Status = gBS->AllocatePool (
                    EfiBootServicesData,
                    0x1000,
                    (VOID **) &UpdateData
                    );
    ASSERT_EFI_ERROR (Status);

    ZeroMem (UpdateData, 0x1000);

    Location                        = (UINT8 *) &UpdateData->Data;

    UpdateData->FormSetUpdate       = TRUE;
    UpdateData->FormCallbackHandle  = (EFI_PHYSICAL_ADDRESS) (UINTN) Private->CallbackHandle;
    UpdateData->FormUpdate          = FALSE;
    UpdateData->FormTitle           = 0;
    UpdateData->DataCount           = 2;

    CreateGotoOpCode (
      1,
      STR_GOTO_FORM1,                                   // Token value for the Prompt
      0,                                                // Goto Help
      0,                                                // Flags
      0,                                                // Key
      &UpdateData->Data                                 // Buffer location to place op-codes
      );

    Location = Location + ((EFI_IFR_OP_HEADER *) &UpdateData->Data)->Length;

    CreateCheckBoxOpCode (
      QuestionId,                                       // Question ID
      1,                                                // Data width (BOOLEAN = 1)
      (UINT16) STRING_TOKEN (STR_CHECK_DYNAMIC_PROMPT), // Token value for the Prompt
      (UINT16) STRING_TOKEN (STR_CHECK_DYNAMIC_HELP),   // Token value for the Help
      EFI_IFR_FLAG_INTERACTIVE,                         // Flags
      0x1236,   // Key
      Location  // Buffer location to place op-codes
      );

    Private->Hii->UpdateForm (
                    Private->Hii,
                    Private->RegisteredHandle,
                    (EFI_FORM_LABEL) 0x1234,
                    TRUE,
                    UpdateData
                    );

    gBS->FreePool (UpdateData);
    QuestionId++;
    break;

  case 0x1235:
    //
    // Allocate space for creation of Buffer
    //
    Status = gBS->AllocatePool (
                    EfiBootServicesData,
                    0x1000,
                    (VOID **)&UpdateData
                    );
    ASSERT_EFI_ERROR (Status);

    ZeroMem (UpdateData, 0x1000);

    //
    // Initialize DataPacket with information intended to remove all
    // previously created op-codes in the dynamic page
    //
    UpdateData->FormSetUpdate       = FALSE;
    UpdateData->FormCallbackHandle  = 0;
    UpdateData->FormUpdate          = FALSE;
    UpdateData->FormTitle           = 0;
    //
    // Unlikely to be more than 0xff op-codes in the dynamic page to remove
    //
    UpdateData->DataCount           = 0xff;
    UpdateData->Data = NULL;

    //
    // Remove all op-codes from dynamic page
    //
    Private->Hii->UpdateForm (
                    Private->Hii,
                    Private->RegisteredHandle,
                    (EFI_FORM_LABEL) 0x1234,  // Label 0x1234
                    FALSE,                    // Remove Op-codes (will never remove form/endform)
                    UpdateData                // Significant value is UpdateData->DataCount
                    );

    UpdateData->FormSetUpdate       = FALSE;
    UpdateData->FormCallbackHandle  = 0;
    UpdateData->FormUpdate          = FALSE;
    UpdateData->FormTitle           = 0;
    UpdateData->DataCount           = 1;

    CreateGotoOpCode (
      1,
      STR_GOTO_FORM1,                         // Token value for the Prompt
      0,                                      // Goto Help
      0,                                      // Flags
      0,                                      // Key
      &UpdateData->Data                       // Buffer location to place op-codes
      );

    Private->Hii->UpdateForm (
                    Private->Hii,
                    Private->RegisteredHandle,
                    (EFI_FORM_LABEL) 0x1234,
                    TRUE,
                    UpdateData
                    );

    gBS->FreePool (UpdateData);
    break;

  case 0x1236:
    //
    // If I hit the checkbox, I enter this case statement...
    //
    //
    // Since I am returning an error (for test purposes) I need to pass in the string for the error
    // I will allocate space for the return value.  If an error occurs (which is the case) I can simply return
    // an error and fill in the string parameter, otherwise, I will return information in the DataArray structure.
    // The browser will free this packet structure
    //
    Status = gBS->AllocatePool (
                    EfiBootServicesData,
                    sizeof (EFI_HII_CALLBACK_PACKET) + sizeof (SAMPLE_STRING) + 2,
                    (VOID **) Packet
                    );
    ASSERT_EFI_ERROR (Status);

    ZeroMem (*Packet, sizeof (EFI_HII_CALLBACK_PACKET) + sizeof (SAMPLE_STRING) + 2);

    //
    // Assign the buffer address to DataPacket
    //
    DataPacket = *Packet;

    StrCpy (DataPacket->String, (CHAR16 *) SAMPLE_STRING);
    return EFI_DEVICE_ERROR;

  case 0x1237:

    Status = gBS->AllocatePool (
                    EfiBootServicesData,
                    sizeof (EFI_HII_CALLBACK_PACKET) + 2,
                    (VOID **) Packet
                    );
    ASSERT_EFI_ERROR (Status);

    ZeroMem (*Packet, sizeof (EFI_HII_CALLBACK_PACKET) + 2);

    //
    // Assign the buffer address to DataPacket
    //
    DataPacket                        = *Packet;

    DataPacket->DataArray.EntryCount  = 1;
    DataPacket->DataArray.NvRamMap    = NULL;
    ((EFI_IFR_DATA_ENTRY *) (&DataPacket->DataArray + 1))->Flags = EXIT_REQUIRED;
    break;

  case 0x1555:
    Value = 0x0001;
    UnicodeSPrint (VariableName, 0x80, (CHAR16 *) L"%d", VAR_EQ_TEST_NAME);

    Status = gRT->SetVariable (
                    VariableName,
                    &mFormSetGuid,
                    EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
                    2,
                    (VOID *) &Value
                    );
    break;

  case 0x1556:
    Value = 0x1000;
    UnicodeSPrint (VariableName, 0x80, (CHAR16 *) L"%d", VAR_EQ_TEST_NAME);

    Status = gRT->SetVariable (
                    VariableName,
                    &mFormSetGuid,
                    EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
                    2,
                    (VOID *) &Value
                    );
    break;

  case 0x1557:
    Value = 0x0000;
    UnicodeSPrint (VariableName, 0x80, (CHAR16 *) L"%d", VAR_EQ_TEST_NAME);

    Status = gRT->SetVariable (
                    VariableName,
                    &mFormSetGuid,
                    EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
                    2,
                    (VOID *) &Value
                    );
    break;

  default:
    break;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
DriverSampleInit (
  IN EFI_HANDLE                   ImageHandle,
  IN EFI_SYSTEM_TABLE             *SystemTable
  )
{
  EFI_STATUS          Status;
  EFI_HII_PROTOCOL    *Hii;
  //
  //  EFI_FORM_BROWSER_PROTOCOL       *FormConfig;
  //
  EFI_HII_PACKAGES    *PackageList;
  EFI_HII_HANDLE      HiiHandle;
  STRING_REF          TokenToUpdate;
  STRING_REF          TokenToUpdate2;
  STRING_REF          TokenToUpdate3;
  CHAR16              *NewString;
  EFI_HII_UPDATE_DATA *UpdateData;
  EFI_CALLBACK_INFO   *CallbackInfo;
  EFI_HANDLE          Handle;
  EFI_SCREEN_DESCRIPTOR   Screen;

  ZeroMem (&Screen, sizeof (EFI_SCREEN_DESCRIPTOR));

  gST->ConOut->QueryMode (gST->ConOut, gST->ConOut->Mode->Mode, &Screen.RightColumn, &Screen.BottomRow);

  //
  // Remove 3 characters from top and bottom
  //
  Screen.TopRow     = 3;
  Screen.BottomRow  = Screen.BottomRow - 3;

  //
  // There should only be one HII protocol
  //
  Status = gBS->LocateProtocol (
                  &gEfiHiiProtocolGuid,
                  NULL,
                  (VOID **) &Hii
                  );
  if (EFI_ERROR (Status)) {
    return Status;;
  }

  /*
  //
  // There should only be one Form Configuration protocol
  //
  Status = gBS->LocateProtocol (
                 &gEfiFormBrowserProtocolGuid, 
                 NULL, 
                 &FormConfig
                 );
  if (EFI_ERROR (Status)) {
    return Status;;
  }
*/
  Status = gBS->AllocatePool (
                  EfiBootServicesData,
                  sizeof (EFI_CALLBACK_INFO),
                  (VOID **) &CallbackInfo
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  CallbackInfo->Signature = EFI_CALLBACK_INFO_SIGNATURE;
  CallbackInfo->Hii       = Hii;

  //
  // This example does not implement worker functions for the NV accessor functions.  Only a callback evaluator
  //
  CallbackInfo->DriverCallback.NvRead   = NULL;
  CallbackInfo->DriverCallback.NvWrite  = NULL;
  CallbackInfo->DriverCallback.Callback = DriverCallback;

  //
  // Install protocol interface
  //
  Handle = NULL;
  Status = gBS->InstallProtocolInterface (
                  &Handle,
                  &gEfiFormCallbackProtocolGuid,
                  EFI_NATIVE_INTERFACE,
                  &CallbackInfo->DriverCallback
                  );

  ASSERT_EFI_ERROR (Status);

  CallbackInfo->CallbackHandle  = Handle;

  PackageList                   = PreparePackages (1, &mStringPackGuid, DriverSampleStrings);
  Status                        = Hii->NewPack (Hii, PackageList, &HiiHandle);
  gBS->FreePool (PackageList);

  PackageList = PreparePackages (1, &mStringPackGuid, InventoryBin);
  Status      = Hii->NewPack (Hii, PackageList, &HiiHandle);
  gBS->FreePool (PackageList);

  PackageList = PreparePackages (1, &mStringPackGuid, VfrBin);
  Status      = Hii->NewPack (Hii, PackageList, &HiiHandle);
  gBS->FreePool (PackageList);

  CallbackInfo->RegisteredHandle = HiiHandle;

  //
  // Very simple example of how one would update a string that is already
  // in the HII database
  //
  TokenToUpdate = (STRING_REF) STR_CPU_STRING2;
  NewString     = (CHAR16 *) L"700 Mhz";

  Hii->NewString (Hii, NULL, HiiHandle, &TokenToUpdate, NewString);

  //
  // Add a string - if 0 will be updated with new Token number
  //
  TokenToUpdate = (STRING_REF) 0;

  //
  // Add a string - if 0 will be updated with new Token number
  //
  TokenToUpdate2 = (STRING_REF) 0;

  //
  // Add a string - if 0 will be updated with new Token number
  //
  TokenToUpdate3 = (STRING_REF) 0;

  Hii->NewString (Hii, NULL, HiiHandle, &TokenToUpdate, (CHAR16 *) L"Desired Speed");
  Hii->NewString (Hii, NULL, HiiHandle, &TokenToUpdate2, (CHAR16 *) L"5 Thz");
  Hii->NewString (Hii, NULL, HiiHandle, &TokenToUpdate3, (CHAR16 *) L"This is next year's desired speed - right?");

  //
  // Allocate space for creation of Buffer
  //
  Status = gBS->AllocatePool (
                  EfiBootServicesData,
                  0x1000,
                  (VOID **) &UpdateData
                  );
  ASSERT_EFI_ERROR (Status);

  ZeroMem (UpdateData, 0x1000);

  //
  // Flag update pending in FormSet
  //
  UpdateData->FormSetUpdate = TRUE;
  //
  // Register CallbackHandle data for FormSet
  //
  UpdateData->FormCallbackHandle = (EFI_PHYSICAL_ADDRESS) (UINTN) CallbackInfo->CallbackHandle;
  UpdateData->FormUpdate  = FALSE;
  UpdateData->FormTitle   = 0;
  UpdateData->DataCount   = 1;

  CreateTextOpCode (TokenToUpdate, TokenToUpdate2, TokenToUpdate3, 0, 0, &UpdateData->Data);

  Hii->UpdateForm (Hii, HiiHandle, (EFI_FORM_LABEL) 100, TRUE, UpdateData);

  gBS->FreePool (UpdateData);

  //
  // Example of how to display only the item we sent to HII
  //
  if (DISPLAY_ONLY_MY_ITEM == 0x0001) {
    //
    // Have the browser pull out our copy of the data, and only display our data
    //
    //    Status = FormConfig->SendForm (FormConfig, TRUE, HiiHandle, NULL, NULL, NULL, &Screen, NULL);
    //
  } else {
    //
    // Have the browser pull out all the data in the HII Database and display it.
    //
    //    Status = FormConfig->SendForm (FormConfig, TRUE, 0, NULL, NULL, NULL, NULL, NULL);
    //
  }

  if (EFI_ERROR (Status)) {
    return Status;
  }

  return EFI_SUCCESS;
}
