UEFI News and Commentary

Wednesday, October 31, 2012

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

Now we'll start to get into the real details. The next section of code disassembles the Forms package. The Forms package is encoded as a series of variable-length data structures called opcodes. This encoding is called Internal Forms Representation or IFR.

Opcodes

Each opcode has the following structure:


First, there is the Opcode, which is an enumerated value that specifies what type of object is being described. Then there is a single Scope bit, which describes whether there are any nested objects. Then there is the Length of the whole opcode (in bytes), including the opcode header and any Optional Opcode Data. Some opcodes do not have any optional data. Others always have it. In all cases, the next opcode is always Length bytes from the current opcode, and the last byte of the last opcode will always align with the end of the Forms package.

Pretty simple. Even if you don't know what an opcode does or what it means, you can just skip it. But there are two wrinkles: GUIDed opcodes and scoped opcodes.

GUIDed Opcodes

Opcode 0x5f does not have any specified meaning, but it does have a specified structure.




This was designed as a get-out-of-jail-free card for forms browsers, so that they could implement extended functionality without risking compatibility problems. Each vendor that would like to provided extended functionality simply creates their own GUID and then modify their browser to do something different. Other browsers will not recognize the GUID and simply skip the opcode.

In fact, this capability is already used in the EDK2 implementation of UEFI found on tianocore.org. If you are interested, look at MdeModulePkg/Include/Guid/MdeModuleHii.h.

Scoped Opcodes

Then there are scoped opcodes. The Scope bit in the opcode header says that all opcodes which follow are nested within--or, in the scope of--this opcode until a matching END opcode (0x29) is found. Theoretically, there is no limit to the number of levels of nesting, but practically it is limited to 10 or so.

When one opcode is nested (or in the scope) of another opcode, it is called a child opcode and the other is called its parent opcode. Child opcodes somehow modify or augment the parent opcode. So, question-style opcodes (numeric, string, etc.) are found in the scope of a form opcode.

When parsing, it is important to keep track of the different scopes, because some opcodes can be used in different contexts. For example, an image opcode (which provides a bitmap) can be found as a child of a form set opcode, a form opcode, various statement opcodes and the one-of-option opcode.

UefiHiiParseFormPkg()

Form Packages are parsed by the function UefiHiiParseFormPkg(), which creates a Form Package object and then goes into a big loop that processes all all of the opcode structures, one by one.When processing the opcodes, one of three things happens:
  1. A new container is created for the object that opcode represents
  2. An existing container is updated with new information or
  3. The Form Package parsing state is updated.
Here is the function with the less interesting bits hidden for clarity:
 
EFI_STATUS
UefiHiiParseFormPkg (
  IN CONST EFI_IFR_OP_HEADER *Op,
  IN UINT32 Size,
  IN UEFI_HII_PACKAGE_P *PkgP,
  OUT SYS_OBJ **PkgData
  )
{
  UINT8 *OpData;
  EFI_STATUS s;
  UEFI_HII_FORM_PACKAGE_P *FormPkgP;
  UEFI_HII_FORM_PKG_STATE S;
  EFI_IFR_OP_HEADER *TempOp;
 
 
 
  ...do error checking on the input...
 
  OpData = (UINT8 *)Op;
  *PkgData = NULL;
 
  //
  // Create the Form Package container.
  //
  FormPkgP = SysNew (UefiHiiFormPackageP);
  if (FormPkgP == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }
  *PkgData = &FormPkgP->Obj;
 
 
  FormPkgP->Pkg = PkgP;                 // track the parent package.
 
  ...initialize the Form Package parsing state to defaults...
 
 
 
  while (Size >= sizeof (EFI_IFR_OP_HEADER) && Size >= Op->Length) {
    S.OpOffset = (UINT32)((UINT8 *)Op - OpData);
 
    ...dump out debug information...
 
    s = EFI_UNSUPPORTED;
    if (UefiFormPkgParse[Op->OpCode].OpCodeName != NULL) {
      if (UefiFormPkgParse[Op->OpCode].StartParse != NULL) {
        if (Op->Length < UefiFormPkgParse[Op->OpCode].OpCodeMinSize) {
          SYSINFO (
 
 
            "S2006 : 0x%08x : '%s' : Opcode size error. "
            "Expected at least %d bytes. Found %d bytes.\n",
            S.OpOffset,
            UefiHiiDumpFormOp (Op),
            UefiFormPkgParse[Op->OpCode].OpCodeMinSize,
            Op->Length);
        } else {
          UefiFormPkgParse[Op->OpCode].StartParse (Op, &S);
        }
      }
    } else {
      SysTrace ("Unknown IFR Opcode: 0x%02x\n", Op->OpCode);
    }
 
    //
    // If the opcode has scope, then push the current parent opcode pointer on
    // the stack. If the opcode is an IFR END opcode, then process the end of
    // scope and pop the current parent opcode pointer from the stack.
    //
    // There are some opcodes that *could* have scope, but did not this time.
    // We treat this as if we had found an END opcode, so that clean up is
    // consistent.
    //
    if (Op->OpCode == EFI_IFR_END_OP) {
      if (Op->Scope) {
        SYSINFO ("END: Opcode cannot have Scope bit set. Ignored\n");
      }
 
      if (SysListIsEmpty (&S.Scopes)) {
        SYSINFO ("END: Unexpected without matching Scoped opcode.\n");
        s = EFI_INVALID_PARAMETER;
        goto exit;
      }

      TempOp = S.ParentOp;
      S.ParentOp = (EFI_IFR_OP_HEADER *)SysListRemoveTail (&S.Scopes);

      if (UefiFormPkgParse[TempOp->OpCode].OpCodeName != NULL &&
          UefiFormPkgParse[TempOp->OpCode].EndParse != NULL) {
        s = UefiFormPkgParse[TempOp->OpCode].EndParse (TempOp, &S);
        if (EFI_ERROR (s)) {
          gUefiHiiTraceIndent--;
          goto exit;
        }
      } 

      gUefiHiiTraceIndent--;
    } else if (Op->Scope) {
      SysListAddTail (&S.Scopes, S.ParentOp);
      S.ParentOp = (EFI_IFR_OP_HEADER *)Op;
      gUefiHiiTraceIndent++;
    } else {
      if (UefiFormPkgParse[Op->OpCode].OpCodeName != NULL && 

          UefiFormPkgParse[Op->OpCode].EndParse != NULL) {
        s = UefiFormPkgParse[Op->OpCode].EndParse (Op, &S);
        if (EFI_ERROR (s)) {
          goto exit;
        }
      }
    }
     
    //
    // Move to the next opcode.
    //
    Size -= Op->Length;
    Op = (EFI_IFR_OP_HEADER *)((UINT8 *)Op + Op->Length);
  }
 
 
  s = EFI_SUCCESS;
exit:
 
 
  ...clean up parsing data structures...
  return EFI_SUCCESS;
}


There are three key data structures used in this function:
  1. S. S is a structure of type UEFI_HII_FORM_PKG_STATE, which contains the current parsing state. It keeps track of the current scope and important current objects, like the current question or the current form. It gets passed around to the parsing functions and updated as the opcodes are processed. We will talk about this a bit more, below.
  2. UefiFormPkgParse[]. This array contains one entry for every possible opcode value, from 0x00 to 0xff. It contains a pointer to the name of the opcode, a pointer the function to call when an opcode is first processed and a function to call when an opcode's scope is closed. For the purposes of this function, an opcode's scope is closed when (a) it has the Scope bit set and then a matching END opcode is found or (b) it does not have the Scope bit set. We do it this way because many opcodes can have child opcodes or not, but some processing has to wait until all child opcodes (if any) have been processed.
  3. FormPkgP. Pointer to the Form Package container object. This is the pointer that is placed into the PkgDataP member of the current Package container for Forms Packages.   

UEFI_HII_FORM_PKG_STATE

This structure holds the current parsing state:

typedef struct _UEFI_HII_FORM_PKG_STATE {
  UINT32 OpOffset;                     

  EFI_IFR_OP_HEADER *ParentOp;         
  UEFI_HII_FORM_PACKAGE_P *FormPkgP;   

  SYS_LIST_O DisableIfP;             
  SYS_LIST_O SuppressIfP;            
  SYS_LIST_O GrayOutIfP;               


  UEFI_HII_FORM_SET_P *FormSetP;     
  UEFI_HII_FORM_P *FormP;            
  UEFI_HII_STMT_P *StmtP;            
  UEFI_HII_OPTION_P *OptionP;        
  UEFI_HII_DEFAULT_P *DefaultP;        


  SYS_LIST Scopes;                   
  SYS_LIST_O ExprP;                  
  SYS_LIST_O ValueP;                   
} UEFI_HII_FORM_PKG_STATE;


The OpOffset field records the offset of the current opcode from the beginning of the forms package. This is used to help display errors or debug information. The ParentOp points to the parent opcode whose scope contains the current opcode, or NULL if it is at the top level. The FormPkgP points to the Form Package container associated with the forms package being parsed.

The next three object lists are used to hold expressions for disabling, suppressing or graying-out various other IFR objects. In IFR, these are actually parent objects and objects like questions and statements and one-of-options are inside their scope. But I prefer to think of these expressions as attributes of the question, so what I do is keep a running list of all of the active parent expressions. Then, when I find a question or a statement or some other IFR object, I make a copy of the active expressions in my container. This is not only my preference (to make these attributes), but it also simplifies some sorts of parent-child error checking.

The next five members point to the containers for important objects that are currently active. For example, once we process the form set opcode and create the Form Set container, we place the pointer in FormSetP. When we leave the form set opcode's scope, FormSetP is set to NULL. It so happens that forms must be in form sets, statements must be in forms and defaults/options must be in certain statements.

The Scopes list is a first-in-last-out stack that contains the pointers to parent IFR opcodes. Each time we enter a scope, the current parent opcode (ParentOp) is pushed on to the stack and each time we exit a scope, the top element in the stack is popped into ParentOp.

The ExprP object list contains the current expression stack. Expressions in IFR operate on an expression stack and are encoded in prefix-notation (operands are encountered before operators). I prefer to store these in a tree structure instead (that is, an operator with zero or more operands). So, when we are parsing expressions, we pop zero or more expression containers from the expression stack and push the newly created expression container.

The ValueP object list contains the results of the most recent IFR value opcode, which will then be attached either to a one-of container or a statement container.

UEFI_HII_FORM_PACKAGE_P

This structure holds information about the Form Package:

typedef struct _UEFI_HII_FORM_PACKAGE_P {
   SYS_OBJ Obj;            


   UEFI_HII_PACKAGE_P *Pkg;
   SYS_LIST_O FormSets;    

} UEFI_HII_FORM_PACKAGE_P;

typedef UEFI_HII_FORM_PACKAGE_P UefiHiiFormPackageP;

The top-most encoded IFR objects in form package are always form sets. So FormSets is an object list containing form set containers for all IFR form set objects, in the order which they were encountered in the Form Package.

Conclusion

Now that we've made it to actually parsing opcodes, things are pretty straight forward. Now there will be either a container object for whatever is encountered in the IFR, or the IFR contents will modify previously existing container objects. There are containers for form sets, forms, statements, one-of options, values, expressions, variable stores, default stores and defaults. In the next article, we will delve into form sets and forms.

GNU EFI

If you really want to write UEFI applications under Linux (using GPL instead of BSD), you can, with the GNU-EFI project on SourceForge. The latest version was released about 6 months ago and contains libraries and header files to creating UEFI executables under a Linux/GCC toolchain. Since I've worked mostly with the EDK2 setup at tianocore.org, it was a fresh look at what it really takes to create an app instead of the conventions I am used to.

Matthew Garrett does a pretty good job of walking through the process of writing a basic app on his blog, including how to print things, how to use the basic UEFI services, etc. as well as giving the "why" of things.

The biggest hang-up I have is realted to the uefi_call_wrapper(). It is used for some CPU architectures because the Linux calling convention and the UEFI calling convention don't match. But having to use the call wrapper makes code harder to read and defeats the parameter error checking (because it uses varargs underneath). The EDK2 toolchain uses the EFIAPI modifier instead, even for GCC and this allows the compiler to automatically determine when to use the different calling convention. I'm not sure why GNU-EFI doesn't do the same.

There are some other minor nits. For example, there are a lot of constants in the header files that don't make sense. Why is the firmware vendor set to "INTEL" and why is the specification revision set to EFI 1.02?

It is fairly complete for basic UEFI apps. And it looks friendly to a Linux developer.

Tuesday, October 30, 2012

Moving Source Code to SourceForge.net

Some great news: we have created a page on SourceForge.net to host the BSD-licensed source code described on this page. So far we have uploaded the SysLib into the SubVersion repository. Soon, some of the graphics code will go there. Then, as we finish each project, we will put the relevant portions there as well. Here's the link: https://sourceforge.net/projects/syslibforuefi/

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.