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.
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 inFfsFindNextFile()
.
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:
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.