Not sure about the non-zero base. You can load selectors with non-zero base in long mode, but the base will not be applied until you are in compatibility mode.
It's perfectly possible to do a far jump to legacy code with a non-zero base. Segment register loads operates exactly the same in long mode as in protected mode.
I posted my EFI 64->32 bit code previously, but I also have simpler code to switch between protected mode and long mode and the reverse. Switching to long mode is pretty easy:
Code: Select all
; EAX = cr3
switch_to_long_mode Proc far
push eax
push ebx
push ecx
push edx
pushf
;
mov ebx,eax
cli
;
mov eax,cr0
and eax,7FFFFFFFh
mov cr0,eax
;
mov ecx,IA32_EFER
rdmsr
or eax,101h
wrmsr
;
mov cr3,ebx
;
mov eax,cr0
or eax,80000000h
mov cr0,eax
;
lidt fword ptr cs:long_idt_size
;
popf
pop edx
pop ecx
pop ebx
pop eax
ret
switch_to_long_mode Endp
Switching back is easy too:
Code: Select all
; EAX = cr3
switch_to_protected_mode Proc far
push eax
push ebx
push ecx
push edx
pushf
;
mov ebx,eax
cli
;
mov eax,cr0
and eax,7FFFFFFFh
mov cr0,eax
;
mov ecx,IA32_EFER
rdmsr
and eax,0FFFFFEFFh
wrmsr
;
mov cr3,ebx
;
mov eax,cr0
or eax,80000000h
mov cr0,eax
;
lidt fword ptr cs:prot_idt_size
;
popf
pop edx
pop ecx
pop ebx
pop eax
ret
switch_to_protected_mode Endp
However, the switching back code is running in compatibility mode since the scheduler is running in compatibility mode and not in long mode. The switching from long mode to compatibility mode happens as part of a syscall or an IRQ, which contain code to do a far call to the common handler code in compatibility mode.
Also note that the GDT can be shared between protected mode and long mode, but not the IDT. There is a need for a specific IDT for long mode.