7. PEIMs

7.1. Introduction

A Pre-EFI Initialization Module (PEIM) represents a unit of code and/or data. It abstracts domain-specific logic and is analogous to a DXE driver. As such, a given group of PEIMs for a platform deployment might include a set of the following:

  • Platform-specific PEIMs

  • Processor-specific PEIMs

  • Chipset-specific PEIMs

  • PEI CIS-prescribed architectural PEIMs

  • Miscellaneous PEIMs

The PEIM encapsulation allows for a platform builder to use services for a given hardware technology without having to build the source of this technology or necessarily understand its implementation. A PEIM-to-PEIM Interface (PPI) is the means by which to abstract hardware-specific complexities to a platform builder’s PEIM. As such, PEIMs can work in concert with other PEIMs using PPIs.

In addition, PEIMs can ascertain a fixed set of services that are always available through the PEI Services Table.

Finally, because the PEIM represents the basic unit of execution beyond the Security (SEC) phase and the PEI Foundation, there will always be some non-zero-sized collection of PEIMs in a platform.

7.2. PEIM Structure

7.2.1. PEIM Structure Overview

Each PEI Module (PEIM) is stored in a file. It consists of the following:

  • Standard header

  • Execute-in-place code/data section

  • Optional relocation information

  • Authentication information, if present

The PEIM binary image can be executed in place from its location in the firmware volume (FV) or from a compressed component that will be shadowed after permanent memory has been installed. The executable section of the PEIM may be either position-dependent or position-independent code. If the executable section of the PEIM is position-dependent code, relocation information must be provided in the PEIM image to allow FV store software to relocate the image to a different location than it is compiled.

Typical PEIM Layout in a Firmware File depicts the typical layout of a PEIM.

_images/V1_PEIMs-2.png

Fig. 7.1 Typical PEIM Layout in a Firmware File

7.2.2. Relocation Information

7.2.2.1. Position-Dependent Code

PEIMs that are developed using position-dependent code require relocation information. When an image in a firmware volume (FV) is updated, the update software will use the relocation information to fix the code image according to the module’s location in the FV. The relocation is done on the authenticated image; therefore, software verifying the integrity of the image must undo the relocation during the verification process.

There is no explicit pointer to this data. Instead, the update and verification tool will know that the image is actually stored as PE32 if the Pe32Image bit is set in the header EFI_COMMON_SECTION_HEADER or EFI_COMMON_SECTION_HEADER2 ; types EFI_COMMON_SECTION_HEADER and EFI_COMMON_SECTION_HEADER2 are defined in the Platform Initialization Specification, Volume 3 . The PE32 specification, in turn, will be used to ascertain the relocation records.

7.2.2.2. Position-Independent Code

If the PEIM is written in position-independent code, then its entry point shall be at the lowest address in the section. This method is useful for creating PEIMs for the Itanium® processor family.

7.2.2.3. Relocation Information Format

The relocations will be contained in a TE or PE32+ image. See the Microsoft Portable Executable and Common Object File Format Specification for more information. The determination of whether the image subscribes to the PE32 image format or is position-independent assembly language is provided by the firmware volume section type. The PEIM that is formatted as PE/COFF will always be linked against a base address of zero. This allows for support of signature checking.

The section may also be compressed if there is a compression encapsulation section.

7.2.3. Authentication Information

This section describes in more detail, the means by which authentication information could be contained in a section of type EFI_SECTION_GUID_DEFINED (see the Platform Initialization Specification , Volume 3, for more information on section types). The information contained in this section could be one of the following:

  • A cryptographic-quality hash computed across the PEIM image

  • A simple checksum

  • A CRC

The GUID defines the meaning of the associated encapsulated data. The relocation section is needed to undo the fix-ups done on the image so the hash that was computed at build time can be confirmed. In other words, the build of a PEIM image is linked against zero, but the update tool will relocate the PEIM image for its execute-in-place address (at least for images that are not position-independent code). Any signing information is calculated on the image after the image has been linked against an address of zero. The relocations on the image will have to be “undone” to determine if the image has been modified.

The image must be linked against address zero by the PEIM provider. The build or update tool will apply the appropriate relocations. The linkage against address zero is key because it allows a subsequent undoing of the relocations.

7.3. PEIM Invocation Entry Point

7.3.1. EFI_PEIM_ENTRY_POINT2

Summary

The PEI Dispatcher will invoke each PEIM one time.

Prototype

typedef
EFI_STATUS
(EFIAPI *EFI_PEIM_ENTRY_POINT2) (
  IN EFI_PEI_FILE_HANDLE       FileHandle,
  IN CONST EFI_PEI_SERVICES    **PeiServices
  );

Parameters

FileHandle

Handle of the file being invoked. Type EFI_PEI_FILE_HANDLE is defined in FfsFindNextFile() .

PeiServices

Describes the list of possible PEI Services.

Description

This function is the entry point for a PEIM. EFI_IMAGE_ENTRY_POINT2 is the equivalent of this state in the UEFI/DXE environment; see the DXE CIS for its definition.

The motivation behind this definition is that the firmware file system has the provision to mark a file as being both a PEIM and DXE driver. The result of this name would be that both the PEI Dispatcher and the DXE Dispatcher would attempt to execute the module. In doing so, it is incumbent upon the code in the entry point of the driver to decide what services are exposed, namely whether to make boot service and runtime calls into the UEFI System Table or to make calls into the PEI Services Table. The means by which to make this decision entail examining the second argument on entry, which is a pointer to the respective foundation’s exported service-call table. Both PEI and UEFI/DXE have a common header, EFI_TABLE_HEADER , for the table. The code in the PEIM or DXE driver will examine the Arg2->Hdr->Signature . If it is EFI_SYSTEM_TABLE_SIGNATURE , the code will assume DXE driver behavior; if it is PEI_SERVICES_SIGNATURE , the code will assume PEIM behavior.

Status Codes Returned

EFI_SUCCESS

The service completed successfully

< 0

There was an error

7.4. PEIM Descriptors

7.4.1. PEIM Descriptors Overview

A PEIM descriptor is the data structure used by PEIMs to export service entry points and data. The descriptor contains the following:

  • Flags

  • A pointer to a GUID

  • A pointer to data

The latter data can include a list of pointers to functions and/or data. It is the function pointers that are commonly referred to as PEIM-to-PEIM Interfaces (PPIs), and the PPI is the unit of software across which PEIMs can invoke services from other PEIMs.

A PEIM also uses a PEIM descriptor to export a service to the PEI Foundation into which the PEI Foundation will pass control in response to an event, namely “notifying” the callback when a PPI is installed or reinstalled. As such, PEIM descriptors serve the dual role of exposing the following:

  • A callable interface/data for other PEIMs

  • A callback interface from the perspective of the PEI Foundation

7.4.2. EFI_PEI_DESCRIPTOR

Summary

This data structure is the means by which callable services are installed and notifications are registered in the PEI phase.

Prototype

typedef union {
  EFI_PEI_NOTIFY_DESCRIPTOR    Notify;
  EFI_PEI_PPI_DESCRIPTOR       Ppi;
} EFI_PEI_DESCRIPTOR;

Parameters

Notify

The typedef structure of the notification descriptor. See the EFI_PEI_NOTIFY_DESCRIPTOR type definition.

Ppi

The typedef structure of the PPI descriptor. See the EFI_PEI_PPI_DESCRIPTOR type definition.

Description

EFI_PEI_DESCRIPTOR is a data structure that can be either a PPI descriptor or a notification descriptor. A PPI descriptor is used to expose callable services to other PEIMs. A notification descriptor is used to register for a notification or callback when a given PPI is installed.

7.4.3. EFI_PEI_NOTIFY_DESCRIPTOR

Summary

The data structure in a given PEIM that tells the PEI Foundation where to invoke the notification service.

Prototype

typedef struct _EFI_PEI_NOTIFY_DESCRIPTOR {
  UINTN                        Flags;
  EFI_GUID                     *Guid;
  EFI_PEIM_NOTIFY_ENTRY_POINT  Notify;
} EFI_PEI_NOTIFY_DESCRIPTOR;

Parameters

Flags

Details if the type of notification is callback or dispatch.

Guid

The address of the EFI_GUID that names the interface.

Notify

Address of the notification callback function itself within the PEIM. Type EFI_PEIM_NOTIFY_ENTRY_POINT is defined in “Related Definitions” below.

Description

EFI_PEI_NOTIFY_DESCRIPTOR is a data structure that is used by a PEIM that needs to be called back when a PPI is installed or reinstalled. The notification is similar to the RegisterProtocolNotify() function in the UEFI 2.0 Specification. The use model is complementary to the dependency expression (depex) and is as follows:

  • A PEIM expresses the PPIs that it must have to execute in its depex list.

  • A PEIM expresses any other PEIMs that it needs, perhaps at some later time, in EFI_PEI_NOTIFY_DESCRIPTOR .

The latter data structure includes the GUID of the PPI for which the PEIM publishing the notification would like to be reinvoked.

Following is an example of the notification use model for EFI_PEI_PERMANENT_MEMORY_INSTALLED_PPI . In this example, a PEIM called SamplePeim executes early in the PEI phase before main memory is available. However, SamplePeim also needs to create some large data structure later in the PEI phase. As such, SamplePeim has a NULL depex, but after its entry point is processed, it needs to call NotifyPpi() with a EFI_PEI_NOTIFY_DESCRIPTOR , where the notification descriptor includes the following:

  • A reference to EFI_PEI_PERMANENT_MEMORY_INSTALLED_PPI

  • A reference to a function within this same PEIM called SampleCallback

When the PEI Foundation finally migrates the system from temporary to permanent memory and installs the EFI_PEI_PERMANENT_MEMORY_INSTALLED_PPI , the PEI Foundation assesses if there are any pending notifications on this PPI. After the PEI Foundation discovers the descriptor from SamplePeim, the PEI Foundation invokes SampleCallback.

With respect to the Flags parameter, the difference between callback and dispatch mode is as follows:

  • Callback mode: Invokes all of the agents that are registered for notification immediately after the PPI is installed.

  • Dispatch mode: Calls the agents that are registered for notification only after the PEIM that installs the PPI in question has returned to the PEI Foundation.

The callback mechanism will give a better quality of service, but it has the downside of possibly deepening the use of the stack (i.e., the agent that installed the PPI that engenders the notification is a PEIM itself that has used the stack already). The dispatcher mode, however, is better from a stack-usage perspective in that when the PEI Foundation invokes the agents that want notification, the stack has returned to the minimum stack usage of just the PEI Foundation.

typedef
EFI_STATUS
(EFIAPI *EFI_PEIM_NOTIFY_ENTRY_POINT) (
  IN EFI_PEI_SERVICES            **PeiServices,
  IN EFI_PEI_NOTIFY_DESCRIPTOR   *NotifyDescriptor,
  IN VOID                        *Ppi
  );
PeiServices

Indirect reference to the PEI Services Table.

NotifyDescriptor

Address of the notification descriptor data structure. Type EFI_PEI_NOTIFY_DESCRIPTOR is defined above.

Ppi

Address of the PPI that was installed. The status code returned from this function is ignored.

7.4.4. EFI_PEI_PPI_DESCRIPTOR

Summary

The data structure through which a PEIM describes available services to the PEI Foundation.

Prototype

typedef struct {
  UINTN                  Flags;
  EFI_GUID               *Guid;
  VOID                   *Ppi;
} EFI_PEI_PPI_DESCRIPTOR;

Parameters

Flags

This field is a set of flags describing the characteristics of this imported table entry. See “Related Definitions” below for possible flag values.

Guid

The address of the EFI_GUID that names the interface.

Ppi

A pointer to the PPI. It contains the information necessary to install a service.

Description

EFI_PEI_PPI_DESCRIPTOR is a data structure that is within the body of a PEIM or created by a PEIM. It includes the following:

  • Information about the nature of the service

  • A reference to a GUID naming the service

  • An associated pointer to either a function or data related to the service

There can be a catenation of one or more of these EFI_PEI_PPI_DESCRIPTOR s. The final descriptor will have the EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST flag set to indicate to the PEI Foundation how many of the descriptors need to be added to the PPI database within the PEI Foundation. The PEI Services that references this data structure include InstallPpi() , ReinstallPpi() , and LocatePpi() .

//
// PEI PPI Services List Descriptors
//

#define EFI_PEI_PPI_DESCRIPTOR_PIC              0x00000001
#define EFI_PEI_PPI_DESCRIPTOR_PPI              0x00000010
#define EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK  0x00000020
#define EFI_PEI_PPI_DESCRIPTOR_NOTIFY_DISPATCH  0x00000040
#define EFI_PEI_PPI_DESCRIPTOR_NOTIFY_TYPES     0x00000060
#define EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST   0x80000000

PEI PPI Services List Descriptors provides descriptions of the fields in the above definition:

Table 7.1 PEI PPI Services List Descriptors

Descriptor

Description

EFI_PEI_PPI_DESCRIPTOR_PIC

When set to 1 this designates that the PPI described by the structure is position independent code PIC

EFI_PEI_PPI_DESCRIPTOR_PPI

When set to 1 this designates that the PPI described by this structure is a normal PPI As such it should be callable by the conventional PEI infrastructure

EFI_PE I_PPI_DESCRIPTOR_NOTIFY_CALLBACK

When set to 1 this flag designates that the service registered in the descriptor is to be invoked at callback This means that if the PPI is installed for which the listener registers a notification then the callback routine will be immediately invoked The danger herein is that the callback will inherit whatever depth had been traversed up to and including this call

EFI_PE I_PPI_DESCRIPTOR_NOTIFY_DISPATCH

When set to 1 this flag designates that the service registered in the descriptor is to be invoked at dispatch This means that if the PPI is installed for which the listener registers a notification then the callback routine will be deferred until the PEIM calling context returns to the PEI Foundation Prior to invocation of the next PEIM the notifications will be dispatched The advantage herein is that the callback will have the maximum available stack depth as any other PEIM

EFI _PEI_PPI_DESCRIPTOR_NOTIFY_TYPES

When set to 1 this flag designates that this is a notification style PPI

EFI_P EI_PPI_DESCRIPTOR_TERMINATE_LIST

This flag is set to 1 in the last structure entry in the list of PEI PPI descriptors This flag is used by the PEI Foundation Services to know that there are no additional interfaces to install

7.5. PEIM-to-PEIM Communication

7.5.1. Overview

PEIMs may invoke other PEIMs. The interfaces themselves are named using GUIDs. Because the PEIMs may be authored by different organizations at different times and updated at different times, references to these interfaces cannot be resolved during their execution by referring to the PEI PPI database. The database is loaded and queried using PEI Services such as InstallPpi() and LocatePpi() .

7.5.2. Dynamic PPI Discovery

7.5.2.1. PPI Database

The PPI database is a data structure that PEIMs can use to discover what interfaces are available or to manage a specific interface. The actual layout of the PPI database is opaque to a PEIM but its contents can be queried and manipulated using the following PEI Services:

  • InstallPpi()

  • ReinstallPpi()

  • LocatePpi()

  • NotifyPpi()

7.5.2.2. Invoking a PPI

When the PEI Foundation examines a PEIM for dispatch eligibility, it examines the dependency expression section of the firmware file. If there are non-NULL contents, the Reverse Polish Notation (RPN) expression is evaluated. Any requested PPI GUIDs in this data structure are queried in the PPI database. The existence in the database of the particular PUSH_GUID depex opcode leads to this expression evaluating to true.

7.5.2.3. Address Resolution

When a PEIM needs to leverage a PPI, it uses the PEI Foundation Service LocatePpi() to discover if an instance of the interface exists. The PEIM could do either of the following:

  • Install the PPI in its depex to ensure that its entry point will not be invoked until the needed PPI is already installed

  • Have a very thin set of code in its entry point that simply registers a notification on the desired PPI.

In the case of either the depex or the notification, the LocatePpi() call will then succeed and the pointer returned on this call references the EFI_PEI_PPI_DESCRIPTOR . It is through this data structure that the actual code entry point can be discovered. If this PEIM is being loaded before permanent memory is available, it will not have resources to cache this discovered interface and will have to search for this interface every time it needs to invoke the service.

It should also be noted that you cannot uninstall a PPI, so the services will be left in the database. If a PPI needs to be shrouded, a version can be “reinstalled” that just returns failure.

Also, there is peril in caching a PPI. For example, if you cache a PPI and the producer of the PPI “reinstalls” it to be something else (i.e., shadows to memory), then you have the possibility that the agent who cached the data will have “stale” or “illegal” data. For example, imagine the Stall PPI, EFI_PEI_STALL_PPI , relocating itself to memory using the Load File PPI, EFI_PEI_LOAD_FILE_PPI , and reinstalling the interface for performance considerations. A way to solve the latter issue, as a platform builder, is by having a different stall PPI for the memory-based one versus that of the Execute In Place (XIP) one.