Reputation: 61
During the development of a small kernel, I stumbled across a strange problem when booting up application processors using the APIC.
As stated on OSDev and the Intel-Manual, the processor first enters Real-Mode, and my goal is to get it to operate in Protected-Mode. After setting up a small stack and enabling the "A20"-Line and far-jumping to my 32-bit code, i tried to clear eax
using xor eax, eax
for sanity purposes.
To my suprise, only the lower word of eax
got cleared, but the high word remained unchanged.
Funnily, if I simply do an xor ax, ax
, instead of xor eax, eax
, the register is cleared entirely.
Below is my code for bootstrapping an application processor using the APIC:
; Extern reference to the GDTR
section .data
extern g_gdtr
; Serves as a temporary stack used for flushing the cpu-pipeline
SMP_BOOT_STACK_SIZE equ 64
smp_boot_stack:
dq 0
dq 0
dq 0
dq 0
section .text
global __smp_ap_rm_entry
align 0x1000
[bits 16]
; Real-Mode Entry point for other processor cores (AP's)
; after a INIT-SIPI-SIPI was issued
__smp_ap_rm_entry:
cli
xor ax, ax
mov ds, ax
mov ss, ax
lea bp, [ds:smp_boot_stack + SMP_BOOT_STACK_SIZE - 1]
mov sp, bp
; Enable A20 line
sti
in al, 0x92
or al, 2
out 0x92, al
cli
lgdt [ds:g_gdtr]
; Enable Protected Mode
mov eax, cr0
or eax, 0x1
mov cr0, eax
; Far jump
push 0x8
push __smp_ap_pm_entry
retf
align 4
[bits 32]
__smp_ap_pm_entry:
mov ax, word 0x10
; Doing this two times is somehow necessary (wtf?)
mov es, ax
mov es, ax
mov ss, ax
mov fs, ax
mov gs, ax
mov ds, ax
xor eax, eax
int 3 ; To check for changed values in qemu
jmp $
Moreover, I have also tried to assign a 32-bit value into a register, e.g. mov eax, 0xDEADBEEF
, but only the BEEF
part remains.
Does anyone have any idea why this is not working?
Upvotes: 0
Views: 347
Reputation: 140445
; Enable Protected Mode
mov eax, cr0
or eax, 0x1
mov cr0, eax
; Far jump
push 0x8
push __smp_ap_pm_entry
retf
When the architecture manual says a MOV CR0 that alters the PE bit must be immediately followed by a FAR JMP, they mean the specific instruction FAR JMP (opcode EA or FF, depending on how you want to express the operand), and they mean it must be the very next instruction. If you don't do this the effects are unpredictable. I suspect your emulator doesn't actually flip the switch until it executes a FAR JMP, so you're still in 16-bit real mode and the machine instruction 31 c0
still means xor ax,ax
rather than xor eax, eax
.
FAR JMP takes an explicit, absolute segment:offset expression; you can't just write jmp far __smp_ap_pm_entry
. I don't know what exactly you will need to write.
(See section 9.9, "Mode Switching", of the Intel® 64 and IA-32 Architectures Software Developer's Manual Combined Volumes 3A, 3B, 3C, and 3D: System Programming Guide. If you haven't already read this manual, now would be an excellent time.)
Upvotes: 1
Reputation: 61
As @sj95126 hinted, it seemed like I had loaded the wrong GDT. After creating and loading a GDT with 32-bit Segment-Descriptors, the problem was resolved.
If anyone is interested, below is the code for my new GDT used for switching from Real-Mode to Protected-Mode:
struct SegmentDescriptor32_s
{
u16 SegmentLimitLow;
u16 BaseAddressLow;
union
{
struct
{
u32 BaseAddressMiddle : 8;
u32 Type : 4;
u32 DescriptorType : 1;
u32 DescriptorPrivilegeLevel : 2;
u32 Present : 1;
u32 SegmentLimitHigh : 4;
u32 System : 1;
u32 LongMode : 1;
u32 DefaultBig : 1;
u32 Granularity : 1;
u32 BaseAddressHigh : 8;
};
u32 Flags;
};
} __attribute__((packed));
__attribute__((aligned(0x1000)))
static struct SegmentDescriptor32_s smp_ap_gdt[] =
{
{ /* Null-Selector */
.SegmentLimitLow = 0x0,
.BaseAddressLow = 0x0,
.Flags = 0x0
},
{ /* Flat Code */
.SegmentLimitLow = 0xFFFF,
.BaseAddressLow = 0x0000,
.Flags = 0x00CF9A00
},
{ /* Flat Data */
.SegmentLimitLow = 0xFFFF,
.BaseAddressLow = 0x0000,
.Flags = 0x008F9200,
},
{ /* TSS */
.SegmentLimitLow = 0x68,
.BaseAddressLow = 0x0000,
.Flags = 0x00CF8900
}
};
Upvotes: 2