UEFI News and Commentary

Showing posts with label HII Database. Show all posts
Showing posts with label HII Database. Show all posts

Saturday, November 03, 2012

HOW TO: Disassembling the HII Database (Part 4)

The HII Database is the portion of the UEFI Specification that manages the user-interface resources, like forms, fonts, images and strings. The tool we've been developing parses the contents of the database and displays it. Up to this point, we've broken down the data into the constituent package lists, and then broken down package lists into packages, and then started looking at how form packages are encoded.

Form Set 

Form packages are encoded as a series of variable-length data structures, called opcodes. The previous article shows how the opcodes are parsed, and then a function for when the opcode is first encountered, and then another function for when the opcode's scope is completed.

EFI_IFR_FORM_SET

The top level objects in a forms package are all form sets. The form set describes a collection of forms, variable stores and default stores, associated with a GUID, prompt text, help text, an image and an animation.

typedef struct _EFI_IFR_FORM_SET {
  EFI_IFR_OP_HEADER        Header;
  EFI_GUID                 Guid;
  EFI_STRING_ID            FormSetTitle;
  EFI_STRING_ID            Help;
  UINT8                    Flags;
//EFI_GUID                 ClassGuid[];
} EFI_IFR_FORM_SET;


The Header  the standard IFR opcode header. The Guid uniquely identifies the form set. The title of the form set (FormSetTitle) and there is also help text (Help). There can be up to three class GUIDs, which provide a way to classify the form sets. These GUIDs (along with Guid) can be used in the SendForm() function call to specify which types of form sets will appear in the user-interface.

UefiHiiParseFormFormSetOp()

The top level objects in a forms package are all form sets. Form sets are collections of forms associated with a GUID.

EFI_STATUS
UefiHiiParseFormFormSetOp (
  IN CONST EFI_IFR_OP_HEADER *Op,
  IN OUT UEFI_HII_FORM_PKG_STATE *S
  )
{
  UINT8 ClassGuidCount;
  EFI_GUID *ClassGuid;
  EFI_STATUS s;
  EFI_IFR_FORM_SET *FormSetOp;
  SYS_STRA Guid;
  UINT32 i;


  FormSetOp = (EFI_IFR_FORM_SET *) Op;

  if (!UefiHiiOpIsValid (Op, S)) {
    return EFI_INVALID_PARAMETER;
  }
  if (S->FormSetP != NULL) {
    SYSINFO ("FORM_SET: Cannot be inside a FORM_SET opcode.\n");
    return EFI_INVALID_PARAMETER;
  }


  ClassGuid = (EFI_GUID *)(FormSetOp + 1);
  ClassGuidCount = FormSetOp->Flags & 0x03; // isolate class GUID count.
  if (ClassGuidCount * sizeof (EFI_GUID) + sizeof (EFI_IFR_FORM_SET) >

      Op->Length) {
    ...

    return EFI_INVALID_PARAMETER;
  }


  ...dump out debug information...

  S->FormSetP = SysNew (UefiHiiFormSetP);
  if (S->FormSetP == NULL) {
    s = EFI_OUT_OF_RESOURCES;
    goto exit;
  }


  memcpy (&S->FormSetP->Id, &FormSetOp->Guid, sizeof (EFI_GUID));
  S->FormSetP->Title = FormSetOp->FormSetTitle;
  S->FormSetP->Help = FormSetOp->Help;
  S->FormSetP->ClassIdCount = ClassGuidCount;
  memcpy (S->FormSetP->ClassIds, ClassGuid, ClassGuidCount * sizeof (EFI_GUID));


  if (!SysListAddTail (&S->FormPkgP->FormSets, S->FormSetP)) {
    SysDelete (S->FormSetP);
    S->FormSetP = NULL;
    s = EFI_OUT_OF_RESOURCES;
    goto exit;
  }


  s = EFI_SUCCESS;
exit:
  SysEmpty (&Guid);
  return s;
}


This function checks to make sure that we aren't already inside of a form set scope. Also, since this opcode can be variable sized (with 0-3 class GUIDs), we also need to make sure that the opcode has a valid size. Then the function creates a new Form Set container and then populates the members with data from the IFR FORM_SET opcode. The Form Package parsing state is updated with the pointer to the Form Set container. Finally, the Form Set container is added to the list maintained as a part of the Form Package container.

UEFI_HII_FORM_SET_P

The UEFI_HII_FORM_SET_P object acts as the Form Set container.

typedef struct _UEFI_HII_FORM_SET_P {
  SYS_OBJ Obj;


  EFI_GUID Id;
  int ClassIdCount;                     // number of entries in ClassIds
  EFI_GUID ClassIds[3];

  EFI_STRING_ID Title;
  EFI_STRING_ID Help;
  EFI_IMAGE_ID Image;
  EFI_ANIMATION_ID Animation;


  SYS_LIST_O Forms;                     // list of Form containers.
  SYS_LIST_O VarStores;                 // list of Variable Store containers.
  SYS_LIST_O DefaultStores;             // list of Default Store containers.

  SYS_MAP_U16P Questions;               // question id <-> Statement container
} UEFI_HII_FORM_SET_P;


typedef UEFI_HII_FORM_SET_P UefiHiiFormSetP;

Most of these members map directly to the IFR FORM_SET opcode, like the GUID (Id), the class GUIDs (ClassIds and ClassIdCount), the title text (Title) and the help text (Help). In addition, as we process the opcodes inside the Form Set scope, we will update the attributes of the Form Set container.  For example, the IFR IMAGE opcode will update the Image member.

There are four types of objects where the identifier is only unique within a form set: forms, variable stores, default stores and questions. Thus, there are three object lists (forms, variable stores, default stores) and one map (for questions). The questions are handled separately because, although the identifier is unique within the form set, they are also a part of the forms.

UefiHiiParseFormFormSetEndOp()

Now, when we reach the end of the Form Set scope, the function UefiHiiParseFormFormSetEndOp() is called.

EFI_STATUS
UefiHiiParseFormFormSetEndOp (
  IN CONST EFI_IFR_OP_HEADER *Op,
  IN OUT UEFI_HII_FORM_PKG_STATE *S
  )
{
  S->FormSetP = NULL;
  return EFI_SUCCESS;
}


Pretty simple, huh? Just update the Form Package parsing state to NULL, indicating that there is no active form set.

Variable Stores

Variable stores describe a virtual buffer used for configuration setting storage for one or more questions. There are three types of variable stores: buffer, EFI variable and name/value. Each of them has an identifier, a name and a GUID.

EFI_IFR_VARSTORE, EFI_IFR_VARSTORE_EFI and EFI_IFR_VARSTORE_NAME_VALUE

There are actually three separate opcodes, one for each type of variable store.

typedef struct {
  EFI_IFR_OP_HEADER Header;
 
  EFI_GUID Guid;
  EFI_VARSTORE_ID VarStoreId;
  UINT16 Size;
//UINT8 Name[];
} EFI_IFR_VARSTORE;


For the buffer variable store, there is a variable store identifier (VarStoreId) associated with a GUID (Guid) and name (Name). In addition, buffer variable stores have to specify the size, in bytes (Size).

typedef struct _EFI_IFR_VARSTORE_NAME_VALUE {
  EFI_IFR_OP_HEADER Header;

  EFI_VARSTORE_ID VarStoreId;
  EFI_GUID Guid;
} EFI_IFR_VARSTORE_NAME_VALUE;


For the name/value variable store, there is a variable store identifier (VarStoreId) associated with a GUID (Guid). There is also a name, but that is provided by the question header rather than being embedded in the variable store opcode.

typedef struct _EFI_IFR_VARSTORE_EFI {
  EFI_IFR_OP_HEADER Header;


  EFI_VARSTORE_ID VarStoreId;
  EFI_GUID Guid;
  UINT32 Attributes
  UINT16 Size;
//UINT8 Name[];
} EFI_IFR_VARSTORE_EFI;


For the EFI variable store, there is a variable store identifier (VarStoreId) associated with a GUID (Guid) and name (Name). In addition, EFI variable stores have to specify the size, in bytes (Size) and the variable attributes (Attributes).

UefiHiiParseFormVarStoreOp()

The Variable Store container is used for all types of variable stores. For buffer variable stores, here is the function:

EFI_STATUS
UefiHiiParseFormVarStoreOp (
  IN EFI_IFR_OP_HEADER *Op,
  IN OUT UEFI_HII_FORM_PKG_STATE *S
  )
{
  EFI_STATUS s;
  EFI_IFR_VARSTORE *VarStoreOp;
  UEFI_HII_VAR_STORE_P *VarStoreP;
  SYS_LIST_POS pos;
  SYS_STRA Guid;
  UINT32 i;


  SysStrAInit (&Guid);
  VarStoreOp = (EFI_IFR_VARSTORE *) Op;

  if (!UefiHiiOpIsValid (Op, S) ||
      !UefiHiiOpInFormSet (Op, S) ||
      !UefiHiiOpNotInForm (Op, S)) {
    return EFI_INVALID_PARAMETER;
  }


  for (i = sizeof (EFI_IFR_VARSTORE); i < Op->Length; i++) {
    if (((UINT8 *)Op)[i] == 0x00) {
      break;
    }
  }

  if (i == Op->Length) {
    return EFI_INVALID_PARAMETER;
  }

  if (VarStoreOp->Size == 0) {
    return EFI_INVALID_PARAMETER;
  }
  if (VarStoreOp->VarStoreId == 0) {
    return EFI_INVALID_PARAMETER;
  }


  ...dump out debug information...

  for (pos = NULL; SysListGetNext (&S->FormSetP->VarStores, &pos, &VarStoreP);) {
    if (!SysIsValid (VarStoreP)) {
      continue;
    }

    if (VarStoreP->Id == VarStoreOp->VarStoreId) {
      s = EFI_SUCCESS;
      goto exit;
    }
  }


  VarStoreP = SysNew (UefiHiiVarStoreP);
  if (VarStoreP == NULL) {
    s = EFI_OUT_OF_RESOURCES;
    goto exit;
  }


  VarStoreP->Type = Op->OpCode;
  VarStoreP->Id = VarStoreOp->VarStoreId;
  memcpy (&VarStoreP->Guid, &VarStoreOp->Guid, sizeof (EFI_GUID));
  VarStoreP->Size = VarStoreOp->Size;
  SysStrACopyAStr (&VarStoreP->Name, (char *)(VarStoreOp + 1));


  if (!SysListAddTail (&S->FormSetP->VarStores, VarStoreP)) {
    s = EFI_OUT_OF_RESOURCES;
    goto exit;
  }


  s = EFI_SUCCESS;
exit:
  SysStrAEmpty (&Guid);
  return s;
}


So this function checks to make sure that opcode is formatted correctly, including the size of the opcode. It also checks that there is only one variable store with the given variable store identifier in the form set. Then it creates the new Variable Store container from the IFR VARSTORE opcode and adds it to the current Form Set container.

UefiHiiParseFormVarStoreEfiOp()

Now let's look at the function for EFI variable stores.

EFI_STATUS
UefiHiiParseFormVarStoreEfiOp (
  IN EFI_IFR_OP_HEADER *Op,
  IN OUT UEFI_HII_FORM_PKG_STATE *S
  )
{
  EFI_STATUS s;
  EFI_IFR_VARSTORE_EFI *VarStoreOp;
  UEFI_HII_VAR_STORE_P *VarStoreP;
  SYS_LIST_POS pos;
  SYS_STRA Guid;
  UINT32 i;


  SysStrAInit (&Guid);
  VarStoreOp = (EFI_IFR_VARSTORE_EFI *) Op;

  if (!UefiHiiOpIsValid (Op, S) ||
      !UefiHiiOpInFormSet (Op, S)) {
    return EFI_INVALID_PARAMETER;
  }


  for (i = sizeof (EFI_IFR_VARSTORE_EFI); i < Op->Length; i++) {
    if (((UINT8 *)Op)[i] == 0x00) {
      break;
    }
  }
  if (i == Op->Length) {
    return EFI_INVALID_PARAMETER;
  }


  if (VarStoreOp->Size == 0) {
    return EFI_INVALID_PARAMETER;
  }
  if (VarStoreOp->VarStoreId == 0) {
    return EFI_INVALID_PARAMETER;
  }
  if (VarStoreOp->Attributes == 0) {
    return EFI_INVALID_PARAMETER;
  }


  ...dump debug information...

  for (pos = NULL; SysListGetNext (&S->FormSetP->VarStores, &pos, &VarStoreP);) {
    if (!SysIsValid (VarStoreP)) {
      continue;
    }
    if (VarStoreP->Id == VarStoreOp->VarStoreId) {
      s = EFI_SUCCESS;
      goto exit;
    }
  }


  VarStoreP = SysNew (UefiHiiVarStoreP);
  if (VarStoreP == NULL) {
    s = EFI_OUT_OF_RESOURCES;
    goto exit;
  }


  VarStoreP->Type = Op->OpCode;
  VarStoreP->Id = VarStoreOp->VarStoreId;
  memcpy (&VarStoreP->Guid, &VarStoreOp->Guid, sizeof (EFI_GUID));
  VarStoreP->Size = VarStoreOp->Size;
  VarStoreP->Attribs = VarStoreOp->Attributes;
  SysStrACopyAStr (&VarStoreP->Name, (char *)(VarStoreOp + 1));


  if (!SysListAddTail (&S->FormSetP->VarStores, VarStoreP)) {
    s = EFI_OUT_OF_RESOURCES;
    goto exit;
  }


  s = EFI_SUCCESS;
exit:
  SysStrAEmpty (&Guid);
  return s;
}


This is very similar to the buffer variable store, except that the type is set with the IFR VARSTORE_EFI opcode value.

UefiHiiParseFormVarStoreNameValueOp()

Now we try the same trick for name/value variable stores. Here is the function:

EFI_STATUS
UefiHiiParseFormVarStoreNameValueOp (
  IN EFI_IFR_OP_HEADER *Op,
  IN OUT UEFI_HII_FORM_PKG_STATE *S
  )
{
  EFI_STATUS s;
  EFI_IFR_VARSTORE_NAME_VALUE *VarStoreOp;
  UEFI_HII_VAR_STORE_P *VarStoreP;
  SYS_LIST_POS pos;
  SYS_STRA Guid;


  SysStrAInit (&Guid);
  VarStoreOp = (EFI_IFR_VARSTORE_NAME_VALUE *) Op;

  if (!UefiHiiOpIsValid (Op, S) ||
      !UefiHiiOpInFormSet (Op, S)) {
    return EFI_INVALID_PARAMETER;
  }


  if (VarStoreOp->VarStoreId == 0) {
    return EFI_INVALID_PARAMETER;
  }


  ...dump debug information...

  for (pos = NULL; SysListGetNext (&S->FormSetP->VarStores, &pos, &VarStoreP);) {
    if (!SysIsValid (VarStoreP)) {
      continue;
    }

    if (VarStoreP->Id == VarStoreOp->VarStoreId) {
      s = EFI_SUCCESS;
      goto exit;
    }
  }


  VarStoreP = SysNew (UefiHiiVarStoreP);
  if (VarStoreP == NULL) {
    s = EFI_OUT_OF_RESOURCES;
    goto exit;
  }


  VarStoreP->Type = Op->OpCode;
  VarStoreP->Id = VarStoreOp->VarStoreId;
  memcpy (&VarStoreP->Guid, &VarStoreOp->Guid, sizeof (EFI_GUID));


  if (!SysListAddTail (&S->FormSetP->VarStores, VarStoreP)) {
    s = EFI_OUT_OF_RESOURCES;
    goto exit;
  }


  s = EFI_SUCCESS;
exit:
  SysStrAEmpty (&Guid);
  return s;
}


Like the two previous functions, the opcode is checked for validity. Then a new Variable Store container is created from the IFR VARSTORE_NAME_VALUE opcode and added to the Form Set.

UEFI_HII_VAR_STORE_P

The Variable Store container structure (UEFI_HII_VAR_STORE_P) describes a variable store.

typedef struct _UEFI_HII_VAR_STORE_P {
  SYS_OBJ Obj;


  UINT8 Type;                     // VARSTORE, VARSTORE_EFI, VARSTORE_NAME_VALUE
  EFI_VARSTORE_ID Id;    
  EFI_GUID Guid;                  // variable store GUID
  SYS_STRA Name;

  UINT16 Size;                    // for VARSTORE
  UINT32 Attribs;                 // for VARSTORE_EFI
} UEFI_HII_VAR_STORE_P;


typedef UEFI_HII_VAR_STORE_P UefiHiiVarStoreP;

The Type describes the type of Variable Store: buffer (VARSTORE), EFI variable (VARSTORE_EFI) and name/value (VARSTORE_NAME_VALUE). The variable stores all have at least a variable store identifier (Id) and a GUID (Guid). The name (Name) and size (Size) is used for buffer and EFI variable stores. The attributes (Attribs) are only used for EFI variable stores.  

Conclusion

So far, we've looked at how to parse the form sets and the associated variable stores. Next time we'll look at forms and default stores. As you can see, each major IFR object type has its own container.

Monday, October 22, 2012

HOW TO: Disassembling the UEFI HII Database (Part 2)

In this series of articles, we are examining the UEFI HII database, a repository for all sorts of user-interface resources used by UEFI applications, including forms, strings, fonts, images and animations. UEFI firmware also provides a built-in API for displaying the forms and interactin the the user called the Form Browser2 protocol.

In part 1, we looked at the top-most layer of our disassembler, which retrieved all of the installed resources from the database. The resources are binary-encoded into variable-length data structures called Packages and then grouped together into Package Lists. Part 1 handled the Package Lists and now we will take a look at the Package structure and parsing code.

UEFI HII Packages

The encoding for UEFI HII Packages is deceptively simple: there is a header and then a body. The header describes what type of package it is and its size, in bytes.

typedef struct {
  UINT32  Length:24;
  UINT32  Type:8;
// UINT8  Data[...];
} EFI_HII_PACKAGE_HEADER;



So, likewise, the basic data structure for holding UEFI HII Packages is quite simple:

#define UEFI_HII_PACKAGE_SIGNATURE 0x5F504855 // "UHP_"
typedef struct _UEFI_HII_PACKAGE_P {
  SYS_OBJ Obj;                         


  UINT8 Type;                           // See EFI_HII_PACKAGE_x
  UEFI_HII_PACKAGE_LIST_P *PkgList;     // package list parent.
  SYS_OBJ *PkgData;                     // ptr to data specific to package type.
} UEFI_HII_PACKAGE_P;


typedef UEFI_HII_PACKAGE_P UefiHiiPackageP;
The Obj member provides the basic object-oriented functionality of the SysLib, including function pointers to initialization, emptying, copying and validity-checking functions.

The Type member matches the Type member of the IFR Package header. There are currently 8 types of packages as well as a GUIDed type for expansion and 32 reserved types for system vendors.

The PkgList member points to the structure that represents the package list which contains this package. If the package was parsed independently, as would happen when disassembling a file  containing only package binary-encoded data, this would be NULL.

The PkgData member points to an object that represents the contents of the package. The exact object depends on the Type member. So for forms, this is a pointer to a UEFI_HII_FORM_PACKAGE_P. By using the object pointer, we can handle copying and memory management without knowing the exact structure.
 

UefiHiiParsePkg()

In part 1, we saw that UefiHiiParsePkgList() walks through all packages in a package list and calls this function to handle actually parsing the package data and converting it into our structure. Essentially this is a factory function which creates a package object. It then uses the Type value to call another parsing function which processes the package body and records the pointer to the resulting data object.

EFI_STATUS
UefiHiiParsePkg (
  IN EFI_HII_PACKAGE_HEADER *Pkg,
  OUT UEFI_HII_PACKAGE *PHandle
  )
{
  EFI_STATUS s;
  UINT8 *PkgData;
  UINT32 PkgDataSize;
  UEFI_HII_PACKAGE_P *PkgP;

 
  if (Pkg == NULL || PHandle == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  if (Pkg->Length < sizeof (EFI_HII_PACKAGE_HEADER)) {
    return EFI_INVALID_PARAMETER;
  }

 
  ...
  PkgP = SysNew (UefiHiiPackageP);
  if (PkgP == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }
  *PHandle = (UEFI_HII_PACKAGE)PkgP;

 
  UefiHiiSetPkgType (*PHandle, (UINT8) Pkg->Type);
 
  PkgData = (UINT8 *)(Pkg + 1);
  PkgDataSize = Pkg->Length - sizeof (EFI_HII_PACKAGE_HEADER);
  switch (Pkg->Type) {

 
  case EFI_HII_PACKAGE_FORMS:
    s = UefiHiiParseFormPkg (
          (EFI_IFR_OP_HEADER *)PkgData,
          PkgDataSize,
          PkgP,
          &PkgP->PkgData
          );
    break;

 
  case EFI_HII_PACKAGE_STRINGS:
    s = UefiHiiParseStringPkg (
          (EFI_HII_STRING_PACKAGE_HDR *)Pkg,
          PkgDataSize,
          PkgP,
          &PkgP->PkgData
          );
    break;

 
  case EFI_HII_PACKAGE_SIMPLE_FONTS:
    s = UefiHiiParseSimpleFontPkg (
          (EFI_HII_SIMPLE_FONT_PACKAGE_HDR *)Pkg,
          PkgDataSize,
          PkgP,
          &PkgP->PkgData
          );
    break;


  case EFI_HII_PACKAGE_ANIMATIONS:
    s = UefiHiiParseAnimationPkg (
          (EFI_HII_ANIMATION_PACKAGE_HDR *)Pkg,
          PkgDataSize,
          PkgP,
          &PkgP->PkgData
          );
    break;


  case EFI_HII_PACKAGE_IMAGES:
    s = UefiHiiParseImagePkg (
          (EFI_HII_IMAGE_PACKAGE_HDR *)Pkg,
          PkgDataSize,
          PkgP,
          &PkgP->PkgData
          );
    break;

  case EFI_HII_PACKAGE_FONTS: 
    s = UefiHiiParseFontPkg (
         (EFI_HII_FONT_PACKAGE_HDR *)Pkg,
         PkgDataSize,
         PkgP,
         &PkgP->PkgData
         );

    break;
  case EFI_HII_PACKAGE_DEVICE_PATH:
    s = EFI_SUCCESS;
    break;

 
  case EFI_HII_PACKAGE_END:
    s = EFI_SUCCESS;
    break;

 
  default:
    s = EFI_INVALID_PARAMETER;
    break;
  }

 
  if (EFI_ERROR(s)) {
    SysDelete (PkgP);
  }
  return s;
}

Conclusion

I have described the actual format of string, font and image packages before, but never the forms. If you look carefully above, you can see that the FORMS package parsing function is slightly different than the others. The string, font, image and animation packages all start with fixed headers which are essentially supersets of the package header. The forms package does not. It uses the standard package header but then contains a series of variable length "opcodes". And what is IFR anyway? More on this next time, where we will go into depth into how forms are encoded.

Friday, October 19, 2012

HOW TO: Disassembling the UEFI HII Database (Part 1)

This article is the first in a series that talks about UEFI's Human Interface Infrastructure (HII) database. The HII Database is the repository for all sorts of user-interface related information in a platform, including forms, strings, bitmaps, fonts and keyboard layouts. Within UEFI, these resources are used primarily to present configuration information to a user. One example is the setup application, common to most PC BIOS firmware implementations. But that is not all. UEFI also uses these resources to implement the Driver Health and User Identification infrastructure. And, of course, our applications can use these.

I'm going to show you the HII database by using the source code to a tool (hiidd) that parses the HII database contents into structures and then displays the information. This tool is not merely a disassembler, but also a foundation for further UEFI tools. It uses some of the SysLib that I described previously.

main()

So let's jump in to main:

int

EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  int ret;

 
  ret = 0;
  InitCmdLine();

  ret = ParseCmdLine (Argc, Argv);
  if (ret != 0) {
    goto error;
  }

  if (gPackageListFromHiiDatabase) {

    verbosePhase("Read package lists from HII database.\n");
    ret = ReadHiiDatabase();
  } else if (gPackageListFromFiles) {
    verbosePhase("Read package lists from files.\n");
    ret = ReadPackageListsFromFiles();
  } else if (gPackageFromFiles) {
    verbosePhase("Read pacakges from files.\n");
    ret = ReadPackagesFromFiles();
  }

  DumpPackageLists();
error:

  ShutCmdLine();
  return ret;
}
 

Pretty standard. We parse the command-line, read the HII database either from UEFI or from a file and then dump it out. The tool supports the following command-line options: 
  • -hiidb - Read the package lists from the HII Database on the machine.
  • -packagelist file-name - Read the package list from the file file-name. The file has the package list format as described in the UEFI specification.
  • -package file-name - Read in an individual package from the file file-name. The file has the package fromat as described in the UEFI specification.
  • -verbose, -v1, -v2, -v3 - Turn on the level of informational output provided by the tool. 1 = phases, 2 = actions, 3 = the kitchen sink. There are three functions: verbosePhase(), verboseAction() and verboseInfo() which will only display the string if the verbosity level is set to the corresponding level.

ReadHiiDatabase()

 Now let's take a look at the main HII Database parsing code:

int
ReadHiiDatabase (void)
{
  EFI_STATUS s;
  EFI_HII_PACKAGE_LIST_HEADER *PackageLists;
  EFI_HII_PACKAGE_LIST_HEADER *PackageList;
  UINTN PackageListSize;
  int PackageListIndex;
  SYS_STRA guid;
  int ret;
  UEFI_HII_PACKAGE_LIST PLHandle;


  SysStrAInit (&guid);

  //
  // All of the package lists are exported into one big buffer.
  //
  s = UefiHiiExportPackageListsA (
        NULL,
        &PackageLists,
        &PackageListSize
        );
  if (EFI_ERROR (s)) {
    ret = 1;
    goto exit;
  }


  PackageListIndex = 0;
  PackageList = PackageLists;
  while (PackageListSize > sizeof (EFI_HII_PACKAGE_LIST_HEADER)) {

    verboseInfo ("Package List #%d\n",
                 PackageListIndex
                 );
    verboseInfo ("  Offset: 0x%08x\n",
                 (UINT32)((UINT8 *)PackageList - (UINT8 *)PackageLists));

    SysStrAFromGuid (&guid, &PackageList->PackageListGuid);
    verboseInfo ("  GUID:   %s\n", SysStrAGetData(&guid));

    if (PackageList->PackageLength > PackageListSize) {
      printf ("error: package list extends beyond end of buffer. "

              "%d bytes in package list. %d bytes in buffer.\n",
              PackageList->PackageLength,
              PackageListSize
              );
      ret = 1;
      goto exit;
    }

    s = UefiHiiParsePkgList (
          PackageList,
          &PLHandle
          );
    if (EFI_ERROR(s)) {
      printf ("error: unable to parse package list.\n");
      ret = 1;
      goto exit;
    }

    if (!SysArrayAppend (&gPackageLists, &PLHandle)) {
      printf ("fatal: out of memory.\n");
      exit (1);
    }


    PackageListIndex++;
    PackageListSize -= PackageList->PackageLength;
    PackageList =

      (EFI_HII_PACKAGE_LIST_HEADER *)
      ((UINT8*)PackageList + PackageList->PackageLength);
  }

  if (PackageListSize != 0) {
    printf("error: HII database array of package lists did not "

           "end on an even boundary.\n");
    ret = 1;
    goto exit;
  }

  ret = 0;
exit:
  SysStrAEmpty (&guid);
  return ret;
}

In this section, we use the library function UefiHiiExportPackageListsA to grab all of the HII package lists from the database into one big buffer. More details on that function later. Then the function UefiHiiParsePkgList() runs through the data, creates a package list handle for each package list found and adds that handle to an array. Each handle is associated with a single package list.

I use a handle here to abstract the relationship between the application and the actual data structures inside the parsing library. This allows me to refactor the code later without messing up the apps that depend on it.

Package Lists are really just big containers for zero or more Packages, identified by a GUID. The GUID is just an identifier that allows the user to uniquely identify a package list in the database. Packages, in turn, are containers for all sorts of interesting things, like Forms, Strings, Fonts, Images, Animations, Keyboard Layouts or OEM data. I've talked about some of these before in the early days of my blog.

UefiHiiParsePkgList()

Inside the UefiHiiParsePkgList(), each handle is actually a pointer to a private data structure constructed by the library:

typedef struct _UEFI_HII_PACKAGE_LIST_P {
  SYS_OBJ Obj;                     // standard object structure. must be first.


  EFI_GUID Id;                     // package list identifier.
  SYS_LIST_O Packages;             // list of packages in package list.
} UEFI_HII_PACKAGE_LIST_P;

#define UEFI_HII_PACKAGE_LIST_SIGNATURE 0x4C504855 // "UHPL"

typedef UEFI_HII_PACKAGE_LIST_P UefiHiiPackageListP;
This structure uses the same object model (SysObj) from my library, which means every instance contains function pointers to instances of Init(), Empty(), Copy(), IsValid() and Dump() as well as a signature so that we can validate object pointers. The member 'Packages' is an Object List container where each list entry is an Object. So when the List container is emptied, all the memory will be freed automatically.

So, each package list is parsed by this function, and a single object of type UEFI_HII_PACKAGE_LIST_P is created to represent it. The pointer is typecast into the package list handle type and returned. 

Here's the actual code:

EFI_STATUS
UefiHiiParsePkgList (
  IN EFI_HII_PACKAGE_LIST_HEADER *PkgList,
  OUT UEFI_HII_PACKAGE_LIST *PLHandle
  )
{
  EFI_STATUS s;
  EFI_HII_PACKAGE_HEADER *Pkg;
  UINT32 PkgListSize;
  UEFI_HII_PACKAGE PHandle;
  UINT32 PkgIndex;


  if (PkgList == NULL || PLHandle == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  if (PkgList->PackageLength < sizeof (EFI_HII_PACKAGE_LIST_HEADER)) {
    return EFI_INVALID_PARAMETER;
  }


  ...

  *PLHandle = (UEFI_HII_PACKAGE_LIST) SysNew (UefiHiiPackageListP);
  if (*PLHandle == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  memcpy (
   &((UEFI_HII_PACKAGE_LIST_P *)(*PLHandle))->Id,
   &PkgList->PackageListGuid,
   sizeof (EFI_GUID)
   );

  PkgIndex = 0;
  PkgListSize = PkgList->PackageLength - sizeof (EFI_HII_PACKAGE_LIST_HEADER);
  Pkg = (EFI_HII_PACKAGE_HEADER *)(PkgList + 1);
  while (PkgListSize >= sizeof (EFI_HII_PACKAGE_HEADER)) {
    ...
   

    if (Pkg->Length > PkgListSize) {
      return EFI_VOLUME_CORRUPTED;
    }

    s = UefiHiiParsePkg (
          Pkg,
          &PHandle
          );
    if (EFI_ERROR(s)) {
      return s;
    }

   
    s = UefiHiiSetPkgPkgList (PHandle, *PLHandle);
    if (EFI_ERROR (s)) {
      return s;
    }


    PkgIndex++;
    PkgListSize -= Pkg->Length;
    Pkg = (EFI_HII_PACKAGE_HEADER *)((UINT8 *)Pkg + Pkg->Length);
  }

  if (PkgListSize != 0) {
    return EFI_VOLUME_CORRUPTED;
  }

  return EFI_SUCCESS;
}

After the package list header is the body of the package list, which consists of a series of variable-length package structures. Each fo the package structures contains is own length, so it is trivial to find the first one, parse it, mark it as belong to this package list (using UefiHiiSetPkgPkgList()) and then advancing the pointer to the next one. There is some error checking code to make sure we don't advance past the end of the buffer and that the end of the last package aligns exactly with the end of the package list body. The main work of parsing the individual packages is done using the function UefiHiiParsePkg().

Conclusion

At this point, we're just scratching the surface of the HII Database. Next week, we'll dive a little deeper into parsing the packages, introduce the UEFI_HII_PACKAGE_P object, and peek into what it means to parse the forms (IFR).

Extra Stuff: UefiHiiExportPackageListsA()

You don't need to read this part unless you are curious how my SysLib's HII database functions actually relate to the functions described in chapter 30 of the UEFI specification. This time, we talked about the function UefiHiiExportPackageListsA(), which acts as a handy wrapper for the HII Database function ExportPackageLists(). It takes care of the memory allocate details for you.

EFI_STATUS
UefiHiiExportPackageListsA (
  IN EFI_HII_HANDLE Handle,
  OUT EFI_HII_PACKAGE_LIST_HEADER **PackageList,
  OUT UINTN *PackageListSize
  )
{
  EFI_STATUS s;


  if (EFI_ERROR (_UefiHiiDatabaseProtocol())) {
    return EFI_NOT_FOUND;
  }


  if (PackageList == NULL || PackageListSize == NULL) {
    return EFI_INVALID_PARAMETER;
  }


  *PackageListSize = 0;
  s = gHiiDb->ExportPackageLists (
                gHiiDb,
                Handle,
                PackageListSize,
                *PackageList
                );

  if (!EFI_ERROR (s)) {
    return s;
  } else if (s != EFI_BUFFER_TOO_SMALL) {
    return s;
  }


  *PackageList = (EFI_HII_PACKAGE_LIST_HEADER *)malloc (*PackageListSize);
  if (*PackageList == NULL) {
    return EFI_BUFFER_TOO_SMALL;
  }

  s = gHiiDb->ExportPackageLists (
                gHiiDb,
                Handle,
                PackageListSize,
                *PackageList
                );
  return s;
}


The function ExportPackageLists() is from the HII Database protocol and takes all of the package lists from the HII Database and puts them in a big buffer. This is done with two calls. The first call returns how much size is actually needed. The second call actually gets the data.