; This boot sector starts protect mode and then prints the canonical hello world. ; It is way more difficult, as we cannot invoke BIOS (no interrupts!). %include "gdtnasm.inc" ;logical values %define FALSE 0x00 %define TRUE 0x01 ;real-mode segments %define LOADSEG 0x07C0 %define STACKSEG 0x9000 %define VIDEOSEG 0xB800 ;BIOS functions %define BIOS_10_PUTCHAR 0x0E %define BIOS_16_GETKEY 0x00 ;text styles %define PLAIN 0x07 %define EMPH 0x20 [BITS 16] ; the bios starts out in 16-bit real mode [ORG 0] ; data offset = 0 jmp start ;******************************************* ; Data used in the boot-loading process ;******************************************* ;data bootdrv db 0 ;messages bootmsg db 'ONABSE-PM, from Pietro Braione',13,10 db 'V.1.0 jan 2003',13,10,0 rebootmsg db 'Press any key to reboot',13,10,0 processormsg db 'Checking for 386+ processor: ',0 need386 db 'Sorry... 386+ required!',13,10,0 found386 db 'Found!',13,10,0 alertpm db 'Starting PM',13,10,0 hwpm db 'Hello world from protect land!',0 ; GDT; note that memory addresses must be linear, so we "linearize" them ; by adding LOADSEG<<4 gdt: dummy_gd desc 0,0,0 ; dummy entry code_gd desc (LOADSEG<<4), 0xFFFFF, D_CODE+D_BIG+D_BIG_LIM+D_READ ; code segment (starts at 0) data_gd desc (LOADSEG<<4), 0xFFFFF, D_DATA+D_BIG+D_BIG_LIM+D_WRITE ; data segment (alias of code) stack_gd desc (STACKSEG<<4), 0xFFFFF, D_DATA+D_BIG+D_BIG_LIM+D_WRITE ; stack segment (starts at 0x90000) video_gd desc (VIDEOSEG<<4), 0xFFFFF, D_DATA+D_BIG+D_BIG_LIM+D_WRITE ; video segment (starts at 0xB8000) flat_gd desc 0, 0xFFFFF, D_DATA+D_BIG+D_BIG_LIM+D_WRITE ; flat data segment (starts at 0) gdt_limit equ $-gdt ; size of GDT ; segment selectors code equ code_gd-gdt data equ data_gd-gdt stack equ stack_gd-gdt video equ video_gd-gdt flat equ flat_gd-gdt ; GDT register; ditto about linearization of base GDT address gdt_reg dw gdt_limit dd gdt+(LOADSEG<<4) ;******************************************* ; Procedures for real mode section ; (16 bit default operand size, BIOS available) ;******************************************* message: ; Dump ds:si to screen. ; Modifies: ax, bx, si + int 0x10 func 0x0E side effects. lodsb ; load byte at ds:si into al or al,al ; test if character is 0 (end) jz .done mov ah,BIOS_10_PUTCHAR ; Put character mov bx,PLAIN ; attribute (plain white on black) int 0x10 ; call BIOS jmp message .done: ret ; ******************************************************************** getkey: ; Waits for a keypress. ; Modifies: ah + int 0x16 func 0 side effects. mov ah,BIOS_16_GETKEY int 0x16 ret ; ******************************************************************** detect_cpu: ; Test if 8088/8086 is present, putting the result in al. ; Modifies: ax. pushf ; Save the flags original value mov al,FALSE ; Inits al ; First tries to clear bit 12-15 (always set in 88/86/188/186) xor ah,ah ; Clear ah push ax ; Copy ax on the stack... popf ; ...and then into flags pushf ; Read flags back into ax... pop ax ; ...using the same stack roundtrip and ah,0xF0 ; Check if high nibble is set cmp ah,0xF0 je .no386 ; If not, processor is 88/86/188/186 ; Then tries to set NT, which is bit 14 of flags (only 386, not 286) mov ah,0x40 ; Set bit 14 of ax push ax ; Copy ax onto the flags as before popf pushf ; Copy the flags into ax as before pop ax and ah,0x40 ; Check if bit 14 is clear jz .no386 ; If not, processor is 286 ; 386 detected mov al,TRUE ; CPU is not 386 .no386: popf ; Pop the original flags back ret ;******************************************** ; The actual code of our boot loading process ;******************************************** start: ; Adjust segment registers mov ax,LOADSEG ; BIOS loads bootsector at segment LOADSEG. We set ds accordingly mov ds,ax ; so we don't have to add LOADSEG<<4 to all our data addresses ; Quickly save what drive we booted from mov [bootdrv], dl ; Set up a stack mov ax,STACKSEG mov ss,ax mov sp,0xFFFF ; Let's use the whole segment. Why not? We can :) ; Display our startup message mov si,bootmsg call message ; Check for 386 processor mov si,processormsg ; Tells the user we're checking if the CPU is 386 or + call message call detect_cpu cmp al,0x00 jnz .go_on mov si,need386 ; No 386? Stops everything call message jmp $ .go_on: mov si,found386 ; 386 found: say ok call message ; Prepare protect mode - a dirty protect mode without interrupts mov si,alertpm ; Alerts user call message lgdt [gdt_reg] ; Loads GDT cli ; Without interrupt handlers this is definitely a must :) mov eax,cr0 ; Loads cr0 register or al,1 ; Sets to 1 its first bit (faaast, only 2 clocks) mov cr0,eax ; Puts it back in cr0 - NOW WE'RE IN PROTECT MODE jmp code:pm_start ; Long jump empties instruction prefetch queue ; and loads cs register for PM [BITS 32] ;******************************************* ; procedures for protect mode section ; (32 bit default operand size, BIOS not available) ;******************************************* pm_messageraw: ; Dump ds:si to es:di. No nifty feature like ; newline, scrolling... ; Modifies: ax, di, zero flag. mov ah,EMPH ; Attribute (black on green) .mloop: lodsb ; Loads byte at ds:si into al or al,al ; Tests if character is 0 (end) jz .done stosw jmp .mloop .done: ret ;******************************************** ; The actual code of our boot loading process ; (follows protect mode section) ;******************************************** pm_start: ; restore ss and ds, sets es to video segment mov ax,stack ; Stack segment mov ss,ax mov ax,data ; Data segment mov ds,ax mov ax,video ; Video segment mov es,ax ; prints hello world mov di,0 ; video memory offset (row=0, col=0) mov si,hwpm call pm_messageraw jmp $ ; hangs ;******************************************* ; Epilogue ;******************************************* ; pads with zero to sector size - 2 times 510-($-$$) db 0 dw 0xAA55 ; marks as boot sector ; pads with zeros to floppy size - use it when necessary ; (it isn't with Bochs and VMware) ;times 1474560-($-$$) db 0