January 30, 2009

FAQ: How To Create A New Protocol Or PPI Definition

This article describes how to create a new protocol or PPI definition in SecureCore-Tiano or AwardCore-Tiano. It assumes you already know the name and definition of the protocol or PPI, but just need to know where to put it and what files to create.

  1. For each protocol, there should be one header file and one source file. The header file contains the protocol definition, the protocol GUID definition, the extern to the GUID declaration, and any related data structures and constants. The source file contains the GUID declaration and the GUID  description.
  2. The name of the source file, the name of the header file, the name of the variable for the GUID declaration and the directory name where the source and header file are located are all derived from the protocol's "shortcut" name. The "shortcut" name is derived from the protocol's name, as follows:

A. Each protocol name is composed of "segments" separated by the '_' character. In the shortcut name, each segment starts with a capital letter and all subsequent letters are lower-case.

B. All '_' characters are removed.

C. "Phoenix" is abbreviated as "Ph"

 Here are some examples.

Protocol Name

Shortcut Name

PHOENIX_SW_SMI_PROTOCOL

PhSwSmiProtocol

EFI_ACPI_TABLE_PROTOCOL

EfiAcpiTableProtocol

EFI_SMM_BASE_PROTOCOL

EfiSmmBaseProtocol

  1. Now, using this shortcut, you can create the EFI GUID in the protocol's header and source file.

A.      In the header file, the GUID extern appears at the end, just before the #endif:

 extern EFI_GUID g<shortcut>Guid;

 For example:

extern EFI_GUID gPhSwSmiProtocolGuid;

B.      In the source file, the GUID definition appears at the end of the file:

EFI_GUID g<shortcut>Guid = <protocol-name>_GUID;

EFI_GUID_DEFINITION(&<shortcut>Guid,"<shortcut>",\

          "long protocol description");

For example:

EFI_GUID gPhSwSmiProtocolGuid = \ 

PHOENIX_SW_SMI_PROTOCOL_GUID;

       

EFI_GUID_DEFINITION(&gPhSwSmiProtocolGuid,"Software SMI Allocator",\

"Phoenix Software SMI Command Value Allocator");

       

  1. There is a second form of the shortcut ("shortcut2") which is used for the  file and directory names. Starting with the "shortcut" name, do the following:

A.      Remove the segment "Protocol" or "Ppi" from the end.

B.      Remove the segment "Efi" from the beginning. If "Efi" was followed by "Dxe" or "Pei", remove that segment also.

For example:

Shortcut

Shortcut Name

PhSwSmiProtocol

PhSwSmi

EfiAcpiTableProtocol

AcpiTable

EfiSmmBaseProtocol

SmmBase

     

  1. Now use this "shortcut2" to create the directory and file name, as follows:

A.      The header file will have the name <shortcut2>.h

B.      The source file will have the name <shortcut2>.c or <shortcut2>.cpp

C.      The directory will have the name <shortcut2>

  1. For protocols, the directory will be placed in the "Protocol" library in the component. Typical locations are:

Phoenix\Core\Protocol\<shortcut2>

Phoenix\Core\Efi\Protocol\<shortcut2>

Phoenix\Feature\feature-name\Protocol\<shortcut2>

Silicon\vendor\[component-type\]component-name\Protocol\<shortcut2>

  

  1. For PPIs, the rules are similar, except that "PPI" replaces Protocol, as follows:

Phoenix\Core\Ppi\<shortcut2>

Phoenix\Core\Efi\Ppi\<shortcut2>

Phoenix\Feature\feature-name\Ppi\<shortcut2>

Silicon\vendor\[component-type\]component-name\Ppi\<shortcut2>

  

  1.  The INF file for the library that contains these directories must be updated to include the source and header file in the [sources.common] section. For example:

PhSwSmiProtocol\PhSwSmiProtocol.c

PhSwSmiProtocol\PhSwSmiProtocol.h

  1. The name of the library, the name of the header file and the name of the GUID should be in your functional specification.
  2. You can find many places where older files do not follow this rule. There are many reasons for this. Just follow these rules! They make it easier for everyone to find the definitions they are looking for.

January 26, 2009

FAQ: Where Are Industry Standard Header Files

Q: Where are header files for industry standard?

A: There following table lists the directories for industry standards.

  • Foundation\Include\IndustryStandard, Phoenix\Core\Include\IndustryStandard - All industry standards except for EFI/UEFI.

  • Foundation\Efi\Include, Phoenix\Core\Efi\Include - UEFI and PI specifications

There are a few lying around in Include\IndustryStandard, but those are being moved to one of the above.

January 09, 2009

Legacy BIOS Services

Just a quick note to tell you that the Legacy BIOS Services have been added to the wiki here. And, while not nearly as interesting, the Legacy 8259 Services have been added to the wiki here.

BIOS Undercover: Launching A Legacy Option ROM In SecureCore-Tiano

Hawker Chen, Development Manager

Tim Lewis, Chief BIOS Architect

Version: SecureCore Tiano (Release 1.4.0)

Legacy option ROMs have been around since the earliest days of the PC-AT architecture, allowing a add-in card to add functionality to which the system BIOS could not provide. Later, with the advent of the BIOS Boot Specification, add-in cards could provide seamless integration of boot devices for the system.

In addition to this, prior to UEFI, the option ROM was the only way of providing plug-in modules in a BIOS independent manner. The option ROM was built into the system BIOS firmware and then the BIOS would discover the option ROM and call its entry point. Hardware and software vendors began packaging their plug-ins for the system BIOS in this format, even if there was no hardware device associated with it.

This article describes a way to:

1.    Integrate a legacy Option ROM into Phoenix’s Tiano-based products (SecureCore-Tiano and AwardCore-Tiano)

2.    Find the option ROM from inside of a platform driver during the boo process, and

3.    Shadow and call the option ROM’s entry point.

STEP 1: How to Integrate the Option Rom Image into the BIOS Image

This task creates one integrates a binary option ROM file into the BIOS image. Here are the steps:

1.    Create an INF file which includes the option ROM image. For example:

BASE_NAME = OemOpRom                              // Name of component

FILE_GUID = 8FC3D37F-9AF1-4088-8372-A72EE51EAC18  // GUID of binary file.

COMPONENT_TYPE = BINARY                           // Format = binary

 

[sources.common]

   OemOpRom.bin                   // File name of the Option Rom image

 

[nmake.common]

[includes.common]

  $(EDK_SOURCE)\Foundation\Efi

2.    Add this INF file to the DSC file in the build tip.’

 

[components]

$(PLATFORM_PATH)\...\OptionRom\OemOpRom.inf FV=FvMain

You can specify exactly which firmware volume the option ROM will be placed in using the FV statement on the same line.

STEP 2: How to Find the Option Rom Image

This task searches the BIOS image for the option ROM file. The option ROM file is located in one of the firmware volumes using a file name. In firmware volumes, files have a GUID for the file name. In this case, it is the GUID specified by FILE_GUID in the INF file.

Rather than making an assumption about which firmware volume the option ROM file is located in, this example searches all firmware volumes until it finds the file and then loads it.

 

//--+

// GetOemOpRom – Find the OEM option ROM and return the address and size

//

// Entry:

//   RomImage – A pointer to the returned pointer to option ROM image.

//   RomSize  - A pointer to the returned integer size of the option ROM.

//

// Exit:

//  EFI_SUCCESS   - RomImage is valid

//  EFI_NOT_FOUND - No RomImage

// 

EFI_STATUS

GetOemOpRom (

  OUT VOID  **RomImage,

  OUT UINTN *RomSize

  )

{

  EFI_STATUS                    Status;

  UINTN                         HandleCount;

  EFI_HANDLE                    *HandleBuffer;

  UINTN                         Index;

  EFI_FIRMWARE_VOLUME_PROTOCOL  *FirmwareVolume;

  UINT32                        AuthenticationStatus;

  VOID                          *LocalRomImage;

  UINTN                         LocalRomSize;

  //

  // Get the list of all of the available firmware volumes

  //

  Status = gBS->LocateHandleBuffer (

                  ByProtocol,

                  &gEfiFirmwareVolumeProtocolGuid,

                  NULL,

                  &HandleCount,

                  &HandleBuffer

                  );

  if (EFI_ERROR (Status) || HandleCount == 0) {

    return EFI_NOT_FOUND;

  }

  //

  // Loop through all of the Firmware Volumes looking for the ROM image

  //

  for (Index = 0; Index < HandleCount; Index++) {

    //

    // Get the Firmware Volume Protocol

    //

    Status = gBS->HandleProtocol (

                    HandleBuffer[Index],

                    &gEfiFirmwareVolumeProtocolGuid,

                     &FirmwareVolume

                    );

    if (EFI_ERROR (Status)) {

      continue;

    }

    //

    // Get the option ROM code from the same Firmware Volume as this driver

    //

    LocalRomImage = NULL;

    LocalRomSize  = 0;

    Status = FirmwareVolume->ReadSection (

                               FirmwareVolume,

                               &mOpRomFileGuid, // The FILE_GUID of file

                               EFI_SECTION_RAW,

                               0,

                               &LocalRomImage,

                               &LocalRomSize,

                               &AuthenticationStatus

                               );

    if (EFI_ERROR (Status)) {

      continue;

    }

    *RomImage = LocalRomImage;

    *RomSize  = LocalRomSize;

    gBS->FreePool (HandleBuffer);

    return EFI_SUCCESS;

  }

  gBS->FreePool (HandleBuffer);

  return EFI_NOT_FOUND;

}

STEP 3: How to Shadow the Option ROM into RAM

This task copies the option ROM image into shadow RAM and then calls the entry point of the image.  The function InstallPciRom()  is defined in the EFI_LEGACY_BIOS_PROTOCOL and handles most of the work. Here is the sample code for this:

 

EFI_LEGACY_BIOS_PROTOCOL  *mLegacyBios;

VOID                      *OemRomImage = NULL;

UINTN                     OemRomSize = 0;

UINTN                     Flags = 0;

CHAR16                    *TempStr = NULL;

VOID                      *RomShadowAddress = NULL;

UINT32                    ShadowedRomSize = 0;

EFI_STATUS                Status;

 

Status = GetOemOpRom(&OemRomImage, &OemRomSize);

if (!EFI_ERROR(Status)) {

  Status = gBS->LocateProtocol (

                  &gEfiLegacyBiosProtocolGuid,

                  NULL,

                  &mLegacyBios

                  );

  if (!EFI_ERROR(Status)) {

    Status = mLegacyBios->InstallPciRom(

                           LegacyBios,

                           NULL,

                           &OemRomImage,

                           &Flags,

                            NULL,

                            NULL,

                           &RomShadowAddress,

                           &ShadowedRomSize

                           );

  }

}

return Status;

January 06, 2009

DXE Services

Another quick note to point out that the reference for PEI Services has been updated here and the Firmware Volume services updated here.

January 02, 2009

PEI Services

Just a quick note to let you know that the PEI Services Table reference material has been added here.

BIOS Undercover: Detecting The Boot Mode

Tim Lewis, Chief BIOS Architect

Thibaut Hourteillan, Software Architect

Great philosophers ask the really tough questions, such as “What is truth?” and “Why is there something instead of nothing?” or “Why are we here?” In the PC architecture, great BIOS engineers are often confronted with a similar question when the platform awakes and begins executing code at the CPU’s reset vector. While it is possible and even convenient to ignore the question of “Why are we here?” for a few microseconds, eventually the BIOS must find out the answer in order to do anything important.

The Boot Mode

From the perspective of BIOS firmware, the platform’s life begins by executing code at the CPU reset vector. The CPU reset vector is a memory location 16 bytes below 4GB. There are several reasons why the system might start executing code at this location. In the Tiano and PI architectures, these reasons are called Boot Modes. Here’s the standard list:

Boot Mode

Description

BOOT_WITH_FULL_CONFIGURATION

After system power-on, configure all standard devices, making no assumptions about what changes might have been made.

BOOT_WITH_MINIMAL_CONFIGURATION

After system power-on, configure the minimum number of devices, making no assumptions about what changes might have been made.

BOOT_ASSUMING_NO_CONFIGURATION_CHANGES

After system power-on, configure all devices the same way they were during the last known-good boot, assuming that no changes have been made.

BOOT_WITH_FULL_CONFIGURATION_PLUS_DIAGNOSTICS

After system power-on, configure all standard devices, making no assumptions about what changes might have been made and perform extended diagnostics.

BOOT_WITH_DEFAULT_SETTINGS

After system power-on, configure all devices using the default settings.

BOOT_ON_S4_RESUME

The system was awakened from an S4 (Hibernate) power-state by a wake event.

BOOT_ON_S5_RESUME

The system was awakened from an S5 (Power-Off) power-state by a wake event other than the normal power button.

BOOT_ON_S2_RESUME

The system was awakened from an S2 (Standby) power-state by a wake event.

BOOT_ON_S3_RESUME

The system was awakened from an S3 (Suspend) power-state by a wake event.

BOOT_ON_FLASH_UPDATE

The system was awakened from some other power-state to update the system BOS flash device.

BOOT_IN_RECOVERY_MODE

The system was awakened from some other power-state, but a crisis recovery condition (such as corrupt flash or a jumper) was detected.

Now, for those of you familiar with the PC architecture, there seem to be some missing, including the famous warm-reset (0x1234 anybody?) software jump to the reset vector and the CPU-only reset (used for core-logic bus adjustments). In general, these other reasons are coerced into one of the above values.   

Some of the listed Boot Modes are easy to detect because there are standard hardware registers on most ACPI-compliant systems which provide the information. But some of the Boot Modes (such as the various power-on modes) require special platform knowledge to distinguish between them. That is why it is the responsibility of one of the early PEI drivers (such as PlatformStage1) to finalize the boot mode and install the EFI_PEI_MASTER_BOOT_MODE_PEIM_PPI. Also, if the Boot Mode is BOOT_IN_RECOVERY_MODE, the platform driver is also responsible to install the EFI_PEI_BOOT_IN_RECOVERY_MODE_PPI.

Reading The Boot Mode In PEI

The PEI Services Table provides the GetBootMode() function to determine the current boot mode. The catch is that the value returned from this function is only guaranteed to be valid after the EFI_PEI_MASTER_BOOT_MODE_PEIM_PPI has been installed. The easiest way to make sure that this is true is to add this PPI into the Dependency Expression (.dxs) file. For example:

#include "EfiDepex.h"

#include EFI_PPI_DEFINITION (CpuIo)

#include EFI_PPI_DEFINITION (PciCfg)

#include EFI_PPI_DEFINITION (SmbusPolicy)

#include EFI_PPI_DEFINITION (Stall)

#include EFI_PPI_DEFINITION (BootMode)

DEPENDENCY_START

    PEI_CPU_IO_PPI_GUID AND

    PEI_PCI_CFG_PPI_GUID AND

    PEI_SMBUS_POLICY_PPI_GUID AND

    PEI_STALL_PPI_GUID AND

    PEI_MASTER_BOOT_MODE_PEIM_PPI

DEPENDENCY_END

If you don’t want to wait until the PPI is installed, then the PEI driver can receive notification, using the NotifyPpi() PEI service. For example:

  EFI_STATUS

  EFIAPI

  MasterBootModeDetected (

    IN struct _EFI_PEI_SERVICES          **PeiServices,

    IN struct _EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor,

    IN VOID                              *Ppi

    )

  {

    ...my callback code...

  }

  EFI_PEI_NOTIFY_DESCRIPTOR NotifyDescriptor = {

    EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST,

    &gPeiMasterBootModePpiGuid,

    MasterBootModeDetected

    };

  Status = (**PeiServices).NotifyPpi(

                            PeiServices,

                            &NotifyDescriptor

                            );

Reading The Boot Mode In DXE

It is possible to determine the boot mode during the DXE phase. The PEI Foundation stores the Boot Mode in the PHIT HOB. The PHIT HOB is always the first HOB in the HOB List. A pointer to the start of the HOB List is installed in the EFI System Configuration Table by the DXE Dispatcher using the EFI_HOB_LIST_GUID. The following code fragrment shows how to retrieve it using the EDK library function EfiLibGetSystemConfigurationTable():

extern EFI_GUID gEfiHobListGuid;

EFI_STATUS                 Status;

EFI_HOB_HANDOFF_INFO_TABLE *PhitHob;

EFI_BOOT_MODE              BootMode;

Status = EfiLibGetSystemConfigurationTable(

           &gEfiHobListGuid,

           (VOID**) &PhitHob

           );

ASSERT_EFI_ERROR(Status);

ASSERT(GET_HOB_TYPE(PhitHob) == EFI_HOB_TYPE_HANDOFF);

BootMode = PhitHob->BootMode;

Conclusion

There you have it: a rundown on the Boot Mode and the ways to get it during PEI and DXE.

December 18, 2008

UEFI Networking

Just a quick note to say that the UEFI EFI_SIMPLE_NETWORK_PROTOCOL has been added to the reference section.

December 08, 2008

BIOS Undercover: Writing A Software SMI Handler

Tim Lewis, Chief BIOS Architect; Cassie Liu, Senior Software Engineer

System Management Interrupts (SMIs) are the highest priority interrupts found on x86 processors. Software SMIs are SMIs generated in response to a software instruction, usually a write to an I/O port such as 0xb2 (the SMI Command Port). The BIOS uses these software SMIs to provide services during late POST and then in runtime.

Softwaresmi

Software writes a value (the SMI Command Value) to the SMI Command Port. Typically the south bridge detects the write to the SMI Command Port and asserts the SMI# pin or sends an SMI message. Each CPU core detects the SMI after the current instruction has been completed. Then, the CPU saves most of the CPU registers in a buffer, switches to System Management Mode (SMM, a variant of big-real mode) and jumps to a pre-defined entry point.

Once inside of SMM, an SMM driver for the south bridge detects the source of the SMI (in this case, the software SMI), and then calls the routine which was registered for the SMI Command Value. After the routine returns, the SMM driver clears the SMI status and performs a “resume” instruction. This instruction forces the CPU to reload the CPU registers from the buffer and return to the instruction after the one which generated the software SMI.

Info In fact, while it appears that the SMI services are invoked immediately after writing to the I/O port and before the next CPU instruction, in some systems, the delay in propagation of the interrupt to the CPU cores may allow the cores to execute further instructions before the SMI is actually detected. This happens because the detection of the I/O write is usually the function of another chip in the system (such as a south bridge) and this chip must then signal the CPU using the SMI# signal or using bus messages. 

Also, in multi-core systems, although the SMI is propagated to all cores, they enter at slightly different times. This discrepancy occurs because SMIs are serviced in between execution of CPU instructions. Since CPU instructions require a different number of clock cycles, some will enter SMM sooner than others. Phoenix code waits for all cores to enter SMM and then, using a semaphore, allows exactly one core to enter the SMI processing loop.

When writing drivers which use software SMIs, there are three primary tasks:

1.    How To Register For Software SMI Notification

2.    How To Handle The Software SMI Notification

3.    How To Generate Software SMIs

STEP 1: How To Register For Notification

The first task is to write an SMM driver which registers for notification when a certain SMI Command Value is written to the SMI Command Port.

Here is the typical entry point code which registers for a software SMI value.

1.    SMM drivers are similar to DXE drivers at the entry point, except they are launched twice. At the first launch, they act as DXE drivers. During the first launch, they call the EFI_SMM_BASE_PROTOCOL’s Register() function to launch the driver a second time in System Management RAM (SMRAM).

2.    The SMM library performs all of the steps required by the SMM CIS to re-launch this driver in System Management RAM (SMRAM). On return, the InSmm flag has been set to TRUE if this is the copy of the driver running in SMRAM or FALSE if this is the normal DXE copy.

3.    If this driver is running in SMRAM, then perform the following steps…

4.    Locate the PHOENIX_SMM_SW_SMI_PROTOCOL. This protocol is a Phoenix service which allocates unique SMI Command Values. 

5.    The AllocateSwSmi() function returns the unique SMI Command Value associated with the specified GUID. If no SMI Command Value has previously been associated with the specified GUID (and SwContext.SwSmiInputValue is 0xFFFFFFFF), then a unique SMI Command Value is returned. If not 0xFFFFFFFF, then SwContext.SwSmIInputValue will become the SMI Command Value OR an error return if the SMI Command Value has previously been assigned.

6.    Locate the EFI_SMM_SW_DISPATCH_PROTOCOL. This protocol is produced by the south bridge driver to callback a function when a specific SMI Command Value was written to the SMI Command Port.

7.    The Register() function is used to register a callback function with a particular SMI Command Value. The SwHandle value returned can be used to later UnRegister() the callback.

EFI_SMM_SYSTEM_TABLE *mSmst;

UINT8                mSwSmiCommandValue;

UINT16               mSwSmiCommandPort;

EFI_GUID gSwSmiSampleDriverGuid = SW_SMI_SAMPLE_DRIVER_GUID;

EFI_DRIVER_ENTRY_POINT(SwSmiSampleDriverEntryPoint);

EFI_STATUS

SwSmiSampleDriverEntryPoint(

  IN EFI_HANDLE         ImageHandle,

  IN EFI_SYSTEM_TABLE   *SystemTable

)

{

  EFI_STATUS                     Status;

  BOOLEAN                        InSmm;

  EFI_SMM_SW_DISPATCH_PROTOCOL   *SwDispatch;

  PHOENIX_SMM_SW_SMI_PROTOCOL    *SwSmiAlloc;

  EFI_SMM_SW_DISPATCH_CONTEXT    SwContext;

  EfiInitializeSmmDriverLib (ImageHandle,SystemTable,&InSmm);

  if (InSmm) {

    gSMM->GetSmstLocation(gSMM,&mSmst);

    Status = gBS->LocateProtocol (

                    &gEfiPhoenixSmmSwSmiProtocolGuid,

                    NULL,

                    (VOID**) &SwSmiAlloc

                    );

    ASSERT_EFI_ERROR (Status);   

    SwContext.SwSmiInputValue = 0xFFFFFFFF;

    Status = SwSmiAlloc->AllocateSwSmi (

               &gSwSmiSampleDriverGuid,

               &SwContext.SwSmiInputValue

               );

    ASSERT_EFI_ERROR (Status);

    mSwSmiCommandValue = (UINT16) SwContext.SwSmiInputValue;

    mSwSmiCommandPort  = SwSmiAlloc->SwSmiCommandPort;

    Status = gBS->LocateProtocol (

                   &gEfiSmmSwDispatchProtocolGuid,

                   NULL,

                   &SwDispatch

                   );

    ASSERT_EFI_ERROR (Status);

    Status = SwDispatch->Register (

                           SwDispatch,

                           SwSmiSampleDriverCallback,

                           &SwContext,

                           &SwHandle

                           );

    ASSERT_EFI_ERROR (Status);

    ...other initialization…

  }

  return EFI_SUCCESS;

}

Info Some coding purists may be worried about the use of the ASSERT_EFI_ERROR macro, which detects an error only on debug versions. Why not always check for the error condition? The real reasons are: 1) it makes the code smaller and 2) it makes the code easier to read. But what about the risk?

In two cases, the macro is used with LocateProtocol(). In this case, the risk is very low because the GUIDs for these protocols are listed in the driver’s dependency expression (see SwSmiSampleDriver.dxs), so the driver won’t be launched unless the driver is actually present. In the third case, the macro is used with the Phoenix SMI Allocation protocol, which, in real life, would only fail if the system were out of memory, which is extremely unlikely in the pre-OS environment.

STEP 2: How To Handle The Software SMI Notification

One the non-SMM code writes the SMI Command Value to the SMI Command Port, eventually control is transferred to the callback function (SwSmiSampleDriverCallback) which was registered during Step 1, above. Typically, Software SMI callbacks need to retrieve and change at the contents of CPU registers, examine the contents of memory, perform their service and then exit.

Reading CPU Registers

Most of the general-purpose registers are saved in a special buffer called the Save State Area when each CPU enters SMM. Other registers are not saved at all. For example, floating-point, XMM and MSRs are not saved.

The callback can read the current values of the general-purpose registers for any of the CPU cores. Since we are interested in the current values of the CPU core which generated the software SMI, we need to figure out which core that is.

Here are the steps:

1.    Determine which CPU core actually generated the software SMI, since all we care about are the CPU registers on that CPU.

2.    Once we have determined which CPU generated the software SMI, we can then access the CPU registers. In this case, we read EAX using ReadSaveState().

3.    Then we can perform our service, perhaps switching services based on the contents of AL.

4.    Finally, we put a signature back into the EAX register using WriteSaveState().

 

VOID

SwSmiSampleDriverCallback (

  IN  EFI_HANDLE                    DispatchHandle,

  IN  EFI_SMM_SW_DISPATCH_CONTEXT   *DispatchContext

  )

{

  EFI_STATUS                 Status;

  UINTN                      CpuIndex;

  EFI_SMM_SAVE_STATE_IO_INFO IoInfo;

  //

  // Find the CPU that triggered the software SMI

  //

  for (CpuIndex = 0; CpuIndex < mSmst->NumberOfCpus; CpuIndex++) {

    Status = mCpu->ReadSaveState(

                     mCpu,

                     sizeof(IoInfo),

                     EFI_SMM_SAVE_STATE_REGISTER_IO,

                     (VOID*) &IoInfo

                     );

    if (Status == EFI_SUCCESS &&

        IoInfo->IoWidth == EFI_SMM_SAVE_STATE_IO_WIDTH_UINT8 &&

        IoInfo->IoPort == (UINT16) mSwSmiCommandPort &&

        * (UINT8*) IoInfo->IoData == mSwSmiCommandValue

        ) {

      break;

    }

  }

  if (CpuIndex == mSmst->NumberOfCpus) {

    return;

  }

 

  UINT32 Eax;

  Status = mCpu->ReadSaveState(

                   mCpu,

                   sizeof(Eax),

                   EFI_SMM_SAVE_STATE_REGISTER_RAX,

                   CpuIndex,

                   (VOID *) &Eax

                   );

  ASSERT_EFI_ERROR(Status);

  ...other service code goes here...

 

  Eax = 0xAA55AA55;

  Status = mCpu->WriteSaveState(

                   mCpu,

                   sizeof(Eax),

                   EFI_SMM_SAVE_STATE_REGISTER_RAX,

                   CpuIndex,

                   (VOID *) &Eax

                   );

}

Reading System Memory

The software SMI handler may need to read the contents of memory using the same context as the CPU which generated the software SMI. In many cases, the non-SMM code wants to pass a pointer to a buffer which will be used by the software SMI handler. But the non-SMM code may be executing with paging turned on or off and there may not be a 1-to-1 mapping between the linear and physical addresses. The software SMI handler, on the other hand, may be executing with paging turned on, but there is guaranteed to be a 1-to-1 mapping between linear and physical addresses. Adding to this, the buffer pointer may point to data which crosses a page boundary.

Phoenix provides a series of services which translate between physical and linear addresses. Phoenix also provides services which convert select/offset format addresses into linear addresses by looking them up in the GDT. These are encompassed in the PHOENIX_SMM_CPU_PAGE_PROTOCOL.

Let’s assume that the code which generates the software SMI passes a pointer to a 20-byte buffer using DS:ESI (x86) or DS:RSI (X64). We will reverse that buffer and pass it back. Here are the basic steps:

1.    Convert the value from DS:RSI to a linear address, using ConvertSegOffsetToLinear().

2.    Using this address, the 20 byte buffer is copied from system memory into a temporary buffer on the stack, taking page tables into account, using CopyFromLinear().

3.    The contents of the buffer are reversed.

4.    The 20 byte buffer is copied from the stack back into system memory, taking page tables into account using CopyToLinear().

 

EFI_VIRTUAL_ADDRESS Address;

Status = mCpuPage->ConvertSegOffsetRegToLinear(

            CpuIndex,

            EFI_SMM_SAVE_STATE_REGISTER_DS,

            EFI_SMM_SAVE_STATE_REGISTER_RSI,

            &Address

            );

ASSERT_EFI_ERROR(Status);

 

UINT8 Buffer[20];

Status = mCpuPage->CopyFromLinear(

           Address,

           CpuIndex,

           (EFI_PHYSICAL_ADDRESS) &Buffer,

           sizeof(Buffer)

           );

ASSERT_EFI_ERROR(Status);

 

for (i = 0; i < sizeof(Buffer)/2; i++) {

  j = Buffer[i];

  Buffer[i] = Buffer[sizeof(Buffer) – i];

  Buffer[sizeof(Buffer) – i] = j;

}

 

Status = mCpuPage->CopyToLinear(

           (EFI_PHYSICAL_ADDRESS) &Buffer,

           CpuIndex,

           Address,

           sizeof(Buffer)

           );

ASSERT_EFI_ERROR(Status);

STEP 3: How To Generate The Software SMI

For applications which want to use the services created during the previous two steps, there are two important steps: first, find out the SMI Command Value and second, generate the software SMI.

Finding The SMI Command Value

The SMI Command Value that was registered by the software SMI driver can be discovered using the QuerySwSmi() function in the PHOENIX_SMM_SW_SMI_PROTOCOL. Here are the steps.

1.    Find the PHOENIX_SMM_SW_SMI_PROTOCOL.

2.    Call QuerySwSmi() using the same GUID specified by the driver which allocated the SMI Command Value. The value returned in SwSmiCommandValue is the SMI Command Value.

UINT8 SwSmiCommandValue;

Status = gBS->LocateProtocol (

                &gEfiPhoenixSmmSwSmiProtocolGuid,

                NULL,

                (VOID**) &SwSmiAlloc

                );

ASSERT_EFI_ERROR (Status);   

SwContext.SwSmiInputValue = 0xFFFFFFFF;

Status = SwSmiAlloc->QuerySwSmi (

           &gSwSmiSampleDriverGuid,

           &SwSmiCommandValue

           );

ASSERT_EFI_ERROR (Status);

Generating The Software SMI

The SMI Command Port can be found in the same protocol and can be used to generate a software SMI with the right value:

_outp(mSwSmiCommandPort, mSwSmiCommandValue);

Conclusion

With this information, you can allocate a unique software SMI value and create new services which rely on either memory or register values. Next time, we will look at how to use the Phoenix SMM Services to “call” protocol functions inside of SMM securely.

BIOS Undercover: Porting C/C++ To Phoenix UEFI (Part 4)

This week we conclude our series of articles by point out how to use the C/C++ libraries with your driver:

1.    Change Your INF Entry Point. Each INF file has an IMAGE_ENTRY_POINT= statement. Change it to one of the following:

EXE Type

IMAGE_ENTRY_POINT

main() Prototype

PEI Module

PeiMainStartup

 

EXTERN_C

EFI_STATUS

EFIAPI

PeiMain(

  IN EFI_FFS_FILE_HEADER* FileHeader,

  IN EFI_PEI_SERVICES**   PeiServices

  );

 

DXE Boot Services Driver

DxeMainBSStartup

 

EXTERN_C

EFI_STATUS

EFIAPI

DxeMain(

  IN EFI_HANDLE       ImageHandle,

  IN EFI_SYSTEM_TABLE *SystemTable

  );

 

DXE Runtime Services Driver

DxeMainRTStartup

 

EXTERN_C

EFI_STATUS

EFIAPI

DxeMain(

  IN EFI_HANDLE       ImageHandle,

  IN EFI_SYSTEM_TABLE *SystemTable

  );

 

SMM

SmmStartup

 

EXTERN_C

EFI_STATUS

EFIAPI

SmmMain(

  IN EFI_HANDLE       ImageHandle,

  IN EFI_SYSTEM_TABLE *SystemTable

  );

 

EFI Shell

mainCRTStartup

 

EXTERN_C

int

EFIAPI

main(

  IN int  argc,

  IN char*argv[]

  );

 

 

2.    Change Your INF [include.common].

Add "$(EFI_SOURCE)\phoenix\features\clibrary\include" to the [include.common] section.

 

3.    Change Your INF [libraries.common].

Add libc.lib to the [libraries.common] section.

4.    Add Libraries To Your DSC.

Add this line to the 32-bit and 64-bit sections of the [libraries.platform] section of the build.dsc file.

$(EFI_SOURCE)\phoenix\Features\Clibrary\bin\$(PROCESSOR)\libc.inf

 

 

Developer's WIKIs

  • WIKIs


Subscription

Search


  • WWW
    Phoenix Blog