/** @file
TcgLegacy.c -- TCG Legacy DXE Driver
This DXE driver implements TCG Legacy BIOS support for TPM 1.2 on UEFI
systems. It bridges the EFI TCG (Trusted Computing Group) protocol with
legacy BIOS INT hooks, providing TPM functionality to legacy option ROMs
and non-UEFI boot paths.
Functional Overview:
1. The entry point (ModuleEntryPoint) saves required UEFI boot service
globals and calls InitTCGLegacyInterface().
2. InitTCGLegacyInterface() locates the Legacy BIOS Protocol, optionally
the TCG2 Protocol, and loads two firmware volume files:
- LEGX16: The legacy BIOS 16-bit thunk layer (copied to F000-E000).
- TPM32: The TPM 1.2 legacy driver (copied to a page-aligned region).
- MPTPM: An optional multi-processor TPM binary.
3. It then allocates TPM code space, installs callbacks, and patches the
BFI ($BIN) marker in the shadow area to complete the legacy interface.
4. A LegacyBoot event is registered via CreateEventEx to unlink the
TPM driver when the OS boots.
File: AmiModulePkg/TCG2/Common/TcgLegacy/TcgLegacy.c
Binary: TcgLegacy.efi (X64)
Size: ~8 KB (0x1FC0)
Source: AMI BIOS, HR650X platform
Copyright (C) AMI Corporation. All rights reserved.
**/
#include <Uefi.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Protocol/LegacyBios.h>
#include <Protocol/TcgService.h>
#include "TcgLegacy.h"
//
// ---------------------------------------------------------------------------
// External global data references (populated by ModuleEntryPoint)
// ---------------------------------------------------------------------------
//
// These are written once by the DXE core bootstrap code before the driver's
// entry point is called; the entry point simply caches them in variables.
//
#pragma data_seg(".data")
//
// Legacy BIOS interface function pointer (stored at 0x1DD0)
// When byte_1DE0 == 0: points to the EFI_LEGACY_BIOS_PROTOCOL instance.
// When byte_1DE0 == 1: set to the TCG2 protocol interface.
//
UINT64 qword_1DD0 = 0;
//
// Installed TPM32 header pointer (stored at 0x1DD8)
// Non-NULL once LinkTPM32Driver() succeeds.
//
UINT64 qword_1DD8 = 0;
//
// Protocol selection flag (stored at 0x1DE0 as byte)
// If 0: Legacy BIOS Protocol is used.
// If 1: TCG2 Protocol is used instead.
//
UINT8 byte_1DE0 = 0;
//
// TCG2 Protocol pointer (stored at 0x1DE8)
// Valid only when byte_1DE0 == 1.
//
UINT64 qword_1DE8 = 0;
//
// System Table pointer (stored at 0x1DF0)
//
EFI_SYSTEM_TABLE *gST = NULL;
//
// Boot Services pointer (stored at 0x1DF8)
//
EFI_BOOT_SERVICES *gBS = NULL;
//
// Image handle (stored at 0x1E00)
//
EFI_HANDLE gImageHandle = NULL;
//
// Runtime Services pointer (stored at 0x1E08)
//
EFI_RUNTIME_SERVICES *gRT = NULL;
//
// Debug Console Output protocol (stored at 0x1E10, cached from GetTcgProtocol)
//
EFI_TCG_PROTOCOL *gTcgProtocol = NULL;
//
// HOB List pointer (stored at 0x1E18)
//
VOID *gHobList = NULL;
//
// CMOS variable byte read from NVRAM index 0x4B (stored at 0x1E20)
// Used by DebugPrint to determine verbosity level.
//
UINT8 gDebugNvramByte = 0;
//
// LEGX16 configuration words (stored at 0x1E28-0x1E30)
// These are saved from the LEGX16 header and written to legacy BIOS
// configuration space during install.
//
UINT16 gLegX16Segment = 0; // +0x1E28
UINT16 gLegX16Shadow = 0; // +0x1E2A
UINT16 gLegX16Init = 0; // +0x1E2C
UINT16 gLegX16Checksum = 0; // +0x1E2E
UINT16 gLegX16Size = 0; // +0x1E30
#pragma data_seg()
//
// TPM Vendor ID Lookup Table (stored at 0x1DC0)
// Each 4-byte entry contains a TPM hardware vendor/device ID.
// The table is compared against MEMORY[0xFED40F00] to detect if
// TPM hardware is already present.
//
// Format: 3 entries, each 4 bytes:
// Entry 0: 0xF5190100 (vendor:device flags)
// Entry 1: 0x011B4E01 (vendor: "NS" = National Semiconductor?)
// Entry 2: 0x00020000 (placeholder/end marker)
//
UINT32 gTpmVendorIdTable[3] = {
0xF5190100,
0x011B4E01,
0x00020000
};
//
// GUID data blocks (must match the GUIDs declared in the header)
//
EFI_GUID gEfiTcgEventLogFormatGuid = TCG_EVENT_LOG_FORMAT_GUID;
EFI_GUID gEfiHobListGuid = EFI_HOB_LIST_GUID;
EFI_GUID gEfiEventLegacyBootGuid = EFI_EVENT_LEGACY_BOOT_GUID;
EFI_GUID gEfiLegacyBiosProtocolGuid = EFI_LEGACY_BIOS_PROTOCOL_GUID;
EFI_GUID gEfiLegacyInterruptProtocolGuid = EFI_LEGACY_INTERRUPT_PROTOCOL_GUID;
EFI_GUID gEfiLegacyBiosPlatformGuid = EFI_LEGACY_BIOS_PLATFORM_PROTOCOL_GUID;
EFI_GUID gAmiTpmPlatformProtocolGuid = AMI_TPM_PLATFORM_PROTOCOL_GUID;
EFI_GUID gEfiTcgProtocolGuid = EFI_TCG_PROTOCOL_GUID;
EFI_GUID gEfiTcg2ProtocolGuid = EFI_TCG2_PROTOCOL_GUID;
//
// ---------------------------------------------------------------------------
// Internal / Library function implementations
// ---------------------------------------------------------------------------
/**
ZeroMemory -- fill a buffer with zeros.
This is called by ZeroMem() and from the internal AllocateAndZeroPages()
function. It uses memset() internally.
@param[in,out] Buffer Pointer to the buffer to fill with zeros.
@param[in] Length Number of bytes to fill.
@return Buffer as passed.
**/
VOID *
EFIAPI
InternalZeroMem (
VOID *Buffer,
UINTN Length
)
{
//
// Zero the buffer using UINT64-aligned writes for the bulk,
// and byte writes for the remainder.
//
ZeroMem (Buffer, Length);
return Buffer;
}
/**
CompareMem -- compare two memory buffers.
@param[in] DestinationBuffer Pointer to the first memory buffer.
@param[in] SourceBuffer Pointer to the second memory buffer.
@param[in] Length Number of bytes to compare.
@return 0 if the buffers are equal; non-zero otherwise.
**/
INTN
EFIAPI
InternalCompareMem (
CONST VOID *DestinationBuffer,
CONST VOID *SourceBuffer,
UINTN Length
)
{
if (DestinationBuffer == SourceBuffer) {
return 0;
}
ASSERT (DestinationBuffer != NULL);
ASSERT (SourceBuffer != NULL);
ASSERT ((Length - 1) <= (MAX_UINTN - (UINTN)DestinationBuffer));
ASSERT ((Length - 1) <= (MAX_UINTN - (UINTN)SourceBuffer));
return CompareMem (DestinationBuffer, SourceBuffer, Length);
}
/**
ReadUnaligned64 -- read a 64-bit value from an unaligned address.
@param[in] Buffer Pointer to the potentially unaligned buffer.
@return The 64-bit value read from Buffer.
**/
UINT64
EFIAPI
ReadUnaligned64 (
CONST UINT64 *Buffer
)
{
ASSERT (Buffer != NULL);
return *Buffer;
}
/**
DebugPrint -- formatted debug output via EFI debug console protocol.
Checks CMOS NVRAM byte at index 0x4B to determine debug verbosity.
If the current error level matches, the message is printed through
the EFI_TCG_PROTOCOL's debug output method (or a console protocol).
@param[in] ErrorLevel Debug error level (bitmask).
@param[in] Format PRINT-style format string.
@param[in] ... Variable arguments.
@return TRUE if the message was output; FALSE otherwise.
**/
BOOLEAN
EFIAPI
DebugPrint (
IN UINTN ErrorLevel,
IN CONST CHAR8 *Format,
...
)
{
EFI_TCG_PROTOCOL *TcgProtocol;
UINTN PrintLevel;
UINT8 NvramByte;
UINT8 NvramStatus;
BOOLEAN Result;
PrintLevel = 0;
Result = FALSE;
TcgProtocol = GetTcgProtocol ();
if (TcgProtocol != NULL) {
//
// Read CMOS NVRAM index 0x4B to determine debug verbosity.
//
NvramByte = IoRead8 (0x70);
IoWrite8 (0x70, NvramByte & 0x80 | 0x4B);
NvramStatus = IoRead8 (0x71);
if (NvramStatus > 3) {
if (NvramStatus == 0) {
//
// Read platform-specific debug flag from 0xFDAF0490.
//
NvramStatus = (*(volatile UINT8 *)0xFDAF0490 & 2) | 1;
}
}
gDebugNvramByte = NvramStatus;
//
// Determine print level: EFI_D_INFO (0x80000004) if verbosity == 1,
// EFI_D_WARN (0x80000006) otherwise.
//
if ((NvramStatus - 1) <= 0xFD) {
PrintLevel = EFI_D_WARN;
if (NvramStatus == 1) {
PrintLevel = EFI_D_INFO;
}
}
//
// If the message's error level matches the current verbosity, print it.
//
if ((PrintLevel & ErrorLevel) != 0) {
VA_LIST Args;
VA_START (Args, Format);
Result = (BOOLEAN)TcgProtocol->DebugPrint (ErrorLevel, Format, Args);
VA_END (Args);
}
}
return Result;
}
/**
DebugAssert -- break on assertion failure.
Invokes the TCG protocol assertion handler if available.
@param[in] FileName Source file name.
@param[in] LineNumber Line number of the assertion.
@param[in] Description Assertion description string.
**/
VOID
EFIAPI
DebugAssert (
IN CONST CHAR8 *FileName,
IN UINTN LineNumber,
IN CONST CHAR8 *Description
)
{
EFI_TCG_PROTOCOL *TcgProtocol;
TcgProtocol = GetTcgProtocol ();
if (TcgProtocol != NULL) {
TcgProtocol->DebugAssert (FileName, LineNumber, Description);
}
}
/**
ZeroMem wrapper with bounds checking.
Checks that the buffer is valid and that the length does not overflow,
then calls InternalZeroMem.
@param[in,out] Buffer Pointer to the buffer to zero.
@param[in] Length Number of bytes to zero.
**/
VOID
EFIAPI
ZeroMem (
IN VOID *Buffer,
IN UINTN Length
)
{
if (Length == 0) {
return;
}
ASSERT (Buffer != NULL);
ASSERT (Length <= (MAX_UINTN - (UINTN)Buffer + 1));
InternalZeroMem (Buffer, Length);
}
/**
Get the HOB (Hand-Off Block) list pointer.
Scans the System Table's configuration table for the HOB List GUID
({36232936-0E76-31C8-A13A-3AF2FC1C3932}) and caches the pointer.
This is the DXE equivalent of GetHobList() from the HOB Library.
@return Pointer to the HOB list, or NULL if not found.
**/
VOID *
EFIAPI
GetHobList (
VOID
)
{
UINTN Index;
UINT64 SystemTable;
UINT64 ConfigTable;
EFI_GUID *Guid;
if (gHobList == NULL) {
SystemTable = (UINT64)gST;
gHobList = NULL;
if (gST->NumberOfTableEntries > 0) {
for (Index = 0; Index < gST->NumberOfTableEntries; Index++) {
ConfigTable = (UINT64)gST->ConfigurationTable;
Guid = &gST->ConfigurationTable[Index].VendorGuid;
if (IsHobGuidMatch ((EFI_GUID *)EFI_HOB_LIST_GUID_PTR, Guid)) {
//
// Found the HOB List entry; cache the pointer.
//
gHobList = gST->ConfigurationTable[Index].VendorTable;
break;
}
}
}
if (gHobList == NULL) {
DEBUG ((EFI_D_ERROR, "ASSERT_EFI_ERROR (Status = Not Found)\n"));
ASSERT (!EFI_ERROR (EFI_NOT_FOUND));
}
ASSERT (gHobList != NULL);
}
return gHobList;
}
/**
HOB GUID comparison helper.
Compares two GUIDs by reading their QWORD values.
@param[in] Guid1 Pointer to first GUID.
@param[in] Guid2 Pointer to second GUID.
@return TRUE if both GUIDs match.
**/
BOOLEAN
IsHobGuidMatch (
IN EFI_GUID *Guid1,
IN EFI_GUID *Guid2
)
{
UINT64 Val1a, Val1b;
UINT64 Val2a, Val2b;
Val1a = ReadUnaligned64 ((UINT64 *)&gEfiHobListGuid);
Val2a = ReadUnaligned64 ((UINT64 *)Guid2);
Val1b = ReadUnaligned64 (((UINT64 *)&gEfiHobListGuid) + 1);
Val2b = ReadUnaligned64 (((UINT64 *)Guid2) + 1);
return (Val1a == Val2a) && (Val1b == Val2b);
}
/**
Locate the EFI TCG Protocol.
Looks up the TCG Protocol by GUID from the handle database. If the
system has >= 17 bytes of CMOS bank 0x4B (an unusual configuration),
the protocol is not returned. The result is cached in gTcgProtocol.
@return Pointer to the EFI_TCG_PROTOCOL instance, or NULL if not found.
**/
EFI_TCG_PROTOCOL *
GetTcgProtocol (
VOID
)
{
UINT64 NvramSize;
if (gTcgProtocol == NULL) {
//
// Read CMOS index 0x4B to determine NVRAM bank size.
//
IoWrite8 (0x70, 0x4B);
NvramSize = IoRead8 (0x71);
IoWrite8 (0x70, 0x4B);
IoRead8 (0x71);
if (NvramSize <= 0x10) {
//
// NVRAM bank is 16 bytes or fewer; try to locate the TCG protocol.
//
if (gBS->LocateProtocol (
&gEfiTcgProtocolGuid,
NULL,
(VOID **)&gTcgProtocol
) < 0) {
gTcgProtocol = NULL;
}
} else {
gTcgProtocol = NULL;
}
}
return gTcgProtocol;
}
/**
Locate a firmware file by GUID from the firmware volume protocol.
Scans all FV instances and searches for a file matching the given GUID.
Returns the file's buffer and size.
@param[in] FileGuid GUID of the file to locate.
@param[out] FileBuffer Receives pointer to the file buffer.
@param[out] FileSize Receives the file size.
@return EFI_SUCCESS if found; EFI_NOT_FOUND if not found.
@return Error codes from LocateHandleBuffer or ReadSection.
**/
EFI_STATUS
LocateFirmwareVolumeFile (
IN EFI_GUID *FileGuid,
OUT VOID **FileBuffer,
OUT UINTN *FileSize
)
{
EFI_STATUS Status;
UINTN HandleCount;
EFI_HANDLE *HandleBuffer;
UINTN Index;
EFI_FIRMWARE_VOLUME2_PROTOCOL *FvProtocol;
UINTN Key;
UINT32 AuthenticationStatus;
*FileBuffer = NULL;
*FileSize = 0;
HandleCount = 0;
HandleBuffer = NULL;
//
// Locate all FV protocol handles.
//
Status = gBS->LocateHandleBuffer (
ByProtocol,
&gEfiFirmwareVolume2ProtocolGuid,
NULL,
&HandleCount,
&HandleBuffer
);
if (EFI_ERROR (Status) || HandleCount == 0) {
return EFI_NOT_FOUND;
}
//
// Scan each FV for the file by GUID.
//
for (Index = 0; Index < HandleCount; Index++) {
Status = gBS->HandleProtocol (
HandleBuffer[Index],
&gEfiFirmwareVolume2ProtocolGuid,
(VOID **)&FvProtocol
);
if (EFI_ERROR (Status)) {
continue;
}
Status = FvProtocol->ReadSection (
FvProtocol,
FileGuid,
EFI_SECTION_RAW,
0,
FileBuffer,
FileSize,
&AuthenticationStatus
);
if (!EFI_ERROR (Status)) {
//
// Found the file.
//
gBS->FreePool (HandleBuffer);
return EFI_SUCCESS;
}
}
gBS->FreePool (HandleBuffer);
return EFI_NOT_FOUND;
}
/**
Allocate one or more pages and zero them.
Wrapper around gBS->AllocatePages() with AllocateAnyPages type and
EFI_MEMORY_TYPE 5 (EfiReservedMemoryType). Returns the physical
address of the allocated pages.
@param[in] Pages Number of 4 KB pages.
@return Physical address (as UINT64), or 0xFFFFFFFF on failure.
**/
UINT64
AllocateAndZeroPages (
IN UINTN Pages
)
{
EFI_STATUS Status;
EFI_PHYSICAL_ADDRESS Address;
UINT64 Result;
Address = 0xFFFFFFFFFFFFFFFFULL;
Status = gBS->AllocatePages (
AllocateAnyPages,
EfiReservedMemoryType,
Pages,
&Address
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "ASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT (!EFI_ERROR (Status));
}
if (!EFI_ERROR (Status)) {
ZeroMem ((VOID *)(UINTN)Address, EFI_PAGES_TO_SIZE (Pages));
}
return (UINT64)Address;
}
///
/// Forward declaration of internal helper.
///
UINT16 *
FindTpmVendorEntry (
IN UINTN NumberOfTableEntries,
IN UINT64 DevicePathTable,
OUT UINT16 *OutMatchingEntry
);
/**
Search a table of ACPI device path entries for a TPM vendor match.
The table is an array of fixed-size entries (24 bytes each), where
each entry has a 16-byte GUID followed by vendor-specific fields.
If the first 16 bytes of an entry match gEfiTcgEventLogFormatGuid and
the entry's type field is 4 (vendor-specific ACPI), it returns the
matching entry pointer + 12.
@param[in] NumberOfTableEntries Number of entries in the table.
@param[in] DevicePathTable Base address of the table.
@param[out] OutMatchingEntry Unused (may be NULL).
@return Pointer to the matching entry + 12 (the vendor data), or NULL.
**/
UINT16 *
FindTpmVendorEntry (
IN UINTN NumberOfTableEntries,
IN UINT64 DevicePathTable,
OUT UINT16 *OutMatchingEntry
)
{
UINTN Index;
UINT64 EntryBase;
UINT16 *Entry;
UINT16 *Current;
INTN Type;
UINT16 *Result;
if (NumberOfTableEntries == 0) {
return NULL;
}
for (Index = 0; Index < NumberOfTableEntries; Index++) {
//
// Each entry is 24 bytes; navigate backwards from the end.
//
EntryBase = DevicePathTable + (NumberOfTableEntries - 1 - Index) * 24;
//
// Compare first 16 bytes against the TCG event log GUID.
//
if (InternalCompareMem (
(VOID *)EntryBase,
&gEfiTcgEventLogFormatGuid,
sizeof (EFI_GUID)
) != 0) {
continue;
}
Entry = (UINT16 *)(EntryBase + 16);
if (Entry != NULL) {
Result = NULL;
//
// Walk the vendor data entries following the GUID.
//
Current = Entry;
while (*Current != 0xFFFF) {
Type = *Current;
if (Type == 4) {
//
// Vendor-specific entry found.
//
break;
}
//
// Skip to next entry (advance by the entry's total length).
//
Current = (UINT16 *)((UINT8 *)Current + Current[1]);
Type = *Current;
}
if (*Current == 0xFFFF) {
Current = Entry;
}
if (*Current == 4) {
//
// Type 4 entry: check if the next 16 bytes match the expected data.
//
if (InternalCompareMem (
(VOID *)(Current + 4),
(VOID *)0x1D20, // Alternative GUID for comparison
sizeof (EFI_GUID)
) == 0) {
Result = Current + 12;
}
}
if (Result != NULL) {
return Result;
}
}
}
return NULL;
}
/**
Allocate pages, zero them, and return the physical address.
Wrapper used by the TCG Legacy init to allocate TPM32 code pages
and MPTPM pages.
@param[in] Pages Number of 4 KB pages to allocate.
@return Physical address on success; 0xFFFFFFFF on failure.
**/
UINT64
AllocateAndZeroPagesWrapper (
IN UINTN Pages
)
{
return AllocateAndZeroPages (Pages);
}
/**
Scan the legacy ROM area at physical address 0xE0000 (0x8000 dwords)
for a BFI ('$BIN') signature marker.
When found, patch the LEGX16 segment number into the BFI structure
and recalculate the checksum.
@param[in] LegBiosPlatform Legacy BIOS Platform Protocol pointer.
@param[in] LegBios Legacy BIOS Protocol pointer.
@param[in] LegX16Segment LEGX16 segment value to patch into BFI.
**/
VOID
ScanAndPatchBfi (
IN UINT64 LegBiosPlatform,
IN UINT64 LegBios,
IN UINT16 LegX16Segment
)
{
UINT32 *RomScan;
UINT32 Index;
UINT8 *BfiEntry;
UINT8 Checksum;
UINT8 *BfiData;
UINTN Count;
BfiEntry = NULL;
RomScan = (UINT32 *)0xE0000;
Count = 0x8000; // 128 KB / 4
for (Index = 0; Index < Count; Index++) {
if (RomScan[Index] == 0x244E4942) { // '$BIN' signature
BfiEntry = (UINT8 *)&RomScan[Index];
Checksum = 0;
BfiData = (UINT8 *)&RomScan[Index];
Count = RomScan[Index + 1]; // Size field in next dword
//
// Calculate checksum over the BFI structure bytes.
//
while (Count > 0) {
Checksum += *BfiData++;
Count--;
}
if (Checksum == 0) {
break; // Valid BFI with matching checksum
}
}
}
if (Index >= 0x8000 || BfiEntry == NULL) {
DEBUG ((EFI_D_ERROR, "\t!!!Not Found BFI\n"));
return;
}
DEBUG ((EFI_D_INFO, "\tFound BFI at 0x%x\n", (UINT32)(UINTN)BfiEntry));
//
// Patch the LEGX16 segment into the BFI structure (offset 35 words = byte 70).
// Recalculate the checksum after patching.
//
*((UINT16 *)BfiEntry + 35) = (UINT16)(LegX16Segment >> 4);
*((UINT16 *)BfiEntry + 36) = 0;
//
// Recalculate checksum.
//
BfiData = BfiEntry + 4; // Skip the signature dword itself
*BfiData = 0; // Clear first byte of remaining structure
if (BfiEntry[5] != 0) {
Checksum = 0;
Count = BfiEntry[5]; // Length of the checksummed region
while (Count > 0) {
Checksum += *BfiEntry++;
Count--;
}
//
// Store the two's complement checksum.
//
*BfiData = ~(Checksum - 1);
}
}
/**
Link the TPM32 driver into the legacy BIOS interface.
Sets the TPM32 header's PModeEntry callback and initializes fields:
- byte_1DE0 == 0: Uses Legacy BIOS Protocol callback.
- byte_1DE0 == 1: Uses TCG2 Protocol callback.
@param[in] Tpm32Header Pointer to the TPM32 header structure.
@param[in] LegBios Legacy BIOS Protocol interface.
**/
VOID
LinkTPM32Driver (
IN TPM32_HEADER *Tpm32Header,
IN EFI_LEGACY_BIOS_PROTOCOL *LegBios
)
{
EFI_STATUS Status;
UINT64 StatusCode;
UINT64 TpmAddress;
Tpm32Header->PmodeEntry = 0; // Offset +0x42 (2 bytes), cleared initially
Tpm32Header->EntryPoint = 0; // Offset +0x04
//
// Cache the Legacy BIOS interface pointer globally.
//
qword_1DD0 = (UINT64)LegBios;
if (byte_1DE0) {
//
// TCG2 path: register a null callback and use TCG2 protocol for init.
//
if (qword_1DE8 == 0) {
return;
}
Tpm32Header->PmodeEntry = (UINT32)(UINTN)NullCallback; // nullsub_1
Status = ((EFI_TCG2_PROTOCOL *)qword_1DE8)->RegisterCallback (
(EFI_TCG2_PROTOCOL *)qword_1DE8,
1,
&TpmAddress,
&StatusCode
);
DEBUG ((EFI_D_INFO, "\n\n TcgLegacy.c Status = %r \n", Status));
Tpm32Header->EntryPoint = 0;
Tpm32Header->TpmSegment = (UINT32)Status;
Tpm32Header->TpmLogSize = 0x8000; // 32 KB log buffer
} else {
//
// Legacy BIOS Protocol path: call the interface's callback registration.
//
Tpm32Header->PmodeEntry = (UINT32)(UINTN)GenericCallback; // sub_410
LegBios->Callback (
LegBios,
(VOID *)&TpmAddress,
0,
&StatusCode,
&TpmAddress
);
DEBUG ((EFI_D_INFO, "\n\n linkTPMDriver: TCGLOG( %x )\n", TpmAddress - 40));
Tpm32Header->TpmSegment = (UINT32)TpmAddress;
Tpm32Header->TpmLogSize = *(UINT32 *)(TpmAddress - 40);
}
Tpm32Header->PmodeEntry = 0; // Ensure PM entry is cleared after init
}
/**
Unlink the TPM32 driver from the legacy BIOS region.
Called from the LegacyBoot event notification. Restores the TPM32 header
fields to their default states and optionally tears down the TCG2 binding.
@param[in] Event Legacy Boot event (unused).
@param[in] Tpm32 Pointer to the TPM32 header to unlink.
**/
VOID
EFIAPI
UnlinkTPM32FromEFI (
IN EFI_EVENT Event,
IN VOID *Tpm32
)
{
EFI_STATUS Status;
UINT64 StatusCode;
UINT64 Buffer;
EFI_LEGACY_BIOS_PROTOCOL *LegBios;
StatusCode = 0;
Buffer = 0;
if (qword_1DD8 == 0) {
return;
}
ASSERT (qword_1DD8 == (UINT64)Tpm32);
DEBUG ((EFI_D_INFO, "UnlinkTPM32fromEFI: TPM32( %x )\n", Tpm32));
qword_1DD8 = 0;
if (byte_1DE0) {
//
// TCG2 path: unregister via TCG2 protocol.
//
if (qword_1DE8 != 0) {
((EFI_TCG2_PROTOCOL *)qword_1DE8)->RegisterCallback (
(EFI_TCG2_PROTOCOL *)qword_1DE8,
1,
&Buffer,
&StatusCode
);
((TPM32_HEADER *)Tpm32)->EntryPoint = 0;
((TPM32_HEADER *)Tpm32)->PmodeEntry = 0;
((TPM32_HEADER *)Tpm32)->TpmLogAddress = (UINT32)StatusCode;
((TPM32_HEADER *)Tpm32)->Reserved1 = 0;
((TPM32_HEADER *)Tpm32)->TpmSegment = 0; // +0x20
((TPM32_HEADER *)Tpm32)->TpmLogSize = 11; // +0x39
((UINT8 *)Tpm32)[0x41] = 1; // +0x41 flag
}
} else {
//
// Legacy BIOS path: unregister via Legacy BIOS Protocol callback.
//
((TPM32_HEADER *)Tpm32)->PmodeEntry = 0;
LegBios = (EFI_LEGACY_BIOS_PROTOCOL *)qword_1DD0;
Status = LegBios->Callback (
LegBios,
(VOID *)&Buffer,
0,
&StatusCode,
&Buffer
);
if (!EFI_ERROR (Status)) {
((TPM32_HEADER *)Tpm32)->TpmLogAddress = (UINT32)(StatusCode - Buffer);
((UINT8 *)Tpm32)[0x1C] = ((UINT8 *)&Buffer)[11];
((TPM32_HEADER *)Tpm32)->TpmSegment = *(UINT32 *)(Buffer - 36);
((TPM32_HEADER *)Tpm32)->TpmLogSize = *(UINT32 *)(Buffer - 32) + 1;
((UINT8 *)Tpm32)[0x41] = 0;
} else {
((UINT32 *)Tpm32)[12] = 0; // Zero TpmLogAddress and TpmLogSize
}
}
}
/**
Dummy/placeholder callback for the TCG2 protocol path.
Does nothing -- used only when byte_1DE0 == 1 to satisfy the interface.
@param[in] a1 Unused.
**/
VOID
NullCallback (
VOID *a1
)
{
//
// Intentionally empty.
//
}
/**
Generic callback for the Legacy BIOS Protocol path.
Invokes the Legacy BIOS interface's callback function +16 entry point
with the given parameters.
@param[in] a1 Parameter forwarded to the callback.
**/
VOID
GenericCallback (
VOID *a1
)
{
UINT8 Buffer;
((EFI_LEGACY_BIOS_PROTOCOL *)qword_1DD0)->Callback (
(EFI_LEGACY_BIOS_PROTOCOL *)qword_1DD0,
a1,
&Buffer,
1
);
}
//
// ---------------------------------------------------------------------------
// Main initialization and entry point
// ---------------------------------------------------------------------------
/**
TCG Legacy Driver entry point.
Caches the EFI system table pointers, gets the HOB list, and calls
InitTCGLegacyInterface().
@param[in] ImageHandle The loaded image's handle.
@param[in] SystemTable The UEFI system table.
@return Status from InitTCGLegacyInterface().
**/
EFI_STATUS
EFIAPI
ModuleEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
//
// Save global handles required by the boot and runtime services libraries.
//
gImageHandle = ImageHandle;
ASSERT (gImageHandle != NULL);
gST = SystemTable;
ASSERT (gST != NULL);
gBS = SystemTable->BootServices;
ASSERT (gBS != NULL);
gRT = SystemTable->RuntimeServices;
ASSERT (gRT != NULL);
//
// Initialize the HOB list for platform configuration.
//
GetHobList ();
//
// Perform the TCG Legacy interface initialization.
//
return InitTCGLegacyInterface ();
}
/**
Main TCG Legacy interface initialization routine.
This is the core function that:
1. Checks for existing TPM hardware at memory-mapped I/O 0xFED40F00.
2. Ensures no TPM32 is already installed.
3. Locates the Legacy BIOS protocols (or falls back to TCG2 Protocol).
4. Loads the LEGX16 and TPM32 firmware volumes from flash.
5. Allocates and populates the TPM shadow region.
6. Installs the TPM32 driver in the legacy BIOS space.
7. Copies LEGX16 to the F000-E000 legacy region.
8. Scans and patches the BFI marker.
9. Registers a LegacyBoot notification event.
@return EFI_SUCCESS on success; error codes otherwise.
**/
EFI_STATUS
EFIAPI
InitTCGLegacyInterface (
VOID
)
{
UINT64 Index;
BOOLEAN MptpmLoaded;
UINT64 LegX16Addr;
EFI_LEGACY_BIOS_PROTOCOL *LegBios;
EFI_LEGACY_BIOS_PLATFORM_PROTOCOL *LegBiosPlatform;
UINT64 TpmCodePageSize;
UINT64 LegX16FileSize;
UINT64 Tpm32FileSize;
UINT64 MptpmFileSize;
UINT64 TpmCodePhysAddr;
UINT64 LegX16Buffer;
UINT64 Tpm32Buffer;
UINT64 MptpmBuffer;
UINT64 LegX16FinalAddr;
UINT64 LegacyRegionBase;
UINT64 LegacyRegionLen;
TPM32_HEADER *Tpm32Header;
UINT64 TpmEntry;
UINT64 TpmSegEntry;
UINTN BfiScanCount;
UINT32 *BfiScan;
UINT8 *BfiEntry;
UINT8 BfiChecksum;
LegX16FinalAddr = 0;
LegX16FileSize = 0;
Tpm32FileSize = 0;
MptpmFileSize = 0;
MptpmLoaded = FALSE;
LegBios = NULL;
LegBiosPlatform = NULL;
Tpm32Header = NULL;
//
// Step 1: Check if there is already TPM hardware at the legacy TPM
// memory-mapped location (0xFED40F00 = TPM_ACCESS register).
//
for (Index = 0; Index < TPM_VENDOR_ID_COUNT; Index++) {
if (*(UINT16 *)((UINT8 *)gTpmVendorIdTable + Index * 4) == *(volatile UINT16 *)0xFED40F00 &&
*(UINT16 *)((UINT8 *)gTpmVendorIdTable + Index * 4 + 2) == *(volatile UINT16 *)0xFED40F02) {
//
// TPM hardware is already present; nothing for us to do.
//
return EFI_SUCCESS;
}
}
//
// Step 2: Ensure TPM32 is not already installed.
//
if (qword_1DD8 != 0) {
DEBUG ((EFI_D_ERROR, "installedTpm32 == 0\n"));
ASSERT (qword_1DD8 == 0);
return EFI_UNSUPPORTED;
}
//
// Step 3: Locate the Legacy BIOS Protocol (0x1DB0) or TCG2 protocol.
//
if (gBS->LocateProtocol (
(EFI_GUID *)EFI_TCG2_PROTOCOL_GUID_PTR,
NULL,
(VOID **)&LegBios
) < 0) {
//
// TCG2 not found; try the AMI TCG protocol.
//
LegBios = NULL;
if (gBS->LocateProtocol (
(EFI_GUID *)AMI_TPM_PLATFORM_GUID_PTR,
NULL,
(VOID **)&qword_1DE8
) < 0) {
return EFI_UNSUPPORTED;
}
byte_1DE0 = 1; // Use TCG2 / AMI TCG protocol path
}
//
// Step 4: Locate the Legacy BIOS Platform Protocol (0x1D80).
//
if (gBS->LocateProtocol (
(EFI_GUID *)EFI_LEGACY_BIOS_PLATFORM_GUID_PTR,
NULL,
(VOID **)&LegBiosPlatform
) < 0) {
DEBUG ((EFI_D_ERROR, "Failed Locate Legacybiosprotocol %r\n", Status));
return EFI_UNSUPPORTED;
}
//
// Step 5: Locate the Legacy BIOS Protocol (0x1D60).
//
if (gBS->LocateProtocol (
(EFI_GUID *)EFI_LEGACY_BIOS_PROTOCOL_GUID_PTR,
NULL,
(VOID **)&LegBios
) < 0) {
DEBUG ((EFI_D_ERROR, "Failed Locate Legacybiosprotocol2 %r\n", Status));
return EFI_UNSUPPORTED;
}
//
// Step 6: Load the LEGX16 binary from a firmware volume file GUID.
//
LegX16Addr = 0;
Status = LocateFirmwareVolumeFile (
(EFI_GUID *)TCG_EVENT_LOG_FORMAT_GUID_PTR, // Placeholder GUID; actual file GUID
(VOID **)&LegX16Addr,
(UINTN *)&LegX16FileSize
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "Failed to load LEGX16: error=%r\n", Status));
return Status;
}
//
// Step 7: Allocate legacy region via Legacy BIOS Platform Protocol (+72).
// This allocates a region in the F000-E000 area for LEGX16.
//
Status = LegBiosPlatform->AllocateLegacyRegion (
LegBiosPlatform,
LegX16FileSize + 16,
0,
16,
&LegacyRegionBase
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "Failed to allocate legacy region LEGX16 file error=%r\n", Status));
return Status;
}
//
// Step 8: Load the TPM32 binary from firmware volume.
//
Status = LocateFirmwareVolumeFile (
(EFI_GUID *)EFI_LEGACY_BIOS_PROTOCOL_GUID_PTR, // Placeholder
(VOID **)&Tpm32Buffer,
(UINTN *)&Tpm32FileSize
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "Failed to load TPM32: error=%r\n", Status));
return Status;
}
//
// Step 9: Allocate page-aligned memory for the TPM32 code.
//
TpmCodePhysAddr = AllocateAndZeroPagesWrapper (
(Tpm32FileSize >> 12) + ((Tpm32FileSize & 0xFFF) != 0)
);
if (TpmCodePhysAddr == 0xFFFFFFFFFFFFFFFFULL) {
return EFI_OUT_OF_RESOURCES;
}
//
// Step 9b: Copy the TPM32 binary to the allocated page.
//
gBS->CopyMem (
(VOID *)(UINTN)TpmCodePhysAddr,
(VOID *)Tpm32Buffer,
Tpm32FileSize
);
//
// Step 10: Allocate a TPM log/data page for the TPM32 driver's use.
//
TpmEntry = AllocateAndZeroPagesWrapper (1); // 1 page = 4 KB
if (TpmEntry == 0xFFFFFFFFFFFFFFFFULL) {
//
// Free the previously allocated TPM code pages.
//
gBS->FreePages (TpmCodePhysAddr, (Tpm32FileSize >> 12) + ((Tpm32FileSize & 0xFFF) != 0));
return EFI_OUT_OF_RESOURCES;
}
Tpm32Header = (TPM32_HEADER *)TpmCodePhysAddr;
Tpm32Header->CodeSegmentBase = (UINT32)TpmEntry;
Tpm32Header->CodeSegmentSize = 1024;
//
// Step 11: Check for vendor ACPI table match (MPTPM or ACPI entry).
//
for (Index = 0; Index < TPM_VENDOR_ID_COUNT; Index++) {
if (*(UINT16 *)((UINT8 *)gTpmVendorIdTable + Index * 4) == *(volatile UINT16 *)0xFED40F00 &&
*(UINT16 *)((UINT8 *)gTpmVendorIdTable + Index * 4 + 2) == *(volatile UINT16 *)0xFED40F02) {
//
// TPM hardware already present; find matching ACPI device path entry.
//
UINT16 *MatchingEntry;
MatchingEntry = FindTpmVendorEntry (
gST->NumberOfTableEntries,
(UINT64)gST->ConfigurationTable,
NULL
);
Tpm32FileSize = (UINT64)(*MatchingEntry - MatchingEntry[2]); // Calculate size
LegX16Addr = Tpm32FileSize;
goto InstallDriver; // Skip MPTPM loading
}
}
//
// Step 12: Load MPTPM (multi-processor TPM) if needed.
//
Status = LocateFirmwareVolumeFile (
(EFI_GUID *)EFI_HOB_LIST_GUID_PTR, // Placeholder
(VOID **)&MptpmBuffer,
(UINTN *)&MptpmFileSize
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "Failed to load MPTPM: error=%r\n", Status));
goto ErrorCleanup;
}
LegX16Addr = AllocateAndZeroPagesWrapper (
(MptpmFileSize >> 12) + ((MptpmFileSize & 0xFFF) != 0)
);
if (LegX16Addr == 0xFFFFFFFFFFFFFFFFULL) {
DEBUG ((EFI_D_ERROR, "Failed to allocate MPTPM space =%r\n", Status));
goto ErrorCleanup;
}
MptpmLoaded = TRUE;
gBS->CopyMem (
(VOID *)(UINTN)LegX16Addr,
(VOID *)MptpmBuffer,
MptpmFileSize
);
InstallDriver:
//
// Step 13: Set the TPM32 header fields.
//
TpmSegEntry = LegX16Addr;
Tpm32Header->TpmLogAddress = (UINT32)TpmSegEntry;
//
// Write the TPM32 entry point into the LEGX16 header at the correct offset.
//
*(UINT32 *)(*(UINT16 *)(LegX16FileSize + 8) + LegX16Buffer) = (UINT32)TpmCodePhysAddr + Tpm32Header->EntryPoint;
*(UINT32 *)(LegX16FileSize + 4) = (UINT32)TpmCodePhysAddr;
//
// Step 14: Link the TPM32 driver into the legacy BIOS interface.
//
LinkTPM32Driver (Tpm32Header, LegBios);
//
// Step 15: Set the LEGX16 control flag (-7424 = 0xE300 shadow enable).
//
*(UINT16 *)(LegX16FileSize + 22) = 0xE300;
//
// Step 16: Copy the LEGX16 image to the final F000-E000 legacy region.
//
Status = LegBiosPlatform->CopyLegacyRegion (
LegBiosPlatform,
LegX16FileSize,
LegacyRegionBase,
LegX16Buffer
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "Failed to Copy final LegX16 to dest (F000-E000 area) error=%r\n", Status));
goto ErrorCleanup;
}
//
// Step 17: Install the legacy interrupt handler.
//
{
EFI_LEGACY_INTERRUPT_PROTOCOL *LegInt;
UINT64 ShadowBase;
//
// The second legacy BIOS protocol instance (0x1D80) provides interrupt
// registration.
//
LegBiosPlatform = (EFI_LEGACY_BIOS_PLATFORM_PROTOCOL *)LegBios; // Reuse for platform operations
Status = LegBiosPlatform->AllocateLegacyRegion (
LegBiosPlatform,
917504,
0x20000,
&ShadowBase
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "ASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT (!EFI_ERROR (Status));
}
//
// Initialize the shadow region data structure (48 bytes).
//
ZeroMem (v46, 48);
LegInt = (EFI_LEGACY_INTERRUPT_PROTOCOL *)ShadowBase;
// Use Legacy BIOS Platform Protocol function +8 to install the interrupt.
LegBiosPlatform->ShadowAndInstallInterruptHandler (
LegBiosPlatform,
(UINT16)(ShadowBase >> 4),
(UINT16)LegInt[9],
v46,
0,
0
);
//
// Save LEGX16 configuration words for later use.
//
gLegX16Segment = (UINT16)(ShadowBase >> 4);
gLegX16Shadow = LegInt[5];
gLegX16Init = LegInt[6];
gLegX16Checksum = LegInt[7];
gLegX16Size = LegInt[8];
DEBUG ((EFI_D_INFO, "\tLEGX16: %x:%x, %x, %x\n",
gLegX16Segment, gLegX16Shadow, gLegX16Init, gLegX16Checksum));
DEBUG ((EFI_D_INFO, "\tTPM32: header:%x entry:%x log:%x logsize:%x\n",
(UINT32)(UINTN)Tpm32Header,
(UINT32)(UINTN)Tpm32Header + Tpm32Header->EntryPoint,
Tpm32Header->TpmLogAddress,
Tpm32Header->TpmLogSize));
DEBUG ((EFI_D_INFO, "\tMPTPM: %x\n", Tpm32Header->TpmSegment));
}
//
// Step 18: Allocate legacy region for the configuration data.
//
{
UINT64 ConfigBase;
Status = LegBiosPlatform->AllocateLegacyRegion (
LegBiosPlatform,
26,
0,
16,
&ConfigBase
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "ASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT (!EFI_ERROR (Status));
goto RegionCleanup;
}
//
// Write the configuration words to the legacy region.
//
Status = LegBiosPlatform->CopyLegacyRegion (
LegBiosPlatform,
10,
ConfigBase,
&gLegX16Segment
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "ASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT (!EFI_ERROR (Status));
goto RegionCleanup;
}
}
//
// Step 19: Release and re-acquire the legacy region for shadow operations.
//
Status = LegBiosPlatform->AllocateLegacyRegion (
LegBiosPlatform,
917504,
0x20000,
&ShadowBase
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "ASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT (!EFI_ERROR (Status));
goto RegionCleanup;
}
//
// Step 20: Scan and patch the BFI marker in the legacy ROM region.
//
ScanAndPatchBfi ((UINT64)LegBiosPlatform, (UINT64)LegBios, gLegX16Segment);
//
// Step 21: Release the shadow region.
//
LegBiosPlatform->AllocateLegacyRegion (
LegBiosPlatform,
917504,
0x20000,
&ShadowBase
);
//
// Step 22: Save the installed TPM32 header pointer.
//
qword_1DD8 = (UINT64)Tpm32Header;
//
// Step 23: Register a Legacy Boot notification event.
//
{
EFI_EVENT LegacyBootEvent;
Status = gBS->CreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
UnlinkTPM32FromEFI,
Tpm32Header,
(EFI_GUID *)EFI_EVENT_LEGACY_BOOT_GUID_PTR,
&LegacyBootEvent
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR,
"!(((INTN)(RETURN_STATUS)(Status = CreateLegacyBootEvent("
" 8, UnlinkTPM32fromEFI, tpm32hdr, &event ))) < 0)\n"));
ASSERT (!EFI_ERROR (Status));
}
}
return EFI_SUCCESS;
RegionCleanup:
//
// Release the shadow region on error.
//
LegBiosPlatform->AllocateLegacyRegion (
LegBiosPlatform,
917504,
0x20000,
&ShadowBase
);
ErrorCleanup:
//
// Free allocated pages on error.
//
if (TpmCodePhysAddr != 0xFFFFFFFFFFFFFFFFULL) {
gBS->FreePages (
TpmCodePhysAddr,
(Tpm32FileSize >> 12) + ((Tpm32FileSize & 0xFFF) != 0)
);
}
if (TpmEntry != 0xFFFFFFFFFFFFFFFFULL) {
gBS->FreePages (TpmEntry, 1);
}
if (MptpmLoaded) {
gBS->FreePages (
LegX16Addr,
(MptpmFileSize >> 12) + ((MptpmFileSize & 0xFFF) != 0)
);
}
DEBUG ((EFI_D_ERROR, "InitTCGLegacyInterface exit fail Status = %r\n", Status));
return Status;
}