; Copyright (C) 2025 Elaina Claus
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program. If not, see .
[BITS 16]
[ORG 0x0500] ; IF YOU CHANGE ORG CHANGE THE SIGN OFFSET AT THE END
[CPU 686]
[map all build/stage2.map]
[WARNING -reloc-abs-byte]
[WARNING -reloc-abs-word]
[WARNING -reloc-abs-dword] ; Yes, we use absolute addresses. surpress these warnings.
%define __STEVIA_STAGE2
%define __STAGE2_SEGMENT 0x0000
; ###############
; Headers/Includes/Definitions
; ###############
%include "util/bochs_magic.inc"
%include "cdecl16.inc"
%include "entry.inc"
%include "config.inc"
%include "early_mem.inc"
%include "error_codes.inc"
section .text
begin_text:
; dl = byte boot_drive
; ax = word part_offset (active partition offset)
; si = ptr PartTable_t partition_table
; di = ptr FAT32_bpb_t fat32_bpb
ALIGN 4, db 0x90
init:
cli ; We do not want to be interrupted
; these 4 are stored in the .data section and are effectivly const types
mov [vbr_part_table_ptr], si ; pointer to partition_table
mov [vbr_fat32_bpb_ptr], di ; pointer to fat32_bpb
mov [boot_drive], dl ; copy boot_drive to globals
mov [partition_offset], ax ; copy partition_offset to globals
mov ax, __STAGE2_SEGMENT ; set all our segments to the configured segment, except es
mov ds, ax ; *
mov fs, ax ; *
mov gs, ax ; *
mov ss, ax
; Zero BSS section
mov cx, (end_bss - begin_bss) ; count = bss length
mov ax, begin_bss
shr ax, 4
mov es, ax ; es = begining of bss section, remember to restore ES later
xor ax, ax ; val = 0
mov di, ax ; dst = 0 (start of segment)
cld
rep stosb
mov ax, __STAGE2_SEGMENT
mov es, ax
; done zeroing bss section
mov sp, stack_top
mov bp, sp
sub sp, 0x10
push bp ; setup a somewhat normal stack frame, minus a ret ptr
sti
jmp word __STAGE2_SEGMENT:main
; ###############
; Core Functions
; ###############
%include "util/kmem_func.nasm"
%include "util/kmemcpy5_func.nasm"
%include "util/kmemset4_func.nasm"
%include "util/error_func.nasm"
; ###############
; Functions
; ###############
%include "util/arena_alloc.nasm"
; ###############
; FAT32 Driver
; ###############
%include 'fat32/FAT32_SYS.inc'
; ###############
; BIOS functions
; ###############
%include 'BIOS/BIOS_SYS.inc'
; structures
struc SteviaInfoStruct_t
.MemoryMapPtr resd 1
.MemoryMapEntries resd 1
endstruc
struc EarlyBootStruct_t
.partition_table resb PartTable_t_size
.fat32_bpb resb FAT32_bpb_t_size
.fat32_ebpb resb FAT32_ebpb_t_size
endstruc
ALIGN 4, db 0x90
main:
__BOCHS_MAGIC_DEBUG
.check_sig:
mov eax, dword [STAGE2_SIG]
cmp eax, 0xDEADBEEF
je main.stage2_main
ERROR STAGE2_SIGNATURE_MISSING
.stage2_main:
; copy partition table data to .data section in stage2
__CDECL16_CALL_ARGS partition_table, word [vbr_part_table_ptr], PartTable_t_size
__CDECL16_CALL kmemcpy, 3
; copy bpb & ebpb to memory
__CDECL16_CALL_ARGS fat32_bpb, word [vbr_fat32_bpb_ptr], (FAT32_bpb_t_size + FAT32_ebpb_t_size)
__CDECL16_CALL kmemcpy, 3
call SetTextMode
call disable_cursor_bios
__CDECL16_CALL_ARGS HelloPrompt_info
__CDECL16_CALL PrintString, 1
; setup the early heap
__CDECL16_CALL_ARGS early_heap_state
; enable A20 gate
call EnableA20
__CDECL16_CALL_ARGS A20_Enabled_OK_info
__CDECL16_CALL PrintString, 1
; get system memory map
call GetMemoryMap
__CDECL16_CALL_ARGS MemoryMap_OK_info
__CDECL16_CALL PrintString, 1
; enter unreal mode (enter PM w/ 16 bit code, 32 bit flat memory model & return to real)
; ds, es will be set to the 64KiB STAGE2_SEGMENT, fs/gs will be flat/huge memory (4GiB)
; use __REFLAT macros to re-flat ds/es for easy transfers to >1MiB
; NOTE: if you modify a segment register you will need to re-unreal it
call EnterUnrealMode
__CDECL16_CALL_ARGS UnrealMode_OK_info
__CDECL16_CALL PrintString, 1
; FAT Driver setup
call InitFATDriver
__CDECL16_CALL_ARGS InitFATSYS_OK_info
__CDECL16_CALL PrintString, 1
;
; Find first cluster of bootable file
call SearchFATDIR
push dword eax ; save first cluster of bootable file
__CDECL16_CALL_ARGS FileFound_OK_info
__CDECL16_CALL PrintString, 1
pop dword eax
push dword eax ; print Cluster of boot file
call PrintDWORD ; void PrintDWORD(uint32_t dword)
add sp, 0x4
; TODO: using first cluster information, start loading the kernel to memory
; TODO: going to need an elf parser, some unreal mode file buffer functions to move the data
hcf:
ERROR STEVIA_DEBUG_OK
; ##############################
;
; SYSTEM CONFIGURATION FUNCTIONS & MACROS
;
; ##############################
; set ds and es segments back to the base of the loader
%ifnmacro __TINY_DS_ES
%macro __TINY_DS_ES 0
mov ax, __STAGE2_SEGMENT
mov ds, ax
mov es, ax
%endmacro
%endif
; for copying between locations in high memory
%ifnmacro __REFLAT_DS_ES
%macro __REFLAT_DS_ES 0
cli ; no interrupts
lgdt [((__STAGE2_SEGMENT << 4) + unreal_gdt_info)] ; load unreal gdt
mov eax, cr0
or eax, 1 ; set pmode bit
mov cr0, eax ; switch to pmode
jmp short $+2 ; i-cache flush
mov ax, 0x10 ; select descriptor 2
mov ds, ax
mov es, ax
mov eax, cr0
and eax, ~1 ; toggle bit 1 of cr0
mov cr0, eax ; back to realmode
jmp short $+2 ; i-cache flush
sti
%endmacro
%endif
; for copying from low memory to high memory (ds on a real segment, es in flat mode)
%ifnmacro __REFLAT_ES
%macro __REFLAT_ES 0
cli ; no interrupts
lgdt [((__STAGE2_SEGMENT << 4) + unreal_gdt_info)] ; load unreal gdt
mov eax, cr0
or eax, 1 ; set pmode bit
mov cr0, eax ; switch to pmode
jmp short $+2 ; i-cache flush
mov ax, 0x10 ; select descriptor 2
mov es, ax
mov eax, cr0
and eax, ~1 ; toggle bit 1 of cr0
mov cr0, eax ; back to realmode
jmp short $+2 ; i-cache flush
sti
%endmacro
%endif
ALIGN 4, db 0x90
EnterUnrealMode:
__CDECL16_PROC_ENTRY
cli ; no interrupts
.func:
lgdt [((__STAGE2_SEGMENT << 4) + unreal_gdt_info)] ; load unreal gdt
mov eax, cr0
or eax, 1 ; set pmode bit
mov cr0, eax ; switch to pmode
; set cs to a pm code segment (0x8) w/ the following
jmp 0x0008:EnterUnrealMode.set_segs
.set_segs:
mov ax, 0x10 ; select descriptor 2
mov ds, ax ; 10h = 0001_0000b
mov es, ax ; es to big data
mov fs, ax
mov gs, ax ; extra segments to big data as well
.pm_start:
; code here is running in protected mode w/ descriptor 0x8
; insert any PM test code needed here
.pm_end:
mov eax, cr0
and eax, ~1 ; toggle bit 1 of cr0
mov cr0, eax ; back to realmode
jmp __STAGE2_SEGMENT:EnterUnrealMode.endp
.endp:
sti ; re-enable interupts
; set ds, es to the STAGE2_SEGMENT, for our model (generally) ds == es == cs
; fs, gs & ss are all still huge data model, and the macro(s) "__REFLAT_xxx" exists
; to easily access data outside of 64KiB boundries using ds/es addressing
__TINY_DS_ES
__CDECL16_PROC_EXIT
ret
end_text:
section .data follows=.text
align 512
begin_data:
; #############
;
; Strings
;
; #############
%define CRLF 0Dh, 0Ah
%macro define_cstr 2
align 16
%1_cstr:
db %2, 00h
%endmacro
%macro define_info 2
align 16
%1_info:
db %2, CRLF, 00h
%endmacro
define_info HelloPrompt, "Hello from Stevia Stage2!"
define_info A20_Enabled_OK, "A20 Enabled OK"
define_info MemoryMap_OK, "Memory map OK"
define_info UnrealMode_OK, "Unreal mode OK"
define_info FileFound_OK, "Found SFN entry for bootable binary, first cluster -> "
define_info InitFATSYS_OK, "FAT32 Driver Init..."
define_info SearchFATDIR, "Searching FAT DIR for bootable file..."
define_info NextFATCluster, "Attempting to find next FAT cluster..."
define_info ReadFATCluster, "Attempting to load next FAT"
define_info MaybeFound_Boot, "Maybe found a file...checking..."
define_cstr BootTarget, "BOOT BIN"
align 16, db 0
BootTarget:
db 'BOOT BIN'
;
; pre-bss init globals (generally const...but there are exceptions)
;
align 8, db 0
boot_drive:
db 0x00
align 8, db 0
partition_offset:
dw 0x0000
align 8, db 0
vbr_fat32_bpb_ptr:
dw 0x0000
align 8, db 0
vbr_part_table_ptr:
dw 0x0000
align 16, db 0
IntToHex_table:
db '0123456789ABCDEF'
; see docs/gdt.txt for a quick refresher on GDT
align 16, db 0
unreal_gdt_info:
unreal_gdt_size: dw (unreal_gdt_end - unreal_gdt_start) - 1
unreal_gdt_ptr: dd ((__STAGE2_SEGMENT << 4) + unreal_gdt_start)
unreal_gdt_start:
; entry 0 (null descriptor)
dq 0 ; first entry is null
; entry 1 (0x8) (16bit code 64KiB limit)
dd 0x0000FFFF ; Base Address(15:0) 31:16, Segment Limit(15:0) 15:0
db 0x00 ; Base Address 23:16
db 1001_1010b ; Access Byte: Present, ring0, S = 1, executable (1), non-conforming, readable, Accessed
db 0000_0000b ; Flags: GR = 4KiB, attr = , Granularity = 4KiB & 16:19 of limit
db 0x00 ; Base Address 31:24
; entry 2 (0x10) (16bit data segment with 4 GiB flat mapping)
dd 0x0000FFFF ; Base Address(15:0) 31:16, Segment Limit(15:0) 15:0
db 0x00 ; Base Address(23:16)
db 1001_0010b ; Access Byte: Present, ring0, S = 1, data (0), non-confirming, writable, present
db 1000_1111b ; Flags: GR = 4KiB, attr = <16-bit/?/?>, Granularity = 4KiB & 16:19 of limit
db 0x00 ; Base Address(31:24)
unreal_gdt_end:
align 16, db 0
gdt32_info:
gdt32_size: dw (gdt32_end - gdt32_start) - 1
gdt32_ptr: dd ((__STAGE2_SEGMENT << 4) + gdt32_start)
gdt32_start:
dq 0
.gdt32_code:
dw 0xFFFF ; code segment (RX)
dw 0x0000
db 0x00
db 1001_1000b ; Access: readable, executable
db 1100_1111b ; 4KB granularity, 32-bit
db 0x00
.gdt32_data: ; data segment (RW)
dw 0xFFFF
dw 0x0000
db 0x00
db 1001_0010b ; Access: readable, writable
db 1100_1111b ; 4KB granularity, 32-bit
db 0x00
.gdt32_stack: ; Stack segment (RW)
dw 0xFFFF
dw 0x0000
db 0x00
db 1001_0010b ; Access: readable, writable
db 1100_1111b ; 4KB granularity, 32-bit
db 0x00
.gdt32_ro_data: ; Read-only data segment (RO)
dw 0xFFFF
dw 0x0000
db 0x00
db 1001_0000b ; Access: readable, not writable
db 1100_1111b ; 4KB granularity, 32-bit
db 0x00
gdt32_end:
align 16,db 0
BUILD_NASM_VER:
db "Stevia Stage2 built with NASM - ", __NASM_VER__, 00h
align 16,db 0
BUILD_DATETIME:
db 'Assembled - ', __DATE__, ' ', __TIME__, 00h
align 16,db 0
BUILD_GIT_VER:
db __GIT_VER__, 00h
align 16,db 0
BUILD_GIT_HASH:
db __GIT_HASH__, 00h
end_data:
%assign bytes_remaining ((MAX_STAGE2_BYTES - 4) - (($ - $$) + (end_text - begin_text)))
%warning STAGE2 has bytes_remaining bytes remaining for code/data (MAX: MAX_STAGE2_BYTES)
; section start location needs to be a 'critical expression'
; i.e resolvable at build time, we are setting 0x7E00 as the offset since
section .sign start=((MAX_STAGE2_BYTES - 512) + 0x0500)
times ((512 - 4) - ($ -$$) ) db 0x90 ; nop
STAGE2_SIG: dd 0xDEADBEEF ; Signature to mark the end of the stage2
section .bss follows=.sign
begin_bss:
; structures
align 16, resb 1
partition_table:
resb PartTable_t_size
align 16, resb 1
fat32_bpb:
resb FAT32_bpb_t_size
fat32_ebpb:
resb FAT32_ebpb_t_size
align 16, resb 1
fat32_nc_data:
resb 16
align 16, resb 1
lba_packet:
resb LBAPkt_t_size
align 16, resb 1
fat32_state:
resb FAT32_State_t_size
align 16, resb 1
SteviaInfo:
resd 4
align 16, resb 1
early_heap_state:
resb ArenaStateStruc_t_size
;
; post-bss init globals
;
;
; large continuous allocations
;
align 16, resb 1
disk_buffer:
resb 512
fat_buffer:
resb 512
dir_buffer:
resb 512
fat_fsinfo:
resb 512
; TODO: this will hold 42 entries from the map function
; the e820 function needs to check that it doesn't overflow
; but realisticly 42 entries is enough for dev work
align 16, resb 1
%define BIOSMemoryMap_SIZE 1024
BIOSMemoryMap:
resb BIOSMemoryMap_SIZE
align 16, resb 1
stack_bottom:
resb 1024
stack_top:
end_bss: