; A boot sector that enters 32-bit protected mode.
;
; To convert to a disk image:
;   cd apps/bos
;   nasm 4.asm -f bin -o boot.bin
; To run:
;   qemu-system-i386 boot.bin
; Or:
;   bochs  # bochsrc loads boot.bin

[org 0x7c00]

  mov bp, 0x9000
  mov sp, bp

  mov bx, MSG_REAL_MODE
  call print_string

  call switch_to_pm
  ; never gets here

  jmp $

switch_to_pm:
  cli
  lgdt [gdt_descriptor]
  ; set LSB of CR0
  mov eax, cr0
  or eax, 0x1
  mov cr0, eax
  jmp CODE_SEG:init_pm  ; Far jump to a new segment containing 32-bit code.
                        ; This also forces the CPU to flush its cache of
                        ; prefetched and real-mode decoded instructions.
  ; never gets here

;; === GDT stuff
gdt_start:

gdt_null:  ; the mandatory null descriptor
  dd 0x0  ; ’dd’ means define double word (i.e. 4 bytes)
  dd 0x0

gdt_code: ; the code segment descriptor
  ; base=0x0, limit=0xfffff,
  ; 1st flags: (present)1 (privilege)00 (descriptor type)1 -> 1001b
  ; type flags: (code)1 (conforming)0 (readable)1 (accessed )0 -> 1010b
  ; 2nd flags: (granularity)1 (32-bit default)1 (64-bit seg)0 (AVL)0 -> 1100b
  dw 0xffff     ; Limit (bits 0-15)
  dw 0x0        ; Base (bits 0-15)
  db 0x0        ; Base (bits 16 -23)
  db 10011010b  ; 1st flags, type flags
  db 11001111b  ; 2nd flags, Limit (bits 16-19)
  db 0x0        ; Base (bits 24 -31)

gdt_data: ; the  data segment descriptor
  ; Same as code segment except for the type flags:
  ; type flags: (code)0 (expand down)0 (writable)1 (accessed)0 -> 0010b
  dw 0xffff    ; Limit (bits 0-15)
  dw 0x0       ; Base (bits 0-15)
  db 0x0       ; Base (bits 16 -23)
  db 10010010b ; 1st flags, type flags
  db 11001111b ; 2nd flags, Limit (bits 16-19)
  db 0x0       ; Base (bits 24 -31)

gdt_end:

gdt_descriptor:
  dw gdt_end - gdt_start - 1  ; last valid byte
  dd gdt_start

; some handy constants
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
;; === end GDT stuff

;; some 16-bit helpers

; print string whose address is in bx
print_string:
  pusha
  mov ah, 0x0e
loop:
  mov al, [bx]
  int 0x10
  add bx, 1
  cmp al, 0
  jne loop
  popa
  ret

;; 32-bit code starts here
[bits  32]

; Initialise registers and the stack once in PM.
init_pm:

  mov ax, DATA_SEG        ; Now in PM, our old segments are meaningless,
  mov ds, ax              ; so we point our segment registers to the
  mov ss, ax              ; data selector we defined in our GDT
  mov es, ax
  mov fs, ax
  mov gs, ax

  mov ebp, 0x90000        ; Update our stack position so it is right
  mov esp, ebp            ; at the top of the free space.

  ;; Protected Mode is now initialized

  mov ebx, MSG_PROT_MODE
  call print_string_pm

  ; all done; hang
  jmp $

; Define  some  constants
VIDEO_MEMORY  equ 0xb8000
WHITE_ON_BLACK  equ 0x0f

; prints a null -terminated  string  pointed  to by EDX
print_string_pm:
  pusha
  mov edx, VIDEO_MEMORY  ; Set  edx to the  start  of vid  mem.
print_string_pm_loop:
  mov al, [ebx]          ; Store  the  char at EBX in AL
  mov ah, WHITE_ON_BLACK ; Store  the  attributes  in AH
  cmp al, 0         ; if (al == 0), at end of string , so
  je print_string_pm_done
  mov [edx], ax     ; Store  char  and  attributes  at  current
                    ; character  cell.
  add ebx , 1       ; Increment  EBX to the  next  char in  string.
  add edx , 2       ; Move to next  character  cell in vid  mem.
  jmp  print_string_pm_loop   ; loop  around  to  print  the  next  char.
print_string_pm_done:
  popa
  ret               ; Return  from  the  function

; Global variables
MSG_REAL_MODE   db "Started in 16-bit Real Mode", 0
MSG_PROT_MODE   db "Successfully landed in 32-bit Protected Mode", 0

; Bootsector padding
times 510-($-$$) db 0
dw 0xaa55