windows-nt/Source/XPSP1/NT/base/boot/lib/amd64/amd64.c

794 lines
18 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1991-2000 Microsoft Corporation
Module Name:
amd64.c
Abstract:
This module contains routines necessary to support loading and
transitioning into an AMD64 kernel. The code in this module has
access to amd64-specific defines found in amd64.h but not to i386-
specific declarations found in i386.h.
Author:
Forrest Foltz (forrestf) 20-Apr-2000
Environment:
Revision History:
--*/
//
// Here, we want the header files to be processed as though building for
// AMD64. Make appropriate definitions and undefs.
//
#define _AMD64_
#define _M_AMD64
#define _WIN64
#undef _X86_
#undef X86
#undef _M_X86_
#undef _M_IX86
//
// Warning 4163 is "function unavailable as intrinsic"
//
#pragma warning(disable:4163)
//
// Warning 4235 is "nonstandard extension used", referring to __unaligned
//
#pragma warning(disable:4235)
//
// Warning 4391 is "incorrect return type for intrinsi function"
//
#pragma warning(disable:4391)
#include "bootx86.h"
#include "amd64prv.h"
#include <pcmp.inc>
#include <ntapic.inc>
//
// GDT: functino to convert selector to GDT entry.
//
PKGDTENTRY64
__inline
GDT_PTR (
PGDT_64 Gdt,
ULONG Selector
)
{
ULONG index;
PKGDTENTRY64 entry;
index = Selector & ~7;
entry = (PKGDTENTRY64)((PUCHAR)Gdt + index);
return entry;
}
//
// Interrupt descriptor table
//
typedef struct _IDT_64 {
KIDTENTRY64 Entries[ MAXIMUM_IDTVECTOR + 1 ];
} IDT_64, *PIDT_64;
#define VIRTUAL_ADDRESS_BITS 48
#define VIRTUAL_ADDRESS_MASK (((ULONGLONG)1 << VIRTUAL_ADDRESS_BITS) - 1)
//
// Longmode ring0 code selector. This actually lives in the middle of the
// transition data stream in amd64s.asm
//
extern USHORT BlAmd64_KGDT64_R0_CODE;
//
// Data values exported to amd64x86.c
//
const ULONG BlAmd64DoubleFaultStackSize = DOUBLE_FAULT_STACK_SIZE;
const ULONG BlAmd64KernelStackSize = KERNEL_STACK_SIZE;
const ULONG BlAmd64McaExceptionStackSize = KERNEL_MCA_EXCEPTION_STACK_SIZE;
const ULONG BlAmd64GdtSize = KGDT64_LAST;
const ULONG BlAmd64IdtSize = sizeof(IDT_64);
#define CONST_EXPORT(x) const ULONG BlAmd64_##x = x;
CONST_EXPORT(MSR_FS_BASE)
CONST_EXPORT(MSR_GS_BASE)
CONST_EXPORT(KGDT64_SYS_TSS)
CONST_EXPORT(MSR_EFER)
CONST_EXPORT(TSS_IST_PANIC)
CONST_EXPORT(TSS_IST_MCA)
const ULONG64 BlAmd64_LOCALAPIC = LOCALAPIC;
const ULONG64 BlAmd64UserSharedData = KI_USER_SHARED_DATA;
//
// Flags to be enabled in the EFER MSR before transitioning to long mode
//
const ULONG BlAmd64_MSR_EFER_Flags = MSR_LME | MSR_SCE;
//
// Array of address bit decode counts and recursive mapping bases,
// one for each level of the mapping
//
AMD64_MAPPING_INFO BlAmd64MappingLevels[ AMD64_MAPPING_LEVELS ] =
{
{ PTE_BASE, PTI_MASK_AMD64, PTI_SHIFT },
{ PDE_BASE, PDI_MASK_AMD64, PDI_SHIFT },
{ PPE_BASE, PPI_MASK, PPI_SHIFT },
{ PXE_BASE, PXI_MASK, PXI_SHIFT }
};
//
// BlAmd64TopLevelPte refers to the physical page number of the Page Map
// Level 4 (PML4) table.
//
// BlAmd64TopLevelPte is not really a page table entry and so does not
// actually exist as an element within a page table. It exists only as
// a convenience to BlAmd64CreateAmd64Mapping().
//
HARDWARE_PTE BlAmd64TopLevelPte;
//
// PAGE_MAP_LEVEL_4 yields the identity-mapped (physical) address of the
// PML4 table.
//
#define PAGE_MAP_LEVEL_4 \
((PPAGE_TABLE)(BlAmd64TopLevelPte.PageFrameNumber << PAGE_SHIFT))
//
// Special PFN for BlAmd64CreateMapping
//
#define PFN_NO_MAP ((PFN_NUMBER)-1)
//
// Size of the VA mapped by a level 0 page table
//
#define PAGE_TABLE_VA ((POINTER64)(PTES_PER_PAGE * PAGE_SIZE))
//
// Prototypes for local functions
//
ARC_STATUS
BlAmd64CreateMappingWorker(
IN ULONGLONG Va,
IN PFN_NUMBER Pfn,
IN ULONG MappingLevel,
IN OUT PHARDWARE_PTE UpperLevelPte
);
VOID
BlAmd64MakePteValid(
IN PHARDWARE_PTE Pte,
IN PFN_NUMBER Pfn
);
VOID
BlAmd64ClearTopLevelPte(
VOID
)
/*++
Routine Description:
This routine simply clears BlAmd64TopLevelPte.
Arguments:
None.
Return Value:
None.
--*/
{
*(PULONG64)&BlAmd64TopLevelPte = 0;
}
ARC_STATUS
BlAmd64CreateMapping(
IN ULONGLONG Va,
IN PFN_NUMBER Pfn
)
/*++
Routine Description:
This function maps a virtual address into a 4-level AMD64 page mapping
structure.
Arguments:
Va - Supplies the 64-bit virtual address to map
Pfn - Supplies the 64-bit physical page number to map the address to
Return Value:
ARC_STATUS - Status of operation.
--*/
{
ARC_STATUS status;
status = BlAmd64CreateMappingWorker( Va & VIRTUAL_ADDRESS_MASK,
Pfn,
AMD64_MAPPING_LEVELS - 1,
&BlAmd64TopLevelPte );
return status;
}
ARC_STATUS
BlAmd64CreateMappingWorker(
IN ULONGLONG Va,
IN PFN_NUMBER Pfn,
IN ULONG MappingLevel,
IN OUT PHARDWARE_PTE UpperLevelPte
)
/*++
Routine Description:
This function creates an address mapping in a single level of an AMD64
4-level mapping structure. It is called only by BlCreateMapping
and by itself, recursively.
Arguments:
Va - Supplies the 64-bit virtual address to map. This address has already
had any insignificant upper bits masked via VIRTUAL_ADDRESS_MASK.
Pfn - Supplies the 64-bit physical page number to map the address to. If
Pfn == PFN_NO_MAP, then all of the page tables are put into
place to support the mapping but the level 0 pte itself is not
actually filled in. This is used to create the HAL's VA mapping
area.
MappingLevel - The mapping level in which to create the appropriate
mapping. Must be 0, 1, 2 or 3.
UpperLevelPte - A pointer to the parent PTE that refers to the page
at this mapping level. If no page exists at this level for this
address, then this routine will allocate one and modify
UpperLevelPte appropriately.
Return Value:
ARC_STATUS - Status of operation.
--*/
{
PFN_NUMBER pageTablePfn;
PAMD64_PAGE_TABLE pageTable;
ULONGLONG va;
PAMD64_MAPPING_INFO mappingInfo;
ULONG pteIndex;
PHARDWARE_PTE pte;
ARC_STATUS status;
BOOLEAN newPage;
mappingInfo = &BlAmd64MappingLevels[ MappingLevel ];
if (UpperLevelPte->Valid == 0) {
//
// A new page table must be allocated.
//
newPage = TRUE;
pageTable = BlAmd64AllocatePageTable();
if (pageTable == NULL) {
return ENOMEM;
}
//
// Reference the new page table with the parent PTE
//
pageTablePfn = (ULONG)pageTable >> PAGE_SHIFT;
BlAmd64MakePteValid( UpperLevelPte, pageTablePfn );
if (MappingLevel == (AMD64_MAPPING_LEVELS - 1)) {
//
// We have just allocated the top-level page. Insert a
// recursive mapping here.
//
pteIndex = (ULONG)((mappingInfo->RecursiveMappingBase >>
mappingInfo->AddressShift) &
mappingInfo->AddressMask);
pte = &pageTable->PteArray[ pteIndex ];
BlAmd64MakePteValid( pte, pageTablePfn );
}
} else {
//
// A page table structure already exists for this level.
//
newPage = FALSE;
pageTablePfn = UpperLevelPte->PageFrameNumber;
pageTable = (PAMD64_PAGE_TABLE)((ULONG)pageTablePfn << PAGE_SHIFT);
}
//
// Derive a pointer to the appropriate PTE within the page table
//
pteIndex =
(ULONG)(Va >> mappingInfo->AddressShift) & mappingInfo->AddressMask;
pte = &pageTable->PteArray[ pteIndex ];
if (MappingLevel == 0) {
if (Pfn != PFN_NO_MAP) {
//
// This is an actual level 0, or PTE, entry. Just set it with
// the Pfn that was passed in.
//
BlAmd64MakePteValid( pte, Pfn );
} else {
//
// This is a special HAL mapping, one that ensures that all
// levels of page table are in place to support this mapping
// but doesn't actually fill in the level 0 PTE.
//
// So do nothing here except break the recursion.
//
}
} else {
//
// More mapping levels to go, call this function recursively and
// process the next level.
//
status = BlAmd64CreateMappingWorker( Va,
Pfn,
MappingLevel - 1,
pte );
if (status != ESUCCESS) {
return status;
}
}
if (newPage != FALSE) {
//
// A new page table was allocated, above. Recursively map
// it within the PTE_BASE region.
//
va = (Va >> mappingInfo->AddressShift);
va *= sizeof(HARDWARE_PTE);
va += mappingInfo->RecursiveMappingBase;
status = BlAmd64CreateMapping( va, pageTablePfn );
if (status != ESUCCESS) {
return status;
}
}
return ESUCCESS;
}
VOID
BlAmd64MakePteValid(
IN OUT PHARDWARE_PTE Pte,
IN PFN_NUMBER Pfn
)
/*++
Routine Description:
This routine fills an AMD64 Pte with the supplied Pfn and makes it
valid.
Arguments:
Pte - Supplies a pointer to the Pte to make valid.
Pfn - Supplies the page frame number to set in the Pte.
Return Value:
ARC_STATUS - Status of operation.
--*/
{
//
// Make sure we're not just overwriting a PFN in an already
// valid PTE.
//
ASSERT( Pte->Valid == 0 || Pte->PageFrameNumber == Pfn );
Pte->PageFrameNumber = Pfn;
Pte->Valid = 1;
Pte->Write = 1;
Pte->Accessed = 1;
Pte->Dirty = 1;
}
VOID
BlAmd64BuildGdtEntry(
IN PGDT_64 Gdt,
IN USHORT Selector,
IN POINTER64 Base,
IN ULONGLONG Limit,
IN ULONG Type,
IN ULONG Dpl,
IN BOOLEAN LongMode,
IN BOOLEAN DefaultBig
)
/*++
Routine Description:
This routine fills in an AMD64 GDT entry.
Arguments:
Gdt - Supplies a pointer to the GDT.
Selector - Segment selector of the GDT entry within Gdt.
Base - Base address value of the descriptor.
Limit - Limit value of the descriptor.
Type - 5-bit type value of the descriptor.
Dpl - Priviledge value of the descriptor.
LongMode - Indicates whether this is a longmode descriptor (valid only
for code segment descriptors).
DefaultBig - Supplies the value for the default/big field in the
descriptor.
Return Value:
None.
--*/
{
ULONG limit20;
PKGDTENTRY64 gdtEntry;
KGDT_BASE gdtBase;
KGDT_LIMIT gdtLimit;
gdtEntry = GDT_PTR(Gdt,Selector);
//
// Set the Base and LongMode fields
//
gdtBase.Base = Base;
gdtEntry->BaseLow = gdtBase.BaseLow;
gdtEntry->Bits.BaseMiddle = gdtBase.BaseMiddle;
gdtEntry->Bits.BaseHigh = gdtBase.BaseHigh;
gdtEntry->Bits.LongMode = 0;
if ((LongMode != FALSE) || (Type == TYPE_TSS64)) {
//
// All long GDT entries use a 64-bit base and have the longmode bit
// set.
//
// In addition, a TSS GDT entry uses a 64-bit but does *not* set the
// longmode bit. This applies to an LDT entry as well, which is not
// used in this OS.
//
if (Type != TYPE_TSS64) {
gdtEntry->Bits.LongMode = 1;
}
gdtEntry->MustBeZero = 0;
gdtEntry->BaseUpper = gdtBase.BaseUpper;
}
//
// Set the Limit and Granularity fields
//
if (Limit > (1 << 20)) {
limit20 = (ULONG)(Limit / PAGE_SIZE);
gdtEntry->Bits.Granularity = 1;
} else {
limit20 = (ULONG)Limit;
gdtEntry->Bits.Granularity = 0;
}
gdtLimit.Limit = limit20;
gdtEntry->LimitLow = gdtLimit.LimitLow;
gdtEntry->Bits.LimitHigh = gdtLimit.LimitHigh;
//
// Set Present = 1 unless this is a NULL descriptor
//
if (Type == 0) {
gdtEntry->Bits.Present = 0;
} else {
gdtEntry->Bits.Present = 1;
}
//
// Set remaining fields
//
gdtEntry->Bits.Type = Type;
gdtEntry->Bits.Dpl = Dpl;
gdtEntry->Bits.DefaultBig = DefaultBig;
gdtEntry->Bits.System = 0;
}
VOID
BlAmd64BuildAmd64GDT(
IN PVOID SysTss,
OUT PVOID Gdt
)
/*++
Routine Description:
This routine initializes the longmode Global Descriptor Table.
Arguments:
SysTss - Supplies a 32-bit KSEG0_X86 pointer to the system TSS.
Gdt - Supplies a 32-bit pointer to the Gdt to fill in.
Return Value:
None.
--*/
{
PGDT_64 gdt64;
POINTER64 sysTss64;
gdt64 = (PGDT_64)Gdt;
//
// KGDT64_NULL: NULL descriptor
//
BlAmd64BuildGdtEntry(gdt64,KGDT64_NULL,
0,0,0,0,0,0); // Null selector, all zeros
//
// KGDT_R0_CODE: Kernel mode code
//
BlAmd64BuildGdtEntry(gdt64,KGDT64_R0_CODE,
0, // Base and limit are ignored
0, // in a long-mode code selector
TYPE_CODE, // Code segment: Execute/Read
DPL_SYSTEM, // Kernel only
TRUE, // Longmode
FALSE); // Not 32-bit default
//
// KGDT_R0_STACK: Kernel mode stack
//
BlAmd64BuildGdtEntry(gdt64,KGDT64_R0_DATA,
0, // Base and limit are ignored
0, // when in long-mode
TYPE_DATA, // Data segment: Read/Write
DPL_SYSTEM, // Kernel only
FALSE, // Not longmode
TRUE); // 32-bit default
//
// KDT_SYS_TSS: Kernel mode system task state
//
sysTss64 = PTR_64(SysTss);
BlAmd64BuildGdtEntry(gdt64,KGDT64_SYS_TSS,
sysTss64, // Base to be filled in at runtime
FIELD_OFFSET(KTSS64,IoMap)-1, // Contains only a KTSS64
TYPE_TSS64, // Not busy TSS
DPL_SYSTEM, // Kernel only
FALSE, // Not longmode
FALSE); // Not 32-bit default
//
// KGDT64_R3_CODE: User mode 64-bit code
//
BlAmd64BuildGdtEntry(gdt64,KGDT64_R3_CODE,
0, // Base and limit are ignored
0, // in a long-mode code selector
TYPE_CODE, // Code segment: Execute/Read
DPL_USER, // User mode
TRUE, // Longmode
FALSE); // Not 32-bit default
//
// KGDT64_R3_CMCODE: User-mode 32-bit code. Flat 2 gig.
//
BlAmd64BuildGdtEntry(gdt64,KGDT64_R3_CMCODE,
0, // Base
0x7FFFFFFF, // 2G limit
TYPE_CODE, // Code segment: Execute/Read
DPL_USER, // User mode
FALSE, // Not longmode
TRUE); // 32-bit default
//
// KGDT64_R3_DATA: User-mode 32-bit data. Flat 2 gig.
//
BlAmd64BuildGdtEntry(gdt64,KGDT64_R3_DATA,
0, // Base
0x7FFFFFFF, // 2G limit
TYPE_DATA, // Data segment: Read/Write
DPL_USER, // User mode
FALSE, // Not longmode
TRUE); // 32-bit default
//
// KGDT64_R3_CMTEB: User-mode 32-bit TEB. Flat 4K.
//
BlAmd64BuildGdtEntry(gdt64,KGDT64_R3_CMTEB,
0, // Base
0x0FFF, // 4K limit
TYPE_DATA, // Data segment: Read/Write
DPL_USER, // User mode
FALSE, // Not longmode
TRUE); // 32-bit default
//
// Set the code selector
//
BlAmd64_KGDT64_R0_CODE = KGDT64_R0_CODE;
}
ARC_STATUS
BlAmd64MapHalVaSpace(
VOID
)
/*++
Routine Description:
This routine initializes the VA space reserved for the HAL. This
involves building all page tables necessary to support the mappings
but not actually filling in any level 0 PTEs.
Arguments:
None.
Return Value:
ARC_STATUS - Status of operation.
--*/
{
POINTER64 va;
ULONG round;
ULONG vaRemaining;
ARC_STATUS status;
//
// The hal has a piece of VA space reserved for it, from HAL_VA_START to
// HAL_VA_START + HAL_VA_SIZE - 1.
//
// This routine ensures that all necessary levels of page tables are
// present to support any mappings that the hal might put there.
//
vaRemaining = HAL_VA_SIZE;
va = HAL_VA_START;
//
// Round VA down to a page table boundary.
//
round = (ULONG)(va & (PAGE_TABLE_VA-1));
va -= round;
vaRemaining += round;
while (TRUE) {
//
// Perform a "mapping". The special PFN_NUMBER sets up all of
// the page tables necessary to support the mapping, without
// actually filling in a level 0 PTE.
//
status = BlAmd64CreateMapping(va, PFN_NO_MAP);
if (status != ESUCCESS) {
return status;
}
if (vaRemaining <= PAGE_TABLE_VA) {
break;
}
vaRemaining -= PAGE_TABLE_VA;
va += PAGE_TABLE_VA;
}
return ESUCCESS;
}