chore: refactor to x86_64 (wip)
+ 190
- 85
new file
bin/bootloader.patched
new file
bin/kernel.bin
@@ -80,20 +80,18 @@ expanded_main:
 
 	call try_enable_a20 ; Try to enable A20 line for accessing memory above 1MB
 	
-	call describe_gdt ; Describe the GDT (so we can load it later in real mode)
-	call describe_idt ; Describe the IDT (so we can load it later in real mode)
+	call describe_gdt ; Describe the GDT so we can switch modes safely
+	call describe_idt ; Describe the IDT so protected mode has a valid IDT
 
 	call load_kernel ; Load the kernel from disk into memory
-	enable_protected_mode
-	; enable_real_mode
-
-	jmp KERNEL_FLAT_ADDRESS ; Jump to the kernel's entry point (assuming it's loaded at KERNEL_FLAT_ADDRESS)
+	call enter_long_mode
 
   jmp $
 
 %include "a20-utility-inl.asm"
 %include "idt-utility-inl.asm"
 %include "kernel-load-utility-inl.asm"
+%include "long-mode-utility-inl.asm"
 
 blankLine: db " ", CR, LF,0
 enableProtectedModeMessage: db "Info: Enabling protected mode!", CR, LF, 0

boot/idt-utility-inl.asm
@@ -1,7 +1,7 @@
 bits 16
 
 gdtEntry:
-	.size: dw 40
+	.size: dw 55
 	.pointer: dd 0x7000
 
 ; void describe_gdt()

...
@@ -32,7 +32,7 @@ describe_gdt:
 	    mov [es:di + 5], byte 0x98	; Access (present and executable are set)
 	    mov [es:di + 6], byte 0x00	; Flags (granularity and size) and limit
 	    mov [es:di + 7], byte 0x00	; More base bits
-	
+
 	    add di, 8
 
 	  ; Data segment descriptor (16-bit)

...
@@ -54,7 +54,7 @@ describe_gdt:
 	    mov [es:di + 5], byte 0x9a	; Access (present, executable, and readable are set)
 	    mov [es:di + 6], byte 0xcf	; Flags (granularity and size) and limit
 	    mov [es:di + 7], byte 0x00	; More base bits
-	
+
 	    add di, 8
 
 	  ; Data segment descriptor (32-bit)

...
@@ -66,8 +66,27 @@ describe_gdt:
 	    mov [es:di + 6], byte 0xcf	; Flags (granularity and size) and limit
 	    mov [es:di + 7], byte 0x00	; More base bits
 
-		add di, 8
+	    add di, 8
+
+	  ; Code segment descriptor (64-bit)
+		gdtCode64Offset: equ 40
+	    mov [es:di],     word 0x0000
+	    mov [es:di + 2], word 0x0000
+	    mov [es:di + 4], byte 0x00	; Base bits
+	    mov [es:di + 5], byte 0x9a	; Access (present and executable are set)
+	    mov [es:di + 6], byte 0xa0	; Flags: granularity=1, long mode
+	    mov [es:di + 7], byte 0x00	; Base bits
+
+	    add di, 8
 
+	  ; Data segment descriptor (64-bit)
+		gdtData64Offset: equ 48
+	    mov [es:di],     word 0x0000
+	    mov [es:di + 2], word 0x0000
+	    mov [es:di + 4], byte 0x00	; Base bits
+	    mov [es:di + 5], byte 0x92	; Access (present and writable are set)
+	    mov [es:di + 6], byte 0x80	; Flags: granularity=1
+	    mov [es:di + 7], byte 0x00	; Base bits
 	.cleanup:
 		pop es
 		ret

boot/kernel-load-utility-inl.asm
@@ -8,56 +8,33 @@ load_kernel:
 		mov si, loadingKernelMessage
 		call print_string
 
-		; Temporary buffer will be 32 sectors at 0x2000
-		tempBufferSegment: equ 0x0200
 		maxSectorsPerRead: equ 32
-
 		sectorsAlreadyRead: dw 0
 		numberOfSectorsToReadNext: dw 0
 
 	.calculate_number_of_sectors:
-		mov dx, word [kernelNumberOfSectors]
-		sub dx, word [sectorsAlreadyRead]
-		cmp dx, maxSectorsPerRead
+		mov ax, word [kernelNumberOfSectors]
+		sub ax, word [sectorsAlreadyRead]
+		cmp ax, maxSectorsPerRead
 		jle .do_read
-		mov dx, maxSectorsPerRead
+		mov ax, maxSectorsPerRead
 
 	.do_read:
-		mov [numberOfSectorsToReadNext], dx
+		mov [numberOfSectorsToReadNext], ax
+
+		; Compute destination offset relative to 1MB using a real-mode segment we can actually encode.
+		movzx eax, word [sectorsAlreadyRead]
+		shl eax, 9
+		mov bx, 0x1000
+		add bx, ax
+		mov ax, 0xf000
+		mov es, ax
 
-		; Sets es:bx to be destination
-		mov dx, tempBufferSegment
-		mov es, dx
-		xor bx, bx
-		
 		mov cx, word [kernelStartSector]
 		add cx, word [sectorsAlreadyRead]
 		mov ax, word [numberOfSectorsToReadNext]
 		call read_disk
 
-	.copy_to_real_location:
-		enable_protected_mode
-
-		; Source location
-		mov esi, tempBufferSegment * 0x10
-
-		; Destination location
-		movzx eax, word [sectorsAlreadyRead]
-		mov ecx, 512
-		mul ecx
-		mov edi, KERNEL_FLAT_ADDRESS
-		add edi, eax
-
-		; We are moving ((512 / 4) * number of sectors) DWORDs
-		mov eax, 128
-		movzx ecx, word [numberOfSectorsToReadNext]
-		mul ecx
-		mov ecx, eax
-
-		rep movsd
-		enable_real_mode
-
-	.read_again_or_finish:
 		mov ax, word [sectorsAlreadyRead]
 		add ax, word [numberOfSectorsToReadNext]
 		mov word [sectorsAlreadyRead], ax

...
@@ -71,4 +48,4 @@ load_kernel:
 		ret
 
 loadingKernelMessage: db "Info: Loading kernel...", CR, LF, 0
-loadedKernelMessage: db "Info: Loaded kernel!", CR, LF, 0
+loadedKernelMessage: db "Info: Loaded kernel!", CR, LF, 0

new file
boot/long-mode-utility-inl.asm
@@ -0,0 +1,73 @@
+bits 32
+
+PML4_ADDR: equ 0x7200
+PDPT_ADDR: equ 0x7300
+PD_ADDR: equ 0x7400
+
+; void setup_page_tables()
+setup_page_tables:
+    ; Zero PML4 table (4096 bytes)
+    mov edi, PML4_ADDR
+    xor eax, eax
+    mov ecx, 1024
+    rep stosd
+
+    ; Zero PDPT table (4096 bytes)
+    mov edi, PDPT_ADDR
+    xor eax, eax
+    mov ecx, 1024
+    rep stosd
+
+    ; Zero PD table (4096 bytes)
+    mov edi, PD_ADDR
+    xor eax, eax
+    mov ecx, 1024
+    rep stosd
+
+    ; PML4[0] -> PDPT
+    mov eax, PDPT_ADDR
+    or eax, 0x03
+    mov [PML4_ADDR], eax
+    mov dword [PML4_ADDR + 4], 0
+
+    ; PDPT[0] -> PD
+    mov eax, PD_ADDR
+    or eax, 0x03
+    mov [PDPT_ADDR], eax
+    mov dword [PDPT_ADDR + 4], 0
+
+    ; PD[0] -> 2MiB identity mapped page
+    mov dword [PD_ADDR], 0x00000083
+    mov dword [PD_ADDR + 4], 0
+
+    ret
+
+; Enters long mode and jumps to the 64-bit kernel entry point.
+enter_long_mode:
+    ; enable protected mode first
+    enable_protected_mode
+
+    call setup_page_tables
+
+    ; Enable PAE
+    mov eax, cr4
+    or eax, 0x20
+    mov cr4, eax
+
+    ; Enable long mode in EFER
+    mov ecx, 0xC0000080
+    rdmsr
+    or eax, 0x100
+    wrmsr
+
+    ; Load page tables
+    mov eax, PML4_ADDR
+    mov cr3, eax
+
+    ; Enable paging
+    mov eax, cr0
+    or eax, 0x80000000
+    mov cr0, eax
+
+    ; Jump into the 64-bit kernel entry at 1MB
+    jmp 0x28:0x00100000

@@ -72,7 +72,7 @@ read_disk:
 
 ; (ch cylinder, cl sector, dh head) calculate_chs(cx LBA-sector)
 calculate_chs:
-	sectorsPerTrack: equ 36
+	sectorsPerTrack: equ 18
 	headsPerCylinder: equ 2
 
 	.calculate_sector:

new file
build/bootloader.bin
new file
build/kernel.elf
new file
build/kernel.o
new file
build/kernelEntryPoint.o
@@ -4,7 +4,7 @@
 
 kernelCompileFlags="-ffreestanding -nostdinc -nostdinc++ \
 					-Wall -Wextra \
-					-o ./bin/kernel.bin -target i386-pc-none-elf \
+					-o ./bin/kernel.bin -target x86_64-unknown-linux-elf \
 					-I ./kernel/"
 kernelLinkFlags="-nostdlib -Wl,--oformat=binary,-T./kernel/linker.ld"
 kernelFiles="./kernel/unityBuild.cpp ./build/kernelEntryPoint.o"

...
@@ -18,24 +18,34 @@ rm *.bin *.img *.iso *.o *.vmdk 2> /dev/null
 
 echo -e "\nBuilding..."
 nasm ./boot/bootloader.asm -I ./boot/ -o ./bin/bootloader.bin || exit 1
-nasm -felf32 ./kernel/entry.asm -o ./build/kernelEntryPoint.o || exit 1
+nasm -f elf64 ./kernel/entry.asm -o ./build/kernelEntryPoint.o || exit 1
 
 use_crystal_kernel=true
 
 if [ "$use_crystal_kernel" = true ]
 then
-	CC=clang LD=ld crystal build --no-debug -Dkernel --cross-compile --target i386-unknown-linux-elf --prelude=empty --link-flags="-nostdlib -T./kernel/linker.ld" -o ./build/kernel ./kernel/kernel.cr
-	# The object file should be at ./build/kernel.o, now link it with the entry point
-	ld -nostdlib -T ./kernel/linker.ld -o ./bin/kernel.bin ./build/kernelEntryPoint.o ./build/kernel.o || exit 1
+	crystal build --no-debug -Dkernel --cross-compile --target x86_64-unknown-linux-elf --prelude=empty -o ./build/kernel ./kernel/kernel.cr
+	ld -m elf_x86_64 -nostdlib -T ./kernel/linker.ld -o ./build/kernel.elf ./build/kernelEntryPoint.o ./build/kernel.o || exit 1
+	objcopy -O binary ./build/kernel.elf ./bin/kernel.bin || exit 1
+
+	bootloader_size=$(stat -c%s ./bin/bootloader.bin)
+	bootloader_extra_sectors=$(( (bootloader_size + 511) / 512 - 1 ))
+	kernel_size=$(stat -c%s ./bin/kernel.bin)
+	kernel_number_of_sectors=$(( (kernel_size + 511) / 512 ))
+	kernel_start_sector=$((1 + bootloader_extra_sectors))
+
+	printf '%b' "\\x$(printf '%02x' "$((bootloader_extra_sectors & 0xff))")\\x00" | dd of=./bin/bootloader.bin bs=1 seek=504 conv=notrunc status=none
+	printf '%b' "\\x$(printf '%02x' "$((kernel_start_sector & 0xff))")\\x00" | dd of=./bin/bootloader.bin bs=1 seek=506 conv=notrunc status=none
+	printf '%b' "\\x$(printf '%02x' "$((kernel_number_of_sectors & 0xff))")\\x00" | dd of=./bin/bootloader.bin bs=1 seek=508 conv=notrunc status=none
+	echo "Patched bootloader sector info: extra=$bootloader_extra_sectors start=$kernel_start_sector sectors=$kernel_number_of_sectors"
 else
 	clang++ $kernelCompileFlags $kernelLinkFlags $kernelFiles || exit 1
 fi
 
-# touch ./bin/kernel.bin
 ./tools/genVDisk --output "crystalos.img" --floppy \
-				  --bootloader ./bin/bootloader.bin --kernel ./bin/kernel.bin
+			  --bootloader ./bin/bootloader.bin --kernel ./bin/kernel.bin
 
 echo -e "\nRunning..."
-qemu-system-i386 -drive if=floppy,index=0,format=raw,file=crystalos.img || exit 1
+qemu-system-x86_64 -drive if=floppy,index=0,format=raw,file=crystalos.img || exit 1
 
-exit 0
+exit 0

@@ -1,13 +1,15 @@
-bits 32
+bits 64
 
-extern kmain
+default rel
 
-section .entry
+extern kmain
 
-; void start()
 global _start
+section .text
+
 _start:
-	mov esp, kernelStackStart
+	xor rbp, rbp
+	lea rsp, [rel kernelStackEnd]
 	call kmain
 
 	.hang:

...
@@ -15,6 +17,7 @@ _start:
 		hlt
 		jmp .hang
 
+;section .bss
 align 16
 kernelStackEnd:
 	times 16384 db 0

@@ -22,22 +22,48 @@ fun kearly(info_ptr: LibBootstrap::StartInfo*)
 end
 
 fun kmain
-  # IDT.enable_interrupts
-  # kprint "Hello from Fluorite."
+  screen = Pointer(UInt16).new(0xb8000_u64)
 
-  address = 0xb800 # Pointer(UInt8).reinterpret(0xb8000)
-
-  string = "[CrystalOS Kernel] Hello from Crystal"
-  string_size = 33 # string.size
-
-  # Loop over each character in the string
-  # [0...string_size];each do |i|
-  #   # Write the character
-  #   address.value = string[i].to_u8
-  #   address += 1
-  #   # Write the attribute byte
-  #   address.value = 0xA0.to_u8
-  #   address += 1
-  # end
+  screen.value = 0x1f43_u16  # C
+  screen += 1
+  screen.value = 0x1f72_u16  # r
+  screen += 1
+  screen.value = 0x1f79_u16  # y
+  screen += 1
+  screen.value = 0x1f73_u16  # s
+  screen += 1
+  screen.value = 0x1f74_u16  # t
+  screen += 1
+  screen.value = 0x1f61_u16  # a
+  screen += 1
+  screen.value = 0x1f6c_u16  # l
+  screen += 1
+  screen.value = 0x1f4f_u16  # O
+  screen += 1
+  screen.value = 0x1f53_u16  # S
+  screen += 1
+  screen.value = 0x1f20_u16  # space
+  screen += 1
+  screen.value = 0x1f6b_u16  # k
+  screen += 1
+  screen.value = 0x1f65_u16  # e
+  screen += 1
+  screen.value = 0x1f72_u16  # r
+  screen += 1
+  screen.value = 0x1f6e_u16  # n
+  screen += 1
+  screen.value = 0x1f65_u16  # e
+  screen += 1
+  screen.value = 0x1f6c_u16  # l
+  screen += 1
+  screen.value = 0x1f20_u16  # space
+  screen += 1
+  screen.value = 0x1f72_u16  # r
+  screen += 1
+  screen.value = 0x1f65_u16  # e
+  screen += 1
+  screen.value = 0x1f61_u16  # a
+  screen += 1
+  screen.value = 0x1f64_u16  # d
 end
 

@@ -1,34 +1,33 @@
-OUTPUT_FORMAT(elf32-i386)
+OUTPUT_FORMAT("elf64-x86-64")
 ENTRY(_start)
 SECTIONS
 {
 	/* The kernel begins at the 1MB physical mark. */
 	. = 0x00100000;
 
-	.text ALIGN(4k) : AT(ADDR(.text))
+	.text ALIGN(4k) :
 	{
 		*(.entry)
 		*(.text .text.*)
 	}
 
-	.rodata ALIGN(4k) : AT(ADDR(.rodata))
+	.rodata ALIGN(4k) :
 	{
 		*(.rodata .rodata.*)
 	}
 
-	.data ALIGN(4k) : AT(ADDR(.data))
+	.data ALIGN(4k) :
 	{
 		*(.data .data.*)
 	}
 
-	/* Not actually *in* the image. */
-	.bss ALIGN(4k) : AT(ADDR(.bss))
+	.bss ALIGN(4k) :
 	{
 		*(COMMON)
 		*(.bss .bss.*)
 	}
 
-  .modules ALIGN(4k) : AT(ADDR(.modules))
+  .modules ALIGN(4k) :
   {
     PROVIDE(__modules_start = .);
     *(.modules*)