Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > alt.os.development > #18739 > unrolled thread

Re: [OSDev] How to switch to long mode in x86 CPUs?

Started bycross@spitfire.i.gajendra.net (Dan Cross)
First post2025-03-01 13:34 +0000
Last post2025-03-01 14:42 +0000
Articles 2 — 2 participants

Back to article view | Back to alt.os.development

This discussion starts older than the indexed window; earlier articles aren't shown. The article labeled Started by below is the oldest one visible, not the original post.


Contents

  Re: [OSDev] How to switch to long mode in x86 CPUs? cross@spitfire.i.gajendra.net (Dan Cross) - 2025-03-01 13:34 +0000
    Re: [OSDev] How to switch to long mode in x86 CPUs? scott@slp53.sl.home (Scott Lurndal) - 2025-03-01 14:42 +0000

#18739 — Re: [OSDev] How to switch to long mode in x86 CPUs?

Fromcross@spitfire.i.gajendra.net (Dan Cross)
Date2025-03-01 13:34 +0000
SubjectRe: [OSDev] How to switch to long mode in x86 CPUs?
Message-ID<vpv2di$5t5$1@reader1.panix.com>
[Note: Followup-To: alt.os.development]

In article <875xkughgw.fsf@onesoftnet.eu.org>,
Ar Rakin  <rakinar2@onesoftnet.eu.org> wrote:
>On 28 Feb 2025 09:59, David Brown <david.brown@hesbynett.no> wrote:
>[snip]
>> On the other hand, if you are interested in learning the intricacies
>> of the x86 world, you need to look elsewhere for information - as
>> Keith says, it is not really C related when you are writing
>> assembly. comp.arch might be a helpful group.
>
>Looks like comp.arch might be the group I needed to find! Thanks.

Comp.arch is not a good choice here.  It's for discussion of
computer architecutre generally, specifics of how to boot an x86
core would be mostly tangential.

alt.os.development is probably your best bet, but please try not
to feed the troll there.

>[snip]
>Also just to clarify, from what I know, since the x86 processors start
>in 16-bit real mode for the sake of compatibility, you'd have to start
>writing 16-bit code first, then switch to 32-bit protected mode, then
>finally switch to 64-bit long mode.  However if you use an existing
>bootloader such as GRUB, then you usually would not need to worry about
>16-bit code since GRUB drops you in 32-bit protected mode.  But still,
>if you wish to switch to 64-bit long mode, you *will* have to mix 64-bit
>and 32-bit code together.
>
>Either I don't know how to, or I just don't feel comfortable mixing
>32-bit and 64-bit code together *when in C* because you will have a hard
>time linking those together.  Please correct me if I am wrong.

Eh...It can be done, but I don't see the point.  Even starting
from the reset vector (e.g., in 16-bit 8086 mode) it's not that
many instructions to get yourself into long mode with paging
enabled.  To see an example of how this is done, have a look at:
https://github.com/oxidecomputer/phbl/blob/main/src/start.S

It's sufficient straight-forward, and so low-level, that doing
this in a high-level language doesn't seem worth it from a
cost/benefit perspective.

	- Dan C.

[toc] | [next] | [standalone]


#18740

Fromscott@slp53.sl.home (Scott Lurndal)
Date2025-03-01 14:42 +0000
Message-ID<w1FwP.46245$SZca.33446@fx13.iad>
In reply to#18739
scott@slp53.sl.home (Scott Lurndal) writes:
>cross@spitfire.i.gajendra.net (Dan Cross) writes:
>>[Note: Followup-To: alt.os.development]
>>
>>In article <875xkughgw.fsf@onesoftnet.eu.org>,
>>Ar Rakin  <rakinar2@onesoftnet.eu.org> wrote:
>>>On 28 Feb 2025 09:59, David Brown <david.brown@hesbynett.no> wrote:
>>>[snip]
>>>> On the other hand, if you are interested in learning the intricacies
>>>> of the x86 world, you need to look elsewhere for information - as
>>>> Keith says, it is not really C related when you are writing
>>>> assembly. comp.arch might be a helpful group.
>>>
>>>Looks like comp.arch might be the group I needed to find! Thanks.
>>
>>Comp.arch is not a good choice here.  It's for discussion of
>>computer architecutre generally, specifics of how to boot an x86
>>core would be mostly tangential.
>
>Here's some x86 bootstrap code from MBR (pre UEFI) days.

And here's the part that runs in long mode.


#include "dvmm_constants.h"
#include "core/asm_offsets.h"

/**
 * \fn void ::dvmmstart(uint32 e820space, uint32 bspflag, uint32 rmseg)
 * \brief  The 64-bit code entry point
 * \param  e820space  The number of bytes of e820 space remaining. Passed
 *         in the <b>%eax</b> register.
 * \param  bspflag    Set to zero to indicate that the Bootstrap Processor
 *         (BSP) or one to indicate that an Application Processor (AP) has
 *         invoked this function.  Passed in the <b>%ebx</b> register.
 * \param  rmseg      The real-mode segment selector used by the second-stage
 *         bootstrap program.  Passed in the <b>%esi</b> register.
 * \remark  This function never returns.
 *
 *    This code is the long mode 64-bit code entry point.  It is
 *    loaded by the PXE boot loader (or 3leaf floppy boot loader) to
 *    physical address 0x100000, virtual address 0xffff830000100000.
 *    It is responsible for setting up the 64 bit GDT and IDT and then
 *    invoking the C++ code.  It executes based at 0x100000 until the
 *    call into ::dvmm_bsp_start, from whence we're running at 0xffff83...
 *
 * \note Note that this code is linked into the main hypervisor ELF image.
 *
 */

SS_CODE=0x10
SS_DATA=0x18

	.code64
	.section inittext,"xa",@progbits
#	.text

# On entry to dvmmstart:
#    %eax contains the amount of e820 buffer space remaining
#    %ebx contains 0 for the bootstrap processor (BSP), and 1 for application
#         processors (AP).
#    %esi contains the real-mode segment selector for the secondary bootstrap
#
	.global dvmmstart
dvmmstart:
	#
	# Get processor into known state.
	#

    cld
    cli

    movl %esi, %r15d		# Save real-mode segment base
    xorq %rsi, %rsi    	        # EAX contains 'memsize' initialized
    movl %eax, %esi             # before calling setup64.s
    movabsq $PHYSMAP_BASE, %r12	# Base DVMM virtual address

#
# 64bit RSP points to the stack's linear address.
#
    leaq init_stack_top(%rip), %rsp
    addq %r12, %rsp             # Rebase the stack

#
# Fill in the GDTR.
#
    leaq gdt64(%rip), %rax      # Load the address of the gdt
    leaq gdtdesc64(%rip), %rcx  # Load the address of the gdt descriptor
    addq $2, %rcx               # &gdt goes 2 bytes into the gdt descriptor
    movq %r12, (%rcx)		# Start with PHYSMAP_BASE
    addq %rax, (%rcx)           # offset to gdt
    lgdtq gdtdesc64(%rip)       # Load the GDT

#
# Load up the new selectors.
#

    movl $SS_DATA, %eax
    movl %eax, %ds
    movl %eax, %ss
    movl %eax, %es
    movl %eax, %fs
    movl %eax, %gs

#
# Fill in the IDTR.
#
    leaq idt64(%rip), %rax      # Load the address of the idt
    leaq idtdesc64(%rip), %rcx  # Load the address of the idt descriptor
    addq $2, %rcx               # &idt goes 2 bytes into the gdt descriptor
    movq %r12, (%rcx)		# Start with PHYSMAP_BASE
    addq %rax, (%rcx)           # add offset to &idt

#
# Fill in the IDT.
#
    testl $1, %ebx		# BSP or AP?
    jnz   2f			# AP, just load IDT

    movabsq $handlerlist, %r11	# List of interrupt handlers
    movl $((idt64_end - idt64) / 16), %ecx    # Number of entries to copy
1:
    movq (%r11), %rdx		# Get handler address
    addq $8, %r11		# Skip to next
    movw %dx, (%rax)            # Set the low 16 bits of the target offset
    movw $SS_CODE, 2(%rax)      # Set the target CS
    movw $0x8e00, 4(%rax)       # Set the gate flags
    shrq $16, %rdx		# Shift temp_int address down by 16
    movw %dx, 6(%rax)           # Set bits 31-16 of the target offset
    shrq $16, %rdx              # Shift temp_int address down by 16 more
    movl %edx, 8(%rax)          # Set bits 63-32 of the target offset

    addq $16, %rax              # Next IDT64 entry
    decw %cx                    # Down by one
    testw $0xffff, %cx          # Is it zero yet?
    jnz  1b                     # No, loop around

2:
    lidt idtdesc64(%rip)        # Load the IDT

#
# Clear BSS
#
    testl $1, %ebx		# BSP or AP?
    jnz   1f			# AP, skip clear BSS & constructors

    xorq %rax, %rax
    leaq _sbss(%rip), %rdi
    movq $0, %rcx
    leaq _ebss(%rip), %rcx
    subq %rdi, %rcx
    rep stosb

    pushq %rsi                  # Save RSI, since we'll need it later.
#
# Invoke the debugger early initialization function
#
    movabsq $debugger, %rdi		    # Set this
    movabsq $_ZN10c_debugger10early_initEv, %rcx    # Call ::early_init
    call *%rcx

#
# Invoke global C++ constructors.
#
    movabsq $__call_constructors, %rcx
    call *%rcx

    popq %rsi                   # Restore RSI.
#
# Invoke C++ code.   Pass begin and end address of memory map.
#
1:
    movq $512, %rax		# Starting with 512 bytes
    subq %rsi, %rax		# Subtract remaining
    addq $0x90000, %rax		# e820 data map address

# Use x86_64 calling conventions
    movq $0x90000, %rdi         # Pass arg1 to main
    addq %r12, %rdi             # Rebase arg1

    movq %rax, %rsi		# Pass arg2 to main
    addq %r12, %rsi             # Rebase arg2
    movq %r15, %rdx		# Arg3 is real-mode segment base
    xorq %rbp, %rbp		# Indicate end of stack for -fframe-pointer

    movabsq $dvmm_bsp_start, %rcx    # We use an indirect jump to invoke main
    testl $1, %ebx		# BSP or AP?
    jz    4f			# BSP, use 'dvmm_bsp_start'

    movabsq $dvmm_ap_start, %rcx # AP, use 'dvmm_ap_start'
4:  call *%rcx			# because it is more than 2GB away from here.

#
# Should never return.
#
1:
    hlt
    jmp 1b

#
# Global Descriptor Table
#
    .align 16,0
gdt64:
    .long    0, 0			# Entry 0 (NULL segment descriptor)
    .long    0, 0			# Entry 1 (reserved)
    .long    0x0000ffff, 0x00AF9A00     # Code read/exec long mode
    .long    0x0000ffff, 0x00CF9200     # Data read/write.
gdt64_end:

gdtdesc64:				# 64 bit GDT Descriptor
    .word    gdt64_end-gdt64-1		# limit
    .quad    PHYSMAP_BASE		# 64 bit GDT base (overwritten above)

#
# Interrupt Descriptor Table
#

/* These are overwritten above in dvmmstart. */
#define IGATE .quad 0x0, 0x0

    .align 16,0
idt64:
    IGATE  #DE
    IGATE  #DB
    IGATE  #NMI
    IGATE  #BP
    IGATE  #OF
    IGATE  #BR
    IGATE  #UD
    IGATE  #NM
    IGATE  #DF
    IGATE  #Reserved
    IGATE  #TS
    IGATE  #NP
    IGATE  #SS
    IGATE  #GP
    IGATE  #PF
    IGATE  #MF
    IGATE  #AC
    IGATE  #MC
    IGATE  #XF
idt64_end:

idtdesc64:
    .word   idt64_end-idt64-1		# limit
    .quad   PHYSMAP_BASE	# 64 bit IDT base (overwritten above)

handlerlist:
    .quad   fault_divide
    .quad   trap_debug
    .quad   fault_nmi
    .quad   trap_breakpoint
    .quad   trap_overflow
    .quad   fault_bounds
    .quad   fault_ud
    .quad   fault_nm
    .quad   abort_df
    .quad   fault_invalid9
    .quad   fault_invtss
    .quad   fault_notpresent
    .quad   fault_stack
    .quad   fault_gp
    .quad   fault_page
    .quad   fault_invalid15
    .quad   fault_fpe
    .quad   fault_alignment
    .quad   machine_check
    .quad   fault_simd
    .quad   fault_invalid20
    .quad   fault_invalid21
    .quad   fault_invalid22
    .quad   fault_invalid23
    .quad   fault_invalid24
    .quad   fault_invalid25
    .quad   fault_invalid26
    .quad   fault_invalid27
    .quad   fault_invalid28
    .quad   fault_invalid29
    .quad   fault_security
    .quad   fault_invalid31

    .align   4096,0
/*
 * Application processor initialization code.  This needs to be aligned on a
 * page boundary, as we use the LAPIC startup interprocessor interrupt (IPI)
 * to start the application processors (AP) from the bootstrap processor (BSP),
 * and the startup API requires a page-aligned address within the first
 * megabyte of DRAM as a start-vector.
 */
    .global  dvmm_ap_init
    .global  dvmm_ap_base
    .code16
dvmm_ap_init:
    jmp    1f
dvmm_ap_base:
    .word   0x0000
1:  wbinvd
    mov    %cs, %ax
    mov    %ax, %ds
    cli

    mov    %cs:(0x2), %ax
    mov    %ax, %es
    sub	   $0x20, %ax
    /*
     * We only need a few bytes (4, to be exact) on the stack used prior
     * to invoking dvmmstart in long-mode.   Use the bottom portion of the
     * segment starting at $3000:$0, the first portion of the 512 bytes at
     * that segment is only used by the bootstrap processor to hold the
     * BIOS E820 table.
     */
    mov    %ax, %ss
    mov    $0x1f0, %sp

    mov    %es, %ax
    mov    %ax, %cs:(2f-dvmm_ap_init)

    movw   $1, %es:(0x20)	# Mark AP startup
    .byte  0xea			# Long Jump
    .word  0x0			# Offset from seg base == 0
2:  .word  0x0			# Real-mode segment base

    .global  dvmm_ap_init_end
dvmm_ap_init_end:
    .long   0

    .global  init_stack
    .global  init_stack_top
    .align   STACK_SIZE_ASM,0
init_stack:
    .space   STACK_SIZE_ASM, 0
init_stack_top:

/* vim: sw=4 sts=4 sta ts=8:
 */

[toc] | [prev] | [standalone]


Back to top | Article view | alt.os.development


csiph-web