audk/UefiCpuPkg/Library/MpInitLib/X64/MpFuncs.nasm

399 lines
12 KiB
NASM
Raw Normal View History

;------------------------------------------------------------------------------ ;
; Copyright (c) 2015 - 2016, Intel Corporation. All rights reserved.<BR>
; This program and the accompanying materials
; are licensed and made available under the terms and conditions of the BSD License
; which accompanies this distribution. The full text of the license may be found at
; http://opensource.org/licenses/bsd-license.php.
;
; THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
; WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
;
; Module Name:
;
; MpFuncs.nasm
;
; Abstract:
;
; This is the assembly code for MP support
;
;-------------------------------------------------------------------------------
%include "MpEqu.inc"
extern ASM_PFX(InitializeFloatingPointUnits)
DEFAULT REL
SECTION .text
;-------------------------------------------------------------------------------------
;RendezvousFunnelProc procedure follows. All APs execute their procedure. This
;procedure serializes all the AP processors through an Init sequence. It must be
;noted that APs arrive here very raw...ie: real mode, no stack.
;ALSO THIS PROCEDURE IS EXECUTED BY APs ONLY ON 16 BIT MODE. HENCE THIS PROC
;IS IN MACHINE CODE.
;-------------------------------------------------------------------------------------
global ASM_PFX(RendezvousFunnelProc)
ASM_PFX(RendezvousFunnelProc):
RendezvousFunnelProcStart:
; At this point CS = 0x(vv00) and ip= 0x0.
; Save BIST information to ebp firstly
BITS 16
mov ebp, eax ; Save BIST information
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
xor ax, ax
mov fs, ax
mov gs, ax
mov si, BufferStartLocation
mov ebx, [si]
mov di, ModeOffsetLocation
mov eax, [di]
mov di, CodeSegmentLocation
mov edx, [di]
mov di, ax
sub di, 02h
mov [di],dx ; Patch long mode CS
sub di, 04h
add eax, ebx
mov [di],eax ; Patch address
mov si, GdtrLocation
o32 lgdt [cs:si]
mov si, IdtrLocation
o32 lidt [cs:si]
mov si, EnableExecuteDisableLocation
cmp byte [si], 0
jz SkipEnableExecuteDisableBit
;
; Enable execute disable bit
;
mov ecx, 0c0000080h ; EFER MSR number
rdmsr ; Read EFER
bts eax, 11 ; Enable Execute Disable Bit
wrmsr ; Write EFER
SkipEnableExecuteDisableBit:
mov di, DataSegmentLocation
mov edi, [di] ; Save long mode DS in edi
mov si, Cr3Location ; Save CR3 in ecx
mov ecx, [si]
xor ax, ax
mov ds, ax ; Clear data segment
mov eax, cr0 ; Get control register 0
or eax, 000000003h ; Set PE bit (bit #0) & MP
mov cr0, eax
mov eax, cr4
bts eax, 5
mov cr4, eax
mov cr3, ecx ; Load CR3
mov ecx, 0c0000080h ; EFER MSR number
rdmsr ; Read EFER
bts eax, 8 ; Set LME=1
wrmsr ; Write EFER
mov eax, cr0 ; Read CR0
bts eax, 31 ; Set PG=1
mov cr0, eax ; Write CR0
jmp 0:strict dword 0 ; far jump to long mode
BITS 64
LongModeStart:
mov eax, edi
mov ds, ax
mov es, ax
mov ss, ax
mov esi, ebx
lea edi, [esi + InitFlagLocation]
cmp qword [edi], 1 ; ApInitConfig
jnz GetApicId
; AP init
mov edi, esi
add edi, LockLocation
mov rax, NotVacantFlag
TestLock:
xchg qword [edi], rax
cmp rax, NotVacantFlag
jz TestLock
lea ecx, [esi + NumApsExecutingLocation]
inc dword [ecx]
mov ebx, [ecx]
Releaselock:
mov rax, VacantFlag
xchg qword [edi], rax
; program stack
mov edi, esi
add edi, StackSizeLocation
mov eax, dword [edi]
mov ecx, ebx
inc ecx
mul ecx ; EAX = StackSize * (CpuNumber + 1)
mov edi, esi
add edi, StackStartAddressLocation
add rax, qword [edi]
mov rsp, rax
jmp CProcedureInvoke
GetApicId:
mov eax, 0
cpuid
cmp eax, 0bh
UefiCpuPkg/MpInitLib: fix feature test for Extended Topology CPUID leaf According to the Intel SDM (325462-060US / September 2016), > INPUT EAX = 0BH: Returns Extended Topology Information > > [...] Software must detect the presence of CPUID leaf 0BH by verifying > (a) the highest leaf index supported by CPUID is >= 0BH, and > (b) CPUID.0BH:EBX[15:0] reports a non-zero value. [...] The "GetApicId" sections in the Ia32 and X64 "MpFuncs.nasm" files do not perform check (b). This causes an actual bug in the following OVMF setup: - Intel W3550 host processor <http://ark.intel.com/products/39720/>, - the QEMU/KVM guest's VCPU model is set to "host", that is, "the CPU visible to the guest should be exactly the same as the host CPU". Under "GetApicId", check (a) passes: the CPUID level of the W3550 is exactly 11 decimal. However, leaf 11 itself is not supported, therefore EDX is set to zero: > If a value entered for CPUID.EAX is less than or equal to the maximum > input value and the leaf is not supported on that processor then 0 is > returned in all the registers. Because we don't check (b), the "GetProcessorNumber" section of the code is reached with an initial APIC ID of 0 in EDX on all of the APs. Given that "GetProcessorNumber" searches the "MP_CPU_EXCHANGE_INFO.CpuInfo[*].InitialApicId" fields for a match, all APs enter ApWakeupFunction() with an identical "NumApsExecuting" parameter. This results in unpredictable guest behavior (crashes, reboots, hangs etc). Reorganize the "GetApicId" section and add the missing check in both assembly files. Cc: Jeff Fan <jeff.fan@intel.com> Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Laszlo Ersek <lersek@redhat.com> Reviewed-by: Jeff Fan <jeff.fan@intel.com>
2016-11-22 13:58:54 +01:00
jb NoX2Apic ; CPUID level below CPUID_EXTENDED_TOPOLOGY
mov eax, 0bh
xor ecx, ecx
cpuid
test ebx, 0ffffh
jz NoX2Apic ; CPUID.0BH:EBX[15:0] is zero
; Processor is x2APIC capable; 32-bit x2APIC ID is already in EDX
jmp GetProcessorNumber
NoX2Apic:
; Processor is not x2APIC capable, so get 8-bit APIC ID
mov eax, 1
cpuid
shr ebx, 24
mov edx, ebx
GetProcessorNumber:
;
; Get processor number for this AP
; Note that BSP may become an AP due to SwitchBsp()
;
xor ebx, ebx
lea eax, [esi + CpuInfoLocation]
mov edi, [eax]
GetNextProcNumber:
cmp dword [edi], edx ; APIC ID match?
jz ProgramStack
UefiCpuPkg/MpInitLib: support 64-bit AP stack addresses The cached "CPU_INFO_IN_HOB.ApTopOfStack" field currently has type UINT32. This is not ideal because the AP stacks are located within "CpuMpData->Buffer", which is allocated with a plain AllocatePages() call in MpInitLibInitialize(): platform CpuMpPei included PEI RAM > 4GB result -------- ----------------- ------------- ------ Ia32 * n/a good Ia32X64 no n/a BAD Ia32X64 yes n/a good X64 no * BAD X64 yes no good X64 yes yes BAD - If we are on an Ia32X64 or X64 platform that does not include CpuMpPei, then CpuDxe cannot reuse the CPU_INFO_IN_HOB structures preallocated by CpuMpPei (through the CpuInitMpLib GUID HOB), and then AllocatePages() -- invoked first in 64-bit DXE -- could return an address outside of 32-bit address space. - If we are on an X64 platform where the permanent PEI RAM extends above the 32-bit address space, then the same issue can surface even if CpuMpPei is included: even the original allocation of the CPU_INFO_IN_HOB structures, by CpuMpPei, could be satisfied from above 4GB. The original "AP init" branch in "X64/MpFuncs.nasm" correctly considers a 64-bit stack start: the "MP_CPU_EXCHANGE_INFO.StackStart" field has type UINTN, and the code uses QWORD addition and movement to set RSP from it. Adapt the "GetApicId" branch of "X64/MpFuncs.nasm": - change the type of "CPU_INFO_IN_HOB.ApTopOfStack" to UINT64, - remove the explicit truncation to UINT32 in InitializeApData(), - update the "GetNextProcNumber" iteration size to the new size of "CPU_INFO_IN_HOB", - set RSP with a QWORD movement from "CPU_INFO_IN_HOB.ApTopOfStack". Because the same CPU_INFO_IN_HOB structure is used by "Ia32/MpFuncs.nasm", we have to update the "GetNextProcNumber" iteration size there as well. The ESP setting can be preserved as a DWORD movement from the original offset (decimal 12), since our integers are little endian. Cc: Jeff Fan <jeff.fan@intel.com> Fixes: 845c5be1fd9bf7edfac4a103dfab70829686978f Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Laszlo Ersek <lersek@redhat.com> Reviewed-by: Jeff Fan <jeff.fan@intel.com>
2016-11-16 23:31:11 +01:00
add edi, 20
inc ebx
jmp GetNextProcNumber
ProgramStack:
UefiCpuPkg/MpInitLib: support 64-bit AP stack addresses The cached "CPU_INFO_IN_HOB.ApTopOfStack" field currently has type UINT32. This is not ideal because the AP stacks are located within "CpuMpData->Buffer", which is allocated with a plain AllocatePages() call in MpInitLibInitialize(): platform CpuMpPei included PEI RAM > 4GB result -------- ----------------- ------------- ------ Ia32 * n/a good Ia32X64 no n/a BAD Ia32X64 yes n/a good X64 no * BAD X64 yes no good X64 yes yes BAD - If we are on an Ia32X64 or X64 platform that does not include CpuMpPei, then CpuDxe cannot reuse the CPU_INFO_IN_HOB structures preallocated by CpuMpPei (through the CpuInitMpLib GUID HOB), and then AllocatePages() -- invoked first in 64-bit DXE -- could return an address outside of 32-bit address space. - If we are on an X64 platform where the permanent PEI RAM extends above the 32-bit address space, then the same issue can surface even if CpuMpPei is included: even the original allocation of the CPU_INFO_IN_HOB structures, by CpuMpPei, could be satisfied from above 4GB. The original "AP init" branch in "X64/MpFuncs.nasm" correctly considers a 64-bit stack start: the "MP_CPU_EXCHANGE_INFO.StackStart" field has type UINTN, and the code uses QWORD addition and movement to set RSP from it. Adapt the "GetApicId" branch of "X64/MpFuncs.nasm": - change the type of "CPU_INFO_IN_HOB.ApTopOfStack" to UINT64, - remove the explicit truncation to UINT32 in InitializeApData(), - update the "GetNextProcNumber" iteration size to the new size of "CPU_INFO_IN_HOB", - set RSP with a QWORD movement from "CPU_INFO_IN_HOB.ApTopOfStack". Because the same CPU_INFO_IN_HOB structure is used by "Ia32/MpFuncs.nasm", we have to update the "GetNextProcNumber" iteration size there as well. The ESP setting can be preserved as a DWORD movement from the original offset (decimal 12), since our integers are little endian. Cc: Jeff Fan <jeff.fan@intel.com> Fixes: 845c5be1fd9bf7edfac4a103dfab70829686978f Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Laszlo Ersek <lersek@redhat.com> Reviewed-by: Jeff Fan <jeff.fan@intel.com>
2016-11-16 23:31:11 +01:00
mov rsp, qword [edi + 12]
CProcedureInvoke:
push rbp ; Push BIST data at top of AP stack
xor rbp, rbp ; Clear ebp for call stack trace
push rbp
mov rbp, rsp
mov rax, ASM_PFX(InitializeFloatingPointUnits)
sub rsp, 20h
call rax ; Call assembly function to initialize FPU per UEFI spec
add rsp, 20h
mov edx, ebx ; edx is NumApsExecuting
mov ecx, esi
add ecx, LockLocation ; rcx is address of exchange info data buffer
mov edi, esi
add edi, ApProcedureLocation
mov rax, qword [edi]
sub rsp, 20h
call rax ; Invoke C function
add rsp, 20h
jmp $ ; Should never reach here
RendezvousFunnelProcEnd:
;-------------------------------------------------------------------------------------
; AsmRelocateApLoop (MwaitSupport, ApTargetCState, PmCodeSegment, TopOfApStack, CountTofinish);
;-------------------------------------------------------------------------------------
global ASM_PFX(AsmRelocateApLoop)
ASM_PFX(AsmRelocateApLoop):
AsmRelocateApLoopStart:
mov rax, [rsp + 40] ; CountTofinish
lock dec dword [rax] ; (*CountTofinish)--
mov rsp, r9
push rcx
push rdx
lea rsi, [PmEntry] ; rsi <- The start address of transition code
push r8
push rsi
DB 0x48
retf
BITS 32
PmEntry:
mov eax, cr0
btr eax, 31 ; Clear CR0.PG
mov cr0, eax ; Disable paging and caches
mov ebx, edx ; Save EntryPoint to rbx, for rdmsr will overwrite rdx
mov ecx, 0xc0000080
rdmsr
and ah, ~ 1 ; Clear LME
wrmsr
mov eax, cr4
and al, ~ (1 << 5) ; Clear PAE
mov cr4, eax
pop edx
add esp, 4
pop ecx,
add esp, 4
cmp cl, 1 ; Check mwait-monitor support
jnz HltLoop
mov ebx, edx ; Save C-State to ebx
MwaitLoop:
mov eax, esp ; Set Monitor Address
xor ecx, ecx ; ecx = 0
xor edx, edx ; edx = 0
monitor
mov eax, ebx ; Mwait Cx, Target C-State per eax[7:4]
shl eax, 4
mwait
jmp MwaitLoop
HltLoop:
cli
hlt
jmp HltLoop
BITS 64
AsmRelocateApLoopEnd:
;-------------------------------------------------------------------------------------
; AsmGetAddressMap (&AddressMap);
;-------------------------------------------------------------------------------------
global ASM_PFX(AsmGetAddressMap)
ASM_PFX(AsmGetAddressMap):
mov rax, ASM_PFX(RendezvousFunnelProc)
mov qword [rcx], rax
mov qword [rcx + 8h], LongModeStart - RendezvousFunnelProcStart
mov qword [rcx + 10h], RendezvousFunnelProcEnd - RendezvousFunnelProcStart
mov rax, ASM_PFX(AsmRelocateApLoop)
mov qword [rcx + 18h], rax
mov qword [rcx + 20h], AsmRelocateApLoopEnd - AsmRelocateApLoopStart
ret
;-------------------------------------------------------------------------------------
;AsmExchangeRole procedure follows. This procedure executed by current BSP, that is
;about to become an AP. It switches its stack with the current AP.
;AsmExchangeRole (IN CPU_EXCHANGE_INFO *MyInfo, IN CPU_EXCHANGE_INFO *OthersInfo);
;-------------------------------------------------------------------------------------
global ASM_PFX(AsmExchangeRole)
ASM_PFX(AsmExchangeRole):
; DO NOT call other functions in this function, since 2 CPU may use 1 stack
; at the same time. If 1 CPU try to call a function, stack will be corrupted.
push rax
push rbx
push rcx
push rdx
push rsi
push rdi
push rbp
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
mov rax, cr0
push rax
mov rax, cr4
push rax
; rsi contains MyInfo pointer
mov rsi, rcx
; rdi contains OthersInfo pointer
mov rdi, rdx
;Store EFLAGS, GDTR and IDTR regiter to stack
pushfq
sgdt [rsi + 16]
sidt [rsi + 26]
; Store the its StackPointer
mov [rsi + 8], rsp
; update its switch state to STORED
mov byte [rsi], CPU_SWITCH_STATE_STORED
WaitForOtherStored:
; wait until the other CPU finish storing its state
cmp byte [rdi], CPU_SWITCH_STATE_STORED
jz OtherStored
pause
jmp WaitForOtherStored
OtherStored:
; Since another CPU already stored its state, load them
; load GDTR value
lgdt [rdi + 16]
; load IDTR value
lidt [rdi + 26]
; load its future StackPointer
mov rsp, [rdi + 8]
; update the other CPU's switch state to LOADED
mov byte [rdi], CPU_SWITCH_STATE_LOADED
WaitForOtherLoaded:
; wait until the other CPU finish loading new state,
; otherwise the data in stack may corrupt
cmp byte [rsi], CPU_SWITCH_STATE_LOADED
jz OtherLoaded
pause
jmp WaitForOtherLoaded
OtherLoaded:
; since the other CPU already get the data it want, leave this procedure
popfq
pop rax
mov cr4, rax
pop rax
mov cr0, rax
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rbp
pop rdi
pop rsi
pop rdx
pop rcx
pop rbx
pop rax
ret