UEFI News and Commentary

Saturday, November 17, 2012

HOW TO: Disassembling the HII Database (Part 5)

During this series, we've been looking at how to disassemble the contents of the UEFI HII database, which is described in chapter 28 of the UEFI specification. So far, we've managed to take apart the package lists and break those into the different types of packages. The Forms type of package are encoded using Internal Forms Representation (IFR), a binary format made up of opcodes. Each opcode describes an object and opcodes can be nested inside of other opcodes or, as we say in HII, they are child objects in the parent object's scope. In part 4, we looked at the first sets of opcodes: Form Sets, which are collections of forms, and the 3 different types of variable stores (buffer, name/value and EFI variable), which describe how configuration settings map back to non-volatile storage.

This time we'll take a look at two more object types (Default Stores and Forms) that can be direct children of Form Sets.

Default Stores

Default Stores are collections of default configuration setting (Question) values, grouped by purpose. There are four different "standard" types of default stores
  1. Normal - The normal set of defaults.
  2. Manufacturing - The defaults to use during manufacturing.
  3. Safe - The defaults to use to make the system as stable as possible.
  4. OEM - There is a large range of default store types set aside for special use by OEM forms.
The default values can be loaded using the RESET_BUTTON Statement or through a browser-specific mechanism.

There are four ways to assign a default value to a specific default store. These are listed from lowest priority to highest priority:

  1. CHECKBOX Question Flag. For this specific type of Question, a default value of On or Off can be assigned to either the Normal or Manufacturing default store type.
  2. Question's Child ONE_OF_OPTION. Question's can have child objects that allow a user-readable name to be assigned to a specific value. Another set of flags also specifies whether the value should serve as the Normal or Manufacturing default store type for the parent Question.
  3. DEFAULT Object. Question's can have child objects that allow a value be assigned to a any default store type.
  4. Question Callback. Question's can provide a callback function that optionally provides a value for any default store type.

EFI_IFR_DEFAULT_STORE

The actual IFR encoding for the structure looks like this:
 typedef struct _EFI_IFR_DEFAULTSTORE {   EFI_IFR_OP_HEADER Header;  EFI_STRING_ID DefaultName;   UINT16 DefaultId; } EFI_IFR_DEFAULTSTORE; The Header  is the normal IFR opcode header. DEFAULT_STORE opcodes never have any child objects, so the Scope bit is never set. The DefaultId identifies the default store type (0 = Normal, 1 = Manufacturing, 2 = Safe and other platform-specific types are 0x4000+). The DefaultName gives the string identifier for a string that gives a user-readable name for the default store. While a platform might already have a name for 0,1 and 2, it is unlikely it would have one for any others. 

UEFI_HII_DEFAULT_STORE

This IFR opcode gets translated into the default store container: typedef struct _UEFI_HII_DEFAULT_STORE_P {
  SYS_OBJ Obj;
 
 

  EFI_STRING_ID Name;                   // default store name.
  UINT16 Id;                            // default store id.
 
 
} UEFI_HII_DEFAULT_STORE_P;
typedef UEFI_HII_DEFAULT_STORE_P UefiHiiDefaultStoreP;


UefiHiiParseFormDefaultStoreOp()

Pretty straight forward. Now here's the parsing code, which checks for input errors. Then it verifies that the default store has not already been declared for this form set. DefaultIds are global across all forms in a form set, so it is possible that they are duplicates. If no duplicate, a new Default Store container is created, initialized and added to the current Form Set.

EFI_STATUS
UefiHiiParseFormDefaultStoreOp (
  IN EFI_IFR_OP_HEADER *Op,
  IN OUT UEFI_HII_FORM_PKG_STATE *S
  )
{
  EFI_STATUS s;
  EFI_IFR_DEFAULTSTORE *DefaultStoreOp;
  UEFI_HII_DEFAULT_STORE_P *DefaultStoreP;
  SYS_LIST_POS pos;
 
 

  DefaultStoreOp = (EFI_IFR_DEFAULTSTORE *) Op;

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

  ...dump out debug information...

  for (pos = NULL;
       SysListGetNext (&S->FormSetP->DefaultStores, &pos, &DefaultStoreP); ) {
 
 
 
    if (!SysIsValid (DefaultStoreP)) {
      continue;
    }
 
 
 
    if (DefaultStoreP->Id == DefaultStoreOp->DefaultId) {
      return EFI_SUCCESS;
    }
  }
 
 

  DefaultStoreP = SysNew (UefiHiiDefaultStoreP);
  if (DefaultStoreP == NULL) {
    s = EFI_OUT_OF_RESOURCES;
    goto exit;
  }
 
 

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

  s = EFI_SUCCESS;
exit:
  return s;
}
 

Forms

With Forms, we really get into the heart of the HII parsing. Each Form has the following attributes:

  1. Form Identifier. Uniquely identifies this form from all others in the same Form Set.
  2. Title String. String to associate with this Form.
  3. Image. Optional image to assocate with this Form. Specified by a nested IMAGE opcode.
  4. Animation. Optional animation to associate with this Form. Specified by a nested ANIMATION opcode.
  5. List of Rules. These are essentially shortcuts or optimizations or "functions" that can be used by any expression in the Form. Each Rule is associated with a Rule Identiifer. Specified by a nested RULE opcode.
  6. List of Statements. These are either static display items (pure Statements) or display items with configurable values (Questions). There are a dozen or so Statement and Question opcodes.
  7. Modal Flag. Indicates whether the user can navigate easily away from this Form (FALSE, using menus) or else must click a button (TRUE). Specified by a nested MODAL_TAG opcode.
  8. Locked. Indicates that the Form and its contents are protected from binary editing. Specified by a nested LOCKED opcode.
  9. Methods. A method indicates a style by which a specific form should be interacted with. This is designed to support the way in the configuration settings controlled by a form would interact with an industry standard or OEM specification. There is only one method described in the UEFI specification (the standard method) and this is the method type assigned to all forms introduced with the FORM opcode. To create any other form types, the FORM_MAP opcode should be used.

EFI_IFR_FORM, EFI_IFR_FORM_MAP

Here are the two IFR opcode definitions:

typedef struct _EFI_IFR_FORM {
  EFI_IFR_OP_HEADER Header;
 
  EFI_FORM_ID FormId;
  EFI_STRING_ID FormTitle;
} EFI_IFR_FORM;
 


typedef struct _EFI_IFR_FORM_MAP_METHOD {
  EFI_STRING_ID MethodTitle;
  EFI_GUID MethodIdentifier;
} EFI_IFR_FORM_MAP_METHOD;


typedef struct _EFI_IFR_FORM_MAP {
  EFI_IFR_OP_HEADER Header;
  EFI_FORM_ID FormId;
//EFI_IFR_FORM_MAP_METHOD Methods[];
} EFI_IFR_FORM_MAP; 

 
The FORM_MAP opcode is variable in length, holding up to three Methods names and GUIDs. The FORM opcode is of fixed length.

UEFI_HII_FORM_P

The application also has a Form container:
 
typedef struct _UEFI_HII_FORM_P {
  SYS_OBJ Obj;
 
  EFI_FORM_ID Id;
  EFI_STRING_ID Title;
  EFI_IMAGE_ID Image;
  EFI_ANIMATION_ID Animation;
 
  BOOLEAN Locked;
  BOOLEAN Modal;                     
 
  SYS_ARRAY MethodIds;
  SYS_ARRAY MethodTitles;
 
  SYS_MAP_U16P Rules;                   // rule id->expression
  SYS_LIST_O Stmts;                     // statements on form, in order.
} UEFI_HII_FORM_P;
 
typedef UEFI_HII_FORM_P UefiHiiFormP;
The Form container contains fields for all of the various attributes. Some of the attributes are optional and only filled further if a nested child opcode contains additional detail.
 

UefiHiiParseFormFormOp(), UefiHIiParseFormFormMapOp()

The code for the FORM and FORM_MAP opcodes is very similar. Both check the input parameters, create a Form container, fill in the Method type and title and add the Form to the Form Set. Also, each changes the current Form Package parsing state so that the current Form is this Form.
 
The Form Map has a small amount of additional code to handle the optional extra Method information, while Form sets a default Method. In this way, the two opcodes are handled identically.
 
EFI_STATUS
UefiHiiParseFormFormOp (
  IN CONST EFI_IFR_OP_HEADER *Op,
  IN OUT UEFI_HII_FORM_PKG_STATE *S
  )
{
  EFI_STATUS s;
  EFI_IFR_FORM *FormOp;
  UINT16 MethodTitle;
  MethodTitle = 0;

  FormOp = (EFI_IFR_FORM *) Op;
 
  if (!UefiHiiOpIsValid (Op, S) ||
      !UefiHiiOpInFormSet (Op, S) ||
      !UefiHiiOpNotInForm (Op, S)) {
    return EFI_INVALID_PARAMETER;
  }
 
  ...dump out form information...
 
  S->FormP = SysNew (UefiHiiFormP);
  if (S->FormP == NULL) {
    s = EFI_OUT_OF_RESOURCES;
    goto exit;
  }
  S->FormP->Id = FormOp->FormId;
  S->FormP->Title = FormOp->FormTitle;
 
  SysArrayAppend (&S->FormP->MethodIds, &gEfiHiiStandardFormGuid);
  SysArrayAppend (&S->FormP->MethodTitles, &MethodTitle);
  if (!SysListAddTail (&S->FormSetP->Forms, S->FormP)) {
    SysDelete (S->FormP);
    S->FormP = NULL;
    s = EFI_OUT_OF_RESOURCES;
    goto exit;
  }
  s = EFI_SUCCESS;
exit:
  return s;
}
  
EFI_STATUS
UefiHiiParseFormFormMapOp (
  IN CONST EFI_IFR_OP_HEADER *Op,
  IN OUT UEFI_HII_FORM_PKG_STATE *S
  )
{
  UINT32 i;
  EFI_STATUS s;
  EFI_IFR_FORM_MAP *FormMapOp;
  EFI_IFR_FORM_MAP_METHOD *FormMapMethod;
  UINT32 FormMapMethodCount;
  FormMapOp = (EFI_IFR_FORM_MAP *) Op;
  if (!UefiHiiOpIsValid (Op, S) ||
      !UefiHiiOpInFormSet (Op, S) ||
      !UefiHiiOpNotInForm (Op, S)) {
    return EFI_INVALID_PARAMETER;
  }
 
  FormMapMethod = (EFI_IFR_FORM_MAP_METHOD *)(FormMapOp + 1);
  FormMapMethodCount =
    (Op->Length - sizeof (EFI_IFR_FORM_MAP))/sizeof(EFI_IFR_FORM_MAP_METHOD);
  if (((FormMapMethodCount * sizeof (EFI_IFR_FORM_MAP_METHOD)) +
        sizeof (EFI_IFR_FORM_MAP)) != Op->Length) {
    return EFI_INVALID_PARAMETER;
  }
  if (FormMapMethodCount == 0) {
    return EFI_INVALID_PARAMETER;
  }
 
  ... dump out form map opcode information...
 
  S->FormP = SysNew (UefiHiiFormP);
  if (S->FormP == NULL) {
    SYSINFO ("out of memory.\n");
    s = EFI_OUT_OF_RESOURCES;
    goto exit;
  }
  S->FormP->Id = FormMapOp->FormId;
 
  for (i = 0; i < FormMapMethodCount; i++) {
    SysArrayAppend (&S->FormP->MethodTitles, &FormMapMethod[i].MethodTitle);
    SysArrayAppend (&S->FormP->MethodIds, &FormMapMethod[i].MethodIdentifier);
  }
  if (!SysListAddTail (&S->FormSetP->Forms, S->FormP)) {
    SysDelete(S->FormP);
    S->FormP = NULL;
    s = EFI_OUT_OF_RESOURCES;
    goto exit;
  }
  s = EFI_SUCCESS;
exit:
  SysEmpty (&Guid);
  return s;
}

UefiHiiParseFormFormEndOp()

This function is closed at the end of the FORM or FORM_MAP opcode's scope. All it does is update the Form Package parsing state so that there is no current Form.

EFI_STATUS
UefiHiiParseFormFormEndOp (
  IN CONST EFI_IFR_OP_HEADER *Op,
  IN OUT UEFI_HII_FORM_PKG_STATE *S
  )
{
  if (!UefiHiiOpIsValid (Op, S)) {
    return EFI_INVALID_PARAMETER;
  }
  S->FormP = NULL;
  return EFI_SUCCESS;
}

Conclusion

Now we've made it down to the Form level of the IFR parsing. Next, we will take a look at the Statement and the parsing and storage for the three pure statements (Text, Subtitle and Reset Button).

Until next time, keep digging deeper.

No comments: